卜部昌平のあまりreblogしないtumblr RSS

Archive

Aug
24th
Tue
permalink

それっぽく表示改(Windows対応)

去年書いたやつがWindowsで動かないからGTK+捨ててQtで書き直したよ。Ruby-GTK2おっくれてるぅー (注: Windows対応のやる気の無さがパない。もうすぐGTK+3が出ようかというのにいまだに3年半も前のGTK+2.10ベースのパッケージが最新ってのではさすがに言い訳できない。wxRubyですら同様の機能は実現可能であると調査済みなので、単にRuby-GTK2が時代から遅れている)

あと「STDINから一行づつ読んで」の部分はWindowsでは最強に実現不可能(STDINがselect()できない)なので、しょうがないのでTwitterのStreamから読む感じにしてみた。IRCからとかにしたい人は自力で弄ってね

# Copyright(c) 2010 Urabe Shyouhei.  All rights Reserved.
#
# Permission is hereby granted, free of  charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the code  without restriction, including without limitation  the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the software,  and to permit persons to whom the  software is furnished to
# do so, subject to the following conditions:
#
#        The above copyright notice and this permission notice shall be
#        included in all copies or substantial portions of the software.
#
# THE SOFTWARE  IS PROVIDED "AS IS",  WITHOUT WARRANTY OF ANY  KIND, EXPRESS OR
# IMPLIED,  INCLUDING BUT  NOT LIMITED  TO THE  WARRANTIES  OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE  AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHOR  OR  COPYRIGHT HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

require 'net/http'
require 'uri'
require 'rubygems'
require 'Qt'
require 'json'

a = Qt::Application.new ARGV

FPS = 30
Font = Qt::Font.new 'Meiryo', 32,  Qt::Font::Bold
Color = Qt::Color.new 255, 255, 32, 200 # rgba
Metrics = Qt::FontMetrics.new Font
Codec = Qt::TextCodec.codecForName 'UTF-8' # JSON is UTF-8.

class TwitterFilter
    def initialize filter, user, pass
        @chunked = false
        @buf = ''
        case filter
        when Array
            q = filter.join ','
        when String
            q = filter
        else
            q = filter.to_s
        end
        k = ["#{user}:#{pass}"].pack "m*"
        @sock = TCPSocket.new 'stream.twitter.com', 80
        str = <<-"end".gsub %r/^\t+/, ''
            POST /1/statuses/filter.json?track=#{URI.escape q} HTTP/1.1
            Host: stream.twitter.com
            Authorization: Basic #{k}

        end
        @sock.write str
        status = @sock.gets
        ok = /200 OK/in.match status
        while l = @sock.gets
            status << l
            case l
            when /^Transfer-Encoding: *chunked/in
                @chunked = true
            when "\r\n"
                if ok
                    return
                else
                    raise status
                end
            end
        end
    end

    def gets
        str = if @chunked then
                    get_a_chunk
                else
                    @sock.read_nonblock 32768
                end
        return nil unless str
        return nil if str == "\r\n"
        @buf << str
        buf2 = Array.new
        loop do
            begin
                obj = JSON.parse @buf
                @buf.replace '' # consume
                return obj
            rescue JSON::ParserError
                if @buf.empty?
                    return nil
                else
                    @buf, tmp = @buf.split(/(?=\{[^{]*\z)/, 2)
                    if tmp.nil?
                        return nil
                    else
                        buf2 << tmp
                    end
                end
            end
        end
    ensure
        if buf2 and not buf2.empty?
            buf2.reverse!
            @buf.replace buf2.join
        end
    end

    private
    def get_a_chunk
        # at the beginning of this method a @sock should be in chunk head.
        return nil unless IO.select [@sock], nil, nil, 0
        case l = @sock.gets when /\A[0-9a-f]+\r\n\z/ni
            len = $&.to_s.to_i 16
            str = @sock.read len
            @sock.read 2 # read empty line
            return str
        else
            raise "not chunked #{l}"
        end
    end
end

class Comment < Qt::Label
    def initialize str, parent
        str.gsub! %r/[\r\n\s]+/um, ' '
        dec = Codec.makeDecoder
        qstr = dec.to_unicode str, str.length
        super qstr, parent
        hide
        p = palette
        p.set_color Qt::Palette::Foreground, Color

        set_font Font
        set_palette p
        set_text_format Qt::PlainText
        set_text_interaction_flags Qt::NoTextInteraction
        set_word_wrap false
        set_indent 0
        set_margin 0
        set_focus_policy Qt::NoFocus
        set_attribute Qt::WA_DeleteOnClose

        tmp = parent.geometry
        h = Metrics.height
        r = Metrics.bounding_rect str
        # note that, for fonts such as Apple Chancety, a bounding rect can
        # extend below (0, 0)
        w = r.width - r.x
        resize w, h
        @w, @h = w, h
    end
    attr_reader :w, :h

    def moeve h, t
        # nicovideo is square, 512pixcels width in 4 secs
        speed = (h / 3.0 * 4.0 + width) / 4.0 * t
        newx = x - speed
        move newx.to_i, y
        if newx < -width
            destroy true, true
            true
        end
    end
end

class Toplevel < Qt::Widget
    def initialize
        super
        @comments = Array.new
        @lines = Array.new
        @tw = TwitterFilter.new $filter, $user, $password
        set_window_flags Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
        set_attribute Qt::WA_TranslucentBackground
        set_attribute Qt::WA_MSWindowsUseDirect3D
        set_attribute Qt::WA_TransparentForMouseEvents
        start_timer 1000/FPS
        show_maximized
    end

    def timerEvent ev
        @t ||= Time.now
        t1 = Time.now
        t2 = t1 - @t
        @t = t1
        @comments.reject! {|i| i.moeve height, t2 }
        @lines.map! do |i|
            next nil unless i
            next nil if i.x <= -i.width
            i
        end
        while obj = @tw.gets
            if obj['text']
                push  obj['text']
            else
                push obj.inspect
                p obj.inspect
            end
        end
    end

    def push str
        c = Comment.new str, self
        h = c.height
        y = height - 20 # 20 for taskbar
        i = -1
        n = y / h
        n.times do |j|
            if (not k = @lines[j]) or \
            (k.y + k.width < width and \
                k.width > c.width)
            then
                i = j
                break
            end
        end
        @comments.push c
        y = if i == -1
            rand height
        else
            @lines[i] = c
            i * h
        end
        c.move geometry.width, y
        c.show
    end
end

a.set_font Font
b = Toplevel.new
a.exec

# Local Variables:
# mode: ruby
# coding: utf-8
# indent-tabs-mode: t
# tab-width: 3
# ruby-indent-level: 3
# fill-column: 79
# default-justification: full
# End:
# vi: ts=3 sw=3
  1. sanemat reblogged this from shyouhei
  2. siyo reblogged this from shyouhei
  3. atm09td reblogged this from shyouhei
  4. shyouhei posted this