OSDN Git Service

ログの読込中にカーソル表示を変えるように変更。
[winbottle/winbottle.git] / bottleclient / LogForm.pas
index f16ef7c..781ed18 100755 (executable)
@@ -6,7 +6,9 @@ uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   ComCtrls, ToolWin, StdCtrls, ExtCtrls, SsParser, BottleDef, Menus,
   Clipbrd, Logs, ShellAPI, Commctrl, DirectSstp, Contnrs, StrUtils,
-  TalkShowFrame, SppList;
+  TalkShowFrame, SppList, HtmlOutputConfig, HtmlOutputProgress,
+  SearchLog, IniFiles, BRegExp, RegexUtils,
+  DateUtils, SAXComps, SAX, BSAX, SAXMS;
 
 type
   // \83\8d\83O\82Ì\95Û\91\95û\96@
@@ -71,6 +73,16 @@ type
     mnSendEditor: TMenuItem;
     timScrollTimer: TTimer;
     mnChangeTabName: TMenuItem;
+    N1: TMenuItem;
+    N2: TMenuItem;
+    mnDeleteLogItem: TMenuItem;
+    mnTabSaveXMLLog: TMenuItem;
+    mnSaveHTML: TMenuItem;
+    mnPopupCopyGhost: TMenuItem;
+    PopupMenuAction: TPopupMenu;
+    mnTestAction: TMenuItem;
+    mnSelAction: TMenuItem;
+    mnAllAction: TMenuItem;
     procedure tbtnClearClick(Sender: TObject);
     procedure FormCreate(Sender: TObject);
     procedure lvwLogChange(Sender: TObject; Item: TListItem;
@@ -119,6 +131,12 @@ type
     procedure lvwLogStartDrag(Sender: TObject;
       var DragObject: TDragObject);
     procedure lvwLogEndDrag(Sender, Target: TObject; X, Y: Integer);
+    procedure mnTabSaveXMLLogClick(Sender: TObject);
+    procedure mnSaveHTMLClick(Sender: TObject);
+    procedure mnPopupCopyGhostClick(Sender: TObject);
+    procedure mnTestActionClick(Sender: TObject);
+    procedure mnSelActionClick(Sender: TObject);
+    procedure mnAllActionClick(Sender: TObject);
   private
     { Private \90é\8c¾ }
     FLastScript: String; //\83X\83N\83\8a\83v\83g\8dÄ\95`\89æ\97}\90§\97p
@@ -131,24 +149,56 @@ type
     FLVScrollDir: TLVScrollDir; // \83X\83N\83\8d\81[\83\8b\95û\8cü
     FLVDragDest: integer;    //\83h\83\8d\83b\83v\82·\82é\88Ê\92u(\82·\82®\89º\82É\82­\82é\83A\83C\83e\83\80\82ÌIndex)
     //
+    // SAX\8aÖ\98A
+    FsReadMess, FsReadElm: boolean;
+    FsDate, FsChannel, FsScript, FsVotes, FsAgrees, FsGhost, FsMid: string;
+    FsNowNode: string;
+    FsLoadFailureMessage: string;
+    FsList: TBottleLogList;
+    //
     procedure UpdateScript(const Script: String);
     procedure UpdateScriptConversationColor(const Script: String);
     procedure UpdateScriptScript(const Script: String);
     procedure mnURLClick(Sender: TObject);
-    procedure ExtractURLs(Script: String; Result: TStrings);
+    function ExtractURLs(Script: String; Urls: TStrings; Labels: TStrings): Boolean;
     function GetDefaultFileName(const Name: String; const Ext: String): String;
     function BottleLogTitled(const LogName: String): TBottleLogList;
     procedure DrawSingleLineScript(LogItem: TLogItem; Rect: TRect;
       Item: TListItem);
     procedure PreviewStyleChange;
     procedure DrawListViewDragBorder(const Rect: TRect);
+    function DoSaveLogXML(Log: TBottleLogList): integer;
+    procedure DoCloseTab(const Index: integer; FCheck: boolean);
+    function DoSearchLog(Condition: TSearchCond): TBottleLogList;
+    procedure SearchLogIndivisual(Condition: TSearchCond;
+      LogList, Result: TBottleLogList; UntilIndex: integer = -1);
+    function CheckLogSave(const Index: integer): integer;
+    procedure actLvwLog(FAction: boolean);
+    procedure LoadSAXItemReset;
+    procedure LoadSAXLoader(FileName: String);
+    procedure SAXErrorHandler1Error(Sender: TObject;
+      const Error: ISAXParseError);
+    procedure SAXErrorHandler1FatalError(Sender: TObject;
+      const Error: ISAXParseError);
+    procedure SAXErrorHandler1Warning(Sender: TObject;
+      const Error: ISAXParseError);
+    procedure SAXContentHandler1Characters(Sender: TObject;
+      const PCh: WideString);
+    procedure SAXContentHandler1EndElement(Sender: TObject;
+      const NamespaceURI, LocalName, QName: WideString);
+    procedure SAXContentHandler1StartElement(Sender: TObject;
+      const NamespaceURI, LocalName, QName: WideString;
+      const Atts: IAttributes);
+    procedure DoDOMLoad;
+    procedure DoSAXLoad;
   protected
     procedure CreateParams(var Params: TCreateParams); override;
   public
     { Public \90é\8c¾ }
     function SelectedBottleLog: TBottleLogList;
     property BottleLogList: TObjectList read FBottleLogList;
-    procedure AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String);
+    procedure AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String;
+      const LogTime: TDateTime; const Vote, Agree: integer);
     procedure AddCurrentSystemLog(const LogName, MessageString: String);
     procedure VoteLog(const MID: String; const Vote: integer);
     procedure AgreeLog(const MID: String; const Agree: integer);
@@ -157,9 +207,12 @@ type
     procedure LogLoaded(Sender: TObject);
     procedure LogLoadFailure(Sender: TObject; const Message: String);
     procedure LogLoadWork(Sender: TObject);
+    procedure HTMLOutputWork(Sender: TObject; const Count: integer;
+      var Canceled: boolean);
     procedure UpdateTab;
     procedure UpdateWindow;
     procedure SelAndFocusMessage(const MID: String);
+    function CheckLog(Sender: TObject): integer;
   end;
 
   TBottleLogDragObject = class(TDragControlObjectEx)
@@ -192,16 +245,18 @@ const
 
 implementation
 
-uses MainForm;
+uses MainForm, SAXAdapters;
 
 {$R *.DFM}
 
 { TfrmLog }
 
-procedure TfrmLog.AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String);
+procedure TfrmLog.AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String;
+  const LogTime: TDateTime; const Vote, Agree: integer);
 var Sel: integer;
 begin
-  BottleLogTitled(LogName).AddScriptLog(Script, Channel, MID, Ghost);
+  BottleLogTitled(LogName).AddScriptLog(Script, Channel, MID, Ghost, LogTime, Vote, Agree);
+  BottleLogTitled(LogName).LogModified := true; // \82±\82Ì\83\8a\83X\83g\82Í\95Ï\8dX\82³\82ê\82½
   if SelectedBottleLog <> BottleLogTitled(LogName) then Exit;
   lvwLog.OnChange := nil; //\83C\83x\83\93\83g\94­\90¶(\82¢\82ë\82¢\82ë\8dÄ\95`\89æ\82ª\8bN\82«\82é)\82Ì\97}\90§
   if lvwLog.Selected <> nil then Sel := lvwLog.Selected.Index else Sel := -1;
