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
|
# -*- coding: utf-8 -*-
# RubyGnome2を用いてUIを表示するプラグイン
require "gtk2"
require File.expand_path File.join(File.dirname(__FILE__), 'mikutter_window')
require File.expand_path File.join(File.dirname(__FILE__), 'tab_container')
Plugin.create :gtk do
@windows_by_slug = {} # slug => Gtk::MikutterWindow
@panes_by_slug = {} # slug => Gtk::NoteBook
@tabs_by_slug = {} # slug => Gtk::EventBox
@timelines_by_slug = {} # slug => Gtk::TimeLine
@profiles_by_slug = {} # slug => Gtk::NoteBook
@profiletabs_by_slug = {} # slug => Gtk::EventBox
@tabchildwidget_by_slug = {} # slug => Gtk::TabChildWidget
@postboxes_by_slug = {} # slug => Gtk::Postbox
@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|
notice "create window #{i_window.slug.inspect}"
window = Gtk::MikutterWindow.new
@windows_by_slug[i_window.slug] = 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.signal_connect("destroy"){
Delayer.freeze
window.destroy
Gtk::Object.main_quit
# Gtk.main_quit
false }
window.ssc(:focus_in_event) {
i_window.active!
false
}
window.ssc('key_press_event'){ |widget, event|
Plugin::GUI.keypress(Gtk::keyname([event.keyval ,event.state]), i_window) }
window.show_all
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_hook_id = UserConfig.connect(:tab_position){ |key, val, before_val, id|
notice "change tab pos to #{TABPOS[val]}"
pane.set_tab_pos(TABPOS[val]) unless pane.destroyed? }
pane.ssc(:page_reordered){ |this|
notice "on_pane_created: page_reordered: #{i_pane.inspect}"
window_order_save_request(i_pane.parent) if i_pane.parent
false }
pane.signal_connect(:page_added){ |this, tabcontainer|
type_strict tabcontainer => Gtk::TabContainer
notice "on_pane_created: page_added: #{i_pane.inspect}"
window_order_save_request(i_pane.parent) if i_pane.parent
i_tab = tabcontainer.i_tab
next false if i_tab.parent == i_pane
notice "on_pane_created: reparent"
i_pane << i_tab
false }
# 子が無くなった時 : このpaneを削除
pane.signal_connect(:page_removed){
notice "on_pane_created: page_removed: #{i_pane.inspect}"
if not(pane.destroyed?) and pane.children.empty? and pane.parent
pane.parent.remove(pane)
UserConfig.disconnect(tab_position_hook_id)
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_profile_created do |i_profile|
create_pane(i_profile) end
on_profiletab_created do |i_profiletab|
create_tab(i_profiletab) end
# タブを作成する
# ==== Args
# [i_tab] タブ
# ==== Return
# Tab(Gtk::EventBox)
def create_tab(i_tab)
notice "create tab #{i_tab.slug.inspect}"
tab = Gtk::EventBox.new.tooltip(i_tab.name)
if i_tab.is_a? Plugin::GUI::Tab
@tabs_by_slug[i_tab.slug] = tab
elsif i_tab.is_a? Plugin::GUI::ProfileTab
@profiletabs_by_slug[i_tab.slug] = tab end
tab_update_icon(i_tab)
tab.ssc(:focus_in_event) {
i_tab.active!
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, e|
if e.button == 3
Plugin::GUI::Command.menu_pop(i_tab) end
false }
tab.ssc(:destroy){
i_tab.destroy
false }
tab.show_all end
# タイムライン作成。
# Gtk::TimeLine
on_timeline_created do |i_timeline|
notice "create timeline #{i_timeline.slug.inspect}"
timeline = Gtk::TimeLine.new
@timelines_by_slug[i_timeline.slug] = timeline
focus_in_event = lambda { |this, event|
#notice "active set to #{i_timeline}"
i_timeline.active!
false }
destroy_event = lambda{ |this|
if not(timeline.tl.destroyed?) and this != timeline.tl
timeline.tl.ssc(:focus_in_event, &focus_in_event)
timeline.tl.ssc(:destroy, &destroy_event) end
false }
timeline.tl.ssc(:focus_in_event, &focus_in_event)
timeline.tl.ssc(:destroy, &destroy_event)
timeline.ssc('key_press_event'){ |widget, event|
Plugin::GUI.keypress(Gtk::keyname([event.keyval ,event.state]), i_timeline) }
timeline.ssc(:destroy){
i_timeline.destroy
false }
timeline.show_all
end
on_gui_pane_join_window do |i_pane, i_window|
puts "gui_pane_join_window #{i_pane.slug.inspect}, #{i_window.slug.inspect}"
window = widgetof(i_window)
pane = widgetof(i_pane)
if pane.parent
if pane.parent != window.panes
notice "pane parent already exists. removing"
pane.parent.remove(pane)
notice "packing"
window.panes.pack_end(pane, false).show_all
notice "done" end
else
notice "pane doesn't have a parent"
window.panes.pack_end(pane, false).show_all
end
end
on_gui_tab_join_pane do |i_tab, i_pane|
notice "gui_tab_join_pane(#{i_tab}, #{i_pane})"
i_widget = i_tab.children.first
notice "#{i_tab} children #{i_tab.children}"
next if not i_widget
widget = widgetof(i_widget)
notice "widget: #{widget}"
next if not widget
tab = widgetof(i_tab)
pane = widgetof(i_pane)
old_pane = widget.get_ancestor(Gtk::Notebook)
notice "pane: #{pane}, old_pane: #{old_pane}"
if tab and pane and old_pane and pane != old_pane
notice "#{widget} removes by #{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
notice "#{widget} pack to #{tab}"
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
window_order_save_request(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_profile_join_tab do |i_profile, i_tab|
widget = widgetof(i_profile)
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|
notice "create postbox #{i_postbox.slug.inspect}"
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 = @postboxes_by_slug[i_postbox.slug] = postbox_parent.add_postbox(i_postbox)
postbox.post.ssc(:focus_in_event) {
i_postbox.active!
false }
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_gui_contextmenu do |event, contextmenu|
widget = widgetof(event.widget)
if widget
Gtk::ContextMenu.new(*contextmenu).popup(widget, event) 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) end end 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))
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
@tabchildwidget_by_slug[i_container.slug] = 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|
notice "nativewidget: #{container} => #{i_tab}"
@tabchildwidget_by_slug[i_container.slug] = container
widget_join_tab(i_tab, container.show_all) end
on_gui_nativewidget_join_profiletab do |i_profiletab, i_container, container|
notice "nativewidget: #{container} => #{i_profiletab}"
@tabchildwidget_by_slug[i_container.slug] = container
widget_join_tab(i_profiletab, container.show_all) end
on_gui_window_rewindstatus do |i_window, text, expire|
statusbar = @windows_by_slug[:default].statusbar
cid = statusbar.get_context_id("system")
mid = statusbar.push(cid, text)
if expire != 0
Reserver.new(expire){
if not statusbar.destroyed?
statusbar.remove(cid, mid) end } end 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_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
# タブ _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
notice "widget_join_tab: #{widget} join #{i_tab}"
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])
index = i_pane.children.find_index{ |child| child.slug == i_tab.slug } || i_pane.children.size
pane.insert_page_menu(index, 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.remove(tab.child) if tab.child
if i_tab.icon.is_a?(String)
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 = at(:windows_geometry, {})
if 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)
notice "create pane #{i_pane.slug.inspect}"
pane = Gtk::Notebook.new
if i_pane.is_a? Plugin::GUI::Pane
@panes_by_slug[i_pane.slug] = pane
elsif i_pane.is_a? Plugin::GUI::Profile
@profiles_by_slug[i_pane.slug] = pane end
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
# ウィンドウ内のペイン、タブの現在の順序を設定に保存する
# ==== Args
# [i_window] ウィンドウ
def window_order_save_request(i_window)
notice "window_order_save_request: #{i_window.inspect}"
type_strict i_window => Plugin::GUI::Window
Delayer.new do
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)))
tab_order << i_widget.slug if i_widget } 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
collection = if cuscadable.is_a? Plugin::GUI::Window
@windows_by_slug
elsif cuscadable.is_a? Plugin::GUI::Pane
@panes_by_slug
elsif cuscadable.is_a? Plugin::GUI::Tab
@tabs_by_slug
elsif cuscadable.is_a? Plugin::GUI::Profile
@profiles_by_slug
elsif cuscadable.is_a? Plugin::GUI::ProfileTab
@profiletabs_by_slug
elsif cuscadable.is_a? Plugin::GUI::Timeline
@timelines_by_slug
elsif cuscadable.is_a? Plugin::GUI::TabChildWidget
@tabchildwidget_by_slug
elsif cuscadable.is_a? Plugin::GUI::Postbox
@postboxes_by_slug end
result = collection[cuscadable.slug]
if result and result.destroyed?
nil
else
result end end
# Gtkオブジェクト _widget_ に対応するウィジェットのオブジェクトを返す
# ==== Args
# [widget] Gtkウィジェット
# ==== Return
# _widget_ に対応するウィジェットオブジェクトまたは偽
def find_implement_widget_by_gtkwidget(widget)
type_strict widget => Gtk::Widget
[
[@windows_by_slug, Plugin::GUI::Window],
[@panes_by_slug, Plugin::GUI::Pane],
[@tabs_by_slug, Plugin::GUI::Tab],
[@panes_by_slug, Plugin::GUI::Profile],
[@tabs_by_slug, Plugin::GUI::ProfileTab],
[@timelines_by_slug, Plugin::GUI::Timeline],
[@tabchildwidget_by_slug, Plugin::GUI::TabChildWidget],
[@postboxes_by_slug, Plugin::GUI::Postbox] ].each{ |collection, klass|
slug, node = *collection.find{ |slug, node|
node == widget }
if slug
return klass.instance(slug) end }
false end
end
module Plugin::Gtk
class GtkError < Exception
end end
|