OSDN Git Service

SLPPの返り値がエラーだった場合+ExtraMessageがなかった場合に、デバッグ用に何のコマンドかを表示するようにした。
[winbottle/winbottle.git] / bottleclient / MainForm.pas
index 3fc78b5..68a94cc 100755 (executable)
@@ -12,13 +12,15 @@ uses
   Menus, StdCtrls, ComCtrls, BRegExp, BottleDef, BottleSstp,
   DirectSstp, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
   IdSLPP20, SsParser, ImgList, AppEvnts, TaskTray, StdActns,
-  ActnList, MPlayer, MenuBar, ToolWin,
-  IniFiles, ExtCtrls, ShellAPI, Contnrs,
-  ConstEditor, Buttons, Clipbrd, HeadValue, Logs,
+  ActnList, MPlayer, MenuBar, ToolWin, SsPlayTime, heClasses, heFountain,
+  SakuraScriptFountain, HEditor,
+  IniFiles, ShellAPI, Contnrs,
+  ConstEditor, Buttons, Clipbrd, HeadValue, Logs, MultipleChoiceEditor,
   IdException, HttpThread, IdHTTP, LogDownload,
   ScriptConsts, DateUtils, BottleChainRule, BottleChainEvent,
-  SakuraSeekerInstance, HEditor, HTSearch, heClasses, heFountain,
-  SakuraScriptFountain, SppList, SurfacePreview, XDOM_2_3_J3;
+  SakuraSeekerInstance, HTSearch,
+  SppList, SurfacePreview, XDOM_2_3_J3,
+  RegexUtils, StrReplace, StrReplaceDialog, ReplacePresetEditor, ExtCtrls;
 
 type
   TSurfacePreviewType = (spHint, spEditor);
@@ -122,7 +124,6 @@ type
     mnLeaveThisChannel: TMenuItem;
     N4: TMenuItem;
     mnGotoVote: TMenuItem;
-    mnGotoGLog: TMenuItem;
     mnGoToHelp: TMenuItem;
     btnSend: TButton;
     btnConfirm: TButton;
@@ -160,7 +161,6 @@ type
     actResetPlugins: TAction;
     N7: TMenuItem;
     mnResetPlugins: TMenuItem;
-    ReplaceDialog: TReplaceDialog;
     actReplace: TAction;
     N10: TMenuItem;
     mnReplace: TMenuItem;
@@ -171,6 +171,13 @@ type
     actAbout: TAction;
     actEditCopy: TEditCopy;
     tbtnSendToLogWindow: TToolButton;
+    SsPlayTime: TSsPlayTime;
+    actUndo: TAction;
+    actRedo: TAction;
+    mnUndo: TMenuItem;
+    mnRedo: TMenuItem;
+    N9: TMenuItem;
+    mnPresetReplaceRoot: TMenuItem;
     procedure actConfirmExecute(Sender: TObject);
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
@@ -222,7 +229,6 @@ type
       var Handled: Boolean);
     procedure mnLeaveThisChannelClick(Sender: TObject);
     procedure mnGotoVoteClick(Sender: TObject);
-    procedure mnGotoGLogClick(Sender: TObject);
     procedure tabChannelMouseMove(Sender: TObject; Shift: TShiftState; X,
       Y: Integer);
     procedure mnGoToHelpClick(Sender: TObject);
@@ -256,28 +262,29 @@ type
     procedure actResetPluginsExecute(Sender: TObject);
     procedure IdSLPP20Connect(Sender: TObject);
     procedure actReplaceExecute(Sender: TObject);
-    procedure ReplaceDialogFind(Sender: TObject);
-    procedure ReplaceDialogReplace(Sender: TObject);
     procedure actSendToEditorExecute(Sender: TObject);
     procedure actSendToLogWindowExecute(Sender: TObject);
     procedure memScriptDragOver(Sender, Source: TObject; X, Y: Integer;
       State: TDragState; var Accept: Boolean);
     procedure memScriptDragDrop(Sender, Source: TObject; X, Y: Integer);
     procedure actDeleteLogItemExecute(Sender: TObject);
+    procedure memScriptSelectionChange(Sender: TObject; Selected: Boolean);
+    procedure actUndoExecute(Sender: TObject);
+    procedure actRedoExecute(Sender: TObject);
+    procedure IdSLPP20ConnectFailed(Sender: TObject);
   private
     FSleeping: boolean;  // \94z\91\97\83X\83\8a\81[\83v\92\86\82©\82Ç\82¤\82©
     FStatusText: String;
     FConnecting: boolean;
     FAdded: boolean;
     FBooted: boolean; //\8f\89\89ñ\8bN\93®\92Ê\90M\97p
+    FEndSession: Boolean; // Windows\8fI\97¹\82ð\8c\9f\92m\82µ\82Ätrue\82É\82È\82é
     FOriginalCaption: String;
     FAutoAddAfterGetChannel: boolean; //\83`\83\83\83\93\83l\83\8b\8eæ\93¾\8cã\82É\83_\83C\83A\83\8d\83O\82È\82µ\82É
                                       //\83`\83\83\83\93\83l\83\8b\82É\8eQ\89Á\82·\82é\82©\82Ç\82¤\82©
     FConstDir: String;
     FSppDir: String;
     //
-    FMutex: THandle; //Mutex\83I\83u\83W\83F\83N\83g\81c\93ñ\8fd\8bN\93®\96h\8e~\97p
-    //
     FNowChannel: String; //\8c»\8dÝ\91I\91ð\82³\82ê\82Ä\82¢\82é\83`\83\83\83\93\83l\83\8b
     JoinChannelsBackup: TStringList; //\88ê\8e\9e\8eg\97p
     //
@@ -304,10 +311,6 @@ type
     //
     FScriptBuffer: TObjectList;  //\83X\83N\83\8a\83v\83g\83N\83\8a\83A\83o\83b\83t\83@
     //
-    //\88È\89º\81A\8d\91\90¨\92²\8d¸\82ð\89ß\93x\82É\91\97\90M\82µ\82È\82¢\82½\82ß\82Ì\95Ï\90\94
-    FLastGhostList: String;    //\83S\81[\83X\83g\83\8a\83X\83g\82Ì\95\8e\9a\97ñ
-    FLastGhostListSend: Int64; //\8d\91\90¨\92²\8d¸\82ð\8dÅ\8cã\82É\91\97\90M\82µ\82½\8e\9e\8d\8f
-    //
     FWM_TaskBarCreated: WORD; // \83^\83X\83N\83o\81[\93o\98^\97p\83E\83B\83\93\83h\83E\83\81\83b\83Z\81[\83WID
     //
     procedure SetStatusText(const Value: String);
@@ -317,6 +320,7 @@ type
     procedure SetAdded(const Value: boolean);
     procedure mnConstClick(Sender: TObject);
     procedure mnConstGroupClick(Sender: TObject);
+    procedure mnPresetReplaceClick(Sender: TObject);
     property Added: boolean read FAdded write SetAdded;
     property Sleeping: boolean read FSleeping write SetSleeping;
     property StatusText: String read FStatusText write SetStatusText;
@@ -332,6 +336,7 @@ type
     procedure UpdateIfGhostBox;
     function BuildMenuConditionCheck(const IfGhost, Ghost: String): boolean;
     procedure BuildMenu(Root: TMenuItem; Simple: boolean);
+    procedure BuildReplaceMenu(Root: TMenuItem);
     procedure PlaySound(const FileName: String);
     //TBottleSstp\8aÖ\8cW\83C\83x\83\93\83g\83n\83\93\83h\83\89
     procedure BottleSstpResendCountChange(Sender: TObject);
@@ -348,6 +353,8 @@ type
       Before, After: array of String): String; overload;
     function TagReplace(Script: String;
       Before, After: TStrings): String; overload;
+    // \83T\81[\83t\83B\83X\82ð\95Ï\8a·\82·\82é
+    function ReplaceSurface(Script: String; Params: TCollection): String;
     procedure ClearEditor;
     procedure CopyFromLogToEditor(Log: TLogItem);
     //
