プロジェクト

全般

プロフィール

0001-SubPartsQuote.patch

Satoshi Okuno, 2016-01-03 14:31

差分を表示:

core/mui/cairo_sub_parts_message_base.rb
1
# -*- coding: utf-8 -*-
2

  
3
require 'gtk2'
4
require 'cairo'
5

  
6
# ナウい引用っぽくメッセージを作るサブパーツのベースクラス
7
# 継承して使おう
8
class Gdk::SubPartsMessageBase < Gdk::SubParts
9
  attr_reader :icon_width, :icon_height
10

  
11
  # サブクラスでMessagesを返すように実装すること
12
  # このメソッドはサブパーツの描画中に何回も呼ばれるので、キャッシュなどで高速化に努めてください
13
  def messages
14
    nil end
15

  
16
  # サブクラスで領域をクリックした時の処理を実装すること
17
  def on_click(this, e, x, y, message)
18
  end
19

  
20
  def initialize(*args)
21
    super
22
    @icon_width, @icon_height, @margin, @edge = 32, 32, 2, 8 end
23

  
24
  def render_messages
25
    if not helper.destroyed?
26
      helper.on_modify
27
      helper.reset_height
28
      helper.ssc(:click) { |this, e, x, y|
29
        ofsty = helper.mainpart_height
30
        helper.subparts.each { |part|
31
          break if part == self
32
          ofsty += part.height }
33
        if ofsty <= y and (ofsty + height) >= y
34
          my = 0
35
          messages.each { |m|
36
            my += message_height(m)
37
            if y <= ofsty + my
38
              on_click(e, m)
39
              break end } end } end end
40

  
41
  def render(context)
42
    if messages and not messages.empty?
43
      messages.inject(0) { |base_y, message|
44
        render_single_message(message, context, base_y) } end end
45

  
46
  def height
47
    if not helper.destroyed? and messages and not messages.empty?
48
      messages.inject(0) { |s, m| s + message_height(m) }
49
    else
50
      0 end end
51

  
52
  private
53

  
54
  def render_single_message(message, context, base_y)
55
    render_outline(message, context, base_y)
56
    render_header(message, context, base_y)
57
    context.save {
58
      context.translate(@margin + @edge, @margin + @edge + base_y)
59
      context.set_source_pixbuf(main_icon(message))
60
      context.paint
61
      context.translate(icon_width + @margin*2, header_left(message).size[1] / Pango::SCALE)
62
      context.set_source_rgb(*([0,0,0]).map{ |c| c.to_f / 65536 })
63
      context.show_pango_layout(main_message(message, context)) }
64

  
65
    base_y + message_height(message) end
66

  
67
  def message_height(message)
68
    [icon_height, (header_left(message).size[1] + main_message(message).size[1]) / Pango::SCALE].max + (@margin + @edge) * 2
69
  end
70

  
71
  # ヘッダ(左)のための Pango::Layout のインスタンスを返す
72
  def header_left(message, context = dummy_context)
73
    attr_list, text = Pango.parse_markup("<b>#{Pango.escape(message[:user][:idname])}</b> #{Pango.escape(message[:user][:name] || '')}")
74
    layout = context.create_pango_layout
75
    layout.attributes = attr_list
76
    layout.font_description = Pango::FontDescription.new(UserConfig[:mumble_basic_font])
77
    layout.text = text
78
    layout end
79

  
80
  # ヘッダ(右)のための Pango::Layout のインスタンスを返す
81
  def header_right(message, context = dummy_context)
82
    now = Time.now
83
    hms = if message[:created].year == now.year && message[:created].month == now.month && message[:created].day == now.day
84
            message[:created].strftime('%H:%M:%S'.freeze)
85
          else
86
            message[:created].strftime('%Y/%m/%d %H:%M:%S'.freeze)
87
          end
88
    attr_list, text = Pango.parse_markup("<span foreground=\"#999999\">#{Pango.escape(hms)}</span>".freeze)
89
    layout = context.create_pango_layout
90
    layout.attributes = attr_list
91
    layout.font_description = Pango::FontDescription.new(UserConfig[:mumble_basic_font])
92
    layout.text = text
93
    layout.alignment = Pango::ALIGN_RIGHT
94
    layout end
95

  
96
  def render_header(message, context, base_y)
97
    header_w = width - @icon_width - @margin*3 - @edge*2
