Project

General

Profile

機能 #763 » builder.rb

Satoshi Okuno, 2015-07-31 22:35

 
1
# -*- coding: utf-8 -*-
2

    
3
miquire :core, 'plugin'
4

    
5
require 'gtk2'
6

    
7
=begin rdoc
8
プラグインに、簡単に設定ファイルを定義する機能を提供する。
9
以下の例は、このクラスを利用してプラグインの設定画面を定義する例。
10
  Plugin.create(:test) do
11
    settings("設定") do
12
      boolean "チェックする", :test_check
13
    end
14
  end
15

    
16
settingsの中身は、 Plugin::Settings のインスタンスの中で実行される。
17
つまり、 Plugin::Settings のインスタンスメソッドは、 _settings{}_ の中で実行できるメソッドと同じです。
18
例ではbooleanメソッドを呼び出して、真偽値を入力させるウィジェットを配置させるように定義している
19
(チェックボックス)。明確にウィジェットを設定できるわけではなくて、値の意味を定義するだけなので、
20
前後関係などに影響されてウィジェットが変わる場合があるかも。
21
=end
22
class Plugin::Settings < Gtk::VBox
23

    
24
  def initialize(plugin)
25
    type_strict plugin => Plugin
26
    super()
27
    @plugin = plugin
28
    if block_given?
29
      instance_eval(&Proc.new)
30
    end
31
  end
32

    
33
  # 複数行テキスト
34
  # ==== Args
35
  # [label] ラベル
36
  # [config] 設定のキー
37
  def multitext(label, config)
38
    container = Gtk::HBox.new(false, 0)
39
    input = Gtk::TextView.new
40
    input.wrap_mode = Gtk::TextTag::WRAP_CHAR
41
    input.border_width = 2
42
    input.accepts_tab = false
43
    input.editable = true
44
    input.width_request = HYDE
45
    input.buffer.text = Listener[config].get || ''
46
    container.pack_start(Gtk::Label.new(label), false, true, 0) if label
47
    container.pack_start(Gtk::Alignment.new(1.0, 0.5, 0, 0).add(input), true, true, 0)
48
    input.buffer.ssc('changed'){ |widget|
49
      Listener[config].set widget.text }
50
    closeup container
51
    container
52
  end
53

    
54
  # 特定範囲の数値入力
55
  # ==== Args
56
  # [label] ラベル
57
  # [config] 設定のキー
58
  # [min] 最低値。これより小さい数字は入力できないようになる
59
  # [max] 最高値。これより大きい数字は入力できないようになる
60
  def adjustment(name, config, min, max)
61
    container = Gtk::HBox.new(false, 0)
62
    container.pack_start(Gtk::Label.new(name), false, true, 0)
63
    adj = Gtk::Adjustment.new((Listener[config].get or min).to_f, min.to_f, max.to_f, 1.0, 5.0, 0.0)
64
    spinner = Gtk::SpinButton.new(adj, 0, 0)
65
    adj.signal_connect('value-changed'){ |widget, e|
66
      Listener[config].set widget.value.to_i
67
      false
68
    }
69
    closeup container.pack_start(Gtk::Alignment.new(1.0, 0.5, 0, 0).add(spinner), true, true, 0)
70
    container
71
  end
72

    
73
  # 真偽値入力
74
  # ==== Args
75
  # [label] ラベル
76
  # [config] 設定のキー
77
  def boolean(label, config)
78
    input = Gtk::CheckButton.new(label)
79
    input.active = Listener[config].get
80
    input.signal_connect('toggled'){ |widget|
81
      Listener[config].set widget.active? }
82
    closeup input
83
    input end
84

    
85
  # ファイルを選択する
86
  # ==== Args
87
  # [label] ラベル
88
  # [config] 設定のキー
89
  # [current] 初期のディレクトリ
90
  def fileselect(label, config, current=Dir.pwd)
91
    fsselect(label, config, current, Gtk::FileChooser::ACTION_OPEN)
92
  end
93

    
94
  # ディレクトリを選択する
95
  # ==== Args
96
  # [label] ラベル
97
  # [config] 設定のキー
98
  # [current] 初期のディレクトリ
99
  def dirselect(label, config, current=Dir.pwd)
100
    fsselect(label, config, current, Gtk::FileChooser::ACTION_SELECT_FOLDER)
101
  end
102
  
103
  # 一行テキストボックス
104
  # ==== Args
