Class: Redwood::ThreadIndexMode

Inherits:
LineCursorMode show all
Defined in:
lib/sup/modes/thread-index-mode.rb

Overview

subclasses should implement:

  • is_relevant?

Constant Summary collapse

DATE_WIDTH =
Time::TO_NICE_S_MAX_LEN
MIN_FROM_WIDTH =
15
LOAD_MORE_THREAD_NUM =
20

Constants inherited from ScrollMode

ScrollMode::COL_JUMP

Instance Attribute Summary

Attributes inherited from LineCursorMode

#curpos

Attributes inherited from ScrollMode

#botline, #leftcol, #topline

Attributes inherited from Mode

#buffer

Instance Method Summary collapse

Methods inherited from LineCursorMode

#draw

Methods inherited from ScrollMode

#at_bottom?, #at_top?, #cancel_search!, #col_left, #col_right, #continue_search_in_buffer, #draw, #ensure_mode_validity, #in_search?, #jump_to_col, #jump_to_end, #jump_to_left, #jump_to_line, #jump_to_start, #line_down, #line_up, #page_down, #page_up, #rightcol, #search_goto_line, #search_in_buffer, #search_start_line

Methods inherited from Mode

#blur, #cancel_search!, #draw, #focus, #handle_input, #help_text, #in_search?, #killable?, load_all_modes, make_name, #name, register_keymap, #resolve_input, #save_to_file

Constructor Details

#initialize(hidden_labels = [], load_thread_opts = {}) ⇒ ThreadIndexMode

Returns a new instance of ThreadIndexMode.



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
# File 'lib/sup/modes/thread-index-mode.rb', line 37

def initialize hidden_labels=[], load_thread_opts={}
  super()
  @mutex = Mutex.new # covers the following variables:
  @threads = {}
  @hidden_threads = {}
  @size_widget_width = nil
  @size_widgets = {}
  @tags = Tagger.new self

  ## these guys, and @text and @lines, are not covered
  @load_thread = nil
  @load_thread_opts = load_thread_opts
  @hidden_labels = hidden_labels + LabelManager::HIDDEN_RESERVED_LABELS
  @date_width = DATE_WIDTH
  
  initialize_threads # defines @ts and @ts_mutex
  update # defines @text and @lines

  UpdateManager.register self

  @last_load_more_size = nil
  to_load_more do |size|
    next if @last_load_more_size == 0
    load_threads :num => 1, :background => false
    load_threads :num => (size - 1),
                 :when_done => lambda { |num| @last_load_more_size = num }
  end
end

Instance Method Details

#[](i) ⇒ Object



67
# File 'lib/sup/modes/thread-index-mode.rb', line 67

def [] i; @text[i]; end

#actually_toggle_archived(t) ⇒ Object



197
198
199
200
201
202
203
204
205
# File 'lib/sup/modes/thread-index-mode.rb', line 197

def actually_toggle_archived t
  if t.has_label? :inbox
    t.remove_label :inbox
    UpdateManager.relay self, :archived, t
  else
    t.apply_label :inbox
    UpdateManager.relay self, :unarchived, t
  end
end

#actually_toggle_deleted(t) ⇒ Object



217
218
219
220
221
222
223
224
225
# File 'lib/sup/modes/thread-index-mode.rb', line 217

def actually_toggle_deleted t
  if t.has_label? :deleted
    t.remove_label :deleted
    UpdateManager.relay self, :undeleted, t
  else
    t.apply_label :deleted
    UpdateManager.relay self, :deleted, t
  end
end

#actually_toggle_spammed(t) ⇒ Object



207
208
209
210
211
212
213
214
215
# File 'lib/sup/modes/thread-index-mode.rb', line 207

def actually_toggle_spammed t
  if t.has_label? :spam
    t.remove_label :spam
    UpdateManager.relay self, :unspammed, t
  else
    t.apply_label :spam
    UpdateManager.relay self, :spammed, t
  end