@@ -239,11 +294,7 @@ end;
 procedure TfrmLog.tbtnClearClick(Sender: TObject);
 begin
   if SelectedBottleLog = nil then Exit;
-  FBottleLogList.Delete(tabBottleLog.TabIndex);
-  tabBottleLog.TabIndex := 0;
-  UpdateTab;
-  UpdateWindow;
-  lvwLogChange(Self, nil, ctState);
+  DoCloseTab(tabBottleLog.TabIndex, true);
 end;
 
 procedure TfrmLog.FormCreate(Sender: TObject);
@@ -307,59 +358,57 @@ end;
 
 procedure TfrmLog.lvwLogChange(Sender: TObject; Item: TListItem;
   Change: TItemChange);
-var Script: String;
+var Script, Text: String;
     Log: TLogItem;
+    Selected, IsNormalBottle: boolean;
 begin
+  Selected := false;
+  IsNormalBottle := false;
   if SelectedBottleLog <> nil then begin
-    StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
     if Change = ctState then begin
       Script := '';
       if lvwLog.Selected <> nil then begin
+        Selected := true;
+        StatusBar.Panels[0].Text := Format('%d/%d\8c\8f', [lvwLog.Selected.Index+1,
+          SelectedBottleLog.Count]);
         Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
-        if (Log.LogType = ltBottle) and not frmSender.Connecting then begin
+//        if (Log.LogType = ltBottle) and not frmSender.Connecting then begin
+        if Log.LogType = ltBottle then begin
+          IsNormalBottle := true;
           Script := Log.Script;
-          frmSender.actVoteMessage.Enabled := true;
-          frmSender.actAgreeMessage.Enabled := true;
-          frmSender.actSendEditor.Enabled := true;
-          frmSender.actInsertCue.Enabled := true;
-          mnPopUpCopyScript.Enabled := true;
-          StatusBar.Panels[1].Text := Format('%d\83o\83C\83g - \83_\83u\83\8b\83N\83\8a\83b\83N\82Å\8dÄ\90¶', [Length(Log.Script)]);
+          Text := Format('%d\83o\83C\83g/%d\95b - \83_\83u\83\8b\83N\83\8a\83b\83N\82Å\8dÄ\90¶',
+            [Length(Log.Script), frmSender.SsPlayTime.PlayTime(Log.Script) div 1000]);
+          StatusBar.Panels[1].Text := Text;
           if Pref.LogWindowPreviewStyle = psImageConversation then
             TalkShowFrame.View(Log)
           else
             UpdateScript(Script);
         end else begin
-          frmSender.actVoteMessage.Enabled := false;
-          frmSender.actAgreeMessage.Enabled := false;
-          frmSender.actSendEditor.Enabled := false;
-          frmSender.actInsertCue.Enabled := false;
-          mnPopUpCopyScript.Enabled := false;
           StatusBar.Panels[1].Text := '';
           UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\82ð\83N\83\8a\83A
         end;
       end else begin
-        frmSender.actVoteMessage.Enabled := false;
-        frmSender.actAgreeMessage.Enabled := false;
-        frmSender.actSendEditor.Enabled := false;
-        frmSender.actInsertCue.Enabled := false;
-        mnPopUpCopyScript.Enabled := false;
+        StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
         StatusBar.Panels[1].Text := '';
         UpdateScript(Script); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
       end;
     end;
     tbtnSaveLog.Enabled := lvwLog.Items.Count > 0;
   end else begin
-    frmSender.actVoteMessage.Enabled := false;
-    frmSender.actAgreeMessage.Enabled := false;
-    frmSender.actSendEditor.Enabled := false;
-    frmSender.actInsertCue.Enabled := false;
-    mnPopUpCopyScript.Enabled := false;
     StatusBar.Panels[0].Text := '';
     UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
   end;
+  frmSender.actVoteMessage.Enabled := Selected and IsNormalBottle;
+  frmSender.actAgreeMessage.Enabled := Selected and IsNormalBottle;
+  frmSender.actSendToEditor.Enabled := Selected and IsNormalBottle;
+  frmSender.actInsertCue.Enabled := Selected;
+  frmSender.actDeleteLogItem.Enabled := Selected;
+  mnPopUpCopyScript.Enabled := Selected and IsNormalBottle;
+  mnPopupCopyGhost.Enabled := Selected and IsNormalBottle;
+  mnSelAction.Enabled := Selected and IsNormalBottle;
 end;
 
-procedure TfrmLog.lvwLogDblClick(Sender: TObject);
+procedure TfrmLog.actLvwLog(FAction: boolean);
 var Script, ErrorMes: String;
     Log, CueItem: TLogItem;
     Res: integer;
@@ -370,7 +419,14 @@ begin
   if Log = nil then Exit;
   if Log.LogType <> ltBottle then
     Exit;
