vy1 = ly / 5 * 4 # ステータスウィンドウとの境界
vy2 = ly / 5 * 1
vy3 = ly / 7 * 6
+ vy4 = ly / 5 * 2
self[name = :STATUS] = MaveStatusView.new({
:NAME => name, # ビューの名前
:NAME => name,
:VIEWS => self,
:GEOMETRY => [vx2, 0, lx - vx2, vy1 + 1],
+ :CLIP => @models[:CLIP],
:STATUS => @models[:STATUS],
})
: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])
self[:INC_SEARCH].tie(@models[:INC_SEARCH])
+ self[:FTEXT_SEARCH].tie(@models[:FTEXT_SEARCH])
+
@stack = [] # ビュー(ウィンドウ)の重なり
def @stack.activate(view) # 指定のビューを最上面にする
#-----------------------------------------------------------
#
+ # フォルダ群を渡す
+ #
+ def folders
+ @models[:FOLDERS]
+ end
+
+ #-----------------------------------------------------------
+ #
# ごみ箱フォルダの確認
#
def is_trash?(folder) # ごみ箱属性を持っているか?
#-----------------------------------------------------------
#
+ # 指定のメッセージ 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
: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
list_items
end
+ def jump(sq)
+ @topsq = sq
+ end
+
def list_items
@items.clear
wy = 1; @folder.each_sq(@topsq) {|sq, level|
state ? @folder.unred(sq) : @folder.red(sq)
}
end
-# @folders.dirty #### 届かない
+ @views.folders.dirty
end
def toggle_flag
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
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
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 の場合
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)
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)
@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|
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)
}
: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
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) # (カーソル/画面)移動
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) # (カーソル/画面)移動
}
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|
#===============================================================================
#
+# 全文検索クエリ入力ビュー
+#
+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