OSDN Git Service

Re-commit for last missing.
[mave/mave.git] / mave_curses.rb
index 74cc913..96cfa59 100644 (file)
@@ -25,6 +25,7 @@ class MaveViews < Hash
                vy1 = ly / 5 * 4                                                                                # ステータスウィンドウとの境界
                vy2 = ly / 5 * 1
                vy3 = ly / 7 * 6
+               vy4 = ly / 5 * 2
 
                self[name = :STATUS]            =       MaveStatusView.new({
                                                                                        :NAME           => name,                                                # ビューの名前
@@ -50,6 +51,7 @@ class MaveViews < Hash
                                                                                        :NAME           => name,
                                                                                        :VIEWS          => self,
                                                                                        :GEOMETRY       => [vx2, 0, lx - vx2, vy1 + 1],
+                                                                                       :CLIP           => @models[:CLIP],
                                                                                        :STATUS         => @models[:STATUS],
                                                                                })
 
@@ -67,6 +69,13 @@ class MaveViews < Hash
                                                                                        :STATUS         => @models[:STATUS],
                                                                                })
 
+               self[name = :FTEXT_SEARCH]      =       MaveFulltextSearchQueryView.new({
+                                                                                       :NAME           => name,
+                                                                                       :VIEWS          => self,
+                                                                                       :GEOMETRY       => [vx3, vy4, lx / 2, 4],
+                                                                                       :STATUS         => @models[:STATUS],
+                                                                               })
+
                self[:STATUS].tie(@models[:STATUS])                                             # ビューに表示対象モデルを割り当てる
 
                self[:FOLDERLIST].tie(@models[:FOLDERS])
@@ -81,6 +90,8 @@ class MaveViews < Hash
 
                self[:INC_SEARCH].tie(@models[:INC_SEARCH])
 
+               self[:FTEXT_SEARCH].tie(@models[:FTEXT_SEARCH])
+
                @stack = []                                                                                             # ビュー(ウィンドウ)の重なり
 
                def @stack.activate(view)                                                               # 指定のビューを最上面にする
@@ -181,6 +192,14 @@ class MaveViews < Hash
 
        #-----------------------------------------------------------
        #
+       #       フォルダ群を渡す
+       #
+       def folders
+               @models[:FOLDERS]
+       end
+
+       #-----------------------------------------------------------
+       #
        #       ごみ箱フォルダの確認
        #
        def is_trash?(folder)                                                                           # ごみ箱属性を持っているか?
@@ -199,6 +218,17 @@ class MaveViews < Hash
 
        #-----------------------------------------------------------
        #
+       #       指定のメッセージ ID を持つメールを返す
+       #
+       def get_mail_by_message_id(message_id)
+               @models[:FOLDERS].each {|folder|
+                       mail = folder.get_mail_by_message_id(message_id) and return(mail)
+               }
+               false
+       end
+
+       #-----------------------------------------------------------
+       #
        #       現在アクティブなビューオブジェクトを返す
        #
        def active
@@ -566,6 +596,9 @@ class MaveSummaryView < MaveSelectView
                        :mk_extract_attachments                 => method(:extract_attachments),
                        :mi_enclose_attachments                 => method(:enclose_attachments),
 
+                       :mk_fulltext_search                             => method(:fulltext_search),
+                       :mk_fulltext_search_all                 => method(:fulltext_search_all),
+
                        :mk_identify_mail                               => method(:identify_mail),
                })
        end
@@ -578,6 +611,10 @@ class MaveSummaryView < MaveSelectView
                list_items
        end
 
+       def jump(sq)
+               @topsq = sq
+       end
+
        def list_items
                @items.clear
                wy = 1; @folder.each_sq(@topsq) {|sq, level|
@@ -718,7 +755,7 @@ class MaveSummaryView < MaveSelectView
                                state ? @folder.unred(sq) : @folder.red(sq)
                        }
                end
