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
|
# -*- coding: utf-8 -*-
require 'mui/gtk_extension'
require 'mui/gtk_contextmenu'
require 'plugin'
require 'miku/miku'
require 'gtk3'
require 'uri'
class Gtk::IntelligentTextview < Gtk::TextView
extend Gem::Deprecate
attr_accessor :fonts
attr_writer :style_generator
alias :get_background= :style_generator=
deprecate :get_background=, "style_generator=", 2017, 02
@@linkrule = MIKU::Cons.list([URI.regexp(['http','https']),
lambda{ |u, clicked| self.openurl(u) },
lambda{ |u, clicked|
Gtk::ContextMenu.new(['リンクのURLをコピー', ret_nth, lambda{ |opt, w| Gtk::Clipboard.copy(u) }],
['開く', ret_nth, lambda{ |opt, w| self.openurl(u) }]).
popup(clicked, true)}])
@@widgetrule = []
def self.addlinkrule(reg, leftclick, rightclick=nil)
@@linkrule = MIKU::Cons.new([reg, leftclick, rightclick].freeze, @@linkrule).freeze end
def self.addwidgetrule(reg, widget = nil, &block)
@@widgetrule = @@widgetrule.unshift([reg, (widget || block)]) end
# URLを開く
def self.openurl(url)
Plugin.call(:open, url)
false end
def initialize(msg = nil, default_fonts = {}, *rest, style: nil)
super(*rest)
@fonts = default_fonts
@style_generator = style
self.editable = false
self.cursor_visible = false
self.wrap_mode = :char
gen_body(msg) if msg
style_context.add_class('intelligentTextView')
Gtk::CssProvider.new.tap do |provider|
provider.load_from_data(<<~CSS)
.intelligentTextView, .intelligentTextView > text {
background-color: transparent;
}
CSS
style_context.add_provider(provider, Gtk::StyleProvider::PRIORITY_APPLICATION)
end
end
# このウィジェットの背景色を返す
# ==== Return
# Gtk::Style
def style_generator
if @style_generator.respond_to? :to_proc
@style_generator.to_proc.call
elsif @style_generator
@style_generator
end
end
alias :get_background :style_generator
deprecate :get_background, "style_generator", 2017, 02
# TODO プライベートにする
def set_cursor(textview, cursor)
textview.get_window(:text).set_cursor(Gdk::Cursor.new(cursor))
end
def bg_modifier(color = style_generator)
if color.is_a? Gtk::Style
warn 'Gtk::IntelligentTextview#bg_modifier(Gtk::Style) is deprecated.'
color = color.to_style_provider
end
if color.is_a? Gtk::StyleProvider
style_context.remove_provider(@style_provider) if @style_provider
style_context.add_provider(color, Gtk::StyleProvider::PRIORITY_APPLICATION)
@style_provider = color
elsif color && get_window(:text).respond_to?(:background=)
get_window(:text).background = color
end
queue_draw
end
# 新しいテキスト _msg_ に内容を差し替える。
# ==== Args
# [msg] 表示する文字列
# ==== Return
# self
def rewind(msg)
set_buffer(Gtk::TextBuffer.new)
gen_body(msg)
end
private
def fonts2tags(fonts)
tags = Hash.new
tags['font'] = UserConfig[fonts['font']] if fonts.has_key?('font')
if fonts.has_key?('foreground')
tags['foreground_gdk'] = Gdk::Color.new(*UserConfig[fonts['foreground']]) end
tags
end
def gen_body(msg, fonts={})
tag_shell = buffer.create_tag('shell', fonts2tags(fonts))
case msg
when String
type_strict fonts => Hash
tags = fonts2tags(fonts)
buffer.insert(buffer.start_iter, msg, { tags: %w[shell] })
apply_links
apply_inner_widget
when Enumerable # score
pos = buffer.end_iter
msg.each_with_index do |note, index|
case
when UserConfig[:miraclepainter_expand_custom_emoji] && note.respond_to?(:inline_photo)
photo = note.inline_photo
font_size = tag_shell.font_desc.forecast_font_size
start = pos.offset
pixbuf = photo.load_pixbuf(width: font_size, height: font_size) do |loaded_pixbuf|
unless buffer.destroyed?
insertion_start = buffer.get_iter_at(offset: start)
buffer.delete(insertion_start, buffer.get_iter_at(offset: start+1))
buffer.insert(insertion_start, loaded_pixbuf)
end
end
buffer.insert(pos, pixbuf)
when clickable?(note)
tagname = "tag#{index}"
create_tag_ifnecessary(tagname, buffer,
->(_tagname, _textview){
Plugin.call(:open, note)
}, nil)
start = pos.offset
buffer.insert(pos, note.description)
buffer.apply_tag(tagname, buffer.get_iter_at(offset: start), pos)
else
buffer.insert(pos, note.description, { tags: %w[shell] })
end
end
end
set_events(tag_shell)
self
end
def set_events(tag_shell)
self.signal_connect('realize'){ bg_modifier }
self.signal_connect('visibility-notify-event'){
if fonts['font'] and tag_shell.font != UserConfig[fonts['font']]
tag_shell.font = UserConfig[fonts['font']] end
if fonts['foreground'] and tag_shell.foreground_gdk.to_s != UserConfig[fonts['foreground']]
tag_shell.foreground_gdk = Gdk::Color.new(*UserConfig[fonts['foreground']]) end
false }
self.signal_connect('event'){
set_cursor(self, :xterm)
false }
end
def create_tag_ifnecessary(tagname, buffer, leftclick, rightclick)
tag = buffer.create_tag(tagname, "underline" => Pango::Underline::SINGLE)
tag.signal_connect('event'){ |this, textview, event, iter|
result = false
if(event.is_a?(Gdk::EventButton)) and
(event.event_type == Gdk::Event::BUTTON_RELEASE) and
not(textview.buffer.selection_bounds)
if (event.button == 1 and leftclick)
leftclick.call(tagname, textview)
elsif(event.button == 3 and rightclick)
rightclick.call(tagname, textview)
result = true end
elsif(event.is_a?(Gdk::EventMotion))
set_cursor(textview, :hand2)
end
result }
tag end
def apply_links
@@linkrule.each{ |param|
reg, left, right = param
buffer.text.scan(reg) {
match = Regexp.last_match
index = buffer.text[0, match.begin(0)].size
body = match.to_s.freeze
create_tag_ifnecessary(body, buffer, left, right) if not buffer.tag_table.lookup(body)
range = buffer.get_range(index, body.size)
buffer.apply_tag(body, *range)
} } end
def apply_inner_widget
offset = 0
@@widgetrule.each{ |param|
reg, widget_generator = param
buffer.text.scan(reg) { |match|
match = Regexp.last_match
index = [buffer.text.size, match.begin(0)].min
body = match.to_s.freeze
range = buffer.get_range(index, body.size + offset)
widget = widget_generator.call(body)
if widget
self.add_child_at_anchor(widget, buffer.create_child_anchor(range[1]))
offset += 1 end } } end
def clickable?(model)
has_model_intent = Plugin.collect(:intent_select_by_model_slug, model.class.slug, Pluggaloid::COLLECT).first
return true if has_model_intent
Plugin.collect(:model_of_uri, model.uri).any? do |model_slug|
Plugin.collect(:intent_select_by_model_slug, model_slug, Pluggaloid::COLLECT).first
end
end
end
|