tkxxs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.autotest +25 -0
- data/.gemtest +0 -0
- data/CONTRIBUTORS +2 -0
- data/History.txt +6 -0
- data/LICENSE +9 -0
- data/Manifest.txt +20 -0
- data/README.txt +168 -0
- data/Rakefile +42 -0
- data/ext/readme.txt +3 -0
- data/ext/tkballoonhelp.rb +332 -0
- data/ext/tkballoonhelp_ORIGINAL.rb +208 -0
- data/images/screenshot.png +0 -0
- data/lib/icon.gif +0 -0
- data/lib/tkxxs.rb +708 -0
- data/lib/tkxxs/conf.rb +259 -0
- data/lib/tkxxs/tkxxs_classes.rb +1571 -0
- data/lib/tkxxs/version.rb +4 -0
- data/rdoc_developers_hanna.bat +15 -0
- data/rdoc_users_hanna.bat +16 -0
- data/samples/big_example.rb +522 -0
- data/samples/small_example.rb +24 -0
- metadata +156 -0
data/lib/tkxxs/conf.rb
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
# encoding: windows-1252
|
2
|
+
# Copyright (c) 2010-2014 Axel Friedrich
|
3
|
+
$stdout.sync = true
|
4
|
+
$stderr.sync = true
|
5
|
+
$FILE_SEP = File::ALT_SEPARATOR || File::SEPARATOR
|
6
|
+
$__DIR__ = File.dirname( File.expand_path( __FILE__ ) ).gsub('\\', '/')
|
7
|
+
$__DIR0__ = File.dirname( File.expand_path( $0 ) ).gsub('\\', '/')
|
8
|
+
$: << File.dirname( File.expand_path( __FILE__ ) )
|
9
|
+
$:.uniq!
|
10
|
+
|
11
|
+
require 'fileutils'
|
12
|
+
require 'yaml'
|
13
|
+
require 'Platform'
|
14
|
+
|
15
|
+
class Object
|
16
|
+
##################################################################
|
17
|
+
# ,dup,nil,Fixnum,can't dup Fixnum
|
18
|
+
#
|
19
|
+
# Potential solution for x = x.dup if x; x = x.is_a?(Fixnum) ? x : x.dup
|
20
|
+
#
|
21
|
+
# From: comp.lang.ruby, Feb 17 2007, "Oppinions on RCR for dup on immutable classes"
|
22
|
+
def dup?
|
23
|
+
dup # Better?: true
|
24
|
+
rescue TypeError
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end # class Object
|
28
|
+
|
29
|
+
##########################################################################
|
30
|
+
##########################################################################
|
31
|
+
# Keywords: ,configuration ,ini ,preferences ,setup
|
32
|
+
# Helps storing and managing configuration data. At present, uses YAML-files to
|
33
|
+
# store the data.
|
34
|
+
#
|
35
|
+
# Important methods:
|
36
|
+
#
|
37
|
+
# Conf.new
|
38
|
+
# Conf#filename Returns the full path of file, where Conf is saved
|
39
|
+
# Conf#section= section section must be String, Integer, Float or nil, _not_ a Symbol
|
40
|
+
# Conf#or_default(key, defaultValue)
|
41
|
+
# Conf#[key]= value key must be a Symbol or String
|
42
|
+
# Conf#[key] Returns the stored value
|
43
|
+
# Conf#save Writes Conf to file
|
44
|
+
# Conf#instant_save = true/false: switches on save to file for every change of Conf
|
45
|
+
#
|
46
|
+
# Having keys and section of the same type and value is no problem.
|
47
|
+
#
|
48
|
+
# Example usage:
|
49
|
+
#
|
50
|
+
# require 'axel/conf'
|
51
|
+
# CONF = Conf.new # Filename for storing CONF can be given as attribute;
|
52
|
+
#
|
53
|
+
# class MyClass
|
54
|
+
#
|
55
|
+
# def initialize( )
|
56
|
+
# CONF.section = "my_section_1" # Optional, default is provided
|
57
|
+
# # Defines a section (hash key) where to read and store data
|
58
|
+
# p x = CONF.or_default( :x, 123 )
|
59
|
+
# # If run the first time, usually Conf["my_section"][:x] is not set to any value.
|
60
|
+
# # If Conf["my_section"][:x] is not set to any value, sets x to 123,
|
61
|
+
# # stores 123 in Conf["my_section"][:x] and saves it to the conf-File.
|
62
|
+
# # If run the next time, Conf["my_section"][:x] is already set to a value
|
63
|
+
# # and x is set to the value of Conf["my_section"][:x]
|
64
|
+
#
|
65
|
+
# p @y = CONF.or_default( :@y, "abc" )
|
66
|
+
#
|
67
|
+
# puts "Config-File is: " + CONF.filename
|
68
|
+
# myMethod(x)
|
69
|
+
# end # initialize
|
70
|
+
#
|
71
|
+
# def myMethod( x )
|
72
|
+
# x = x*x
|
73
|
+
# @y = "new_y"
|
74
|
+
#
|
75
|
+
# CONF[:x] = x # sets Conf["my_section"][:x] to the new value of x
|
76
|
+
# CONF[:@y] = @y
|
77
|
+
# puts "CONF[:x] is " + CONF[:x].inspect
|
78
|
+
# puts "CONF[:@y] is " + CONF[:@y].inspect
|
79
|
+
#
|
80
|
+
# CONF.save # saves the content of Conf.
|
81
|
+
# end # myMethod
|
82
|
+
#
|
83
|
+
# end # MyClass
|
84
|
+
#
|
85
|
+
# klass = MyClass.new
|
86
|
+
#
|
87
|
+
class Conf < Hash
|
88
|
+
|
89
|
+
alias of []
|
90
|
+
alias orig_to_hash to_hash
|
91
|
+
|
92
|
+
attr_accessor :instant_save
|
93
|
+
attr_reader :section
|
94
|
+
##########################################################################
|
95
|
+
# Attributes: configFileDNE: path including filename and fileextension
|
96
|
+
def initialize( configFileDNE=Conf.filename_proposal )
|
97
|
+
#p :xxx
|
98
|
+
raise unless configFileDNE
|
99
|
+
@configFileDNE = configFileDNE.dup.strip.gsub('\\', '/')
|
100
|
+
@instant_save = false
|
101
|
+
@section = nil
|
102
|
+
|
103
|
+
if File.file?( @configFileDNE )
|
104
|
+
dataFromFile = read
|
105
|
+
self.replace( dataFromFile ) if dataFromFile
|
106
|
+
end
|
107
|
+
#exit
|
108
|
+
self.section = nil unless self.has_key?(nil) # section "nil" always needed
|
109
|
+
|
110
|
+
super()
|
111
|
+
save() # writeable?
|
112
|
+
end # initialize
|
113
|
+
|
114
|
+
def delete_conf_file( )
|
115
|
+
File.delete( @configFileDNE )
|
116
|
+
end # delete_conf_file
|
117
|
+
|
118
|
+
def to_hash( )
|
119
|
+
h = Hash.new
|
120
|
+
self.each {|key, val|
|
121
|
+
h[key] = val.dup ? val.dup : val
|
122
|
+
}
|
123
|
+
h
|
124
|
+
end # to_hash
|
125
|
+
|
126
|
+
##################################################################
|
127
|
+
# Returns the filename of the config file
|
128
|
+
def filename( )
|
129
|
+
@configFileDNE
|
130
|
+
end # filename
|
131
|
+
|
132
|
+
##################################################################
|
133
|
+
# Returns the value corresponding to key, while Config is pointing to the
|
134
|
+
# section set by Conf::section= . Attributes: key: Symbol or String
|
135
|
+
def []( key )
|
136
|
+
raise "Key must be a Symbol or String" unless key.is_a?( Symbol ) || key.is_a?( String )
|
137
|
+
|
138
|
+
res = self.of(@section)[key]
|
139
|
+
res = res.dup? ? res.dup : res
|
140
|
+
res
|
141
|
+
end # []
|
142
|
+
|
143
|
+
##################################################################
|
144
|
+
# Sets the value for a corresponding key. If key does not exist, it will be created.
|
145
|
+
# Attributes: key: Symbol or String; value
|
146
|
+
def []=( key, value )
|
147
|
+
raise "key must be a Symbol or String" unless key.is_a?( Symbol ) || key.is_a?( String )
|
148
|
+
|
149
|
+
self.of(@section).store( key, value)
|
150
|
+
self.save if @instant_save
|
151
|
+
value
|
152
|
+
end # []=
|
153
|
+
|
154
|
+
##################################################################
|
155
|
+
# If key is not an existing key, key will be created with value =
|
156
|
+
# defaultValue and saves it to the config-File. Returns defaultValue.
|
157
|
+
#
|
158
|
+
# If key is an exsiting key, the correspondig value will be returned.
|
159
|
+
def or_default( key, defaultValue )
|
160
|
+
raise "key must be a Symbol or String" unless key.is_a?( Symbol ) || key.is_a?( String )
|
161
|
+
|
162
|
+
if self.of(@section).has_key?( key ) # ToDo: Nicer: create Conf::has_key?
|
163
|
+
res = self[key]
|
164
|
+
else
|
165
|
+
self[key]= defaultValue
|
166
|
+
res = defaultValue
|
167
|
+
self.save ## if @instant_save # 2009-06-11
|
168
|
+
end
|
169
|
+
|
170
|
+
res
|
171
|
+
end # default[]=
|
172
|
+
|
173
|
+
##################################################################
|
174
|
+
# Sets Config to a section. Different sections may have the same keys.
|
175
|
+
# section=nil is a valid section (it's somehow the 'root'-section)
|
176
|
+
# Attributes: section = any String, Integer or Float or nil; default is nil
|
177
|
+
def section=( section=nil )
|
178
|
+
unless ( section.is_a?(String) || section.is_a?(Integer) || section.is_a?(Float) || !section )
|
179
|
+
raise "Section must be String, Integer, Float or nil but #{ section.inspect } is a #{ section.class }!"
|
180
|
+
end
|
181
|
+
unless self.has_key?(section)
|
182
|
+
self.store(section, {})
|
183
|
+
self.save if @instant_save
|
184
|
+
end
|
185
|
+
|
186
|
+
@section = section.dup? ? section.dup : nil
|
187
|
+
end # section=
|
188
|
+
|
189
|
+
def read()
|
190
|
+
FileUtils.mkpath( File.dirname( @configFileDNE ) )
|
191
|
+
# Ruby 1.8.7 cannot read psych-formated YAML-Files. As of 2013-03-15, Ruby
|
192
|
+
# 1.8.7 and 1.9 can read syck-formated YAML Files. For compatibility, I
|
193
|
+
# choose syck-format.
|
194
|
+
YAML::ENGINE.yamler = "syck" if YAML.const_defined?( :ENGINE )
|
195
|
+
File.open( @configFileDNE ) { |f| YAML.load(f) }
|
196
|
+
end
|
197
|
+
|
198
|
+
## def save()
|
199
|
+
## FileUtils.mkpath( File.dirname( @configFileDNE ) )
|
200
|
+
## File.open( @configFileDNE, "w" ) { |f| YAML.dump(self, f) }
|
201
|
+
## end
|
202
|
+
|
203
|
+
def save()
|
204
|
+
FileUtils.mkpath( File.dirname( @configFileDNE ) )
|
205
|
+
YAML::ENGINE.yamler = "syck" if YAML.const_defined?( :ENGINE )
|
206
|
+
File.open( @configFileDNE, "w" ) { |f| YAML.dump(self, f) }
|
207
|
+
# I write a 2nd File in psych-format in case syck is not longer available
|
208
|
+
# sometimes.
|
209
|
+
if YAML.const_defined?( :ENGINE )
|
210
|
+
YAML::ENGINE.yamler = "psych"
|
211
|
+
File.open( @configFileDNE + '_psy', "w" ) { |f| YAML.dump(self, f) }
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
##################################################################
|
217
|
+
# Deletes the config-File and clears all data of Conf.
|
218
|
+
def delete( )
|
219
|
+
File.delete(@configFileDNE)
|
220
|
+
self.clear
|
221
|
+
self
|
222
|
+
end # delete
|
223
|
+
|
224
|
+
##################################################################
|
225
|
+
# From: Joel VanderWerf: "preferences-0.3.0".
|
226
|
+
# Utility method for guessing a suitable directory to store preferences.
|
227
|
+
# On win32, tries +APPDATA+, +USERPROFILE+, and +HOME+. On other platforms,
|
228
|
+
# tries +HOME+ and ~. Raises EnvError if preferences dir cannot be chosen.
|
229
|
+
# Not called by any of the Preferences library code, but available to the
|
230
|
+
# client code for constructing an argument to Preferences.new.
|
231
|
+
#
|
232
|
+
# Some modifications by Axel
|
233
|
+
def Conf.dir
|
234
|
+
case Platform::OS.to_s.downcase
|
235
|
+
when 'win32'
|
236
|
+
dir =
|
237
|
+
ENV['APPDATA'] || # C:\Documents and Settings\name\Application Data
|
238
|
+
ENV['USERPROFILE'] || # C:\Documents and Settings\name
|
239
|
+
ENV['HOME']
|
240
|
+
else
|
241
|
+
dir =
|
242
|
+
ENV['HOME'] ||
|
243
|
+
File.expand_path('~')
|
244
|
+
end
|
245
|
+
|
246
|
+
unless dir
|
247
|
+
raise EnvError, "Can't determine a configuration directory."
|
248
|
+
end
|
249
|
+
dir = dir.gsub('\\', '/')
|
250
|
+
dir
|
251
|
+
end # Conf.dir
|
252
|
+
|
253
|
+
def Conf.filename_proposal( extension='.cfg' )
|
254
|
+
fileN = File.basename( $0, File.extname($0) )
|
255
|
+
configFileDNE = File.join( Conf.dir, fileN, fileN + extension.to_s)
|
256
|
+
configFileDNE
|
257
|
+
end # Conf.filename_proposal
|
258
|
+
|
259
|
+
end # class Conf
|
@@ -0,0 +1,1571 @@
|
|
1
|
+
# encoding: Windows-1252 :encoding=Windows-1252:
|
2
|
+
# Copyright (c) Axel Friedrich 2010-2014
|
3
|
+
STDOUT.sync = true
|
4
|
+
STDERR.sync = true
|
5
|
+
|
6
|
+
##########################################################################
|
7
|
+
##########################################################################
|
8
|
+
class Hash
|
9
|
+
|
10
|
+
##################################################################
|
11
|
+
# Somewhat deeper dup
|
12
|
+
def dup2!( )
|
13
|
+
self.each_pair {|k,v|
|
14
|
+
self[k] = v.dup if v.class == String
|
15
|
+
}
|
16
|
+
self
|
17
|
+
end # dup2!
|
18
|
+
|
19
|
+
end # class Hash
|
20
|
+
|
21
|
+
##########################################################################
|
22
|
+
##########################################################################
|
23
|
+
# Creates dialogs for TKXXS
|
24
|
+
module TKXXS_CLASSES
|
25
|
+
|
26
|
+
##################################################################
|
27
|
+
# Purpose:
|
28
|
+
# Interpret last argument always as +Hash#, independent from the
|
29
|
+
# number of arguments:
|
30
|
+
# * Searches for the first argument which is a Hash, from right to
|
31
|
+
# left, starting with the last argument. (TODO: Why not simply
|
32
|
+
# detect if the last argument is a Hash?)
|
33
|
+
# * If Hash exists:
|
34
|
+
# ** Move the hash to the last element of args.
|
35
|
+
# ** Set the element, where the Hash comes from to nil, if it was
|
36
|
+
# not the last element.
|
37
|
+
# ** All other elements remain unchanged.
|
38
|
+
# * If no Hash exist:
|
39
|
+
# ** Sets the last element of args to {}
|
40
|
+
# ** All other elements remain unchanged.
|
41
|
+
#
|
42
|
+
# *Returns:* The rearranged args.
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
# def m1( c=nil,b=nil,a=nil,h=nil )
|
46
|
+
# c, b, a, hash = args_1(c,b,a,h)
|
47
|
+
# p [c, b, a, hash]
|
48
|
+
#
|
49
|
+
# hash = {:a=>'aa',
|
50
|
+
# :b=>'bb',
|
51
|
+
# :c=>'cc'
|
52
|
+
# }.merge(hash)
|
53
|
+
#
|
54
|
+
# c = hash[:c] unless c
|
55
|
+
# b = hash[:b] unless b
|
56
|
+
# a = hash[:a] unless a
|
57
|
+
# nil
|
58
|
+
# end # m1
|
59
|
+
#
|
60
|
+
# m1(:c,:b,:a, {:h=>'hh'}) # => [:c, :b, :a, {:h=>"hh"}]
|
61
|
+
# m1(:c, {:h=>'hh'}) # => [:c, nil, nil, {:h=>"hh"}]
|
62
|
+
# m1(:c) # => [:c, nil, nil, {}]
|
63
|
+
# m1() # => [nil, nil, nil, {}]
|
64
|
+
#
|
65
|
+
def TKXXS_CLASSES.args_1( *args )
|
66
|
+
args = args.reverse
|
67
|
+
args.each_with_index {|arg, i|
|
68
|
+
if arg.class == Hash
|
69
|
+
args[i] = nil
|
70
|
+
args[0] = arg
|
71
|
+
break
|
72
|
+
end
|
73
|
+
}
|
74
|
+
args[0] =
|
75
|
+
unless args[0]
|
76
|
+
{}
|
77
|
+
else
|
78
|
+
## Marshal.load(Marshal.dump( args[0] ) ) # Doesn't work because of Proc
|
79
|
+
args[0].dup2! # args[0] must always be a Hash
|
80
|
+
end
|
81
|
+
args = args.reverse
|
82
|
+
args
|
83
|
+
end # TKXXS_CLASSES.args_1
|
84
|
+
|
85
|
+
##################################################################
|
86
|
+
# Set the value of new variables from the values of the hash.
|
87
|
+
#
|
88
|
+
# New variables are _always_: +question+ and +help+.
|
89
|
+
#
|
90
|
+
# 'hash' gets modified.
|
91
|
+
#
|
92
|
+
# On the left side of the equal sign: _always_ 'help, hash,
|
93
|
+
# question', thereafter the additional variables.
|
94
|
+
#
|
95
|
+
# Arguments of args_2: 'help,hash,question' and then the (optional)
|
96
|
+
# hash-keys, which shall be assigned to the additional variables.
|
97
|
+
# :help and :question can be put at arbitrary position, or omitted.
|
98
|
+
# hash _must_ have: :configSection, :question.
|
99
|
+
#
|
100
|
+
# If the variable +help+ and/or +question+ are already set (not
|
101
|
+
# equal +nil+), then they leave unchanged.
|
102
|
+
#
|
103
|
+
# EXAMPLE:
|
104
|
+
# help = 'h'
|
105
|
+
# hash = {
|
106
|
+
# :help=>'heeelp',
|
107
|
+
# :question=>'MyQuestion',
|
108
|
+
# :title=>'MyTitle',
|
109
|
+
# :SomethingElse=>'bla'
|
110
|
+
# }
|
111
|
+
# help, hash, question, title =
|
112
|
+
# TKXXS_CLASSES.args_2( help, hash, question, :title)
|
113
|
+
# # => ["h", {:SomethingElse=>"bla"}, "MyQuestion", "MyTitle"]
|
114
|
+
#
|
115
|
+
# * 'h' in +help+ _not_ overwritten, because +help+ was defined before;
|
116
|
+
# * Every key in +hash+ deleted, which corresponds to the args of +args_2+;
|
117
|
+
# * +question+ now set, because it was _not_ defined before;
|
118
|
+
# * +title+ set to <tt>hash[:title]</tt>
|
119
|
+
def TKXXS_CLASSES.args_2( help,hash,question,*keys )
|
120
|
+
hash.delete(:configSection)
|
121
|
+
q = hash.delete(:question)
|
122
|
+
question = q unless question
|
123
|
+
h = hash.delete(:help)
|
124
|
+
help = h unless help
|
125
|
+
|
126
|
+
values = [help,hash,question]
|
127
|
+
keys.each {|key|
|
128
|
+
val = hash.delete(key)
|
129
|
+
values << val
|
130
|
+
}
|
131
|
+
values[1] = hash
|
132
|
+
values
|
133
|
+
end # TKXXS_CLASSES.args_2
|
134
|
+
|
135
|
+
##########################################################################
|
136
|
+
##########################################################################
|
137
|
+
class TextW < TkText
|
138
|
+
include Tk::Tile
|
139
|
+
def initialize( parent, hash={} )
|
140
|
+
#---- Frame
|
141
|
+
@frame = Frame.new(parent) {padding "3 3 3 3"}.
|
142
|
+
pack(:fill=>:both, :expand=>true)
|
143
|
+
|
144
|
+
hash = Marshal.load(Marshal.dump( hash ) ) # Should work because never uses Proc
|
145
|
+
|
146
|
+
hash = {
|
147
|
+
:font=>'"Courier New" 10',
|
148
|
+
:wrap=>'word',
|
149
|
+
:bd=>'2'
|
150
|
+
}.merge(hash)
|
151
|
+
|
152
|
+
#---- self = TkText
|
153
|
+
super(@frame, hash)
|
154
|
+
|
155
|
+
#---- Scrollbars
|
156
|
+
sy = Scrollbar.new(@frame)
|
157
|
+
sy.pack('side'=>'right', 'fill'=>'y')
|
158
|
+
self.yscrollbar(sy)
|
159
|
+
|
160
|
+
sx = Scrollbar.new(@frame)
|
161
|
+
sx.pack('side'=>'bottom', 'fill'=>'x')
|
162
|
+
self.xscrollbar(sx)
|
163
|
+
|
164
|
+
self.pack('expand'=>'yes', 'fill'=>'both')
|
165
|
+
end # initialize
|
166
|
+
|
167
|
+
end # class TextW
|
168
|
+
|
169
|
+
##########################################################################
|
170
|
+
##########################################################################
|
171
|
+
class AskSingleLineD < Tk::Tile::Entry
|
172
|
+
include Tk::Tile
|
173
|
+
CONF = nil unless defined?(CONF)
|
174
|
+
attr_accessor :dialog # in: "self.dialog
|
175
|
+
|
176
|
+
##########################################################################
|
177
|
+
# See: TKXXS.ask_single_line
|
178
|
+
def initialize( question=nil, help=nil, hash=nil )
|
179
|
+
question, help, hash = TKXXS_CLASSES.args_1(question, help, hash)
|
180
|
+
|
181
|
+
# Must always include: :question, :help, :configSection
|
182
|
+
hash = { # Default values
|
183
|
+
:question => "?",
|
184
|
+
:help => nil,
|
185
|
+
:configSection => nil,
|
186
|
+
:defaultEntry => '',
|
187
|
+
}.merge(hash)
|
188
|
+
|
189
|
+
# Necessary, because hash[:configSection] is deleted later on
|
190
|
+
# with +args_2+.
|
191
|
+
CONF.section = hash[:configSection] if CONF # ?
|
192
|
+
|
193
|
+
help, hash, question, defaultEntry =
|
194
|
+
TKXXS_CLASSES.args_2(help,hash,question,:defaultEntry)
|
195
|
+
|
196
|
+
teardownDone = false
|
197
|
+
@ans = nil
|
198
|
+
@goOn = goOn = TkVariable.new
|
199
|
+
# Because you cannot use instance-vars in procs. TODO: smarter way?
|
200
|
+
|
201
|
+
Tk.update # Show any calling widget first to make it lower than @dialog
|
202
|
+
#---- Toplevel: @dialog
|
203
|
+
@dialog=Toplevel.new() {title "Please enter your answer..."}
|
204
|
+
@dialog.geometry = CONF[:dialogGeom] if CONF
|
205
|
+
@dialog.raise
|
206
|
+
@dialog.bind('Destroy') {
|
207
|
+
unless teardownDone
|
208
|
+
if CONF
|
209
|
+
CONF[:dialogGeom] = @dialog.geometry
|
210
|
+
CONF.section = nil
|
211
|
+
end
|
212
|
+
goOn.value = '1' # !
|
213
|
+
teardownDone = true
|
214
|
+
end
|
215
|
+
}
|
216
|
+
|
217
|
+
#---- Frame
|
218
|
+
@frame = Frame.new(@dialog) {padding "3 3 3 3"}.
|
219
|
+
pack(:fill=>:both,:expand=>true)
|
220
|
+
|
221
|
+
#---- Label
|
222
|
+
@lbl = Label.new(@frame, :text=>question){
|
223
|
+
font $font if $font
|
224
|
+
wraplength '5i'
|
225
|
+
justify 'left'
|
226
|
+
}
|
227
|
+
@lbl.grid(:column=>0,:columnspan=>3,:sticky=>'ws')
|
228
|
+
|
229
|
+
#---- self = TkEntry
|
230
|
+
super(@frame, hash)
|
231
|
+
self.grid(:column=>0,:columnspan=>3,:sticky=>'ew')
|
232
|
+
self.insert(0, defaultEntry)
|
233
|
+
bind('Key-Return') {
|
234
|
+
ans_( self.get )
|
235
|
+
self.dialog.destroy
|
236
|
+
goOn.value = '1'
|
237
|
+
}
|
238
|
+
|
239
|
+
#---- Button-Cancel
|
240
|
+
@btn = Button.new(@frame, :text=>"Cancel").
|
241
|
+
grid(:column=>0,:row=>2)
|
242
|
+
@btn.command {
|
243
|
+
self.dialog.destroy
|
244
|
+
}
|
245
|
+
|
246
|
+
#---- Button-OK
|
247
|
+
@btn2 = Button.new(@frame, :text=>"OK").
|
248
|
+
grid(:column=>2,:row=>2)
|
249
|
+
@btn2.command {
|
250
|
+
ans_( self.get )
|
251
|
+
self.dialog.destroy
|
252
|
+
goOn.value = '1'
|
253
|
+
}
|
254
|
+
|
255
|
+
#---- Balloonhelp
|
256
|
+
if help
|
257
|
+
BalloonHelp.new(self, :text => help )
|
258
|
+
end
|
259
|
+
|
260
|
+
@frame.grid_columnconfigure([0,2],:weight=>0)
|
261
|
+
@frame.grid_columnconfigure(1,:weight=>1)
|
262
|
+
@frame.grid_rowconfigure(0,:weight=>1)
|
263
|
+
@frame.grid_rowconfigure([1,2],:weight=>0)
|
264
|
+
|
265
|
+
focus
|
266
|
+
update
|
267
|
+
end # initialize
|
268
|
+
|
269
|
+
##########################################################################
|
270
|
+
# *Returns*: (String or nil) The answer of the dialog. 'Cancel' returns nil.
|
271
|
+
def answer( )
|
272
|
+
@dialog.raise
|
273
|
+
@goOn.wait
|
274
|
+
@ans
|
275
|
+
end # ans
|
276
|
+
|
277
|
+
private
|
278
|
+
|
279
|
+
def private____________________( )
|
280
|
+
end # private____________________
|
281
|
+
|
282
|
+
def ans_( ans ) # TODO: Easier way?
|
283
|
+
@ans = ans.dup if ans
|
284
|
+
end # ans=
|
285
|
+
|
286
|
+
end # class AskSingleLineD
|
287
|
+
|
288
|
+
|
289
|
+
##########################################################################
|
290
|
+
##########################################################################
|
291
|
+
class AskMultiLineD < TkText
|
292
|
+
# class AskMultiLineD < TextW
|
293
|
+
include Tk::Tile
|
294
|
+
CONF = nil unless defined?(CONF)
|
295
|
+
attr_accessor :dialog # in: "self.dialog
|
296
|
+
|
297
|
+
##################################################################
|
298
|
+
# TODO: implement in TKXXS.
|
299
|
+
def initialize( question=nil, help=nil, hash=nil )
|
300
|
+
question, help, hash = TKXXS_CLASSES.args_1(question, help, hash)
|
301
|
+
|
302
|
+
# Must always include: :question, :help, :configSection
|
303
|
+
hash = {
|
304
|
+
:question => "?",
|
305
|
+
:help => nil,
|
306
|
+
:configSection => nil,
|
307
|
+
:title => "Please enter your answer..."
|
308
|
+
}.merge(hash)
|
309
|
+
|
310
|
+
# Necessary, because hash[:configSection] is deleted later on
|
311
|
+
# in args_2.
|
312
|
+
CONF.section = hash[:configSection] if CONF # ?
|
313
|
+
|
314
|
+
# help, hash, question, title =
|
315
|
+
# TKXXS_CLASSES.args_2( help, hash, question, :title)
|
316
|
+
help, hash, question, title =
|
317
|
+
TKXXS_CLASSES.args_2( help, hash, question, :title)
|
318
|
+
|
319
|
+
teardownDone = false
|
320
|
+
@ans = nil
|
321
|
+
@goOn = goOn = TkVariable.new
|
322
|
+
# Because you cannot use instance-vars in procs. TODO: smarter way?
|
323
|
+
|
324
|
+
Tk.update # Show any calling widget first to make it lower than @dialog
|
325
|
+
#---- Toplevel: @dialog
|
326
|
+
# @dialog=Toplevel.new() {title('title')}
|
327
|
+
@dialog=Toplevel.new() {title(title)}
|
328
|
+
@dialog.geometry = CONF[:dialogGeom] if CONF
|
329
|
+
@dialog.raise
|
330
|
+
@dialog.bind('Destroy') {
|
331
|
+
unless teardownDone
|
332
|
+
if CONF
|
333
|
+
CONF[:dialogGeom] = @dialog.geometry
|
334
|
+
CONF.section = nil
|
335
|
+
end
|
336
|
+
goOn.value = '1' # !
|
337
|
+
teardownDone = true
|
338
|
+
end
|
339
|
+
}
|
340
|
+
|
341
|
+
#---- Frame
|
342
|
+
@frame = Frame.new(@dialog) {padding "3 3 3 3"}.
|
343
|
+
pack(:fill=>:both,:expand=>true)
|
344
|
+
|
345
|
+
#---- Label
|
346
|
+
@lbl = Label.new(@frame, :text=>question){
|
347
|
+
font $font if $font
|
348
|
+
wraplength '5i'
|
349
|
+
justify 'left'
|
350
|
+
}
|
351
|
+
@lbl.grid(:sticky=>'news')
|
352
|
+
|
353
|
+
#---- self = TextW
|
354
|
+
super(@frame, hash)
|
355
|
+
self.grid(:column=>0,:row=>1,:sticky=>'news')
|
356
|
+
@tagSel = TkTextTagSel.new(self)
|
357
|
+
bind('Control-Key-a') {
|
358
|
+
# From:
|
359
|
+
# "Using Control-a to select all text in a text widget : TCL",
|
360
|
+
# http://objectmix.com/tcl/35276-using-control-select-all-text-text-widget.html
|
361
|
+
@tagSel.add('0.0', :end)
|
362
|
+
Kernel.raise TkCallbackBreak
|
363
|
+
}
|
364
|
+
##/ bind('Key-Return') {
|
365
|
+
##/ ans_( self.get )
|
366
|
+
##/ self.dialog.destroy
|
367
|
+
##/ goOn.value = '1'
|
368
|
+
##/ }
|
369
|
+
|
370
|
+
#---- Scrollbars
|
371
|
+
sy = Scrollbar.new(@frame)
|
372
|
+
sy.grid(:column=>1,:row=>1,:sticky=>'ns')
|
373
|
+
self.yscrollbar(sy)
|
374
|
+
|
375
|
+
sx = Scrollbar.new(@frame)
|
376
|
+
sx.grid(:column=>0,:row=>2,:sticky=>'ew')
|
377
|
+
self.xscrollbar(sx)
|
378
|
+
|
379
|
+
#---- Button-Cancel
|
380
|
+
@btn = Button.new(@frame, :text=>"Cancel").
|
381
|
+
grid(:row=>3,:column=>0,:sticky=>'w')
|
382
|
+
@btn.command {
|
383
|
+
self.dialog.destroy
|
384
|
+
}
|
385
|
+
|
386
|
+
#---- Button-OK
|
387
|
+
@btn2 = Button.new(@frame, :text=>"OK").
|
388
|
+
grid(:row=>3,:column=>0,:sticky=>'e')
|
389
|
+
## grid(:row=>3,:column=>0,:sticky=>'e',:columnspan=>2)
|
390
|
+
@btn2.command {
|
391
|
+
got = self.get('0.0', 'end -1c')
|
392
|
+
ans_( got )
|
393
|
+
self.dialog.destroy
|
394
|
+
goOn.value = '1'
|
395
|
+
}
|
396
|
+
|
397
|
+
@frame.grid_columnconfigure(0, :weight=>1)
|
398
|
+
@frame.grid_columnconfigure(1, :weight=>0)
|
399
|
+
@frame.grid_rowconfigure([0,2,3], :weight=>0)
|
400
|
+
@frame.grid_rowconfigure(1, :weight=>1)
|
401
|
+
@lbl.bind('Configure'){
|
402
|
+
@lbl.wraplength = TkWinfo.width(@frame) - 40
|
403
|
+
}
|
404
|
+
|
405
|
+
#---- Balloonhelp
|
406
|
+
BalloonHelp.new(self,:text => help) if help
|
407
|
+
|
408
|
+
self.focus
|
409
|
+
##Tk.update
|
410
|
+
end # initialize
|
411
|
+
|
412
|
+
def answer( )
|
413
|
+
@dialog.raise
|
414
|
+
@goOn.wait
|
415
|
+
@ans
|
416
|
+
end # ans
|
417
|
+
|
418
|
+
private
|
419
|
+
|
420
|
+
def private____________________( )
|
421
|
+
end # private____________________
|
422
|
+
|
423
|
+
def ans_( ans ) # Is there an easier way?
|
424
|
+
@ans = ans.dup if ans
|
425
|
+
end # ans=
|
426
|
+
|
427
|
+
end # class AskMultiLineD
|
428
|
+
|
429
|
+
|
430
|
+
##########################################################################
|
431
|
+
##########################################################################
|
432
|
+
class SingleChoiceD < TkListbox
|
433
|
+
include Tk::Tile
|
434
|
+
CONF = nil unless defined?(CONF)
|
435
|
+
|
436
|
+
attr_accessor :dialog # in: "self.dialog
|
437
|
+
|
438
|
+
|
439
|
+
##########################################################################
|
440
|
+
# See: TKXXS::single_choice
|
441
|
+
def initialize( aryWithChoices, help=nil, hash=nil )
|
442
|
+
aryWithChoices, help, hash = TKXXS_CLASSES.args_1(aryWithChoices, help, hash)
|
443
|
+
|
444
|
+
@allChoices, @allClients, @allHelp = all_choices_clients_help(aryWithChoices)
|
445
|
+
|
446
|
+
# Must always include: :question, :help, :configSection
|
447
|
+
hash = {
|
448
|
+
:question => "Choose one by single-click",
|
449
|
+
:help => nil,
|
450
|
+
:configSection => nil,
|
451
|
+
:title => 'Choose one',
|
452
|
+
:bd=>'2', # TODO: noch ben�tigt?
|
453
|
+
:searchFieldHelp => "Enter RegExp for filter list here\n(not case sensitive)",
|
454
|
+
:returnChoiceAndClient=>false
|
455
|
+
}.merge(hash)
|
456
|
+
|
457
|
+
# Necessary, because hash[:configSection] is deleted later on
|
458
|
+
# in args_2.
|
459
|
+
CONF.section = hash[:configSection] if CONF # ?
|
460
|
+
|
461
|
+
# help, hash, question, title =
|
462
|
+
# TKXXS_CLASSES.args_2( help, hash, question, :title)
|
463
|
+
help,hash,question,title,searchFieldHelp,@returnChoiceAndClient=
|
464
|
+
TKXXS_CLASSES.args_2(help,hash,question,:title,:searchFieldHelp,:returnChoiceAndClient)
|
465
|
+
@allHelp ||= help
|
466
|
+
|
467
|
+
teardownDone = false
|
468
|
+
@ans = nil
|
469
|
+
@goOn = goOn = TkVariable.new
|
470
|
+
# Because you cannot use instance-vars in procs. TODO: smarter way?
|
471
|
+
|
472
|
+
@choices, @clients, @help = @allChoices, @allClients, @allHelp
|
473
|
+
|
474
|
+
Tk.update # Show any calling widget first to make it lower than @dialog
|
475
|
+
#---- Toplevel: @dialog
|
476
|
+
@dialog=Toplevel.new() {title(title)}
|
477
|
+
@dialog.geometry = CONF[:dialogGeom] if CONF
|
478
|
+
@dialog.bind('Destroy') {
|
479
|
+
unless teardownDone
|
480
|
+
goOn.value = '1' # !
|
481
|
+
$tkxxs_.delete(object_id)
|
482
|
+
if CONF
|
483
|
+
CONF[:dialogGeom] = @dialog.geometry
|
484
|
+
CONF.section = nil
|
485
|
+
##/ CONF.save
|
486
|
+
end
|
487
|
+
teardownDone = true
|
488
|
+
end # unless
|
489
|
+
}
|
490
|
+
|
491
|
+
#---- Top Frame
|
492
|
+
@frame = Frame.new(@dialog) {padding "3 3 3 3"}.
|
493
|
+
pack(:fill=>:both,:expand=>true)
|
494
|
+
|
495
|
+
#---- Label
|
496
|
+
@lbl = Label.new(@frame, :text=>question).
|
497
|
+
grid(:sticky=>'ew')
|
498
|
+
|
499
|
+
#---- Search-Entry
|
500
|
+
filterRegex = nil
|
501
|
+
@entry = Entry.new(@frame) {|ent|
|
502
|
+
grid(:sticky=>'ew')
|
503
|
+
BalloonHelp.new(ent,:text => searchFieldHelp)
|
504
|
+
}
|
505
|
+
|
506
|
+
@entry.bind('Key-Return') { list_entry_chosen( goOn ) }
|
507
|
+
@entry.bind('Key-Down') { self.focus }
|
508
|
+
@entry.bind('Any-KeyRelease') {
|
509
|
+
entryVal = @entry.get
|
510
|
+
begin
|
511
|
+
filterRegex = Regexp.new(entryVal, Regexp::IGNORECASE)
|
512
|
+
populate_list( filterRegex )
|
513
|
+
rescue RegexpError
|
514
|
+
@lbl.text = "RegexpError"
|
515
|
+
else
|
516
|
+
@lbl.text = question
|
517
|
+
end
|
518
|
+
|
519
|
+
}
|
520
|
+
@entry.focus
|
521
|
+
|
522
|
+
# self = TkListbox
|
523
|
+
# TODO: Kein Weg, das interne Padding zu vergr��ern??
|
524
|
+
super(@frame, hash)
|
525
|
+
self.grid(:column=>0,:sticky=>'news')
|
526
|
+
self.bind('ButtonRelease-1') {
|
527
|
+
list_entry_chosen( goOn )
|
528
|
+
}
|
529
|
+
self.bind('Key-Return') {
|
530
|
+
list_entry_chosen( goOn )
|
531
|
+
}
|
532
|
+
|
533
|
+
#---- Scrollbar
|
534
|
+
scrollb = Scrollbar.new(@frame).grid(:column=>1,:row=>2,:sticky=>'ns')
|
535
|
+
self.yscrollbar(scrollb)
|
536
|
+
##self.width = 0 # Width as needed
|
537
|
+
##self.height = 30 # TODO: Make configurable
|
538
|
+
|
539
|
+
#---- Button-Cancel
|
540
|
+
@btn = Button.new(@frame, :text=>"Cancel").
|
541
|
+
grid(:row=>3,:column=>0,:sticky=>'w')
|
542
|
+
@btn.command {
|
543
|
+
self.destroy
|
544
|
+
}
|
545
|
+
|
546
|
+
@frame.grid_columnconfigure(0, :weight=>1)
|
547
|
+
@frame.grid_columnconfigure(1, :weight=>0)
|
548
|
+
@frame.grid_rowconfigure([0,1,3], :weight=>0)
|
549
|
+
@frame.grid_rowconfigure(2, :weight=>1)
|
550
|
+
|
551
|
+
#---- Balloonhelp
|
552
|
+
if @help
|
553
|
+
$tkxxs_[object_id] = {:listBalloonHelp=>@help.dup}
|
554
|
+
list_balloonhelp
|
555
|
+
end
|
556
|
+
|
557
|
+
populate_list(nil)
|
558
|
+
##/ insert(0, *allChoices)
|
559
|
+
##/ self.selection_set 0
|
560
|
+
update
|
561
|
+
end # initialize
|
562
|
+
|
563
|
+
def all_choices_clients_help( aryWithChoices, filterRegex=nil )
|
564
|
+
return [ [], [], nil] if !aryWithChoices || aryWithChoices.empty?
|
565
|
+
|
566
|
+
case aryWithChoices[0].class.to_s
|
567
|
+
when 'String'
|
568
|
+
choices = aryWithChoices
|
569
|
+
clients = choices.dup
|
570
|
+
## choicesAndClients = choices.zip(clients)
|
571
|
+
help = nil
|
572
|
+
when 'Array'
|
573
|
+
choices, clients, tmpHelp = aryWithChoices.transpose
|
574
|
+
## choicesAndClients = aryWithChoices
|
575
|
+
help = tmpHelp if tmpHelp
|
576
|
+
else
|
577
|
+
raise "ERROR: aryWithChoices[0].class must be 'String' or 'Array',\n" +
|
578
|
+
"but is #{ aryWithChoices[0].inspect }.class = #{ aryWithChoices[0].class } "
|
579
|
+
end
|
580
|
+
|
581
|
+
[choices, clients, help]
|
582
|
+
end # all_choices_clients_help
|
583
|
+
|
584
|
+
def populate_list( filterRegex=nil )
|
585
|
+
if filterRegex
|
586
|
+
@choices, @clients, help = filtered_choices_clients_help( filterRegex )
|
587
|
+
else
|
588
|
+
@choices, @clients, help = @allChoices, @allClients, @allHelp
|
589
|
+
end
|
590
|
+
|
591
|
+
self.delete(0, :end)
|
592
|
+
if @choices.empty?
|
593
|
+
self.insert(0, [] )
|
594
|
+
else
|
595
|
+
self.insert(0, *@choices)
|
596
|
+
self.selection_set(0)
|
597
|
+
self.activate(0)
|
598
|
+
## puts sprintf("--DEBUG: $tkxxs_: %1s ", $tkxxs_.inspect) #loe
|
599
|
+
## puts sprintf("--DEBUG: object_id: %1s ", object_id.inspect) #loe
|
600
|
+
## puts sprintf("--DEBUG: help: %1s ", help.inspect) #loe
|
601
|
+
$tkxxs_[object_id][:listBalloonHelp].replace(help) if help
|
602
|
+
end
|
603
|
+
end # populate_list
|
604
|
+
|
605
|
+
def filtered_choices_clients_help( filterRegex )
|
606
|
+
choices = []
|
607
|
+
clients = []
|
608
|
+
help = []
|
609
|
+
@allChoices.each_with_index {|ch, idx|
|
610
|
+
if ch[filterRegex]
|
611
|
+
choices << ch
|
612
|
+
clients << @allClients[idx]
|
613
|
+
help << @allHelp[idx] if @allHelp # ToDo: Gef�hrlich, falls nicht Array
|
614
|
+
end
|
615
|
+
}
|
616
|
+
help = nil if help.empty? # +2010-02-15 F�r pathchooser
|
617
|
+
|
618
|
+
[choices, clients, help]
|
619
|
+
end # filtered_choices_clients_help
|
620
|
+
|
621
|
+
def list_entry_chosen( goOn )
|
622
|
+
idx = self.curselection[0]
|
623
|
+
ans_( idx )
|
624
|
+
goOn.value = '1'
|
625
|
+
end # list_entry_chosen
|
626
|
+
|
627
|
+
def list_balloonhelp( )
|
628
|
+
bbb = BalloonHelp.new(self,
|
629
|
+
:command=>proc{|x,y,bhelp,parent|
|
630
|
+
idx = parent.nearest(y)
|
631
|
+
bhelp.text($tkxxs_[object_id][:listBalloonHelp][idx])
|
632
|
+
}
|
633
|
+
)
|
634
|
+
nil
|
635
|
+
end # list_balloonhelp
|
636
|
+
|
637
|
+
##################################################################
|
638
|
+
# *Returns:*
|
639
|
+
# The right side of +aryWithChoices+ if :returnChoiceAndClient == +false+ (default),
|
640
|
+
# both sides of +aryWithChoices+ if :returnChoiceAndClient == +true+,
|
641
|
+
# +nil+, if 'Cancel' was clicked.
|
642
|
+
def answer( )
|
643
|
+
@goOn.wait
|
644
|
+
self.dialog.destroy
|
645
|
+
idx = @ans
|
646
|
+
if @returnChoiceAndClient
|
647
|
+
@ans = [ @choices[idx], @clients[idx] ]
|
648
|
+
res = @ans
|
649
|
+
else
|
650
|
+
res = @ans ? @clients[idx] : nil
|
651
|
+
end
|
652
|
+
res
|
653
|
+
end # ans
|
654
|
+
|
655
|
+
##/ def answer( opts={:both=>false} )
|
656
|
+
##/ @goOn.wait
|
657
|
+
##/ self.dialog.destroy
|
658
|
+
##/ idx = @ans
|
659
|
+
##/ if opts[:both]
|
660
|
+
##/ @ans = [ @choices[idx], @clients[idx] ]
|
661
|
+
##/ res = @ans
|
662
|
+
##/ else
|
663
|
+
##/ res = @ans ? @clients[idx] : nil
|
664
|
+
##/ end
|
665
|
+
##/ res
|
666
|
+
##/ end # ans
|
667
|
+
|
668
|
+
private
|
669
|
+
|
670
|
+
def private____________________( )
|
671
|
+
end # private____________________
|
672
|
+
|
673
|
+
def ans_( ans ) # Is there an easier way?
|
674
|
+
@ans = ans if ans
|
675
|
+
end # ans=
|
676
|
+
|
677
|
+
end # class SingleChoiceD
|
678
|
+
|
679
|
+
##########################################################################
|
680
|
+
##########################################################################
|
681
|
+
# MultiChoiceD has no search box like SingleChoiceD, up to now.
|
682
|
+
class MultiChoiceD < TkListbox
|
683
|
+
include Tk::Tile
|
684
|
+
CONF = nil unless defined?(CONF)
|
685
|
+
attr_accessor :dialog # in: "self.dialog
|
686
|
+
|
687
|
+
##################################################################
|
688
|
+
# See: TKXXS::multi_choice
|
689
|
+
def initialize( aryWithChoices, help=nil, hash=nil )
|
690
|
+
aryWithChoices, help, hash = TKXXS_CLASSES.args_1(aryWithChoices, help, hash)
|
691
|
+
hash = {
|
692
|
+
:selectmode => :multiple,
|
693
|
+
:question => "Choose multiple:",
|
694
|
+
:help => nil,
|
695
|
+
:bd=>'2',
|
696
|
+
:configSection => nil,
|
697
|
+
:title=>'Please choose multiple...',
|
698
|
+
:returnChoiceAndClient=>false
|
699
|
+
}.merge(hash)
|
700
|
+
|
701
|
+
# Necessary, because hash[:configSection] is deleted later on
|
702
|
+
# in args_2.
|
703
|
+
CONF.section = hash[:configSection] if CONF # ?
|
704
|
+
|
705
|
+
help,hash,question,title,searchFieldHelp,@returnChoiceAndClient=
|
706
|
+
TKXXS_CLASSES.args_2(help,hash,question,:title,:searchFieldHelp,:returnChoiceAndClient)
|
707
|
+
|
708
|
+
##/ question = hash[:question] unless question
|
709
|
+
##/ help = hash[:help] unless help
|
710
|
+
##/ CONF.section = hash[:configSection] if CONF
|
711
|
+
##/
|
712
|
+
##/ hash.delete :question
|
713
|
+
##/ hash.delete :help
|
714
|
+
##/ hash.delete :configSection
|
715
|
+
|
716
|
+
teardownDone = false
|
717
|
+
@ans = nil
|
718
|
+
@goOn = goOn = TkVariable.new
|
719
|
+
# Because you cannot use instance-vars in procs. TODO: smarter way?
|
720
|
+
case aryWithChoices[0].class.to_s
|
721
|
+
when 'String'
|
722
|
+
choices = aryWithChoices
|
723
|
+
clients = choices.dup
|
724
|
+
choicesAndClients = choices.zip(clients)
|
725
|
+
when 'Array'
|
726
|
+
choices, clients, tmpHelp = aryWithChoices.transpose
|
727
|
+
choicesAndClients = aryWithChoices
|
728
|
+
help = tmpHelp if tmpHelp
|
729
|
+
else
|
730
|
+
raise "ERROR: aryWithChoices[0].class must be 'String' or 'Array',\nbut is #{ aryWithChoices[0].inspect }.class = #{ aryWithChoices[0].class } "
|
731
|
+
end
|
732
|
+
|
733
|
+
Tk.update # Show any calling widget first to make it lower than @dialog
|
734
|
+
#---- Toplevel: @dialog
|
735
|
+
@dialog=Toplevel.new() {title(title)}
|
736
|
+
@dialog.geometry = CONF[:dialogGeom] if CONF
|
737
|
+
@dialog.bind('Destroy') {
|
738
|
+
unless teardownDone
|
739
|
+
goOn.value = '1' # !
|
740
|
+
if CONF
|
741
|
+
CONF[:dialogGeom] = @dialog.geometry
|
742
|
+
CONF.section = nil
|
743
|
+
end
|
744
|
+
teardownDone = true
|
745
|
+
end # unless
|
746
|
+
}
|
747
|
+
|
748
|
+
#---- Frame
|
749
|
+
@frame = Frame.new(@dialog) {padding "3 3 3 3"}.
|
750
|
+
pack(:fill=>:both,:expand=>true)
|
751
|
+
|
752
|
+
#---- Label
|
753
|
+
@lbl = Label.new(@frame){
|
754
|
+
text question
|
755
|
+
font $font if $font
|
756
|
+
wraplength '400'
|
757
|
+
justify 'left'
|
758
|
+
}
|
759
|
+
@lbl.grid(:sticky=>'news')
|
760
|
+
|
761
|
+
#---- self = TkListbox
|
762
|
+
super(@frame, hash)
|
763
|
+
# TODO: Is there no way to increase the internal padding??
|
764
|
+
#self.pack(:side=>:left,:expand=>1,:fill=>:both)
|
765
|
+
self.grid(:sticky=>'news')
|
766
|
+
|
767
|
+
#---- Scrollbar
|
768
|
+
scrollb = Scrollbar.new(@frame).
|
769
|
+
grid(:row=>1,:column=>1,:sticky=>'news')
|
770
|
+
self.yscrollbar(scrollb)
|
771
|
+
|
772
|
+
#---- Button-Cancel
|
773
|
+
@btn = Button.new(@frame, :text=>"Cancel").
|
774
|
+
grid(:row=>2,:column=>0,:sticky=>'w')
|
775
|
+
@btn.command {
|
776
|
+
self.destroy
|
777
|
+
}
|
778
|
+
|
779
|
+
#---- Button-OK
|
780
|
+
@btn = Button.new(@frame, :text=>"OK").
|
781
|
+
grid(:row=>2,:column=>0,:sticky=>'e')
|
782
|
+
@btn.command {
|
783
|
+
ans = []
|
784
|
+
# TODO: Change to self.curselection like in SingleChoiceD
|
785
|
+
begin
|
786
|
+
choicesAndClients.each_with_index {|cc, idx|
|
787
|
+
ans << cc if self.selection_includes(idx)
|
788
|
+
}
|
789
|
+
rescue
|
790
|
+
ans = []
|
791
|
+
end
|
792
|
+
ans_( ans )
|
793
|
+
goOn.value = '1'
|
794
|
+
}
|
795
|
+
|
796
|
+
@frame.grid_columnconfigure(0, :weight=>1)
|
797
|
+
@frame.grid_columnconfigure(1, :weight=>0)
|
798
|
+
@frame.grid_rowconfigure([0,2], :weight=>0)
|
799
|
+
@frame.grid_rowconfigure(1, :weight=>1)
|
800
|
+
insert(0, *choices)
|
801
|
+
@lbl.bind('Configure'){
|
802
|
+
@lbl.wraplength = TkWinfo.width(@frame) - 40
|
803
|
+
}
|
804
|
+
|
805
|
+
#---- Balloonhelp
|
806
|
+
if help
|
807
|
+
BalloonHelp.new(self,
|
808
|
+
:command=>proc{|x,y,bhelp,parent|
|
809
|
+
idx = parent.nearest(y)
|
810
|
+
bhelp.text( help[idx] )
|
811
|
+
}
|
812
|
+
)
|
813
|
+
end
|
814
|
+
|
815
|
+
update
|
816
|
+
end # initialize
|
817
|
+
|
818
|
+
##/ def answer( )
|
819
|
+
##/ @goOn.wait
|
820
|
+
##/ self.dialog.destroy # includes grab("release")
|
821
|
+
##/ @ans = [] unless @ans
|
822
|
+
##/ @ans
|
823
|
+
##/ end # ans
|
824
|
+
##################################################################
|
825
|
+
# *Returns:*
|
826
|
+
# * An Array of the chosen right sides of +aryWithChoices+ if :returnChoiceAndClient == +false+ (default),
|
827
|
+
# * An Array of the chosen right and left sides of +aryWithChoices+ if :returnChoiceAndClient == +true+,
|
828
|
+
# * +nil+, if 'Cancel' was clicked.
|
829
|
+
#
|
830
|
+
def answer( )
|
831
|
+
@goOn.wait
|
832
|
+
self.dialog.destroy
|
833
|
+
@ans = [] unless @ans
|
834
|
+
|
835
|
+
if @returnChoiceAndClient
|
836
|
+
return @ans.transpose[0].zip(@ans.transpose[1]) # wg. help
|
837
|
+
else
|
838
|
+
return @ans.transpose[1]
|
839
|
+
end
|
840
|
+
end # ans
|
841
|
+
|
842
|
+
private
|
843
|
+
|
844
|
+
def private____________________( )
|
845
|
+
end # private____________________
|
846
|
+
|
847
|
+
def ans_( ans ) # Is there an easier way?
|
848
|
+
@ans = ans.dup if ans
|
849
|
+
ans
|
850
|
+
end # ans=
|
851
|
+
|
852
|
+
end # class MultiChoiceD
|
853
|
+
|
854
|
+
class FileAndDirChooser
|
855
|
+
include Tk::Tile
|
856
|
+
CONF = nil unless defined?(CONF)
|
857
|
+
|
858
|
+
attr_accessor :dialog # in: "self.dialog
|
859
|
+
|
860
|
+
##########################################################################
|
861
|
+
# This class provides the common functions for all dir- and file-choosers.
|
862
|
+
#
|
863
|
+
# To get this dialog explain, run the example and point the mouse
|
864
|
+
# at each button.
|
865
|
+
#
|
866
|
+
# *Params*:
|
867
|
+
# * +initialdir+ - (String, optional) Initial dir; default = +nil+
|
868
|
+
# -> Working dir at the time of calling this method.
|
869
|
+
# * +help+ - (String, optional) ; Text used in the BalloonHelp;
|
870
|
+
# default = +nil+ -> No help.
|
871
|
+
# * +hash+ - (Hash, optional)
|
872
|
+
# * <tt>:initialdir</tt> - Like above.
|
873
|
+
# * <tt>:help</tt> - Like above.
|
874
|
+
# * <tt>:mode</tt> - One of :choosedir, :openfile, :openfiles, :savefile.
|
875
|
+
# * <tt>:question</tt> - (String) Your question; +nil+ -> no question.
|
876
|
+
# * <tt>:title</tt> - (String) Title of the dialog window.
|
877
|
+
# * <tt>:defaultEntry</tt> - (String) Path, shown in the entry field.
|
878
|
+
# * <tt>:validate</tt> - +true+ or +false+; if true, a valid path
|
879
|
+
# must be chosen, canceling the dialog is not possible.
|
880
|
+
# * <tt>:configSection</tt> - (any String, Integer or Float or nil) Not
|
881
|
+
# important. Sets the section in the config-file, where for example the
|
882
|
+
# window size and position is stored.
|
883
|
+
def initialize( initialdir=nil,help=nil,hash=nil )
|
884
|
+
initialdir, help, hash =
|
885
|
+
TKXXS_CLASSES.args_1( initialdir,help,hash )
|
886
|
+
|
887
|
+
# Must always include: :question, :help, :configSection
|
888
|
+
hash = {
|
889
|
+
:mode=>:openfiles,
|
890
|
+
:initialdir => Dir.pwd,
|
891
|
+
:question => "Please choose the desired files:",
|
892
|
+
:help => nil,
|
893
|
+
:configSection => nil,
|
894
|
+
:title => 'Choose Files',
|
895
|
+
:defaultEntry => '',
|
896
|
+
:validate=>nil,
|
897
|
+
}.merge(hash)
|
898
|
+
|
899
|
+
# Necessary, because hash[:configSection] is deleted later on
|
900
|
+
# in args_2.
|
901
|
+
CONF.section = hash[:configSection] if CONF # ?
|
902
|
+
|
903
|
+
help,hash,question,initialdirH,mode,defaultEntry,@validate =
|
904
|
+
TKXXS_CLASSES.args_2(help,hash,question,:initialdir,:mode,:defaultEntry,:validate)
|
905
|
+
hash[:initialdir] = initialdir || initialdirH # Tja, etwas umstaendlich
|
906
|
+
|
907
|
+
@teardownDone = false
|
908
|
+
@mode = mode
|
909
|
+
@paths = nil
|
910
|
+
@ans = nil
|
911
|
+
@goOn = goOn = TkVariable.new
|
912
|
+
# Because you cannot use instance-vars in procs. TODO: smarter way?
|
913
|
+
|
914
|
+
Tk.update # Show any calling widget first to make it lower than @dialog
|
915
|
+
|
916
|
+
#---- Toplevel: @dialog
|
917
|
+
@dialog=Tk::Toplevel.new() {title(hash[:title])}
|
918
|
+
@dialog.geometry = CONF[:pathChooserGeom] if CONF
|
919
|
+
|
920
|
+
# This is neccessary for handling the close-button ('X') of the window
|
921
|
+
@dialog.protocol(:WM_DELETE_WINDOW) {validate_and_leave}
|
922
|
+
|
923
|
+
##/ @dialog.bind('Destroy') {
|
924
|
+
##/ unless teardownDone
|
925
|
+
##/ goOn.value = '1' # !
|
926
|
+
##/ $tkxxs_.delete(object_id)
|
927
|
+
##/ if CONF
|
928
|
+
##/ CONF[:pathChooserGeom] = @dialog.geometry
|
929
|
+
##/ CONF.section = nil
|
930
|
+
##/ ##/ CONF.save
|
931
|
+
##/ end
|
932
|
+
##/ teardownDone = true
|
933
|
+
##/ end # unless
|
934
|
+
##/ }
|
935
|
+
|
936
|
+
#---- Top Frame
|
937
|
+
@frame = Frame.new(@dialog) {padding "3 3 3 3"}.
|
938
|
+
pack(:fill=>:both,:expand=>true)
|
939
|
+
|
940
|
+
#---- Label (question)
|
941
|
+
@qLbl = question_lbl(question,help).grid(:columnspan=>6,:sticky=>'nsew')
|
942
|
+
|
943
|
+
#---- Labels ("Recent", "Favorites", ...)
|
944
|
+
@recLbl = recent_lbl.grid(:column=>0,:columnspan=>2,:row=>1)
|
945
|
+
@favLbl = favorite_lbl.grid(:column=>2,:columnspan=>2,:row=>1)
|
946
|
+
@pasteLbl = paste_lbl.grid(:column=>4,:row=>1)
|
947
|
+
|
948
|
+
#---- Buttons ("Dirs", "Files", "Browse", ...
|
949
|
+
@recDirsBtn = recent_dirs_btn(hash).grid(:row=>2,:column=>0)
|
950
|
+
@recFilesBtn = recent_files_btn.grid(:row=>2,:column=>1)
|
951
|
+
@favDirsBtn = favorite_dirs_btn(hash).grid(:row=>2,:column=>2)
|
952
|
+
@favFilesBtn = favorite_files_btn.grid(:row=>2,:column=>3)
|
953
|
+
@pasteBtn = paste_btn.grid(:row=>2,:column=>4)
|
954
|
+
@browseBtn = browse_btn(hash).grid(:row=>2,:column=>5)
|
955
|
+
|
956
|
+
#---- Entry (Path)
|
957
|
+
@entry = entry(defaultEntry).grid(:row=>3,:column=>0,:columnspan=>6,:sticky=>'ew')
|
958
|
+
|
959
|
+
#---- Buttons ("Cancel", "OK")
|
960
|
+
@cancelBtn = cancel_btn.grid(:row=>4,:column=>0)
|
961
|
+
@okBtn = ok_btn.grid(:row=>4,:column=>5)
|
962
|
+
|
963
|
+
#---- CheckButton ("Add to Favorites")
|
964
|
+
@favChkLbl = favorite_chk_label.grid(:row=>4,:column=>1,:columnspan=>2,:sticky=>'e')
|
965
|
+
@favDirsChkVal = nil
|
966
|
+
@favDirsChk = add2favorites_dirs_chk.grid(:row=>4,:column=>3,:columnspan=>1)
|
967
|
+
@favFilesChkVal = nil
|
968
|
+
unless @mode == :choosedir
|
969
|
+
@favFilesChk = add2favorites_files_chk.
|
970
|
+
grid(:row=>4,:column=>4,:columnspan=>1,:sticky=>'w')
|
971
|
+
end
|
972
|
+
|
973
|
+
#---- Text (showing other path formats)
|
974
|
+
@txt = text2.grid(:row=>5,:column=>0,:columnspan=>6,:sticky=>'sew')
|
975
|
+
|
976
|
+
#---- grid_configure
|
977
|
+
@frame.grid_columnconfigure([0,1,2,3,4,5], :weight=>1)
|
978
|
+
@frame.grid_rowconfigure([0,1,2,3,4], :weight=>0)
|
979
|
+
@frame.grid_rowconfigure([5], :weight=>1)
|
980
|
+
|
981
|
+
@entry.focus
|
982
|
+
Tk.update
|
983
|
+
##/ @dialog.raise
|
984
|
+
end # initialize
|
985
|
+
|
986
|
+
##################################################################
|
987
|
+
# For FileAndDirChooser, @validate is simply true or false. If
|
988
|
+
# user presses "Cancel" or the close button ('X') in the upper
|
989
|
+
# right, this is detected and user is informed by a message box.
|
990
|
+
# Otherwise, all chosen paths already have been checked "inline",
|
991
|
+
# hence no further validation is neccessary.
|
992
|
+
def validate_and_leave( )
|
993
|
+
# path(s) is/are not valid.
|
994
|
+
# TODO: Missleading method name; better(?): handle_invalid_path
|
995
|
+
if @validate
|
996
|
+
# Retry or completly exit the application
|
997
|
+
ans = Tk.messageBox(:icon=>:warning, :type=>:retrycancel,
|
998
|
+
:title=>'Message',
|
999
|
+
:message=>"INVALID ANSWER\n\nCANCEL will completely EXIT the application!"
|
1000
|
+
## :detail=>"blablabla\n"*50
|
1001
|
+
)
|
1002
|
+
if ans == 'cancel'
|
1003
|
+
Tk.exit
|
1004
|
+
exit # Todo: n�tig?
|
1005
|
+
end
|
1006
|
+
else
|
1007
|
+
set_paths(nil) # uses the invalid path
|
1008
|
+
end
|
1009
|
+
end # validate_and_leave
|
1010
|
+
|
1011
|
+
def set_paths( paths )
|
1012
|
+
@paths = paths
|
1013
|
+
favorites_and_recent(paths) if paths && !paths.empty?
|
1014
|
+
@goOn.value = '1'
|
1015
|
+
nil
|
1016
|
+
end # set_paths
|
1017
|
+
|
1018
|
+
##################################################################
|
1019
|
+
# When dialog is finished, always +set_paths+ must be called. For
|
1020
|
+
# 'Cancel', use set_paths(nil)
|
1021
|
+
#
|
1022
|
+
# *Returns*: (String or Array or nil) Path(s); +nil+, if 'Cancel'
|
1023
|
+
# was clicked.
|
1024
|
+
def answer( )
|
1025
|
+
@dialog.raise # Raise dialog above all other windows
|
1026
|
+
@goOn.wait # Force hold on of the dialog
|
1027
|
+
# Tear down
|
1028
|
+
unless @teardownDone # TODO: not neccessary anymore?
|
1029
|
+
$tkxxs_.delete(object_id)
|
1030
|
+
if CONF # Save settings (e.g., window size)
|
1031
|
+
CONF[:pathChooserGeom] = @dialog.geometry
|
1032
|
+
CONF.section = nil
|
1033
|
+
##/ CONF.save
|
1034
|
+
end
|
1035
|
+
@teardownDone = true
|
1036
|
+
##/ @goOn.value = '1'
|
1037
|
+
@dialog.destroy # Remove dialog window
|
1038
|
+
end # unless
|
1039
|
+
Tk.update
|
1040
|
+
|
1041
|
+
@paths # return the "answer"
|
1042
|
+
end # ans
|
1043
|
+
|
1044
|
+
##/ ##################################################################
|
1045
|
+
##/ # Saves CONF!
|
1046
|
+
##/ def dialog_destroy( )
|
1047
|
+
##/ unless @teardownDone
|
1048
|
+
##/ $tkxxs_.delete(object_id)
|
1049
|
+
##/ if CONF
|
1050
|
+
##/ CONF[:pathChooserGeom] = @dialog.geometry
|
1051
|
+
##/ CONF.section = nil
|
1052
|
+
##/ ##/ CONF.save
|
1053
|
+
##/ end
|
1054
|
+
##/ @teardownDone = true
|
1055
|
+
##/ ##/ @goOn.value = '1'
|
1056
|
+
##/ @dialog.destroy
|
1057
|
+
##/ end # unless
|
1058
|
+
##/ nil
|
1059
|
+
##/ end # dialog_destroy
|
1060
|
+
|
1061
|
+
def question_lbl( question, help)
|
1062
|
+
lbl = Label.new(@frame, :text=>question){|w|
|
1063
|
+
BalloonHelp.new(w,:text=>help)
|
1064
|
+
}
|
1065
|
+
end # question_lbl
|
1066
|
+
|
1067
|
+
def recent_lbl( )
|
1068
|
+
lbl = Label.new(@frame, :text=>"Recent")
|
1069
|
+
end # recent_lbl
|
1070
|
+
|
1071
|
+
def favorite_lbl( )
|
1072
|
+
lbl = Label.new(@frame, :text=>"Favorites")
|
1073
|
+
end # favorite_lbl
|
1074
|
+
|
1075
|
+
def paste_lbl( )
|
1076
|
+
Label.new(@frame, :text=>"Clipboard")
|
1077
|
+
end # paste_lbl
|
1078
|
+
|
1079
|
+
def recent_dirs_btn( hash )
|
1080
|
+
btn = Button.new(@frame, :text=>"Dirs"){|w|
|
1081
|
+
BalloonHelp.new(w,:text=>"Recent directories")
|
1082
|
+
}
|
1083
|
+
btn.command {
|
1084
|
+
if CONF
|
1085
|
+
dirs = CONF[:recentDirs] || []
|
1086
|
+
## dir = SingleChoiceD.new(dirs.sort).answer
|
1087
|
+
dir = SingleChoiceD.new(dirs).answer # unsorted
|
1088
|
+
if dir
|
1089
|
+
hash2 = hash.merge({:initialdir=>dir}).dup
|
1090
|
+
##/ filesStr = Tk.getOpenFile(hash2)
|
1091
|
+
##/ set_paths( tks_result_to_ary( filesStr ) )
|
1092
|
+
case @mode
|
1093
|
+
##/ when :choosedir; set_paths(dir)
|
1094
|
+
when :choosedir; validate_and_set_path( dir )
|
1095
|
+
when :openfiles; choose(hash2)
|
1096
|
+
when :openfile; choose(hash2)
|
1097
|
+
when :savefile; choose(hash2)
|
1098
|
+
else; raise
|
1099
|
+
end # case
|
1100
|
+
end
|
1101
|
+
end # if CONF
|
1102
|
+
}
|
1103
|
+
btn
|
1104
|
+
end # recent_dirs_btn
|
1105
|
+
|
1106
|
+
def recent_files_btn( )
|
1107
|
+
btn = Button.new(@frame, :text=>"Files"){|w|
|
1108
|
+
BalloonHelp.new(w,:text=>"Recent files")
|
1109
|
+
}
|
1110
|
+
btn.command {
|
1111
|
+
if CONF
|
1112
|
+
files = CONF[:recentFiles] || []
|
1113
|
+
## filePath = SingleChoiceD.new(files.sort).answer
|
1114
|
+
filePath = SingleChoiceD.new(files).answer # unsorted
|
1115
|
+
if filePath
|
1116
|
+
case @mode
|
1117
|
+
when :choosedir
|
1118
|
+
validate_and_set_path(File.dirname(filePath))
|
1119
|
+
when :openfiles
|
1120
|
+
validate_and_set_path( filePath )
|
1121
|
+
when :openfile, :savefile
|
1122
|
+
validate_and_set_path( filePath )
|
1123
|
+
else; raise
|
1124
|
+
end # case
|
1125
|
+
end
|
1126
|
+
end # if CONF
|
1127
|
+
}
|
1128
|
+
btn
|
1129
|
+
end # recent_files_btn
|
1130
|
+
|
1131
|
+
def favorite_dirs_btn( hash )
|
1132
|
+
btn = Button.new(@frame, :text=>"Dirs"){|w|
|
1133
|
+
BalloonHelp.new(w,:text=>"Favorite directories")
|
1134
|
+
}
|
1135
|
+
btn.command {
|
1136
|
+
if CONF
|
1137
|
+
favDirs = CONF[:favoriteDirs] || []
|
1138
|
+
dir = SingleChoiceD.new(favDirs.sort).answer
|
1139
|
+
if dir
|
1140
|
+
hash2 = hash.merge({:initialdir=>dir}).dup
|
1141
|
+
##/ filesStr = Tk.getOpenFile(hash2)
|
1142
|
+
##/ set_paths( tks_result_to_ary( filesStr ) )
|
1143
|
+
case @mode
|
1144
|
+
when :choosedir; validate_and_set_path( dir )
|
1145
|
+
when :openfiles; choose(hash2)
|
1146
|
+
when :openfile; choose(hash2)
|
1147
|
+
when :savefile; choose(hash2)
|
1148
|
+
else; raise
|
1149
|
+
end # case
|
1150
|
+
end
|
1151
|
+
end # if CONF
|
1152
|
+
}
|
1153
|
+
btn
|
1154
|
+
end # favorite_dirs_btn
|
1155
|
+
|
1156
|
+
def favorite_files_btn( )
|
1157
|
+
btn = Button.new(@frame, :text=>"Files"){|w|
|
1158
|
+
BalloonHelp.new(w,:text=>"Favorite files")
|
1159
|
+
}
|
1160
|
+
btn.command {
|
1161
|
+
if CONF
|
1162
|
+
favFiles = CONF[:favoriteFiles] || []
|
1163
|
+
filePath = SingleChoiceD.new(favFiles.sort).answer
|
1164
|
+
if filePath
|
1165
|
+
case @mode
|
1166
|
+
when :choosedir
|
1167
|
+
validate_and_set_path(File.dirname(filePath))
|
1168
|
+
when :openfiles
|
1169
|
+
validate_and_set_path( [filePath] )
|
1170
|
+
when :openfile, :savefile
|
1171
|
+
validate_and_set_path( filePath )
|
1172
|
+
else; raise
|
1173
|
+
end # case
|
1174
|
+
end
|
1175
|
+
end # if CONF
|
1176
|
+
}
|
1177
|
+
btn
|
1178
|
+
end # favorite_files_btn
|
1179
|
+
|
1180
|
+
def paste_btn( )
|
1181
|
+
btn = Button.new(@frame, :text=>"Paste"){|w|
|
1182
|
+
BalloonHelp.new(w,:text=>"Paste clipboard to entry field")
|
1183
|
+
}
|
1184
|
+
btn.command {
|
1185
|
+
@entry.delete(0, :end)
|
1186
|
+
begin
|
1187
|
+
clip = TkClipboard.get
|
1188
|
+
rescue => err # Clipboard no String
|
1189
|
+
clip = "Nothing usefull in clipboard"
|
1190
|
+
end
|
1191
|
+
@entry.insert(0, clip)
|
1192
|
+
}
|
1193
|
+
btn
|
1194
|
+
end # paste_btn
|
1195
|
+
|
1196
|
+
def browse_btn( hash )
|
1197
|
+
btn = Button.new(@frame, :text=>"Browse"){|w|
|
1198
|
+
BalloonHelp.new(w,:text=>"Search for directory or file manually")
|
1199
|
+
}
|
1200
|
+
entry = @entry
|
1201
|
+
btn.command {
|
1202
|
+
entryStr = @entry.get.strip.gsub('\\', '/').chomp('/')
|
1203
|
+
unless entryStr.empty?
|
1204
|
+
if File.directory?(entryStr)
|
1205
|
+
hash[:initialdir] = entryStr
|
1206
|
+
elsif File.file?(entryStr)
|
1207
|
+
hash[:initialdir] = File.dirname(entryStr)
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
choose(hash)
|
1211
|
+
}
|
1212
|
+
end # browse_btn
|
1213
|
+
|
1214
|
+
def entry( defaultEntry )
|
1215
|
+
entry = Entry.new(@frame) {|ent|
|
1216
|
+
BalloonHelp.new(ent,:text=>"Type or paste a path here.")
|
1217
|
+
}
|
1218
|
+
entry.insert(0, defaultEntry)
|
1219
|
+
entry.bind('Key-Return') { use_entry(@entry.get) }
|
1220
|
+
end # entry
|
1221
|
+
|
1222
|
+
def cancel_btn( )
|
1223
|
+
btn = Button.new(@frame, :text=>"Cancel"){|w|
|
1224
|
+
BalloonHelp.new(w,:text=>"Do nothing")
|
1225
|
+
}
|
1226
|
+
btn.command {
|
1227
|
+
validate_and_leave
|
1228
|
+
###set_paths(nil)
|
1229
|
+
##/ dialog_destroy
|
1230
|
+
}
|
1231
|
+
btn
|
1232
|
+
end # cancel_btn
|
1233
|
+
|
1234
|
+
def ok_btn( )
|
1235
|
+
btn = Button.new(@frame, :text=>"Use entry"){|w|
|
1236
|
+
BalloonHelp.new(w,:text=>"Use path from entry box")
|
1237
|
+
}
|
1238
|
+
btn.command { use_entry(@entry.get) }
|
1239
|
+
end # ok_btn
|
1240
|
+
|
1241
|
+
def use_entry( str )
|
1242
|
+
str = str.strip.gsub('\\', '/').chomp('/')
|
1243
|
+
validate_and_set_path( str )
|
1244
|
+
end # use_entry
|
1245
|
+
|
1246
|
+
def validate_and_set_path( str )
|
1247
|
+
case @mode
|
1248
|
+
when :choosedir
|
1249
|
+
if File.directory?(str)
|
1250
|
+
set_paths(str)
|
1251
|
+
elsif File.file?(str)
|
1252
|
+
set_paths( File.dirname(str) )
|
1253
|
+
else
|
1254
|
+
@txt.replace('0.0', :end, %Q<"#{ str }" is no > +
|
1255
|
+
"valid directory, please choose a valid one!")
|
1256
|
+
@entry.focus
|
1257
|
+
end
|
1258
|
+
when :openfiles, :openfile
|
1259
|
+
if File.file?(str)
|
1260
|
+
@mode == :openfile ? set_paths(str) : set_paths([str])
|
1261
|
+
else
|
1262
|
+
@txt.replace('0.0', :end, %Q<"#{ str }" is no > +
|
1263
|
+
"valid file, please choose a valid one!")
|
1264
|
+
@entry.focus
|
1265
|
+
end
|
1266
|
+
when :savefile
|
1267
|
+
if @validate
|
1268
|
+
dir = File.dirname(str)
|
1269
|
+
if File.directory?(dir)
|
1270
|
+
set_paths(str)
|
1271
|
+
else
|
1272
|
+
@txt.replace('0.0', :end, %Q<"#{ dir }" is no > +
|
1273
|
+
"valid directory, please choose a valid one!")
|
1274
|
+
@entry.focus
|
1275
|
+
end
|
1276
|
+
else
|
1277
|
+
# if file does not exist: create file and dir
|
1278
|
+
set_paths(str)
|
1279
|
+
end
|
1280
|
+
else; raise
|
1281
|
+
end # case
|
1282
|
+
nil
|
1283
|
+
end # validate_and_set_path
|
1284
|
+
|
1285
|
+
def favorite_chk_label( )
|
1286
|
+
lbl = Label.new(@frame, :text=>"Add to favorites")
|
1287
|
+
end # favorite_chk_label
|
1288
|
+
|
1289
|
+
def add2favorites_dirs_chk( )
|
1290
|
+
favChkVar = TkVariable.new(0)
|
1291
|
+
favChk = CheckButton.new(@frame) {|w|
|
1292
|
+
text 'dirs'
|
1293
|
+
variable favChkVar
|
1294
|
+
##/ onvalue 'metric'; offvalue 'imperial'
|
1295
|
+
BalloonHelp.new(w,:text=>"If checked, the path(s) of files or directories you are going to choose will be added to the favorite directories.")
|
1296
|
+
}
|
1297
|
+
favChk.command { self.fav_dirs_chk_changed(favChkVar.value) }
|
1298
|
+
favChk
|
1299
|
+
end # add2favorites_dirs_chk
|
1300
|
+
|
1301
|
+
def add2favorites_files_chk( )
|
1302
|
+
favChkVar = TkVariable.new(0)
|
1303
|
+
favChk = CheckButton.new(@frame) {|w|
|
1304
|
+
text 'files'
|
1305
|
+
variable favChkVar
|
1306
|
+
##/ onvalue 'metric'; offvalue 'imperial'
|
1307
|
+
BalloonHelp.new(w,:text=>"If checked, the files(s) you are going to choose will be added to the favorite files.")
|
1308
|
+
}
|
1309
|
+
favChk.command { self.fav_files_chk_changed(favChkVar.value) }
|
1310
|
+
favChk
|
1311
|
+
end # add2favorites_files_chk
|
1312
|
+
|
1313
|
+
def fav_dirs_chk_changed( val )
|
1314
|
+
@favDirsChkVal = val == '1' ? true : false
|
1315
|
+
@favDirsChkVal
|
1316
|
+
end # fav_dirs_chk_changed
|
1317
|
+
|
1318
|
+
def fav_files_chk_changed( val )
|
1319
|
+
@favFilesChkVal = val == '1' ? true : false
|
1320
|
+
@favDirsChk.invoke if @favFilesChkVal && !@favDirsChkVal
|
1321
|
+
@favFilesChkVal
|
1322
|
+
end # fav_files_chk_changed
|
1323
|
+
|
1324
|
+
def text2( )
|
1325
|
+
txt = TkText.new(@frame)
|
1326
|
+
tagSel = TkTextTagSel.new(txt)
|
1327
|
+
txt.bind('Control-Key-a') {
|
1328
|
+
# From: "Using Control-a to select all text in a text widget : TCL", http://objectmix.com/tcl/35276-using-control-select-all-text-text-widget.html
|
1329
|
+
tagSel.add('0.0', :end)
|
1330
|
+
Kernel.raise TkCallbackBreak
|
1331
|
+
}
|
1332
|
+
txt
|
1333
|
+
end # text2
|
1334
|
+
|
1335
|
+
def favorites_and_recent( paths )
|
1336
|
+
fav_dirs(paths) if @favDirsChkVal # For any mode
|
1337
|
+
fav_files(paths) if @favFilesChkVal # For any mode but :choosedir; no @favFilesChk for chooseDirectory
|
1338
|
+
recent_dirs(paths)
|
1339
|
+
recent_files(paths) unless @mode == :choosedir
|
1340
|
+
CONF.save
|
1341
|
+
nil
|
1342
|
+
end # favorites_and_recents
|
1343
|
+
|
1344
|
+
def fav_dirs( paths )
|
1345
|
+
return nil unless paths
|
1346
|
+
paths = paths.dup
|
1347
|
+
favDirs = CONF[:favoriteDirs] || []
|
1348
|
+
paths = [paths] if paths.class == String
|
1349
|
+
paths.map! {|p|
|
1350
|
+
File.file?(p) ? File.dirname(p) : p
|
1351
|
+
}
|
1352
|
+
favDirs.concat(paths)
|
1353
|
+
favDirs.uniq!
|
1354
|
+
CONF[:favoriteDirs] = favDirs
|
1355
|
+
favDirs
|
1356
|
+
end # fav_dirs
|
1357
|
+
|
1358
|
+
def fav_files( paths )
|
1359
|
+
return nil unless paths
|
1360
|
+
paths = paths.dup
|
1361
|
+
favFiles = CONF[:favoriteFiles] || []
|
1362
|
+
paths = [paths] if paths.class == String
|
1363
|
+
favFiles.concat(paths)
|
1364
|
+
favFiles.uniq!
|
1365
|
+
CONF[:favoriteFiles] = favFiles
|
1366
|
+
favFiles
|
1367
|
+
end # fav_files
|
1368
|
+
|
1369
|
+
def recent_dirs( paths )
|
1370
|
+
return nil unless paths
|
1371
|
+
paths = paths.dup
|
1372
|
+
dirs = CONF[:recentDirs] || []
|
1373
|
+
paths = [paths] if paths.class == String
|
1374
|
+
paths.map! {|p|
|
1375
|
+
p = File.file?(p) ? File.dirname(p) : p
|
1376
|
+
p = File.directory?(p) ? p : nil
|
1377
|
+
p
|
1378
|
+
}
|
1379
|
+
paths.compact!
|
1380
|
+
dirs = paths + dirs
|
1381
|
+
dirs.uniq!
|
1382
|
+
CONF[:recentDirs] = dirs[0, CONF[:recentDirsSize] ]
|
1383
|
+
dirs
|
1384
|
+
end # recent_dirs
|
1385
|
+
|
1386
|
+
def recent_files( paths)
|
1387
|
+
return nil unless paths
|
1388
|
+
paths = paths.dup
|
1389
|
+
files = CONF[:recentFiles] || []
|
1390
|
+
paths = [paths] if paths.class == String
|
1391
|
+
files = paths + files
|
1392
|
+
files.uniq!
|
1393
|
+
CONF[:recentFiles] = files[0, CONF[:recentFilesSize] ]
|
1394
|
+
files
|
1395
|
+
end # recent_files
|
1396
|
+
|
1397
|
+
def tks_result_to_ary( filesListStr )
|
1398
|
+
paths = []
|
1399
|
+
if filesListStr[0,1]=='{'
|
1400
|
+
filesListStr.scan( / \{ ( [^\}]+ ) \} /x) {
|
1401
|
+
paths << $1
|
1402
|
+
}
|
1403
|
+
else
|
1404
|
+
paths = filesListStr.split(' ')
|
1405
|
+
end
|
1406
|
+
|
1407
|
+
paths
|
1408
|
+
end # tks_result_to_ary
|
1409
|
+
end # class FileAndDirChooser
|
1410
|
+
|
1411
|
+
##########################################################################
|
1412
|
+
##########################################################################
|
1413
|
+
class ChooseDirD < FileAndDirChooser
|
1414
|
+
|
1415
|
+
##################################################################
|
1416
|
+
# See: TKXXS.choose_dir
|
1417
|
+
def initialize( initialdir=nil,help=nil,hash=nil )
|
1418
|
+
initialdir, help, hash =
|
1419
|
+
TKXXS_CLASSES.args_1( initialdir,help,hash )
|
1420
|
+
hash = {
|
1421
|
+
:mode => :choosedir,
|
1422
|
+
:question=>"Please choose a directory",
|
1423
|
+
:title=>"Choose Directory",
|
1424
|
+
}.merge(hash)
|
1425
|
+
super(initialdir, help, hash)
|
1426
|
+
end
|
1427
|
+
|
1428
|
+
def choose( hash )
|
1429
|
+
dirStr = Tk.chooseDirectory( hash )
|
1430
|
+
path = dirStr.empty? ? nil : dirStr
|
1431
|
+
|
1432
|
+
if path
|
1433
|
+
set_paths( path )
|
1434
|
+
else
|
1435
|
+
validate_and_leave
|
1436
|
+
end
|
1437
|
+
nil
|
1438
|
+
end # choose
|
1439
|
+
|
1440
|
+
alias :path :answer
|
1441
|
+
end # class ChooseDirD
|
1442
|
+
|
1443
|
+
##########################################################################
|
1444
|
+
##########################################################################
|
1445
|
+
class OpenFilesD < FileAndDirChooser
|
1446
|
+
##################################################################
|
1447
|
+
# See: TKXXS.open_files
|
1448
|
+
def initialize( initialdir=nil,help=nil,hash=nil )
|
1449
|
+
initialdir, help, hash =
|
1450
|
+
TKXXS_CLASSES.args_1( initialdir,help,hash )
|
1451
|
+
hash = {
|
1452
|
+
:mode => :openfiles,
|
1453
|
+
:question=>"Please choose the desired files",
|
1454
|
+
:title=>"Open Files",
|
1455
|
+
:filetypes => [['All files','*']],
|
1456
|
+
:multiple=>true
|
1457
|
+
}.merge(hash)
|
1458
|
+
super(initialdir, help, hash)
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
def choose( hash )
|
1462
|
+
filesStr = Tk.getOpenFile(hash)
|
1463
|
+
if filesStr
|
1464
|
+
set_paths( tks_result_to_ary( filesStr ) )
|
1465
|
+
else
|
1466
|
+
validate_and_leave
|
1467
|
+
end
|
1468
|
+
nil
|
1469
|
+
end # choose
|
1470
|
+
|
1471
|
+
alias :paths :answer
|
1472
|
+
end # class OpenFilesD
|
1473
|
+
|
1474
|
+
##########################################################################
|
1475
|
+
##########################################################################
|
1476
|
+
class OpenFileD < FileAndDirChooser
|
1477
|
+
|
1478
|
+
##################################################################
|
1479
|
+
# See: TKXXS.open_file
|
1480
|
+
def initialize( initialdir=nil,help=nil,hash=nil )
|
1481
|
+
initialdir, help, hash =
|
1482
|
+
TKXXS_CLASSES.args_1( initialdir,help,hash )
|
1483
|
+
hash = {
|
1484
|
+
:mode => :openfile,
|
1485
|
+
:question=>"Please choose the desired file",
|
1486
|
+
:title=>"Open File",
|
1487
|
+
:filetypes => [['All files','*']],
|
1488
|
+
:multiple=>false
|
1489
|
+
}.merge(hash)
|
1490
|
+
super(initialdir, help, hash)
|
1491
|
+
end
|
1492
|
+
|
1493
|
+
def choose( hash )
|
1494
|
+
fileStr = Tk.getOpenFile(hash)
|
1495
|
+
if fileStr
|
1496
|
+
set_paths( fileStr )
|
1497
|
+
else
|
1498
|
+
validate_and_leave
|
1499
|
+
end
|
1500
|
+
nil
|
1501
|
+
end # choose
|
1502
|
+
|
1503
|
+
alias :path :answer
|
1504
|
+
end # class OpenFilesD
|
1505
|
+
|
1506
|
+
##########################################################################
|
1507
|
+
##########################################################################
|
1508
|
+
class SaveFileD < FileAndDirChooser
|
1509
|
+
|
1510
|
+
##################################################################
|
1511
|
+
# See: TKXXS.save_file
|
1512
|
+
def initialize( initialdir=nil,help=nil,hash=nil )
|
1513
|
+
initialdir, help, hash =
|
1514
|
+
TKXXS_CLASSES.args_1( initialdir,help,hash )
|
1515
|
+
hash = {
|
1516
|
+
:mode => :savefile,
|
1517
|
+
:question=>"Please choose, where to save the file:",
|
1518
|
+
:title=>"Save File As...",
|
1519
|
+
:filetypes => [['All files','*']],
|
1520
|
+
:initialfile => 'Untitled', # Default filename, extension will be added automatically by filetypes-setting
|
1521
|
+
:defaultextension => nil,
|
1522
|
+
}.merge(hash)
|
1523
|
+
super(initialdir, help, hash)
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
def choose( hash )
|
1527
|
+
fileStr = Tk.getSaveFile(hash)
|
1528
|
+
path = fileStr.empty? ? nil : fileStr
|
1529
|
+
if path
|
1530
|
+
set_paths( path )
|
1531
|
+
else
|
1532
|
+
validate_and_leave
|
1533
|
+
end
|
1534
|
+
nil
|
1535
|
+
end # choose
|
1536
|
+
|
1537
|
+
alias :path :answer
|
1538
|
+
end # class SaveFileD
|
1539
|
+
|
1540
|
+
##/ ##########################################################################
|
1541
|
+
##/ ##########################################################################
|
1542
|
+
##/ class OpenFileD < OpenFilesD
|
1543
|
+
##/
|
1544
|
+
##/ def initialize( initialdir=nil,help=nil,hash=nil )
|
1545
|
+
##/ initialdir, help, hash =
|
1546
|
+
##/ TKXXS_CLASSES.args_1( initialdir,help,hash )
|
1547
|
+
##/
|
1548
|
+
##/ # Must always include: :question, :help, :configSection
|
1549
|
+
##/ hash = {
|
1550
|
+
##/ :initialdir => Dir.pwd,
|
1551
|
+
##/ :question => "Please choose the desired file:",
|
1552
|
+
##/ :help => nil,
|
1553
|
+
##/ :configSection => nil,
|
1554
|
+
##/ :filetypes => [['All files','*']],
|
1555
|
+
##/ :multiple => 0,
|
1556
|
+
##/ }.merge(hash)
|
1557
|
+
##/
|
1558
|
+
##/ super(initialdir,help,hash)
|
1559
|
+
##/ end # initialize
|
1560
|
+
##/
|
1561
|
+
##/ def path( )
|
1562
|
+
##/ CONF.section = nil if CONF
|
1563
|
+
##/ @paths[0]
|
1564
|
+
##/ end # paths
|
1565
|
+
##/ alias :answer :path
|
1566
|
+
##/ end # class OpenFileD
|
1567
|
+
|
1568
|
+
|
1569
|
+
end # class
|
1570
|
+
|
1571
|
+
|