tkxxs 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+