-#              @folders.dirty                                                                                  #### 届かない
+               @views.folders.dirty
        end
 
        def toggle_flag
@@ -784,7 +821,7 @@ class MaveSummaryView < MaveSelectView
                        next if((skip -= 1) > -1)
                        yield(progress.to_s) if((progress += 1) % 10 == 0)
                        mail = @folder.get_mail(sq)
-                       found_sq = sq and break if(@folder.abstract_of_mail(sq, mail).downcase.index(str))      # mail) =~ /#{str}/i)
+                       found_sq = sq and break if(@folder.abstract_of_mail(sq, mail).downcase.index(str.downcase))     # mail) =~ /#{str}/i)
                }
                if(found_sq and !target_cursor(found_sq, :SQ, nil))             # (カーソル/画面)移動
                        @topsq = found_sq
@@ -800,7 +837,7 @@ class MaveSummaryView < MaveSelectView
                        next if((skip -= 1) > -1)
                        yield(progress.to_s) if((progress += 1) % 10 == 0)
                        mail = @folder.get_mail(sq)
-                       found_sq = sq and break if(@folder.abstract_of_mail(sq, mail).downcase.index(str))      # mail) =~ /#{str}/i)
+                       found_sq = sq and break if(@folder.abstract_of_mail(sq, mail).downcase.index(str.downcase))     # mail) =~ /#{str}/i)
                }
                if(found_sq and !target_cursor(found_sq, :SQ, nil))             # (カーソル/画面)移動
                        @topsq = found_sq
