tkxxs 0.1.1 → 0.1.2

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