98
    context.save{
99
      context.translate(@icon_width + @margin*2 + @edge, @margin + @edge + base_y)
100
      context.set_source_rgb(0,0,0)
101
      hl_layout, hr_layout = header_left(message, context), header_right(message, context)
102
      context.show_pango_layout(hl_layout)
103
      context.save{
104
        context.translate(header_w - hr_layout.size[0] / Pango::SCALE, 0)
105
        if (hl_layout.size[0] / Pango::SCALE) > header_w - hr_layout.size[0] / Pango::SCALE - 20
106
          r, g, b = get_backgroundcolor
107
          grad = Cairo::LinearPattern.new(-20, base_y, hr_layout.size[0] / Pango::SCALE + 20, base_y)
108
          grad.add_color_stop_rgba(0.0, r, g, b, 0.0)
109
          grad.add_color_stop_rgba(20.0 / (hr_layout.size[0] / Pango::SCALE + 20), r, g, b, 1.0)
110
          grad.add_color_stop_rgba(1.0, r, g, b, 1.0)
111
          context.rectangle(-20, base_y, hr_layout.size[0] / Pango::SCALE + 20, hr_layout.size[1] / Pango::SCALE + base_y)
112
          context.set_source(grad)
113
          context.fill() end
114
        context.show_pango_layout(hr_layout) } }
115
  end
116

  
117
  def main_message(message, context = dummy_context)
118
    attr_list, text = Pango.parse_markup(Pango.escape(message.to_show))
119
    layout = context.create_pango_layout
120
    layout.width = (width - @icon_width - @margin*3 - @edge*2) * Pango::SCALE
121
    layout.attributes = attr_list
122
    layout.wrap = Pango::WRAP_CHAR
123
    layout.font_description = Pango::FontDescription.new(UserConfig[:mumble_reply_font])
124
    layout.text = text
125
    layout end
126

  
127
  def render_outline(message, context, base_y)
128
    mh = message_height(message)
129
    context.save {
130
      context.pseudo_blur(4) {
131
        context.fill {
132
          context.set_source_rgb(*([32767, 32767, 32767]).map{ |c| c.to_f / 65536 })
133
          context.rounded_rectangle(@edge, @edge + base_y, width - @edge*2, mh - @edge*2, 4)
134
        }
135
      }
136
      context.fill {
137
        context.set_source_rgb(*([65535, 65535, 65535]).map{ |c| c.to_f / 65536 })
138
        context.rounded_rectangle(@edge, @edge + base_y, width - @edge*2, mh - @edge*2, 4)
139
      }
140
    }
141
  end
142

  
143
  def main_icon(message)
144
    Gdk::WebImageLoader.pixbuf(message[:user][:profile_image_url], icon_width, icon_height){ |pixbuf|
145
      helper.on_modify } end
146

  
147
  def get_backgroundcolor
148
    [1.0, 1.0, 1.0]
149
  end
150
end
core/mui/cairo_sub_parts_quote.rb
1 1
# -*- coding: utf-8 -*-
2 2

  
3
miquire :mui, 'sub_parts_voter'
3
miquire :mui, 'sub_parts_message_base'
4 4

  
5 5
require 'gtk2'
6 6
require 'cairo'
7 7

  
8
class Gdk::SubPartsQuote < Gdk::SubParts
8
class Gdk::SubPartsQuote < Gdk::SubPartsMessageBase
9 9
  register
10 10

  
11
  attr_reader :icon_width, :icon_height
11
  def messages
12
    @messages end
13

  
14
  def on_click(e, message)
15
    case e.button
16
    when 1
17
      Plugin.filtering(:command, {}).first[:smartthread][:exec].call(Struct.new(:messages).new([message]))
18
      end end
12 19

  
13 20
  def initialize(*args)
14 21
    super
15
    @icon_width, @icon_height, @margin, @edge = 32, 32, 2, 8
16 22
    if helper.message.quoting?
17 23
      Thread.new(helper.message) { |m|
18 24
        m.quoting_messages(true)
19 25
      }.next{ |quoting|
20 26
        @messages = Messages.new(quoting).freeze
21 27
        render_messages
22
      }.terminate('コメント付きリツイート描画中にエラーが発生しました') end end
23

  
24
  def render_messages
25
    if not helper.destroyed?
26
      helper.on_modify
27
      helper.reset_height
28
      helper.ssc(:click) { |this, e, x, y|
29
        ofsty = helper.mainpart_height
30
        helper.subparts.each { |part|
31
          break if part == self
32
          ofsty += part.height }
33
        if ofsty <= y and (ofsty + height) >= y
34
          case e.button
35
          when 1
36
            my = 0
37
            @messages.each { |m|
38
              my += message_height(m)
39
              if y <= ofsty + my
40
                Plugin.filtering(:command, {}).first[:smartthread][:exec].call(Struct.new(:messages).new([m]))
41
                break end } end end } end end
42

  
43
  def render(context)
44
    if @messages and not @messages.empty?
45
      @messages.inject(0) { |base_y, message|
46
        render_single_message(message, context, base_y) } end end
47

  
48
  def height
49
    if not helper.destroyed? and @messages and not @messages.empty?
50
      @messages.inject(0) { |s, m| s + message_height(m) }
51
    else
52
      0 end end
53

  
54
  private
55

  
56
  def render_single_message(message, context, base_y)
57
    render_outline(message, context, base_y)
58
    render_header(message, context, base_y)