end

#actually_toggle_starred(t) ⇒ Object



175
176
177
178
179
180
181
182
183
# File 'lib/sup/modes/thread-index-mode.rb', line 175

def actually_toggle_starred t
  if t.has_label? :starred # if ANY message has a star
    t.remove_label :starred # remove from all
    UpdateManager.relay self, :unstarred, t
  else
    t.first.add_label :starred # add only to first
    UpdateManager.relay self, :starred, t
  end
end

#apply_to_taggedObject



354
# File 'lib/sup/modes/thread-index-mode.rb', line 354

def apply_to_tagged; @tags.apply_to_tagged; end

#cleanupObject



329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/sup/modes/thread-index-mode.rb', line 329

def cleanup
  UpdateManager.unregister self

  if @load_thread
    @load_thread.kill 
    BufferManager.clear @mbid if @mbid
    sleep 0.1 # TODO: necessary?
    BufferManager.erase_flash
  end
  save
  super
end

#contains_thread?(t) ⇒ Boolean

Returns:

  • (Boolean)


68
# File 'lib/sup/modes/thread-index-mode.rb', line 68

def contains_thread? t; @threads.include?(t) end

#edit_labelsObject



356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/sup/modes/thread-index-mode.rb', line 356

def edit_labels
  thread = cursor_thread or return
  speciall = (@hidden_labels + LabelManager::RESERVED_LABELS).uniq
  keepl, modifyl = thread.labels.partition { |t| speciall.member? t }

  user_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", modifyl, @hidden_labels

  return unless user_labels
  thread.labels = keepl + user_labels
  user_labels.each { |l| LabelManager << l }
  update_text_for_line curpos
end

#edit_messageObject



164
165
166
167
168
169
170
171
172
173
# File 'lib/sup/modes/thread-index-mode.rb', line 164

def edit_message
  return unless(t = cursor_thread)
  message, *crap = t.find { |m, *o| m.has_label? :draft }
  if message
    mode = ResumeMode.new message
    BufferManager.spawn "Edit message", mode
  else
    BufferManager.flash "Not a draft message!"
  end
end

#forwardObject



393
394
395
396
397
398
399
# File 'lib/sup/modes/thread-index-mode.rb', line 393

def forward
  t = cursor_thread or return
  m = t.latest_message
  return if m.nil? # probably won't happen
  m.load_from_source!
  ForwardMode.spawn_nicely m
end

#handle_add_update(sender, m) ⇒ Object



135
136
137
138
139
140
141
142
# File 'lib/sup/modes/thread-index-mode.rb', line 135

def handle_add_update sender, m
  @ts_mutex.synchronize do
    return unless is_relevant?(m) || @ts.is_relevant?(m)
    @ts.load_thread_for_message m
  end
  update
  BufferManager.draw_screen
end

#handle_archived_update(*a) ⇒ Object



124
# File 'lib/sup/modes/thread-index-mode.rb', line 124

def handle_archived_update *a; handle_read_update(*a); end

#handle_delete_update(sender, mid) ⇒ Object



144
145
146
147
148
149
150
151
# File 'lib/sup/modes/thread-index-mode.rb', line 144

def handle_delete_update sender, mid
  @ts_mutex.synchronize do
    return unless @ts.contains_id? mid
    @ts.remove mid
  end
  update
  BufferManager.draw_screen
end

#handle_deleted_update(sender, t) ⇒ Object



126
127
128
129
130
# File 'lib/sup/modes/thread-index-mode.rb', line 126

def handle_deleted_update sender, t
  handle_read_update sender, t
  hide_thread t
  regen_text
end

#handle_label_thread_update(sender, t) ⇒ Object



112
113
114
115
116
# File 'lib/sup/modes/thread-index-mode.rb', line 112

def handle_label_thread_update sender, t
  l = @lines[t] or return
  update_text_for_line l
  BufferManager.draw_screen
