New upstream version 3.9.0~alpha1

parent 692c40dd
......@@ -11,7 +11,6 @@ ruby '>= 2.3.0'
group :default do
gem 'oauth', '>= 0.5.1'
gem 'json_pure', '~> 1.8'
gem 'addressable', '>= 2.5.2', '< 2.6'
gem 'diva', '>= 0.3.2', '< 2.0'
gem 'memoist', '>= 0.16', '< 0.17'
......
......@@ -46,6 +46,6 @@ module CHIConfig
NeverRetrieveOverlappedMumble = false
# このソフトのバージョン。
VERSION = [3,8,8,9999]
VERSION = [3,9,0,1]
end
......@@ -3,7 +3,7 @@ class Diva::Model::Memory
include Diva::DataSource
def initialize(klass=Diva::Model)
@storage = WeakStorage.new(Integer, klass) end
@storage = WeakStorage.new(Integer, klass, name: "diva-model-memory(#{klass})") end
def findbyid(id, policy)
if id.is_a? Enumerable
......
......@@ -5,30 +5,24 @@
require 'set'
require 'thread'
END{
ObjectSpace.each_object(WeakStore){ |s|
s.exit = true
}
}
require 'weakref'
class WeakStore
attr_accessor :exit
def initialize
@_storage = gen_storage
@_mutex = Monitor.new
@_tls_name = "weakstore_lock_#{@_mutex.object_id}".to_sym
@exit = false end
end
def atomic(&proc)
if not exit
Thread.current[@_tls_name] ||= 0
Thread.current[@_tls_name] += 1
begin
@_mutex.synchronize(&proc)
ensure
Thread.current[@_tls_name] -= 1 end end end
Thread.current[@_tls_name] ||= 0
Thread.current[@_tls_name] += 1
begin
@_mutex.synchronize(&proc)
ensure
Thread.current[@_tls_name] -= 1
end
end
protected
......@@ -174,45 +168,63 @@ class SizeLimitedStorage < WeakStore
class WeakStorage < WeakStore
def initialize(key_class, val_class)
def initialize(key_class, val_class, name: Kernel.caller(1).first)
@key_class, @val_class = key_class, val_class
@name = -name
super()
end
def [](key)
begin
result = atomic{
ObjectSpace._id2ref(storage[key]) if storage.has_key?(key) }
type_strict result => @val_class if result
result
rescue RangeError => e
error "#{key} was deleted"
nil end end
result = atomic do
wr = storage[key]
if wr
if wr.weakref_alive?
wr.__getobj__
else
count_before = storage.size
storage.keep_if do |_, v|
v.weakref_alive?
end
count_after = storage.size
notice "#{@name}: #{count_before} -> #{count_after}, #{count_before - count_after} reference(s) was deleted."
nil
end
end
end
type_strict result => @val_class if result
result
end
alias add []
def []=(key, val)
type_strict key => @key_class, val => @val_class
atomic{
ObjectSpace.define_finalizer(val, &gen_deleter)
storage[key] = val.object_id } end
atomic do
storage[key] = WeakRef.new(val)
result = storage[key].__getobj__
unless result.eql?(val)
error "#{@name}: object does not match!!!"
error "#{@name}: given value: #{val.inspect}"
error "#{@name}: stored value: #{result.inspect}"
abort
end
end
end
alias store []=
def has_key?(key)
atomic{
storage.has_key?(key) } end
atomic { storage[key]&.weakref_alive? }
end
def inspect
atomic{ "#<WeakStorage(#{@key_class} => #{@val_class}): #{storage.size}>" }
atomic { "#<WeakStorage(#{@name}: #{@key_class} => #{@val_class}): #{storage.size}>" }
end
private
def gen_deleter
@deleter ||= lambda{ |objid| @_storage.delete_if{ |key, val| val == objid } }
end
def gen_storage
Hash.new end end
Hash.new
end
end
class WeakSet < WeakStore
include Enumerable
......
......@@ -127,12 +127,12 @@ module Gtk
if record and record.message
return render_message(record.message)
else
self.pixbuf = Skin['notfound.png'].pixbuf(width: 64, height: 64) end
self.pixbuf = Skin[:notfound].pixbuf(width: 64, height: 64) end
rescue Exception => err
error "#{err.class} by uri: #{uri} model: #{record ? record.message.inspect : nil}"
raise if Mopt.debug
error err
self.pixbuf = Skin['notfound.png'].pixbuf(width: 64, height: 64) end
self.pixbuf = Skin[:notfound].pixbuf(width: 64, height: 64) end
private
......
......@@ -9,6 +9,7 @@ module Gdk::Coordinate
CoordinateStruct = Struct.new(:main_icon, :main_text, :header_text, :reply)
DEPTH = Gdk::Visual.system.depth
SCALE = Gdk::Visual.system.screen.resolution / 100
class Region
extend Memoist
......@@ -77,11 +78,15 @@ module Gdk::Coordinate
new
end
def scale(val)
Gdk.scale(val)
end
protected
# 寸法の初期化
def coordinator(width)
@width, @color, @icon_width, @icon_height, @icon_margin = [width, 1].max, DEPTH, 48, 48, 2
@width, @color, @icon_width, @icon_height, @icon_margin = [width, 1].max, DEPTH, scale(48), scale(48), scale(2)
end
# 座標系を構造体にまとめて返す
......
......@@ -9,7 +9,7 @@ miquire :mui, 'textselector'
miquire :mui, 'sub_parts_helper'
miquire :mui, 'replyviewer'
miquire :mui, 'sub_parts_favorite'
miquire :mui, 'sub_parts_retweet'
miquire :mui, 'sub_parts_share'
miquire :mui, 'sub_parts_quote'
miquire :mui, 'markup_generator'
miquire :mui, 'special_edge'
......@@ -130,7 +130,7 @@ class Gdk::MiraclePainter < Gtk::Object
@pixbuf
else
@last_modify_height = height
Skin['loading.png'].pixbuf(width: @last_modify_height, height: @last_modify_height)
Skin[:loading].pixbuf(width: @last_modify_height, height: @last_modify_height)
end
end
......@@ -339,15 +339,17 @@ class Gdk::MiraclePainter < Gtk::Object
private
def dummy_context
Gdk::Pixmap.new(nil, 1, 1, color).create_cairo_context end
Cairo::Context.dummy
end
deprecate :dummy_context, "Cairo::Context.dummy", 2020, 6
# 本文のための Pango::Layout のインスタンスを返す
def main_message(context = dummy_context)
def main_message(context = Cairo::Context.dummy)
layout = context.create_pango_layout
font = Plugin.filtering(:message_font, message, nil).last
layout.font_description = font_description(font) if font
layout.width = pos.main_text.width * Pango::SCALE
layout.attributes = textselector_attr_list(description_attr_list(emoji_height: emoji_height(layout.font_description)))
layout.attributes = textselector_attr_list(description_attr_list(emoji_height: layout.font_description.forecast_font_size))
layout.wrap = Pango::WrapMode::CHAR
color = Plugin.filtering(:message_font_color, message, nil).last
color = BLACK if not(color and color.is_a? Array and 3 == color.size)
......@@ -368,8 +370,10 @@ class Gdk::MiraclePainter < Gtk::Object
end
layout end
memoize def font_description(font)
Pango::FontDescription.new(font)
@@font_description = Hash.new{|h,k| h[k] = {} } # {scale => {font => FontDescription}}
def font_description(font)
@@font_description[scale(0xffff)][font] ||=
Pango::FontDescription.new(font).tap{|fd| fd.size = scale(fd.size) }
end
# 絵文字を描画する時の一辺の大きさを返す
......@@ -377,15 +381,14 @@ class Gdk::MiraclePainter < Gtk::Object
# [font] font description
# ==== Return
# [Integer] 高さ(px)
memoize def emoji_height(font)
layout = dummy_context.create_pango_layout
layout.font_description = font
layout.text = '.'
layout.pixel_size[1]
def emoji_height(font)
font.forecast_font_size
end
deprecate :emoji_height, "Pango::FontDescription#forecast_font_size", 2020, 6
# ヘッダ(左)のための Pango::Layout のインスタンスを返す
def header_left(context = dummy_context)
def header_left(context = Cairo::Context.dummy)
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)
......@@ -407,7 +410,7 @@ class Gdk::MiraclePainter < Gtk::Object
end
# ヘッダ(右)のための Pango::Layout のインスタンスを返す
def header_right(context = dummy_context)
def header_right(context = Cairo::Context.dummy)
hms = timestamp_label
attr_list, text = Pango.parse_markup(hms)
layout = context.create_pango_layout
......
......@@ -33,15 +33,19 @@ class Gdk::ReplyViewer < Gdk::SubPartsMessageBase
if show_edge?
unless @edge == EDGE_PRESENT_SIZE
@edge = EDGE_PRESENT_SIZE
helper.reset_height end
helper.reset_height
end
else
unless @edge == EDGE_ABSENT_SIZE
@edge = EDGE_ABSENT_SIZE
helper.reset_height end end
@edge end
helper.reset_height
end
end
helper.scale(@edge)
end
def badge(_message)
Skin['reply.png'].pixbuf(width: badge_radius*2, height: badge_radius*2)
Skin[:reply].pixbuf(width: badge_radius*2, height: badge_radius*2)
end
def background_color(message)
......@@ -55,7 +59,7 @@ class Gdk::ReplyViewer < Gdk::SubPartsMessageBase
UserConfig[:reply_text_color].map{ |c| c.to_f / 65536 } end
def main_text_font(message)
Pango::FontDescription.new(UserConfig[:reply_text_font]) end
helper.font_description(UserConfig[:reply_text_font]) end
def header_left_content(*args)
if show_header?
......@@ -68,9 +72,12 @@ class Gdk::ReplyViewer < Gdk::SubPartsMessageBase
def icon_size
if show_icon?
if UserConfig[:reply_icon_size]
Gdk::Rectangle.new(0, 0, UserConfig[:reply_icon_size], UserConfig[:reply_icon_size])
Gdk::Rectangle.new(0, 0, helper.scale(UserConfig[:reply_icon_size]), helper.scale(UserConfig[:reply_icon_size]))
else
super end end end
super
end
end
end
def text_max_line_count(message)
UserConfig[:reply_text_max_line_count] || super end
......
......@@ -6,6 +6,7 @@ require 'gtk2'
require 'cairo'
module Gdk::SubPartsHelper
extend Gem::Deprecate
def initialize(*args)
@subparts_height = nil
......@@ -41,6 +42,7 @@ module Gdk::SubPartsHelper
subparts.inject(0){ |sum, part| sum + part.height } end end
class Gdk::SubParts
extend Gem::Deprecate
include UiThreadOnly
attr_reader :helper
......@@ -71,6 +73,8 @@ class Gdk::SubParts
0 end
def dummy_context
Gdk::Pixmap.new(nil, 1, 1, @helper.color).create_cairo_context end
Cairo::Context.dummy
end
deprecate :dummy_context, "Cairo::Context.dummy", 2020, 6
end
......@@ -139,7 +139,8 @@ class Gdk::SubPartsMessageBase < Gdk::SubParts
# [Gdk::Rectangle] サイズ(px)。xとyは無視され、widthとheightのみが利用される
# [nil] アイコンを表示しない
def icon_size
Gdk::Rectangle.new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE) end
Gdk::Rectangle.new(0, 0, helper.scale(DEFAULT_ICON_SIZE), helper.scale(DEFAULT_ICON_SIZE))
end
# _message_ の本文のテキスト色を返す
# ==== Args
......@@ -168,17 +169,26 @@ class Gdk::SubPartsMessageBase < Gdk::SubParts
# :nodoc:
memoize def default_font
Pango::FontDescription.new(UserConfig[:reply_text_font]) end
helper.font_description(UserConfig[:reply_text_font])
end
attr_reader :margin
def margin
helper.scale(@margin)
end
attr_reader :edge
def edge
helper.scale(@edge)
end
# Fixnum 枠線の太さ(px)
attr_reader :border_weight
def border_weight
helper.scale(@border_weight)
end
# Fixnum バッジの半径(px)
attr_reader :badge_radius
def badge_radius
helper.scale(@badge_radius)
end
# :nodoc:
def initialize(*args)
......@@ -267,18 +277,13 @@ class Gdk::SubPartsMessageBase < Gdk::SubParts
else
(result / pango_layout.line_count) * text_max_line_count(message) + pango_layout.spacing/Pango::SCALE * 2 end end
# 絵文字を描画する時の一辺の大きさを返す
# ==== Return
# [Integer] 高さ(px)
memoize def emoji_height
layout = dummy_context.create_pango_layout
layout.font_description = default_font
layout.text = '.'
layout.pixel_size[1]
def emoji_height
default_font.forecast_font_size
end
deprecate :emoji_height, "Pango::FontDescription#forecast_font_size", 2020, 6
# ヘッダ(左)のための Pango::Layout のインスタンスを返す
def header_left(message, context = dummy_context)
def header_left(message, context = Cairo::Context.dummy)
text, font, attr_list = header_left_content(message)
if text
layout = context.create_pango_layout
......@@ -288,7 +293,7 @@ class Gdk::SubPartsMessageBase < Gdk::SubParts
layout end end
# ヘッダ(右)のための Pango::Layout のインスタンスを返す
def header_right(message, context = dummy_context)
def header_right(message, context = Cairo::Context.dummy)
text, font, attr_list = header_right_content(message)
if text
layout = context.create_pango_layout
......@@ -331,7 +336,7 @@ class Gdk::SubPartsMessageBase < Gdk::SubParts
context.show_pango_layout(hr_layout)
hr_layout end end end
def main_message(message, context = dummy_context)
def main_message(message, context = Cairo::Context.dummy)
layout = context.create_pango_layout
layout.width = (width - icon_width - margin*3 - edge*2) * Pango::SCALE
layout.attributes = description_attr_list(message)
......@@ -456,7 +461,8 @@ class Gdk::SubPartsMessageBase < Gdk::SubParts
end_index = start_index + note.description.bytesize
if UserConfig[:miraclepainter_expand_custom_emoji] && note.respond_to?(:inline_photo)
end_index += -note.description.bytesize + 1
rect = Pango::Rectangle.new(0, 0, emoji_height * Pango::SCALE, emoji_height * Pango::SCALE)
wh = default_font.forecast_font_size * Pango::SCALE
rect = Pango::Rectangle.new(0, 0, wh, wh)
shape = Pango::AttrShape.new(rect, rect, note.inline_photo)
shape.start_index = start_index
shape.end_index = end_index
......
......@@ -55,15 +55,19 @@ class Gdk::SubPartsQuote < Gdk::SubPartsMessageBase
if show_edge?
unless @edge == EDGE_PRESENT_SIZE
@edge = EDGE_PRESENT_SIZE
helper.reset_height end
helper.reset_height
end
else
unless @edge == EDGE_ABSENT_SIZE
@edge = EDGE_ABSENT_SIZE
helper.reset_height end end
@edge end
helper.reset_height
end
end
helper.scale(@edge)
end
def badge(_message)
Skin['quote.png'].pixbuf(width: @badge_radius*2, height: @badge_radius*2) end
Skin[:quote].pixbuf(width: badge_radius*2, height: badge_radius*2) end
def background_color(message)
color = Plugin.filtering(:subparts_quote_background_color, message, UserConfig[:quote_background_color]).last
......@@ -79,7 +83,8 @@ class Gdk::SubPartsQuote < Gdk::SubPartsMessageBase
super end end
def main_text_font(message)
Pango::FontDescription.new(UserConfig[:quote_text_font]) end
helper.font_description(UserConfig[:quote_text_font])
end
def header_left_content(*args)
if show_header?
......@@ -92,7 +97,7 @@ class Gdk::SubPartsQuote < Gdk::SubPartsMessageBase
def icon_size
if show_icon?
if UserConfig[:quote_icon_size]
Gdk::Rectangle.new(0, 0, UserConfig[:quote_icon_size], UserConfig[:quote_icon_size])
Gdk::Rectangle.new(0, 0, helper.scale(UserConfig[:quote_icon_size]), helper.scale(UserConfig[:quote_icon_size]))
else
super end end end
......
# -*- coding: utf-8 -*-
miquire :mui, 'sub_parts_voter'
require 'gtk2'
require 'cairo'
class Gdk::SubPartsRetweet < Gdk::SubPartsVoter
extend Memoist
register
def get_vote_count
[helper.message[:retweet_count] || 0, super].max
end
def get_default_votes
helper.message.retweeted_by
end
memoize def title_icon_model
Skin.photo('retweet.png')
end
def name
:retweeted end
Plugin.create(:sub_parts_retweet) do
on_retweet do |retweets|
retweets.deach{ |retweet|
Gdk::MiraclePainter.findbymessage_d(retweet.retweet_source(true)).next{ |mps|
mps.deach{ |mp|
if not mp.destroyed? and mp.subparts
begin
mp.subparts.find{ |sp| sp.class == Gdk::SubPartsRetweet }.add(retweet[:user])
mp.on_modify
rescue Gdk::MiraclePainter::DestroyedError
nil end end } }.terminate("retweet error") } end
on_retweet_destroyed do |source, user, retweet_id|
Gdk::MiraclePainter.findbymessage_d(source).next{ |mps|
mps.deach{ |mp|
if not mp.destroyed? and mp.subparts
begin
mp.subparts.find{ |sp| sp.class == Gdk::SubPartsRetweet }.delete(user)
mp.on_modify
rescue Gdk::MiraclePainter::DestroyedError
nil end end }.terminate("retweet destroy error")
}
end
end
end
# -*- coding: utf-8 -*-
miquire :mui, 'sub_parts_voter'
require 'gtk2'
require 'cairo'
class Gdk::SubPartsShare < Gdk::SubPartsVoter
extend Memoist
register
def get_vote_count