OSDN Git Service

URLジャンプメニューに選択肢名も表示するようにした
[winbottle/winbottle.git] / bottleclient / LogForm.pas
index 594388a..9bb3696 100755 (executable)
@@ -5,12 +5,17 @@ interface
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   ComCtrls, ToolWin, StdCtrls, ExtCtrls, SsParser, BottleDef, Menus,
-  Clipbrd, Logs, ShellAPI, Commctrl, DirectSstp, Contnrs, xmldom, XMLIntf,
-  msxmldom, XMLDoc;
+  Clipbrd, Logs, ShellAPI, Commctrl, DirectSstp, Contnrs, StrUtils,
+  TalkShowFrame, SppList, HtmlOutputConfig, HtmlOutputProgress,
+  SearchLog, IniFiles, BRegExp, RegexUtils;
 
 type
+  // \83\8d\83O\82Ì\95Û\91\95û\96@
   TSaveLogType = (stLog, stLogWithChannels, stText, stXML);
 
+  // \83\8a\83X\83g\83r\83\85\81[\82Ì\83X\83N\83\8d\81[\83\8b\95û\8cü
+  TLVScrollDir = (lvScrollUp, lvScrollDown);
+
   TfrmLog = class(TForm)
     ToolBar: TToolBar;
     tbtnClear: TToolButton;
@@ -26,7 +31,6 @@ type
     SaveDialog: TSaveDialog;
     pnlPanel: TPanel;
     Splitter: TSplitter;
-    edtScript: TRichEdit;
     mnPopUpCopyScript: TMenuItem;
     PopupMenuSaveLog: TPopupMenu;
     mnSaveLog: TMenuItem;
@@ -50,7 +54,30 @@ type
     PopupMenuTab: TPopupMenu;
     mnCloseTab: TMenuItem;
     tbtnFindBottle: TToolButton;
-    XMLDocument: TXMLDocument;
+    tbtnOpenLog: TToolButton;
+    OpenDialog: TOpenDialog;
+    tbtnInsertCue: TToolButton;
+    mnInsertCue: TMenuItem;
+    PopupMenuListPreviewStyle: TPopupMenu;
+    mnListPreviewStyleNormal: TMenuItem;
+    mnListPreviewStyleTagStripped: TMenuItem;
+    tbtnListPreviewStyle: TToolButton;
+    mnListPreviewStyleNoColor: TMenuItem;
+    SsParserForTalkShow: TSsParser;
+    mnPreviewStyleConversationImage: TMenuItem;
+    pnlPreviewArea: TPanel;
+    TalkShowFrame: TfrmTalkShow;
+    edtScript: TRichEdit;
+    tbtnSendEditor: TToolButton;
+    mnSendEditor: TMenuItem;
+    timScrollTimer: TTimer;
+    mnChangeTabName: TMenuItem;
+    N1: TMenuItem;
+    N2: TMenuItem;
+    mnDeleteLogItem: TMenuItem;
+    mnTabSaveXMLLog: TMenuItem;
+    mnSaveHTML: TMenuItem;
+    mnPopupCopyGhost: TMenuItem;
     procedure tbtnClearClick(Sender: TObject);
     procedure FormCreate(Sender: TObject);
     procedure lvwLogChange(Sender: TObject; Item: TListItem;
@@ -69,9 +96,6 @@ type
     procedure PopupMenuListViewPopup(Sender: TObject);
     procedure lvwLogCustomDrawItem(Sender: TCustomListView;
       Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
-    procedure lvwLogCustomDrawSubItem(Sender: TCustomListView;
-      Item: TListItem; SubItem: Integer; State: TCustomDrawState;
-      var DefaultDraw: Boolean);
     procedure PopupMenuPreviewStylePopup(Sender: TObject);
     procedure mnPreviewStyleClick(Sender: TObject);
     procedure tbtnPreviewStyleClick(Sender: TObject);
@@ -82,77 +106,123 @@ type
       var Handled: Boolean);
     procedure mnCloseTabClick(Sender: TObject);
     procedure tbtnFindBottleClick(Sender: TObject);
+    procedure tbtnOpenLogClick(Sender: TObject);
+    procedure tabBottleLogMouseDown(Sender: TObject; Button: TMouseButton;
+      Shift: TShiftState; X, Y: Integer);
+    procedure tabBottleLogDragOver(Sender, Source: TObject; X, Y: Integer;
+      State: TDragState; var Accept: Boolean);
+    procedure tabBottleLogDragDrop(Sender, Source: TObject; X, Y: Integer);
+    procedure tabBottleLogEndDrag(Sender, Target: TObject; X, Y: Integer);
+    procedure lvwLogDrawItem(Sender: TCustomListView; Item: TListItem;
+      Rect: TRect; State: TOwnerDrawState);
+    procedure mnListPreviewStyleClick(Sender: TObject);
+    procedure tbtnListPreviewStyleClick(Sender: TObject);
+    procedure PopupMenuListPreviewStylePopup(Sender: TObject);
+    procedure lvwLogDragOver(Sender, Source: TObject; X, Y: Integer;
+      State: TDragState; var Accept: Boolean);
+    procedure lvwLogDragDrop(Sender, Source: TObject; X, Y: Integer);
+    procedure timScrollTimerTimer(Sender: TObject);
+    procedure mnChangeTabNameClick(Sender: TObject);
+    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);
   private
     { Private \90é\8c¾ }
     FLastScript: String; //\83X\83N\83\8a\83v\83g\8dÄ\95`\89æ\97}\90§\97p
     FBottleLogList: TObjectList;
+    //
+    FDragTabIndex: integer; //\83^\83u\83h\83\89\83b\83O\83h\83\8d\83b\83v\8aÖ\98A
+    FDragTabDest: integer;  //\83h\83\8d\83b\83v\82·\82é\88Ê\92u(\82·\82®\89E\82É\82­\82é\83^\83u\82Ì\83C\83\93\83f\83b\83N\83X)
+    //
+    // \83\8a\83X\83g\83r\83\85\81[\83h\83\89\83b\83O\83h\83\8d\83b\83v\8aÖ\98A
+    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)
+    //
     procedure UpdateScript(const Script: String);
     procedure UpdateScriptConversationColor(const Script: String);
-    procedure UpdateScriptConversationNoColor(const Script: String);
     procedure UpdateScriptScript(const Script: String);
-    procedure DoSaveLog(SaveType: TSaveLogType; Ext: String;
-      Filter: integer);
     procedure mnURLClick(Sender: TObject);
-    procedure ExtractURLs(Script: String; Result: TStrings);
-    function XmlEntity(S: String): String;
-    function GetCurrentBottleLog: TBottleLogList;
+    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);
+    procedure DoSaveLogXML(Log: TBottleLogList);
+    procedure DoCloseTab(const Index: integer);
+    function DoSearchLog(Condition: TSearchCond): TBottleLogList;
+    procedure SearchLogIndivisual(Condition: TSearchCond;
+      LogList, Result: TBottleLogList; UntilIndex: integer = -1);
   protected
     procedure CreateParams(var Params: TCreateParams); override;
   public
     { Public \90é\8c¾ }
     function SelectedBottleLog: TBottleLogList;
-    property CurrentBottleLog: TBottleLogList read GetCurrentBottleLog;
     property BottleLogList: TObjectList read FBottleLogList;
-    procedure AddCurrentScriptLog(const Script, Channel, MID, Ghost: String);
-    procedure AddCurrentSystemLog(const MessageString: String);
+    procedure AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String);
+    procedure AddCurrentSystemLog(const LogName, MessageString: String);
     procedure VoteLog(const MID: String; const Vote: integer);
     procedure AgreeLog(const MID: String; const Agree: integer);
-    procedure SetBottleStatusToPlaying(const MID: String);
-    procedure SetBottleStatusToOpened(const MID: String);
+    procedure SetBottleState(const MID: String; State: TLogState);
+    procedure AllBottleOpened;
     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);
   end;
 
+  TBottleLogDragObject = class(TDragControlObjectEx)
+  private
+    FBottleLogList: TBottleLogList;
+    FLogItem: TLogItem;
+    procedure SetBottleLogList(const Value: TBottleLogList);
+    procedure SetLogItem(const Value: TLogItem);
+  protected
+    function GetDragImages: TDragImageList; override;
+  public
+    property BottleLogList: TBottleLogList read FBottleLogList write SetBottleLogList;
+    property LogItem: TLogItem read FLogItem write SetLogItem;
+  end;
 
 var
   frmLog: TfrmLog;
 