end

#handle_label_update(sender, m) ⇒ Object



107
108
109
110
# File 'lib/sup/modes/thread-index-mode.rb', line 107

def handle_label_update sender, m
  t = @ts_mutex.synchronize { @ts.thread_for(m) } or return
  handle_label_thread_update sender, t
end

#handle_read_update(sender, t) ⇒ Object



118
119
120
121
122
# File 'lib/sup/modes/thread-index-mode.rb', line 118

def handle_read_update sender, t
  l = @lines[t] or return
  update_text_for_line l
  BufferManager.draw_screen
end

#is_relevant?(m) ⇒ Boolean

overwrite me!

Returns:

  • (Boolean)


133
# File 'lib/sup/modes/thread-index-mode.rb', line 133

def is_relevant? m; false; end

#jump_to_next_newObject



255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/sup/modes/thread-index-mode.rb', line 255

def jump_to_next_new
  n = @mutex.synchronize do
    ((curpos + 1) ... lines).find { |i| @threads[i].has_label? :unread } ||
      (0 ... curpos).find { |i| @threads[i].has_label? :unread }
  end
  if n
    ## jump there if necessary
    jump_to_line n unless n >= topline && n < botline
    set_cursor_pos n
  else
    BufferManager.flash "No new messages"
  end
end

#killObject



303
304
305
306
# File 'lib/sup/modes/thread-index-mode.rb', line 303

def kill
  t = cursor_thread or return
  multi_kill [t]
end

#linesObject



66
# File 'lib/sup/modes/thread-index-mode.rb', line 66

def lines; @text.length; end

#load_n_threads(n = LOAD_MORE_THREAD_NUM, opts = {}) ⇒ Object

TODO: figure out @ts_mutex in this method



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/sup/modes/thread-index-mode.rb', line 411

def load_n_threads n=LOAD_MORE_THREAD_NUM, opts={}
  @mbid = BufferManager.say "Searching for threads..."
  orig_size = @ts.size
  last_update = Time.now
  @ts.load_n_threads(@ts.size + n, opts) do |i|
    if (Time.now - last_update) >= 0.25
      BufferManager.say "Loaded #{i.pluralize 'thread'}...", @mbid
      update
      BufferManager.draw_screen
      last_update = Time.now
    end
  end
  @ts.threads.each { |th| th.labels.each { |l| LabelManager << l } }

  update
  BufferManager.clear @mbid
  @mbid = nil
  BufferManager.draw_screen
  @ts.size - orig_size
end

#load_n_threads_background(n = LOAD_MORE_THREAD_NUM, opts = {}) ⇒ Object



401
402
403
404
405
406
407
408
# File 'lib/sup/modes/thread-index-mode.rb', line 401

def load_n_threads_background n=LOAD_MORE_THREAD_NUM, opts={}
  return if @load_thread # todo: wrap in mutex
  @load_thread = Redwood::reporting_thread("load threads for thread-index-mode") do
    num = load_n_threads n, opts
    opts[:when_done].call(num) if opts[:when_done]
    @load_thread = nil
  end
end

#load_threads(opts = {}) ⇒ Object



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# File 'lib/sup/modes/thread-index-mode.rb', line 441

def load_threads opts={}
  n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM

  myopts = @load_thread_opts.merge({ :when_done => (lambda do |num|
    opts[:when_done].call(num) if opts[:when_done]
    if num > 0
      BufferManager.flash "Found #{num.pluralize 'thread'}."
    else
      BufferManager.flash "No matches."
    end
  end)})

  if opts[:background] || opts[:background].nil?
    load_n_threads_background n, myopts
  else
    load_n_threads n, myopts
  end
end

#multi_edit_labels(threads) ⇒ Object



369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/sup/modes/thread-index-mode.rb', line 369

