aboutsummaryrefslogtreecommitdiffstats
path: root/core/plugin/openimg/window.rb
blob: ee57f07ae774cba1108a1d33f9a7dc444d5ca22b (plain)
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
# -*- coding: utf-8 -*-

module Plugin::Openimg
  class Window < Gtk::Window
    attr_reader :photo

    def initialize(photo, next_opener)
      super()
      @photo = photo
      @image_surface = loading_surface
      @next_opener = next_opener
      window_settings
      ssc(:destroy, &:destroy)
    end

    def start_loading
      Thread.new {
        Plugin.filtering(:openimg_pixbuf_from_display_url, photo, nil, nil)
      }.next { |_, pixbufloader, complete_promise|
        if pixbufloader.is_a? Gdk::PixbufLoader
          rect = nil
          pixbufloader.ssc(:area_updated, self) do |_, x, y, width, height|
            if rect
              rect[:left] = [rect[:left], x].min
              rect[:top] = [rect[:top], y].min
              rect[:right] = [rect[:right], x+width].max
              rect[:bottom] = [rect[:bottom], y+height].max
            else
              rect = {left: x, top: y, right: x+width, bottom: y+height}
              Delayer.new do
                progress(pixbufloader.pixbuf,
                         x: rect[:left],
                         y: rect[:top],
                         width: rect[:right] - rect[:left],
                         height: rect[:bottom] - rect[:top])
                rect = nil
              end
            end
            true end

          complete_promise.next{
            progress(pixbufloader.pixbuf, paint: true)
            pixbufloader.close
          }.trap { |exception|
            error exception
            @image_surface = error_surface
            redraw(repaint: true)
          }
        else
          warn "cant open: #{photo}"
          @image_surface = error_surface
          redraw(repaint: true) end
      }.trap{ |exception|
        error exception
        @image_surface = error_surface
        redraw(repaint: true)
      }
      self
    end

    private

    def window_settings
      set_title(photo.perma_link.to_s)
      set_role('mikutter_image_preview'.freeze)
      set_type_hint(Gdk::Window::TYPE_HINT_DIALOG)
      set_default_size(*default_size)
      add(Gtk::VBox.new.closeup(w_toolbar).add(w_wrap))
    end

    def redraw(repaint: true)
      return if w_wrap.destroyed?
      gdk_window = w_wrap.window
      return unless gdk_window
      ew, eh = gdk_window.geometry[2,2]
      return if(ew == 0 or eh == 0)
      context = gdk_window.create_cairo_context
      context.save do
        if repaint
          context.set_source_color(Cairo::Color::BLACK)
          context.paint end
        if (ew * @image_surface.height) > (eh * @image_surface.width)
          rate = eh.to_f / @image_surface.height
          context.translate((ew - @image_surface.width*rate)/2, 0)
        else
          rate = ew.to_f / @image_surface.width
          context.translate(0, (eh - @image_surface.height*rate)/2) end
        context.scale(rate, rate)
        context.set_source(Cairo::SurfacePattern.new(@image_surface))
        context.paint end
    rescue => _
      error _ end

    def progress(pixbuf, x: 0, y: 0, width: 0, height: 0, paint: false)
      return unless pixbuf
      context = nil
      size_changed = false
      unless @image_surface.width == pixbuf.width and @image_surface.height == pixbuf.height
        size_changed = true
        @image_surface = Cairo::ImageSurface.new(pixbuf.width, pixbuf.height)
        context = Cairo::Context.new(@image_surface)
        context.save do
          context.set_source_color(Cairo::Color::BLACK)
          context.paint end end
      context ||= Cairo::Context.new(@image_surface)
      context.save do
        context.set_source_pixbuf(pixbuf)
        if paint
          context.paint
        else
          context.rectangle(x, y, width, height)
          context.fill end end
      redraw(repaint: paint || size_changed)
    end

    #
    # === Widgetたち
    #

    def w_wrap
      @w_wrap ||= ::Gtk::DrawingArea.new.tap{|w|
        w.ssc(:size_allocate, &gen_wrap_size_allocate)
        w.ssc(:expose_event, &gen_wrap_expose_event)
      }
    end

    def w_toolbar
      @w_toolbar ||= ::Gtk::Toolbar.new.tap{|w| w.insert(0, w_browser) }
    end

    def w_browser
      @w_browser ||= ::Gtk::ToolButton.new(
        Gtk::Image.new(GdkPixbuf::Pixbuf.new(file: Skin.get('forward.png'), width: 24, height: 24))
      ).tap{|w|
        w.ssc(:clicked, &gen_browser_clicked)
      }
    end

    #
    # === イベントハンドラ
    #

    def gen_browser_clicked
      proc do
        Plugin.call(:open, @next_opener)
        false
      end
    end

    def gen_wrap_expose_event
      proc do |widget|
        redraw(repaint: true)
        true
      end
    end

    def gen_wrap_size_allocate
      last_size = nil
      proc do |widget|
        if widget.window && last_size != widget.window.geometry[2,2]
          last_size = widget.window.geometry[2,2]
          redraw(repaint: true)
        end
        false
      end
    end

    #
    # === その他
    #

    def default_size
      @size || [640, 480]
    end

    def loading_surface
      surface = Cairo::ImageSurface.from_png(Skin.get('loading.png'))
      surface
    end

    def error_surface
      surface = Cairo::ImageSurface.from_png(Skin.get('notfound.png'))
      surface
    end

  end
end