@@ -355,6 +362,8 @@ type
     procedure AppendXMLLog(const FileName: String; Args: THeadValue);
   protected
     procedure WndProc(var Message: TMessage); override;
+    procedure WMQueryEndSession(var msg: TWMQueryEndSession);
+      message WM_QUERYENDSESSION;
   public
     function DoTrans(var Script: String;
       Options: TScriptTransOptions): String; overload;
@@ -399,6 +408,7 @@ function Token(const Str: String; const Delimiter: char;
   const Index: integer): String;
 
 function StringReplaceEx(const Before: String; List: THeadValue): String;
+procedure OpenBrowser(const Url: String);
 
 implementation
 
@@ -425,6 +435,18 @@ begin
   end;
 end;
 
+// \83u\83\89\83E\83U\82ÅURL\82ð\8aJ\82­\8f\88\97\9d
+procedure OpenBrowser(const Url: String);
+begin
+  if Pref.BrowserExeName='' then
+    begin
+      ShellExecute(HWND(nil), 'open', PChar(Url), nil, nil, SW_SHOW);
+    end else
+    begin
+      ShellExecute(HWND(nil), 'open', PChar(Pref.BrowserExeName), PChar(Url), nil, SW_SHOW);
+    end;
+end;
+
 // \95\8e\9a\97ñ\82ð\92u\82«\8a·\82¦\82é\83\86\81[\83e\83B\83\8a\83e\83B\8aÖ\90\94
 function StringReplaceEx(const Before: String; List: THeadValue): String;
 var
@@ -458,17 +480,36 @@ begin
   end;
 end;
 
+
 {TfrmSender}
 
 procedure TfrmSender.actConfirmExecute(Sender: TObject);
-var AScript, Err, AGhost: String;
-    Item: TLogItem;
+var
+  AScript, Err, AGhost: String;
+  Item: TLogItem;
+  Choice: integer;
 begin