-function CurrentBottleLog: TBottleLogList;
-
 const
   IconBottle    = 17;
   IconOpened    = 30;
   IconPlaying   = 31;
   IconSystemLog = 26;
+  IconURL       = 43;
   SubChannel    = 0;
-  SubVotes      = 1;
-  SubAgrees     = 2;
-  SubScript     = 3;
+  SubGhost      = 1;
+  SubVotes      = 2;
+  SubAgrees     = 3;
+  SubScript     = 4;
 
 implementation
 
-uses MainForm, StrUtils;
+uses MainForm;
 
 {$R *.DFM}
 
-function CurrentBottleLog: TBottleLogList;
-begin
-  Result := frmLog.CurrentBottleLog;
-end;
-
 { TfrmLog }
 
-procedure TfrmLog.AddCurrentScriptLog(const Script, Channel, MID, Ghost: String);
+procedure TfrmLog.AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String);
 var Sel: integer;
 begin
-  CurrentBottleLog.AddScriptLog(Script, Channel, MID, Ghost);
-  if SelectedBottleLog <> CurrentBottleLog then Exit;
+  BottleLogTitled(LogName).AddScriptLog(Script, Channel, MID, Ghost);
+  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;
-  lvwLog.Items.Count := CurrentBottleLog.Count;
+  lvwLog.Items.Count := SelectedBottleLog.Count;
   UpdateWindow;
   if Sel >= 0 then begin
     lvwLog.Selected := lvwLog.Items[Sel + 1];
@@ -163,14 +233,14 @@ begin
   lvwLog.OnChange := lvwLogChange;
 end;
 
-procedure TfrmLog.AddCurrentSystemLog(const MessageString: String);
+procedure TfrmLog.AddCurrentSystemLog(const LogName, MessageString: String);
 var Sel: integer;
 begin
-  CurrentBottleLog.AddSystemLog(MessageString);
-  if SelectedBottleLog <> CurrentBottleLog then Exit;
+  BottleLogTitled(LogName).AddSystemLog(MessageString);
+  if SelectedBottleLog <> BottleLogTitled(LogName) then Exit;
   lvwLog.OnChange := nil;
   if lvwLog.Selected <> nil then Sel := lvwLog.Selected.Index else Sel := -1;
-  lvwLog.Items.Count := CurrentBottleLog.Count;
+  lvwLog.Items.Count := SelectedBottleLog.Count;
   UpdateWindow;
   if Sel >= 0 then begin
     lvwLog.Selected := lvwLog.Items[Sel + 1];
@@ -185,24 +255,15 @@ end;
 
 procedure TfrmLog.tbtnClearClick(Sender: TObject);
 begin
-  if SelectedBottleLog = CurrentBottleLog then begin
-    CurrentBottleLog.Clear;
-    lvwLog.Items.Count := 0;
-    lvwLog.Invalidate;
-    lvwLogChange(Self, nil, ctState);
-  end else begin
-    FBottleLogList.Delete(tabBottleLog.TabIndex);
-    tabBottleLog.TabIndex := 0;
-    UpdateTab;
-    UpdateWindow;
-    lvwLogChange(Self, nil, ctState);
-  end;
+  if SelectedBottleLog = nil then Exit;
+  DoCloseTab(tabBottleLog.TabIndex);
 end;
 
 procedure TfrmLog.FormCreate(Sender: TObject);
+var i: integer;
 begin
+  FLVDragDest := -1; // \83\8a\83X\83g\83r\83\85\81[\82Ì\83h\83\89\83b\83O\92\86\82Å\82Í\82È\82¢
   FBottleLogList := TObjectList.Create;
-  FBottleLogList.Add(TBottleLogList.Create('\83J\83\8c\83\93\83g')); // CurrentBottleLog
 
   SsParser.TagPattern.Assign(frmSender.SsParser.TagPattern);
   SsParser.MetaPattern.Assign(frmSender.SsParser.MetaPattern);
@@ -214,104 +275,159 @@ begin
     Self.Height := Bottom - Top + 1;
   end;
   lvwLog.DoubleBuffered := true;
-  edtScript.Height := Pref.LogWindowDividerPos;
+  pnlPreviewArea.Height := Pref.LogWindowDividerPos;
+
+  i := 0;
+  while Token(Pref.LogWindowColumnWidth, ',', i) <> '' do begin
+    lvwLog.Columns[i].Width := StrToIntDef(Token(Pref.LogWindowColumnWidth, ',', i), 100);
+    Inc(i);
+  end;
+
+  SsParserForTalkShow.TagPattern.Assign(SsParser.TagPattern);
+  SsParserForTalkShow.MetaPattern.Assign(SsParser.MetaPattern);
+  SsParserForTalkShow.EscapeInvalidMeta := false;
+  SsParserForTalkShow.LeaveEscape := false;
+  TalkShowFrame.SsParser := self.SsParserForTalkShow;
+
+  TalkShowFrame.SetPreviewFont(edtScript.Font);
+  TalkShowFrame.PrevControl := lvwLog;
+
+  PreviewStyleChange;
   UpdateWindow; // Reset window color and enabled status of some buttons
 end;
 
 procedure TfrmLog.FormDestroy(Sender: TObject);
+var i: integer;
+    WidthStr: String;
 begin
+  WidthStr := '';
+  for i := 0 to lvwLog.Columns.Count-1 do begin
+    if i > 0 then WidthStr := WidthStr + ',';
+    WidthStr := WidthStr + IntToStr(lvwLog.Column[i].Width);
+  end;
+  Pref.LogWindowColumnWidth := WidthStr;
+
   with Pref.LogWindowPosition do begin
     Left   := Self.Left;
     Top    := Self.Top;
     Right  := Self.Left + Self.Width - 1;
     Bottom := Self.Top + Self.Height - 1;
   end;
-  Pref.LogWindowDividerPos := edtScript.Height;
+  Pref.LogWindowDividerPos := pnlPreviewArea.Height;
 
   FreeAndNil(FBottleLogList);
 end;
 
 procedure TfrmLog.lvwLogChange(Sender: TObject; Item: TListItem;
   Change: TItemChange);
-var Script: String;
+var Script, Text: String;
     Log: TLogItem;
+    Selected, IsNormalBottle: boolean;
 begin
-  StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
-  if Change = ctState then begin
-    Script := '';
-    if lvwLog.Selected <> nil then begin
-      Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
-      if (Log.LogType = ltBottle) and not frmSender.Connecting then begin
-        Script := Log.Script;
-        frmSender.actVoteMessage.Enabled := true;
-        frmSender.actAgreeMessage.Enabled := true;
-        mnPopUpCopyScript.Enabled := true;
-        UpdateScript(Script);
+  Selected := false;
+  IsNormalBottle := false;
+  if SelectedBottleLog <> nil then begin
+    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
+          IsNormalBottle := true;
+          Script := 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
+          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;
-        mnPopUpCopyScript.Enabled := false;
-        UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\82ð\83N\83\8a\83A
+        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 else begin
-      frmSender.actVoteMessage.Enabled := false;
-      frmSender.actAgreeMessage.Enabled := false;
-      mnPopUpCopyScript.Enabled := false;
-      UpdateScript(Script); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
     end;
+    tbtnSaveLog.Enabled := lvwLog.Items.Count > 0;
+  end else begin
+    StatusBar.Panels[0].Text := '';
+    UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
   end;
-  tbtnSaveLog.Enabled := lvwLog.Items.Count > 0;
+  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;
 end;
 
 procedure TfrmLog.lvwLogDblClick(Sender: TObject);
-var Script: String;
-    Opt: TScriptTransOptions;
-    SOpt: TSstpSendOptions;
-    Ghost: String;
-    Log: TLogItem;
+var Script, ErrorMes: String;
+    Log, CueItem: TLogItem;
+    Res: integer;
 begin
-  if lvwLog.Selected = nil then Exit;
-  //Log := TLogItem(lvwLog.Selected.Data);
+  if lvwLog.Selected = nil then
+    Exit;
   Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
   if Log = nil then Exit;
