register_keymap do |k|
k.add :load_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
+ k.add :cancel_search, "Cancel current search", :ctrl_g
k.add :reload, "Refresh view", '@'
k.add :toggle_archived, "Toggle archived status", 'a'
k.add :toggle_starred, "Star or unstar all messages in thread", '*'
k.add :forward, "Forward latest message in a thread", 'f'
k.add :toggle_tagged, "Tag/untag selected thread", 't'
k.add :toggle_tagged_all, "Tag/untag all threads", 'T'
- k.add :tag_matching, "Tag/untag all threads", 'g'
+ k.add :tag_matching, "Tag matching threads", 'g'
k.add :apply_to_tagged, "Apply next command to all tagged threads", ';'
+ k.add :join_threads, "Force tagged threads to be joined into the same thread", '#'
end
def initialize hidden_labels=[], load_thread_opts={}
@load_thread_opts = load_thread_opts
@hidden_labels = hidden_labels + LabelManager::HIDDEN_RESERVED_LABELS
@date_width = DATE_WIDTH
+
+ @interrupt_search = false
initialize_threads # defines @ts and @ts_mutex
update # defines @text and @lines
end
## open up a thread view window
- def select t=nil
+ def select t=nil, when_done=nil
t ||= cursor_thread or return
Redwood::reporting_thread("load messages for thread-view-mode") do
m.load_from_source!
end
end
- mode = ThreadViewMode.new t, @hidden_labels
+ mode = ThreadViewMode.new t, @hidden_labels, self
BufferManager.spawn t.subj, mode
BufferManager.draw_screen
- mode.jump_to_first_open
+ mode.jump_to_first_open true
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.first
+ when_done.call if when_done
end
end
def multi_select threads
threads.each { |t| select t }
end
+
+ ## this is called by thread-view-modes when the user wants to view
+ ## the next thread without going to index-mode. we update the cursor
+ ## as a convenience.
+ def launch_next_thread_after thread, &b
+ l = @lines[thread] or return
+ t = @mutex.synchronize do
+ if l < @threads.length - 1
+ set_cursor_pos l + 1 # move out of mutex?
+ @threads[l + 1]
+ end
+ end or return
+
+ select t, b
+ end
def handle_single_message_labeled_update sender, m
## no need to do anything different here; we don't differentiate
end
end
- def handle_read_update sender, m
+ def handle_simple_update sender, m
t = thread_containing(m) or return
l = @lines[t] or return
update_text_for_line l
end
- def handle_archived_update *a; handle_read_update(*a); end
-
- def handle_deleted_update sender, m
- t = thread_containing(m) or return
- hide_thread t
- regen_text
+ %w(read unread archived starred unstarred).each do |state|
+ define_method "handle_#{state}_update" do |*a|
+ handle_simple_update(*a)
+ end
end
## overwrite me!
BufferManager.draw_screen
end
- def handle_deleted_update sender, m
+ def handle_single_message_deleted_update sender, m
@ts_mutex.synchronize do
return unless @ts.contains? m
@ts.remove_id m.id
update
end
+ def handle_deleted_update sender, m
+ @ts_mutex.synchronize do
+ return unless @ts.contains? m
+ @ts.remove_thread_containing_id m.id
+ end
+ update
+ end
+
def handle_undeleted_update sender, m
add_or_unhide m
end
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
+ @threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| [t.date, t.first.id] }.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
+ def join_threads
+ ## this command has no non-tagged form. as a convenience, allow this
+ ## command to be applied to tagged threads without hitting ';'.
+ @tags.apply_to_tagged :join_threads
+ end
+
+ def multi_join_threads threads
+ @ts.join_threads threads or return
+ @tags.drop_all_tags # otherwise we have tag pointers to invalid threads!
+ update
+ end
+
def jump_to_next_new
n = @mutex.synchronize do
((curpos + 1) ... lines).find { |i| @threads[i].has_label? :unread } ||
## TODO: figure out @ts_mutex in this method
def load_n_threads n=LOAD_MORE_THREAD_NUM, opts={}
+ @interrupt_search = false
@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
+ BufferManager.say "Loaded #{i.pluralize 'thread'} (use ^G to cancel)...", @mbid
update
BufferManager.draw_screen
last_update = Time.now
end
+ break if @interrupt_search
end
@ts.threads.each { |th| th.labels.each { |l| LabelManager << l } }
end
end
+ def cancel_search
+ @interrupt_search = true
+ end
+
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]
+
+ cancelled = @interrupt_search?" (search cancelled by user)":""
+
if num > 0
- BufferManager.flash "Found #{num.pluralize 'thread'}."
+ BufferManager.flash "Found #{num.pluralize 'thread'}#{cancelled}."
else
BufferManager.flash "No matches."
end
protected
def add_or_unhide m
- if @hidden_threads[m]
- @hidden_threads.delete m
- ## now it will re-appear when #update is called
- else
- Redwood::log "#{self}: adding: #{m}"
- @ts_mutex.synchronize do
- return unless is_relevant?(m) || @ts.is_relevant?(m)
+ @ts_mutex.synchronize do
+ if (is_relevant?(m) || @ts.is_relevant?(m)) && !@ts.contains?(m)
@ts.load_thread_for_message m
end
+
+ @hidden_threads.delete @ts.thread_for(m)
end
update