aboutsummaryrefslogtreecommitdiffstats
path: root/core/plugin/gtk/gtk.rb
blob: 5e54034652449c9cc0a54bab48c155d66292f1a1 (plain)
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
# -*- coding: utf-8 -*-

# RubyGnome2を用いてUIを表示するプラグイン

require "gtk2"

miquire :mui,
'cell_renderer_message', 'coordinate_module', 'icon_over_button', 'inner_tl', 'markup_generator',
'miracle_painter', 'replyviewer', 'sub_parts_favorite', 'sub_parts_helper',
'sub_parts_retweet', 'sub_parts_voter', 'textselector', 'timeline', 'contextmenu', 'crud',
'extension', 'intelligent_textview', 'keyconfig', 'listlist', 'message_picker', 'mtk', 'postbox',
'pseudo_signal_handler', 'selectbox', 'timeline_utils', 'userlist', 'webicon'

require File.expand_path File.join(File.dirname(__FILE__), 'mikutter_window')
require File.expand_path File.join(File.dirname(__FILE__), 'tab_container')
require File.expand_path File.join(File.dirname(__FILE__), 'tab_toolbar')
require File.expand_path File.join(File.dirname(__FILE__), 'delayer')
require File.expand_path File.join(File.dirname(__FILE__), 'slug_dictionary')
require File.expand_path File.join(File.dirname(__FILE__), 'mainloop')
require File.expand_path File.join(File.dirname(__FILE__), 'konami_watcher')
require_relative 'dialog_window'