-  if Log.LogType <> ltBottle then Exit;
-  Script := Log.Script;
-  Opt := [toConvertURL, toWaitScriptEnd];
-  if Pref.NoTransUrl then Opt := Opt + [toNoChoice];
-  if Pref.IgnoreFrequentYenS then Opt := Opt + [toIgnoreFrequentYenS];
-  if Pref.FixMessySurface then Opt := Opt + [toFixMessySurface];
-  frmSender.DoTrans(Script, Opt);
-
-  Ghost := frmSender.GetChannelPrefs(Log.Channel).TargetGhost;
-  if Ghost = '' then //\83`\83\83\83\93\83l\83\8b\8ew\92è\83S\81[\83X\83g
-    if frmSender.ChannelList.Channel[Log.Channel] <> nil then
-      Ghost := frmSender.ChannelList.Channel[Log.Channel].Ghost;
-  //\96Ú\95W\83S\81[\83X\83g\8c\88\92è
-  if Log.Ghost <> '' then Ghost := Log.Ghost;
-  if frmSender.GetChannelPrefs(Log.Channel).IgnoreIfGhost then
-    Ghost := frmSender.GetChannelPrefs(Log.Channel).TargetGhost;
-  //\83^\81[\83Q\83b\83g\83S\81[\83X\83g\8am\92è
-  Ghost := frmSender.SetHWndToFavoriteGhost(Ghost);
-  frmSender.DirectSstp.SstpSender := 'SSTP Bottle -\81y\83\8d\83O\8dÄ\90\81z';
-  if Pref.NoTranslate then SOpt := [soNoTranslate] else SOpt := [];
-  frmSender.DirectSstp.SstpSEND(Script, SOpt, frmSender.GhostNameToSetName(Ghost));
+  if Log.LogType <> ltBottle then
+    Exit;
+  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+
+        ErrorMes + #13#10 +
+        '\8b­\90§\93I\82É\8dÄ\90\82µ\82Ü\82·\82©?'#13#10,
+        mtWarning, mbOkCancel, 0
+      );
+    if Res = mrCancel then
+      Exit;
+  end;
+
+  CueItem := TLogItem.Create(Log);
+  try
+    CueItem.Script := Script;
+    frmSender.BottleSstp.Unshift(CueItem);
+  except
+    CueItem.Free;
+  end;
 end;
 
 procedure TfrmLog.UpdateScriptConversationColor(const Script: String);
 var i: integer;
     scr: String;
-    UnyuTalking, Talked: boolean;
+    UnyuTalking, Talked, InSynchronized: boolean;
 begin
   scr := Script;
   frmSender.DoTrans(scr, [toConvertURL]);
   SsParser.LeaveEscape := false;
   SsParser.InputString := scr;
-  SsParser.LeaveEscape := true;
   UnyuTalking := false;
   Talked := false; //'\h\u\h\u'\82Ì\82æ\82¤\82È\83X\83N\83\8a\83v\83g\82Å\8bó\82«\8ds\82ð\8dì\82ç\82È\82¢\82½\82ß\82Ì\91[\92u
+  InSynchronized := false;
   edtScript.Text := '';
   edtScript.Color := Pref.BgColor;
   for i := 0 to SsParser.Count-1 do begin
+    if (SsParser[i] = '\_s') and not InSynchronized then begin
+      InSynchronized := true;
+      if Talked then begin
+        edtScript.SelText := #13#10;
+        Talked := false;
+      end;
+    end else if (SsParser[i] = '\_s') and InSynchronized then begin
+      InSynchronized := false;
+      if Talked then begin
+        edtScript.SelText := #13#10;
+        Talked := false;
+      end;
+    end;
     if (SsParser[i] = '\u') and not UnyuTalking then begin
       UnyuTalking := true;
       if Talked then begin
@@ -327,7 +443,9 @@ begin
       end;
     end;
     if SsParser.MarkUpType[i] = mtStr then begin
-      if UnyuTalking then
+      if InSynchronized then
+        edtScript.SelAttributes.Color := Pref.TalkColorS
+      else if UnyuTalking then
         edtScript.SelAttributes.Color := Pref.TalkColorU
       else
         edtScript.SelAttributes.Color := Pref.TalkColorH;
@@ -342,46 +460,6 @@ begin
   end;
 end;
 
-procedure TfrmLog.UpdateScriptConversationNoColor(const Script: String);
-var Scr: String;
-    i: integer;
-    UnyuTalking, Talked, LastUnyuTalked: boolean;
-begin
-  Scr := Script;
-  frmSender.DoTrans(Scr, [toConvertURL]);
-  SsParser.LeaveEscape := false;
-  SsParser.InputString := Scr;
-  SsParser.LeaveEscape := true;
-  edtScript.Text := '';
-  edtScript.Color := clWindow;
-  edtScript.DefAttributes.Color := clWindowText;
-  edtScript.SelAttributes.Color := clWindowText;
-  Talked := false;
-  UnyuTalking := false;
-  LastUnyuTalked := false;
-  for i := 0 to SsParser.Count-1 do begin
-    if (SsParser[i] = '\u') and not UnyuTalking then begin
-      UnyuTalking := true;
-    end;
-    if (SsParser[i] = '\h') and UnyuTalking then begin
-      UnyuTalking := false;
-    end;
-    if SsParser.MarkUpType[i] in [mtStr, mtMeta] then begin
-      if not Talked then begin
-        if UnyuTalking then Scr := '\82¤:' else Scr := '\82³:';
-      end;
-      if Talked and (UnyuTalking <> LastUnyuTalked) then begin
-        Scr := Scr + #13#10;
-        if UnyuTalking then Scr := Scr + '\82¤:' else Scr := Scr + '\82³:';
-      end;
-      Scr := Scr + SsParser[i];
-      Talked := true;
-      LastUnyuTalked := UnyuTalking;
-    end;
-  end;
-  edtScript.Text := Scr;
-end;
-
 procedure TfrmLog.lvwLogKeyPress(Sender: TObject; var Key: Char);
 begin
   if Key = #13 then lvwLogDblClick(Sender);
@@ -400,36 +478,34 @@ begin
     Selected := Selected;
 end;
 
-procedure TfrmLog.mnSaveLogClick(Sender: TObject);
-begin
-  SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
-  SaveDialog.DefaultExt := 'log';
-  SaveDialog.FilterIndex := 1;
-  if SaveDialog.Execute then
-    SelectedBottleLog.SaveToSstpLog(SaveDialog.FileName, false);
-end;
-
 procedure TfrmLog.lvwLogColumnClick(Sender: TObject; Column: TListColumn);
 var SortType: TBottleLogSortType;
     SelectedMID: String;
     SortColumn: integer;
 begin
+  if SelectedBottleLog = nil then
+    Exit;
   if lvwLog.Selected <> nil then
-    SelectedMID := SelectedBottleLog.Bottles[lvwLog.Selected.Index].MID;
+    SelectedMID := SelectedBottleLog.Bottles[lvwLog.Selected.Index].MID
+  else
+    SelectedMID := '';
 
   SortColumn := Column.Index;
   case SortColumn-1 of
     -1: SortType := stLogTime;
     subChannel: SortType := stChannel;
+    subGhost:   SortType := stGhost;
     subVotes:   SortType := stVote;
     subAgrees:  SortType := stAgree;
     subScript:  SortType := stScript;
-  else SortType := stLogTime;
+  else
+    SortType := stLogTime;
   end;
 
   SelectedBottleLog.SortBottles(SortType);
   lvwLog.Invalidate;
-  SelAndFocusMessage(SelectedMID);
+  if Length(SelectedMID) > 0 then
+    SelAndFocusMessage(SelectedMID);
 end;
 
 
@@ -444,99 +520,36 @@ begin
   Clip.SetTextBuf(PChar(Log.Script));
 end;
 
-procedure TfrmLog.SetBottleStatusToOpened(const MID: String);
-begin
-  if CurrentBottleLog.Bottle(MID) <> nil then begin
-    CurrentBottleLog.Bottle(MID).State := lsOpened;
-    lvwLog.OnChange := nil;
-    lvwLog.Invalidate;
-    lvwLog.OnChange := lvwLogChange;
-  end;
-end;
-
-procedure TfrmLog.SetBottleStatusToPlaying(const MID: String);
+procedure TfrmLog.SetBottleState(const MID: String; State: TLogState);
+var i: integer;
+    Bottle: TLogItem;
 begin
-  if CurrentBottleLog.Bottle(MID) <> nil then begin
-    CurrentBottleLog.Bottle(MID).State := lsPlaying;
-    lvwLog.OnChange := nil;
-    lvwLog.Invalidate;
-    lvwLog.OnChange := lvwLogChange;
+  for i := 0 to FBottleLogList.Count-1 do begin
+    Bottle := (FBottleLogList[i] as TBottleLogList).Bottle(MID);
+    if Bottle <> nil then begin
+      Bottle.State := State;
+      lvwLog.OnChange := nil;
+      lvwLog.Invalidate;
+      lvwLog.OnChange := lvwLogChange;
+    end;
   end;
 end;
 