-  Script := frmSender.ScriptTransForSSTP(Log.Script, ErrorMes);
+  //\92P\91Ì\83A\83N\83V\83\87\83\93\82ª\97L\8cø\82Å\82 \82ê\82Î\83X\83N\83\8a\83v\83g\82Ì\95Ï\8a·\82µ\82È\82¢
+  if FAction then
+  begin
+    Script :=  Log.Script;
+    ErrorMes := '';
+  end else
+    Script := frmSender.ScriptTransForSSTP(Log.Script, ErrorMes);
+
   if ErrorMes <> '' then
   begin
     Res := MessageDlg('\96â\91è\82Ì\82 \82é\83X\83N\83\8a\83v\83g\82Å\82·\81B\8dÄ\90\82Å\82«\82Ü\82¹\82ñ\81B'#13#10+
@@ -384,13 +440,31 @@ begin
 
   CueItem := TLogItem.Create(Log);
   try
-    CueItem.Script := Script;
-    frmSender.BottleSstp.Unshift(CueItem);
+    //\83A\83N\83V\83\87\83\93\82ª\97L\8cø\82Å\82 \82ê\82Î\92Ê\8fí\8f\88\97\9d\8fÈ\97ª
+    if FAction then
+    begin
+      //\8c^\95Ï\8a·\82Æ\8eó\90M
+      frmSender.BottleCnv(CueItem);
+      CueItem.FreeInstance;
+    end else
+    begin
+      //\83`\83\83\83\93\83l\83\8b\83S\81[\83X\83g\91Î\8dô
+      if CueItem.Ghost = '' then
+        if ChannelList.Channel[CueItem.Channel] <> nil then
+          CueItem.Ghost := ChannelList.Channel[CueItem.Channel].Ghost;
+      CueItem.Script := Script;
+      frmSender.BottleSstp.Unshift(CueItem);
+    end;
   except
     CueItem.Free;
   end;
 end;
 
+procedure TfrmLog.lvwLogDblClick(Sender: TObject);
+begin
+  actLvwLog(false);
+end;
+
 procedure TfrmLog.UpdateScriptConversationColor(const Script: String);
 var i: integer;
     scr: String;
@@ -530,7 +604,7 @@ procedure TfrmLog.mnSaveLogClick(Sender: TObject);
 begin
   if SelectedBottleLog = nil then Exit;
   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
-  SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
+  SaveDialog.InitialDir := Pref.LogDir;
   SaveDialog.DefaultExt := 'log';
   SaveDialog.FilterIndex := 1;
   if SaveDialog.Execute then
@@ -541,7 +615,7 @@ procedure TfrmLog.mnSaveLogChannelClick(Sender: TObject);
 begin
   if SelectedBottleLog = nil then Exit;
   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
-  SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
+  SaveDialog.InitialDir := Pref.LogDir;
   SaveDialog.DefaultExt := 'log';
   SaveDialog.FilterIndex := 1;
   if SaveDialog.Execute then
@@ -552,7 +626,7 @@ procedure TfrmLog.mnSaveLogScriptClick(Sender: TObject);
 begin
   if SelectedBottleLog = nil then Exit;
   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.txt');
-  SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
+  SaveDialog.InitialDir := Pref.LogDir;
   SaveDialog.DefaultExt := 'txt';
   SaveDialog.FilterIndex := 2;
   if SaveDialog.Execute then
@@ -562,12 +636,7 @@ end;
 procedure TfrmLog.mnSaveLogXMLClick(Sender: TObject);
 begin
   if SelectedBottleLog = nil then Exit;
-  SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.xml');
-  SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
-  SaveDialog.DefaultExt := 'xml';
-  SaveDialog.FilterIndex := 3;
-  if SaveDialog.Execute then
-    SelectedBottleLog.SaveToXmlFile(SaveDialog.FileName);
+  DoSaveLogXML(SelectedBottleLog);
 end;
 
 procedure TfrmLog.lvwLogData(Sender: TObject; Item: TListItem);
@@ -612,13 +681,8 @@ end;
 procedure TfrmLog.UpdateWindow;
 var EnabledFlag: boolean;
 begin
-  if true then begin // ColorScript
-    if lvwLog.Color <> Pref.BgColor then lvwLog.Color := Pref.BgColor;
-    if lvwLog.Font.Color <> Pref.TalkColorH then lvwLog.Font.Color := Pref.TalkColorH;
-  end else begin
-    if lvwLog.Color <> clWindow then lvwLog.Color := clWindow;
-    if lvwLog.Font.Color <> clWindowText then lvwLog.Font.Color := clWindowText;
-  end;
+  lvwLog.Color := Pref.BgColor;
+  lvwLog.Font.Color := Pref.TextColor;
   if SelectedBottleLog <> nil then begin
     Caption := '\83\8d\83O - ' + SelectedBottleLog.Title;
     StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
@@ -642,6 +706,9 @@ procedure TfrmLog.PopupMenuListViewPopup(Sender: TObject);
 var Log: TLogItem;
     Child: TMenuItem;
     Urls: TStringList;
+    Labels: TStringList;
+    ProcessedUrl: String;
+    ProcessedLabel: String;
     i: integer;
 begin
   for i := mnJumpURL.Count-1 downto 0 do begin
@@ -652,12 +719,19 @@ begin
   Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
   if Log = nil then Exit;
   Urls := TStringList.Create;
+  Labels := TStringList.Create;
   try
-    ExtractURLs(Log.Script, Urls);
+    ExtractURLs(Log.Script, Urls, Labels);
     for i := 0 to Urls.Count-1 do begin
       Child := TMenuItem.Create(Self);
       with Child do begin
-        Caption := Format('(&%d) %s', [i+1, StringReplace(Urls[i], '&', '&&', [rfReplaceAll])]);
+        ProcessedUrl := StringReplace(Urls[i], '&', '&&', [rfReplaceAll]);
+        ProcessedLabel := StringReplace(Labels[i], '&', '&&', [rfReplaceAll]);
+        if Length(ProcessedLabel) > 0 then begin
+          Caption := Format('[%s] %s (&%d)', [ProcessedLabel, ProcessedUrl, i+1]);
+        end else begin
+          Caption := Format('%s (&%d)', [ProcessedUrl, i+1]);
+        end;
         Tag := i;
         OnClick := mnURLClick;
         AutoHotkeys := maManual;
@@ -667,52 +741,75 @@ begin
     mnJumpURL.Enabled := Urls.Count > 0;
   finally
     Urls.Free;
+    Labels.Free;
   end;
 end;
 
 procedure TfrmLog.mnURLClick(Sender: TObject);
 var LogItem: TLogItem;
-    URL: String;
+    URL: string;
     Urls: TStringList;
+
 begin
   if (lvwLog.Selected = nil) or (SelectedBottleLog = nil) then Exit;
   LogItem := SelectedBottleLog[lvwLog.Selected.Index] as TLogItem;
   Urls := TStringList.Create;
   try
-    ExtractURLs(LogItem.Script, Urls);
+    ExtractURLs(LogItem.Script, Urls, nil);
     URL := Urls[(Sender as TMenuItem).Tag];
-    ShellExecute(Handle, 'open', PChar(URL), nil, nil, SW_SHOW);
+    OpenBrowser(URL);
   finally
     Urls.Free;
   end;
 end;
 
-procedure TfrmLog.ExtractURLs(Script: String; Result: TStrings);
-var i, u, j: integer;
+function TfrmLog.ExtractURLs(Script: String; Urls: TStrings; Labels: TStrings): Boolean;
+var i, u, j, count: integer;
     s: String;
 begin
-  Result.Clear;
+  count := 0;
   SsParser.InputString := Script;
+  SsParser.LeaveEscape := true;
   for i := 0 to SsParser.Count-1 do begin
-    if (SsParser.Match(SsParser[i], '\URL%b') > 0) then begin
+    if (SsParser.Match(SsParser[i], '\URL%b') > 0)
+    and (SsParser.MarkUpType[i] = mtTag) then
+    begin
       for u := 7 downto 1 do begin
         if (SsParser.Match(SsParser[i],
             '\URL%b'+StringReplace(StringOfChar('-', u*2),
             '-', '%b', [rfReplaceAll]))) > 0 then begin
           for j := 1 to u do begin
             s := SsParser.GetParam(SsParser[i], j*2);
-            if Pos('http://', s) > 0 then Result.Add(s);
+            if Pos('http://', s) > 0 then begin
+              if Urls <> nil then Urls.Add(s);
+              count := count + 1;
+            end;
+            if Labels <> nil then begin
+              s := SsParser.GetParam(SsParser[i], j*2+1);
+              Labels.Add(s);
+            end;
           end;
           Break;
         end;
       end;
-      if SsParser.Match(SsParser[i], '\URL%b%b') = 0 then begin //\8aÈ\88Õ\94ÅURL\95Ï\8a·
+      if SsParser.Match(SsParser[i], '\URL%b%b') = 0 then begin
         //\8aÈ\88Õ\8c`\8e®\URL\83^\83O\95Ï\8a·
         s := SsParser.GetParam(SsParser[i], 1);
-        if Pos('http://', s) > 0 then Result.Add(s);
+        if Pos('http://', s) > 0 then begin
+          if Urls <> nil then Urls.Add(s);
+          count := count + 1;
+        end
       end;
     end;
   end;
+
+  //\83\89\83x\83\8b\82Ì\90\94\82ðURL\82Ì\90\94\82É\82 \82í\82¹\82é - \82¢\82¿\82¢\82¿\94»\92è\82µ\82È\82­\82Ä\82à\82¢\82¢\82æ\82¤\82É
+  if Urls <> nil then begin
+    if Labels <> nil then begin
+      while Urls.Count > Labels.Count do Labels.Add('');
+    end;
+  end;
+  Result := count > 0;
 end;
 
 procedure TfrmLog.SelAndFocusMessage(const MID: String);
@@ -829,11 +926,15 @@ end;
 
 procedure TfrmLog.tabBottleLogChange(Sender: TObject);
 begin
+  // StatusBar\82Ì\8c\8f\90\94\95\\8e¦\82âListView.Items.Count\82ð\8dX\90V\82·\82é
   UpdateWindow;
-  if SelectedBottleLog.SelectedIndex >= 0 then begin
-    lvwLog.Items[SelectedBottleLog.SelectedIndex].Selected := true;
-    if lvwLog.Focused then lvwLog.Selected.Focused := true;
-  end;
+  // \83A\83C\83e\83\80\82Ì\91I\91ð\8fó\91Ô\82ð\95\9c\8bA\82·\82é
+  with SelectedBottleLog do
+    if (SelectedIndex >= 0) and (Count > SelectedIndex) then
+    begin
+      lvwLog.Items[SelectedIndex].Selected := true;
+      if lvwLog.Focused then lvwLog.Selected.Focused := true;
+    end;
   lvwLogChange(Self, nil, ctState);
 end;
 
@@ -916,62 +1017,77 @@ begin
 end;
 
 procedure TfrmLog.mnCloseTabClick(Sender: TObject);
-var PrevSelection: TBottleLogList; // \95Â\82\82½\82Æ\82«\83^\83u\82ª\82¸\82ê\82È\82¢\82æ\82¤\82É\82·\82é\8f\88\97\9d\97p
-    i: integer;
 begin
-  PrevSelection := SelectedBottleLog;
-  FBottleLogList.Delete(tabBottleLog.Tag);
-  UpdateTab;
-  // \83^\83u\82¸\82ê\96h\8e~\8f\88\97\9d
-  for i := 0 to FBottleLogList.Count-1 do
-    if FBottleLogList[i] = PrevSelection then
-      tabBottleLog.TabIndex := i;
-  UpdateWindow;
-  lvwLogChange(Self, nil, ctState);
+  DoCloseTab(tabBottleLog.Tag, true);
 end;
 
 procedure TfrmLog.tbtnFindBottleClick(Sender: TObject);
-var Query: String;
-    ResultLog: TBottleLogList;
-    Item1, Item2: TLogItem;
-    i, matched: integer;
+var ResultLog: TBottleLogList;
+    Cond: TSearchCond;
+    i: integer;
+    CList, GList: THashedStringList;
 begin
-  if SelectedBottleLog = nil then Exit;
-  if SelectedBottleLog.Count = 0 then begin
-    ShowMessage('\8c\9f\8dõ\91Î\8fÛ\82ª\8bó\82Å\82·\81B');
-    Exit;
-  end;
-  Query := '';
-  matched := 0;
-  if InputQuery('\83X\83N\83\8a\83v\83g\96{\95\82ð\8c\9f\8dõ', '\8c\9f\8dõ\95\8e\9a\97ñ', Query) then begin
-    if Query = '' then Exit;
-    ResultLog := TBottleLogList.Create('\8c\9f\8dõ\8c\8b\89Ê');
-    for i := 0 to SelectedBottleLog.Count-1 do begin
-      Item1 := SelectedBottleLog.Items[i] as TLogItem;
-      if AnsiContainsText(Item1.Script, Query) and (Item1.LogType = ltBottle) then begin
-        matched := matched + 1;
-        Item2 := TLogItem.Create(ltBottle, Item1.MID, Item1.Channel,
-          Item1.Script, Item1.Ghost, Item1.LogTime);
-        Item2.State := lsOpened;
-        Item2.Votes := Item1.Votes;
-        Item2.Agrees := Item1.Agrees;
-        ResultLog.Add(Item2);
+  Application.CreateForm(TfrmSearchLog, frmSearchLog);
+  Cond := TSearchCond.Create(nil);
+  try
+    try
+      with frmSearchLog do
+      begin
+        // \8c»\8dÝ\83\8d\83O\82É\82 \82é\83S\81[\83X\83g\82Æ\83`\83\83\83\93\83l\83\8b\82Ì\83\8a\83X\83g\82ð\8eæ\93¾
+        // \8fd\82½\82¢\82©\82à??
+        CList := THashedStringList.Create;
+        GList := THashedStringList.Create;
+        try
+          for i := 0 to BottleLogList.Count-1 do
+          begin
+            with BottleLogList[i] as TBottleLogList do
+            begin
+              ExtractUniqueChannels(CList);
+              ExtractUniqueGhosts(GList);
+            end;
+          end;
+          CList.Sort;
+          GList.Sort;
+          ChannelList := CList;
+          GhostList   := GList;
+        finally
+          CList.Free;
+          GList.Free;
+        end;
+        if not Execute then
+          Exit
+        else
+          Cond.Assign(Condition);
       end;
+    finally
+      frmSearchLog.Release;
     end;
-    if matched = 0 then
-      ResultLog.AddSystemLog('\8c©\82Â\82©\82è\82Ü\82¹\82ñ\82Å\82µ\82½');
+    // \8c\9f\8dõ\8eÀ\8ds
+    ResultLog := DoSearchLog(Cond);
+    // \90V\83^\83u\82ð\8dì\90¬\82µ\82Ä\89æ\96Ê\8dX\90V
     BottleLogList.Add(ResultLog);
     UpdateTab;
     tabBottleLog.TabIndex := BottleLogList.Count-1;
     UpdateWindow;
+  finally
+    Cond.Free;
   end;
 end;
 
 procedure TfrmLog.tbtnOpenLogClick(Sender: TObject);
+begin
+  if Pref.UseSAXReader then
+    DoSAXLoad
+  else
+    DoDOMLoad;
+end;
+
+procedure TfrmLog.DoDOMLoad;
 var BottleLog: TBottleLogList;
     i, Index: integer;
 begin
   Index := -1;
+  OpenDialog.InitialDir := Pref.LogDir;
   if OpenDialog.Execute then begin
     for i := 0 to OpenDialog.Files.Count-1 do begin
       BottleLog := TBottleLogList.Create(ExtractFileName(OpenDialog.Files[i]));
@@ -998,6 +1114,7 @@ function TfrmLog.GetDefaultFileName(const Name, Ext: String): String;
 begin
   Result := StringReplace(Name, '/', '', [rfReplaceAll]);
   Result := StringReplace(Result, ' ', '', [rfReplaceAll]);
+  Result := SafeFileName(Result);
   Result := ChangeFileExt(Result, Ext);
 end;
 
@@ -1033,13 +1150,20 @@ procedure TfrmLog.tabBottleLogMouseDown(Sender: TObject;
   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
 var Index: integer;
 begin
-  with tabBottleLog do begin
-    Index := IndexOfTabAt(X, Y);
-    if Index = -1 then Exit; //\83^\83u\82ª\82È\82¢\82Ì\82Å\83h\83\89\83b\83O\82Å\82«\82È\82¢
-    if Button = mbLeft then begin
-      FDragTabIndex := Index; //\83h\83\89\83b\83O\82·\82é\83^\83u\82Ì\83C\83\93\83f\83b\83N\83X\82ð\95Û\91
-      BeginDrag(False);
-      FDragTabDest := -1;     //\83h\83\89\83b\83O\98g\90ü\95`\89æ\83t\83\89\83O\83N\83\8a\83A\82Ì\82½\82ß
+  if Button = mbMiddle then
+  begin
+    //\92\86\83{\83^\83\93\83N\83\8a\83b\83N\82Å\83^\83u\8dí\8f\9c
+    DoCloseTab(tabBottleLog.IndexOfTabAt(X, Y), true);
+  end else
+  begin
+    with tabBottleLog do begin
+      Index := IndexOfTabAt(X, Y);
+      if Index = -1 then Exit; //\83^\83u\82ª\82È\82¢\82Ì\82Å\83h\83\89\83b\83O\82Å\82«\82È\82¢
+      if Button = mbLeft then begin
+        FDragTabIndex := Index; //\83h\83\89\83b\83O\82·\82é\83^\83u\82Ì\83C\83\93\83f\83b\83N\83X\82ð\95Û\91
+        BeginDrag(False);
+        FDragTabDest := -1;     //\83h\83\89\83b\83O\98g\90ü\95`\89æ\83t\83\89\83O\83N\83\8a\83A\82Ì\82½\82ß
+      end;
     end;
   end;
 end;
@@ -1091,7 +1215,6 @@ begin
   end else if Source is TBottleLogDragObject then
   begin
     // \83\8d\83O\8d\80\96Ú\82Ì\83h\83\89\83b\83O(\83\8d\83O\82ð\95Ê\82Ì\83^\83u\82É\88Ú\93®\82·\82é)\82Ì\8fê\8d\87
-    Accept := true;
     Index := tabBottleLog.IndexOfTabAt(X, Y);
     if tabBottleLog.TabIndex <> Index then
     begin
@@ -1099,7 +1222,7 @@ begin
       // \83^\83u\82ð\90Ø\91Ö\82¦\82é
       tabBottleLogChanging(Self, dummy);
       tabBottleLog.TabIndex := Index;
-      tabBottleLogChange(Self);
+      UpdateWindow;
     end;
   end;
 end;
@@ -1125,7 +1248,11 @@ end;
 
 procedure TfrmLog.LogLoadWork(Sender: TObject);
 begin
-  if Sender = SelectedBottleLog then lvwLog.Invalidate;
+  if Sender = SelectedBottleLog then
+  begin
+    lvwLog.Invalidate;
+    lvwLog.Items.Count := SelectedBottleLog.Count;
+  end;
 end;
 
 procedure TfrmLog.lvwLogDrawItem(Sender: TCustomListView; Item: TListItem;
@@ -1136,20 +1263,17 @@ var
   Ico: TIcon;
   sub, Ex: integer;
   Bottle: TLogItem;
-  DummyStr: TStringList;
 begin
   Bottle := SelectedBottleLog.Bottles[Item.Index];
   if Bottle.HasURL = huUndefined then
   begin
-    DummyStr := TStringList.Create;
     try
-      ExtractURLs(Bottle.Script, DummyStr);
-      if DummyStr.Count > 0 then
+      if ExtractURLs(Bottle.Script, nil, nil) then
         Bottle.HasURL := huYes
       else
         Bottle.HasURL := huNo;
     finally
-      DummyStr.Free;
+
     end;
   end;
 
@@ -1186,7 +1310,7 @@ begin
     else
       lvwLog.Canvas.Font.Color := clWindowText;
   end else
-    lvwLog.Canvas.Font.Color := Pref.TalkColorH;
+  lvwLog.Canvas.Font.Color := Pref.TextColor;
   lvwLog.Canvas.Refresh;
 
   // \83L\83\83\83v\83V\83\87\83\93(\93ú\95t)
@@ -1195,7 +1319,7 @@ begin
   Inc(DestRect.Top, 2);
   Dec(DestRect.Right, 2);
   DrawTextEx(lvwLog.Canvas.Handle, PChar(Item.Caption), -1, DestRect,
-    DT_SINGLELINE or DT_END_ELLIPSIS, nil);
+    DT_SINGLELINE or DT_RIGHT, nil);
   ListView_GetItemRect(lvwLog.Handle, Item.Index, DestRect, LVIR_ICON);
   Ico := TIcon.Create;
   try
@@ -1210,6 +1334,13 @@ begin
     if sub = SubScript then Continue;
     ListView_GetSubItemRect(lvwLog.Handle, Item.Index, sub + 1,
       LVIR_BOUNDS, @DestRect);
+    if DestRect.Right - DestRect.Left <= 16 then
+    begin
+      // \8b·\82·\82¬\82é\8fê\8d\87\82Í\95\8e\9a\97ñ\82ð\95`\89æ\82µ\82È\82¢\81B
+      // 16\82Æ\82¢\82¤\90\94\8e\9a\82Í\8eÀ\91ª\92l\81B\89½\82©\82Ì\83o\83O\82Á\82Û
+      lvwLog.Canvas.FillRect(DestRect);
+      Continue;
+    end;
     Inc(DestRect.Left, 2);
     Inc(DestRect.Top, 2);
     Dec(DestRect.Right, 2);
@@ -1385,6 +1516,7 @@ var
   Rec: TRect; // \83_\83~\81[\81B
 begin
   Accept := False;
+  // \82Æ\82è\82 \82¦\82¸\8eó\82¯\95t\82¯\82é\89Â\94\\90«\82ª\82 \82é\82Ì\82ÍTBottleLogDragObject\82¾\82¯
   if not (Source is TBottleLogDragObject) then
     Exit;
 
@@ -1396,10 +1528,11 @@ begin
   // \83h\83\8d\83b\83v\88Ê\92u\82É Item \82ª\82 \82ê\82Î\83h\83\8d\83b\83v\82ð\8b\96\89Â\82·\82é
   if Target <> nil then
   begin
-    Accept := True;
+    Accept := true;
     FLVDragDest := Target.Index;
   end else
   begin
+    Accept := true;
     FLVDragDest := -1;
   end;
 
@@ -1416,18 +1549,23 @@ begin
     DrawListViewDragBorder(Rec);
   end;
 
-  if (lvwLog.topItem <> nil) and (Y - lvwLog.TopItem.Top < 10) then
-  begin
-    FLVScrollDir := lvScrollDown;
-    if not timScrollTimer.Enabled then
-      timScrollTimer.Enabled := true;
-  end else if (lvwLog.Height - Y) < 10 then
+  // \83X\83N\83\8d\81[\83\8b\8aÖ\8cW
+  if lvwLog.Items.Count > 0 then
   begin
-    FLVScrollDir := lvScrollUp;
-    if not timScrollTimer.Enabled then
-      timScrollTimer.Enabled := true;
-  end
-  else
+    if (lvwLog.topItem <> nil) and (Y - lvwLog.TopItem.Top < 10) then
+    begin
+      FLVScrollDir := lvScrollDown;
+      if not timScrollTimer.Enabled then
+        timScrollTimer.Enabled := true;
+    end else if (lvwLog.Height - Y) < 10 then
+    begin
+      FLVScrollDir := lvScrollUp;
+      if not timScrollTimer.Enabled then
+        timScrollTimer.Enabled := true;
+    end
+    else
+      timScrollTimer.Enabled := false;
+  end else
     timScrollTimer.Enabled := false;
 end;
 
@@ -1438,21 +1576,47 @@ var
   SrcLog: TObject;
 begin
   timScrollTimer.Enabled := false;
-  TargetItem := lvwLog.GetItemAt(X, Y).Index;
 
   if not (Source is TBottleLogDragObject) then
     Exit;
   Src := Source as TBottleLogDragObject;
 
+  if lvwLog.GetItemAt(X, Y) <> nil then
+    TargetItem := lvwLog.GetItemAt(X, Y).Index
+  else
+    TargetItem := -1;
+
   lvwLog.Items.BeginUpdate; // \83h\83\8d\83b\83v\92\86\82Í\95\\8e¦\82ð\97}\8e~\82·\82é\81@\8fd\97v\81I
   try
     // \83h\83\8d\83b\83v\88Ê\92u\82É Item \82ð\88Ú\93®\82·\82é
-    SrcLog := Src.BottleLogList.Extract(Src.LogItem);
-    SelectedBottleLog.Insert(TargetItem, SrcLog);
+    if (GetAsyncKeyState(VK_CONTROL) and $8000) > 0 then
+    begin // \83R\83s\81[\88Ú\93®\82Ì\8fê\8d\87
+      SrcLog := TLogItem.Create(Src.LogItem);
+      SelectedBottleLog.LogModified := true; // \95Ï\8dX\88µ\82¢\82É\82·\82é
+    end else // \88Ú\93®\82¾\82¯\82·\82é\8fê\8d\87
+    begin
+      SrcLog := Src.BottleLogList.Extract(Src.LogItem);
+      // \88Ú\93®\8c³\82Æ\88Ú\93®\90æ\82ª\88á\82Á\82Ä\82¢\82ê\82Î\97¼\95û\82Ì\83t\83\89\83O\82ð\97§\82Ä\82é
+      if SelectedBottleLog.SelectedIndex <> Src.BottleLogList.SelectedIndex then
+      begin
+        Src.BottleLogList.LogModified := true; // \88Ú\93®\8c³
+        SelectedBottleLog.LogModified := true; // \88Ú\93®\90æ
+      end;
+    end;
+    if TargetItem >= 0 then
+    begin
+      // \82·\82Å\82É\91\8dÝ\82·\82é\83A\83C\83e\83\80\82Ì\8fã\82É\83h\83\8d\83b\83v\82µ\82½\8fê\8d\87
+      SelectedBottleLog.Insert(TargetItem, SrcLog);
+    end else
+    begin
+      // ListView\82Ì\97]\94\92\82É\83h\83\8d\83b\83v\82µ\82½\8fê\8d\87(Insert\82Å\82«\82È\82¢)
+      TargetItem := SelectedBottleLog.Add(SrcLog);
+    end;
     lvwLog.Items[TargetItem].Selected := true;
     lvwLog.Items[TargetItem].Focused := true;
   finally
     lvwLog.Items.EndUpdate;
+    UpdateWindow;
   end;
 end;
 
@@ -1521,6 +1685,148 @@ begin
   end;
 end;
 
+function TfrmLog.DoSaveLogXML(Log: TBottleLogList): integer;
+var
+  Res: integer;
+begin
+  Res := idYes;
+  SaveDialog.FileName := GetDefaultFileName(Log.Title, '.xml');
+  SaveDialog.InitialDir := Pref.LogDir;
+  SaveDialog.DefaultExt := 'xml';
+  SaveDialog.FilterIndex := 3;
+  if SaveDialog.Execute then
+    Log.SaveToXmlFile(SaveDialog.FileName)
+  else
+    Res := idCancel;
+  Result := Res;
+end;
+
+procedure TfrmLog.DoCloseTab(const Index: integer; FCheck: boolean);
+var
+  Confirm: String;
+  PrevSelection: TBottleLogList; // \95Â\82\82½\82Æ\82«\83^\83u\82ª\82¸\82ê\82È\82¢\82æ\82¤\82É\82·\82é\8f\88\97\9d\97p
+  i: integer;
+begin
+  if Pref.ConfirmOnTabClose and FCheck then
+  begin
+    Confirm := Format('\83^\83u"%s"\82ð\95Â\82\82Ü\82·\82©?', [(FBottleLogList[Index] as TBottleLogList).Title]);
+    if MessageDlg(Confirm, mtConfirmation, mbOkCancel, 0) = mrCancel then
+      Exit;
+  end;
+  if CheckLogSave(Index) = idCancel then exit; // \83\8d\83O\82Ì\95Û\91\8am\94F
+  PrevSelection := SelectedBottleLog;
+  FBottleLogList.Delete(Index);
+  UpdateTab;
+  // \83^\83u\82¸\82ê\96h\8e~\8f\88\97\9d
+  for i := 0 to FBottleLogList.Count-1 do
+    if FBottleLogList[i] = PrevSelection then
+      tabBottleLog.TabIndex := i;
+  UpdateWindow;
+  lvwLogChange(Self, nil, ctState);
+end;
+
+procedure TfrmLog.HTMLOutputWork(Sender: TObject; const Count: integer;
+  var Canceled: boolean);
+begin
+  frmHTMLOutputProgress.ProgressBar.Position := Count;
+  Application.ProcessMessages;
+  if frmHTMLOutputProgress.Canceled then
+    Canceled := true;
+end;
+
+function TfrmLog.DoSearchLog(Condition: TSearchCond): TBottleLogList;
+var i, UntilIndex: integer;
+begin
+  Result := TBottleLogList.Create('\8c\9f\8dõ\8c\8b\89Ê');
+  if Condition.SearchLogRange in [srSelectedLogList, srAboveSelectedLog] then
+  begin
+    if SelectedBottleLog = nil then
+    begin
+      ShowMessage('\8c\9f\8dõ\91Î\8fÛ\82ª\82 \82è\82Ü\82¹\82ñ');
+      Result.Free;
+      Result := nil;
+      Exit;
+    end else
+    begin
+      if Condition.SearchLogRange = srSelectedLogList then
+        UntilIndex := -1
+      else if lvwLog.Selected = nil then
+        UntilIndex := -1
+      else
+        UntilIndex := lvwLog.Selected.Index;
+      SearchLogIndivisual(Condition, SelectedBottleLog, Result, UntilIndex);
+    end;
+  end else if Condition.SearchLogRange = srAllLogLists then
+  begin
+    for i := 0 to BottleLogList.Count-1 do
+    begin
+      SearchLogIndivisual(Condition, BottleLogList[i] as TBottleLogList,
+        Result);
+    end;
+  end;
+
+  if Result.Count = 0 then
+    Result.AddSystemLog('\8c©\82Â\82©\82è\82Ü\82¹\82ñ\82Å\82µ\82½\81B');
+end;
+
+procedure TfrmLog.SearchLogIndivisual(Condition: TSearchCond; LogList,
+  Result: TBottleLogList; UntilIndex: integer = -1);
+var
+  i, Max: integer;
+  Bottle, New: TLogItem;
+  Ok: boolean;
+begin
+  // 1\8cÂ\82Ì\83\8d\83O\83^\83u\82É\91Î\82µ\82Ä\8c\9f\8dõ\82ð\82©\82¯\82é\81BUntilIndex\82Å\94Í\88Í\8ew\92è(\8fÈ\97ª\8e\9e\82»\82Ì\83^\83u\91S\91Ì)
+  if UntilIndex >= 0 then
+    Max := UntilIndex
+  else
+    Max := LogList.Count-1;
+  for i := 0 to Max do
+  begin
+    // \8fð\8c\8f\94»\92è
+    Bottle := LogList.Bottles[i];
+    if Bottle.LogType <> ltBottle then
+      Continue;
+    Ok := true;
+    // \83X\83N\83\8a\83v\83g\83p\83^\81[\83\93\82Å\89ð\90Í
+    if Condition.ScriptPattern <> '' then
+    begin
+      if Condition.ScriptRegExp then
+      begin
+        try
+          if not RegExp.Match(Condition.ScriptPattern, Bottle.Script) then
+            Ok := false;
+        except
+          on EBRegExpError do
+            Ok := false; //\96­\82È\90³\8bK\95\\8c»\82ð\8fR\82é
+        end;
+      end else
+      begin
+        if not AnsiContainsText(Bottle.Script, Condition.ScriptPattern) then
+          Ok := false;
+      end;
+    end;
+    // \83`\83\83\83\93\83l\83\8b\96¼\81A\83S\81[\83X\83g\96¼\81A\93\8a\95[\93¯\88Ó
+    if Condition.Channel <> '' then
+      if not AnsiContainsText(Bottle.Channel, Condition.Channel) then
+        Ok := false;
+    if Condition.Ghost <> '' then
+      if not AnsiContainsText(Bottle.Ghost, Condition.Ghost) then
+        Ok := false;
+    if Condition.MinVote > Bottle.Votes then
+      Ok := false;
+    if Condition.MinAgree > Bottle.Agrees then
+      Ok := false;
+    // \8fð\8c\8f\82É\88ê\92v\82µ\82½\82à\82Ì\82ð\8c\8b\89Ê\83\8a\83X\83g\82É\92Ç\89Á
+    if Ok then
+    begin
+      New := TLogItem.Create(Bottle); // \83R\83s\81[\83R\83\93\83X\83g\83\89\83N\83^
+      New.State := lsOpened;
+      Result.Add(New);
+    end;
+  end;
+end;
+
 { TBottleLogDragObject }
 
 function TBottleLogDragObject.GetDragImages: TDragImageList;
@@ -1540,4 +1846,322 @@ begin
   FLogItem := Value;
 end;
 
+procedure TfrmLog.mnTabSaveXMLLogClick(Sender: TObject);
+begin
+  DoSaveLogXML(FBottleLogList[tabBottleLog.Tag] as TBottleLogList);
+end;
+
+procedure TfrmLog.mnSaveHTMLClick(Sender: TObject);
+var
+  LogList, SB: TBottleLogList;
+  i: integer;
+  Options: THTMLOutputOptions;
+begin
+  SB := SelectedBottleLog;
+  if SB = nil then
+    Exit;
+  if SB.Count = 0 then
+    Exit;
+  Application.CreateForm(TfrmHTMLOutputConfig, frmHTMLOutputConfig);
+  with frmHTMLOutputConfig do
+    try
+      // Show HTML save option dialog
+      if not Execute then
+        Exit;
+      LogList := TBottleLogList.Create('');
+      try
+        case Range of
+          orAll:
+            for i := SB.Count-1 downto 0 do
+              if SB.Bottles[i].LogType = ltBottle then
+                LogList.Add(TLogItem.Create(SB.Bottles[i]));
+          orSelected:
+            if SB.Bottles[lvwLog.Selected.Index].LogType = ltBottle then
+              LogList.Add(TLogItem.Create(SB.Bottles[lvwLog.Selected.Index]))
+            else
+              ShowMessage('\82±\82Ì\83\81\83b\83Z\81[\83W\82Í\95Û\91\82Å\82«\82Ü\82¹\82ñ');
+          orUpward:
+            for i := lvwLog.Selected.Index downto 0 do
+              if SB.Bottles[i].LogType = ltBottle then
+                LogList.Add(TLogItem.Create(SB.Bottles[i]));
+        end;
+        Options.ImageDir := ImageDir;
+        Options.UseColor := UseColor;
+        Options.ImageType := ImageType;
+        Application.CreateForm(TfrmHTMLOutputProgress, frmHTMLOutputProgress);
+        try
+          frmHTMLOutputProgress.Show;
+          LogList.OnHTMLOutputWork := HTMLOutputWork;
+          LogList.SaveToHTML(FileName, Options, SsParser);
+        finally
+          frmHTMLOutputProgress.Release;
+        end;
+      finally
+        LogList.Free;
+      end;
+    finally
+      Release;
+    end;
+end;
+
+procedure TfrmLog.mnPopupCopyGhostClick(Sender: TObject);
+var
+  Log: TLogItem;
+  Clip: TClipBoard;
+begin
+  Log := SelectedBottleLog.Bottles[frmLog.lvwLog.Selected.Index];
+  if Log = nil then Exit;
+  Clip := ClipBoard();
+  Clip.SetTextBuf(PChar(Log.Ghost));
+end;
+
+function TfrmLog.CheckLog(Sender: TObject): integer;
+var
+  i, Res: integer;
+begin
+  // \91S\82Ä\82Ì\83\8a\83X\83g\81i\83^\83u\81j\83`\83F\83b\83N\82·\82é
+  // frmSender\82©\82ç\8fI\97¹\8e\9e\82É\8cÄ\82Ñ\8fo\82³\82ê\82é
+  Res := idNo;
+  for i := BottleLogList.Count-1 downto 0 do
+  begin
+    Res := CheckLogSave(i);
+    if Res = idCancel then break;
+    DoCloseTab(i, false);
+  end;
+  Result := Res;
+end;
+
+function TfrmLog.CheckLogSave(const Index: integer): integer;
+var
+  Res: integer;
+  Confirm: string;
+begin
+  // \83\8a\83X\83g\82ð\83`\83F\83b\83N\82µ\81A\95Û\91\8f\88\97\9d\82ð\8cÄ\82Ñ\8fo\82·
+  Res := idNo;
+  if (BottleLogList[Index] as TBottleLogList).LogModified then
+  begin
+    Confirm := Format('\83^\83u"%s"\82Ì\93à\97e\82Í\95Ï\8dX\82³\82ê\82Ä\82¢\82Ü\82·\81B'#13#10#13#10 +
+      '\95Û\91\82µ\82Ü\82·\82©\81H', [(FBottleLogList[Index] as TBottleLogList).Title]);
+    Res := MessageDlg(Confirm, mtConfirmation, mbYesNoCancel, 0);
+    if Res = idYes then
+      Res := DoSaveLogXML(FBottleLogList[Index] as TBottleLogList);
+    if Res = idNo then
+      (BottleLogList[Index] as TBottleLogList).LogModified := false;
+  end;
+  Result := Res;
+end;
+
+procedure TfrmLog.mnTestActionClick(Sender: TObject);
+begin
+  // \82±\82±\82©\82ç\83A\83N\83V\83\87\83\93
+  Screen.Cursor := crHourGlass;
+  frmSender.LogInsertCue(true, false); // \83A\83N\83V\83\87\83\93\83e\83X\83g\81i\98A\91±\8dÄ\90\81j
+  Screen.Cursor := crDefault;
+end;
+
+procedure TfrmLog.mnSelActionClick(Sender: TObject);
+begin
+  // \92P\91Ì\83A\83N\83V\83\87\83\93
+  actLvwLog(true);
+end;
+
+procedure TfrmLog.mnAllActionClick(Sender: TObject);
+begin
+  // \82±\82Ì\83^\83u\93à\91S\82Ä\83A\83N\83V\83\87\83\93
+  Screen.Cursor := crHourGlass;
+  frmSender.LogInsertCue(true, true); // \83A\83N\83V\83\87\83\93\83e\83X\83g\81i\98A\91±\8dÄ\90\81\95\91S\82Ä\81j
+  Screen.Cursor := crDefault;
+end;
+
+procedure TfrmLog.LoadSAXLoader(FileName: String);
+var XMLReader: IXMLReader;
+    XMLBufReader: IBufferedXMLReader;
+    Vendor: TSAXVendor;
+    SAXErrorHandler1: TSAXErrorHandler;
+    SAXContentHandler1: TSAXContentHandler;
+    ContentHandler: IContentHandler;
+begin
+  LoadSAXItemReset;
+  FsReadMess := false;
+  FsNowNode  := '';
+  FsReadElm  := false;
+  FsLoadFailureMessage := '';
+
+  // \83C\83x\83\93\83g\92è\8b`
+  SAXContentHandler1 := TSAXContentHandler.Create(nil);
+  SAXContentHandler1.OnCharacters   := SAXContentHandler1Characters;
+  SAXContentHandler1.OnEndElement   := SAXContentHandler1EndElement;
+  SAXContentHandler1.OnStartElement := SAXContentHandler1StartElement;
+
+  SAXErrorHandler1 := TSAXErrorHandler.Create(nil);
+  SAXErrorHandler1.OnWarning    := SAXErrorHandler1Warning;
+  SAXErrorHandler1.OnError      := SAXErrorHandler1Error;
+  SAXErrorHandler1.OnFatalError := SAXErrorHandler1FatalError;
+
+  ContentHandler := SAXContentHandler1;
+  try
+    // Get the Default SAX Vendor and XML Reader
+    Vendor:= GetSAXVendor('MSXML');   // MSXML\82ð\97\98\97p\82·\82é(\97v:SAX_WIDESTRINGS)
+    if Vendor is TBufferedSAXVendor then
+    begin
+      XMLBufReader:= TBufferedSAXVendor(Vendor).BufferedXMLReader;
+      XMLBufReader.setContentHandler(Adapt(ContentHandler, XMLBufReader));
+      XMLBufReader.setErrorHandler(SAXErrorHandler1);
+      // This time we send the InputSource we created
+      XMLBufReader.parse(FileName);
+      XMLBufReader:= nil;
+    end else
+    begin
+      XMLReader:= Vendor.XMLReader;
+      XMLReader.setContentHandler(ContentHandler);
+      XMLReader.setErrorHandler(SAXErrorHandler1);
+      // This time we send the InputSource we created
+      XMLReader.parse(FileName);
+      XMLReader:= nil;
+    end;
+  finally
+    SAXErrorHandler1.Free;
+    SAXContentHandler1.Free;
+  end;
+end;
+
+procedure TfrmLog.LoadSAXItemReset;
+begin
+  FsDate    := '';
+  FsChannel := '';
+  FsScript  := '';
+  FsVotes   := '';
+  FsAgrees  := '';
+  FsGhost   := '';
+  FsMid     := '';
+end;
+
+procedure TfrmLog.SAXErrorHandler1Error(Sender: TObject;
+  const Error: ISAXParseError);
+begin
+  FsList.AddSystemLog(Error.getMessage + ' Line ' + IntToStr(Error.getLineNumber) +
+    ' Column ' + IntToStr(Error.getColumnNumber));
+end;
+
+procedure TfrmLog.SAXErrorHandler1FatalError(Sender: TObject;
+  const Error: ISAXParseError);
+begin
+  FsList.AddSystemLog(Error.getMessage + ' Line ' + IntToStr(Error.getLineNumber) +
+    ' Column ' + IntToStr(Error.getColumnNumber));
+end;
+
+procedure TfrmLog.SAXErrorHandler1Warning(Sender: TObject;
+  const Error: ISAXParseError);
+begin
+  FsList.AddSystemLog(Error.getMessage + ' Line ' + IntToStr(Error.getLineNumber) +
+    ' Column ' + IntToStr(Error.getColumnNumber));
+end;
+
+procedure TfrmLog.SAXContentHandler1Characters(Sender: TObject;
+  const PCh: WideString);
+begin
+  // \83\8d\83O\8fî\95ñ\93Ç\82Ý\8eæ\82è
+  // &\93\99\82Å\95ª\89ð\82³\82ê\82é\82Ì\82Å\8c\8b\8d\87\82·\82é\95K\97v\82ª\82 \82é
+  if(not FsReadElm)then Exit; // \83\8d\83O\8fî\95ñ\88È\8aO\82Í\96³\8e\8b
+  if(FsNowNode = 'date')then
+    FsDate := PCh;
+  if(FsNowNode = 'channel')then
+    FsChannel := FsChannel + PCh;
+  if(FsNowNode = 'script')then
+    FsScript := FsScript + PCh;
+  if(FsNowNode = 'votes')then
+    FsVotes := PCh;
+  if(FsNowNode = 'agrees')then
+    FsAgrees := Pch;
+  if(FsNowNode = 'ghost')then
+    FsGhost := FsGhost + Pch;
+end;
+
+procedure TfrmLog.SAXContentHandler1EndElement(Sender: TObject;
+  const NamespaceURI, LocalName, QName: WideString);
+var Time: TDateTime;
+    Item: TLogItem;
+begin
+  if(qName = 'message')then
+  begin
+    FsReadMess := false; // \83\8d\83O\8fî\95ñ\8fI\82í\82è
+
+    TryStrToDateTime(Trim(FsDate), Time);
+    Item := TLogItem.Create(ltBottle, FsMid, FsChannel, FsScript, FsGhost, Time);
+    Item.Votes  := StrToIntDef(FsVotes, 0);
+    Item.Agrees := StrToIntDef(FsAgrees, 0);
+    Item.State  := lsOpened;
+    try
+      FsList.Add(Item);
+    except
+      Item.Free;
+    end;
+    LoadSAXItemReset; // \83\8d\83O\8fî\95ñ\83N\83\8a\83A
+  end;
+  FsReadElm := false; // \83^\83O\8fI\97¹
+end;
+
+procedure TfrmLog.SAXContentHandler1StartElement(Sender: TObject;
+  const NamespaceURI, LocalName, QName: WideString;
+  const Atts: IAttributes);
+begin
+  FsNowNode := qName;          // \8d¡\8c©\82Ä\82¢\82é\83m\81[\83h
+  if(qName = 'bottlelog')then  // EndElment\82Å\83`\83F\83b\83N\82µ\82Ä\82¢\82È\82¢
+  begin
+    if(Atts.getValue('version') <> '1.0')then
+    begin
+      FsLoadFailureMessage := Format('\97L\8cø\82È\8c`\8e®\82Å\82Í\82 \82è\82Ü\82¹\82ñ\81B' +
+        '\82±\82Ì\83\8d\83O\83t\83@\83C\83\8b\82Ì\83o\81[\83W\83\87\83\93(%s)\82Í\93Ç\82Ý\8d\9e\82ß\82Ü\82¹\82ñ\81B', [Atts.getValue('version')]);
+      raise EXMLFileOpenException.Create(FsLoadFailureMessage);
+      exit;
+    end;
+  end else if(qName = 'message')then
+  begin
+    FsReadMess := true;   // \83\8d\83O\8fî\95ñ\93Ç\82Ý\8d\9e\82ÝON
+    FsMid := Atts.getValue('mid');
+  end else if((qName = 'date') // \82±\82Ì\83`\83F\83b\83N\97v\82ç\82È\82¢\82©\82à
+    or (qName = 'channel')
+    or (qName = 'script')
+    or (qName = 'votes')
+    or (qName = 'agrees')
+    or (qName = 'ghost'))then
+  begin
+    FsReadElm := true;    // \83^\83O\8aJ\8en
+  end;
+end;
+
+procedure TfrmLog.DoSAXLoad;
+var BottleLog: TBottleLogList;
+    i, Index: integer;
+begin
+  Index := -1;
+  OpenDialog.InitialDir := Pref.LogDir;
+  if OpenDialog.Execute then begin
+    Screen.Cursor := crHourGlass;
+    for i := 0 to OpenDialog.Files.Count-1 do begin
+      BottleLog := TBottleLogList.Create(ExtractFileName(OpenDialog.Files[i]));
+      FsList := BottleLog;
+      try
+        with BottleLog do
+        begin
+          try
+            LoadSAXLoader(OpenDialog.Files[i]);
+          except
+            if FsLoadFailureMessage <> '' then
+              FsList.AddSystemLog(FsLoadFailureMessage);
+          end;
+        end;
+        Index := BottleLogList.Add(FsList); // \8dÅ\8cã\82É\8aJ\82¢\82½\83\8d\83O\82Ì\88Ê\92u\82ð\8bL\89¯
+      except
+        BottleLog.Free;
+        FsList.Free;
+      end;
+    end;
+    Screen.Cursor := crDefault;
+    UpdateTab;
+    if Index >= 0 then tabBottleLog.TabIndex := Index;
+    UpdateWindow;
+  end;
+end;
+
 end.