Last active
December 15, 2015 21:38
-
-
Save ssov/5326711 to your computer and use it in GitHub Desktop.
fuck 糞コテ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
Plugin.create(:fuck_kusokote) do | |
class Gdk::MiraclePainter < Gtk::Object | |
=begin | |
type_register | |
signal_new(:modified, GLib::Signal::RUN_FIRST, nil, nil) | |
signal_new(:expose_event, GLib::Signal::RUN_FIRST, nil, nil) | |
include Gdk::Coordinate | |
include Gdk::IconOverButton | |
include Gdk::TextSelector | |
include Gdk::SubPartsHelper | |
include Gdk::MarkupGenerator | |
include UiThreadOnly | |
EMPTY = Set.new.freeze | |
Event = Struct.new(:event, :message, :timeline, :miraclepainter) | |
WHITE = [65536, 65536, 65536].freeze | |
BLACK = [0, 0, 0].freeze | |
attr_reader :message, :p_message, :tree, :selected | |
alias :to_message :message | |
# @@miracle_painters = Hash.new | |
# _message_ を内部に持っているGdk::MiraclePainterの集合をSetで返す。 | |
# ログ数によってはかなり重い処理なので注意 | |
def self.findbymessage(message) | |
type_strict message => :to_message | |
message = message.to_message | |
result = Set.new | |
Gtk::TimeLine.timelines.each{ |tl| | |
found = tl.get_record_by_message(message) | |
result << found.miracle_painter if found } | |
result.freeze | |
end | |
# findbymessage のdeferred版。 | |
def self.findbymessage_d(message) | |
type_strict message => :to_message | |
message = message.to_message | |
result = Set.new | |
Gtk::TimeLine.timelines.deach{ |tl| | |
if not tl.destroyed? | |
found = tl.get_record_by_message(message) | |
result << found.miracle_painter if found end | |
}.next{ | |
result.freeze } | |
end | |
def self.mp_modifier | |
@mp_modifier ||= lambda { |miracle_painter| | |
if (not miracle_painter.destroyed?) and (not miracle_painter.tree.destroyed?) | |
miracle_painter.tree.model.each{ |model, path, iter| | |
if iter[0].to_i == miracle_painter.message[:id] | |
miracle_painter.tree.queue_draw | |
break end } end | |
false } end | |
def initialize(message, *coodinate) | |
type_strict message => :to_message | |
@p_message = message | |
@message = message.to_message | |
@selected = false | |
type_strict @message => Message | |
super() | |
coordinator(*coodinate) | |
ssc(:modified, &Gdk::MiraclePainter.mp_modifier) | |
end | |
signal_new(:click, GLib::Signal::RUN_FIRST, nil, nil, | |
Gdk::EventButton, Integer, Integer) | |
signal_new(:motion_notify_event, GLib::Signal::RUN_FIRST, nil, nil, | |
Integer, Integer) | |
signal_new(:leave_notify_event, GLib::Signal::RUN_FIRST, nil, nil) | |
def signal_do_click(event, cell_x, cell_y) | |
end | |
def signal_do_motion_notify_event(cell_x, cell_y) | |
end | |
def signal_do_leave_notify_event() | |
end | |
# Gtk::TimeLine::InnerTLのインスタンスを設定する。今後、このインスタンスは _new_ に所属するものとして振舞う | |
def set_tree(new) | |
type_strict new => Gtk::TimeLine::InnerTL | |
@tree = new | |
self end | |
# TLに表示するための Gdk::Pixmap のインスタンスを返す | |
def pixmap | |
@pixmap ||= gen_pixmap | |
end | |
# TLに表示するための Gdk::Pixbuf のインスタンスを返す | |
def pixbuf | |
@pixbuf ||= gen_pixbuf | |
end | |
# MiraclePainterの座標x, y上でポインティングデバイスのボタン1が押されたことを通知する | |
def pressed(x, y) | |
textselector_press(*main_pos_to_index_forclick(x, y)[1..2]) | |
end | |
# MiraclePainterの座標x, y上でポインティングデバイスのボタン1が離されたことを通知する | |
def released(x=nil, y=nil) | |
if(x == y and not x) | |
unselect | |
else | |
textselector_release(*main_pos_to_index_forclick(x, y)[1..2]) end end | |
# 座標 ( _x_ , _y_ ) にクリックイベントを発生させる | |
def clicked(x, y, e) | |
signal_emit(:click, e, x, y) | |
case e.button | |
when 1 | |
iob_clicked | |
if not textselector_range | |
index = main_pos_to_index(x, y) | |
if index | |
l = message.links.segment_by_index(index) | |
l[:callback].call(l) if l and l[:callback] end end | |
when 3 | |
@tree.get_ancestor(Gtk::Window).set_focus(@tree) | |
Plugin::GUI::Command.menu_pop | |
end end | |
def on_selected | |
if not frozen? | |
@selected = true | |
on_modify end end | |
def on_unselected | |
if not frozen? | |
@selected = false | |
on_modify end end | |
# 座標 ( _x_ , _y_ ) にマウスオーバーイベントを発生させる | |
def point_moved(x, y) | |
point_moved_main_icon(x, y) | |
signal_emit(:motion_notify_event, x, y) | |
textselector_select(*main_pos_to_index_forclick(x, y)[1..2]) end | |
# leaveイベントを発生させる | |
def point_leaved(x, y) | |
iob_main_leave | |
signal_emit(:leave_notify_event) | |
# textselector_release | |
end | |
# MiraclePainterが選択解除されたことを通知する | |
def unselect | |
textselector_unselect end | |
def iob_icon_pixbuf | |
[ ["reply.png", "etc.png"], | |
["retweet.png", | |
message.favorite? ? "unfav.png" : "fav.png"] ] end | |
def iob_icon_pixbuf_off | |
[ [(UserConfig[:show_replied_icon] and message.mentioned_by_me? and "reply.png"), | |
nil], | |
[message.retweeted? ? "retweet.png" : nil, | |
message.favorite? ? "unfav.png" : nil] | |
] | |
end | |
def iob_reply_clicked | |
@tree.imaginary.create_reply_postbox(message) end | |
def iob_retweet_clicked | |
if message.retweeted? | |
retweet = message.retweeted_statuses.find(&:from_me?) | |
retweet.destroy if retweet | |
else | |
message.retweet | |
end | |
# @tree.imaginary.create_reply_postbox(message, :retweet => true) | |
end | |
def iob_fav_clicked | |
message.favorite(!message.favorite?) | |
end | |
def iob_etc_clicked | |
end | |
# つぶやきの左上座標から、クリックされた文字のインデックスを返す | |
def main_pos_to_index(x, y) | |
x -= pos.main_text.x | |
y -= pos.main_text.y | |
inside, byte, trailing = *main_message.xy_to_index(x * Pango::SCALE, y * Pango::SCALE) | |
message.to_s.get_index_from_byte(byte) if inside end | |
def main_pos_to_index_forclick(x, y) | |
x -= pos.main_text.x | |
y -= pos.main_text.y | |
result = main_message.xy_to_index(x * Pango::SCALE, y * Pango::SCALE) | |
result[1] = message.to_s.get_index_from_byte(result[1]) | |
return *result end | |
def signal_do_modified() | |
end | |
def signal_do_expose_event() | |
end | |
# 更新イベントを発生させる | |
def on_modify(event=true) | |
if not destroyed? | |
@pixmap = nil | |
@pixbuf = nil | |
@coordinate = nil | |
if(defined? @last_modify_height and @last_modify_height != height) | |
tree.get_column(0).queue_resize | |
@last_modify_height = height end | |
signal_emit('modified') if event | |
end | |
end | |
# 画面上にこれが表示されているかを返す | |
def visible? | |
if tree | |
start, last = tree.visible_range | |
if start | |
range = tree.selected_range_bytime | |
if(tree.vadjustment.value == 0) | |
range.first <= message.modified.to_i | |
else | |
range.include?(message.modified.to_i) end end | |
else | |
true end end | |
def destroy | |
def self.tree | |
raise DestroyedError.new end | |
def self.to_message | |
raise DestroyedError.new end | |
def self.p_message | |
raise DestroyedError.new end | |
instance_variables.each{ |v| | |
instance_variable_set(v, nil) } | |
@tree = nil | |
signal_emit('destroy') | |
super | |
freeze | |
end | |
private | |
def dummy_context | |
Gdk::Pixmap.new(nil, 1, 1, color).create_cairo_context end | |
=end | |
# 本文のための Pango::Layout のインスタンスを返す | |
def main_message(context = dummy_context) | |
begin | |
attr_list, text = Pango.parse_markup(textselector_markup(styled_main_text)) | |
rescue GLib::Error => e | |
attr_list, text = nil, Pango.escape(message.to_show) | |
end | |
layout = context.create_pango_layout | |
layout.width = pos.main_text.width * Pango::SCALE | |
layout.attributes = attr_list if attr_list | |
layout.wrap = Pango::WRAP_CHAR | |
color = Plugin.filtering(:message_font_color, message, nil).last | |
color = BLACK if not(color and color.is_a? Array and 3 == color.size) | |
font = Plugin.filtering(:message_font, message, nil).last | |
context.set_source_rgb(*color.map{ |c| c.to_f / 65536 }) | |
layout.font_description = Pango::FontDescription.new(font) if font | |
layout.text = text.gsub(/@\w+/, "@anonymous") | |
layout end | |
=begin | |
# ヘッダ(左)のための Pango::Layout のインスタンスを返す | |
def header_left(context = dummy_context) | |
attr_list, text = header_left_markup | |
color = Plugin.filtering(:message_header_left_font_color, message, nil).last | |
color = BLACK if not(color and color.is_a? Array and 3 == color.size) | |
font = Plugin.filtering(:message_header_left_font, message, nil).last | |
layout = context.create_pango_layout | |
layout.attributes = attr_list | |
context.set_source_rgb(*color.map{ |c| c.to_f / 65536 }) | |
layout.font_description = Pango::FontDescription.new(font) if font | |
layout.text = text | |
layout end | |
=end | |
def header_left_markup | |
Pango.parse_markup("名前:<span color='blue'><b><u>以下、名無しにかわりましてVIPがお送りします</u></b></span>:#{timestamp_label} ID:#{Digest::SHA1.hexdigest(message[:user][:idname])[0,9]}") | |
end | |
# ヘッダ(右)のための Pango::Layout のインスタンスを返す | |
def header_right(context = dummy_context) | |
hms = timestamp_label | |
attr_list, text = Pango.parse_markup(hms) | |
layout = context.create_pango_layout | |
layout.attributes = attr_list | |
font = Plugin.filtering(:message_header_right_font, message, nil).last | |
layout.font_description = Pango::FontDescription.new(font) if font | |
layout.text = "" | |
layout.alignment = Pango::ALIGN_RIGHT | |
layout end | |
=begin | |
# pixmapを組み立てる | |
def gen_pixmap | |
pm = Gdk::Pixmap.new(nil, width, height, color) | |
render_to_context pm.create_cairo_context | |
pm | |
end | |
# pixbufを組み立てる | |
def gen_pixbuf | |
@pixmap = gen_pixmap | |
Gdk::Pixbuf.from_drawable(nil, @pixmap, 0, 0, width, height) | |
end | |
# アイコンのpixbufを返す | |
def main_icon | |
@main_icon ||= Gdk::WebImageLoader.pixbuf(message[:user][:profile_image_url], icon_width, icon_height){ |pixbuf| | |
if not destroyed? | |
@main_icon = pixbuf | |
on_modify end } end | |
# 背景色を返す | |
def get_backgroundcolor | |
color = Plugin.filtering(:message_background_color, self, nil).last | |
if color.is_a? Array and 3 == color.size | |
color.map{ |c| c.to_f / 65536 } | |
else | |
WHITE end end | |
=end | |
# Graphic Context にパーツを描画 | |
def render_to_context(context) | |
render_background context | |
render_main_text context | |
render_parts context | |
end | |
=begin | |
def render_background(context) | |
context.save{ | |
context.set_source_rgb(*get_backgroundcolor) | |
context.rectangle(0,0,width,height) | |
context.fill | |
} | |
end | |
def render_main_icon(context) | |
context.save{ | |
context.translate(pos.main_icon.x, pos.main_icon.x) | |
context.set_source_pixbuf(main_icon) | |
context.paint | |
} | |
render_icon_over_button(context) | |
end | |
=end | |
def render_main_text(context) | |
context.save{ | |
context.translate(0, pos.header_text.y) | |
context.set_source_rgb(0,0,0) | |
hl_layout = header_left(context) | |
context.show_pango_layout(hl_layout) | |
hr_layout = header_right(context) | |
hr_color = Plugin.filtering(:message_header_right_font_color, message, nil).last | |
hr_color = BLACK if not(hr_color and hr_color.is_a? Array and 3 == hr_color.size) | |
hl_rectangle = Gdk::Rectangle.new(pos.header_text.x, pos.header_text.y, | |
hl_layout.size[0] / Pango::SCALE, hl_layout.size[1] / Pango::SCALE) | |
hr_rectangle = Gdk::Rectangle.new(pos.header_text.x + pos.header_text.w - (hr_layout.size[0] / Pango::SCALE), pos.header_text.y, | |
hr_layout.size[0] / Pango::SCALE, hr_layout.size[1] / Pango::SCALE) | |
@hl_region = Gdk::Region.new(hl_rectangle) | |
@hr_region = Gdk::Region.new(hr_rectangle) | |
context.save{ | |
context.translate(pos.header_text.w - (hr_layout.size[0] / Pango::SCALE), 0) | |
context.set_source_rgb(*hr_color.map{ |c| c.to_f / 65536 }) | |
context.show_pango_layout(hr_layout) } } | |
context.save{ | |
context.translate(20, pos.main_text.y) | |
context.show_pango_layout(main_message(context)) } | |
end | |
=begin | |
Delayer.new{ | |
Plugin.create(:core).add_event(:posted){ |service, messages| | |
messages.each{ |message| | |
if(replyto_source = message.replyto_source) | |
findbymessage(replyto_source).each{ |mp| | |
mp.on_modify } end } } | |
Plugin.create(:core).add_event(:favorite){ |service, user, message| | |
if(user.is_me?) | |
findbymessage(message).each{ |mp| | |
mp.on_modify } end } | |
} | |
class DestroyedError < Exception | |
end | |
=end | |
end | |
# 日付表示 | |
class ::Gdk::MiraclePainter | |
def timestamp_label | |
Pango.escape(message[:created].strftime("%Y/%m/%d(#{%w(日 月 火 水 木 金 土)[message[:created].wday]}) %H:%M:%S")) | |
end | |
end | |
# 自分以外のfav, RTのアイコン表示なし | |
class ::Gdk::SubPartsVoter < Gdk::SubParts | |
def put_voter(context) | |
context.translate(@icon_ofst, 0) | |
xpos = @icon_ofst | |
@avatar_rect = [] | |
votes.each{ |user| | |
if user.is_me? | |
left = xpos | |
xpos += render_user(context, user) | |
@avatar_rect << (left...xpos) | |
break if width <= xpos | |
end | |
} | |
end | |
end | |
# リプライのアイコン表示なし、@anonymous置換 | |
class Gdk::ReplyViewer < Gdk::SubParts | |
def render(context) | |
if helper.visible? and message | |
context.save{ | |
context.translate(20, 0) | |
context.set_source_rgb(*(UserConfig[:mumble_reply_color] || [0,0,0]).map{ |c| c.to_f / 65536 }) | |
context.show_pango_layout(main_message(context)) } | |
end | |
end | |
def main_message(context = dummy_context) | |
attr_list, text = Pango.parse_markup(escaped_main_text) | |
layout = context.create_pango_layout | |
layout.width = (width - @icon_width - @margin*3) * Pango::SCALE | |
layout.attributes = attr_list | |
layout.wrap = Pango::WRAP_CHAR | |
layout.font_description = Pango::FontDescription.new(UserConfig[:mumble_reply_font]) | |
layout.text = text.gsub(/@\w+/, "@anonymous") | |
layout | |
end | |
end | |
Plugin.create :profile do | |
=begin | |
# 予定 | |
on_show_profile do |service, user| | |
container = profile_head(user) | |
i_profile = tab nil, "ID:#{Digest::SHA1.hexdigest(user[:idname])[0,9]} のプロフィール" do | |
set_icon MUI::Skin.get('icon.png') | |
set_deletable true | |
shrink | |
nativewidget container | |
expand | |
profile nil end | |
Plugin.call(:profiletab, i_profile, user) | |
Plugin.call(:filter_stream_reconnect_request) | |
end | |
=end | |
command(:aboutuser, | |
name: lambda { |opt| | |
if defined? opt.messages.first and opt.messages.first.repliable? | |
u = opt.messages.first.user | |
"ID:#{Digest::SHA1.hexdigest(u[:idname])[0,9]}について".gsub(/_/, '__') | |
else | |
"ユーザについて" end }, | |
condition: Plugin::Command::CanReplyAll, | |
icon: lambda{ |opt| MUI::Skin.get('icon.png') }, | |
visible: true, | |
role: :timeline) do |opt| | |
Plugin.call(:show_profile, Service.primary, opt.messages.first.user) | |
end | |
def relation_bar(user) | |
icon_size = Gdk::Rectangle.new(0, 0, 32, 32) | |
arrow_size = Gdk::Rectangle.new(0, 0, 16, 16) | |
container = ::Gtk::VBox.new(false, 4) | |
Service.all.each{ |me| | |
following = followed = nil | |
w_following_label = ::Gtk::Label.new("関係を取得中") | |
w_followed_label = ::Gtk::Label.new("") | |
w_eventbox_image_following = ::Gtk::EventBox.new | |
w_eventbox_image_followed = ::Gtk::EventBox.new | |
relation = if me.user_obj == user | |
::Gtk::Label.new("それはあなたです!") | |
else | |
::Gtk::HBox.new. | |
closeup(w_eventbox_image_following). | |
closeup(w_following_label) end | |
relation_container = ::Gtk::HBox.new(false, icon_size.width/2) | |
relation_container.closeup(::Gtk::WebIcon.new(me.user_obj[:profile_image_url], icon_size).tooltip("#{me.user}(#{me.user_obj[:name]})")) | |
relation_container.closeup(::Gtk::VBox.new. | |
closeup(relation). | |
closeup(::Gtk::HBox.new. | |
closeup(w_eventbox_image_followed). | |
closeup(w_followed_label))) | |
relation_container.closeup(::Gtk::WebIcon.new(MUI::Skin.get('icon.png'), icon_size).tooltip("#{Digest::SHA1.hexdigest(user.idname)}")) | |
if me.user_obj != user | |
followbutton = ::Gtk::Button.new | |
followbutton.sensitive = false | |
# フォローしている状態の更新 | |
m_following_refresh = lambda { |new| | |
if not w_eventbox_image_following.destroyed? | |
following = new | |
if not w_eventbox_image_following.children.empty? | |
w_eventbox_image_following.remove(w_eventbox_image_following.children.first) end | |
w_eventbox_image_following.style = w_eventbox_image_following.parent.style | |
w_eventbox_image_following.add(::Gtk::WebIcon.new(MUI::Skin.get(new ? "arrow_following.png" : "arrow_notfollowing.png"), arrow_size).show_all) | |
w_following_label.text = new ? "フョローしている" : "フョローしていない" | |
followbutton.label = new ? "解除" : "フョロー" end } | |
# フォローされている状態の更新 | |
m_followed_refresh = lambda { |new| | |
if not w_eventbox_image_followed.destroyed? | |
followed = new | |
if not w_eventbox_image_followed.children.empty? | |
w_eventbox_image_followed.remove(w_eventbox_image_followed.children.first) end | |
w_eventbox_image_followed.style = w_eventbox_image_followed.parent.style | |
w_eventbox_image_followed.add(::Gtk::WebIcon.new(MUI::Skin.get(new ? "arrow_followed.png" : "arrow_notfollowed.png"), arrow_size).show_all) | |
w_followed_label.text = new ? "フョローされている" : "フョローされていない" end } | |
Service.primary.friendship(target_id: user[:id], source_id: me.user_obj[:id]).next{ |rel| | |
if rel and not(w_eventbox_image_following.destroyed?) | |
m_following_refresh.call(rel[:following]) | |
m_followed_refresh.call(rel[:followed_by]) | |
handler_followings_created = on_followings_created do |service, dst_users| | |
if service == me and dst_users.include?(user) | |
m_following_refresh.call(true) end end | |
handler_followings_destroy = on_followings_destroy do |service, dst_users| | |
if service == me and dst_users.include?(user) | |
m_following_refresh.call(false) end end | |
followbutton.ssc(:clicked){ | |
followbutton.sensitive = false | |
event = following ? :followings_destroy : :followings_created | |
me.__send__(following ? :unfollow : :follow, user).next{ |msg| | |
Plugin.call(event, me, [user]) | |
followbutton.sensitive = true unless followbutton.destroyed? }. | |
terminate.trap{ | |
followbutton.sensitive = true unless followbutton.destroyed? } | |
true } | |
followbutton.signal_connect(:destroy){ | |
detach(:followings_created, handler_followings_created) | |
detach(:followings_destroy, handler_followings_destroy) | |
false } | |
followbutton.sensitive = true end | |
}.terminate.trap{ | |
w_following_label.text = "取得できませんでした" } end | |
container.closeup(relation_container.closeup(followbutton)) } | |
container end | |
def profile_head(user) | |
eventbox = ::Gtk::EventBox.new | |
eventbox.ssc('visibility-notify-event'){ | |
eventbox.style = background_color | |
false } | |
eventbox.add(::Gtk::VBox.new(false, 0). | |
add(::Gtk::HBox.new(false, 16). | |
closeup(::Gtk::WebIcon.new(MUI::Skin.get('icon.png'), 128, 128).top). | |
closeup(::Gtk::VBox.new.closeup(user_name(user)).closeup(profile_table(user))))) | |
scrolledwindow = ::Gtk::ScrolledWindow.new | |
scrolledwindow.height_request = 128 + 24 | |
scrolledwindow.set_policy(::Gtk::POLICY_AUTOMATIC, ::Gtk::POLICY_NEVER) | |
scrolledwindow.add_with_viewport(eventbox) | |
end | |
def user_name(user) | |
w_screen_name = ::Gtk::Label.new.set_markup("<b><u><span foreground=\"#0000ff\">以下、名無しにかわりましてVIPがお送りします</span></u></b>") | |
w_ev = ::Gtk::EventBox.new | |
w_ev.modify_bg(::Gtk::STATE_NORMAL, Gdk::Color.new(0xffff, 0xffff, 0xffff)) | |
w_ev.ssc(:realize) { | |
w_ev.window.set_cursor(Gdk::Cursor.new(Gdk::Cursor::HAND2)) | |
false } | |
w_ev.ssc(:button_press_event) { |this, e| | |
if e.button == 1 | |
::Gtk.openurl("http://twitter.com/#{user[:idname]}") | |
true end } | |
::Gtk::HBox.new(false, 16).closeup(w_ev.add(w_screen_name)).closeup(::Gtk::Label.new("@anonymous")) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment