hob/settings: add a scroll bar for the box with mirrors
[bitbake.git] / lib / bb / ui / crumbs / hig.py
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 glob
24 import glib
25 import gtk
26 import gobject
27 import hashlib
28 import os
29 import re
30 import shlex
31 import subprocess
32 import tempfile
33 from bb.ui.crumbs.hobcolor import HobColors
34 from bb.ui.crumbs.hobwidget import hic, HobViewTable, HobInfoButton, HobButton, HobAltButton, HobIconChecker
35 from bb.ui.crumbs.progressbar import HobProgressBar
36 import bb.ui.crumbs.utils
37 import bb.process
38
39 """
40 The following are convenience classes for implementing GNOME HIG compliant
41 BitBake GUI's
42 In summary: spacing = 12px, border-width = 6px
43 """
44
45
46 class SettingsUIHelper():
47
48     def gen_label_widget(self, content):
49         label = gtk.Label()
50         label.set_alignment(0, 0)
51         label.set_markup(content)
52         label.show()
53         return label
54
55     def gen_label_info_widget(self, content, tooltip):
56         table = gtk.Table(1, 10, False)
57         label = self.gen_label_widget(content)
58         info = HobInfoButton(tooltip, self)
59         table.attach(label, 0, 1, 0, 1, xoptions=gtk.FILL)
60         table.attach(info, 1, 2, 0, 1, xoptions=gtk.FILL, xpadding=10)
61         return table
62
63     def gen_spinner_widget(self, content, lower, upper, tooltip=""):
64         hbox = gtk.HBox(False, 12)
65         adjust = gtk.Adjustment(value=content, lower=lower, upper=upper, step_incr=1)
66         spinner = gtk.SpinButton(adjustment=adjust, climb_rate=1, digits=0)
67
68         spinner.set_value(content)
69         hbox.pack_start(spinner, expand=False, fill=False)
70
71         info = HobInfoButton(tooltip, self)
72         hbox.pack_start(info, expand=False, fill=False)
73
74         hbox.show_all()
75         return hbox, spinner
76
77     def gen_combo_widget(self, curr_item, all_item, tooltip=""):
78         hbox = gtk.HBox(False, 12)
79         combo = gtk.combo_box_new_text()
80         hbox.pack_start(combo, expand=False, fill=False)
81
82         index = 0
83         for item in all_item or []:
84             combo.append_text(item)
85             if item == curr_item:
86                 combo.set_active(index)
87             index += 1
88
89         info = HobInfoButton(tooltip, self)
90         hbox.pack_start(info, expand=False, fill=False)
91
92         hbox.show_all()
93         return hbox, combo
94
95     def entry_widget_select_path_cb(self, action, parent, entry):
96         dialog = gtk.FileChooserDialog("", parent,
97                                        gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
98         text = entry.get_text()
99         dialog.set_current_folder(text if len(text) > 0 else os.getcwd())
100         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
101         HobAltButton.style_button(button)
102         button = dialog.add_button("Open", gtk.RESPONSE_YES)
103         HobButton.style_button(button)
104         response = dialog.run()
105         if response == gtk.RESPONSE_YES:
106             path = dialog.get_filename()
107             entry.set_text(path)
108
109         dialog.destroy()
110
111     def gen_entry_widget(self, content, parent, tooltip="", need_button=True):
112         hbox = gtk.HBox(False, 12)
113         entry = gtk.Entry()
114         entry.set_text(content)
115         entry.set_size_request(350,30)
116
117         if need_button:
118             table = gtk.Table(1, 10, False)
119             hbox.pack_start(table, expand=True, fill=True)
120             table.attach(entry, 0, 9, 0, 1, xoptions=gtk.SHRINK)
121             image = gtk.Image()
122             image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON)
123             open_button = gtk.Button()
124             open_button.set_image(image)
125             open_button.connect("clicked", self.entry_widget_select_path_cb, parent, entry)
126             table.attach(open_button, 9, 10, 0, 1, xoptions=gtk.SHRINK)
127         else:
128             hbox.pack_start(entry, expand=True, fill=True)
129
130         if tooltip != "":
131             info = HobInfoButton(tooltip, self)
132             hbox.pack_start(info, expand=False, fill=False)
133
134         hbox.show_all()
135         return hbox, entry
136
137     def gen_mirror_entry_widget(self, content, index, match_content=""):
138         hbox = gtk.HBox(False)
139         entry = gtk.Entry()
140         content = content[:-2]
141         entry.set_text(content)
142         entry.set_size_request(350,30)
143
144         entry_match = gtk.Entry()
145         entry_match.set_text(match_content)
146         entry_match.set_size_request(100,30)
147
148         table = gtk.Table(2, 5, False)
149         table.set_row_spacings(12)
150         table.set_col_spacings(6)
151         hbox.pack_start(table, expand=True, fill=True)
152
153         label_configuration = gtk.Label("Configuration")
154         label_configuration.set_alignment(0.0,0.5)
155         label_mirror_url = gtk.Label("Mirror URL")
156         label_mirror_url.set_alignment(0.0,0.5)
157         label_match = gtk.Label("Match")
158         label_match.set_alignment(0.0,0.5)
159         label_replace_with = gtk.Label("Replace with")
160         label_replace_with.set_alignment(0.0,0.5)
161
162         combo = gtk.combo_box_new_text()
163         combo.append_text("Standard")
164         combo.append_text("Custom")
165         if match_content == "":
166             combo.set_active(0)
167         else:
168             combo.set_active(1)
169         combo.connect("changed", self.on_combo_changed, index)
170         combo.set_size_request(100,30)
171
172         delete_button = HobAltButton("Delete")
173         delete_button.connect("clicked", self.delete_cb, index, entry)
174         if content == "" and index == 0  and len(self.sstatemirrors_list) == 1:
175             delete_button.set_sensitive(False)
176         delete_button.set_size_request(100, 30)
177
178         entry_match.connect("changed", self.insert_entry_match_cb, index)
179         entry.connect("changed", self.insert_entry_cb, index, delete_button)
180
181         if match_content == "":
182             table.attach(label_configuration, 1, 2, 0, 1, xoptions=gtk.SHRINK|gtk.FILL)
183             table.attach(label_mirror_url, 2, 3, 0, 1, xoptions=gtk.SHRINK|gtk.FILL)
184             table.attach(combo, 1, 2, 1, 2, xoptions=gtk.SHRINK)
185             table.attach(entry, 2, 3, 1, 2, xoptions=gtk.SHRINK)
186             table.attach(delete_button, 3, 4, 1, 2, xoptions=gtk.SHRINK)
187         else:
188             table.attach(label_configuration, 1, 2, 0, 1, xoptions=gtk.SHRINK|gtk.FILL)
189             table.attach(label_match, 2, 3, 0, 1, xoptions=gtk.SHRINK|gtk.FILL)
190             table.attach(label_replace_with, 3, 4, 0, 1, xoptions=gtk.SHRINK|gtk.FILL)
191             table.attach(combo, 1, 2, 1, 2, xoptions=gtk.SHRINK)
192             table.attach(entry_match, 2, 3, 1, 2, xoptions=gtk.SHRINK)
193             table.attach(entry, 3, 4, 1, 2, xoptions=gtk.SHRINK)
194             table.attach(delete_button, 4, 5, 1, 2, xoptions=gtk.SHRINK)
195
196         hbox.show_all()
197         return hbox
198
199     def insert_entry_match_cb(self, entry_match, index):
200         self.sstatemirrors_list[index][2] = entry_match.get_text()
201
202     def insert_entry_cb(self, entry, index, button):
203         self.sstatemirrors_list[index][1] = entry.get_text()
204         if entry.get_text() == "" and index == 0:
205             button.set_sensitive(False)
206         else:
207             button.set_sensitive(True)
208
209     def on_combo_changed(self, combo, index):
210         if combo.get_active_text() == "Standard":
211             self.sstatemirrors_list[index][0] = 0
212             self.sstatemirrors_list[index][2] = "file://(.*)"
213         else:
214             self.sstatemirrors_list[index][0] = 1
215         self.refresh_shared_state_page()
216
217     def delete_cb(self, button, index, entry):
218         if index == 0 and len(self.sstatemirrors_list)==1:
219             entry.set_text("")
220         else:
221             self.sstatemirrors_list.pop(index)
222             self.refresh_shared_state_page()
223
224     def add_mirror(self, button):
225         tooltip = "Select the pre-built mirror that will speed your build"
226         index = len(self.sstatemirrors_list)
227         sm_list = [0, "", "file://(.*)"]
228         self.sstatemirrors_list.append(sm_list)
229         self.refresh_shared_state_page()
230
231 #
232 # CrumbsDialog
233 #
234 class CrumbsDialog(gtk.Dialog):
235     """
236     A GNOME HIG compliant dialog widget.
237     Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
238     """
239     def __init__(self, title="", parent=None, flags=0, buttons=None):
240         super(CrumbsDialog, self).__init__(title, parent, flags, buttons)
241
242         self.set_property("has-separator", False) # note: deprecated in 2.22
243
244         self.set_border_width(6)
245         self.vbox.set_property("spacing", 12)
246         self.action_area.set_property("spacing", 12)
247         self.action_area.set_property("border-width", 6)
248
249 class CrumbsMessageDialog(CrumbsDialog):
250     """
251     A GNOME HIG compliant dialog widget.
252     Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
253     """
254     def __init__(self, parent=None, label="", icon=gtk.STOCK_INFO, msg=""):
255         super(CrumbsMessageDialog, self).__init__("", parent, gtk.DIALOG_MODAL)
256
257         self.set_border_width(6)
258         self.vbox.set_property("spacing", 12)
259         self.action_area.set_property("spacing", 12)
260         self.action_area.set_property("border-width", 6)
261
262         first_column = gtk.HBox(spacing=12)
263         first_column.set_property("border-width", 6)
264         first_column.show()
265         self.vbox.add(first_column)
266
267         self.icon = gtk.Image()
268         # We have our own Info icon which should be used in preference of the stock icon
269         self.icon_chk = HobIconChecker()
270         self.icon.set_from_stock(self.icon_chk.check_stock_icon(icon), gtk.ICON_SIZE_DIALOG)
271         self.icon.set_property("yalign", 0.00)
272         self.icon.show()
273         first_column.pack_start(self.icon, expand=False, fill=True, padding=0)
274         
275         if 0 <= len(msg) < 200:
276             lbl = label + "%s" % glib.markup_escape_text(msg)
277             self.label_short = gtk.Label()
278             self.label_short.set_use_markup(True)
279             self.label_short.set_line_wrap(True)
280             self.label_short.set_markup(lbl)
281             self.label_short.set_property("yalign", 0.00)
282             self.label_short.show()
283             first_column.add(self.label_short)
284         else:
285             second_row = gtk.VBox(spacing=12)
286             second_row.set_property("border-width", 6)
287             self.label_long = gtk.Label()
288             self.label_long.set_use_markup(True)
289             self.label_long.set_line_wrap(True)
290             self.label_long.set_markup(label)
291             self.label_long.set_alignment(0.0, 0.0)
292             second_row.pack_start(self.label_long, expand=False, fill=False, padding=0)
293             self.label_long.show()
294             self.textWindow = gtk.ScrolledWindow()
295             self.textWindow.set_shadow_type(gtk.SHADOW_IN)
296             self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
297             self.msgView = gtk.TextView()
298             self.msgView.set_editable(False)
299             self.msgView.set_wrap_mode(gtk.WRAP_WORD)
300             self.msgView.set_cursor_visible(False)
301             self.msgView.set_size_request(300, 300)
302             self.buf = gtk.TextBuffer()
303             self.buf.set_text(msg)
304             self.msgView.set_buffer(self.buf)
305             self.textWindow.add(self.msgView)
306             self.msgView.show()
307             second_row.add(self.textWindow)
308             self.textWindow.show()
309             first_column.add(second_row)
310             second_row.show()
311
312 #
313 # SimpleSettings Dialog
314 #
315 class SimpleSettingsDialog (CrumbsDialog, SettingsUIHelper):
316
317     (BUILD_ENV_PAGE_ID,
318      SHARED_STATE_PAGE_ID,
319      PROXIES_PAGE_ID,
320      OTHERS_PAGE_ID) = range(4)
321
322     (TEST_NETWORK_NONE,
323      TEST_NETWORK_INITIAL,
324      TEST_NETWORK_RUNNING,
325      TEST_NETWORK_PASSED,
326      TEST_NETWORK_FAILED,
327      TEST_NETWORK_CANCELED) = range(6)
328
329     def __init__(self, title, configuration, all_image_types,
330             all_package_formats, all_distros, all_sdk_machines,
331             max_threads, parent, flags, handler, buttons=None):
332         super(SimpleSettingsDialog, self).__init__(title, parent, flags, buttons)
333
334         # class members from other objects
335         # bitbake settings from Builder.Configuration
336         self.configuration = configuration
337         self.image_types = all_image_types
338         self.all_package_formats = all_package_formats
339         self.all_distros = all_distros
340         self.all_sdk_machines = all_sdk_machines
341         self.max_threads = max_threads
342
343         # class members for internal use
344         self.dldir_text = None
345         self.sstatedir_text = None
346         self.sstatemirrors_list = []
347         self.sstatemirrors_changed = 0
348         self.bb_spinner = None
349         self.pmake_spinner = None
350         self.rootfs_size_spinner = None
351         self.extra_size_spinner = None
352         self.gplv3_checkbox = None
353         self.toolchain_checkbox = None
354         self.setting_store = None
355         self.image_types_checkbuttons = {}
356
357         self.md5 = self.config_md5()
358         self.proxy_md5 = self.config_proxy_md5()
359         self.settings_changed = False
360         self.proxy_settings_changed = False
361         self.handler = handler
362         self.proxy_test_ran = False
363
364         # create visual elements on the dialog
365         self.create_visual_elements()
366         self.connect("response", self.response_cb)
367
368     def _get_sorted_value(self, var):
369         return " ".join(sorted(str(var).split())) + "\n"
370
371     def config_proxy_md5(self):
372         data = ("ENABLE_PROXY: "         + self._get_sorted_value(self.configuration.enable_proxy))
373         if self.configuration.enable_proxy:
374             for protocol in self.configuration.proxies.keys():
375                 data += (protocol + ": " + self._get_sorted_value(self.configuration.combine_proxy(protocol)))
376         return hashlib.md5(data).hexdigest()
377
378     def config_md5(self):
379         data = ""
380         for key in self.configuration.extra_setting.keys():
381             data += (key + ": " + self._get_sorted_value(self.configuration.extra_setting[key]))
382         return hashlib.md5(data).hexdigest()
383
384     def details_cb(self, button, parent, protocol):
385         dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details",
386             user = self.configuration.proxies[protocol][1],
387             passwd = self.configuration.proxies[protocol][2],
388             parent = parent,
389             flags = gtk.DIALOG_MODAL
390                     | gtk.DIALOG_DESTROY_WITH_PARENT
391                     | gtk.DIALOG_NO_SEPARATOR)
392         dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
393         response = dialog.run()
394         if response == gtk.RESPONSE_OK:
395             self.configuration.proxies[protocol][1] = dialog.user
396             self.configuration.proxies[protocol][2] = dialog.passwd
397             self.refresh_proxy_components()
398         dialog.destroy()
399
400     def gen_proxy_entry_widget(self, protocol, parent, need_button=True, line=0):
401         label = gtk.Label(protocol.upper() + " proxy")
402         self.proxy_table.attach(label, 0, 1, line, line+1, xpadding=24)
403
404         proxy_entry = gtk.Entry()
405         proxy_entry.set_size_request(300, -1)
406         self.proxy_table.attach(proxy_entry, 1, 2, line, line+1, ypadding=4)
407
408         self.proxy_table.attach(gtk.Label(":"), 2, 3, line, line+1, xpadding=12, ypadding=4)
409
410         port_entry = gtk.Entry()
411         port_entry.set_size_request(60, -1)
412         self.proxy_table.attach(port_entry, 3, 4, line, line+1, ypadding=4)
413
414         details_button = HobAltButton("Details")
415         details_button.connect("clicked", self.details_cb, parent, protocol)
416         self.proxy_table.attach(details_button, 4, 5, line, line+1, xpadding=4, yoptions=gtk.EXPAND)
417
418         return proxy_entry, port_entry, details_button
419
420     def refresh_proxy_components(self):
421         self.same_checkbox.set_sensitive(self.configuration.enable_proxy)
422
423         self.http_proxy.set_text(self.configuration.combine_host_only("http"))
424         self.http_proxy.set_editable(self.configuration.enable_proxy)
425         self.http_proxy.set_sensitive(self.configuration.enable_proxy)
426         self.http_proxy_port.set_text(self.configuration.combine_port_only("http"))
427         self.http_proxy_port.set_editable(self.configuration.enable_proxy)
428         self.http_proxy_port.set_sensitive(self.configuration.enable_proxy)
429         self.http_proxy_details.set_sensitive(self.configuration.enable_proxy)
430
431         self.https_proxy.set_text(self.configuration.combine_host_only("https"))
432         self.https_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
433         self.https_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
434         self.https_proxy_port.set_text(self.configuration.combine_port_only("https"))
435         self.https_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
436         self.https_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
437         self.https_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
438
439         self.ftp_proxy.set_text(self.configuration.combine_host_only("ftp"))
440         self.ftp_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
441         self.ftp_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
442         self.ftp_proxy_port.set_text(self.configuration.combine_port_only("ftp"))
443         self.ftp_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
444         self.ftp_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
445         self.ftp_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
446
447         self.git_proxy.set_text(self.configuration.combine_host_only("git"))
448         self.git_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
449         self.git_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
450         self.git_proxy_port.set_text(self.configuration.combine_port_only("git"))
451         self.git_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
452         self.git_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
453         self.git_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
454
455         self.cvs_proxy.set_text(self.configuration.combine_host_only("cvs"))
456         self.cvs_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
457         self.cvs_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
458         self.cvs_proxy_port.set_text(self.configuration.combine_port_only("cvs"))
459         self.cvs_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
460         self.cvs_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
461         self.cvs_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
462
463         if self.configuration.same_proxy:
464             if self.http_proxy.get_text():
465                 [w.set_text(self.http_proxy.get_text()) for w in self.same_proxy_addresses]
466             if self.http_proxy_port.get_text():
467                 [w.set_text(self.http_proxy_port.get_text()) for w in self.same_proxy_ports]
468
469     def proxy_checkbox_toggled_cb(self, button):
470         self.configuration.enable_proxy = self.proxy_checkbox.get_active()
471         if not self.configuration.enable_proxy:
472             self.configuration.same_proxy = False
473             self.same_checkbox.set_active(self.configuration.same_proxy)
474         self.save_proxy_data()
475         self.refresh_proxy_components()
476
477     def same_checkbox_toggled_cb(self, button):
478         self.configuration.same_proxy = self.same_checkbox.get_active()
479         self.save_proxy_data()
480         self.refresh_proxy_components()
481
482     def save_proxy_data(self):
483         self.configuration.split_proxy("http", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
484         if self.configuration.same_proxy:
485             self.configuration.split_proxy("https", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
486             self.configuration.split_proxy("ftp", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
487             self.configuration.split_proxy("git", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
488             self.configuration.split_proxy("cvs", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
489         else:
490             self.configuration.split_proxy("https", self.https_proxy.get_text() + ":" + self.https_proxy_port.get_text())
491             self.configuration.split_proxy("ftp", self.ftp_proxy.get_text() + ":" + self.ftp_proxy_port.get_text())
492             self.configuration.split_proxy("git", self.git_proxy.get_text() + ":" + self.git_proxy_port.get_text())
493             self.configuration.split_proxy("cvs", self.cvs_proxy.get_text() + ":" + self.cvs_proxy_port.get_text())       
494
495     def response_cb(self, dialog, response_id):
496         if response_id == gtk.RESPONSE_YES:
497             # Check that all proxy entries have a corresponding port
498             for proxy, port in zip(self.all_proxy_addresses, self.all_proxy_ports):
499                 if proxy.get_text() and not port.get_text():
500                     lbl = "<b>Enter all port numbers</b>\n\n"
501                     msg = "Proxy servers require a port number. Please make sure you have entered a port number for each proxy server."
502                     dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING, msg)
503                     button = dialog.add_button("Close", gtk.RESPONSE_OK)
504                     HobButton.style_button(button)
505                     response = dialog.run()
506                     dialog.destroy()
507                     self.emit_stop_by_name("response")
508                     return
509
510         self.configuration.dldir = self.dldir_text.get_text()
511         self.configuration.sstatedir = self.sstatedir_text.get_text()
512         self.configuration.sstatemirror = ""
513         for mirror in self.sstatemirrors_list:
514             if mirror[1] != "":
515                 if mirror[1].endswith("\\1"):
516                     smirror = mirror[2] + " " + mirror[1] + " \\n "
517                 else:
518                     smirror = mirror[2] + " " + mirror[1] + "\\1 \\n "
519                 self.configuration.sstatemirror += smirror
520         self.configuration.bbthread = self.bb_spinner.get_value_as_int()
521         self.configuration.pmake = self.pmake_spinner.get_value_as_int()
522         self.save_proxy_data()
523         self.configuration.extra_setting = {}
524         it = self.setting_store.get_iter_first()
525         while it:
526             key = self.setting_store.get_value(it, 0)
527             value = self.setting_store.get_value(it, 1)
528             self.configuration.extra_setting[key] = value
529             it = self.setting_store.iter_next(it)
530
531         md5 = self.config_md5()
532         self.settings_changed = (self.md5 != md5)
533         self.proxy_settings_changed = (self.proxy_md5 != self.config_proxy_md5())
534
535     def create_build_environment_page(self):
536         advanced_vbox = gtk.VBox(False, 6)
537         advanced_vbox.set_border_width(6)
538
539         advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Parallel threads</span>'), expand=False, fill=False)
540         sub_vbox = gtk.VBox(False, 6)
541         advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
542         label = self.gen_label_widget("BitBake parallel threads")
543         tooltip = "Sets the number of threads that BitBake tasks can simultaneously run. See the <a href=\""
544         tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
545         tooltip += "poky-ref-manual.html#var-BB_NUMBER_THREADS\">Poky reference manual</a> for information"
546         bbthread_widget, self.bb_spinner = self.gen_spinner_widget(self.configuration.bbthread, 1, self.max_threads, tooltip)
547         sub_vbox.pack_start(label, expand=False, fill=False)
548         sub_vbox.pack_start(bbthread_widget, expand=False, fill=False)
549
550         sub_vbox = gtk.VBox(False, 6)
551         advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
552         label = self.gen_label_widget("Make parallel threads")
553         tooltip = "Sets the maximum number of threads the host can use during the build. See the <a href=\""
554         tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
555         tooltip += "poky-ref-manual.html#var-PARALLEL_MAKE\">Poky reference manual</a> for information"
556         pmake_widget, self.pmake_spinner = self.gen_spinner_widget(self.configuration.pmake, 1, self.max_threads, tooltip)
557         sub_vbox.pack_start(label, expand=False, fill=False)
558         sub_vbox.pack_start(pmake_widget, expand=False, fill=False)
559
560         advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Downloaded source code</span>'), expand=False, fill=False)
561         sub_vbox = gtk.VBox(False, 6)
562         advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
563         label = self.gen_label_widget("Downloads directory")
564         tooltip = "Select a folder that caches the upstream project source code"
565         dldir_widget, self.dldir_text = self.gen_entry_widget(self.configuration.dldir, self, tooltip)
566         sub_vbox.pack_start(label, expand=False, fill=False)
567         sub_vbox.pack_start(dldir_widget, expand=False, fill=False)
568
569         return advanced_vbox
570
571     def create_shared_state_page(self):
572         advanced_vbox = gtk.VBox(False)
573         advanced_vbox.set_border_width(12)
574
575         sub_vbox = gtk.VBox(False)
576         advanced_vbox.pack_start(sub_vbox, expand=False, fill=False, padding=24)
577         content = "<span>Shared state directory</span>"
578         tooltip = "Select a folder that caches your prebuilt results"
579         label = self.gen_label_info_widget(content, tooltip)
580         sstatedir_widget, self.sstatedir_text = self.gen_entry_widget(self.configuration.sstatedir, self)
581         sub_vbox.pack_start(label, expand=False, fill=False)
582         sub_vbox.pack_start(sstatedir_widget, expand=False, fill=False, padding=12)
583
584         content = "<span weight=\"bold\">Shared state mirrors</span>"
585         tooltip = "URLs pointing to pre-built mirrors that will speed your build. "
586         tooltip += "Select the \'Standard\' configuration if the structure of your "
587         tooltip += "mirror replicates the structure of your local shared state directory. "
588         tooltip += "For more information on shared state mirrors, check the <a href=\""
589         tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
590         tooltip += "poky-ref-manual.html#shared-state\">Yocto Project Reference Manual</a>."
591         table = self.gen_label_info_widget(content, tooltip)
592         advanced_vbox.pack_start(table, expand=False, fill=False)
593
594         sub_vbox = gtk.VBox(False)
595         scroll = gtk.ScrolledWindow()
596         scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
597         scroll.add_with_viewport(sub_vbox)
598         scroll.connect('size-allocate', self.scroll_changed)
599         advanced_vbox.pack_start(scroll, gtk.TRUE, gtk.TRUE, 0)
600         searched_string = "file://"
601
602         if self.sstatemirrors_changed == 0:
603             self.sstatemirrors_changed = 1
604             sstatemirrors = self.configuration.sstatemirror
605             if sstatemirrors == "":
606                 sm_list = [ 0, "", "file://(.*)"]
607                 self.sstatemirrors_list.append(sm_list)
608             else:
609                 while sstatemirrors.find(searched_string) != -1:
610                     if sstatemirrors.find(searched_string,1) != -1:
611                         sstatemirror = sstatemirrors[:sstatemirrors.find(searched_string,1)]
612                         sstatemirrors = sstatemirrors[sstatemirrors.find(searched_string,1):]
613                     else:
614                         sstatemirror = sstatemirrors
615                         sstatemirrors = sstatemirrors[1:]
616
617                     sstatemirror_fields = [x for x in sstatemirror.split(' ') if x.strip()]
618                     if sstatemirror_fields[0] == "file://(.*)":
619                         sm_list = [ 0, sstatemirror_fields[1], "file://(.*)"]
620                     else:
621                         sm_list = [ 1, sstatemirror_fields[1], sstatemirror_fields[0]]
622                     self.sstatemirrors_list.append(sm_list)
623
624         index = 0
625         for mirror in self.sstatemirrors_list:
626             if mirror[0] == 0:
627                 sstatemirror_widget = self.gen_mirror_entry_widget(mirror[1], index)
628             else:
629                 sstatemirror_widget = self.gen_mirror_entry_widget(mirror[1], index, mirror[2])
630             sub_vbox.pack_start(sstatemirror_widget, expand=False, fill=False, padding=9)
631             index += 1
632
633         table = gtk.Table(1, 1, False)
634         table.set_col_spacings(6)
635         add_mirror_button = HobAltButton("Add another mirror")
636         add_mirror_button.connect("clicked", self.add_mirror)
637         add_mirror_button.set_size_request(150,30)
638         table.attach(add_mirror_button, 1, 2, 0, 1, xoptions=gtk.SHRINK)
639         advanced_vbox.pack_start(table, expand=False, fill=False, padding=9)
640
641         return advanced_vbox
642
643     def refresh_shared_state_page(self):
644         page_num = self.nb.get_current_page()
645         self.nb.remove_page(page_num);
646         self.nb.insert_page(self.create_shared_state_page(), gtk.Label("Shared state"),page_num)
647         self.show_all()
648         self.nb.set_current_page(page_num)
649
650     def test_proxy_ended(self, passed):
651         self.proxy_test_running = False
652         self.set_test_proxy_state(self.TEST_NETWORK_PASSED if passed else self.TEST_NETWORK_FAILED)
653         self.set_sensitive(True)
654         self.refresh_proxy_components()
655
656     def timer_func(self):
657         self.test_proxy_progress.pulse()
658         return self.proxy_test_running
659
660     def test_network_button_cb(self, b):
661         self.set_test_proxy_state(self.TEST_NETWORK_RUNNING)
662         self.set_sensitive(False)
663         self.save_proxy_data()
664         if self.configuration.enable_proxy == True:
665             self.handler.set_http_proxy(self.configuration.combine_proxy("http"))
666             self.handler.set_https_proxy(self.configuration.combine_proxy("https"))
667             self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp"))
668             self.handler.set_git_proxy(self.configuration.combine_host_only("git"), self.configuration.combine_port_only("git"))
669             self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs"))
670         elif self.configuration.enable_proxy == False:
671             self.handler.set_http_proxy("")
672             self.handler.set_https_proxy("")
673             self.handler.set_ftp_proxy("")
674             self.handler.set_git_proxy("", "")
675             self.handler.set_cvs_proxy("", "")
676         self.proxy_test_ran = True
677         self.proxy_test_running = True
678         gobject.timeout_add(100, self.timer_func)
679         self.handler.trigger_network_test()
680
681     def test_proxy_focus_event(self, w, direction):
682         if self.test_proxy_state in [self.TEST_NETWORK_PASSED, self.TEST_NETWORK_FAILED]:
683             self.set_test_proxy_state(self.TEST_NETWORK_INITIAL)
684         return False
685
686     def http_proxy_changed(self, e):
687         if not self.configuration.same_proxy:
688             return
689         if e == self.http_proxy:
690             [w.set_text(self.http_proxy.get_text()) for w in self.same_proxy_addresses]
691         else:
692             [w.set_text(self.http_proxy_port.get_text()) for w in self.same_proxy_ports]
693
694     def proxy_address_focus_out_event(self, w, direction):
695         text = w.get_text()
696         if not text:
697             return False
698         if text.find("//") == -1:
699             w.set_text("http://" + text)
700         return False
701
702     def set_test_proxy_state(self, state):
703         if self.test_proxy_state == state:
704             return
705         [self.proxy_table.remove(w) for w in self.test_gui_elements]
706         if state == self.TEST_NETWORK_INITIAL:
707             self.proxy_table.attach(self.test_network_button, 1, 2, 5, 6)
708             self.test_network_button.show()
709         elif state == self.TEST_NETWORK_RUNNING:
710             self.test_proxy_progress.set_rcstyle("running")
711             self.test_proxy_progress.set_text("Testing network configuration")
712             self.proxy_table.attach(self.test_proxy_progress, 0, 5, 5, 6, xpadding=4)
713             self.test_proxy_progress.show()
714         else: # passed or failed
715             self.dummy_progress.update(1.0)
716             if state == self.TEST_NETWORK_PASSED:
717                 self.dummy_progress.set_text("Your network is properly configured")
718                 self.dummy_progress.set_rcstyle("running")
719             else:
720                 self.dummy_progress.set_text("Network test failed")
721                 self.dummy_progress.set_rcstyle("fail")
722             self.proxy_table.attach(self.dummy_progress, 0, 4, 5, 6)
723             self.proxy_table.attach(self.retest_network_button, 4, 5, 5, 6, xpadding=4)
724             self.dummy_progress.show()
725             self.retest_network_button.show()
726         self.test_proxy_state = state
727
728     def create_network_page(self):
729         advanced_vbox = gtk.VBox(False, 6)
730         advanced_vbox.set_border_width(6)
731         self.same_proxy_addresses = []
732         self.same_proxy_ports = []
733         self.all_proxy_ports = []
734         self.all_proxy_addresses = []
735
736         sub_vbox = gtk.VBox(False, 6)
737         advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
738         label = self.gen_label_widget("<span weight=\"bold\">Set the proxies used when fetching source code</span>")
739         tooltip = "Set the proxies used when fetching source code.  A blank field uses a direct internet connection."
740         info = HobInfoButton(tooltip, self)
741         hbox = gtk.HBox(False, 12)
742         hbox.pack_start(label, expand=True, fill=True)
743         hbox.pack_start(info, expand=False, fill=False)
744         sub_vbox.pack_start(hbox, expand=False, fill=False)
745
746         proxy_test_focus = []
747         self.direct_checkbox = gtk.RadioButton(None, "Direct network connection")
748         proxy_test_focus.append(self.direct_checkbox)
749         self.direct_checkbox.set_tooltip_text("Check this box to use a direct internet connection with no proxy")
750         self.direct_checkbox.set_active(not self.configuration.enable_proxy)
751         sub_vbox.pack_start(self.direct_checkbox, expand=False, fill=False)
752
753         self.proxy_checkbox = gtk.RadioButton(self.direct_checkbox, "Manual proxy configuration")
754         proxy_test_focus.append(self.proxy_checkbox)
755         self.proxy_checkbox.set_tooltip_text("Check this box to manually set up a specific proxy")
756         self.proxy_checkbox.set_active(self.configuration.enable_proxy)
757         sub_vbox.pack_start(self.proxy_checkbox, expand=False, fill=False)
758
759         self.same_checkbox = gtk.CheckButton("Use the HTTP proxy for all protocols")
760         proxy_test_focus.append(self.same_checkbox)
761         self.same_checkbox.set_tooltip_text("Check this box to use the HTTP proxy for all five proxies")
762         self.same_checkbox.set_active(self.configuration.same_proxy)
763         hbox = gtk.HBox(False, 12)
764         hbox.pack_start(self.same_checkbox, expand=False, fill=False, padding=24)
765         sub_vbox.pack_start(hbox, expand=False, fill=False)
766
767         self.proxy_table = gtk.Table(6, 5, False)
768         self.http_proxy, self.http_proxy_port, self.http_proxy_details = self.gen_proxy_entry_widget(
769             "http", self, True, 0)
770         proxy_test_focus +=[self.http_proxy, self.http_proxy_port]
771         self.http_proxy.connect("changed", self.http_proxy_changed)
772         self.http_proxy_port.connect("changed", self.http_proxy_changed)
773
774         self.https_proxy, self.https_proxy_port, self.https_proxy_details = self.gen_proxy_entry_widget(
775             "https", self, True, 1)
776         proxy_test_focus += [self.https_proxy, self.https_proxy_port]
777         self.same_proxy_addresses.append(self.https_proxy)
778         self.same_proxy_ports.append(self.https_proxy_port)
779
780         self.ftp_proxy, self.ftp_proxy_port, self.ftp_proxy_details = self.gen_proxy_entry_widget(
781             "ftp", self, True, 2)
782         proxy_test_focus += [self.ftp_proxy, self.ftp_proxy_port]
783         self.same_proxy_addresses.append(self.ftp_proxy)
784         self.same_proxy_ports.append(self.ftp_proxy_port)
785
786         self.git_proxy, self.git_proxy_port, self.git_proxy_details = self.gen_proxy_entry_widget(
787             "git", self, True, 3)
788         proxy_test_focus += [self.git_proxy, self.git_proxy_port]
789         self.same_proxy_addresses.append(self.git_proxy)
790         self.same_proxy_ports.append(self.git_proxy_port)
791
792         self.cvs_proxy, self.cvs_proxy_port, self.cvs_proxy_details = self.gen_proxy_entry_widget(
793             "cvs", self, True, 4)
794         proxy_test_focus += [self.cvs_proxy, self.cvs_proxy_port]
795         self.same_proxy_addresses.append(self.cvs_proxy)
796         self.same_proxy_ports.append(self.cvs_proxy_port)
797         self.all_proxy_ports = self.same_proxy_ports + [self.http_proxy_port]
798         self.all_proxy_addresses = self.same_proxy_addresses + [self.http_proxy]
799         sub_vbox.pack_start(self.proxy_table, expand=False, fill=False)
800         self.proxy_table.show_all()
801
802         # Create the graphical elements for the network test feature, but don't display them yet
803         self.test_network_button = HobAltButton("Test network configuration")
804         self.test_network_button.connect("clicked", self.test_network_button_cb)
805         self.test_proxy_progress = HobProgressBar()
806         self.dummy_progress = HobProgressBar()
807         self.retest_network_button = HobAltButton("Retest")
808         self.retest_network_button.connect("clicked", self.test_network_button_cb)
809         self.test_gui_elements = [self.test_network_button, self.test_proxy_progress, self.dummy_progress, self.retest_network_button]
810         # Initialize the network tester
811         self.test_proxy_state = self.TEST_NETWORK_NONE
812         self.set_test_proxy_state(self.TEST_NETWORK_INITIAL)
813         self.proxy_test_passed_id = self.handler.connect("network-passed", lambda h:self.test_proxy_ended(True))
814         self.proxy_test_failed_id = self.handler.connect("network-failed", lambda h:self.test_proxy_ended(False))
815         [w.connect("focus-in-event", self.test_proxy_focus_event) for w in proxy_test_focus]
816         [w.connect("focus-out-event", self.proxy_address_focus_out_event) for w in self.all_proxy_addresses]
817
818         self.direct_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb)
819         self.proxy_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb)
820         self.same_checkbox.connect("toggled", self.same_checkbox_toggled_cb)
821
822         self.refresh_proxy_components()
823         return advanced_vbox
824
825     def switch_to_page(self, page_id):
826         self.nb.set_current_page(page_id)
827
828     def details_cb(self, button, parent, protocol):
829         dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details",
830             user = self.configuration.proxies[protocol][1],
831             passwd = self.configuration.proxies[protocol][2],
832             parent = parent,
833             flags = gtk.DIALOG_MODAL
834                     | gtk.DIALOG_DESTROY_WITH_PARENT
835                     | gtk.DIALOG_NO_SEPARATOR)
836         dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
837         response = dialog.run()
838         if response == gtk.RESPONSE_OK:
839             self.configuration.proxies[protocol][1] = dialog.user
840             self.configuration.proxies[protocol][2] = dialog.passwd
841             self.refresh_proxy_components()
842         dialog.destroy()    
843
844     def rootfs_combo_changed_cb(self, rootfs_combo, all_package_format, check_hbox):
845         combo_item = self.rootfs_combo.get_active_text()
846         for child in check_hbox.get_children():
847             if isinstance(child, gtk.CheckButton):
848                 check_hbox.remove(child)
849         for format in all_package_format:
850             if format != combo_item:
851                 check_button = gtk.CheckButton(format)
852                 check_hbox.pack_start(check_button, expand=False, fill=False)
853         check_hbox.show_all()
854
855     def gen_pkgfmt_widget(self, curr_package_format, all_package_format, tooltip_combo="", tooltip_extra=""):
856         pkgfmt_hbox = gtk.HBox(False, 24)
857
858         rootfs_vbox = gtk.VBox(False, 6)
859         pkgfmt_hbox.pack_start(rootfs_vbox, expand=False, fill=False)
860
861         label = self.gen_label_widget("Root file system package format")
862         rootfs_vbox.pack_start(label, expand=False, fill=False)
863
864         rootfs_format = ""
865         if curr_package_format:
866             rootfs_format = curr_package_format.split()[0]
867
868         rootfs_format_widget, rootfs_combo = self.gen_combo_widget(rootfs_format, all_package_format, tooltip_combo)
869         rootfs_vbox.pack_start(rootfs_format_widget, expand=False, fill=False)
870
871         extra_vbox = gtk.VBox(False, 6)
872         pkgfmt_hbox.pack_start(extra_vbox, expand=False, fill=False)
873
874         label = self.gen_label_widget("Additional package formats")
875         extra_vbox.pack_start(label, expand=False, fill=False)
876
877         check_hbox = gtk.HBox(False, 12)
878         extra_vbox.pack_start(check_hbox, expand=False, fill=False)
879         for format in all_package_format:
880             if format != rootfs_format:
881                 check_button = gtk.CheckButton(format)
882                 is_active = (format in curr_package_format.split())
883                 check_button.set_active(is_active)
884                 check_hbox.pack_start(check_button, expand=False, fill=False)
885
886         info = HobInfoButton(tooltip_extra, self)
887         check_hbox.pack_end(info, expand=False, fill=False)
888
889         rootfs_combo.connect("changed", self.rootfs_combo_changed_cb, all_package_format, check_hbox)
890
891         pkgfmt_hbox.show_all()
892
893         return pkgfmt_hbox, rootfs_combo, check_hbox
894
895     def editable_settings_cell_edited(self, cell, path_string, new_text, model):
896         it = model.get_iter_from_string(path_string)
897         column = cell.get_data("column")
898         model.set(it, column, new_text)
899
900     def editable_settings_add_item_clicked(self, button, model):
901         new_item = ["##KEY##", "##VALUE##"]
902
903         iter = model.append()
904         model.set (iter,
905             0, new_item[0],
906             1, new_item[1],
907        )
908
909     def editable_settings_remove_item_clicked(self, button, treeview):
910         selection = treeview.get_selection()
911         model, iter = selection.get_selected()
912
913         if iter:
914             path = model.get_path(iter)[0]
915             model.remove(iter)
916  
917     def gen_editable_settings(self, setting, tooltip=""):
918         setting_hbox = gtk.HBox(False, 12)
919
920         vbox = gtk.VBox(False, 12)
921         setting_hbox.pack_start(vbox, expand=True, fill=True)
922
923         setting_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
924         for key in setting.keys():
925             setting_store.set(setting_store.append(), 0, key, 1, setting[key])
926
927         setting_tree = gtk.TreeView(setting_store)
928         setting_tree.set_headers_visible(True)
929         setting_tree.set_size_request(300, 100)
930
931         col = gtk.TreeViewColumn('Key')
932         col.set_min_width(100)
933         col.set_max_width(150)
934         col.set_resizable(True)
935         col1 = gtk.TreeViewColumn('Value')
936         col1.set_min_width(100)
937         col1.set_max_width(150)
938         col1.set_resizable(True)
939         setting_tree.append_column(col)
940         setting_tree.append_column(col1)
941         cell = gtk.CellRendererText()
942         cell.set_property('width-chars', 10)
943         cell.set_property('editable', True)
944         cell.set_data("column", 0)
945         cell.connect("edited", self.editable_settings_cell_edited, setting_store)
946         cell1 = gtk.CellRendererText()
947         cell1.set_property('width-chars', 10)
948         cell1.set_property('editable', True)
949         cell1.set_data("column", 1)
950         cell1.connect("edited", self.editable_settings_cell_edited, setting_store)
951         col.pack_start(cell, True)
952         col1.pack_end(cell1, True)
953         col.set_attributes(cell, text=0)
954         col1.set_attributes(cell1, text=1)
955
956         scroll = gtk.ScrolledWindow()
957         scroll.set_shadow_type(gtk.SHADOW_IN)
958         scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
959         scroll.add(setting_tree)
960         vbox.pack_start(scroll, expand=True, fill=True)
961
962         # some buttons
963         hbox = gtk.HBox(True, 6)
964         vbox.pack_start(hbox, False, False)
965
966         button = gtk.Button(stock=gtk.STOCK_ADD)
967         button.connect("clicked", self.editable_settings_add_item_clicked, setting_store)
968         hbox.pack_start(button)
969
970         button = gtk.Button(stock=gtk.STOCK_REMOVE)
971         button.connect("clicked", self.editable_settings_remove_item_clicked, setting_tree)
972         hbox.pack_start(button)
973
974         info = HobInfoButton(tooltip, self)
975         setting_hbox.pack_start(info, expand=False, fill=False)
976
977         return setting_hbox, setting_store
978
979     def create_others_page(self):
980         advanced_vbox = gtk.VBox(False, 6)
981         advanced_vbox.set_border_width(6)
982
983         sub_vbox = gtk.VBox(False, 6)
984         advanced_vbox.pack_start(sub_vbox, expand=True, fill=True)
985         label = self.gen_label_widget("<span weight=\"bold\">Add your own variables:</span>")
986         tooltip = "These are key/value pairs for your extra settings. Click \'Add\' and then directly edit the key and the value"
987         setting_widget, self.setting_store = self.gen_editable_settings(self.configuration.extra_setting, tooltip)
988         sub_vbox.pack_start(label, expand=False, fill=False)
989         sub_vbox.pack_start(setting_widget, expand=True, fill=True)
990
991         return advanced_vbox
992
993     def create_visual_elements(self):
994         self.nb = gtk.Notebook()
995         self.nb.set_show_tabs(True)        
996         self.nb.append_page(self.create_build_environment_page(), gtk.Label("Build environment"))
997         self.nb.append_page(self.create_shared_state_page(), gtk.Label("Shared state"))
998         self.nb.append_page(self.create_network_page(), gtk.Label("Network"))        
999         self.nb.append_page(self.create_others_page(), gtk.Label("Others"))
1000         self.nb.set_current_page(0)
1001         self.vbox.pack_start(self.nb, expand=True, fill=True)
1002         self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True)
1003
1004         self.show_all()
1005
1006     def destroy(self):
1007         self.handler.disconnect(self.proxy_test_passed_id)
1008         self.handler.disconnect(self.proxy_test_failed_id)
1009         super(SimpleSettingsDialog, self).destroy()
1010
1011     def scroll_changed(self, widget, event, data=None):
1012         adj = widget.get_vadjustment()
1013         adj.set_value(adj.upper - adj.page_size)
1014
1015 #
1016 # AdvancedSettings Dialog
1017 #
1018 class AdvancedSettingDialog (CrumbsDialog, SettingsUIHelper):
1019     
1020     def details_cb(self, button, parent, protocol):
1021         dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details",
1022             user = self.configuration.proxies[protocol][1],
1023             passwd = self.configuration.proxies[protocol][2],
1024             parent = parent,
1025             flags = gtk.DIALOG_MODAL
1026                     | gtk.DIALOG_DESTROY_WITH_PARENT
1027                     | gtk.DIALOG_NO_SEPARATOR)
1028         dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
1029         response = dialog.run()
1030         if response == gtk.RESPONSE_OK:
1031             self.configuration.proxies[protocol][1] = dialog.user
1032             self.configuration.proxies[protocol][2] = dialog.passwd
1033             self.refresh_proxy_components()
1034         dialog.destroy()
1035
1036     def set_save_button(self, button):
1037         self.save_button = button
1038
1039     def rootfs_combo_changed_cb(self, rootfs_combo, all_package_format, check_hbox):
1040         combo_item = self.rootfs_combo.get_active_text()
1041         modified = False
1042         for child in check_hbox.get_children():
1043             if isinstance(child, gtk.CheckButton):
1044                 check_hbox.remove(child)
1045                 modified = True
1046         for format in all_package_format:
1047             if format != combo_item:
1048                 check_button = gtk.CheckButton(format)
1049                 check_hbox.pack_start(check_button, expand=False, fill=False)
1050                 modified = True
1051         if modified:
1052             check_hbox.remove(self.pkgfmt_info)
1053             check_hbox.pack_start(self.pkgfmt_info, expand=False, fill=False)
1054         check_hbox.show_all()
1055
1056     def gen_pkgfmt_widget(self, curr_package_format, all_package_format, tooltip_combo="", tooltip_extra=""):
1057         pkgfmt_vbox = gtk.VBox(False, 6)
1058
1059         label = self.gen_label_widget("Root file system package format")
1060         pkgfmt_vbox.pack_start(label, expand=False, fill=False)
1061
1062         rootfs_format = ""
1063         if curr_package_format:
1064             rootfs_format = curr_package_format.split()[0]
1065
1066         rootfs_format_widget, rootfs_combo = self.gen_combo_widget(rootfs_format, all_package_format, tooltip_combo)
1067         pkgfmt_vbox.pack_start(rootfs_format_widget, expand=False, fill=False)
1068
1069         label = self.gen_label_widget("Additional package formats")
1070         pkgfmt_vbox.pack_start(label, expand=False, fill=False)
1071
1072         check_hbox = gtk.HBox(False, 12)
1073         pkgfmt_vbox.pack_start(check_hbox, expand=False, fill=False)
1074         for format in all_package_format:
1075             if format != rootfs_format:
1076                 check_button = gtk.CheckButton(format)
1077                 is_active = (format in curr_package_format.split())
1078                 check_button.set_active(is_active)
1079                 check_hbox.pack_start(check_button, expand=False, fill=False)
1080
1081         self.pkgfmt_info = HobInfoButton(tooltip_extra, self)
1082         check_hbox.pack_start(self.pkgfmt_info, expand=False, fill=False)
1083
1084         rootfs_combo.connect("changed", self.rootfs_combo_changed_cb, all_package_format, check_hbox)
1085
1086         pkgfmt_vbox.show_all()
1087
1088         return pkgfmt_vbox, rootfs_combo, check_hbox
1089
1090     def __init__(self, title, configuration, all_image_types,
1091             all_package_formats, all_distros, all_sdk_machines,
1092             max_threads, parent, flags, buttons=None):
1093         super(AdvancedSettingDialog, self).__init__(title, parent, flags, buttons)
1094
1095         # class members from other objects
1096         # bitbake settings from Builder.Configuration
1097         self.configuration = configuration
1098         self.image_types = all_image_types
1099         self.all_package_formats = all_package_formats
1100         self.all_distros = all_distros[:]
1101         self.all_sdk_machines = all_sdk_machines
1102         self.max_threads = max_threads
1103
1104         # class members for internal use
1105         self.distro_combo = None
1106         self.dldir_text = None
1107         self.sstatedir_text = None
1108         self.sstatemirror_text = None
1109         self.bb_spinner = None
1110         self.pmake_spinner = None
1111         self.rootfs_size_spinner = None
1112         self.extra_size_spinner = None
1113         self.gplv3_checkbox = None
1114         self.toolchain_checkbox = None
1115         self.image_types_checkbuttons = {}
1116
1117         self.md5 = self.config_md5()
1118         self.settings_changed = False
1119
1120         # create visual elements on the dialog
1121         self.save_button = None
1122         self.create_visual_elements()
1123         self.connect("response", self.response_cb)
1124
1125     def _get_sorted_value(self, var):
1126         return " ".join(sorted(str(var).split())) + "\n"
1127
1128     def config_md5(self):
1129         data = ""
1130         data += ("PACKAGE_CLASSES: "      + self.configuration.curr_package_format + '\n')
1131         data += ("DISTRO: "               + self._get_sorted_value(self.configuration.curr_distro))
1132         data += ("IMAGE_ROOTFS_SIZE: "    + self._get_sorted_value(self.configuration.image_rootfs_size))
1133         data += ("IMAGE_EXTRA_SIZE: "     + self._get_sorted_value(self.configuration.image_extra_size))
1134         data += ("INCOMPATIBLE_LICENSE: " + self._get_sorted_value(self.configuration.incompat_license))
1135         data += ("SDK_MACHINE: "          + self._get_sorted_value(self.configuration.curr_sdk_machine))
1136         data += ("TOOLCHAIN_BUILD: "      + self._get_sorted_value(self.configuration.toolchain_build))
1137         data += ("IMAGE_FSTYPES: "        + self._get_sorted_value(self.configuration.image_fstypes))
1138         return hashlib.md5(data).hexdigest()
1139
1140     def create_visual_elements(self):
1141         self.nb = gtk.Notebook()
1142         self.nb.set_show_tabs(True)
1143         self.nb.append_page(self.create_image_types_page(), gtk.Label("Image types"))
1144         self.nb.append_page(self.create_output_page(), gtk.Label("Output"))
1145         self.nb.set_current_page(0)
1146         self.vbox.pack_start(self.nb, expand=True, fill=True)
1147         self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True)
1148
1149         self.show_all()
1150
1151     def get_num_checked_image_types(self):
1152         total = 0
1153         for b in self.image_types_checkbuttons.values():
1154             if b.get_active():
1155               total = total + 1
1156         return total
1157
1158     def set_save_button_state(self):
1159         if self.save_button:
1160             self.save_button.set_sensitive(self.get_num_checked_image_types() > 0)
1161
1162     def image_type_checkbutton_clicked_cb(self, button):
1163         self.set_save_button_state()
1164         if self.get_num_checked_image_types() == 0:
1165             # Show an error dialog
1166             lbl = "<b>Select an image type</b>\n\nYou need to select at least one image type."
1167             dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
1168             button = dialog.add_button("OK", gtk.RESPONSE_OK)
1169             HobButton.style_button(button)
1170             response = dialog.run()
1171             dialog.destroy()
1172
1173     def create_image_types_page(self):
1174         main_vbox = gtk.VBox(False, 16)
1175         main_vbox.set_border_width(6)
1176
1177         advanced_vbox = gtk.VBox(False, 6)
1178         advanced_vbox.set_border_width(6)
1179
1180         distro_vbox = gtk.VBox(False, 6)        
1181         label = self.gen_label_widget("Distro:")
1182         tooltip = "Selects the Yocto Project distribution you want"
1183         try:
1184             i = self.all_distros.index( "defaultsetup" )
1185         except ValueError:
1186             i = -1
1187         if i != -1:
1188             self.all_distros[ i ] = "Default"
1189             if self.configuration.curr_distro == "defaultsetup":
1190                 self.configuration.curr_distro = "Default"
1191         distro_widget, self.distro_combo = self.gen_combo_widget(self.configuration.curr_distro, self.all_distros, tooltip)
1192         distro_vbox.pack_start(label, expand=False, fill=False)
1193         distro_vbox.pack_start(distro_widget, expand=False, fill=False)
1194         main_vbox.pack_start(distro_vbox, expand=False, fill=False)
1195
1196
1197         rows = (len(self.image_types)+1)/3
1198         table = gtk.Table(rows + 1, 10, True)
1199         advanced_vbox.pack_start(table, expand=False, fill=False)
1200
1201         tooltip = "Image file system types you want."
1202         info = HobInfoButton(tooltip, self)
1203         label = self.gen_label_widget("Image types:")
1204         align = gtk.Alignment(0, 0.5, 0, 0)
1205         table.attach(align, 0, 4, 0, 1)
1206         align.add(label)
1207         table.attach(info, 4, 5, 0, 1)
1208
1209         i = 1
1210         j = 1
1211         for image_type in sorted(self.image_types):
1212             self.image_types_checkbuttons[image_type] = gtk.CheckButton(image_type)
1213             self.image_types_checkbuttons[image_type].connect("toggled", self.image_type_checkbutton_clicked_cb)
1214             article = ""
1215             if image_type.startswith(("a", "e", "i", "o", "u")):
1216                 article = "n"
1217             self.image_types_checkbuttons[image_type].set_tooltip_text("Build a%s %s image" % (article, image_type))
1218             table.attach(self.image_types_checkbuttons[image_type], j - 1, j + 3, i, i + 1)
1219             if image_type in self.configuration.image_fstypes.split():
1220                 self.image_types_checkbuttons[image_type].set_active(True)
1221             i += 1
1222             if i > rows:
1223                 i = 1
1224                 j = j + 4
1225
1226         main_vbox.pack_start(advanced_vbox, expand=False, fill=False)
1227         self.set_save_button_state()
1228         
1229         return main_vbox
1230
1231     def create_output_page(self):
1232         advanced_vbox = gtk.VBox(False, 6)
1233         advanced_vbox.set_border_width(6)
1234
1235         advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Package format</span>'), expand=False, fill=False)
1236         sub_vbox = gtk.VBox(False, 6)
1237         advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
1238         tooltip_combo = "Selects the package format used to generate rootfs."
1239         tooltip_extra = "Selects extra package formats to build"
1240         pkgfmt_widget, self.rootfs_combo, self.check_hbox = self.gen_pkgfmt_widget(self.configuration.curr_package_format, self.all_package_formats, tooltip_combo, tooltip_extra)
1241         sub_vbox.pack_start(pkgfmt_widget, expand=False, fill=False)
1242
1243         advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Image size</span>'), expand=False, fill=False)
1244         sub_vbox = gtk.VBox(False, 6)
1245         advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
1246         label = self.gen_label_widget("Image basic size (in MB)")
1247         tooltip = "Sets the basic size of your target image.\nThis is the basic size of your target image unless your selected package size exceeds this value or you select \'Image Extra Size\'."
1248         rootfs_size_widget, self.rootfs_size_spinner = self.gen_spinner_widget(int(self.configuration.image_rootfs_size*1.0/1024), 0, 65536, tooltip)
1249         sub_vbox.pack_start(label, expand=False, fill=False)
1250         sub_vbox.pack_start(rootfs_size_widget, expand=False, fill=False)
1251
1252         sub_vbox = gtk.VBox(False, 6)
1253         advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
1254         label = self.gen_label_widget("Additional free space (in MB)")
1255         tooltip = "Sets the extra free space of your target image.\nBy default, the system reserves 30% of your image size as free space. If your image contains zypper, it brings in 50MB more space. The maximum free space is 64GB."
1256         extra_size_widget, self.extra_size_spinner = self.gen_spinner_widget(int(self.configuration.image_extra_size*1.0/1024), 0, 65536, tooltip)
1257         sub_vbox.pack_start(label, expand=False, fill=False)
1258         sub_vbox.pack_start(extra_size_widget, expand=False, fill=False)
1259
1260         advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Licensing</span>'), expand=False, fill=False)
1261         self.gplv3_checkbox = gtk.CheckButton("Exclude GPLv3 packages")
1262         self.gplv3_checkbox.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image")
1263         if "GPLv3" in self.configuration.incompat_license.split():
1264             self.gplv3_checkbox.set_active(True)
1265         else:
1266             self.gplv3_checkbox.set_active(False)
1267         advanced_vbox.pack_start(self.gplv3_checkbox, expand=False, fill=False)
1268
1269         advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Toolchain</span>'), expand=False, fill=False)
1270         sub_hbox = gtk.HBox(False, 6)
1271         advanced_vbox.pack_start(sub_hbox, expand=False, fill=False)
1272         self.toolchain_checkbox = gtk.CheckButton("Build toolchain")
1273         self.toolchain_checkbox.set_tooltip_text("Check this box to build the related toolchain with your image")
1274         self.toolchain_checkbox.set_active(self.configuration.toolchain_build)
1275         sub_hbox.pack_start(self.toolchain_checkbox, expand=False, fill=False)
1276
1277         tooltip = "Selects the host platform for which you want to run the toolchain"
1278         sdk_machine_widget, self.sdk_machine_combo = self.gen_combo_widget(self.configuration.curr_sdk_machine, self.all_sdk_machines, tooltip)
1279         sub_hbox.pack_start(sdk_machine_widget, expand=False, fill=False)
1280
1281         return advanced_vbox
1282
1283     def response_cb(self, dialog, response_id):
1284         package_format = []
1285         package_format.append(self.rootfs_combo.get_active_text())
1286         for child in self.check_hbox:
1287             if isinstance(child, gtk.CheckButton) and child.get_active():
1288                 package_format.append(child.get_label())
1289         self.configuration.curr_package_format = " ".join(package_format)
1290
1291         distro = self.distro_combo.get_active_text()
1292         if distro == "Default":
1293             distro = "defaultsetup"
1294         self.configuration.curr_distro = distro
1295         self.configuration.image_rootfs_size = self.rootfs_size_spinner.get_value_as_int() * 1024
1296         self.configuration.image_extra_size = self.extra_size_spinner.get_value_as_int() * 1024
1297
1298         self.configuration.image_fstypes = ""
1299         for image_type in self.image_types:
1300             if self.image_types_checkbuttons[image_type].get_active():
1301                 self.configuration.image_fstypes += (" " + image_type)
1302         self.configuration.image_fstypes.strip()
1303
1304         if self.gplv3_checkbox.get_active():
1305             if "GPLv3" not in self.configuration.incompat_license.split():
1306                 self.configuration.incompat_license += " GPLv3"
1307         else:
1308             if "GPLv3" in self.configuration.incompat_license.split():
1309                 self.configuration.incompat_license = self.configuration.incompat_license.split().remove("GPLv3")
1310                 self.configuration.incompat_license = " ".join(self.configuration.incompat_license or [])
1311         self.configuration.incompat_license = self.configuration.incompat_license.strip()
1312
1313         self.configuration.toolchain_build = self.toolchain_checkbox.get_active()
1314         self.configuration.curr_sdk_machine = self.sdk_machine_combo.get_active_text()
1315         md5 = self.config_md5()
1316         self.settings_changed = (self.md5 != md5)
1317
1318 #
1319 # DeployImageDialog
1320 #
1321 class DeployImageDialog (CrumbsDialog):
1322
1323     __dummy_usb__ = "--select a usb drive--"
1324
1325     def __init__(self, title, image_path, parent, flags, buttons=None, standalone=False):
1326         super(DeployImageDialog, self).__init__(title, parent, flags, buttons)
1327
1328         self.image_path = image_path
1329         self.standalone = standalone
1330
1331         self.create_visual_elements()
1332         self.connect("response", self.response_cb)
1333
1334     def create_visual_elements(self):
1335         self.set_size_request(600, 400)
1336         label = gtk.Label()
1337         label.set_alignment(0.0, 0.5)
1338         markup = "<span font_desc='12'>The image to be written into usb drive:</span>"
1339         label.set_markup(markup)
1340         self.vbox.pack_start(label, expand=False, fill=False, padding=2)
1341
1342         table = gtk.Table(2, 10, False)
1343         table.set_col_spacings(5)
1344         table.set_row_spacings(5)
1345         self.vbox.pack_start(table, expand=True, fill=True)
1346
1347         scroll = gtk.ScrolledWindow()
1348         scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
1349         scroll.set_shadow_type(gtk.SHADOW_IN)
1350         tv = gtk.TextView()
1351         tv.set_editable(False)
1352         tv.set_wrap_mode(gtk.WRAP_WORD)
1353         tv.set_cursor_visible(False)
1354         self.buf = gtk.TextBuffer()
1355         self.buf.set_text(self.image_path)
1356         tv.set_buffer(self.buf)
1357         scroll.add(tv)
1358         table.attach(scroll, 0, 10, 0, 1)
1359
1360         # There are 2 ways to use DeployImageDialog
1361         # One way is that called by HOB when the 'Deploy Image' button is clicked
1362         # The other way is that called by a standalone script.
1363         # Following block of codes handles the latter way. It adds a 'Select Image' button and
1364         # emit a signal when the button is clicked.
1365         if self.standalone:
1366                 gobject.signal_new("select_image_clicked", self, gobject.SIGNAL_RUN_FIRST,
1367                                    gobject.TYPE_NONE, ())
1368                 icon = gtk.Image()
1369                 pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_IMAGES_DISPLAY_FILE)
1370                 icon.set_from_pixbuf(pix_buffer)
1371                 button = gtk.Button("Select Image")
1372                 button.set_image(icon)
1373                 #button.set_size_request(140, 50)
1374                 table.attach(button, 9, 10, 1, 2, gtk.FILL, 0, 0, 0)
1375                 button.connect("clicked", self.select_image_button_clicked_cb)
1376
1377         separator = gtk.HSeparator()
1378         self.vbox.pack_start(separator, expand=False, fill=False, padding=10)
1379
1380         self.usb_desc = gtk.Label()
1381         self.usb_desc.set_alignment(0.0, 0.5)
1382         markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
1383         self.usb_desc.set_markup(markup)
1384
1385         self.usb_combo = gtk.combo_box_new_text()
1386         self.usb_combo.connect("changed", self.usb_combo_changed_cb)
1387         model = self.usb_combo.get_model()
1388         model.clear()
1389         self.usb_combo.append_text(self.__dummy_usb__)
1390         for usb in self.find_all_usb_devices():
1391             self.usb_combo.append_text("/dev/" + usb)
1392         self.usb_combo.set_active(0)
1393         self.vbox.pack_start(self.usb_combo, expand=False, fill=False)
1394         self.vbox.pack_start(self.usb_desc, expand=False, fill=False, padding=2)
1395
1396         self.progress_bar = HobProgressBar()
1397         self.vbox.pack_start(self.progress_bar, expand=False, fill=False)
1398         separator = gtk.HSeparator()
1399         self.vbox.pack_start(separator, expand=False, fill=True, padding=10)
1400
1401         self.vbox.show_all()
1402         self.progress_bar.hide()
1403
1404     def set_image_text_buffer(self, image_path):
1405         self.buf.set_text(image_path)
1406
1407     def set_image_path(self, image_path):
1408         self.image_path = image_path
1409
1410     def popen_read(self, cmd):
1411         tmpout, errors = bb.process.run("%s" % cmd)
1412         return tmpout.strip()
1413
1414     def find_all_usb_devices(self):
1415         usb_devs = [ os.readlink(u)
1416             for u in glob.glob('/dev/disk/by-id/usb*')
1417             if not re.search(r'part\d+', u) ]
1418         return [ '%s' % u[u.rfind('/')+1:] for u in usb_devs ]
1419
1420     def get_usb_info(self, dev):
1421         return "%s %s" % \
1422             (self.popen_read('cat /sys/class/block/%s/device/vendor' % dev),
1423             self.popen_read('cat /sys/class/block/%s/device/model' % dev))
1424
1425     def select_image_button_clicked_cb(self, button):
1426             self.emit('select_image_clicked')
1427
1428     def usb_combo_changed_cb(self, usb_combo):
1429         combo_item = self.usb_combo.get_active_text()
1430         if not combo_item or combo_item == self.__dummy_usb__:
1431             markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
1432             self.usb_desc.set_markup(markup)
1433         else:
1434             markup = "<span font_desc='12'>" + self.get_usb_info(combo_item.lstrip("/dev/")) + "</span>"
1435             self.usb_desc.set_markup(markup)
1436
1437     def response_cb(self, dialog, response_id):
1438         if response_id == gtk.RESPONSE_YES:
1439             lbl = ''
1440             combo_item = self.usb_combo.get_active_text()
1441             if combo_item and combo_item != self.__dummy_usb__ and self.image_path:
1442                 cmdline = bb.ui.crumbs.utils.which_terminal()
1443                 if cmdline:
1444                     tmpfile = tempfile.NamedTemporaryFile()
1445                     cmdline += "\"sudo dd if=" + self.image_path + \
1446                                 " of=" + combo_item + "; echo $? > " + tmpfile.name + "\""
1447                     subprocess.call(shlex.split(cmdline))
1448
1449                     if int(tmpfile.readline().strip()) == 0:
1450                         lbl = "<b>Deploy image successfully.</b>"
1451                     else:
1452                         lbl = "<b>Failed to deploy image.</b>\nPlease check image <b>%s</b> exists and USB device <b>%s</b> is writable." % (self.image_path, combo_item)
1453                     tmpfile.close()
1454             else:
1455                 if not self.image_path:
1456                     lbl = "<b>No selection made.</b>\nYou have not selected an image to deploy."
1457                 else:
1458                     lbl = "<b>No selection made.</b>\nYou have not selected a USB device."
1459             if len(lbl):
1460                 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1461                 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
1462                 HobButton.style_button(button)
1463                 crumbs_dialog.run()
1464                 crumbs_dialog.destroy()
1465
1466     def update_progress_bar(self, title, fraction, status=None):
1467         self.progress_bar.update(fraction)
1468         self.progress_bar.set_title(title)
1469         self.progress_bar.set_rcstyle(status)
1470
1471     def write_file(self, ifile, ofile):
1472         self.progress_bar.reset()
1473         self.progress_bar.show()
1474
1475         f_from = os.open(ifile, os.O_RDONLY)
1476         f_to = os.open(ofile, os.O_WRONLY)
1477
1478         total_size = os.stat(ifile).st_size
1479         written_size = 0
1480
1481         while True:
1482             buf = os.read(f_from, 1024*1024)
1483             if not buf:
1484                 break
1485             os.write(f_to, buf)
1486             written_size += 1024*1024
1487             self.update_progress_bar("Writing to usb:", written_size * 1.0/total_size)
1488
1489         self.update_progress_bar("Writing completed:", 1.0)
1490         os.close(f_from)
1491         os.close(f_to)
1492         self.progress_bar.hide()
1493
1494 class CellRendererPixbufActivatable(gtk.CellRendererPixbuf):
1495     """
1496     A custom CellRenderer implementation which is activatable
1497     so that we can handle user clicks
1498     """
1499     __gsignals__    = { 'clicked' : (gobject.SIGNAL_RUN_LAST,
1500                                      gobject.TYPE_NONE,
1501                                      (gobject.TYPE_STRING,)), }
1502
1503     def __init__(self):
1504         gtk.CellRendererPixbuf.__init__(self)
1505         self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
1506         self.set_property('follow-state', True)
1507
1508     """
1509     Respond to a user click on a cell
1510     """
1511     def do_activate(self, even, widget, path, background_area, cell_area, flags):
1512         self.emit('clicked', path)
1513
1514 #
1515 # LayerSelectionDialog
1516 #
1517 class LayerSelectionDialog (CrumbsDialog):
1518
1519     def gen_label_widget(self, content):
1520         label = gtk.Label()
1521         label.set_alignment(0, 0)
1522         label.set_markup(content)
1523         label.show()
1524         return label
1525
1526     def layer_widget_toggled_cb(self, cell, path, layer_store):
1527         name = layer_store[path][0]
1528         toggle = not layer_store[path][1]
1529         layer_store[path][1] = toggle
1530
1531     def layer_widget_add_clicked_cb(self, action, layer_store, parent):
1532         dialog = gtk.FileChooserDialog("Add new layer", parent,
1533                                        gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
1534         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1535         HobAltButton.style_button(button)
1536         button = dialog.add_button("Open", gtk.RESPONSE_YES)
1537         HobButton.style_button(button)
1538         label = gtk.Label("Select the layer you wish to add")
1539         label.show()
1540         dialog.set_extra_widget(label)
1541         response = dialog.run()
1542         path = dialog.get_filename()
1543         dialog.destroy()
1544
1545         lbl = "<b>Error</b>\nUnable to load layer <i>%s</i> because " % path
1546         if response == gtk.RESPONSE_YES:
1547             import os
1548             import os.path
1549             layers = []
1550             it = layer_store.get_iter_first()
1551             while it:
1552                 layers.append(layer_store.get_value(it, 0))
1553                 it = layer_store.iter_next(it)
1554
1555             if not path:
1556                 lbl += "it is an invalid path."
1557             elif not os.path.exists(path+"/conf/layer.conf"):
1558                 lbl += "there is no layer.conf inside the directory."
1559             elif path in layers:
1560                 lbl += "it is already in loaded layers."
1561             else:
1562                 layer_store.append([path])
1563                 return
1564             dialog = CrumbsMessageDialog(parent, lbl)
1565             dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
1566             response = dialog.run()
1567             dialog.destroy()
1568
1569     def layer_widget_del_clicked_cb(self, action, tree_selection, layer_store):
1570         model, iter = tree_selection.get_selected()
1571         if iter:
1572             layer_store.remove(iter)
1573
1574
1575     def gen_layer_widget(self, layers, layers_avail, window, tooltip=""):
1576         hbox = gtk.HBox(False, 6)
1577
1578         layer_tv = gtk.TreeView()
1579         layer_tv.set_rules_hint(True)
1580         layer_tv.set_headers_visible(False)
1581         tree_selection = layer_tv.get_selection()
1582         tree_selection.set_mode(gtk.SELECTION_NONE)
1583
1584         col0= gtk.TreeViewColumn('Path')
1585         cell0 = gtk.CellRendererText()
1586         cell0.set_padding(5,2)
1587         col0.pack_start(cell0, True)
1588         col0.set_cell_data_func(cell0, self.draw_layer_path_cb)
1589         layer_tv.append_column(col0)
1590
1591         scroll = gtk.ScrolledWindow()
1592         scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
1593         scroll.set_shadow_type(gtk.SHADOW_IN)
1594         scroll.add(layer_tv)
1595
1596         table_layer = gtk.Table(2, 10, False)
1597         hbox.pack_start(table_layer, expand=True, fill=True)
1598
1599         table_layer.attach(scroll, 0, 10, 0, 1)
1600
1601         layer_store = gtk.ListStore(gobject.TYPE_STRING)
1602         for layer in layers:
1603             layer_store.append([layer])
1604
1605         col1 = gtk.TreeViewColumn('Enabled')
1606         layer_tv.append_column(col1)
1607
1608         cell1 = CellRendererPixbufActivatable()
1609         cell1.set_fixed_size(-1,35)
1610         cell1.connect("clicked", self.del_cell_clicked_cb, layer_store)
1611         col1.pack_start(cell1, True)
1612         col1.set_cell_data_func(cell1, self.draw_delete_button_cb, layer_tv)
1613
1614         add_button = gtk.Button()
1615         add_button.set_relief(gtk.RELIEF_NONE)
1616         box = gtk.HBox(False, 6)
1617         box.show()
1618         add_button.add(box)
1619         add_button.connect("enter-notify-event", self.add_hover_cb)
1620         add_button.connect("leave-notify-event", self.add_leave_cb)
1621         self.im = gtk.Image()
1622         self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
1623         self.im.show()
1624         box.pack_start(self.im, expand=False, fill=False, padding=6)
1625         lbl = gtk.Label("Add layer")
1626         lbl.set_alignment(0.0, 0.5)
1627         lbl.show()
1628         box.pack_start(lbl, expand=True, fill=True, padding=6)
1629         add_button.connect("clicked", self.layer_widget_add_clicked_cb, layer_store, window)
1630         table_layer.attach(add_button, 0, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 6)
1631         layer_tv.set_model(layer_store)
1632
1633         hbox.show_all()
1634
1635         return hbox, layer_store
1636
1637     def add_hover_cb(self, button, event):
1638         self.im.set_from_file(hic.ICON_INDI_ADD_HOVER_FILE)
1639
1640     def add_leave_cb(self, button, event):
1641         self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
1642
1643     def __init__(self, title, layers, all_layers, parent, flags, buttons=None):
1644         super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons)
1645
1646         # class members from other objects
1647         self.layers = layers
1648         self.all_layers = all_layers
1649         self.layers_changed = False
1650
1651         # icon for remove button in TreeView
1652         im = gtk.Image()
1653         im.set_from_file(hic.ICON_INDI_REMOVE_FILE)
1654         self.rem_icon = im.get_pixbuf()
1655
1656         # class members for internal use
1657         self.layer_store = None
1658
1659         # create visual elements on the dialog
1660         self.create_visual_elements()
1661         self.connect("response", self.response_cb)
1662
1663     def create_visual_elements(self):
1664         layer_widget, self.layer_store = self.gen_layer_widget(self.layers, self.all_layers, self, None)
1665         layer_widget.set_size_request(450, 250)
1666         self.vbox.pack_start(layer_widget, expand=True, fill=True)
1667         self.show_all()
1668
1669     def response_cb(self, dialog, response_id):
1670         model = self.layer_store
1671         it = model.get_iter_first()
1672         layers = []
1673         while it:
1674             layers.append(model.get_value(it, 0))
1675             it = model.iter_next(it)
1676
1677         self.layers_changed = (self.layers != layers)
1678         self.layers = layers
1679
1680     """
1681     A custom cell_data_func to draw a delete 'button' in the TreeView for layers
1682     other than the meta layer. The deletion of which is prevented so that the
1683     user can't shoot themselves in the foot too badly.
1684     """
1685     def draw_delete_button_cb(self, col, cell, model, it, tv):
1686         path =  model.get_value(it, 0)
1687         # Trailing slashes are uncommon in bblayers.conf but confuse os.path.basename
1688         path.rstrip('/')
1689         name = os.path.basename(path)
1690         if name == "meta" or name == "meta-hob":
1691             cell.set_sensitive(False)
1692             cell.set_property('pixbuf', None)
1693             cell.set_property('mode', gtk.CELL_RENDERER_MODE_INERT)
1694         else:
1695             cell.set_property('pixbuf', self.rem_icon)
1696             cell.set_sensitive(True)
1697             cell.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
1698
1699         return True
1700
1701     """
1702     A custom cell_data_func to write an extra message into the layer path cell
1703     for the meta layer. We should inform the user that they can't remove it for
1704     their own safety.
1705     """
1706     def draw_layer_path_cb(self, col, cell, model, it):
1707         path = model.get_value(it, 0)
1708         name = os.path.basename(path)
1709         if name == "meta":
1710             cell.set_property('markup', "<b>Core layer for images: it cannot be removed</b>\n%s" % path)
1711         elif name == "meta-hob":
1712             cell.set_property('markup', "<b>Core layer for Hob: it cannot be removed</b>\n%s" % path)
1713         else:
1714             cell.set_property('text', path)
1715
1716     def del_cell_clicked_cb(self, cell, path, model):
1717         it = model.get_iter_from_string(path)
1718         model.remove(it)
1719
1720 class ImageSelectionDialog (CrumbsDialog):
1721
1722     __columns__ = [{
1723             'col_name' : 'Image name',
1724             'col_id'   : 0,
1725             'col_style': 'text',
1726             'col_min'  : 400,
1727             'col_max'  : 400
1728         }, {
1729             'col_name' : 'Select',
1730             'col_id'   : 1,
1731             'col_style': 'radio toggle',
1732             'col_min'  : 160,
1733             'col_max'  : 160
1734     }]
1735
1736
1737     def __init__(self, image_folder, image_types, title, parent, flags, buttons=None, image_extension = {}):
1738         super(ImageSelectionDialog, self).__init__(title, parent, flags, buttons)
1739         self.connect("response", self.response_cb)
1740
1741         self.image_folder = image_folder
1742         self.image_types  = image_types
1743         self.image_list = []
1744         self.image_names = []
1745         self.image_extension = image_extension
1746
1747         # create visual elements on the dialog
1748         self.create_visual_elements()
1749
1750         self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
1751         self.fill_image_store()
1752
1753     def create_visual_elements(self):
1754         hbox = gtk.HBox(False, 6)
1755
1756         self.vbox.pack_start(hbox, expand=False, fill=False)
1757
1758         entry = gtk.Entry()
1759         entry.set_text(self.image_folder)
1760         table = gtk.Table(1, 10, True)
1761         table.set_size_request(560, -1)
1762         hbox.pack_start(table, expand=False, fill=False)
1763         table.attach(entry, 0, 9, 0, 1)
1764         image = gtk.Image()
1765         image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
1766         open_button = gtk.Button()
1767         open_button.set_image(image)
1768         open_button.connect("clicked", self.select_path_cb, self, entry)
1769         table.attach(open_button, 9, 10, 0, 1)
1770
1771         self.image_table = HobViewTable(self.__columns__)
1772         self.image_table.set_size_request(-1, 300)
1773         self.image_table.connect("toggled", self.toggled_cb)
1774         self.image_table.connect_group_selection(self.table_selected_cb)
1775         self.image_table.connect("row-activated", self.row_actived_cb)
1776         self.vbox.pack_start(self.image_table, expand=True, fill=True)
1777
1778         self.show_all()
1779
1780     def change_image_cb(self, model, path, columnid):
1781         if not model:
1782             return
1783         iter = model.get_iter_first()
1784         while iter:
1785             rowpath = model.get_path(iter)
1786             model[rowpath][columnid] = False
1787             iter = model.iter_next(iter)
1788
1789         model[path][columnid] = True
1790
1791     def toggled_cb(self, table, cell, path, columnid, tree):
1792         model = tree.get_model()
1793         self.change_image_cb(model, path, columnid)
1794
1795     def table_selected_cb(self, selection):
1796         model, paths = selection.get_selected_rows()
1797         if paths:
1798             self.change_image_cb(model, paths[0], 1)
1799
1800     def row_actived_cb(self, tab, model, path):
1801         self.change_image_cb(model, path, 1)
1802         self.emit('response', gtk.RESPONSE_YES)
1803
1804     def select_path_cb(self, action, parent, entry):
1805         dialog = gtk.FileChooserDialog("", parent,
1806                                        gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
1807         text = entry.get_text()
1808         dialog.set_current_folder(text if len(text) > 0 else os.getcwd())
1809         button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1810         HobAltButton.style_button(button)
1811         button = dialog.add_button("Open", gtk.RESPONSE_YES)
1812         HobButton.style_button(button)
1813         response = dialog.run()
1814         if response == gtk.RESPONSE_YES:
1815             path = dialog.get_filename()
1816             entry.set_text(path)
1817             self.image_folder = path
1818             self.fill_image_store()
1819
1820         dialog.destroy()
1821
1822     def fill_image_store(self):
1823         self.image_list = []
1824         self.image_store.clear()
1825         imageset = set()
1826         for root, dirs, files in os.walk(self.image_folder):
1827             # ignore the sub directories
1828             dirs[:] = []
1829             for f in files:
1830                 for image_type in self.image_types:
1831                     if image_type in self.image_extension:
1832                         real_types = self.image_extension[image_type]
1833                     else:
1834                         real_types = [image_type]
1835                     for real_image_type in real_types:
1836                         if f.endswith('.' + real_image_type):
1837                             imageset.add(f.rsplit('.' + real_image_type)[0].rsplit('.rootfs')[0])
1838                             self.image_list.append(f)
1839
1840         for image in imageset:
1841             self.image_store.set(self.image_store.append(), 0, image, 1, False)
1842
1843         self.image_table.set_model(self.image_store)
1844
1845     def response_cb(self, dialog, response_id):
1846         self.image_names = []
1847         if response_id == gtk.RESPONSE_YES:
1848             iter = self.image_store.get_iter_first()
1849             while iter:
1850                 path = self.image_store.get_path(iter)
1851                 if self.image_store[path][1]:
1852                     for f in self.image_list:
1853                         if f.startswith(self.image_store[path][0] + '.'):
1854                             self.image_names.append(f)
1855                     break
1856                 iter = self.image_store.iter_next(iter)
1857
1858 class ProxyDetailsDialog (CrumbsDialog):
1859
1860     def __init__(self, title, user, passwd, parent, flags, buttons=None):
1861         super(ProxyDetailsDialog, self).__init__(title, parent, flags, buttons)
1862         self.connect("response", self.response_cb)
1863
1864         self.auth = not (user == None or passwd == None or user == "")
1865         self.user = user or ""
1866         self.passwd = passwd or ""
1867
1868         # create visual elements on the dialog
1869         self.create_visual_elements()
1870
1871     def create_visual_elements(self):
1872         self.auth_checkbox = gtk.CheckButton("Use authentication")
1873         self.auth_checkbox.set_tooltip_text("Check this box to set the username and the password")
1874         self.auth_checkbox.set_active(self.auth)
1875         self.auth_checkbox.connect("toggled", self.auth_checkbox_toggled_cb)
1876         self.vbox.pack_start(self.auth_checkbox, expand=False, fill=False)
1877
1878         hbox = gtk.HBox(False, 6)
1879         self.user_label = gtk.Label("Username:")
1880         self.user_text = gtk.Entry()
1881         self.user_text.set_text(self.user)
1882         hbox.pack_start(self.user_label, expand=False, fill=False)
1883         hbox.pack_end(self.user_text, expand=False, fill=False)
1884         self.vbox.pack_start(hbox, expand=False, fill=False)
1885
1886         hbox = gtk.HBox(False, 6)
1887         self.passwd_label = gtk.Label("Password:")
1888         self.passwd_text = gtk.Entry()
1889         self.passwd_text.set_text(self.passwd)
1890         hbox.pack_start(self.passwd_label, expand=False, fill=False)
1891         hbox.pack_end(self.passwd_text, expand=False, fill=False)
1892         self.vbox.pack_start(hbox, expand=False, fill=False)
1893
1894         self.refresh_auth_components()
1895         self.show_all()
1896
1897     def refresh_auth_components(self):
1898         self.user_label.set_sensitive(self.auth)
1899         self.user_text.set_editable(self.auth)
1900         self.user_text.set_sensitive(self.auth)
1901         self.passwd_label.set_sensitive(self.auth)
1902         self.passwd_text.set_editable(self.auth)
1903         self.passwd_text.set_sensitive(self.auth)
1904
1905     def auth_checkbox_toggled_cb(self, button):
1906         self.auth = self.auth_checkbox.get_active()
1907         self.refresh_auth_components()
1908
1909     def response_cb(self, dialog, response_id):
1910         if response_id == gtk.RESPONSE_OK:
1911             if self.auth:
1912                 self.user = self.user_text.get_text()
1913                 self.passwd = self.passwd_text.get_text()
1914             else:
1915                 self.user = None
1916                 self.passwd = None