1#
2#  Tile theme engin (tile widget set) support
3#                               by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)
4#
5
6require 'tk'
7require 'tk/ttk_selector'
8
9# call setup script for general 'tkextlib' libraries
10require 'tkextlib/setup.rb'
11
12# library directory
13require 'tkextlib/tile/setup.rb'
14
15# load package
16# TkPackage.require('tile', '0.4')
17# TkPackage.require('tile', '0.6')
18# TkPackage.require('tile', '0.7')
19if Tk::TK_MAJOR_VERSION > 8 ||
20    (Tk::TK_MAJOR_VERSION == 8 && Tk::TK_MINOR_VERSION >= 5)
21  begin
22    TkPackage.require('tile') # for compatibility (version check of 'tile')
23  rescue RuntimeError
24    # ignore, even if cannot find package 'tile'
25  end
26  pkgname = 'Ttk'
27else
28  pkgname = 'tile'
29end
30
31begin
32  verstr = TkPackage.require(pkgname)
33rescue RuntimeError
34  # define dummy methods
35  module Tk
36    module Tile
37      CANNOT_FIND_PACKAGE = true
38      def self.const_missing(sym)
39        TkPackage.require(PACKAGE_NAME)
40      end
41      def self.method_missing(*args)
42        TkPackage.require(PACKAGE_NAME)
43      end
44    end
45  end
46  Tk.__cannot_find_tk_package_for_widget_set__(:Ttk, pkgname)
47  if pkgname == 'Ttk'
48    verstr = Tk::TK_PATCHLEVEL  # dummy
49  else
50    verstr = '0.7'  # dummy
51  end
52end
53
54ver = verstr.split('.')
55if ver[0].to_i == 0
56  # Tile extension package
57  if ver[1].to_i <= 4
58    # version 0.4 or former
59    module Tk
60      module Tile
61        USE_TILE_NAMESPACE = true
62        USE_TTK_NAMESPACE  = false
63        TILE_SPEC_VERSION_ID = 0
64      end
65    end
66  elsif ver[1].to_i <= 6
67    # version 0.5 -- version 0.6
68    module Tk
69      module Tile
70        USE_TILE_NAMESPACE = true
71        USE_TTK_NAMESPACE  = true
72        TILE_SPEC_VERSION_ID = 5
73      end
74    end
75  elsif ver[1].to_i <= 7
76    module Tk
77      module Tile
78        USE_TILE_NAMESPACE = false
79        USE_TTK_NAMESPACE  = true
80        TILE_SPEC_VERSION_ID = 7
81      end
82    end
83  else
84    # version 0.8 or later
85    module Tk
86      module Tile
87        USE_TILE_NAMESPACE = false
88        USE_TTK_NAMESPACE  = true
89        TILE_SPEC_VERSION_ID = 8
90      end
91    end
92  end
93
94  module Tk::Tile
95    PACKAGE_NAME = 'tile'.freeze
96  end
97else
98  # Ttk package merged Tcl/Tk core (Tcl/Tk 8.5+)
99  module Tk
100    module Tile
101      USE_TILE_NAMESPACE = false
102      USE_TTK_NAMESPACE  = true
103      TILE_SPEC_VERSION_ID = 8
104
105      PACKAGE_NAME = 'Ttk'.freeze
106    end
107  end
108end
109
110# autoload
111module Tk
112  module Tile
113    TkComm::TkExtlibAutoloadModule.unshift(self)
114
115    def self.package_name
116      PACKAGE_NAME
117    end
118
119    def self.package_version
120      begin
121        TkPackage.require(PACKAGE_NAME)
122      rescue
123        ''
124      end
125    end
126
127    def self.__Import_Tile_Widgets__!
128      warn 'Warning: "Tk::Tile::__Import_Tile_Widgets__!" is obsolete.' <<
129           ' To control default widget set, use "Tk.default_widget_set = :Ttk"'
130      Tk.tk_call('namespace', 'import', '-force', 'ttk::*')
131    end
132
133    def self.__define_LoadImages_proc_for_compatibility__!
134      # Ttk 8.5 (Tile 0.8) lost 'LoadImages' utility procedure.
135      # So, some old scripts doen't work, because those scripts use the
136      # procedure to define local styles.
137      # Of course, rewriting such Tcl/Tk scripts isn't difficult for
138      # Tcl/Tk users. However, it may be troublesome for Ruby/Tk users
139      # who use such Tcl/Tk scripts as it is.
140      # This method may help Ruby/Tk users who don't want to modify old
141      # Tcl/Tk scripts for the latest version of Ttk (Tile) extension.
142      # This method defines a comaptible 'LoadImages' procedure on the
143      # Tcl/Tk interpreter working under Ruby/Tk.
144      # Please give attention to use this method. It may conflict with
145      # some definitions on Tcl/Tk scripts.
146      klass_name = self.name
147      proc_name = 'LoadImages'
148      if Tk::Tile::USE_TTK_NAMESPACE
149        ns_list = ['::tile']
150        if Tk.info(:commands, "::ttk::#{proc_name}").empty?
151          ns_list << '::ttk'
152        end
153      else # Tk::Tile::USE_TILE_NAMESPACE
154        ns_list = ['::ttk']
155        if Tk.info(:commands, "::tile::#{proc_name}").empty?
156          ns_list << '::tile'
157        end
158      end
159
160      ns_list.each{|ns|
161        cmd = "#{ns}::#{proc_name}"
162        unless Tk.info(:commands, cmd).empty?
163          #fail RuntimeError, "can't define '#{cmd}' command (already exist)"
164
165          # do nothing !!!
166          warn "Warning: can't define '#{cmd}' command (already exist)" if $DEBUG
167          next
168        end
169        TkNamespace.eval(ns){
170          TkCore::INTERP.add_tk_procs(proc_name, 'imgdir {patterns {*.gif}}',
171                                      <<-'EOS')
172            foreach pattern $patterns {
173              foreach file [glob -directory $imgdir $pattern] {
174                set img [file tail [file rootname $file]]
175                if {![info exists images($img)]} {
176                  set images($img) [image create photo -file $file]
177                }
178              }
179            }
180            return [array get images]
181          EOS
182        }
183      }
184    end
185
186    def self.load_images(imgdir, pat=nil)
187      if Tk::Tile::TILE_SPEC_VERSION_ID < 8
188        if Tk::Tile::USE_TTK_NAMESPACE
189          cmd = '::ttk::LoadImages'
190        else # Tk::Tile::USE_TILE_NAMESPACE
191          cmd = '::tile::LoadImages'
192        end
193        pat ||= TkComm::None
194        images = Hash[*TkComm.simplelist(Tk.tk_call(cmd, imgdir, pat))]
195        images.keys.each{|k|
196          images[k] = TkPhotoImage.new(:imagename=>images[k],
197                                       :without_creating=>true)
198        }
199      else ## TILE_SPEC_VERSION_ID >= 8
200        pat ||= '*.gif'
201        if pat.kind_of?(Array)
202          pat_list = pat
203        else
204          pat_list = [ pat ]
205        end
206        Dir.chdir(imgdir){
207          pat_list.each{|pat|
208            Dir.glob(pat).each{|f|
209              img = File.basename(f, '.*')
210              unless TkComm.bool(Tk.info('exists', "images(#{img})"))
211                Tk.tk_call('set', "images(#{img})",
212                           Tk.tk_call('image', 'create', 'photo', '-file', f))
213              end
214            }
215          }
216        }
217        images = Hash[*TkComm.simplelist(Tk.tk_call('array', 'get', 'images'))]
218        images.keys.each{|k|
219          images[k] = TkPhotoImage.new(:imagename=>images[k],
220                                       :without_creating=>true)
221        }
222      end
223
224      images
225    end
226
227    def self.style(*args)
228      args.map!{|arg| TkComm._get_eval_string(arg)}.join('.')
229    end
230
231    def self.themes(glob_ptn = nil)
232      if TILE_SPEC_VERSION_ID < 8 && Tk.info(:commands, '::ttk::themes').empty?
233        fail RuntimeError, 'not support glob option' if glob_ptn
234        cmd = ['::tile::availableThemes']
235      else
236        glob_ptn = '*' unless glob_ptn
237        cmd = ['::ttk::themes', glob_ptn]
238      end
239
240      begin
241        TkComm.simplelist(Tk.tk_call_without_enc(*cmd))
242      rescue
243        TkComm.simplelist(Tk.tk_call('lsearch', '-all', '-inline',
244                                     Tk::Tile::Style.theme_names,
245                                     glob_ptn))
246      end
247    end
248
249    def self.set_theme(theme)
250      if TILE_SPEC_VERSION_ID < 8 && Tk.info(:commands, '::ttk::setTheme').empty?
251        cmd = '::tile::setTheme'
252      else
253        cmd = '::ttk::setTheme'
254      end
255
256      begin
257        Tk.tk_call_without_enc(cmd, theme)
258      rescue
259        Tk::Tile::Style.theme_use(theme)
260      end
261    end
262
263    module KeyNav
264      if Tk::Tile::TILE_SPEC_VERSION_ID < 8
265        def self.enableMnemonics(w)
266          Tk.tk_call('::keynav::enableMnemonics', w)
267        end
268        def self.defaultButton(w)
269          Tk.tk_call('::keynav::defaultButton', w)
270        end
271      else # dummy
272        def self.enableMnemonics(w)
273          ""
274        end
275        def self.defaultButton(w)
276          ""
277        end
278      end
279    end
280
281    module Font
282      Default      = 'TkDefaultFont'
283      Text         = 'TkTextFont'
284      Heading      = 'TkHeadingFont'
285      Caption      = 'TkCaptionFont'
286      Tooltip      = 'TkTooltipFont'
287
288      Fixed        = 'TkFixedFont'
289      Menu         = 'TkMenuFont'
290      SmallCaption = 'TkSmallCaptionFont'
291      Icon         = 'TkIconFont'
292
293      TkFont::SYSTEM_FONT_NAMES.add [
294        'TkDefaultFont', 'TkTextFont', 'TkHeadingFont',
295        'TkCaptionFont', 'TkTooltipFont', 'TkFixedFont',
296        'TkMenuFont', 'TkSmallCaptionFont', 'TkIconFont'
297      ]
298    end
299
300    module ParseStyleLayout
301      def _style_layout(lst)
302        ret = []
303        until lst.empty?
304          sub = [lst.shift]
305          keys = {}
306
307          until lst.empty?
308            if lst[0][0] == ?-
309              k = lst.shift[1..-1]
310              children = lst.shift
311              children = _style_layout(children) if children.kind_of?(Array)
312              keys[k] = children
313            else
314              break
315            end
316          end
317
318          sub << keys unless keys.empty?
319          ret << sub
320        end
321        ret
322      end
323      private :_style_layout
324    end
325
326    module TileWidget
327      include Tk::Tile::ParseStyleLayout
328
329      def __val2ruby_optkeys  # { key=>proc, ... }
330        # The method is used to convert a opt-value to a ruby's object.
331        # When get the value of the option "key", "proc.call(value)" is called.
332        super().update('style'=>proc{|v| _style_layout(list(v))})
333      end
334      private :__val2ruby_optkeys
335
336      def ttk_instate(state, script=nil, &b)
337        if script
338          tk_send('instate', state, script)
339        elsif b
340          tk_send('instate', state, Proc.new(&b))
341        else
342          bool(tk_send('instate', state))
343        end
344      end
345      alias tile_instate ttk_instate
346
347      def ttk_state(state=nil)
348        if state
349          tk_send('state', state)
350        else
351          list(tk_send('state'))
352        end
353      end
354      alias tile_state ttk_state
355
356      def ttk_identify(x, y)
357        ret = tk_send_without_enc('identify', x, y)
358        (ret.empty?)? nil: ret
359      end
360      alias tile_identify ttk_identify
361
362      # remove instate/state/identify method
363      # to avoid the conflict with widget options
364      if Tk.const_defined?(:USE_OBSOLETE_TILE_STATE_METHOD) && Tk::USE_OBSOLETE_TILE_STATE_METHOD
365        alias instate  ttk_instate
366        alias state    ttk_state
367        alias identify ttk_identify
368      end
369    end
370
371    ######################################
372
373    autoload :TButton,       'tkextlib/tile/tbutton'
374    autoload :Button,        'tkextlib/tile/tbutton'
375
376    autoload :TCheckButton,  'tkextlib/tile/tcheckbutton'
377    autoload :CheckButton,   'tkextlib/tile/tcheckbutton'
378    autoload :TCheckbutton,  'tkextlib/tile/tcheckbutton'
379    autoload :Checkbutton,   'tkextlib/tile/tcheckbutton'
380
381    autoload :Dialog,        'tkextlib/tile/dialog'
382
383    autoload :TEntry,        'tkextlib/tile/tentry'
384    autoload :Entry,         'tkextlib/tile/tentry'
385
386    autoload :TCombobox,     'tkextlib/tile/tcombobox'
387    autoload :Combobox,      'tkextlib/tile/tcombobox'
388
389    autoload :TFrame,        'tkextlib/tile/tframe'
390    autoload :Frame,         'tkextlib/tile/tframe'
391
392    autoload :TLabelframe,   'tkextlib/tile/tlabelframe'
393    autoload :Labelframe,    'tkextlib/tile/tlabelframe'
394    autoload :TLabelFrame,   'tkextlib/tile/tlabelframe'
395    autoload :LabelFrame,    'tkextlib/tile/tlabelframe'
396
397    autoload :TLabel,        'tkextlib/tile/tlabel'
398    autoload :Label,         'tkextlib/tile/tlabel'
399
400    autoload :TMenubutton,   'tkextlib/tile/tmenubutton'
401    autoload :Menubutton,    'tkextlib/tile/tmenubutton'
402    autoload :TMenuButton,   'tkextlib/tile/tmenubutton'
403    autoload :MenuButton,    'tkextlib/tile/tmenubutton'
404
405    autoload :TNotebook,     'tkextlib/tile/tnotebook'
406    autoload :Notebook,      'tkextlib/tile/tnotebook'
407
408    autoload :TPaned,        'tkextlib/tile/tpaned'
409    autoload :Paned,         'tkextlib/tile/tpaned'
410    autoload :PanedWindow,   'tkextlib/tile/tpaned'
411    autoload :Panedwindow,   'tkextlib/tile/tpaned'
412
413    autoload :TProgressbar,  'tkextlib/tile/tprogressbar'
414    autoload :Progressbar,   'tkextlib/tile/tprogressbar'
415
416    autoload :TRadioButton,  'tkextlib/tile/tradiobutton'
417    autoload :RadioButton,   'tkextlib/tile/tradiobutton'
418    autoload :TRadiobutton,  'tkextlib/tile/tradiobutton'
419    autoload :Radiobutton,   'tkextlib/tile/tradiobutton'
420
421    autoload :TScale,        'tkextlib/tile/tscale'
422    autoload :Scale,         'tkextlib/tile/tscale'
423    autoload :TProgress,     'tkextlib/tile/tscale'
424    autoload :Progress,      'tkextlib/tile/tscale'
425
426    autoload :TScrollbar,    'tkextlib/tile/tscrollbar'
427    autoload :Scrollbar,     'tkextlib/tile/tscrollbar'
428    autoload :XScrollbar,    'tkextlib/tile/tscrollbar'
429    autoload :YScrollbar,    'tkextlib/tile/tscrollbar'
430
431    autoload :TSeparator,    'tkextlib/tile/tseparator'
432    autoload :Separator,     'tkextlib/tile/tseparator'
433
434    autoload :TSpinbox,      'tkextlib/tile/tspinbox'
435    autoload :Spinbox,       'tkextlib/tile/tspinbox'
436
437    autoload :TSquare,       'tkextlib/tile/tsquare'
438    autoload :Square,        'tkextlib/tile/tsquare'
439
440    autoload :SizeGrip,      'tkextlib/tile/sizegrip'
441    autoload :Sizegrip,      'tkextlib/tile/sizegrip'
442
443    autoload :Treeview,      'tkextlib/tile/treeview'
444
445    autoload :Style,         'tkextlib/tile/style'
446  end
447end
448
449Ttk = Tk::Tile
450