105
  # [label] ラベル
106
  # [config] 設定のキー
107
  def input(label, config)
108
    container = Gtk::HBox.new(false, 0)
109
    input = Gtk::Entry.new
110
    input.text = Listener[config].get || ""
111
    container.pack_start(Gtk::Label.new(label), false, true, 0) if label
112
    container.pack_start(Gtk::Alignment.new(1.0, 0.5, 0, 0).add(input), true, true, 0)
113
    input.signal_connect('changed'){ |widget|
114
      Listener[config].set widget.text }
115
    closeup container
116
    container
117
  end
118

    
119
  # 一行テキストボックス(非表示)
120
  # ==== Args
121
  # [label] ラベル
122
  # [config] 設定のキー
123
  def inputpass(label, config)
124
    container = Gtk::HBox.new(false, 0)
125
    input = Gtk::Entry.new
126
    input.visibility = false
127
    input.text = Listener[config].get
128
    container.pack_start(Gtk::Label.new(label), false, true, 0) if label
129
    container.pack_start(Gtk::Alignment.new(1.0, 0.5, 0, 0).add(input), true, true, 0)
130
    input.signal_connect('changed'){ |widget|
131
      Listener[config].set widget.text }
132
    closeup container
133
    container
134
  end
135

    
136
  # 複数テキストボックス
137
  # 任意個の項目を入力させて、配列で受け取る。
138
  # ==== Args
139
  # [label] ラベル
140
  # [config] 設定のキー
141
  def multi(label, config)
142
    settings(label) do
143
      container, box = Gtk::HBox.new(false, 0), Gtk::VBox.new(false, 0)
144
      input_ary = []
145
      btn_add = Gtk::Button.new(Gtk::Stock::ADD)
146
      array_converter = lambda {
147
        c = Listener[config].get || []
148
        (c.is_a?(Array) ? c : [c]).compact }
149
      add_button = lambda { |content|
150
        input = Gtk::Entry.new
151
        input.text = content.to_s
152
        input.ssc(:changed) { |w|
153
          Listener[config].set w.parent.children.map(&:text).compact }
154
        input.ssc('focus_out_event'){ |w|
155
          w.parent.remove(w) if w.text.empty?
156
          false }
157
        box.closeup input
158
        input }
159
      input_ary = array_converter.call.each(&add_button)
160
      btn_add.ssc(:clicked) { |w|
161
        w.get_ancestor(Gtk::Window).set_focus(add_button.call("").show)
162
        false }
163
      container.pack_start(box, true, true, 0)
164
      container.pack_start(Gtk::Alignment.new(1.0, 1.0, 0, 0).add(btn_add), false, true, 0)
165
      closeup container
166
      container
167
    end
168
  end
169

    
170
  # 設定のグループ。関連の強い設定をカテゴライズできる。
171
  # ==== Args
172
  # [title] ラベル
173
  # [&block] ブロック
174
  def settings(title)
175
    group = Gtk::Frame.new.set_border_width(8)
176
    if(title.is_a?(Gtk::Widget))
177
      group.set_label_widget(title)
178
    else
179
      group.set_label(title) end
180
    box = Plugin::Settings.new(@plugin).set_border_width(4)
181
    box.instance_eval(&Proc.new)
182
    closeup group.add(box)
183
    group
184
  end
185

    
186
  # 〜についてダイアログを出すためのボタン。押すとダイアログが出てくる
187
  # ==== Args
188
  # [label] ラベル
189
  # [options]
190
  #   設定値。以下のキーを含むハッシュ。
191
  #   _:name_ :: ソフトウェア名
192
  #   _:version_ :: バージョン
193
  #   _:copyright_ :: コピーライト
194
  #   _:comments_ :: コメント
195
  #   _:license_ :: ライセンス
196
  #   _:website_ :: Webページ
197
  #   _:logo_ :: ロゴ画像のフルパス
198
  #   _:authors_ :: 作者の名前。通常Twitter screen name(Array)
199
  #   _:artists_ :: デザイナとかの名前。通常Twitter screen name(Array)
200
  #   _:documenters_ :: ドキュメントかいた人とかの名前。通常Twitter screen name(Array)
201
  def about(label, options={})
202
    about = Gtk::Button.new(label)
203
    about.signal_connect("clicked"){
204
      dialog = Gtk::AboutDialog.new.show
205
      options.each { |key, value|
206
        dialog.__send__("#{key}=", about_converter[key][value]) }
207
      dialog.signal_connect('response') { dialog.destroy } }
