From: Brett Williams Date: 2005-04-28T01:24:54+09:00 Subject: Re: tk_optionMenu bug in 1.8.2 --------------000400030303070702020802 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit Hidetoshi NAGAI wrote: > From: H.Yamamoto > Subject: Re: tk_optionMenu bug in 1.8.2 > Date: Wed, 27 Apr 2005 13:47:31 +0900 > Message-ID: <20050427134711.1A7CE290.ocean@m2.ccsnet.ne.jp> > >>>It now works for the little test case H.Yamamoto wrote, but still >>>fails in our code with the same bgerror problem as before. >> >>Maybe because second argument of TclTkIp.new is nil? > > > Hmmm... If so, that is a matter of course. > > # Without Tk, "bgerror" command isn't defined also. The nil to the interpreter is not in this problem (completely different application). I have attached a standalone file which does this. Just run it and push any of the three add buttons to reproduce the error. Sorry it isn't smaller -- if this isn't good enough I can attempt to gut pieces of it at a time to see how small I can get it. Thanks very much for your help so far. --------------000400030303070702020802 Content-Type: text/plain; name="pgui.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="pgui.rb" #!/usr/bin/env ruby ### # File PackageGUI.rb # # Created on:: 03 May 2004 # Author:: Brett Williams # (C) Copyright Agilent Technologies Inc. 2004. All Rights Reserved. ########################################################################### require 'tk' require 'tkscrollbox' require 'ostruct' require 'rexml/document' module ACTMfg module Tools ### # The PackageGUI class ########################################################################### class PackageGUI class RequireConflictWidget def initialize(root, function, contents, neutered = false) @root = root @function = function @neutered = neutered # Set up frames @enclosing_frame = TkFrame.new(@root) @label_frame = TkFrame.new(@enclosing_frame) { relief 'ridge' borderwidth 2 } @button_frame = TkFrame.new(@enclosing_frame) { relief 'ridge' borderwidth 2 } @list_frame = TkFrame.new(@enclosing_frame) { relief 'ridge' borderwidth 2 } @label_frame.pack('side'=>'top') @list_frame.pack('side'=>'top') @button_frame.pack('side'=>'top') label = TkLabel.new(@label_frame) { text function } label.pack('side'=>'top') @list = TkScrollbox.new(@list_frame, 'height'=>4) @list.pack('expand'=>true, 'fill'=>'both') contents.each { |item| @list.insert('end', item.to_s) } # Now add the buttons, where most of the action takes place add = make_add_button() edit = make_edit_button() list = @list delete = TkButton.new(@button_frame, 'state'=>'disabled') { text 'Delete' command { list.delete(list.curselection) } } list = @list @list.bind('') do cur_selection = list.curselection if cur_selection.nil? or cur_selection.empty? edit.configure('state'=>'disabled') delete.configure('state'=>'disabled') else edit.configure('state'=>'normal') delete.configure('state'=>'normal') end end TkGrid.configure(add, edit, delete) end def get(beg, last) return @list.get(beg, last) end def delete(beg, last) return @list.delete(beg, last) end def insert(index, element) return @list.insert(index, element) end def enclosing_frame() return @enclosing_frame end def make_add_button() button_type = 'Add' list = @list function = @function neutered = @neutered main_button = TkButton.new(@button_frame) do text button_type command do win = TkToplevel.new(root) { title "#{button_type} #{function} window" } # Frame to hold the entries data_frame = TkFrame.new(win) data_frame.pack('side' => 'top') # Frame to hole the buttons button_frame = TkFrame.new(win) button_frame.pack('side' => 'top') # Package version widgets # The version widgets need to be defined before the # name widgets because the name widgets call the # version widgets ver_frame = TkFrame.new(data_frame) ver_label = TkLabel.new(ver_frame) { text "Version" } ver_label.pack('side' => 'top') ver_entry = TkEntry.new(ver_frame) { width 10 } ver_entry.pack('side' => 'top') if not neutered ver_listbox = TkScrollbox.new(ver_frame) { width 7; height 15; selectmode 'browse' } ver_listbox.bind('ButtonRelease-1') do ver_entry.delete('0', 'end') ver_entry.insert('0', ver_listbox.get(ver_listbox.curselection())) end ver_listbox.pack('side' => 'top') ver_listbox_xsb = TkScrollbar.new(ver_frame) ver_listbox.xscrollbar(ver_listbox_xsb) ver_listbox_xsb.pack('fill' => 'x') end # Package name wigets pkg_frame = TkFrame.new(data_frame) pkg_label = TkLabel.new(pkg_frame) { text "Package Name" } pkg_label.pack('side' => 'top') pkg_entry = TkEntry.new(pkg_frame) { width 28 } pkg_entry.pack('side' => 'top') if not neutered pkg_listbox = TkScrollbox.new(pkg_frame) { width 25; height 15; selectmode 'browse' } ['a','b','c','x'].uniq.sort.each do |name| pkg_listbox.insert('end', name) end pkg_listbox.bind('ButtonRelease-1') do pkg_name = pkg_listbox.get(pkg_listbox.curselection()) pkg_entry.delete('0', 'end') pkg_entry.insert('0', pkg_name) ver_listbox.delete('0', 'end') ['1','2','3','4'].sort.uniq.reverse_each do |full_version| ver_listbox.insert('end', full_version) end end pkg_listbox.pack('side' => 'top') pkg_listbox_xsb = TkScrollbar.new(pkg_frame) pkg_listbox.xscrollbar(pkg_listbox_xsb) pkg_listbox_xsb.pack('fill' => 'x') end # Operator widgets operator_frame = TkFrame.new(data_frame) operator_label = TkLabel.new(operator_frame) { text "Operator" } operator_label.pack('side' => 'top') names = ['None', '<', '<=', '=', '>=', '>'] operator_var = TkVariable.new() puts "------------ CONSTRUCTING TKOPTIONMENU" operator = TkOptionMenubutton.new(operator_frame, operator_var, *names) puts "------------ DONE CONSTRUCTING TKOPTIONMENU" operator.pack('side' => 'top') # Pack all the widgets into the data frame pkg_frame.pack('side' => 'left', 'anchor' => 'nw') if not neutered operator_frame.pack('side' => 'left', 'anchor' => 'nw') ver_frame.pack('side' => 'left', 'anchor' => 'nw') end # OK buttion ok = TkButton.new(button_frame) do text 'OK' command do new_item = ACTMfg::RPM::RequireOrConflict.new(pkg_entry.value, operator_var.to_s, ver_entry.value) list.insert('end', new_item.to_s) win.destroy end end # Cancel button cancel = TkButton.new(button_frame) do text 'Cancel' command { win.destroy } end # Pack all the widgets into the button frame ok.pack('side' => 'left') cancel.pack('side' => 'left') win.wait_visibility win.focus end end return main_button end def make_edit_button() button_type = 'Edit' list = @list function = @function main_button = TkButton.new(@button_frame, 'state'=>'disabled') do text button_type command do # Find which item was selected in the list puts "Editing #{list.get(list.curselection)}" pos = list.curselection orig_contents = list.get(pos) sel = ACTMfg::RPM::RequireOrConflict.new_from_str(orig_contents) list.delete(pos) # Top level window win = TkToplevel.new(root) { title "#{button_type} #{function} window" } # Package name entry pkg_label = TkLabel.new(win) { text "Package Name" } pkg_entry = TkEntry.new(win) { width 25; insert(0, sel.name) } # Operator entry operator_label = TkLabel.new(win) { text "Operator" } names = ['None', '<', '<=', '=', '>=', '>'] def_val = sel.operator.nil? ? 'None' : sel.operator names.delete(def_val) operator_var = TkVariable.new() operator = TkOptionMenubutton.new(win, operator_var, def_val, *names) # Version entry ver_label = TkLabel.new(win) { text "Version" } ver_entry = TkEntry.new(win) { width 6; insert(0, sel.version) } # Now add OK button, where information is retrieved ok = TkButton.new(win) do text 'OK' command do new_item = ACTMfg::RPM::RequireOrConflict.new(pkg_entry.value, operator_var.to_s, ver_entry.value) list.insert(pos, new_item.to_s) win.destroy end end cancel = TkButton.new(win) do text 'Cancel' command { list.insert(pos, orig_contents); win.destroy } end TkGrid.configure(pkg_label, operator_label, ver_label) TkGrid.configure(pkg_entry, operator, ver_entry, ok, cancel) win.wait_visibility win.focus end end return main_button end end #class RequireConflictWidget def initialize(auto_flag, xml_input, xml_output, cust_file, dev_file) @auto_flag = auto_flag @save_flag = false @cust_file = cust_file @dev_file = dev_file @xml_input = xml_input @xml_output = xml_output @cust_load_proc = proc do @cust_file = @file_widgets['cust'].value File.open(@cust_file) do |file| @cust_text_entry.delete('1.0', 'end') @cust_text_entry.insert('end', file.read) end end @cust_clear_proc = proc do @cust_file = @file_widgets['cust'].value if (not File.exists?(@cust_file) or File.size?(@cust_file)) File.open(@cust_file, 'w') {} end end @dev_load_proc = proc do @dev_file = @file_widgets['dev'].value File.open(@dev_file) do |file| @dev_text_entry.delete('1.0', 'end') @dev_text_entry.insert('end', file.read) end end @dev_clear_proc = proc do @dev_file = @file_widgets['dev'].value if (not File.exists?(@dev_file) or File.size?(@dev_file)) File.open(@dev_file, 'w') {} end end @load_proc = proc do @xml_input = @file_widgets['input'].value # Get data from xml file get_xml_data(@xml_input) # Populate TK widgets with ruby data @widgets.each do |field, widget| widget.value = @data_values[field] end @req_list.delete(0, 'end') @requires.each { |item| @req_list.insert('end', item.to_s) } @conf_list.delete(0, 'end') @conflicts.each { |item| @conf_list.insert('end', item.to_s) } @prov_list.delete(0, 'end') @provides.each { |item| @prov_list.insert('end', item.to_s) } end @save_proc = proc do file = @file_widgets['output'] if file.value == "" @xml_output = STDOUT else @xml_output = file.value end @widgets.each do |field, widget| @data_values[field] = widget.value #puts "updated #{field} to be #{widget.value}" end validate_data(@data_values) # Now get anything out of the req, conf, or provides @requires = @req_list.get(0, 'end').map do |stringrep| ACTMfg::RPM::RequireOrConflict.new_from_str(stringrep) end @conflicts = @conf_list.get(0, 'end').map do |stringrep| ACTMfg::RPM::RequireOrConflict.new_from_str(stringrep) end @provides = @prov_list.get(0, 'end') write_xml(@xml_output) @save_flag = true end @quit_proc = proc do # Empty the changes files and update the xml timestamp if (@auto_flag and @save_flag) @cust_file = @file_widgets['cust'].value if (not File.exists?(@cust_file) or File.size?(@cust_file)) File.open(@cust_file, 'w') {} end @dev_file = @file_widgets['dev'].value if (not File.exists?(@dev_file) or File.size?(@dev_file)) File.open(@dev_file, 'w') {} end file = @file_widgets['output'] if file.value == "" @xml_output = STDOUT else @xml_output = file.value end File.utime(Time.now, Time.now, @xml_output) end exit 0 end setup_initial_data(@xml_input) layout() # Read in the changes if (@auto_flag) File.open(@cust_file) do |file| @cust_text_entry.delete('1.0', 'end') @cust_text_entry.insert('end', file.read) end File.open(@dev_file) do |file| @dev_text_entry.delete('1.0', 'end') @dev_text_entry.insert('end', file.read) end end # Start the GUI Tk.mainloop() end def setup_initial_data(xml) # Data we care about is stored in Tk variables, for ease of use # with the GUI portions. # Get data from xml file get_xml_data(xml) # Hashes to let us get data back out of widgets @widgets = Hash.new @file_widgets = Hash.new end def get_xml_data(xml) # fake it! This level of indirection comes about because there # may be another way to prepopulate information for this data # (for example, an .rc or user info file). Keeping options open. @xml_reader = nil @xml_data = OpenStruct.new @package_name = '?' #@xml_data.packager = ACTMfg::RPM::Packager.new(nil, nil, nil) @xml_data.packager = OpenStruct.new @data_values = { 'Summary' => @xml_data.summary, 'Doc URL' => @xml_data.doc_url, 'Packager Name' => @xml_data.packager.name, 'Packager Phone' => @xml_data.packager.phone, 'Packager Email' => @xml_data.packager.email, 'Description' => @xml_data.description, 'Customer Changes' => @xml_data.raw_customer_changes, 'Developer Changes' => @xml_data.raw_developer_changes, 'Pre-install' => @xml_data.pre_install, 'Post-install' => @xml_data.post_install, 'Pre-uninstall' => @xml_data.pre_uninstall, 'Post-uninstall' => @xml_data.post_uninstall, } # More complex data here @requires = @xml_data.requires @requires = [] if @requires.nil? @conflicts = @xml_data.conflicts @conflicts = [] if @conflicts.nil? @provides = @xml_data.provides @provides = [] if @provides.nil? end def layout() package_name = @package_name @root = TkRoot.new() { title "Package XML for '#{package_name}'" } frame_opts = { "side"=>'top', 'fill'=>'both', 'expand'=>true, 'pady'=>3, 'padx'=>5 } @file_frame = TkFrame.new(@root) { relief 'ridge' borderwidth 3 }.pack(frame_opts) @field_frame = TkFrame.new(@root) { relief 'ridge' borderwidth 3 }.pack(frame_opts) @pkg_frame = TkFrame.new(@root) { relief 'ridge' borderwidth 3 }.pack(frame_opts) @text_frame = TkFrame.new(@root) { relief 'ridge' borderwidth 3 }.pack(frame_opts) setup_file_frame() setup_field_frame() setup_pkg_frame() setup_text_frame() end def create_file_entry (root, label_text, entry_value, b1_text, b1_proc, b2_text = nil, b2_proc = nil) label = TkLabel.new(root) { text label_text } entry = TkEntry.new(root) { width 60 } entry.insert(0, entry_value) b1 = TkButton.new(root) { text b1_text command b1_proc } if not b2_text.nil? b2 = TkButton.new(root) { text b2_text command b2_proc } TkGrid.configure(label, entry, b1, b2) else TkGrid.configure(label, entry, b1) end return entry end def setup_file_frame() entry = create_file_entry(@file_frame, 'Input XML', @xml_input, 'Load', @load_proc) @file_widgets['input'] = entry if @xml_output == STDOUT entry = create_file_entry(@file_frame, 'Output XML', '', 'Save', @save_proc, 'Quit', @quit_proc) else entry = create_file_entry(@file_frame, 'Output XML', @xml_output, 'Save', @save_proc, 'Quit', @quit_proc) end @file_widgets['output'] = entry entry = create_file_entry(@file_frame, 'Customer Changes', @cust_file, 'Load', @cust_load_proc, 'Clear', @cust_clear_proc) @file_widgets['cust'] = entry entry = create_file_entry(@file_frame, 'Developer Changes', @dev_file, 'Load', @dev_load_proc, 'Clear', @dev_clear_proc) @file_widgets['dev'] = entry end def setup_field_frame() ['Summary', 'Packager Name', 'Packager Phone', 'Packager Email', 'Doc URL'].each do |field| raise "internal error, no field named #{field}" unless @data_values.include?(field) value = @data_values[field] label = TkLabel.new(@field_frame, 'anchor'=>'w') { text field } entry = TkEntry.new(@field_frame) { width 50 } TkGrid.configure(label, entry) entry.insert(0, value) @widgets[field] = entry end end def setup_pkg_frame() # We need two RequireConflictWidgets, one for requires, one for # conflicts. A simple list will go in the third one for provides. @req_list = RequireConflictWidget.new(@pkg_frame, 'Requires', @requires) @conf_list = RequireConflictWidget.new(@pkg_frame, 'Conflicts', @conflicts) @prov_list = RequireConflictWidget.new(@pkg_frame, 'Provides', @provides, true) TkGrid.configure(@req_list.enclosing_frame, @conf_list.enclosing_frame, @prov_list.enclosing_frame) end def text_entry(field, buttons = true) label_opts = { 'relief' => 'solid', 'background' => 'lightblue' } left_frame = TkFrame.new(@text_frame) right_frame = TkFrame.new(@text_frame) label = TkLabel.new(left_frame, label_opts) { text field } #label.pack('side'=>'top') TkGrid.grid(label) text = TkText.new(right_frame) { width 72; height 10; wrap 'none' } sb = TkScrollbar.new(right_frame) text.yscrollbar(sb) sb2 = TkScrollbar.new(right_frame) text.xscrollbar(sb2) TkGrid.configure(text, sb, 'sticky'=>'ns') TkGrid.configure(sb2, 'sticky'=>'ew') TkGrid.configure(left_frame,right_frame, 'sticky'=>'ns') value = @data_values[field] text.insert('end', value) @widgets[field] = text import_button = nil clear_button = nil if buttons import_button = TkButton.new(left_frame, {"text"=>'Import from File'}) TkGrid.grid(import_button) # import_button.pack('side'=>'top') clear_button = TkButton.new(left_frame, {"text"=>'Clear File Contents'}) #clear_button.pack('side'=>'top') TkGrid.grid(clear_button) end return text, import_button, clear_button end def setup_text_frame() desc, d_but = text_entry('Description', false) @cust_text_entry, cust_import, cust_clear = text_entry('Customer Changes') cust_import.command @cust_load_proc cust_clear.command @cust_clear_proc @dev_text_entry, dev_import, dev_clear = text_entry('Developer Changes') dev_import.command @dev_load_proc dev_clear.command @dev_clear_proc button_frame = TkFrame.new(@text_frame) TkGrid.grid(button_frame, 'columnspan'=>2) save = TkButton.new(button_frame, {"text"=>'Save', 'command'=>@save_proc}) # clear_text = 'Clear customer and developer changes from files' # clear_old = TkButton.new(@bottom_frame, {"text"=>clear_text}) # clear_old.command { # File.delete(@cust_file) if File.exists?(@cust_file) # File.delete(@dev_file) if File.exists?(@dev_file) # } save.pack('side'=>'left') quit = TkButton.new(button_frame, {'text'=>'Quit', 'command'=>@quit_proc}) quit.pack('side'=>'left') end def validate_data(data) # The following must not be nil or empty ['Summary', 'Packager Name', 'Packager Phone', 'Packager Email' ].each do |field| value = data[field] if value.nil? or value.empty? raise "Illegal (empty) value for '#{field}'" end end # The following cannot have any line that starts with * ['Customer Changes', 'Developer Changes'].each do |field| text = data[field] text.each do |line| if line =~ /^\s*\*/ raise "Cannot have an asterisk at the start of a line in #{field}!" end end end end def write_xml(fn = @xml_output) header = <<-EOS EOS doc = REXML::Document.new(header) doc << REXML::XMLDecl.new(1.0, nil, false) root = REXML::Element.new('package', doc) root.attributes['summary'] = @data_values['Summary'] url = @data_values['Doc URL'] root.attributes['doc-url'] = url unless url.nil? or url.empty? desc = REXML::Element.new('description', root) REXML::CData.new(@data_values['Description'], true, desc) packager = REXML::Element.new('packager', root) packager.attributes['name'] = @data_values['Packager Name'] packager.attributes['email'] = @data_values['Packager Email'] packager.attributes['phone'] = @data_values['Packager Phone'] cust = REXML::Element.new('customer-changes', root) REXML::CData.new(@data_values['Customer Changes'], true, cust) dev = REXML::Element.new('developer-changes', root) REXML::CData.new(@data_values['Developer Changes'], true, dev) # Add req, conf, and provides @requires.each do |req| node = REXML::Element.new('require', root) node.attributes['name'] = req.name node.attributes['operator'] = req.xml_operator node.attributes['version'] = req.version end @conflicts.each do |req| node = REXML::Element.new('conflict', root) node.attributes['name'] = req.name node.attributes['operator'] = req.xml_operator node.attributes['version'] = req.version end @provides.each do |prov| node = REXML::Element.new('provide', root) node.attributes['name'] = prov end # Now, add optional elements ['Pre-install', 'Post-install', 'Pre-uninstall', 'Post-uninstall'].each do |hook| script = @data_values[hook] unless script.nil? or script.empty? node = REXML::Element.new("#{hook.downcase}-script", root) REXML::CData.new(script, true, node) end end case "#{fn.class}" when 'NilClass' doc.write(STDOUT, -1) when 'IO' doc.write(fn, -1) else File.open(fn, 'w') do |file| doc.write(file, -1) end end end end end end if __FILE__ == $0 # set up default variables auto_flag = false input_xml = nil output_xml = nil customer_txt = nil developer_txt = nil # Run the real code pg = ACTMfg::Tools::PackageGUI.new(auto_flag, input_xml, output_xml, customer_txt, developer_txt) end --------------000400030303070702020802--