def multi_edit_labels threads
  answer = BufferManager.ask :add_labels, "add labels: "
  return unless answer
  user_labels = answer.split(/\s+/).map { |l| l.intern }
  
  hl = user_labels.select { |l| @hidden_labels.member? l }
  if hl.empty?
    threads.each { |t| user_labels.each { |l| t.apply_label l } }
    user_labels.each { |l| LabelManager << l }
  else
    BufferManager.flash "'#{hl}' is a reserved label!"
  end
  regen_text
end

#multi_kill(threads) ⇒ Object



308
309
310
311
312
313
314
315
# File 'lib/sup/modes/thread-index-mode.rb', line 308

def multi_kill threads
  threads.each do |t|
    t.apply_label :killed
    hide_thread t
  end
  regen_text
  BufferManager.flash "#{threads.size.pluralize 'Thread'} killed."
end

#multi_select(threads) ⇒ Object



103
104
105
# File 'lib/sup/modes/thread-index-mode.rb', line 103

def multi_select threads
  threads.each { |t| select t }
end

#multi_toggle_archived(threads) ⇒ Object



233
234
235
236
# File 'lib/sup/modes/thread-index-mode.rb', line 233

def multi_toggle_archived threads
  threads.each { |t| actually_toggle_archived t }
  regen_text
end

#multi_toggle_deleted(threads) ⇒ Object

see comment for multi_toggle_spam



295
296
297
298
299
300
301
# File 'lib/sup/modes/thread-index-mode.rb', line 295

def multi_toggle_deleted threads
  threads.each do |t|
    actually_toggle_deleted t
    hide_thread t 
  end
  regen_text
end

#multi_toggle_new(threads) ⇒ Object



245
246
247
248
# File 'lib/sup/modes/thread-index-mode.rb', line 245

def multi_toggle_new threads
  threads.each { |t| t.toggle_label :unread }
  regen_text
end

#multi_toggle_spam(threads) ⇒ Object

both spam and deleted have the curious characteristic that you always want to hide the thread after either applying or removing that label. in all thread-index-views except for label-search-results-mode, when you mark a message as spam or deleted, you want it to disappear immediately; in LSRM, you only see deleted or spam emails, and when you undelete or unspam them you also want them to disappear immediately.



281
282
283
284
285
286
287
# File 'lib/sup/modes/thread-index-mode.rb', line 281

def multi_toggle_spam threads
  threads.each do |t|
    actually_toggle_spammed t
    hide_thread t 
  end
  regen_text
end

#multi_toggle_starred(threads) ⇒ Object



192
193
194
195
# File 'lib/sup/modes/thread-index-mode.rb', line 192

def multi_toggle_starred threads
  threads.each { |t| actually_toggle_starred t }
  regen_text
end

#multi_toggle_tagged(threads) ⇒ Object



250
251
252
253
# File 'lib/sup/modes/thread-index-mode.rb', line 250

def multi_toggle_tagged threads
  @mutex.synchronize { @tags.drop_all_tags }
  regen_text
end

#reloadObject



70
71
72
73
74
# File 'lib/sup/modes/thread-index-mode.rb', line 70

def reload
  drop_all_threads
  BufferManager.draw_screen
  load_threads :num => buffer.content_height
end

#replyObject



384
385
386
387
388
389
390
391
# File 'lib/sup/modes/thread-index-mode.rb', line 384

def reply
  t = cursor_thread or return
  m = t.latest_message
  return if m.nil? # probably won't happen
  m.load_from_source!
  mode = ReplyMode.new m
  BufferManager.spawn "Reply to #{m.subj}", mode
end

#resize(rows, cols) ⇒ Object



461
462
463
464
# File 'lib/sup/modes/thread-index-mode.rb', line 461

def resize rows, cols
  regen_text
  super
end

#saveObject



317
318
319
320
321
322
323
324
325
326
327
# File 'lib/sup/modes/thread-index-mode.rb', line 317

