File: timeline.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 (212 lines) | stat: -rw-r--r-- 5,652 bytes parent folder | download
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
# frozen_string_literal: true

require 'mui/gtk_postbox'

module Plugin::Gtk3
  # 投稿ボックスとスクロール可能のリストビューを備えたウィジェット
  class Timeline < Gtk::Grid
    class << self
      attr_accessor :current

      def update_rows(model)
        @instances.each do |instance|
          instance.bulk_add([model]) if instance.include?(model)
        end
      end

      def remove_rows(models)
        @instances.each do |instance|
          instance.bulk_remove(models)
        end
      end

      def miracle_painters_of(message)
        Enumerator.new do |yielder|
          @instances.each do |instance|
            detect = instance.find_miracle_painter_by_message(message)
            yielder << detect if detect
          end
        end
      end

      def new(*)
        instance = super
        (@instances ||= []).push(instance)
        @current ||= instance
        instance
      end
    end

    type_register

    attr_reader :postbox, :order, :imaginary

    def initialize(imaginary=nil)
      super()

      self.name = 'timeline'
      self.orientation = :vertical

      @uri_mp_dict = {} # { String (model uri) => MiraclePainter }
      @imaginary = imaginary
      @order = ->(m) { m.modified.to_i }
      @postbox = Gtk::Grid.new.tap do |grid|
        grid.orientation = :vertical
      end
      @listbox = Gtk::ListBox.new.tap do |listbox|
        listbox.selection_mode = :multiple
        listbox.activate_on_single_click = false
        listbox.set_sort_func do |a, b|
          @order.(b.model) <=> @order.(a.model)
        end
      end
      ssc(:destroy) do
        @imaginary.destroy
      end

      add @postbox
      add(Gtk::ScrolledWindow.new.tap do |sw|
            sw.set_policy :never, :automatic
            sw.overlay_scrolling = false
            sw.expand = true
            sw.add @listbox
          end)
    end

    def order=(order)
      @order = order
      @listbox.invalidate_sort
    end

    def include?(message)
      @uri_mp_dict.key?(message.uri.to_s)
    end

    def active
      @imaginary.active!(true, true)

      if self.class.current && self.class.current != self && !self.class.current.destroyed?
        self.class.current.unselect_all
      end
      self.class.current = self
    end

    def keypress(keyname)
      Plugin::GUI.keypress keyname, @imaginary
    end

    def bulk_add(models)
      update_ordinal = false
      filtered_models = Plugin.filtering(:show_filter, [*models]).first
      filtered_models.each do |message_or_share|
        message = message_or_share.retweet_source || message_or_share
        mp = find_miracle_painter_by_message(message)
        if mp
          update_ordinal |= @order.(mp.model) != @order.(message)
          mp.message = message
        else
          row = MiraclePainter.new(message)
          row.show_all
          @listbox.add(row)
          @uri_mp_dict[message.uri.to_s] = row
        end
      end
      @listbox.invalidate_sort if update_ordinal
      overflow = @listbox.children[1000..]
      bulk_remove(overflow.map(&:message)) if overflow
    end

    def bulk_remove(messages)
      messages.map { |message|
        @uri_mp_dict.delete(message.uri.to_s)
      }.to_a.compact.each(&@listbox.method(:remove))
    end

    def clear
      raise NotImplementedError
    end

    def size
      @uri_mp_dict.size
    end

    def select_row(row)
      @listbox.select_row(row) unless selected_rows.include?(row)
    end

    def select_row_at_index(index)
      unselect_all
      @listbox.select_row @listbox.get_row_at_index index
    end

    def unselect_all
      selected_rows.each do |row|
        @listbox.unselect_row row
      end
    end

    def jump_to(to)
      case to
      when :top
        @listbox.adjustment.value = @listbox.adjustment.lower
        select_row_at_index(0)
      when :up
        @listbox.adjustment.value -= @listbox.adjustment.page_increment
      when :down
        @listbox.adjustment.value += @listbox.adjustment.page_increment
      end
    end

    def selected_rows
      @listbox.selected_rows
    end

    def popup_menu(event)
      menu = Gtk::Menu.new
      menu.attach_to_widget self

      ev, menus = Plugin::GUI::Command.get_menu_items @imaginary
      Gtk::ContextMenu.new(*menus).build!(self, ev, menu = menu)

      menu.show_all
      menu.popup_at_pointer event

      # TODO: gtk3 参照を保持しておかないとGCされてしまう
      @menu = menu
    end

    def add_postbox(i_postbox)
      # ずっと表示される(投稿しても消えない)PostBoxの処理
      # 既にprocっぽいものが入っているときはそのままにしておく
      options = i_postbox.options.dup
      if options[:delegate_other] && !options[:delegate_other].respond_to?(:to_proc)
        i_timeline = i_postbox.ancestor_of(Plugin::GUI::Timeline)
        options[:delegate_other] = postbox_delegation_generator(i_timeline)
        options[:postboxstorage] = postbox
      end
      create_postbox(options)
    end

    def find_miracle_painter_by_message(message)
      @uri_mp_dict[message.uri.to_s]
    end

    private

    def create_postbox(options, &block)
      options = options.dup
      options[:before_post_hook] = ->(_this) {
        get_ancestor(Gtk::Window).set_focus(self) unless destroyed?
      }
      pb = Gtk::PostBox.new(**options).show_all
      postbox << pb
      pb.on_delete(&block) if block
      get_ancestor(Gtk::Window).set_focus(pb.post)
      pb
    end

    def postbox_delegation_generator(i_timeline)
      ->(params) { i_timeline.create_postbox(params) }
    end
  end
end