File: gtk_intelligent_textview.rb

package info (click to toggle)
mikutter 5.0.4%2Bdfsg1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 9,700 kB
  • sloc: ruby: 21,307; sh: 181; makefile: 19
file content (216 lines) | stat: -rw-r--r-- 7,523 bytes parent folder | download | duplicates (2)
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