def save
  dirty_threads = @mutex.synchronize { (@threads + @hidden_threads.keys).select { |t| t.dirty? } }
  return if dirty_threads.empty?

  BufferManager.say("Saving threads...") do |say_id|
    dirty_threads.each_with_index do |t, i|
      BufferManager.say "Saving modified thread #{i + 1} of #{dirty_threads.length}...", say_id
      t.save Index
    end
  end
end

#select(t = nil) ⇒ Object

open up a thread view window



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
# File 'lib/sup/modes/thread-index-mode.rb', line 77

def select t=nil
  t ||= cursor_thread or return

  Redwood::reporting_thread("load messages for thread-view-mode") do
    num = t.size
    message = "Loading #{num.pluralize 'message body'}..."
    BufferManager.say(message) do |sid|
      t.each_with_index do |(m, *o), i|
        next unless m
        BufferManager.say "#{message} (#{i}/#{num})", sid if t.size > 1
        m.load_from_source! 
      end
    end
    mode = ThreadViewMode.new t, @hidden_labels
    BufferManager.spawn t.subj, mode
    BufferManager.draw_screen
    mode.jump_to_first_open
    BufferManager.draw_screen # lame TODO: make this unnecessary
    ## the first draw_screen is needed before topline and botline
    ## are set, and the second to show the cursor having moved

    update_text_for_line curpos
    UpdateManager.relay self, :read, t
  end
end

#statusObject



433
434
435
436
437
438
439
# File 'lib/sup/modes/thread-index-mode.rb', line 433

def status
  if (l = lines) == 0
    "line 0 of 0"
  else
    "line #{curpos + 1} of #{l} #{dirty? ? '*modified*' : ''}"
  end
end

#toggle_archivedObject



227
228
229
230
231
# File 'lib/sup/modes/thread-index-mode.rb', line 227

def toggle_archived 
  t = cursor_thread or return
  actually_toggle_archived t
  update_text_for_line curpos
end

#toggle_deletedObject



289
290
291
292
# File 'lib/sup/modes/thread-index-mode.rb', line 289

def toggle_deleted
  t = cursor_thread or return
  multi_toggle_deleted [t]
end

#toggle_newObject



238
239
240
241
242
243
# File 'lib/sup/modes/thread-index-mode.rb', line 238

def toggle_new
  t = cursor_thread or return
  t.toggle_label :unread
  update_text_for_line curpos
  cursor_down
end

#toggle_spamObject



269
270
271
272
# File 'lib/sup/modes/thread-index-mode.rb', line 269

def toggle_spam
  t = cursor_thread or return
  multi_toggle_spam [t]
end

#toggle_starredObject



185
186
187
188
189
190
# File 'lib/sup/modes/thread-index-mode.rb', line 185

def toggle_starred 
  t = cursor_thread or return
  actually_toggle_starred t
  update_text_for_line curpos
  cursor_down
end

#toggle_taggedObject



342
343
344
345
346
347
# File 'lib/sup/modes/thread-index-mode.rb', line 342

def toggle_tagged
  t = cursor_thread or return
  @mutex.synchronize { @tags.toggle_tag_for t }
  update_text_for_line curpos
  cursor_down
end

#toggle_tagged_allObject



349
350
351
352
# File 'lib/sup/modes/thread-index-mode.rb', line 349

def toggle_tagged_all
  @mutex.synchronize { @threads.each { |t| @tags.toggle_tag_for t } }
  regen_text
end

#updateObject



153
154
155
156
157
158
159
160
161
162
# File 'lib/sup/modes/thread-index-mode.rb', line 153

def update
  @mutex.synchronize do
    ## let's see you do THIS in python
    @threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| t.date }.reverse
    @size_widgets = @threads.map { |t| size_widget_for_thread t }
    @size_widget_width = @size_widgets.max_of { |w| w.length }
  end

  regen_text
end