-procedure TfrmLog.DoSaveLog(SaveType: TSaveLogType; Ext: String; Filter: integer);
-var i: integer;
-    Log: TStringList;
-    LogItem: TLogItem;
-    Date: String;
-const
-  DayStr: array[1..7] of String = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
+procedure TfrmLog.mnSaveLogClick(Sender: TObject);
 begin
+  if SelectedBottleLog = nil then Exit;
+  SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
-  SaveDialog.DefaultExt := Ext;
-  SaveDialog.FilterIndex := Filter;
-  if SaveDialog.Execute then begin
-    Log := nil;
-    try
-      Log := TStringList.Create;
-      case SaveType of
-        stLog, stLogWithChannels: begin
-          for i := 0 to SelectedBottleLog.Count -1 do begin
-            LogItem := SelectedBottleLog.Bottles[i];
-            if LogItem = nil then Continue;
-            if LogItem.LogType <> ltBottle then Continue;
-            Date := FormatDateTime('yyyy/mm/dd hh:nn:ss ', LogItem.LogTime);
-            Date := Date + '(' + DayStr[DayOfWeek(LogItem.LogTime)] + ')';
-            if SaveType = stLogWithChannels then
-              Date := Date + ',' + LogItem.Channel +',SEND,' + LogItem.Script
-            else
-              Date := Date + ',0.0.0.0,SEND,' + LogItem.Script;
-            Log.Add(Date);
-          end;
-        end;
-        stText: begin
-          for i := 0 to SelectedBottleLog.Count -1 do begin
-            LogItem := SelectedBottleLog.Bottles[i];
-            if LogItem = nil then Continue;
-            if LogItem.LogType <> ltBottle then Continue;
-            Log.Add(LogItem.Script);
-          end;
-        end;
-        stXML: begin
-          Log.Add('<?xml version=''1.0'' encoding=''Shift_JIS''?>');
-          Log.Add('<bottlelog saved=''' + FormatDateTime('yyyy/mm/dd hh:nn:ss', Now) + '''>');
-          for i := 0 to SelectedBottleLog.Count -1 do begin
-            LogItem := SelectedBottleLog.Bottles[i];
-            if LogItem = nil then Continue;
-            if LogItem.LogType <> ltBottle then Continue;
-            Date := FormatDateTime('yyyy/mm/dd hh:nn:ss', LogItem.LogTime);
-            Log.Add(Format('<message mid=''%s''>', [LogItem.MID]));
-            Log.Add('<date>' + Date + '</date>');
-            Log.Add('<channel>' + XmlEntity(LogItem.Channel) + '</channel>');
-            //
-            Log.Add('<script>' + XmlEntity(LogItem.Script) + '</script>');
-            Log.Add('<votes>' + IntToStr(LogItem.Votes) + '</votes>');
-            Log.Add('<agrees>' + IntToStr(LogItem.Agrees) + '</agrees>');
-            //
-            if LogItem.Ghost = '' then
-              Log.Add('<ghost />')
-            else begin
-              Log.Add(Format('<ghost>%s</ghost>', [XmlEntity(LogItem.Ghost)]));
-            end;
-            Log.Add('</message>');
-          end;
-          Log.Add('</bottlelog>');
-        end;
-      end;
-      Log.SaveToFile(SaveDialog.FileName);
-    finally
-      Log.Free;
-    end;
-  end;
+  SaveDialog.DefaultExt := 'log';
+  SaveDialog.FilterIndex := 1;
+  if SaveDialog.Execute then
+    SelectedBottleLog.SaveToSstpLog(SaveDialog.FileName, false);
 end;
 
 procedure TfrmLog.mnSaveLogChannelClick(Sender: TObject);
 begin
+  if SelectedBottleLog = nil then Exit;
+  SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
   SaveDialog.DefaultExt := 'log';
   SaveDialog.FilterIndex := 1;
@@ -546,6 +559,8 @@ end;
 
 procedure TfrmLog.mnSaveLogScriptClick(Sender: TObject);
 begin
+  if SelectedBottleLog = nil then Exit;
+  SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.txt');
   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
   SaveDialog.DefaultExt := 'txt';
   SaveDialog.FilterIndex := 2;
@@ -555,11 +570,8 @@ end;
 
 procedure TfrmLog.mnSaveLogXMLClick(Sender: TObject);
 begin
-  SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
-  SaveDialog.DefaultExt := 'xml';
-  SaveDialog.FilterIndex := 3;
-  if SaveDialog.Execute then
-    SelectedBottleLog.SaveToXmlFile(SaveDialog.FileName, XMLDocument);
+  if SelectedBottleLog = nil then Exit;
+  DoSaveLogXML(SelectedBottleLog);
 end;
 
 procedure TfrmLog.lvwLogData(Sender: TObject; Item: TListItem);
@@ -572,17 +584,21 @@ begin
   with Item do begin
     Caption := FormatDateTime('yy/mm/dd hh:nn:ss', Log.LogTime);
     SubItems.Clear;
-    if Log.Ghost <> '' then
-      SubItems.Add(Log.Channel + '/' + Log.Ghost)
-    else
-      SubItems.Add(Log.Channel);
+    SubItems.Add(Log.Channel);
+    SubItems.Add(Log.Ghost);
     if Log.LogType = ltBottle then begin
-      SubItems.Add(IntToStr(Log.Votes));
-      SubItems.Add(IntToStr(Log.Agrees));
+      if Log.Votes > 0 then
+        SubItems.Add(IntToStr(Log.Votes))
+      else
+        SubItems.Add('');
+      if Log.Agrees > 0 then
+        SubItems.Add(IntToStr(Log.Agrees))
+      else
+        SubItems.Add('');
     end else begin
       // \83V\83X\83e\83\80\83\8d\83O\82È\82Ç\82Í\93\8a\95[\81E\93¯\88Ó\82ð\95\\8e¦\82µ\82È\82¢
-      SubItems.Add('-');
-      SubItems.Add('-');
+      SubItems.Add('');
+      SubItems.Add('');
     end;
     SubItems.Add(Log.Script);
 
@@ -598,24 +614,36 @@ begin
 end;
 
 procedure TfrmLog.UpdateWindow;
+var EnabledFlag: boolean;
 begin
-  StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
-  if Pref.ColorScript then begin
-    if lvwLog.Color <> Pref.BgColor then lvwLog.Color := Pref.BgColor;
-    if lvwLog.Font.Color <> Pref.TalkColorH then lvwLog.Font.Color := Pref.TalkColorH;
+  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';
+    lvwLog.Items.Count := SelectedBottleLog.Count;
   end else begin
-    if lvwLog.Color <> clWindow then lvwLog.Color := clWindow;
-    if lvwLog.Font.Color <> clWindowText then lvwLog.Font.Color := clWindowText;
+    Caption := '\83\8d\83O';
+    StatusBar.Panels[0].Text := '';
+    StatusBar.Panels[1].Text := '';
+    lvwLog.Items.Count := 0;
   end;
-  lvwLog.Items.Count := SelectedBottleLog.Count;
+
+  EnabledFlag := SelectedBottleLog <> nil;
+  tbtnClear.Enabled := EnabledFlag;
+  tbtnSaveLog.Enabled := EnabledFlag;
+  tbtnFindBottle.Enabled := EnabledFlag;
+
   lvwLog.Invalidate;
-  //lvwLogChange(Self, lvwLog.Selected, ctState);
 end;
 
 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
@@ -625,14 +653,21 @@ begin
   if lvwLog.Selected = nil then Exit;
   Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
   if Log = nil then Exit;
-  Urls := nil;
+  Urls := TStringList.Create;
+  Labels := TStringList.Create;
   try
-    Urls := TStringList.Create;
-    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, Urls[i]]);
+        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;
         mnJumpURL.Add(Child);
@@ -641,45 +676,75 @@ begin
     mnJumpURL.Enabled := Urls.Count > 0;
   finally
     Urls.Free;
+    Labels.Free;
   end;
 end;
 
 procedure TfrmLog.mnURLClick(Sender: TObject);
-var URL: String;
+var LogItem: TLogItem;
+    URL: string;
+    Urls: TStringList;
+
 begin
-  URL := (Sender as TMenuItem).Caption;
-  RegExp.Subst('s/^\(&?\d\) //', URL);
-  ShellExecute(Handle, 'open', PChar(URL), nil, nil, SW_SHOW);
+  if (lvwLog.Selected = nil) or (SelectedBottleLog = nil) then Exit;
+  LogItem := SelectedBottleLog[lvwLog.Selected.Index] as TLogItem;
+  Urls := TStringList.Create;
+  try
+    ExtractURLs(LogItem.Script, Urls, nil);
+    URL := Urls[(Sender as TMenuItem).Tag];
+    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;
-  SsParser.LeaveEscape := false;
+  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);
@@ -701,66 +766,11 @@ begin
   //
 end;
 
-procedure TfrmLog.lvwLogCustomDrawSubItem(Sender: TCustomListView;
-  Item: TListItem; SubItem: Integer; State: TCustomDrawState;
-  var DefaultDraw: Boolean);
-{var
-  DestRect: TRect;
-  Script: String;
-  i, x, w: integer;
-  SavedDC: integer;
-  Mark: TSsMarkUpType;}
-begin
-  Exit // !!
-  {if (SubItem <> SubScript+1) or (not Pref.ColorScript) then Exit; // DefaultDraw = true
-  // Custom Script Coloring
-  DefaultDraw := false;
-  SavedDC := SaveDC(lvwLog.Canvas.Handle);
-  try
-    ListView_GetSubItemRect(lvwLog.Handle, Item.Index, SubScript+1, LVIR_BOUNDS, @DestRect);
-
-    lvwLog.Canvas.Brush.Style := bsSolid;
-    if cdsSelected in State then begin
-      lvwLog.Canvas.Brush.Color := clHighlight
-    end else begin
-      lvwLog.Canvas.Brush.Color := Pref.BgColor;
-    end;
-    lvwLog.Canvas.FillRect(DestRect);
-    lvwLog.Canvas.Brush.Style := bsClear;
-
-    Script := Item.SubItems[SubScript];
-    // DrawTextEx(lvwLog.Canvas.Handle, PChar(Script), -1, DestRect, DT_END_ELLIPSIS, nil);
-    SsParser.InputString := Script;
-    x := 6;
-    for i := 0 to SsParser.Count - 1 do begin
-      Mark := SsParser.MarkUpType[i];
-      case Mark of
-        mtMeta:   lvwLog.Canvas.Font.Color := Pref.MetaWordColor;
-        mtTag:    lvwLog.Canvas.Font.Color := Pref.MarkUpColor;
-        mtTagErr: lvwLog.Canvas.Font.Color := Pref.MarkErrorColor;
-        else begin
-          lvwLog.Canvas.Font.Color := Pref.TalkColorH;
-        end;
-      end;
-      w := lvwLog.Canvas.TextWidth(SsParser[i]);
-      lvwLog.Canvas.TextRect(DestRect, DestRect.Left + x, DestRect.Top + 2, SsParser[i]);
-      x := x + w;
-      if DestRect.Right - DestRect.Left < x then Break;
-    end;
-  finally
-    RestoreDC(lvwLog.Canvas.Handle, SavedDC);
-  end;}
-end;
-
 procedure TfrmLog.UpdateScript(const Script: String);
 begin
   if Script <> FLastScript then begin
     if Pref.LogWindowPreviewStyle = psConversation then begin
-      if Pref.ColorScript then begin
-        UpdateScriptConversationColor(Script);
-      end else begin
-        UpdateScriptConversationNoColor(Script);
-      end;
+      UpdateScriptConversationColor(Script);
     end else begin
       UpdateScriptScript(Script);
     end;
@@ -784,46 +794,44 @@ begin
     for i := 0 to Items.Count-1 do
       Items[i].Checked := (Sender as TMenuItem).Tag = Items[i].Tag;
   Pref.LogWindowPreviewStyle := TLogWindowPreviewStyle((Sender as TMenuItem).Tag);
+  PreviewStyleChange;
   FLastScript := '';
   lvwLogChange(self, lvwLog.Selected, ctState);
 end;
 
 procedure TfrmLog.UpdateScriptScript(const Script: String);
 var
-  UnyuTalking: boolean;
+  UnyuTalking, InSynchronized: boolean;
   i: integer;
 begin
-  if Pref.ColorScript then begin
-    edtScript.Color := Pref.BgColor;
-  end else begin
-    edtScript.Color := clWindow;
-    edtScript.DefAttributes.Color := clWindowText;
-    edtScript.SelAttributes.Color := clWindowText;
-  end;
+  edtScript.Color := Pref.BgColor;
   SsParser.LeaveEscape := true;
   SsParser.InputString := Script;
   edtScript.Text := '';
   edtScript.SelAttributes.Color := clWindowText;
   UnyuTalking := false;
+  InSynchronized := false;
   for i := 0 to SsParser.Count-1 do begin
-    if Pref.ColorScript then begin
-      case SsParser.MarkUpType[i] of
-        mtStr: begin
-          if UnyuTalking then
-            edtScript.SelAttributes.Color := Pref.TalkColorU
-          else
-            edtScript.SelAttributes.Color := Pref.TalkColorH;
-        end;
-        mtTag: begin
-          edtScript.SelAttributes.Color := Pref.MarkUpColor;
-          if SsParser[i] = '\h' then
-            UnyuTalking := false
-          else if SsParser[i] = '\u' then
-            UnyuTalking := true;
-        end;
-        mtMeta:   edtScript.SelAttributes.Color := Pref.MetaWordColor;
-        mtTagErr: edtScript.SelAttributes.Color := Pref.MarkErrorColor;
+    case SsParser.MarkUpType[i] of
+      mtStr: begin
+        if InSynchronized then
+          edtScript.SelAttributes.Color := Pref.TalkColorS
+        else if UnyuTalking then
+          edtScript.SelAttributes.Color := Pref.TalkColorU
+        else
+          edtScript.SelAttributes.Color := Pref.TalkColorH;
       end;
+      mtTag: begin
+        edtScript.SelAttributes.Color := Pref.MarkUpColor;
+        if SsParser[i] = '\h' then
+          UnyuTalking := false
+        else if SsParser[i] = '\u' then
+          UnyuTalking := true
+        else if SsParser[i] = '\_s' then
+          InSynchronized := not InSynchronized;
+      end;
+      mtMeta:   edtScript.SelAttributes.Color := Pref.MetaWordColor;
+      mtTagErr: edtScript.SelAttributes.Color := Pref.MarkErrorColor;
     end;
     edtScript.SelText := SsParser[i];
     if (SsParser[i] = '\n') and (Pref.LogWindowPreviewStyle = psScriptWithLineBreak) then
@@ -839,34 +847,29 @@ begin
   if sel > Ord(High(TLogWindowPreviewStyle)) then sel := 0;
   Pref.LogWindowPreviewStyle := TLogWindowPreviewStyle(sel);
   FLastScript := '';
+  PreviewStyleChange;
   lvwLogChange(self, lvwLog.Selected, ctState);
 end;
 
-function TfrmLog.XmlEntity(S: String): String;
-begin
-  S := StringReplace(S, '&', '&amp;', [rfReplaceAll]);
-  S := StringReplace(S, '<', '&lt;', [rfReplaceAll]);
-  S := StringReplace(S, '>', '&gt;', [rfReplaceAll]);
-  Result := S;
-end;
-
 function TfrmLog.SelectedBottleLog: TBottleLogList;
 begin
-  Result := FBottleLogList.Items[tabBottleLog.TabIndex] as TBottleLogList;
-end;
-
-function TfrmLog.GetCurrentBottleLog: TBottleLogList;
-begin
-  Result := FBottleLogList.Items[0] as TBottleLogList;
+  if tabBottleLog.TabIndex >= 0 then
+    Result := FBottleLogList.Items[tabBottleLog.TabIndex] as TBottleLogList
+  else
+    Result := nil;
 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;
 
@@ -878,23 +881,26 @@ begin
 end;
 
 procedure TfrmLog.UpdateTab;
-var i: integer;
-    cur: TBottleLogList;
+var i, cur: integer;
 begin
-  cur := SelectedBottleLog;
+  cur := tabBottleLog.tabIndex;
   tabBottleLog.Tabs.Clear;
   for i := 0 to FBottleLogList.Count - 1 do begin
     tabBottleLog.Tabs.Add((FBottleLogList[i] as TBottleLogList).Title);
   end;
-  tabBottleLog.TabIndex := FBottleLogList.IndexOf(cur);
+  if FBottleLogList.Count > 0 then begin
+    if cur < FBottleLogList.Count then
+      tabBottleLog.TabIndex := cur
+    else
+      tabBottleLog.TabIndex := FBottleLogList.Count-1;
+  end;
 end;
 
 procedure TfrmLog.LogLoadFailure(Sender: TObject; const Message: String);
 begin
   Beep;
   ShowMessage(Message);
-  (Sender as TBottleLogList).AddSystemLog(Message);
-  lvwLog.Invalidate;
+  if Sender = SelectedBottleLog then UpdateWindow;
 end;
 
 procedure TfrmLog.AgreeLog(const MID: String; const Agree: integer);
@@ -928,6 +934,8 @@ end;
 procedure TfrmLog.tabBottleLogChanging(Sender: TObject;
   var AllowChange: Boolean);
 begin
+  // \8c»\8dÝ\91I\91ð\82³\82ê\82Ä\82¢\82é\83\8d\83O\82Ì\91I\91ð\8fó\91Ô\82ð\95Û\91
+  if SelectedBottleLog = nil then Exit;
   if lvwLog.Selected <> nil then
     SelectedBottleLog.SelectedIndex := lvwLog.Selected.Index
   else
@@ -940,54 +948,883 @@ begin
   with tabBottleLog do begin
     Tag := IndexOfTabAt(MousePos.X, MousePos.Y);
     if Tag < 0 then Handled := true;
-    mnCloseTab.Enabled := Tag > 0;
   end;
 end;
 
 procedure TfrmLog.mnCloseTabClick(Sender: TObject);
 begin
-  if tabBottleLog.Tag = 0 then Exit;
-  FBottleLogList.Delete(tabBottleLog.Tag);
-  tabBottleLog.TabIndex := 0;
-  UpdateTab;
-  UpdateWindow;
-  lvwLogChange(Self, nil, ctState);
+  DoCloseTab(tabBottleLog.Tag);
 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.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);
+var BottleLog: TBottleLogList;
+    i, Index: integer;
+begin
+  Index := -1;
+  if OpenDialog.Execute then begin
+    for i := 0 to OpenDialog.Files.Count-1 do begin
+      BottleLog := TBottleLogList.Create(ExtractFileName(OpenDialog.Files[i]));
+      try
+        with BottleLog do
+        begin
+          OnLoaded := LogLoaded;
+          OnLoadFailure := LogLoadFailure;
+          OnLoadWork := LogLoadWork;
+          BottleLog.LoadFromXMLFile(OpenDialog.Files[i]);
+        end;
+        Index := BottleLogList.Add(BottleLog); // \8dÅ\8cã\82É\8aJ\82¢\82½\83\8d\83O\82Ì\88Ê\92u\82ð\8bL\89¯
+      except
+        BottleLog.Free;
+      end;
+    end;
+    UpdateTab;
+    if Index >= 0 then tabBottleLog.TabIndex := Index;
+    UpdateWindow;
+  end;
+end;
+
+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;
+
+function TfrmLog.BottleLogTitled(const LogName: String): TBottleLogList;
+var i: integer;
+begin
+  for i := 0 to FBottleLogList.Count-1 do begin
+    if (FBottleLogList[i] as TBottleLogList).Title = LogName then begin
+      Result := (FBottleLogList[i] as TBottleLogList);
+      Exit;
+    end;
+  end;
+  // \8c©\82Â\82©\82ç\82È\82¢\8fê\8d\87
+  Result := TBottleLogList.Create(LogName); // \90V\82µ\82­\8dì\82é
+  FBottleLogList.Add(Result);
+  UpdateTab;
+  if FBottleLogList.Count = 1 then tabBottleLog.TabIndex := 0;
+end;
+
+procedure TfrmLog.AllBottleOpened;
+var i, j: integer;
+    Log: TBottleLogList;
+begin
+  for i := 0 to FBottleLogList.Count-1 do begin
+    Log  := FBottleLogList[i] as TBottleLogList;
+    for j := 0 to Log.Count-1 do begin
+      Log.Bottles[j].State := lsOpened;
+    end;
+  end;
+end;
+
+procedure TfrmLog.tabBottleLogMouseDown(Sender: TObject;
+  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+var Index: integer;
+begin
+  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));
+  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;
+
+procedure TfrmLog.tabBottleLogDragOver(Sender, Source: TObject; X,
+  Y: Integer; State: TDragState; var Accept: Boolean);
+var TargetRect: TRect;
+    OldDest, Index: integer;
+    dummy: boolean;
+begin
+  // \83^\83u\82Ì\83h\83\89\83b\83O(\83^\83u\82Ì\8f\87\94Ô\93ü\82ê\91Ö\82¦)\82Ü\82½\82Í\81A
+  // \83\8d\83O\83A\83C\83e\83\80\82Ì\83h\83\89\83b\83O(\83\8d\83O\82ð\95Ê\82Ì\83^\83u\82É\88Ú\93®)\82Ì
+  // \97¼\95û\82Ì\83h\83\89\83b\83O\82ð\8eó\82¯\95t\82¯\82é
+  Accept := false;
+  if Source = tabBottleLog then
+  begin
+    // \83^\83u\82Ì\8f\87\94Ô\93ü\82ê\91Ö\82¦\82Ì\8fê\8d\87
+    Accept := true;
+    with tabBottleLog do begin
+      OldDest := FDragTabDest;
+      FDragTabDest := IndexOfTabAt(X, Y);
+      if FDragTabDest = -1 then begin
+        Accept := false; //\82±\82Ì\8fê\8d\87\82Í\83h\83\8d\83b\83v\82ð\94F\82ß\82È\82¢
+        Exit;
+      end;
+      with Canvas do begin
+        Pen.Mode := pmNot;
+        Pen.Width := 3;
+      end;
+      if (OldDest <> FDragTabDest) and (OldDest >= 0) then begin
+        //\88È\91O\82Ì\98g\90ü\8fÁ\8b\8e
+        TargetRect := TabRect(OldDest);
+        with Canvas do begin
+          Brush.Style := bsClear;
+          Rectangle(TargetRect.Left, TargetRect.Top,
+                    TargetRect.Right, TargetRect.Bottom);
+        end;
+      end;
+      if (OldDest <> FDragTabDest) then begin
+        //\90V\82µ\82¢\98g\90ü\95`\89æ
+        TargetRect := TabRect(FDragTabDest);
+        with Canvas do begin
+          Brush.Style := bsClear;
+          Rectangle(TargetRect.Left, TargetRect.Top,
+                    TargetRect.Right, TargetRect.Bottom);
+        end;
+      end;
+    end;
+  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
+    Index := tabBottleLog.IndexOfTabAt(X, Y);
+    if tabBottleLog.TabIndex <> Index then
+    begin
+      FLVDragDest := -1; // \98g\90ü\82Í\82Ü\82¾\95\\8e¦\82³\82ê\82È\82¢\82Í\82¸
+      // \83^\83u\82ð\90Ø\91Ö\82¦\82é
+      tabBottleLogChanging(Self, dummy);
+      tabBottleLog.TabIndex := Index;
+      UpdateWindow;
+    end;
+  end;
+end;
+
+procedure TfrmLog.tabBottleLogDragDrop(Sender, Source: TObject; X,
+  Y: Integer);
+var DestIndex: integer;
+begin
+  with tabBottleLog do begin
+    DestIndex := IndexOfTabAt(X, Y);
+    Tabs.Move(FDragTabIndex, DestIndex);
+    FBottleLogList.Move(FDragTabIndex, DestIndex);
+  end;
+end;
+
+procedure TfrmLog.tabBottleLogEndDrag(Sender, Target: TObject; X,
+  Y: Integer);
+begin
+  //\8b­\90§\93I\82É\83^\83u\82ð\8dÄ\95`\89æ\82³\82¹\82é\81B\98g\90ü\8fÁ\82µ\91Î\8dô
+  tabBottleLog.Tabs.BeginUpdate;
+  tabBottleLog.Tabs.EndUpdate;
+end;
+
+procedure TfrmLog.LogLoadWork(Sender: TObject);
+begin
+  if Sender = SelectedBottleLog then
+  begin
+    lvwLog.Invalidate;
+    lvwLog.Items.Count := SelectedBottleLog.Count;
+  end;
+end;
+
+procedure TfrmLog.lvwLogDrawItem(Sender: TCustomListView; Item: TListItem;
+  Rect: TRect; State: TOwnerDrawState);
+var
+  DestRect: TRect;
+  Script: String;
+  Ico: TIcon;
+  sub, Ex: integer;
+  Bottle: TLogItem;
+begin
+  Bottle := SelectedBottleLog.Bottles[Item.Index];
+  if Bottle.HasURL = huUndefined then
+  begin
+    try
+      if ExtractURLs(Bottle.Script, nil, nil) then
+        Bottle.HasURL := huYes
+      else
+        Bottle.HasURL := huNo;
+    finally
+    
+    end;
+  end;
+
+  // \94w\8ci\8fÁ\8b\8e
+  ListView_GetItemRect(lvwLog.Handle, Item.Index, DestRect, LVIR_BOUNDS);
+
+  // \94w\8ci\82Ì\90F\82Í\91I\91ð\8fó\91Ô\81E\91I\91ð\94ñ\83A\83N\83e\83B\83u\8fó\91Ô\81E\92Ê\8fí\8fó\91Ô\82Ì3\92Ê\82è
+  lvwLog.Canvas.Brush.Style := bsSolid;
+  if Item.Selected then begin
+    if lvwLog.Focused then
+      lvwLog.Canvas.Brush.Color := clHighlight
+    else
+      lvwLog.Canvas.Brush.Color := clBtnFace;
+  end else begin
+    lvwLog.Canvas.Brush.Color := Pref.BgColor;
+  end;
+  lvwLog.Canvas.FillRect(DestRect);
+  lvwLog.Canvas.Brush.Style := bsClear;
+  // \83t\83H\81[\83J\83X\82ª\82 \82é\8fê\8d\87\82É\82Í\83t\83H\81[\83J\83X\82Ì\98g\90ü\82ð\88ø\82­
+  if Item.Focused and lvwLog.Focused then
+    lvwLog.Canvas.DrawFocusRect(DestRect);
+
+  // \83h\83\89\83b\83O\92\86\82È\82ç\98g\90ü\82ð\95`\89æ\82·\82é
+  if FLVDragDest = Item.Index then
+  begin
+    DestRect := Item.DisplayRect(drBounds);
+    DrawListViewDragBorder(DestRect);
+  end;
+
+  if Item.Selected then
+  begin
+    if lvwLog.Focused then
+      lvwLog.Canvas.Font.Color := clHighlightText
+    else
+      lvwLog.Canvas.Font.Color := clWindowText;
+  end else
+  lvwLog.Canvas.Font.Color := Pref.TextColor;
+  lvwLog.Canvas.Refresh;
+
+  // \83L\83\83\83v\83V\83\87\83\93(\93ú\95t)
+  ListView_GetItemRect(lvwLog.Handle, Item.Index, DestRect, LVIR_LABEL);
+  Inc(DestRect.Left, 2);
+  Inc(DestRect.Top, 2);
+  Dec(DestRect.Right, 2);
+  DrawTextEx(lvwLog.Canvas.Handle, PChar(Item.Caption), -1, DestRect,
+    DT_SINGLELINE or DT_RIGHT, nil);
+  ListView_GetItemRect(lvwLog.Handle, Item.Index, DestRect, LVIR_ICON);
+  Ico := TIcon.Create;
+  try
+    lvwLog.SmallImages.GetIcon(Item.ImageIndex, Ico);
+    lvwLog.Canvas.Draw(DestRect.Left, DestRect.Top, Ico);
+  finally
+    Ico.Free;
+  end;
+  // \83L\83\83\83v\83V\83\87\83\93\82Å\82à\83X\83N\83\8a\83v\83g\82Å\82à\82È\82¢\82à\82Ì
+  for sub := 0 to Item.SubItems.Count-1 do
+  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);
+    Ex := DT_NOPREFIX or DT_SINGLELINE or DT_END_ELLIPSIS;
+    if lvwLog.Columns[sub+1].Alignment = taRightJustify then
+      Ex := Ex or DT_RIGHT;
+    DrawTextEx(lvwLog.Canvas.Handle, PChar(Item.SubItems[sub]), -1, DestRect,
+      Ex, nil);
+  end;
+  // \83X\83N\83\8a\83v\83g
+  ListView_GetSubItemRect(lvwLog.Handle, Item.Index, SubScript + 1,
+    LVIR_BOUNDS, @DestRect);
+  Script := Item.SubItems[SubScript];
+  DrawSingleLineScript(Bottle, DestRect, Item);
+
+end;
+
+procedure TfrmLog.DrawSingleLineScript(LogItem: TLogItem;
+  Rect: TRect; Item: TListItem);
+var
+  i, x, w: integer;
+  UnyuTalking, Synchronized, Spaced: boolean;
+  Mark: TSsMarkUpType;
+  Script: String;
+  Ico: TIcon;
+  procedure ScopeChange;
+  begin
+    if (not Spaced) and (Pref.LogListPreviewStyle = psTagStripped) then
+    begin
+      Inc(x, 7);
+      Spaced := true;
+    end;
+  end;
+begin
+  Script := LogItem.Script;
+  x := 3;
+
+  if LogItem.HasURL = huYes then
+  begin
+    Ico := TIcon.Create;
+    try
+      lvwLog.SmallImages.GetIcon(IconURL, Ico);
+      lvwLog.Canvas.Draw(Rect.Left + x, Rect.Top, Ico);
+      Inc(x, 20);
+    finally
+      Ico.Free;
+    end;
+  end;
+
+  if Pref.LogListPreviewStyle = psNoColor then
+  begin
+    Inc(Rect.Left, x);
+    Inc(Rect.Top, 2);
+    Dec(Rect.Right, 2);
+    DrawTextEx(lvwLog.Canvas.Handle, PChar(Script), -1, Rect,
+      DT_SINGLELINE or DT_END_ELLIPSIS or DT_NOPREFIX, nil);
+    Exit;
+  end;
+
+  SsParser.LeaveEscape := Pref.LogListPreviewStyle = psNormal;
+  SsParser.InputString := Script;
+
+  UnyuTalking := false;
+  Synchronized := false;
+  Spaced := true; // \83^\83O\8fÈ\97ª\95\\8e¦\8e\9e\82É\95s\95K\97v\82É\83X\83R\81[\83v\95Ï\8a·\8e\9e\82Ì\83X\83y\81[\83X\82ð\8bó\82¯\82È\82¢
+                  // \82½\82ß\82Ì\83t\83\89\83O
+  for i := 0 to SsParser.Count - 1 do begin
+    if SsParser[i] = '\h' then
+    begin
+      UnyuTalking := false;
+      ScopeChange;
+    end else if SsParser[i] = '\u' then
+    begin
+      UnyuTalking := true;
+      ScopeChange;
+    end else if SsParser[i] = '\_s' then
+    begin
+      Synchronized := not Synchronized;
+      ScopeChange;
+    end else if (Pos('\n', SsParser[i]) = 1) or (SsParser[i] = '\c') then
+    begin
+      ScopeChange;
+    end;
+    Mark := SsParser.MarkUpType[i];
+    case Mark of
+      mtMeta:
+        begin
+          lvwLog.Canvas.Font.Color := Pref.MetaWordColor;
+          Spaced := false;
+        end;
+      mtTag:
+        if Pref.LogListPreviewStyle = psNormal then
+          lvwLog.Canvas.Font.Color := Pref.MarkUpColor
+        else
+        begin
+          Continue;
+        end;
+      mtTagErr:
+        lvwLog.Canvas.Font.Color := Pref.MarkErrorColor;
+      else begin
+        Spaced := false;
+        if Synchronized then
+          lvwLog.Canvas.Font.Color := Pref.TalkColorS
+        else if UnyuTalking then
+          lvwLog.Canvas.Font.Color := Pref.TalkColorU
+        else
+          lvwLog.Canvas.Font.Color := Pref.TalkColorH;
+      end;
+    end;
+    if Item.Selected then
+    begin
+      if lvwLog.Focused then
+        lvwLog.Canvas.Font.Color := clHighlightText
+      else
+        lvwLog.Canvas.Font.Color := clWindowText;
+    end;
+    lvwLog.Canvas.Refresh;
+    w := lvwLog.Canvas.TextWidth(SsParser[i]);
+    lvwLog.Canvas.TextRect(Rect, Rect.Left + x, Rect.Top + 2, SsParser[i]);
+    x := x + w;
+    if Rect.Right - Rect.Left < x then Break;
+  end;
+end;
+
+procedure TfrmLog.mnListPreviewStyleClick(Sender: TObject);
+var i: integer;
+begin
+  with PopupMenuListPreviewStyle do
+    for i := 0 to Items.Count-1 do
+      Items[i].Checked := (Sender as TMenuItem).Tag = Items[i].Tag;
+  Pref.LogListPreviewStyle := TLogListPreviewStyle((Sender as TMenuItem).Tag);
+  lvwLog.Invalidate;
+end;
+
+procedure TfrmLog.tbtnListPreviewStyleClick(Sender: TObject);
+var sel: integer;
+begin
+  sel := Ord(Pref.LogListPreviewStyle);
+  sel := sel + 1;
+  if sel > Ord(High(TLogListPreviewStyle)) then sel := 0;
+  Pref.LogListPreviewStyle := TLogListPreviewStyle(sel);
+  lvwLog.Invalidate;
+end;
+
+procedure TfrmLog.PopupMenuListPreviewStylePopup(Sender: TObject);
+var i: integer;
+begin
+  with PopupMenuListPreviewStyle do
+    for i := 0 to Items.Count-1 do
+      Items[i].Checked := Items[i].Tag = Ord(Pref.LogListPreviewStyle)
+end;
+
+procedure TfrmLog.PreviewStyleChange;
+begin
+  if Pref.LogWindowPreviewStyle = psImageConversation then
+  begin
+    if Spps.Count = 0 then
+      ShowMessage('\83T\81[\83t\83B\83X\83v\83\8c\83r\83\85\81[\97p\83v\83\89\83O\83C\83\93\82ª\91\8dÝ\82µ\82Ü\82¹\82ñ\81B');
+    edtScript.Visible := false;
+    TalkShowFrame.Visible := true;
+  end else
+  begin
+    edtScript.Visible := true;
+    TalkShowFrame.Visible := false;
+  end;
+end;
+
+procedure TfrmLog.lvwLogDragOver(Sender, Source: TObject; X, Y: Integer;
+  State: TDragState; var Accept: Boolean);
+var
+  Target: TListItem;
+  OldDest: integer;
+  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;
+
+  Target := lvwLog.GetItemAt(X, Y);
+
+  // \82±\82ê\88È\91O\82É\95`\89æ\82³\82ê\82Ä\82¢\82½\98g\82Ì\83C\83\93\83f\83b\83N\83X
+  OldDest := FLVDragDest;
+
+  // \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;
+    FLVDragDest := Target.Index;
+  end else
+  begin
+    Accept := true;
+    FLVDragDest := -1;
+  end;
+
+  // \88È\91O\82Ì\98g\90ü\82ð\8dí\8f\9c
+  if (OldDest > -1) and (FLVDragDest <> OldDest) then
+  begin
+    Rec := lvwLog.Items[OldDest].DisplayRect(drBounds);
+    DrawListViewDragBorder(Rec);
+  end;
+  // \83h\83\89\83b\83O\90æ\82Ì\98g\90ü\82ð\95`\89æ
+  if (Target <> nil) and (FLVDragDest <> OldDest) then
+  begin
+    Rec := Target.DisplayRect(drBounds);
+    DrawListViewDragBorder(Rec);
+  end;
+
+  // \83X\83N\83\8d\81[\83\8b\8aÖ\8cW
+  if lvwLog.Items.Count > 0 then
+  begin
+    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;
+
+procedure TfrmLog.lvwLogDragDrop(Sender, Source: TObject; X, Y: Integer);
+var
+  TargetItem: integer;
+  Src: TBottleLogDragObject;
+  SrcLog: TObject;
+begin
+  timScrollTimer.Enabled := false;
+
+  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é
+    if (GetAsyncKeyState(VK_CONTROL) and $8000) > 0 then
+    begin // \83R\83s\81[\88Ú\93®\82Ì\8fê\8d\87
+      SrcLog := TLogItem.Create(Src.LogItem);
+    end else // \88Ú\93®\82¾\82¯\82·\82é\8fê\8d\87
+    begin
+      SrcLog := Src.BottleLogList.Extract(Src.LogItem);
+    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;
+
+procedure TfrmLog.timScrollTimerTimer(Sender: TObject);
+var
+  ScrollHeight: Integer;
+begin
+  // \83X\83N\83\8d\81[\83\8b\97Ê\82ð\8b\81\82ß\82é
+  ScrollHeight := 0;
+  if lvwLog.Items.Count > 2 then
+  begin
+    ScrollHeight := lvwLog.Items[1].Top - lvwLog.Items[0].Top;
+  end;
+
+  case FLVScrollDir of
+    lvScrollUp: lvwLog.Scroll(0, ScrollHeight);
+    lvSCrollDown: lvwLog.Scroll(0, -ScrollHeight);
+  end;
+  lvwLog.Invalidate;  // \8dÅ\90V\82Ì\8fó\91Ô\82É\8dÄ\95`\89æ\82·\82é
+  lvwLog.Update;
+end;
+
+procedure TfrmLog.mnChangeTabNameClick(Sender: TObject);
+var Name: String;
+begin
+  Name := (FBottleLogList[tabBottleLog.Tag] as TBottleLogList).Title;
+  InputQuery('\96¼\91O\82Ì\95Ï\8dX', '\90V\82µ\82¢\83^\83u\82Ì\96¼\91O', Name);
+  (FBottleLogList[tabBottleLog.Tag] as TBottleLogList).Title := Name;
+  UpdateTab;
+end;
+
+procedure TfrmLog.lvwLogStartDrag(Sender: TObject;
+  var DragObject: TDragObject);
+var Drag: TBottleLogDragObject;
+begin
+  // \92Ê\8fí\82ÌListView\97p\82Ì\83h\83\89\83b\83O\83I\83u\83W\83F\83N\83g\82Í
+  // OS\82É\82æ\82Á\82Ä\82Í\81A\88Ú\93®\82·\82é\82Æ\82«\82É\83A\83C\83e\83\80\82Ì\83C\83\81\81[\83W\82ð\94¼\93§\96¾\82Å\95`\89æ\82µ\82Ä\82µ\82Ü\82¤\81B
+  // TDragObject\82©\82ç\92¼\90Ú\8cp\8f³\82µ\82½\82¾\82¯\82Ì\82à\82Ì(\83C\83\81\81[\83W\82ð\8e\9d\82Á\82Ä\82¢\82È\82¢)\82ð\8eg\82¤\82Æ
+  // \94¼\93§\96¾\83C\83\81\81[\83W\82Ì\95`\89æ\82Í\97}\90§\82Å\82«\82é\81B
+  Drag := TBottleLogDragObject.Create(lvwLog);
+  Drag.BottleLogList := SelectedBottleLog;
+  Drag.LogItem := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
+  DragObject := Drag;
+end;
+
+procedure TfrmLog.lvwLogEndDrag(Sender, Target: TObject; X, Y: Integer);
+begin
+  // \98g\90ü\8fÁ\82µ\97p\82É\8b­\90§\93I\82É\8dÄ\95`\89æ\82³\82¹\82é
+  timScrollTimer.Enabled := false;
+  FLVDragDest := -1;
+  UpdateWindow;
+end;
+
+procedure TfrmLog.DrawListViewDragBorder(const Rect: TRect);
+var Rec: TRect;
+begin
+  Rec := Rect;
+  InflateRect(Rec, -1, -1);
+  with lvwLog.Canvas do
+  begin
+    Pen.Mode := pmNot;
+    Pen.Width := 3;
+    Brush.Style := bsClear;
+    Refresh; // \95K\97v
+    Rectangle(Rec);
+  end;
+end;
+
+procedure TfrmLog.DoSaveLogXML(Log: TBottleLogList);
+begin
+  SaveDialog.FileName := GetDefaultFileName(Log.Title, '.xml');
+  SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
+  SaveDialog.DefaultExt := 'xml';
+  SaveDialog.FilterIndex := 3;
+  if SaveDialog.Execute then
+    Log.SaveToXmlFile(SaveDialog.FileName);
+end;
+
+procedure TfrmLog.DoCloseTab(const Index: integer);
+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 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;
+  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;
+begin
+  // \92\86\93r\94¼\92[\82È\83h\83\89\83b\83O\83C\83\81\81[\83W\82ð\95\\8e¦\82µ\82È\82¢\82æ\82¤\82É\82·\82é
+  Result := nil;
+end;
+
+procedure TBottleLogDragObject.SetBottleLogList(
+  const Value: TBottleLogList);
+begin
+  FBottleLogList := Value;
+end;
+
+procedure TBottleLogDragObject.SetLogItem(const Value: TLogItem);
+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;
 
 end.