Plugin.create :gtk do
  @slug_dictionary = Plugin::Gtk::SlugDictionary.new # widget_type => {slug => Gtk}
  @tabs_promise = {}                     # slug => Deferred

  TABPOS = [Gtk::POS_TOP, Gtk::POS_BOTTOM, Gtk::POS_LEFT, Gtk::POS_RIGHT]

  # ウィンドウ作成。
  # PostBoxとか複数のペインを持つための処理が入るので、Gtk::MikutterWindowクラスを新設してそれを使う
  on_window_created do |i_window|
    window = ::Gtk::MikutterWindow.new(i_window, self)
    @slug_dictionary.add(i_window, window)
    window.title = i_window.name
    window.set_size_request(240, 240)
    geometry = get_window_geometry(i_window.slug)
    window.set_default_size(*geometry[:size])
    window.move(*geometry[:position])
    window.ssc(:event){ |window, event|
      if event.is_a? Gdk::EventConfigure
        geometry = (UserConfig[:windows_geometry] || {}).melt
        size = window.window.geometry[2,2]
        position = window.position
        modified = false
        if defined?(geometry[i_window.slug]) and geometry[i_window.slug].is_a? Hash
          geometry[i_window.slug] = geometry[i_window.slug].melt
          if geometry[i_window.slug][:size] != size
            modified = geometry[i_window.slug][:size] = size end
          if geometry[i_window.slug][:position] != position
            modified = geometry[i_window.slug][:position] = position end
        else
          modified = geometry[i_window.slug] = {
            size: size,
            position: position } end
        if modified
          UserConfig[:windows_geometry] = geometry end end
      false }
    window.ssc("destroy"){
      Delayer.freeze
      window.destroy
      ::Gtk::Object.main_quit
      # Gtk.main_quit
      false }
    window.ssc(:focus_in_event) {
      i_window.active!(true, true)
      false
    }
    window.ssc('key_press_event'){ |widget, event|
      Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_window) }
    window.show_all
  end

  on_gui_window_change_icon do |i_window, icon|
    window = widgetof(i_window)
    if window
      window.icon = icon.load_pixbuf(width: 256, height: 256){|pb|
        window.icon = pb if not window.destroyed?
      }
    end
  end

  # ペイン作成。
  # ペインはGtk::NoteBook
  on_pane_created do |i_pane|
    pane = create_pane(i_pane)
    pane.set_tab_border(0).set_group_id(0).set_scrollable(true)
    pane.set_tab_pos(TABPOS[UserConfig[:tab_position]])
    tab_position_listener = on_userconfig_modify do |key, val|
      next if key != :tab_position
      if pane.destroyed?
        tab_position_listener.detach
      else
        pane.set_tab_pos(TABPOS[val])
      end
    end
    pane.ssc(:page_reordered){ |this, tabcontainer, index|
        Plugin.call(:rewind_window_order, i_pane.parent) if i_pane.parent
      i_tab = tabcontainer.i_tab
      if i_tab
        i_pane.reorder_child(i_tab, index) end
      Plugin.call(:after_gui_tab_reordered, i_tab)
      false }
    pane.ssc(:switch_page){ |this, page, pagenum|
      if pagenum == pane.page
        i_pane.set_active_child(pane.get_nth_page(pagenum).i_tab, true) end }
    pane.signal_connect(:page_added){ |this, tabcontainer, index|
      type_strict tabcontainer => ::Gtk::TabContainer
      Plugin.call(:rewind_window_order, i_pane.parent) if i_pane.parent
      i_tab = tabcontainer.i_tab
      next false if i_tab.parent == i_pane
      Plugin.call(:after_gui_tab_reparent, i_tab, i_tab.parent, i_pane)
      i_pane.add_child(i_tab, index)
      false }
    # 子が無くなった時 : このpaneを削除
    pane.signal_connect(:page_removed){
      if not(pane.destroyed?) and pane.children.empty? and pane.parent
        pane.parent.remove(pane)
        tab_position_listener.detach
        pane_order_delete(i_pane)
        i_pane.destroy end
      false }
  end

  # タブ作成。
  # タブには実体が無いので、タブのアイコンのところをGtk::EventBoxにしておいて、それを実体ということにしておく
  on_tab_created do |i_tab|
    tab = create_tab(i_tab)
    if @tabs_promise[i_tab.slug]
      @tabs_promise[i_tab.slug].call(tab)
      @tabs_promise.delete(i_tab.slug) end end

  on_cluster_created do |i_cluster|
    create_pane(i_cluster) end

  on_fragment_created do |i_fragment|
    create_tab(i_fragment) end

  # タブを作成する
  # ==== Args
  # [i_tab] タブ
  # ==== Return
  # Tab(Gtk::EventBox)
  def create_tab(i_tab)
    tab = ::Gtk::EventBox.new.tooltip(i_tab.name)
    @slug_dictionary.add(i_tab, tab)
    tab_update_icon(i_tab)
    tab.ssc(:focus_in_event) {
      i_tab.active!(true, true)
      false
    }
    tab.ssc(:key_press_event){ |widget, event|
      Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_tab) }
    tab.ssc(:button_press_event) { |this, event|
      if event.button == 3
        Plugin::GUI::Command.menu_pop(i_tab)
      else
        Plugin::GUI.keypress(::Gtk::buttonname([event.event_type, event.button, event.state]), i_tab)
      end
      false }
    tab.ssc(:destroy) {
      i_tab.destroy
      false }
    tab.show_all end

  on_tab_toolbar_created do |i_tab_toolbar|
    tab_toolbar = ::Gtk::TabToolbar.new(i_tab_toolbar).show_all
    @slug_dictionary.add(i_tab_toolbar, tab_toolbar)
  end

  on_gui_tab_toolbar_join_tab do |i_tab_toolbar, i_tab|
    widget = widgetof(i_tab_toolbar)
    widget_join_tab(i_tab, widget) if widget
  end

  # タイムライン作成。
  # Gtk::TimeLine
  on_timeline_created do |i_timeline|
    gtk_timeline = ::Gtk::TimeLine.new(i_timeline)
    @slug_dictionary.add(i_timeline, gtk_timeline)
    gtk_timeline.tl.ssc(key_press_event: timeline_key_press_event(i_timeline),
                        focus_in_event:  timeline_focus_in_event(i_timeline),
                        destroy:         timeline_destroy_event(i_timeline))
    gtk_timeline.show_all
  end

  # Timelineウィジェットのfocus_in_eventのコールバックを返す
  # ==== Args
  # [i_timeline] タイムラインのインターフェイス
  # ==== Return
  # Proc
  def timeline_focus_in_event(i_timeline)
    lambda { |this, event|
      if this.focus?
        i_timeline.active!(true, true) end
      false } end

  # Timelineウィジェットのkey_press_eventのコールバックを返す
  # ==== Args
  # [i_timeline] タイムラインのインターフェイス
  # ==== Return
  # Proc
  def timeline_key_press_event(i_timeline)
    lambda { |widget, event|
      Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_timeline) } end

  # Timelineウィジェットのdestroyのコールバックを返す
  # ==== Args
  # [i_timeline] タイムラインのインターフェイス
  # ==== Return
  # Proc
  def timeline_destroy_event(i_timeline)
    lambda { |this|
      i_timeline.destroy
      false } end

  on_gui_pane_join_window do |i_pane, i_window|
    window = widgetof(i_window)
    pane = widgetof(i_pane)
    if pane.parent
      if pane.parent != window.panes
        pane.parent.remove(pane)
        window.panes.pack_end(pane, false).show_all end
    else
      window.panes.pack_end(pane, false).show_all
    end
  end

  on_gui_tab_join_pane do |i_tab, i_pane|
    i_widget = i_tab.children.first
    next if not i_widget
    widget = widgetof(i_widget)
    next if not widget
    tab = widgetof(i_tab)
    pane = widgetof(i_pane)
    old_pane = widget.get_ancestor(::Gtk::Notebook)
    if tab and pane and old_pane and pane != old_pane
      if tab.parent
        page_num = tab.parent.get_tab_pos_by_tab(tab)
        if page_num
          tab.parent.remove_page(page_num)
        else
          raise Plugin::Gtk::GtkError, "#{tab} not found in #{tab.parent}" end end
      i_tab.children.each{ |i_child|
        w_child = widgetof(i_child)
        w_child.parent.remove(w_child)
        widget_join_tab(i_tab, w_child) }
      tab.show_all end
    Plugin.call(:rewind_window_order, i_pane.parent) if i_pane.parent
  end

  on_gui_timeline_join_tab do |i_timeline, i_tab|
    widget = widgetof(i_timeline)
    widget_join_tab(i_tab, widget) if widget end

  on_gui_cluster_join_tab do |i_cluster, i_tab|
    widget = widgetof(i_cluster)
    widget_join_tab(i_tab, widget) if widget end

  on_gui_timeline_add_messages do |i_timeline, messages|
    gtk_timeline = widgetof(i_timeline)
    gtk_timeline.add(messages) if gtk_timeline and not gtk_timeline.destroyed? end

  on_gui_postbox_join_widget do |i_postbox|
    type_strict i_postbox => Plugin::GUI::Postbox
    i_postbox_parent = i_postbox.parent
    next if not i_postbox_parent
    postbox_parent = widgetof(i_postbox_parent)
    next if not postbox_parent
    postbox = @slug_dictionary.add(i_postbox, postbox_parent.add_postbox(i_postbox))
    postbox.post.ssc(:focus_in_event) {
      i_postbox.active!(true, true)
      false }

    postbox.post.ssc("populate-popup"){ |widget, menu|
      (event, items) = Plugin::GUI::Command.get_menu_items(i_postbox)

      menu.append(Gtk::SeparatorMenuItem.new) if items.length != 0
      menu2 = Gtk::ContextMenu.new(*items).build!(i_postbox, event, menu)
      menu2.show_all

      true }

    postbox.post.ssc('key_press_event'){ |widget, event|
      Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_postbox) }
    postbox.post.ssc(:destroy){
      i_postbox.destroy
      false }
  end

  on_gui_tab_change_icon do |i_tab|
    tab_update_icon(i_tab) end

  on_tab_toolbar_rewind do |i_tab_toolbar|
    tab_toolbar = widgetof(i_tab_toolbar)
    if tab_toolbar
      tab_toolbar.set_button end end

  on_gui_contextmenu do |event, contextmenu|
    widget = widgetof(event.widget)
    if widget
      ::Gtk::ContextMenu.new(*contextmenu).popup(widget, event) end end

  on_gui_timeline_clear do |i_timeline|
    timeline = widgetof(i_timeline)
    if timeline
      timeline.clear end end

  on_gui_timeline_scroll_to_top do |i_timeline|
    timeline = widgetof(i_timeline)
    if timeline
      timeline.set_cursor_to_display_top end end

  on_gui_timeline_move_cursor_to do |i_timeline, message|
    tl = widgetof(i_timeline)
    if tl
      path, column = tl.cursor
      if path and column
        case message
        when :prev
          path.prev!
          tl.set_cursor(path, column, false)
        when :next
          path.next!
          tl.set_cursor(path, column, false)
        else
          if message.is_a? Integer
            path, = *tl.get_path(0, message)
              tl.set_cursor(path, column, false) if path end end end end end

  on_gui_timeline_set_order do |i_timeline, order|
    widgetof(i_timeline).set_order(&order) end

  filter_gui_timeline_select_messages do |i_timeline, messages|
    [i_timeline,
     messages.select(&widgetof(i_timeline).method(:include?))] end

  filter_gui_timeline_reject_messages do |i_timeline, messages|
    w_timeline = widgetof(i_timeline)
    if w_timeline
      [i_timeline,
       messages.reject(&w_timeline.method(:include?))]
    else
      [i_timeline, messages]
    end
  end

  on_gui_postbox_post do |i_postbox|
    postbox = widgetof(i_postbox)
    if postbox
      postbox.post_it end end

  # i_widget.destroyされた時に呼ばれる。
  # 必要ならば、ウィジェットの実体もあわせて削除する。
  on_gui_destroy do |i_widget|
    widget = widgetof(i_widget)
    if widget and not widget.destroyed?
      if i_widget.is_a?(Plugin::GUI::Tab) and i_widget.parent
        pane = widgetof(i_widget.parent)
        if pane
          pane.n_pages.times{ |pagenum|
            if widget == pane.get_tab_label(pane.get_nth_page(pagenum))
              Plugin.call(:rewind_window_order, i_widget.parent.parent)
              pane.remove_page(pagenum)
              break end } end
      else
        widget.parent.remove(widget) if widget.parent
        widget.destroy end end end

  # 互換性のため
  on_mui_tab_regist do |container, name, icon|
    slug = name.to_sym
    i_tab = Plugin::GUI::Tab.instance(slug, name)
    i_tab.set_icon(icon).expand
    i_container = Plugin::GUI::TabChildWidget.instance
    @slug_dictionary.add(i_container, container)
    i_tab << i_container
    @tabs_promise[i_tab.slug] = (@tabs_promise[i_tab.slug] || Deferred.new).next{ |tab|
      widget_join_tab(i_tab, container.show_all) } end

  # Gtkオブジェクトをタブに入れる
  on_gui_nativewidget_join_tab do |i_tab, i_container, container|
    @slug_dictionary.add(i_container, container)
    widget_join_tab(i_tab, container.show_all) end

  on_gui_nativewidget_join_fragment do |i_fragment, i_container, container|
    @slug_dictionary.add(i_container, container)
    widget_join_tab(i_fragment, container.show_all) end

  on_gui_window_rewindstatus do |i_window, text, expire|
    window = @slug_dictionary.get(Plugin::GUI::Window, :default)
    next if not window
    statusbar = window.statusbar
    cid = statusbar.get_context_id("system")
    mid = statusbar.push(cid, text)
    if expire != 0
      Reserver.new(expire, thread: Delayer) do
        if not statusbar.destroyed?
          statusbar.remove(cid, mid)
        end
      end
    end
  end

  on_gui_child_activated do |i_parent, i_child, activated_by_toolkit|
    type_strict i_parent => Plugin::GUI::HierarchyParent, i_child => Plugin::GUI::HierarchyChild
    if !activated_by_toolkit
      if i_child.is_a?(Plugin::GUI::TabLike)
        i_pane = i_parent
        i_tab = i_child
        pane = widgetof(i_pane)
        tab = widgetof(i_tab)
        if pane and tab
          pagenum = pane.get_tab_pos_by_tab(tab)
          pane.page = pagenum if pagenum and pane.page != pagenum end
      elsif i_parent.is_a?(Plugin::GUI::Window)
        i_term = i_child.respond_to?(:active_chain) ? i_child.active_chain.last : i_child
        if i_term
          window = widgetof(i_parent)
          widget = widgetof(i_term)
          if window and widget
            if widget.respond_to? :active
              widget.active
            else
              window.set_focus(widget) end end end end end end

  on_posted do |service, messages|
    messages.each{ |message|
      if(replyto_source = message.replyto_source)
        Gdk::MiraclePainter.findbymessage(replyto_source).each{ |mp|
          mp.on_modify } end } end

  on_favorite do |service, user, message|
    if(user.me?)
      Gdk::MiraclePainter.findbymessage(message).each{ |mp|
        mp.on_modify } end end

  on_konami_activate do
    Gtk.konami_load
  end

  filter_gui_postbox_input_editable do |i_postbox, editable|
    postbox = widgetof(i_postbox)
    if postbox
      [i_postbox, postbox && postbox.post.editable?]
    else
      [i_postbox, editable] end end

  filter_gui_timeline_cursor_position do |i_timeline, y|
    timeline = widgetof(i_timeline)
    if timeline
      path, column = *timeline.cursor
      if path
        rect = timeline.get_cell_area(path, column)
        next [i_timeline, rect.y + (rect.height / 2).to_i] end
    end
    [i_timeline, y] end

  filter_gui_timeline_selected_messages do |i_timeline, messages|
    timeline = widgetof(i_timeline)
    if timeline
      [i_timeline, messages + timeline.get_active_messages]
    else
      [i_timeline, messages] end end

  filter_gui_timeline_selected_text do |i_timeline, message, text|
    timeline = widgetof(i_timeline)
    next [i_timeline, message, text] if not timeline
    record = timeline.get_record_by_message(message)
    next [i_timeline, message, text] if not record
    range = record.miracle_painter.textselector_range
    next [i_timeline, message, text] if not range
    [i_timeline, message, message.entity.to_s[range]]
  end

  filter_gui_destroyed do |i_widget|
    if i_widget.is_a? Plugin::GUI::Widget
      [!widgetof(i_widget)]
    else
      [i_widget] end end

  filter_gui_get_gtk_widget do |i_widget|
    [widgetof(i_widget)] end

  on_gui_dialog do |plugin, title, default, proc, promise|
    Plugin::Gtk::DialogWindow.open(plugin: plugin, title: title, default: default, promise: promise, &proc)
  end

  # タブ _tab_ に _widget_ を入れる
  # ==== Args
  # [i_tab] タブ
  # [widget] Gtkウィジェット
  def widget_join_tab(i_tab, widget)
    tab = widgetof(i_tab)
    return false if not tab
    i_pane = i_tab.parent
    return false if not i_pane
    pane = widgetof(i_pane)
    return false if not pane
    is_tab = i_tab.is_a?(Plugin::GUI::Tab)
    has_child = is_tab and
      not(i_tab.temporary_tab?) and
      not(i_tab.children.any?{ |child|
            not child.is_a? Plugin::GUI::TabToolbar })
    if has_child
      Plugin.call(:rewind_window_order, i_pane.parent) end
    container_index = pane.get_tab_pos_by_tab(tab)
    if container_index
      container = pane.get_nth_page(container_index)
      if container
        return container.pack_start(widget, i_tab.pack_rule[container.children.size]) end end
    if tab.parent
      raise Plugin::Gtk::GtkError, "Gtk Widget #{tab.inspect} of Tab(#{i_tab.slug.inspect}) has parent Gtk Widget #{tab.parent.inspect}" end
    container = ::Gtk::TabContainer.new(i_tab).show_all
    container.ssc(:key_press_event){ |w, event|
      Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_tab) }
    container.pack_start(widget, i_tab.pack_rule[container.children.size])
    pane.append_page(container, tab)
    pane.set_tab_reorderable(container, true).set_tab_detachable(container, true)
    true end

  def tab_update_icon(i_tab)
    type_strict i_tab => Plugin::GUI::TabLike
    tab = widgetof(i_tab)
    if tab
      tab.tooltip(i_tab.name)
      tab.remove(tab.child) if tab.child
      if i_tab.icon
        tab.add(::Gtk::WebIcon.new(i_tab.icon, 24, 24).show)
      else
        tab.add(::Gtk::Label.new(i_tab.name).show) end end
    self end

  def get_window_geometry(slug)
    type_strict slug => Symbol
    geo = UserConfig[:windows_geometry]
    if defined? geo[slug]
      geo[slug]
    else
      size = [Gdk.screen_width/3, Gdk.screen_height*4/5]
      { size: size,
        position: [Gdk.screen_width - size[0], Gdk.screen_height/2 - size[1]/2] } end end

  # ペインを作成
  # ==== Args
  # [i_pane] ペイン
  # ==== Return
  # ペイン(Gtk::Notebook)
  def create_pane(i_pane)
    pane = ::Gtk::Notebook.new
    @slug_dictionary.add(i_pane, pane)
    pane.ssc('key_press_event'){ |widget, event|
      Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_pane) }
    pane.ssc(:destroy){
      i_pane.destroy if i_pane.destroyed?
      false }
    pane.show_all end

  # ウィンドウ内のペイン、タブの現在の順序を設定に保存する
  on_rewind_window_order do |i_window|
    if :default == i_window.slug
      panes_order = {}
      i_window.children.each{ |i_pane|
        if i_pane.is_a? Plugin::GUI::Pane
          tab_order = []
          pane = widgetof(i_pane)
          if pane
            pane.n_pages.times{ |page_num|
              i_widget = find_implement_widget_by_gtkwidget(pane.get_tab_label(pane.get_nth_page(page_num)))
              if i_widget and not i_widget.temporary_tab? and i_widget.children.any?{ |child| not child.is_a? Plugin::GUI::TabToolbar }
                tab_order << i_widget.slug end } end
          panes_order[i_pane.slug] = tab_order if not tab_order.empty? end }
      ui_tab_order = (UserConfig[:ui_tab_order] || {}).melt
      ui_tab_order[i_window.slug] = panes_order
      UserConfig[:ui_tab_order] = ui_tab_order end end

  # ペインを順序リストから削除する
  # ==== Args
  # [i_pane] ペイン
  def pane_order_delete(i_pane)
    order = UserConfig[:ui_tab_order].melt
    i_window = i_pane.parent
    order[i_window.slug] = order[i_window.slug].melt
    order[i_window.slug].delete(i_pane.slug)
    UserConfig[:ui_tab_order] = order
  end

  # _cuscadable_ に対応するGtkオブジェクトを返す
  # ==== Args
  # [cuscadable] ウィンドウ、ペイン、タブ、タイムライン等
  # ==== Return
  # 対応するGtkオブジェクト
  def widgetof(cuscadable)
    type_strict cuscadable => :slug
    result = @slug_dictionary.get(cuscadable)
    if result and result.destroyed?
      nil
    else
      result end end

  # Gtkオブジェクト _widget_ に対応するウィジェットのオブジェクトを返す
  # ==== Args
  # [widget] Gtkウィジェット
  # ==== Return
  # _widget_ に対応するウィジェットオブジェクトまたは偽
  def find_implement_widget_by_gtkwidget(widget)
    @slug_dictionary.imaginally_by_gtk(widget) end
end

module Plugin::Gtk
  class GtkError < Exception
  end end