Skip to content

Instantly share code, notes, and snippets.

@ssov
Last active December 15, 2015 21:38
Show Gist options
  • Save ssov/5326711 to your computer and use it in GitHub Desktop.
Save ssov/5326711 to your computer and use it in GitHub Desktop.
fuck 糞コテ
# -*- 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