-  AScript := GetScriptText;
+  // Partial Confirmation
+  if memScript.SelText <> '' then
+  begin
+    Choice := 0;
+    if not Pref.AutoPartialConfirm then
+      if not MultipleChoiceEdit('\8am\94F', ['\91I\91ð\95\94\95ª\82Ì\82Ý', '\83X\83N\83\8a\83v\83g\91S\91Ì'], Choice) then
+        Exit;
+    if Choice = 0 then
+    begin
+      AScript := memScript.SelText;
+      AScript := StringReplace(Pref.PartialConfirmFormat, '|', AScript, []);
+    end else
+      AScript := GetScriptText;
+  end else
+    AScript := GetScriptText;
+  AScript := StringReplace(AScript, #13#10, '', [rfReplaceAll]);
+
   if Length(AScript) = 0 then Exit;
   YenETrans;
   AScript := ScriptTransForSSTP(AScript, Err);
-  if AScript = '' then
+  if Err <> '' then
   begin
     ShowMessage(Err);
     Exit;
@@ -512,6 +553,7 @@ begin
   FSppDir := ExtractFileDir(Application.ExeName)+'\spp';
   Spps.LoadFromDirectory(FSppDir);
   ConstructMenu(false);
+  BuildReplaceMenu(mnPresetReplaceRoot);
 
   Str := TStringList.Create;
   try
@@ -523,7 +565,7 @@ begin
         Str.LoadFromFile(ExtractFilePath(Application.ExeName)+'defrule.txt');
         BottleChainRuleList := StringToComponent(Str.Text) as TBottleChainRuleList;
       except
-        Showmessage('defrule.txt\93Ç\82Ý\8d\9e\82Ý\92\86\82É\92v\96½\93I\83G\83\89\81[\82ª\94­\90\82µ\82Ü\82µ\82½\81Bdefrule.txt\82ð\8dÄ\83C\83\93\83X\83g\81[\83\8b\82µ\82Ä\82­\82¾\82³\82¢\81B');
+        ShowMessage('defrule.txt\93Ç\82Ý\8d\9e\82Ý\92\86\82É\92v\96½\93I\83G\83\89\81[\82ª\94­\90\82µ\82Ü\82µ\82½\81Bdefrule.txt\82ð\8dÄ\83C\83\93\83X\83g\81[\83\8b\82µ\82Ä\82­\82¾\82³\82¢\81B');
         Application.Terminate;
         Application.ProcessMessages;
         Exit;
@@ -535,20 +577,6 @@ begin
 
   FOriginalCaption := Self.Caption;
 
-  {$IFDEF NOMUTEX}
-  ShowMessage('\93ñ\8fd\8bN\93®\8b\96\89Â\83o\81[\83W\83\87\83\93\82Å\82·\81B'#13#10 + VersionString);
-  {$ELSE}
-  FMutex := CreateMutex(nil, true, 'SSTPBottleClient2');
-  if GetLastError = ERROR_ALREADY_EXISTS then begin
-    Beep;
-    ShowMessage('SSTP Bottle Client\82Í\93ñ\8fd\8bN\93®\82Å\82«\82Ü\82¹\82ñ');
-    CloseHandle(FMutex);
-    Application.Terminate;
-    Application.ProcessMessages; //WM_QUIT\82ð\97¬\82·
-    Exit;
-  end;
-  {$ENDIF}
-
   UpdateLayout;
   mnShowToolBar.Checked := Pref.ShowToolBar;
   mnShowConstBar.Checked := Pref.ShowConstBar;
@@ -562,7 +590,6 @@ begin
 
   // URL\83W\83\83\83\93\83v\90æ\82ð\83q\83\93\83g\95\8e\9a\97ñ\82Æ\82µ\82Ä\90Ý\92è
   mnGoToHP.Hint := Pref.HomePage;
-  mnGotoGlog.Hint := Pref.GLogPage;
   mnGotoVote.Hint := Pref.VotePage;
   mnGotoHelp.Hint := Pref.HelpPage;
 
@@ -577,6 +604,9 @@ begin
     Application.Terminate;
   end;
 
+  // \8dÄ\90\8e\9e\8aÔ\90\84\92è\83R\83\93\83|\81[\83l\83\93\83g\82É\83p\83\89\83\81\81[\83^\82ð\8ew\92è
+  SsPlayTime.PlayTimeParams := Pref.PlayTimeParams;
+
   // \83\81\83C\83\93\83E\83B\83\93\83h\83E\82Ì\88Ê\92u\82Æ\83T\83C\83Y\82ð\95\9c\8bA
   with Pref.SenderWindowPosition do begin
     Self.Left   := Left;
@@ -628,9 +658,6 @@ begin
   SaveChainRuleList;
   BottleChainRuleList.Free;
 
-  {$IFDEF BOTTLEMUTEX}
-  ReleaseMutex(FMutex);
-  {$ENDIF}
 end;
 
 
@@ -651,7 +678,7 @@ begin
     actStart.Enabled := true;
     actStop.Enabled := true;
     actSend.Enabled := true;
-    frmLog.lvwLogChange(Self, nil, ctState);
+    frmLog.lvwLogClick(Self);
     mnGetNewId.Enabled := Pref.LUID = '';
     Screen.Cursor := crDefault;
   end;
@@ -684,27 +711,34 @@ begin
     Exit;
   end;
 
-  if Pref.NeedConfirmBeforeSend and FScriptModified then begin
+  YenETrans;
+  Talk := StringReplace(GetScriptText, #13#10, '', [rfReplaceAll]);
+  Err := DoTrans(Talk, [toWarnMessySurface, toWarnCheck]);
+  if Err <> '' then begin
+    MessageDlg(Err, mtWarning, [mbOk], 0);
+    Exit;
+  end;
+
+  if Pref.NeedConfirmBeforeSend and FScriptModified then
+  begin
     MessageDlg('\91\97\90M\91O\82É\83\8d\81[\83J\83\8b\8am\94F\82µ\82Ä\82­\82¾\82³\82¢\81B', mtError, [mbOk], 0);
     Exit;
   end;
 
+  if Length(Talk) < Pref.MinScriptLength then
+  begin
+    MessageDlg('\8dÅ\8f¬\91\97\90M\83o\83C\83g\90\94\88È\89º\82Å\82·\81B', mtError, [mbOk], 0);
+    Exit;
+  end;
+
   if not Pref.NoConfirm then begin
     if not SendConfirmDialog(FNowChannel, cbxTargetGhost.Text) then Exit;
   end;
 
-  YenETrans;
-  Talk := GetScriptText;
-  Err := DoTrans(Talk, [toWarnMessySurface, toWarnCheck]);
-  if Err <> '' then begin
-    MessageDlg(Err, mtWarning, [mbOk], 0);
-    Exit;
-  end;
-  Command := nil;
   Ghost := '';
   if cbxTargetGhost.ItemIndex > 0 then Ghost := cbxTargetGhost.Text;
+  Command := TStringList.Create;
   try
-    Command := TStringList.Create;
     with Command do begin
       Add('Command: sendBroadcast');
       Add('Channel: ' + FNowChannel);
@@ -737,36 +771,24 @@ begin
   end;
   IdSlpp20.LUID := Pref.LUID;
   self.Cursor := crHourGlass;
-  try
-    if IdSlpp20.Connected then IdSlpp20.Disconnect;
-    if Pref.UseHttpProxy then begin
-      IdSlpp20.Host := Pref.ProxyAddress;
-      IdSlpp20.Port := Pref.ProxyPort;
-      IdSlpp20.ProxyMode := true;
+  if IdSlpp20.Connected then IdSlpp20.Disconnect;
+  if Pref.UseHttpProxy then begin
+    IdSlpp20.Host := Pref.ProxyAddress;
+    IdSlpp20.Port := Pref.ProxyPort;
+    IdSlpp20.ProxyMode := true;
+    if Pref.ProxyNeedAuthentication then begin
+      IdSlpp20.ProxyUser := Pref.ProxyUser;
+      IdSlpp20.ProxyPass := Pref.ProxyPass;
     end else begin
-      IdSlpp20.Host := 'bottle.mikage.to';
-      IdSlpp20.Port := 9871;
-      IdSlpp20.ProxyMode := false;
-    end;
-    IdSlpp20.Connect;
-  except
-    on EIdException do begin
-      Added := false;
-      if FBeginConnectFailCount = 0 then begin
-        Inc(FBeginConnectFailCount);
-        Beep;
-        if Pref.UseHttpProxy then
-          ShowMessage('HTTP Proxy\82ð\92Ê\82\82ÄSSTP Bottle\83T\81[\83o\82É\90Ú\91±\82Å\82«\82Ü\82¹\82ñ\82Å\82µ\82½\81B'#13#10 +
-                      '\83l\83b\83g\83\8f\81[\83N\82Ì\8fó\91Ô\81EProxy\82Ì\8fó\91Ô\82ð\8am\94F\82µ\82Ä\82­\82¾\82³\82¢\81B'#13#10 +
-                      '\82 \82é\82¢\82Í\83T\81[\83o\82ª\83_\83E\83\93\82µ\82Ä\82¢\82é\89Â\94\\90«\82ª\82 \82è\82Ü\82·\81B')
-        else
-          ShowMessage('SSTP Bottle\83T\81[\83o\82É\90Ú\91±\82Å\82«\82Ü\82¹\82ñ\82Å\82µ\82½\81B'#13#10 +
-                      '\83l\83b\83g\83\8f\81[\83N\82É\8cq\82ª\82Á\82Ä\82¢\82é\82©\8am\94F\82µ\82Ä\82­\82¾\82³\82¢\81B'#13#10 +
-                      '\82 \82é\82¢\82Í\83T\81[\83o\82ª\83_\83E\83\93\82µ\82Ä\82¢\82é\89Â\94\\90«\82ª\82 \82è\82Ü\82·\81B');
-      end else
-        Inc(FBeginConnectFailCount);
+      IdSlpp20.ProxyUser := '';
+      IdSlpp20.ProxyPass := '';
     end;
+  end else begin
+    IdSlpp20.Host := Pref.BottleServer;
+    IdSlpp20.Port := Pref.BottleServerPort;
+    IdSlpp20.ProxyMode := false;
   end;
+  IdSlpp20.ConnectServer;
   self.Cursor := crDefault;
 end;
 
@@ -803,7 +825,8 @@ begin
     try
       HeadValue := THeadValue.Create(Str);
     except
-      ShowMessage('SSTP Bottle\83T\81[\83o\82ª\89ð\90Í\82Å\82«\82È\82¢\83G\83\89\81[\82ð\95Ô\82µ\82Ü\82µ\82½\81B');
+      Beep;
+      frmMessageBox.ShowMessage('SSTP Bottle\83T\81[\83o\82ª\89ð\90Í\82Å\82«\82È\82¢\83G\83\89\81[\82ð\95Ô\82µ\82Ü\82µ\82½\81B');
       Exit;
     end;
     Command := HeadValue['Command'];
@@ -811,11 +834,11 @@ begin
     if ResStr = 'Err' then begin
       if HeadValue['ExtraMessage'] <> '' then begin
         Beep;
-        ShowMessage('SSTP Bottle\83T\81[\83o\82ª\8e\9f\82Ì\83G\83\89\81[\82ð\95Ô\82µ\82Ü\82µ\82½:'#13#10 +
+        frmMessageBox.ShowMessage('SSTP Bottle\83T\81[\83o\82ª\8e\9f\82Ì\83G\83\89\81[\82ð\95Ô\82µ\82Ü\82µ\82½:'#13#10 +
                      HeadValue['ExtraMessage']);
       end else begin
         Beep;
-        ShowMessage('SSTP Bottle\83T\81[\83o\82ª\89½\82ç\82©\82Ì\83G\83\89\81[\82ð\95Ô\82µ\82Ü\82µ\82½\81B');
+        frmMessageBox.ShowMessage('SSTP Bottle\83T\81[\83o\82ª\89½\82ç\82©\82Ì\83G\83\89\81[\82ð\95Ô\82µ\82Ü\82µ\82½\81B'#13#10 + '\81i\83R\83}\83\93\83h\81u' + Command + '\81v\82ð\8f\88\97\9d\92\86\81j');
       end;
     end;
     if (Command = 'sendBroadcast') and (ResStr = 'OK') then begin
@@ -888,7 +911,7 @@ begin
     if (Command = 'setChannels') then begin
       if ResStr <> 'OK' then begin
         Beep;
-        ShowMessage('\83`\83\83\83\93\83l\83\8b\90Ý\92è\82É\8e¸\94s\82µ\82Ü\82µ\82½\81B\82à\82¤\88ê\93x\93o\98^\82µ\82È\82¨\82µ\82Ä\82­\82¾\82³\82¢');
+        frmMessageBox.ShowMessage('\83`\83\83\83\93\83l\83\8b\90Ý\92è\82É\8e¸\94s\82µ\82Ü\82µ\82½\81B\82à\82¤\88ê\93x\93o\98^\82µ\82È\82¨\82µ\82Ä\82­\82¾\82³\82¢');
         ShowHintLabel('\83`\83\83\83\93\83l\83\8b\90Ý\92è\82É\8e¸\94s\82µ\82Ü\82µ\82½', WarningColor);
       end;
     end;
@@ -917,11 +940,8 @@ end;
 procedure TfrmSender.actStopExecute(Sender: TObject);
 begin
   // \8b­\90§\8dÄ\90Ú\91±\82ð\8ds\82¤
-  IdSlpp20.OnDisconnect := nil;
-  if IdSlpp20.Connected then IdSlpp20.Disconnect;
-  FAutoAddAfterGetChannel := true;
-  BeginConnect;
-  IdSlpp20.OnDisconnect := Slpp20Disconnect;
+  FBeginConnectFailCount := 0; // \8e©\93®\8dÄ\90Ú\91±\83J\83E\83\93\83^\83\8a\83Z\83b\83g
+  RetryBeginConnect;
 end;
 
 procedure TfrmSender.FormShow(Sender: TObject);
@@ -930,24 +950,32 @@ begin
   //LUID\82ª\8eæ\93¾\82³\82ê\82Ä\82¢\82ê\82Î\91\81\91¬\93o\98^\81B\82»\82¤\82Å\82È\82¯\82ê\82ÎLUID\8eæ\93¾\81B
   if Pref.LUID <> '' then BeginConnect
   else mnGetNewIdClick(Self);
+
   FAutoAddAfterGetChannel := Pref.AutoStart;
   FBooted := true;
+
   frmLog.Show;
   frmSurfacePreview.Show;
   Self.Show;
+
+  //\83G\83f\83B\83^\83v\83\8c\83r\83\85\81[\82ª\91O\89ñ\95\\8e¦\8fó\91Ô\82È\82ç\82±\82±\82Å\95\\8e¦\82·\82é
+  if Pref.ShowEditorPreviewWindow then actEditorPreviewExecute(Sender);
+
   SakuraSeeker.BeginDetect;
   SakuraSeekerDetectResultChanged(self);
-  if SakuraSeeker.Count = 0 then
+  if (SakuraSeeker.Count = 0) and not Pref.NoWarnOfEmptyFMO then
     frmMessageBox.ShowMessage('\83S\81[\83X\83g(SSTP\83T\81[\83o)\82ª1\82Â\82à\8bN\93®\82µ\82Ä\82¢\82Ü\82¹\82ñ\81B'#13#10 +
       'SSTP Bottle\82ð\97\98\97p\82·\82é\82½\82ß\82É\82Í\81A\83S\81[\83X\83g\82ð\93¯\8e\9e\82É\8bN\93®\82µ\82Ä\82­\82¾\82³\82¢\81B'#13#10 +
       '\8fÚ\8d×\82Í\83w\83\8b\83v\82ð\82²\97\97\89º\82³\82¢\81B');
 end;
 
 procedure TfrmSender.actAboutClick(Sender: TObject);
-var Str: String;
+var
+  Str: String;
 begin
   Str := VersionString + #13#10 + BottleDisclaimer + #13#10#13#10;
-  Str := Str + Format('Compiler Version: %f', [CompilerVersion]);
+  Str := Str + Format('Compiler Version: %f', [CompilerVersion]) + #13#10;
+  Str := Str + 'Indy Version: ' + IdSLPP20.Version;
   frmMessageBox.ShowMessage(Str);
 end;
 
@@ -971,10 +999,13 @@ begin
 end;
 
 procedure TfrmSender.memScriptChange(Sender: TObject);
-var Script: String;
+var
+  Script: String;
+  Text: String;
 begin
   Script := StringReplace(GetScriptText, #13#10, '', [rfReplaceAll]);
-  StatusBar.Panels[PanelBytes].Text := IntToStr(length(Script)) + '\83o\83C\83g';
+  Text := Format('%d\83o\83C\83g/%d\95b', [Length(Script), SsPlayTime.PlayTime(Script) div 1000]);
+  StatusBar.Panels[PanelBytes].Text := Text;
   FScriptModified := true;
   EditorPreview;
 end;
@@ -1125,10 +1156,21 @@ begin
   BuildMenu(mnPopConst, Simple);
   BuildMenu(mnPopUpConst.Items, Simple);
   BuildMenu(ConstBarMenu.Items, Simple);
-  //ConstMenuBar.Menu := nil;
-  ConstMenuBar.AutoSize := false;
-  ConstMenuBar.Width := 1000;
+
+  {$IFDEF CONDITIONALEXPRESSIONS}
+    {$IF Declared(CompilerVersion)}
+      {$IF CompilerVersion < 15.0} // Delphi6\88È\89º\82È\82ç
+        // \83\81\83j\83\85\81[\82ð\88ê\93x\8fÁ\82·
+        // \82È\82º\82©2\89ñ\96Ú\88È\8d~\82Ì\95\\8e¦\82ª\82¨\82©\82µ\82­\82È\82é\96â\91è\82Ì\8fC\90³
+        // ported from YasagureClient
+        ConstMenuBar.AutoSize := false;
+        ConstMenuBar.Menu := nil;
+      {$IFEND}
+    {$IFEND}
+  {$ENDIF}
+  
   ConstMenuBar.Menu := ConstBarMenu;
+  ConstMenuBar.Width := 1000;
   ConstMenuBar.AutoSize := true;
 end;
 
@@ -1188,7 +1230,7 @@ begin
     SynchronizedColor.Color := Pref.TalkColorS;
   end;
   memScript.Ruler.Visible := Pref.ShowRuler;
-  memScript.Ruler.Color := Pref.TalkColorH;
+  memScript.Ruler.Color := Pref.TextColor;
   memScript.Color := Pref.BgColor;
 
   ToolBar.Visible := Pref.ShowToolBar;
@@ -1201,6 +1243,7 @@ begin
       tpTop:    Align  := alTop;
       tpBottom: Align := alBottom;
     end;
+    TabWidth := Pref.TabWidth;
   end;
 end;
 
@@ -1243,7 +1286,8 @@ begin
                 Url[UrlCount] := SsParser.GetParam(SsParser[i], UrlCount*2+2);
                 UrlName[UrlCount] := SsParser.GetParam(SsParser[i], UrlCount*2+3);
                 if UrlName[UrlCount] = '' then UrlName[UrlCount] := Url[UrlCount];
-                if Pos('http://', Url[UrlCount]) > 0 then Inc(UrlCount);
+                //https\82È\82Ç\82ª\92Ç\89Á\82³\82ê\82é\8e\9e\91Î\8dô\81c\81c://\82ð\8c\9f\8dõ\82·\82ê\82Î\82½\82Ô\82ñ\91å\8fä\95v
+                if Pos('://', Url[UrlCount]) > 0 then Inc(UrlCount);
               end;
             end;
             if UrlCount > 0 then UrlCancel := SsParser.GetParam(SsParser[i], 1);
@@ -1254,7 +1298,8 @@ begin
             Url[0] := SsParser.GetParam(SsParser[i], 1);
             UrlName[0] := '\8ds\82­\81@\81@\81@\81@\81@\81@';
             UrlCancel  := '\8ds\82©\82È\82¢\81@\81@\81@\81@';
-            if Pos('http://', Url[0]) > 0 then begin
+            //https\82È\82Ç\82ª\92Ç\89Á\82³\82ê\82é\8e\9e\91Î\8dô\81c\81c://\82ð\8c\9f\8dõ\82·\82ê\82Î\82½\82Ô\82ñ\91å\8fä\95v
+            if Pos('://', Url[0]) > 0 then begin
               UrlCount := 1;
               if not QuickSection then
                 Script := Script + '\_q' + Url[0] + '\_q'
@@ -1311,8 +1356,8 @@ begin
         Script := Script + '\h';
         for i := 0 to UrlCount-1 do begin
           Script := Script + Format('\n{%s}(%s)', [UrlName[i], Url[i]]);
-          Script := Script + Format('\n{%s}', [UrlCancel]);
         end;
+        Script := Script + Format('\n{%s}', [UrlCancel]);
       end;
     end else
       FoundUrl := false;
@@ -1339,6 +1384,11 @@ begin
         Result := '"' + SsParser[i] + '"'#13#10 +
                   '\82Í\81ASSTP Bottle\82Å\94F\82ß\82ç\82ê\82È\82¢\82©\81A\94F\8e¯\82Å\82«\82È\82¢\83^\83O\82Å\82·\81B';
         Exit;
+      end else if SsParser.MarkUpType[i] = mtStr then begin
+        if Pos(#9,SsParser[i]) > 0 then begin
+          Result := '\83^\83u\95\8e\9a\82Í\81ASSTP Bottle\82Å\8eg\97p\82Å\82«\82Ü\82¹\82ñ\81B';
+          Exit;
+        end;
       end;
     end;
     if (SsParser[0] <> '\t') and Pref.WarnYenTNotExist then begin
@@ -1365,7 +1415,7 @@ end;
 
 procedure TfrmSender.mnGoToHPClick(Sender: TObject);
 begin
-  ShellExecute(Handle, 'open', PChar(Pref.HomePage), nil, nil, SW_SHOW);
+  OpenBrowser(Pref.HomePage);
 end;
 
 procedure TfrmSender.ShowHintLabel(const Mes: String; Col: TColor);
@@ -1413,15 +1463,24 @@ begin
         //\83\81\83b\83Z\81[\83W\8eó\90M
         DispatchBottle(EventType, HeadValue);
       end;
+
       etMemberCount: begin
+        //\91\8d\8eQ\89Á\8eÒ\90\94
         StatusBar.Panels[PanelMembers].Text := HeadValue['Num'] + '\90l'
       end;
+
       etChannelCount: begin
+        //\83`\83\83\83\93\83l\83\8b\95Ê\8eQ\89Á\8eÒ\90\94
         try
-          ChannelList.Channel[HeadValue['Channel']].Members := StrToInt(HeadValue['Num']);
+          if HeadValue['Channel'] <> '' then begin
+            if ChannelList.Channel[HeadValue['Channel']] <> nil then begin
+              ChannelList.Channel[HeadValue['Channel']].Members := StrToInt(HeadValue['Num']);
+            end;
+          end;
         except
         end;
       end;
+
       etConnectOk: begin
         ShowHintLabel('SSTP Bottle\83T\81[\83o\82Æ\92Ê\90M\8am\97§\81B');
         Added := true;
@@ -1429,34 +1488,50 @@ begin
         //\83`\83\83\83\93\83l\83\8b\8e©\93®\93o\98^
         if not Connecting then
           PostCommand(['Command: getChannels']);
-        SakuraSeeker.BeginDetect;
+        try
+          SakuraSeeker.BeginDetect;
+        except
+          on E: Exception do ShowHintLabel(E.Message,WarningColor);
+        end;
       end;
+
       etChannelList: begin
         UpdateJoinChannelList(HeadValue);
         // \8dÅ\8cã\82É\8eQ\89Á\82µ\82Ä\82¢\82½\83`\83\83\83\93\83l\83\8b\82ð\8bL\98^\82·\82é
         if JoinChannelsBackup = nil then JoinChannelsBackup := TStringList.Create;
         JoinChannelsBackup.Assign(JoinChannels);
       end;
+
       etCloseChannel: begin
-        with JoinChannels do
-          if IndexOf(HeadValue['Channel']) >= 0 then
-            Delete(IndexOf(HeadValue['Channel']));
-        with tabChannel do begin
-          if Tabs.IndexOf(HeadValue['Channel']) >= 0 then
-            Tabs.Delete(Tabs.IndexOf(HeadValue['Channel']));
-          if Tabs.Count > 0 then TabIndex := 0 else TabIndex := -1;
-          tabChannelChange(self);
+        //\83`\83\83\83\93\83l\83\8b\94p\8e~
+        if HeadValue['Channel'] <> '' then begin
+          with JoinChannels do
+            if IndexOf(HeadValue['Channel']) >= 0 then
+              Delete(IndexOf(HeadValue['Channel']));
+          with tabChannel do begin
+            if Tabs.IndexOf(HeadValue['Channel']) >= 0 then
+              Tabs.Delete(Tabs.IndexOf(HeadValue['Channel']));
+            if Tabs.Count > 0 then TabIndex := 0 else TabIndex := -1;
+            tabChannelChange(self);
+          end;
+          ShowHintLabel(HeadValue['Channel'] + '\83`\83\83\83\93\83l\83\8b\82Í\94p\8e~\82³\82ê\82Ü\82µ\82½',
+                        WarningColor);
+          frmLog.AddCurrentSystemLog('SYSTEM', HeadValue['Channel'] + '\83`\83\83\83\93\83l\83\8b\82Í\94p\8e~\82³\82ê\82Ü\82µ\82½');
+          frmMessageBox.ShowMessage(HeadValue['Channel'] + '\83`\83\83\83\93\83l\83\8b\82Í\94p\8e~\82³\82ê\82Ü\82µ\82½');
         end;
-        ShowHintLabel(HeadValue['Channel'] + '\83`\83\83\83\93\83l\83\8b\82Í\94p\8e~\82³\82ê\82Ü\82µ\82½',
-                      WarningColor);
-        frmLog.AddCurrentSystemLog('SYSTEM', HeadValue['Channel'] + '\83`\83\83\83\93\83l\83\8b\82Í\94p\8e~\82³\82ê\82Ü\82µ\82½');
-        frmMessageBox.ShowMessage(HeadValue['Channel'] + '\83`\83\83\83\93\83l\83\8b\82Í\94p\8e~\82³\82ê\82Ü\82µ\82½');
       end;
+
       etForceBroadcastInformation: begin
-        if HeadValue['Type'] = 'Vote' then begin
-          frmLog.VoteLog(HeadValue['MID'], StrToIntDef(HeadValue['Num'], 0));
-        end else if HeadValue['Type'] = 'Agree' then begin
-          frmLog.AgreeLog(HeadValue['MID'], StrToIntDef(HeadValue['Num'], 0));
+        //\93\8a\95[\81^\93¯\88Ó\81^\82»\82Ì\91¼\83u\83\8d\81[\83h\83L\83\83\83X\83g\8fî\95ñ
+        if HeadValue['MID'] <> '' then begin
+          try //Num\82ª\90\94\92l\82Å\82È\82©\82Á\82½\82Æ\82«\91Î\8dô
+            if HeadValue['Type'] = 'Vote' then begin
+              frmLog.VoteLog(HeadValue['MID'], StrToIntDef(HeadValue['Num'], 0));
+            end else if HeadValue['Type'] = 'Agree' then begin
+              frmLog.AgreeLog(HeadValue['MID'], StrToIntDef(HeadValue['Num'], 0));
+            end;
+          except
+          end;
         end;
       end;
     end;
@@ -1489,6 +1564,7 @@ begin
     frmSetting := nil;
   end;
   //
+  BuildReplaceMenu(mnPresetReplaceRoot);
   UpdateLayout;
   tabChannel.Repaint;
   frmLog.UpdateWindow;
@@ -1527,42 +1603,8 @@ begin
 end;
 
 procedure TfrmSender.SakuraSeekerDetectResultChanged(Sender: TObject);
-var i: integer;
-    GhostList: String;
-    Http: THTTPDownloadThread;
-    SendOk: boolean;
 begin
   UpdateIfGhostBox; // \83h\83\8d\83b\83v\83_\83E\83\93\82Ì\92\86\90g\82ð\8f\91\82«\8a·\82¦\82é
-
-  if (FLastGhostListSend <> 0) and
-    (GetTickCount < FLastGhostListSend + 1000*60) then
-  begin
-    Exit;
-  end;
-  FLastGhostListSend := GetTickCount;
-
-  //\8d\91\90¨\92²\8d¸\82É\8eQ\89Á
-  if FBooted and not Pref.NoSendGhostList and (SakuraSeeker.Count > 0) then begin
-    GhostList := 'CCC=' + ParamsEncode('\88¤');
-    GhostList := GhostList + '&LUID=' + Pref.LUID;
-    SendOk := false;
-    for i := 0 to SakuraSeeker.Count-1 do begin
-      if SakuraSeeker[i].Name <> '' then begin//\82±\82ê\82ª\82È\82¢\82Æ\82½\82Ü\82ÉFMO\89ó\82ê\82Å\8bó\82Ì\83S\81[\83X\83g\82ð\91\97\82Á\82Ä\82µ\82Ü\82¤
-        GhostList := GhostList + '&GHOST=' + ParamsEncode(SakuraSeeker[i].SetName);
-        SendOk := true;
-      end;
-    end;
-    if SendOk and (FLastGhostList <> GhostList) then begin
-      FLastGhostList := GhostList;
-      Http := THTTPDownloadThread.Create(BottleServer, Pref.CgiNameGhost, GhostList);
-      if Pref.UseHttpProxy then begin
-        Http.ProxyServer := Pref.ProxyAddress;
-        Http.ProxyPort   := Pref.ProxyPort;
-      end;
-      Http.FreeOnTerminate := true;
-      Http.Resume;
-    end;
-  end;
 end;
 
 procedure TfrmSender.UpdateChannelInfo(Dat: THeadValue);
@@ -1648,8 +1690,9 @@ begin
     Tabs.Clear;
     for i := 0 to JoinChannels.Count-1 do begin
       //\8eó\90M\90ê\97p\83`\83\83\83\93\83l\83\8b\82Í\95\\8e¦\82µ\82È\82¢
-      if not ChannelList.Channel[JoinChannels[i]].NoPost then
-        Tabs.Add(JoinChannels[i]);
+      if ChannelList.Channel[JoinChannels[i]] <> nil then
+        if not ChannelList.Channel[JoinChannels[i]].NoPost then
+          Tabs.Add(JoinChannels[i]);
     end;
     Tabs.EndUpdate;
     // \8c³\82©\82ç\83`\83\83\83\93\83l\83\8b\82É\8eQ\89Á\82µ\82Ä\82¢\82½\8fê\8d\87\82Í
@@ -1724,10 +1767,11 @@ var Opt: TSstpSendOptions;
     CueItem: TLogItem;
     ReplaceHash: THeadValue; 
 begin
+  Channel := Dat['Channel'];
+
   Opt := [];
   if Pref.NoTranslate then Opt := Opt + [soNoTranslate];
   if Pref.NoDescript  then Opt := Opt + [soNoDescript];
-  Channel := Dat['Channel'];
   case EventType of
     etScript: Sender := Channel;
     etForceBroadcast: Sender := '\81y\82¨\92m\82ç\82¹\81z';
@@ -1808,7 +1852,8 @@ begin
           begin
             Dat['TargetGhost'] := (Action as TBottleChainOverrideGhostAction).TargetGhost;
           end;
-          if Action is TBottleChainQuitAction then Application.Terminate;
+          if Action is TBottleChainQuitAction then
+            Application.Terminate;
           if Action is TBottleChainSaveTextLogAction then
             AppendTextLog(StringReplaceEx((Action as TBottleChainSaveTextLogAction).FileName, ReplaceHash),
               Format('%s,%s,%s,%s', [Dat['Channel'], Dat['IfGhost'],
@@ -1816,6 +1861,8 @@ begin
           if Action is TBottleChainSaveXMLLogAction then
             AppendXMLLog(StringReplaceEx((Action as TBottleChainSaveXMLLogAction).FileName, ReplaceHash),
               Dat);
+          if Action is TBottleChainSurfaceReplaceAction then
+            Script := ReplaceSurface(Script, (Action as TBottleChainSurfaceReplaceAction).Params);
         end;
         if BreakFlag then Break;
       end;
@@ -1909,10 +1956,14 @@ begin
   PostStr := Command.Text;
   PostStr := ParamsEncode(PostStr);
   try
-    FHttp := THTTPDownloadThread.Create(BottleServer, Pref.CgiName, PostStr);
+    FHttp := THTTPDownloadThread.Create(Pref.BottleServer, Pref.CgiName, PostStr);
     if Pref.UseHttpProxy then begin
       FHttp.ProxyServer := Pref.ProxyAddress;
       FHttp.ProxyPort   := Pref.ProxyPort;
+      if Pref.ProxyNeedAuthentication then begin
+        FHttp.ProxyUser := Pref.ProxyUser;
+        FHttp.ProxyPass := Pref.ProxyPass;
+      end;
     end;
     FHttp.OnSuccess := HttpSuccess;
     FHttp.OnConnectionFailed := HttpFailure;
@@ -1929,8 +1980,8 @@ end;
 procedure TfrmSender.actVoteMessageExecute(Sender: TObject);
 var Log: TLogItem;
 begin
-  if frmLog.lvwLog.Selected = nil then Exit;
-  Log := frmLog.SelectedBottleLog[frmLog.lvwLog.Selected.Index] as TLogItem;
+  if frmLog.lvwLog.ItemIndex < 0 then Exit;
+  Log := frmLog.SelectedBottleLog[frmLog.lvwLog.ItemIndex] as TLogItem;
   if Log = nil then Exit;
   if Log.LogType <> ltBottle then Exit;
   PostCommand([
@@ -1945,8 +1996,8 @@ end;
 procedure TfrmSender.actAgreeMessageExecute(Sender: TObject);
 var Log: TLogItem;
 begin
-  if frmLog.lvwLog.Selected = nil then Exit;
-  Log := frmLog.SelectedBottleLog[frmLog.lvwLog.Selected.Index] as TLogItem;
+  if frmLog.lvwLog.ItemIndex < 0 then Exit;
+  Log := frmLog.SelectedBottleLog[frmLog.lvwLog.ItemIndex] as TLogItem;
   if Log = nil then Exit;
   if Log.LogType <> ltBottle then Exit;
   PostCommand([
@@ -2021,12 +2072,7 @@ end;
 
 procedure TfrmSender.mnGotoVoteClick(Sender: TObject);
 begin
-  ShellExecute(Handle, 'open', PChar(Pref.VotePage), nil, nil, SW_SHOW);
-end;
-
-procedure TfrmSender.mnGotoGLogClick(Sender: TObject);
-begin
-  ShellExecute(Handle, 'open', PChar(Pref.GLogPage), nil, nil, SW_SHOW);
+  OpenBrowser(Pref.VotePage);
 end;
 
 procedure TfrmSender.tabChannelMouseMove(Sender: TObject;
@@ -2043,7 +2089,7 @@ end;
 
 procedure TfrmSender.mnGoToHelpClick(Sender: TObject);
 begin
-  ShellExecute(Handle, 'open', PChar(Pref.HelpPage), nil, nil, SW_SHOW);
+  OpenBrowser(Pref.HelpPage);
 end;
 
 procedure TfrmSender.tabChannelMouseDown(Sender: TObject;
@@ -2163,9 +2209,11 @@ end;
 procedure TfrmSender.FormCloseQuery(Sender: TObject;
   var CanClose: Boolean);
 begin
-  if not Pref.ConfirmOnExit then Exit;
+  if (not Pref.ConfirmOnExit) or FEndSession then
+    Exit;
   if MessageDlg('SSTP Bottle Client\82ð\8fI\97¹\82µ\82Ü\82·', mtConfirmation,
-                mbOkCancel, 0) = mrCancel then CanClose := false;
+                mbOkCancel, 0) = mrCancel then
+    CanClose := false;
 end;
 
 procedure TfrmSender.UpdateIfGhostBox;
@@ -2218,7 +2266,7 @@ begin
   SysUtils.Beep;
   Beep;
   ShowHintLabel('SSTP Bottle\83T\81[\83o\82Æ\82Ì\90Ú\91±\82É\8e¸\94s\82µ\82Ü\82µ\82½', WarningColor);
-  ShowMessage('SSTP Bottle\83T\81[\83o\82Æ\82Ì\90Ú\91±\82É\8e¸\94s\82µ\82Ü\82µ\82½'#13#10 +
+  frmMessageBox.ShowMessage('SSTP Bottle\83T\81[\83o\82Æ\82Ì\90Ú\91±\82É\8e¸\94s\82µ\82Ü\82µ\82½'#13#10 +
     (Sender as THTTPDownloadThread).LastErrorMessage);
   Connecting := false;
 end;
@@ -2268,7 +2316,7 @@ end;
 
 procedure TfrmSender.timDisconnectCheckTimerTimer(Sender: TObject);
 begin
-  if (IdSlpp20.LastReadTimeInterval > BottleServerTimeOut) then begin
+  if (IdSlpp20.LastReadTimeInterval > Pref.ReconnectWait * 60000) then begin
     SysUtils.Beep;
     frmLog.AddCurrentSystemLog('SYSTEM', 'SSTP Bottle\83T\81[\83o\82Æ\82Ì\90Ú\91±\82ª\83^\83C\83\80\83A\83E\83g\82µ\82Ü\82µ\82½');
     if IdSlpp20.Connected then IdSlpp20.Disconnect;
@@ -2288,8 +2336,11 @@ procedure TfrmSender.RetryBeginConnect;
 begin
   if FBeginConnectFailCount < 3 then begin
     // \90Ø\92f\82³\82ê\82Ä\82¢\82ê\82Î\8dÄ\90Ú\91±
+    IdSlpp20.OnDisconnect := nil;
+    if IdSlpp20.Connected then IdSlpp20.Disconnect;
     FAutoAddAfterGetChannel := true;
     BeginConnect;
+    IdSlpp20.OnDisconnect := Slpp20Disconnect;
   end else if FBeginConnectFailCount = 3 then begin
     frmLog.AddCurrentSystemLog('SYSTEM', '\8dÄ\90Ú\91±\8e©\93®\83\8a\83g\83\89\83C\82ð\92\86\8e~\82µ\82Ü\82·');
     frmMessageBox.ShowMessage(
@@ -2401,12 +2452,9 @@ begin
       if ScriptConstList.GetMenuByID(Root.Items[i].Tag).IfGhost <> '' then
         Root.Items[i].Free;
     end;
-  end else begin
+  end else
     // \91S\95\94\8dí\8f\9c
-    for i := Root.Count-1 downto 0 do begin
-      Root.Items[i].Free;
-    end;
-  end;
+    Root.Clear;
 
   count := -1;
   for i := 0 to ScriptConstList.Count-1 do begin
@@ -2421,11 +2469,14 @@ begin
         end;
 
       Menu1 := TMenuItem.Create(Root);
-      Menu1.Caption := ScriptConstList[i][j].Caption;
-      Menu1.Hint    := ScriptConstList[i][j].Caption;
-      Menu1.AutoHotkeys := maManual;
-      Menu1.Tag := ScriptConstList[i][j].ID;
-      Menu1.OnClick := mnConstGroupClick;
+      with Menu1 do
+      begin
+        Caption := ScriptConstList[i][j].Caption;
+        Hint    := ScriptConstList[i][j].Caption;
+        AutoHotkeys := maManual;
+        Tag := ScriptConstList[i][j].ID;
+        OnClick := mnConstGroupClick;
+      end;
 
       if not Simple then begin
         Root.Add(Menu1);
@@ -2543,12 +2594,12 @@ begin
   end;
   FBottleSstp.Clear;
   frmLog.AllBottleOpened;
-  if frmLog.lvwLog.Selected = nil then Exit;
+  if frmLog.lvwLog.ItemIndex < 0 then Exit;
   Log := frmLog.SelectedBottleLog;
   if Log = nil then Exit;
   FBottleSSTP.OnResendCountChange := nil;
   errCount := 0;
-  for i := frmLog.lvwLog.Selected.Index downto 0 do begin
+  for i := frmLog.lvwLog.ItemIndex downto 0 do begin
     if (Log[i] as TLogItem).LogType <> ltBottle then Continue;
     InsertItem := TLogItem.Create(Log[i] as TLogItem);
     try
@@ -2602,17 +2653,21 @@ begin
 end;
 
 procedure TfrmSender.FormResize(Sender: TObject);
-var w: integer;
+var w, SelS, SelL: integer;
 begin
   // \83G\83f\83B\83^\81[\95\94\95ª\82Ì\83\8f\81[\83h\83\89\83b\83v\95\9d\82ð\92²\90®\82·\82é
   if memScript.ColWidth <> 0 then
   begin
     with memScript do
     begin
+      SelS := SelStart;
+      SelL := SelLength;
       w := Width - LeftMargin - ColWidth div 2;
       if ScrollBars in [ssVertical, ssBoth] then
         w := w - GetSystemMetrics(SM_CYVSCROLL);
       WrapOption.WrapByte := w div ColWidth;
+      SelStart  := SelS;
+      SelLength := SelL;
     end;
   end;
 end;
@@ -2637,6 +2692,16 @@ begin
   memScript.SelectAll;
 end;
 
+procedure TfrmSender.actUndoExecute(Sender: TObject);
+begin
+  memScript.Undo;
+end;
+
+procedure TfrmSender.actRedoExecute(Sender: TObject);
+begin
+  memScript.Redo;
+end;
+
 function TfrmSender.IsSurfaceTag(const Script: String;
   var ID: integer): boolean;
 begin
@@ -2778,8 +2843,10 @@ begin
   else
   begin
     Application.CreateForm(TfrmEditorTalkShow, frmEditorTalkShow);
+    frmEditorTalkShow.TalkShowFrame.SetPreviewFont(memScript.Font);
     frmEditorTalkShow.Show;
   end;
+  Pref.ShowEditorPreviewWindow := True;
   EditorPreview;
 end;
 
@@ -2871,53 +2938,44 @@ begin
     inherited;
 end;
 
-// \8c\9f\8dõ\81E\92u\8a·\83_\83C\83A\83\8d\83O\82ð\8f\89\8aú\89»\82µ\82Ä\95\\8e¦
+// \92u\8a·
 procedure TfrmSender.actReplaceExecute(Sender: TObject);
+var
+  Form: TfrmStrReplaceDialog;
+  Lines: String;
+  Options: TReplaceFlags;
 begin
-  with ReplaceDialog do
-  begin
-    FindText := '';
-    ReplaceText := '';
-    Execute;
-  end;
-end;
-
-// \8c\9f\8dõ\81E\92u\8a·
-procedure TfrmSender.ReplaceDialogFind(Sender: TObject);
-var Opt: TSearchOptions;
-begin
-  Opt := [sfrDown];
-  if frMatchCase in ReplaceDialog.Options then
-    Opt := Opt + [sfrMatchCase];
-  if frWholeWord in ReplaceDialog.Options then
-    Opt := Opt + [sfrWholeWord];
-  memScript.Search(ReplaceDialog.FindText, Opt);
-end;
-
-// \8c\9f\8dõ\81E\92u\8a·
-procedure TfrmSender.ReplaceDialogReplace(Sender: TObject);
-var Opt: TSearchOptions;
-begin
-  Opt := [sfrDown];
-  if frMatchCase in ReplaceDialog.Options then
-    Opt := Opt + [sfrMatchCase];
-  if frWholeWord in ReplaceDialog.Options then
-    Opt := Opt + [sfrWholeWord];
-  if frReplaceAll in ReplaceDialog.Options then
-    while memScript.Search(ReplaceDialog.FindText, Opt) do
-      memScript.SelText := ReplaceDialog.ReplaceText
-  else
-  begin
-    if memScript.Search(ReplaceDialog.FindText, Opt) then
-      memScript.SelText := ReplaceDialog.ReplaceText
+  Application.CreateForm(TfrmStrReplaceDialog, Form);
+  try
+    if Form.Execute then
+    begin
+      with Form.Pair do
+      begin
+        Lines := memScript.Lines.Text;
+        Options := [rfReplaceAll];
+        if IgnoreCase then
+          Options := Options + [rfIgnoreCase];
+        if UseRegExp then
+          Lines := StringReplaceRegExp(Lines, BeforeStr, AfterStr, Options)
+        else
+          Lines := StringReplace(Lines, BeforeStr, AfterStr, Options);
+      end;
+      if Lines <> memScript.Lines.Text then
+      begin
+        memScript.SelectAll;
+        memScript.SelText := Lines;
+      end;
+    end;
+  finally
+    Form.Release;
   end;
 end;
 
 procedure TfrmSender.actSendToEditorExecute(Sender: TObject);
 var Log: TLogItem;
 begin
-  if frmLog.lvwLog.Selected = nil then Exit;
-  Log := frmLog.SelectedBottleLog[frmLog.lvwLog.Selected.Index] as TLogItem;
+  if frmLog.lvwLog.ItemIndex < 0 then Exit;
+  Log := frmLog.SelectedBottleLog[frmLog.lvwLog.ItemIndex] as TLogItem;
   if Log = nil then Exit;
   CopyFromLogToEditor(Log);
 end;
@@ -2983,11 +3041,11 @@ begin
   // \83\8d\83O\83E\83B\83\93\83h\83E\82Ì\8cÂ\95Ê\83\8d\83O\82ð\8dí\8f\9c\82·\82é
   if frmLog.SelectedBottleLog = nil then
     Exit;
-  if frmLog.lvwLog.Selected = nil then
+  if frmLog.lvwLog.ItemIndex = -1 then
     Exit;
-  frmLog.SelectedBottleLog.Delete(frmLog.lvwLog.Selected.Index);
+  frmLog.SelectedBottleLog.Delete(frmLog.lvwLog.ItemIndex);
   frmLog.UpdateWindow;
-  frmLog.lvwLogChange(Self, nil, ctState);
+  frmLog.lvwLogClick(Self);
 end;
 
 procedure TfrmSender.ClearEditor;
@@ -3131,4 +3189,155 @@ begin
   end;
 end;
 
+procedure TfrmSender.memScriptSelectionChange(Sender: TObject;
+  Selected: Boolean);
+var
+  SelText: String;
+begin
+  SelText := memScript.SelText;
+  if SelText <> '' then
+  begin
+    StatusBar.Panels[PanelBytes].Text := Format('(%d\83o\83C\83g)', [Length(SelText)]);
+  end else
+  begin
+    memScriptChange(Self);
+  end; 
+end;
+
+function TfrmSender.ReplaceSurface(Script: String;
+  Params: TCollection): String;
+var
+  Flag, OldLeaveEscape, OldEscapeInvalidMeta: boolean;
+  OldStr, Tag: String;
+  i, j, k, Cur: integer;
+  Item: TSurfaceReplaceItem;
+  Before: TSurfaceReplaceBeforeItem;
+begin
+  Result := '';
+  Cur := 0;
+  with SsParser do
+  begin
+    OldStr := InputString;
+    OldLeaveEscape := LeaveEscape;
+    OldEscapeInvalidMeta := EscapeInvalidMeta;
+    LeaveEscape := true;
+    EscapeInvalidMeta := false;
+    InputString := Script;
+  end;
+  for i := 0 to SsParser.Count-1 do
+  begin
+    if SsParser.MarkUpType[i] <> mtTag then
+    begin
+      Result := Result + SsParser.Str[i];
+      Continue;
+    end;
+    Tag := SsParser.Str[i];
+    if SsParser.Match(Tag, '\s%d') = 2 then
+      Cur := Ord(Tag[3]) - Ord('0')
+    else if SsParser.Match(Tag, '\s[%D]') > 0 then
+      Cur := StrToInt(SsParser.GetParam(Tag, 1))
+    else
+    begin
+      Result := Result + Tag;
+      Continue;
+    end;
+    Flag := false;
+    for j := 0 to Params.Count-1 do
+    begin
+      Item := Params.Items[j] as TSurfaceReplaceItem;
+      for k := 0 to Item.Before.Count-1 do
+      begin
+        Before := Item.Before.Items[k] as TSurfaceReplaceBeforeItem;
+        if (Cur >= Before.FromNo) and (Cur <= Before.ToNo) then
+        begin
+          Flag := true;
+          Result := Result + Format('\s[%d]', [Item.After]);
+          Break;
+        end;
+      end;
+      if Flag then
+        Break;
+    end;
+    if not Flag then
+      Result := Result + Tag;
+  end;
+  with SsParser do
+  begin
+    LeaveEscape := OldLeaveEscape;
+    EscapeInvalidMeta := OldEscapeInvalidMeta;
+    InputString := OldStr;
+  end;
+end;
+
+procedure TfrmSender.WMQueryEndSession(var msg: TWMQueryEndSession);
+begin
+  // Windows\82ª\8fI\97¹\82µ\82æ\82¤\82Æ\82µ\82Ä\82¢\82é\82Ì\82ð\8a´\92m\82·\82é
+  FEndSession := true;
+  inherited;
+end;
+
+procedure TfrmSender.IdSLPP20ConnectFailed(Sender: TObject);
+begin
+  Added := false;
+  if FBeginConnectFailCount = 0 then
+  begin
+    Beep;
+    if Pref.UseHttpProxy then
+      frmMessageBox.ShowMessage('HTTP Proxy\82ð\92Ê\82\82ÄSSTP Bottle\83T\81[\83o\82É\90Ú\91±\82Å\82«\82Ü\82¹\82ñ\82Å\82µ\82½\81B'#13#10 +
+                  '\83l\83b\83g\83\8f\81[\83N\82Ì\8fó\91Ô\81EProxy\82Ì\8fó\91Ô\82ð\8am\94F\82µ\82Ä\82­\82¾\82³\82¢\81B'#13#10 +
+                  '\82 \82é\82¢\82Í\83T\81[\83o\82ª\83_\83E\83\93\82µ\82Ä\82¢\82é\89Â\94\\90«\82ª\82 \82è\82Ü\82·\81B')
+    else
+      frmMessageBox.ShowMessage('SSTP Bottle\83T\81[\83o\82É\90Ú\91±\82Å\82«\82Ü\82¹\82ñ\82Å\82µ\82½\81B'#13#10 +
+                  '\83l\83b\83g\83\8f\81[\83N\82É\8cq\82ª\82Á\82Ä\82¢\82é\82©\8am\94F\82µ\82Ä\82­\82¾\82³\82¢\81B'#13#10 +
+                  '\82 \82é\82¢\82Í\83T\81[\83o\82ª\83_\83E\83\93\82µ\82Ä\82¢\82é\89Â\94\\90«\82ª\82 \82è\82Ü\82·\81B');
+  end;
+  ShowHintLabel('SSTP Bottle Server\90Ú\91±\82É\8e¸\94s\82µ\82Ü\82µ\82½', WarningColor);
+  Inc(FBeginConnectFailCount);
+end;
+
+procedure TfrmSender.BuildReplaceMenu(Root: TMenuItem);
+var
+  i: integer;
+  Presets: TReplacePresetCollection;
+  NewItem: TMenuItem;
+begin
+  Root.Clear;
+  Presets := Pref.ReplacePresets.Presets;
+  for i := 0 to Presets.Count-1 do
+  begin
+    NewItem := TMenuItem.Create(Root);
+    with NewItem do
+    begin
+      Caption := Presets[i].Title;
+      ShortCut := Presets[i].ShortCut;
+      AutoHotkeys := maManual;
+      Hint := Presets[i].Pairs.StringExpression;
+      Tag := i;
+      OnClick := mnPresetReplaceClick;
+    end;
+    Root.Add(NewItem);
+  end;
+  Root.Enabled := Presets.Count > 0;
+end;
+
+procedure TfrmSender.mnPresetReplaceClick(Sender: TObject);
+var
+  Preset: TReplacePreset;
+  Lines, New: string;
+begin
+  // \83v\83\8a\83Z\83b\83g\92u\8a·\82ð\8eÀ\8ds
+  Preset := Pref.ReplacePresets.Presets[(Sender as TMenuItem).Tag];
+  Lines := memScript.Lines.Text;
+  New := Preset.Pairs.ExecuteReplace(Lines);
+  if New <> Lines then
+  begin
+    memScript.SelectAll;
+    memScript.SelText := New;
+  end;
+  if Preset.ConfirmAfterReplace then
+  begin
+    btnConfirm.Click;
+  end;
+end;
+
 end.