59
    context.save {
60
      context.translate(@margin + @edge, @margin + @edge + base_y)
61
      context.set_source_pixbuf(main_icon(message))
62
      context.paint
63
      context.translate(icon_width + @margin*2, header_left(message).size[1] / Pango::SCALE)
64
      context.set_source_rgb(*([0,0,0]).map{ |c| c.to_f / 65536 })
65
      context.show_pango_layout(main_message(message, context)) }
66

  
67
    base_y + message_height(message) end
68

  
69
  def message_height(message)
70
    [icon_height, (header_left(message).size[1] + main_message(message).size[1]) / Pango::SCALE].max + (@margin + @edge) * 2
71
  end
72

  
73
  # ヘッダ(左)のための Pango::Layout のインスタンスを返す
74
  def header_left(message, context = dummy_context)
75
    attr_list, text = Pango.parse_markup("<b>#{Pango.escape(message[:user][:idname])}</b> #{Pango.escape(message[:user][:name] || '')}")
76
    layout = context.create_pango_layout
77
    layout.attributes = attr_list
78
    layout.font_description = Pango::FontDescription.new(UserConfig[:mumble_basic_font])
79
    layout.text = text
80
    layout end
81

  
82
  # ヘッダ(右)のための Pango::Layout のインスタンスを返す
83
  def header_right(message, context = dummy_context)
84
    now = Time.now
85
    hms = if message[:created].year == now.year && message[:created].month == now.month && message[:created].day == now.day
86
            message[:created].strftime('%H:%M:%S'.freeze)
87
          else
88
            message[:created].strftime('%Y/%m/%d %H:%M:%S'.freeze)
89
          end
90
    attr_list, text = Pango.parse_markup("<span foreground=\"#999999\">#{Pango.escape(hms)}</span>".freeze)
91
    layout = context.create_pango_layout
92
    layout.attributes = attr_list
93
    layout.font_description = Pango::FontDescription.new(UserConfig[:mumble_basic_font])
94
    layout.text = text
95
    layout.alignment = Pango::ALIGN_RIGHT
96
    layout end
97

  
98
  def render_header(message, context, base_y)
99
    header_w = width - @icon_width - @margin*3 - @edge*2
100
    context.save{
101
      context.translate(@icon_width + @margin*2 + @edge, @margin + @edge + base_y)
102
      context.set_source_rgb(0,0,0)
103
      hl_layout, hr_layout = header_left(message, context), header_right(message, context)
104
      context.show_pango_layout(hl_layout)
105
      context.save{
106
        context.translate(header_w - hr_layout.size[0] / Pango::SCALE, 0)
107
        if (hl_layout.size[0] / Pango::SCALE) > header_w - hr_layout.size[0] / Pango::SCALE - 20
108
          r, g, b = get_backgroundcolor
109
          grad = Cairo::LinearPattern.new(-20, base_y, hr_layout.size[0] / Pango::SCALE + 20, base_y)
110
          grad.add_color_stop_rgba(0.0, r, g, b, 0.0)
111
          grad.add_color_stop_rgba(20.0 / (hr_layout.size[0] / Pango::SCALE + 20), r, g, b, 1.0)
112
          grad.add_color_stop_rgba(1.0, r, g, b, 1.0)
113
          context.rectangle(-20, base_y, hr_layout.size[0] / Pango::SCALE + 20, hr_layout.size[1] / Pango::SCALE + base_y)
114
          context.set_source(grad)
115
          context.fill() end
116
        context.show_pango_layout(hr_layout) } }
117
  end
118

  
119
  def main_message(message, context = dummy_context)
120
    attr_list, text = Pango.parse_markup(Pango.escape(message.to_show))
121
    layout = context.create_pango_layout
122
    layout.width = (width - @icon_width - @margin*3 - @edge*2) * Pango::SCALE
123
    layout.attributes = attr_list
124
    layout.wrap = Pango::WRAP_CHAR
125
    layout.font_description = Pango::FontDescription.new(UserConfig[:mumble_reply_font])
126
    layout.text = text
127
    layout end
128

  
129
  def render_outline(message, context, base_y)
130
    mh = message_height(message)
131
    context.save {
132
      context.pseudo_blur(4) {
133
        context.fill {
134
          context.set_source_rgb(*([32767, 32767, 32767]).map{ |c| c.to_f / 65536 })
135
          context.rounded_rectangle(@edge, @edge + base_y, width - @edge*2, mh - @edge*2, 4)
136
        }
137
      }
138
      context.fill {
139
        context.set_source_rgb(*([65535, 65535, 65535]).map{ |c| c.to_f / 65536 })
140
        context.rounded_rectangle(@edge, @edge + base_y, width - @edge*2, mh - @edge*2, 4)
141
      }
142
    }
143
  end
144

  
145
  def main_icon(message)
146
    Gdk::WebImageLoader.pixbuf(message[:user][:profile_image_url], icon_width, icon_height){ |pixbuf|
147
      helper.on_modify } end
148

  
149
  def get_backgroundcolor
150
    [1.0, 1.0, 1.0]
151
  end
152
end
28
      }.terminate('コメント付きリツイート描画中にエラーが発生しました') end end end
153
-