File: dialog.rb

package info (click to toggle)
mikutter 5.0.4%2Bdfsg1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 9,700 kB
  • sloc: ruby: 21,307; sh: 181; makefile: 19
file content (252 lines) | stat: -rw-r--r-- 7,255 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# -*- coding: utf-8 -*-

require 'mui/gtk_form_dsl'
require 'mui/gtk_form_dsl_multi_select'
require 'mui/gtk_form_dsl_select'

module Plugin::Gtk3
  class Dialog < Gtk::Dialog
    # ダイアログを開く。このメソッドを直接利用せずに、Pluginのdialog DSLを利用すること。
    # ==== Args
    # [title:] ダイアログのタイトルバーに表示する内容(String)
    # [parent:] 親ウィンドウ.ダイアログはモーダルとして開かれる.
    # [promise:] 入力が完了・中断された時に呼ばれるDeferedオブジェクト
    # [plugin:] 呼び出し元のPluggaloid Plugin
    # [default:] エレメントのデフォルト値。{キー: デフォルト値}のようなHash
    # [&proc] DSLブロック
    # ==== Return
    # 作成されたDialogのインスタンス
    def self.open(**kwrest, &proc)
      dialog = new(**kwrest, &proc)
      dialog.show_all
      dialog
    end

    def initialize(title:, parent:, promise:, plugin:, default:, &p)
      super(title: title, parent: parent)
      @plugin = plugin
      @promise = promise
      @container = Container.new(plugin, default.to_h.dup, &p)
      @container.error_observer = self
      child.add @container
      set_size_request(480, 300)
      set_window_position(:center)
      add_button(Gtk::Stock::OK, :ok)
      add_button(Gtk::Stock::CANCEL, :cancel)
      register_response_listener
      @container.run
    end

    def on_abort(err)
      if err.is_a?(String)
        Delayer.new do
          set_sensitive(false)
          alert = Gtk::MessageDialog.new(nil,
                                         Gtk::Dialog::DESTROY_WITH_PARENT,
                                         Gtk::MessageDialog::ERROR,
                                         Gtk::MessageDialog::BUTTONS_CLOSE,
                                         err)
          alert.ssc(:response){|widget| widget.destroy; false }
          alert.ssc(:destroy) do
            set_sensitive(true)
            @container.reset
            @container.run
            false
          end
          alert.show_all
        end
      else
        Delayer.new do
          @promise.fail(err) if @promise
          @promise = nil
          destroy
        end
      end
    end

    private

    def register_response_listener
      ssc(:response) do |widget, response|
        if Gtk::ResponseType::OK == response
          case @container.state
          when Container::STATE_WAIT
            @container.run(Response::Ok.new(@container))
          when Container::STATE_EXIT
            @promise.call(Response::Ok.new(@container)) if @promise
            @promise = nil
            destroy
          end
        else
          @promise.fail(Response::Cancel.new(@container)) if @promise
          @promise = nil
          destroy
        end
        true
      end
      ssc(:destroy) do
        @promise.fail(Response::Cancel.new(@container)) if @promise
        @promise = nil
        false
      end
      @container.ssc(:state_changed) do |widget, state|
        action_area.sensitive = widget.sensitive?
        false
      end
    end

    module Response
      class Base
        attr_reader :result

        def initialize(values)
          @values = values.to_h.freeze
          @result = values.result_of_proc
        end

        def [](k)
          @values[k.to_sym]
        end

        def to_h
          @values.to_h
        end
      end

      class Ok < Base
        def ok? ; true end
        def state ; :ok end
      end

      class Cancel < Base
        def ok? ; false end
        def state ; :cancel end
      end

      class Close < Base
        def ok? ; false end
        def state ; :close end
      end
    end

    class Container < Gtk::Grid
      EXIT = :exit
      AWAIT = :await

      STATE_INIT = :dialog_state_init
      STATE_RUN = :dialog_state_run
      STATE_EXIT = :dialog_state_exit
      STATE_WAIT = :dialog_state_wait
      STATE_AWAIT = :dialog_state_await

      include Gtk::FormDSL

      attr_reader :state, :result_of_proc, :awaiting_deferred
      attr_accessor :error_observer

      # dialog DSLから利用するメソッド。
      # dialogウィンドウのエレメントの配置を、ユーザが次へボタンを押すまで中断する。
      # 次へボタンが押されたら、 その時点で各エレメントに入力された内容を格納した
      # Plugin::Gtk::DialogWindow::Response::Ok のインスタンスを返す
      def await_input
        Fiber.yield
      end

      # dialog DSLから利用するメソッド。
      # 初期値を動的に設定するためのメソッド。
      # {エレメントのキー: 値} のように書くことで、複数同時に設定できる。
      # 既に置かれたエレメントの内容がこのメソッドによって書き換わることはないので、
      # エレメントを配置する前に呼び出す必要がある。
      def set_value(v={})
        @values.merge!(v)
      end

      # dialog DSLから利用するメソッド。
      # Deferredを受け取り、その処理が終わるまで処理を止める。
      # 処理が終わると、deferの結果を返す。処理が失敗していると、
      # ダイアログウィンドウを閉じ、dialog DSLのtrapブロックを呼ぶ。
      def await(defer)
        Fiber.yield(AWAIT, defer)
      end

      def create_inner_setting
        self.class.new(@plugin, @values)
      end

      def initialize(plugin, default=Hash.new, &p)
        @plugin = plugin
        @values = default
        @proc = p
        reset

        super() {}
        self.margin = 30
        self.row_spacing = self.column_spacing = 12
      end

      def run(response=nil)
        Deferred.new do
          case state
          when STATE_INIT
            @fiber = Fiber.new do
              @result_of_proc = instance_eval(&@proc)
              if @result_of_proc.is_a? Delayer::Deferred::Deferredable::Awaitable
                @result_of_proc = await(@result_of_proc)
              end
              EXIT
            end
            resume(response)
          when STATE_WAIT
            children.each(&method(:remove))
            resume(response)
          when STATE_AWAIT
            resume(response)
          end
        end.trap do |err|
          error err
        end
      end

      def resume(response)
        @state = STATE_RUN
        result, *args = @fiber.resume(response)
        set_sensitive(true)
        show_all
        case result
        when EXIT
          @state = STATE_EXIT
        when AWAIT
          @state = STATE_AWAIT
          @awaiting_deferred, = *args
          set_sensitive(false)
          @awaiting_deferred.next{|deferred_result|
            run(deferred_result)
          }.trap{|err|
            error err

            @error_observer.on_abort(err) if @error_observer
          }
        else
          @state = STATE_WAIT
        end
      end

      def [](key)
        @values[key.to_sym]
      end

      def []=(key, value)
        @values[key.to_sym] = value
      end

      def reset
        @state = STATE_INIT
        self
      end

      def to_h
        @values.dup
      end
    end
  end
end