@@ -887,6 +924,7 @@ class MaveSummaryView < MaveSelectView
        def move_mail(copy = false)                                                                     # メールを移動
                if(@marks.size == 0)
                        callback = Proc.new {|chosen|
+                               @topsq = @folder.next_sq(@topsq) if(@items[@nth][:SQ] == @topsq)
                                flags = @folder.delete_mail(@items[@nth][:SQ]) unless(copy)     #### 先にファイルを消して問題ない?
                                chosen.add_mail(@items[@nth][:INSTANCE], flags)
                                @folder.move_related_directory(@items[@nth][:INSTANCE].unique_name, chosen)     #### copy の場合
@@ -895,6 +933,7 @@ class MaveSummaryView < MaveSelectView
                else
                        callback = Proc.new {|chosen|
                                @marks.keys.sort.each {|sq|
+                                       @topsq = @folder.next_sq(@topsq) if(sq == @topsq)
                                        flags = @folder.delete_mail(sq) unless(copy)
                                        chosen.add_mail(@marks[sq], flags)
                                        @folder.move_related_directory(@marks[sq].unique_name, chosen)
@@ -916,11 +955,13 @@ class MaveSummaryView < MaveSelectView
        def delete_mail                                                                                         # メールを削除
                trash_folder = @views.is_trash?(@folder) ? nil : @views.current_trash_folder
                if(@marks.size == 0)
+                       @topsq = @folder.next_sq(@topsq) if(@items[@nth][:SQ] == @topsq)
                        flags = @folder.delete_mail(@items[@nth][:SQ])
                        trash_folder.add_mail(@items[@nth][:INSTANCE], flags) if(trash_folder)
                        @folder.move_related_directory(@items[@nth][:INSTANCE].unique_name, trash_folder)
                else
                        @marks.keys.sort.each {|sq|
+                               @topsq = @folder.next_sq(@topsq) if(sq == @topsq)
                                flags = @folder.delete_mail(sq)
                                trash_folder.add_mail(@marks[sq], flags) if(trash_folder)
                                @folder.move_related_directory(@marks[sq].unique_name, trash_folder)
@@ -951,7 +992,30 @@ class MaveSummaryView < MaveSelectView
                @folder.enclose_attachments(@current)
        end
 
-       def identify_mail                                                                                       # メール情報を表示
+       #-------------------------------------- MaveSummaryView ----
+       #
+       #       全文検索ビュー、起動処理
+       #
+       def fulltext_search(all = false)
+               callback = Proc.new {|mail|
+                       mail.folder.red(mail.sq)
+                       @views.activate(:PREVIEW)
+                       @views[:PREVIEW].tagjump({:MESSAGE_ID => mail.message_id, :LINE => nil})        #### summary に戻りたい
+               }
+               (it = @views[:FTEXT_SEARCH]).set_title(it.name.to_s.downcase + (all ? ' all' : ''))
+               it.set_callback(callback)
+               it.set_target_folder(all ? :ALL : @folder)
+               @views.activate(:FTEXT_SEARCH)
+       end
+       def fulltext_search_all
+               fulltext_search(true)
+       end
+
+       #-------------------------------------- MaveSummaryView ----
+       #
+       #       メールの詳細情報を表示する
+       #
+       def identify_mail
                @status.log(['==== ' + _('Mail Identification') + ' ===='])
                if(@current)
                        @current.identify {|id|
@@ -990,11 +1054,14 @@ end
 class MavePreviewView < MaveBaseView
 
        def initialize(params)
-               @status = params[:STATUS]                                                               # 関連モデル
+               @clip = params[:CLIP]                                                                   # 関連モデル
+               @status = params[:STATUS]
 
                @topline = 0                                                                                    # 表示先頭のメール行番号
                @cur_pos = 15                                                                                   # カーソルの位置(オフセット)
 
+               @tagstack = []                                                                                  # タグジャンプからの戻り用スタック
+
                @separator = ''; (1..26).each {|n|
                        @separator << '====+====%d' % (n % 10)
                }
@@ -1026,6 +1093,17 @@ class MavePreviewView < MaveBaseView
 
                        :mk_extract_attachment                  => method(:extract_attachment),
 
+                       :mk_clip                                                => method(:clip),
+                       :mk_append_next_clip                    => method(:append_next_clip),
+#                      :mk_yank                                                => method(:yank),
+                       :mk_find_tag                                    => method(:find_tag),
+                       :mk_pop_tag_mark                                => method(:pop_tag_mark),
+
+                       :mk_shell_command                               => method(:shell_command),
+
+                       :mk_fulltext_search                             => method(:fulltext_search),
+                       :mk_fulltext_search_all                 => method(:fulltext_search_all),
+
                        :mk_identify_mail                               => method(:identify_mail),
                })
        end
@@ -1115,7 +1193,7 @@ class MavePreviewView < MaveBaseView
        def search_forward(str, skip = 0)
                found_n = nil; progress = skip
                @mail.body_each(@topline + @cur_pos + skip) {|line|             # 検索
-                       found_n = progress and break if(line.downcase.index(str))       # mail) =~ /#{str}/i)
+                       found_n = progress and break if(line.downcase.index(str.downcase))      # mail) =~ /#{str}/i)
                        yield(progress) if((progress += 1) % 10 == 0)
                } if(@mail)
                @topline += found_n if(found_n)                                                 # (カーソル/画面)移動
@@ -1124,7 +1202,7 @@ class MavePreviewView < MaveBaseView
        def search_backward(str, skip = 0)
                found_n = nil; progress = skip
                @mail.body_reverse_each(@topline + @cur_pos - skip) {|line|             # 検索
-                       found_n = progress and break if(line.downcase.index(str))       # mail) =~ /#{str}/i)
+                       found_n = progress and break if(line.downcase.index(str.downcase))      # mail) =~ /#{str}/i)
                        yield(progress) if((progress += 1) % 10 == 0)
                } if(@mail)
                @topline -= found_n if(found_n)                                                 # (カーソル/画面)移動
@@ -1157,7 +1235,123 @@ class MavePreviewView < MaveBaseView
                }
        end
 
-       def identify_mail                                                                                       # メール情報を表示
+       #-------------------------------------- MavePreviewView ----
+       #
+       #       メールの切り抜きを作る/貼る
+       #
+       def clip(append = false)
+               unless(@last_clip_message_id == @mail.message_id)               # clip 対象のメールが前回と異なる
+                       @clip.rotate unless(append)
+                       @clip.clip_header(@mail, @topline + @cur_pos)
+                       @last_clip_message_id = @mail.message_id
+               end
+               @clip.clip_body(@mail, @topline + @cur_pos)
+               nekst
+       end
+
+       def append_next_clip
+               clip(true)
+       end
+
+#      def yank
+#              @clip.open {|fh|                                                                                # PseudoMail をオープンして、書き戻す ※未実装
+#                      fh.each {|line|
+#                              @status.log([_('yank: %s'), line.chomp.decode_mh])
+#                      }
+#              }
+#      end
+
+       #-------------------------------------- MavePreviewView ----
+       #
+       #       カーソル行のシェルコマンドの実行結果をメール化する
+       #
+       def shell_command
+               command = @mail[@topline + @cur_pos]
+               command.gsub!(/%%self_filename%%/, @mail.path)
+               folder = @mail.folder
+               IO.popen(command) {|stdout|
+                       results = MaveMail.new({:FILE => stdout})                       # ヘッダを MaveMail に解析させる
+                       it = results.header['X-mave-store-folder'] and folder = (it != '%%drop%%') ? @views.folders.open_folder(it) : false
+                       folder.create_mail_shell_command(results.header, results.heads, stdout) if(folder)
+               }
+               folder  ? @status.log([_('Shell command was executed. Result was stored in the [%s] folder.'), folder.name]) \
+                               : @status.log([_('Shell command was executed. Result was dropped.')])
+       end
+
+       #-------------------------------------- MavePreviewView ----
+       #
+       #       タグジャンプを行う
+       #
+       def tagjump(to, from = nil)
+               if(mail = @views.get_mail_by_message_id(to[:MESSAGE_ID]))
+                       @tagstack << from if(from)                                                              # 現地点をタグスタックに積む
+                       @views[:SUMMARY].unmark_all
+                       @views[:SUMMARY].tie(mail.folder)
+                       mail.folder.unfold_parents(mail.sq)                                             # 対象メールの折りたたみ状態を解除
+                       @views[:SUMMARY].jump(mail.sq)                                                  # タグジャンプ
+                       @views[:SUMMARY].list_items
+                       @views[:SUMMARY].target_cursor(nil)
+                       if(it = to[:LINE])
+                               @topline = it.to_i - @cur_pos - 1
+                               (0..@topline).each {|n| @mail[n] }                                      #### なぜか、事前になめる必要あり
+                       end
+               else
+                       @status.log([_('Tag not found.')])
+               end
+       end
+
+       def find_tag
+               filename = message_id = sup = nil
+               [0, 1, -1, 2, -2, 3, -3, 4, -4].each {|n|                               # ±4 行の範囲のタグを探す
+                       tag = @mail[@topline + @cur_pos + n]
+                       tag =~ %r|file://(/\S+)\s+(.*)| and filename = $1 and sup = $2 and break
+                       tag =~ /(<[^>]+>)\s+(.*)/ and message_id = $1 and sup = $2 and break
+                       tag =~ %r|(https?://\S+)| and `opera #{$1}` and return  # とりあえず
+               }
+               filename and begin
+                       mailfile = File.new(filename) rescue raise('Target file was not found.')
+                       message_id = MaveMail.new({:FILE => mailfile}).message_id rescue raise('Target mail format was not correct.')
+               rescue
+                       @status.log([_($!.message)]); return
+               end
+               if(message_id)
+                       tagjump({:MESSAGE_ID => message_id, :LINE => (sup =~ /line:(\d+)/) ? $1.to_i : nil}, {:MESSAGE_ID => @mail.message_id, :LINE => @topline + @cur_pos + 1})
+               else
+                       @status.log([_('Tag was not found.')])
+               end
+       end
+
+       def pop_tag_mark
+               if(tag = @tagstack.pop)
+                       tagjump(tag)
+               else
+                       @status.log([_('Tagstack was empty.')])
+               end
+       end
+
+       #-------------------------------------- MavePreviewView ----
+       #
+       #       全文検索ビュー、起動処理
+       #
+       def fulltext_search(all = false)
+               callback = Proc.new {|mail|
+                       mail.folder.red(mail.sq)
+                       @views[:PREVIEW].tagjump({:MESSAGE_ID => mail.message_id, :LINE => nil}, {:MESSAGE_ID => @mail.message_id, :LINE => @topline + @cur_pos + 1})
+               }
+               (it = @views[:FTEXT_SEARCH]).set_title(it.name.to_s.downcase + (all ? ' all' : ''))
+               it.set_callback(callback)
+               it.set_target_folder(all ? :ALL : @mail.folder)
+               @views.activate(:FTEXT_SEARCH)
+       end
+       def fulltext_search_all
+               fulltext_search(true)
+       end
+
+       #-------------------------------------- MavePreviewView ----
+       #
+       #       メールの詳細情報を表示する
+       #
+       def identify_mail
                @status.log(['==== ' + _('Mail Identification') + ' ===='])
                if(@mail)
                        @mail.identify {|id|
@@ -1404,6 +1598,71 @@ end
 
 #===============================================================================
 #
+#      全文検索クエリ入力ビュー
+#
+class MaveFulltextSearchQueryView < MaveTextBoxView
+
+       def initialize(params)
+               super
+
+               set_prompt(_('Fulltext-search: '))
+
+               @actions.update({
+               })
+       end
+
+       def tie(ftext_search_model)
+               @textbox = ftext_search_model                                                   # ビューにモデルを関連づける
+               @textbox.tie(self, :FTEXT_SEARCH) if(@textbox)                  # 表示担当モデルに自分を通知する
+       end
+
+       def set_callback(proc)
+               @callback = proc
+       end
+
+       def set_target_folder(folder)                                                           # 検索対象フォルダを受け取る
+               @target_folder = folder
+       end
+
+       def execute
+               sq = nil
+               begin
+                       @target_folder == :ALL and raise('not provided')
+                       @target_folder.methods.include?('fulltext_search') or raise('no method')
+                       folder = nil
+                       @target_folder.fulltext_search(@textbox.text) {|stdout|
+                               results = MaveMail.new({:FILE => stdout})               # ヘッダを MaveMail に解析させる
+                               it = results.header['X-mave-store-folder'] and folder = (it != '%%drop%%') ? @views.folders.open_folder(it) : false
+                               sq = folder.create_mail_shell_command(results.header, results.heads, stdout) if(folder)
+                       } or raise('disabled')
+                       folder  ? @status.log([_('Full-text search was executed. Result was stored in the [%s] folder.'), folder.name]) \
+                                       : @status.log([_('Full-text search was executed. Result was dropped.')])
+               rescue
+                       case($!.message)
+                       when('not provided');   @status.log([_('Function not provided yet.')])
+                       when('no method');              @status.log([_('The folder has no full-text search method.')])
+                       when('disabled');               @status.log([_('The full-text search method has disabled.')])
+                       else;                                   @status.log([_('Unexpected error occurred. reason=[%1$s]'), $!.message.split(/\r?\n/)[0]])
+                       end
+               end
+               @textbox.clear
+               @views.disable(:FTEXT_SEARCH)
+               @callback.call(folder.get_mail(sq)) if(sq)
+       end
+
+       def quit
+               @textbox.clear
+               @views.disable(:FTEXT_SEARCH)
+       end
+
+       def update
+               super
+               refresh
+       end
+end
+
+#===============================================================================
+#
 #      ステータスビュー
 #
 class MaveStatusView < MaveBaseView