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
|
# -*- coding:utf-8 -*-
=begin
= Gtk::PostBox
つぶやき入力ボックス。
=end
require 'gtk2'
require 'thread'
miquire :mui, 'miracle_painter'
miquire :mui, 'intelligent_textview'
module Gtk
class PostBox < Gtk::EventBox
attr_accessor :return_to_top
@@ringlock = Mutex.new
@@postboxes = []
# 既存のGtk::PostBoxのインスタンスを返す
def self.list
return @@postboxes
end
def initialize(postable = PostToPrimaryService.new, options = {})
type_strict postable => :post, options => Hash
mainthread_only
@posting = nil
@return_to_top = nil
@options = options
@postable = postable
super()
signal_connect('parent-set'){
if parent
sw = get_ancestor(Gtk::ScrolledWindow)
if(sw)
@return_to_top = sw.vadjustment.value == 0
else
@return_to_top = false end
post_it if @options[:delegated_by] end }
add(generate_box)
set_border_width(2)
regist end
def generate_box
@replies = []
result = Gtk::HBox.new(false, 0).closeup(widget_tool).pack_start(widget_post).closeup(widget_remain).closeup(widget_send)
if(reply?)
w_replies = Gtk::VBox.new.add(result)
in_reply_to_all.each{ |message|
w_reply = Gtk::HBox.new
itv = Gtk::IntelligentTextview.new(message.to_show, 'font' => :mumble_basic_font)
itv.get_background = lambda{ get_backgroundstyle(message) }
itv.bg_modifier
ev = Gtk::EventBox.new
ev.style = get_backgroundstyle(message)
w_replies.closeup(ev.add(w_reply.closeup(Gtk::WebIcon.new(message[:user][:profile_image_url], 32, 32).top).add(itv)))
@replies << itv
}
w_replies
else
result end end
def widget_post
return @post if defined?(@post)
@post = gen_widget_post
post_set_default_text(@post)
@post.wrap_mode = Gtk::TextTag::WRAP_CHAR
@post.border_width = 2
@post.buffer.ssc('changed') { |textview|
refresh_buttons(false)
false }
@post.signal_connect_after('focus_out_event', &method(:focus_out_event))
@post end
alias post widget_post
def widget_remain
return @remain if defined?(@remain)
@remain = Gtk::Label.new('---')
Delayer.new{
if not @remain.destroyed?
@remain.set_text(remain_charcount.to_s) end }
widget_post.buffer.ssc(:changed){ |textview, event|
@remain.set_text(remain_charcount.to_s) }
@remain end
def widget_send
return @send if defined?(@send)
@send = Gtk::Button.new.add(Gtk::WebIcon.new(Skin.get('post.png'), 16, 16))
@send.sensitive = postable?
@send.signal_connect('clicked'){|button|
post_it
false }
@send end
def widget_tool
return @tool if defined?(@tool)
@tool = Gtk::Button.new.add(Gtk::WebIcon.new(Skin.get('close.png'), 16, 16))
@tool.signal_connect_after('focus_out_event', &method(:focus_out_event))
@tool.ssc('event'){
@tool.sensitive = destructible? || posting?
false }
@tool.ssc('clicked'){
if posting?
@posting.cancel
@tool.sensitive = destructible? || posting?
cancel_post
else
destroy if destructible? end
false }
@tool end
# 各ボタンのクリック可否状態を更新する
def refresh_buttons(refresh_brothers = true)
if refresh_brothers and @options.has_key?(:postboxstorage)
@options[:postboxstorage].children.each{ |brother|
brother.refresh_buttons(false) }
else
widget_send.sensitive = postable?
widget_tool.sensitive = destructible? || posting? end end
# 現在メッセージの投稿中なら真を返す
def posting?
!!@posting end
# このPostBoxにフォーカスを合わせる
def active
get_ancestor(Gtk::Window).set_focus(widget_post) if(get_ancestor(Gtk::Window)) end
# 入力されている投稿する。投稿に成功したら、self.destroyを呼んで自分自身を削除する
def post_it
if postable?
return unless before_post
text = widget_post.buffer.text
text += UserConfig[:footer] if add_footer?
@posting = service.post(:message => text){ |event, msg|
case event
when :start
Delayer.new{ start_post }
when :fail
Delayer.new{ end_post }
when :success
Delayer.new{ destroy } end } end end
def destroy
@@ringlock.synchronize{
if not(destroyed?) and not(frozen?) and parent
parent.remove(self)
@@postboxes.delete(self)
super
on_delete
self.freeze end } end
private
def gen_widget_post
Gtk::TextView.new end
def postable?
not(widget_post.buffer.text.empty?) and (/[^\s]/ === widget_post.buffer.text) end
# 新しいPostBoxを作り、そちらにフォーカスを回す
def before_post
if(@options[:postboxstorage])
return false if delegate
if not @options[:delegated_by]
postbox = Gtk::PostBox.new(@postable, @options)
@options[:postboxstorage].
pack_start(postbox).
show_all.
get_ancestor(Gtk::Window).
set_focus(postbox.widget_post) end end
if @options[:before_post_hook]
@options[:before_post_hook].call(self) end
Plugin.call(:before_postbox_post, widget_post.buffer.text)
true end
def start_post
if not(frozen? or destroyed?)
# @posting = Thread.current
widget_post.editable = false
[widget_post, widget_send].compact.each{|widget| widget.sensitive = false }
widget_tool.sensitive = true
end end
def end_post
if not(frozen? or destroyed?)
@posting = nil
widget_post.editable = true
[widget_post, widget_send].compact.each{|widget| widget.sensitive = true } end end
# ユーザによって投稿が中止された場合に呼ばれる
def cancel_post
if not(frozen? or destroyed?)
if @options[:delegated_by]
@options[:delegated_by].widget_post.buffer.text = widget_post.buffer.text
destroy
else
end_post end end end
def delegate
if(@options[:postboxstorage] and @options[:delegate_other])
options = @options.clone
options[:delegate_other] = false
options[:delegated_by] = self
@options[:postboxstorage].pack_start(Gtk::PostBox.new(@postable, options)).show_all
true end end
def service
if UserConfig[:legacy_retweet_act_as_reply]
@postable
else
(retweet? ? Service.primary : @postable) end end
def post_is_empty?
widget_post.buffer.text.empty? or
(defined?(@postable[:user]) ? widget_post.buffer.text == "@#{@postable[:user][:idname]} " : false) end
def brothers
if(@options[:postboxstorage])
@options[:postboxstorage].children.find_all{|c| c.sensitive? }
else
[] end end
def lonely?
brothers.size <= 1 end
# フォーカスが外れたことによって削除して良いなら真を返す。
def destructible?
if(@options.has_key?(:postboxstorage))
return false if lonely? or (brothers - [self]).any?{ |w| w.posting? }
post_is_empty?
else
true end end
# _related_widgets_ のうちどれもアクティブではなく、フォーカスが外れたら削除される設定の場合、このウィジェットを削除する
def destroy_if_necessary(*related_widgets)
if(not(frozen?) and not([widget_post, *related_widgets].compact.any?{ |w| w.focus? }) and destructible?)
destroy
true end end
def on_delete
if(block_given?)
@on_delete = Proc.new
elsif defined? @on_delete
@on_delete.call end end
def reply?
@postable.is_a?(Retriever::Model) end
def retweet?
@options[:retweet] end
def regist
@@ringlock.synchronize{
@@postboxes << self } end
def add_footer?
if retweet?
not UserConfig[:footer_exclude_retweet]
elsif reply?
not UserConfig[:footer_exclude_reply]
else
true end end
def remain_charcount
if not widget_post.destroyed?
footer = if add_footer? then UserConfig[:footer].size else 0 end
140 - widget_post.buffer.text.size - footer end end
def focus_out_event(widget, event=nil)
options = @options
Delayer.new{
if(not(frozen?) and not(options.has_key?(:postboxstorage)) and post_is_empty?)
destroy_if_necessary(widget_send, widget_tool, *@replies) end }
false end
# Initialize Methods
def get_backgroundcolor(message)
if(message.from_me?)
UserConfig[:mumble_self_bg]
elsif(message.to_me?)
UserConfig[:mumble_reply_bg]
else
UserConfig[:mumble_basic_bg] end end
def get_backgroundstyle(message)
style = Gtk::Style.new()
color = get_backgroundcolor(message)
[Gtk::STATE_ACTIVE, Gtk::STATE_NORMAL, Gtk::STATE_SELECTED, Gtk::STATE_PRELIGHT, Gtk::STATE_INSENSITIVE].each{ |state|
style.set_bg(state, *color) }
style end
def post_set_default_text(post)
if @options[:delegated_by]
post.buffer.text = @options[:delegated_by].post.buffer.text
@options[:delegated_by].post.buffer.text = ''
elsif retweet?
post.buffer.text = " RT @" + @postable.idname + ": " + @postable.to_show
post.buffer.place_cursor(post.buffer.start_iter)
elsif reply?
post.buffer.text = reply_users + ' ' + post.buffer.text end
post.accepts_tab = false end
def reply_users
replies = [@postable.idname]
if(@options[:subreplies].is_a? Enumerable)
replies += @options[:subreplies].map{ |m| m.to_message.idname } end
if @options[:exclude_myself]
replies = replies.select{|x| x != @postable.service.idname }
end
replies.uniq.map{ |x| "@#{x}" }.join(' ')
end
# 全てのリプライ元を返す
def in_reply_to_all
result = Set.new
if reply?
result << @postable
if @options[:subreplies].is_a? Enumerable
result += @options[:subreplies] end end
result end
class PostToPrimaryService
def post(*args, &proc)
Service.primary.post(*args, &proc)
end
def service
self
end
end
end
end
|