208
    closeup about
209
    about end
210

    
211
  # フォントを決定させる。押すとフォント、サイズを設定するダイアログが出てくる。
212
  # ==== Args
213
  # [label] ラベル
214
  # [config] 設定のキー
215
  def font(label, config)
216
    closeup container = Gtk::HBox.new(false, 0).add(Gtk::Label.new(label).left).closeup(fontselect(label, config))
217
    container end
218

    
219
  # 色を決定させる。押すと色を設定するダイアログが出てくる。
220
  # ==== Args
221
  # [label] ラベル
222
  # [config] 設定のキー
223
  def color(label, config)
224
    closeup container = Gtk::HBox.new(false, 0).add(Gtk::Label.new(label).left).closeup(colorselect(label, config))
225
    container end
226

    
227
  # フォントと色を決定させる。
228
  # ==== Args
229
  # [label] ラベル
230
  # [font] フォントの設定のキー
231
  # [color] 色の設定のキー
232
  def fontcolor(label, font, color)
233
    closeup container = font(label, font).closeup(colorselect(label, color))
234
    container end
235

    
236
  # 要素を1つ選択させる
237
  # ==== Args
238
  # [label] ラベル
239
  # [config] 設定のキー
240
  # [default]
241
  #   連想配列で、 _値_ => _ラベル_ の形式で、デフォルト値を与える。
242
  #   _block_ と同時に与えれられたら、 _default_ の値が先に入って、 _block_ は後に入る。
243
  # [&block] 内容
244
  def select(label, config, default = {})
245
    builder = Plugin::Settings::Select.new(@plugin, default)
246
    builder.instance_eval(&Proc.new) if block_given?
247
    closeup container = builder.build(label, config)
248
    container end
249

    
250
  # 要素を複数個選択させる
251
  # ==== Args
252
  # [label] ラベル
253
  # [config] 設定のキー
254
  # [default]
255
  #   連想配列で、 _値_ => _ラベル_ の形式で、デフォルト値を与える。
256
  #   _block_ と同時に与えれられたら、 _default_ の値が先に入って、 _block_ は後に入る。
257
  # [&block] 内容
258
  def multiselect(label, config, default = {})
259
    builder = Plugin::Settings::MultiSelect.new(@plugin, default)
260
    builder.instance_eval(&Proc.new) if block_given?
261
    closeup container = builder.build(label, config)
262
    container end
263

    
264
  private
265
  def about_converter
266
    Hash.new(ret_nth).merge!( :logo => lambda{ |value| Gtk::WebIcon.new(value).pixbuf rescue nil } ) end
267
  memoize :about_converter
268

    
269
  def colorselect(label, config)
270
    color = Listener[config].get
271
    button = Gtk::ColorButton.new((color and Gdk::Color.new(*color)))
272
    button.title = label
273
    button.signal_connect('color-set'){ |w|
274
      Listener[config].set w.color.to_a }
275
    button end
276

    
277
  def fontselect(label, config)
278
    button = Gtk::FontButton.new(Listener[config].get)
279
    button.title = label
280
    button.signal_connect('font-set'){ |w|
281
      Listener[config].set w.font_name }
282
    button end
283

    
284
  def fsselect(label, config, current=Dir.pwd, action)
285
    container = input(label, config)
286
    input = container.children.last.children.first
287
    button = Gtk::Button.new('参照')
288
    container.pack_start(button, false)
289
    button.signal_connect('clicked'){ |widget|
290
      dialog = Gtk::FileChooserDialog.new("Open File",
291
                                          widget.get_ancestor(Gtk::Window),
292
                                          action,
293
                                          nil,
294
                                          [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
295
                                          [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT])
296
      dialog.current_folder = File.expand_path(current)
297
      if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
298
        Listener[config].set dialog.filename
299
        input.text = dialog.filename
300
      end
301
      dialog.destroy
302
    }
303
    container
304
  end
305
 
306
  def method_missing(*args, &block)
307
    @plugin.__send__(*args, &block)
308
  end
309

    
310
end
311

    
312
require File.expand_path File.join(File.dirname(__FILE__), 'select')
313
require File.expand_path File.join(File.dirname(__FILE__), 'multiselect')
314
require File.expand_path File.join(File.dirname(__FILE__), 'listener')
(1-1/2)