OSDN Git Service

Faster log item dragging
[winbottle/winbottle.git] / bottleclient / LogForm.pas
1 unit LogForm;
2
3 interface
4
5 uses
6   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
7   ComCtrls, ToolWin, StdCtrls, ExtCtrls, SsParser, BottleDef, Menus,
8   Clipbrd, Logs, ShellAPI, Commctrl, DirectSstp, Contnrs, StrUtils,
9   TalkShowFrame, SppList;
10
11 type
12   // \83\8d\83O\82Ì\95Û\91\95û\96@
13   TSaveLogType = (stLog, stLogWithChannels, stText, stXML);
14
15   // \83\8a\83X\83g\83r\83\85\81[\82Ì\83X\83N\83\8d\81[\83\8b\95û\8cü
16   TLVScrollDir = (lvScrollUp, lvScrollDown);
17
18   TfrmLog = class(TForm)
19     ToolBar: TToolBar;
20     tbtnClear: TToolButton;
21     pnlUpper: TPanel;
22     SsParser: TSsParser;
23     StatusBar: TStatusBar;
24     tbtnSaveLog: TToolButton;
25     PopupMenuPreview: TPopupMenu;
26     mnPopCopy: TMenuItem;
27     tbtnVoteMessage: TToolButton;
28     PopupMenuListView: TPopupMenu;
29     mnPopUpVoteMessage: TMenuItem;
30     SaveDialog: TSaveDialog;
31     pnlPanel: TPanel;
32     Splitter: TSplitter;
33     mnPopUpCopyScript: TMenuItem;
34     PopupMenuSaveLog: TPopupMenu;
35     mnSaveLog: TMenuItem;
36     mnSaveLogChannel: TMenuItem;
37     mnSaveLogScript: TMenuItem;
38     mnSaveLogXML: TMenuItem;
39     ToolButton1: TToolButton;
40     mnJumpURL: TMenuItem;
41     mnPopUpAgreeMessage: TMenuItem;
42     tbtnAgreeMessage: TToolButton;
43     ToolButton2: TToolButton;
44     tbtnPreviewStyle: TToolButton;
45     PopupMenuPreviewStyle: TPopupMenu;
46     mnPreviewStyleConversation: TMenuItem;
47     mnPreviewStyleScript: TMenuItem;
48     mnPreviewStyleScriptWithLineBreak: TMenuItem;
49     Panel1: TPanel;
50     tabBottleLog: TTabControl;
51     lvwLog: TListView;
52     tbtnDownloadLog: TToolButton;
53     PopupMenuTab: TPopupMenu;
54     mnCloseTab: TMenuItem;
55     tbtnFindBottle: TToolButton;
56     tbtnOpenLog: TToolButton;
57     OpenDialog: TOpenDialog;
58     tbtnInsertCue: TToolButton;
59     mnInsertCue: TMenuItem;
60     PopupMenuListPreviewStyle: TPopupMenu;
61     mnListPreviewStyleNormal: TMenuItem;
62     mnListPreviewStyleTagStripped: TMenuItem;
63     tbtnListPreviewStyle: TToolButton;
64     mnListPreviewStyleNoColor: TMenuItem;
65     SsParserForTalkShow: TSsParser;
66     mnPreviewStyleConversationImage: TMenuItem;
67     pnlPreviewArea: TPanel;
68     TalkShowFrame: TfrmTalkShow;
69     edtScript: TRichEdit;
70     tbtnSendEditor: TToolButton;
71     mnSendEditor: TMenuItem;
72     timScrollTimer: TTimer;
73     mnChangeTabName: TMenuItem;
74     procedure tbtnClearClick(Sender: TObject);
75     procedure FormCreate(Sender: TObject);
76     procedure lvwLogChange(Sender: TObject; Item: TListItem;
77       Change: TItemChange);
78     procedure lvwLogDblClick(Sender: TObject);
79     procedure lvwLogKeyPress(Sender: TObject; var Key: Char);
80     procedure FormDestroy(Sender: TObject);
81     procedure lvwLogClick(Sender: TObject);
82     procedure mnSaveLogClick(Sender: TObject);
83     procedure lvwLogColumnClick(Sender: TObject; Column: TListColumn);
84     procedure mnPopUpCopyScriptClick(Sender: TObject);
85     procedure mnSaveLogChannelClick(Sender: TObject);
86     procedure mnSaveLogScriptClick(Sender: TObject);
87     procedure mnSaveLogXMLClick(Sender: TObject);
88     procedure lvwLogData(Sender: TObject; Item: TListItem);
89     procedure PopupMenuListViewPopup(Sender: TObject);
90     procedure lvwLogCustomDrawItem(Sender: TCustomListView;
91       Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
92     procedure PopupMenuPreviewStylePopup(Sender: TObject);
93     procedure mnPreviewStyleClick(Sender: TObject);
94     procedure tbtnPreviewStyleClick(Sender: TObject);
95     procedure tabBottleLogChange(Sender: TObject);
96     procedure tabBottleLogChanging(Sender: TObject;
97       var AllowChange: Boolean);
98     procedure tabBottleLogContextPopup(Sender: TObject; MousePos: TPoint;
99       var Handled: Boolean);
100     procedure mnCloseTabClick(Sender: TObject);
101     procedure tbtnFindBottleClick(Sender: TObject);
102     procedure tbtnOpenLogClick(Sender: TObject);
103     procedure tabBottleLogMouseDown(Sender: TObject; Button: TMouseButton;
104       Shift: TShiftState; X, Y: Integer);
105     procedure tabBottleLogDragOver(Sender, Source: TObject; X, Y: Integer;
106       State: TDragState; var Accept: Boolean);
107     procedure tabBottleLogDragDrop(Sender, Source: TObject; X, Y: Integer);
108     procedure tabBottleLogEndDrag(Sender, Target: TObject; X, Y: Integer);
109     procedure lvwLogDrawItem(Sender: TCustomListView; Item: TListItem;
110       Rect: TRect; State: TOwnerDrawState);
111     procedure mnListPreviewStyleClick(Sender: TObject);
112     procedure tbtnListPreviewStyleClick(Sender: TObject);
113     procedure PopupMenuListPreviewStylePopup(Sender: TObject);
114     procedure lvwLogDragOver(Sender, Source: TObject; X, Y: Integer;
115       State: TDragState; var Accept: Boolean);
116     procedure lvwLogDragDrop(Sender, Source: TObject; X, Y: Integer);
117     procedure timScrollTimerTimer(Sender: TObject);
118     procedure mnChangeTabNameClick(Sender: TObject);
119     procedure lvwLogStartDrag(Sender: TObject;
120       var DragObject: TDragObject);
121   private
122     { Private \90é\8c¾ }
123     FLastScript: String; //\83X\83N\83\8a\83v\83g\8dÄ\95`\89æ\97}\90§\97p
124     FBottleLogList: TObjectList;
125     //
126     FDragTabIndex: integer; //\83^\83u\83h\83\89\83b\83O\83h\83\8d\83b\83v\8aÖ\98A
127     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)
128     //
129     // \83\8a\83X\83g\83r\83\85\81[\83h\83\89\83b\83O\83h\83\8d\83b\83v\8aÖ\98A
130     FLVScrollDir: TLVScrollDir; // \83X\83N\83\8d\81[\83\8b\95û\8cü
131     FLVDragDest: integer;    //\83h\83\8d\83b\83v\82·\82é\88Ê\92u(\82·\82®\89º\82É\82­\82é\83A\83C\83e\83\80\82ÌIndex)
132     //
133     procedure UpdateScript(const Script: String);
134     procedure UpdateScriptConversationColor(const Script: String);
135     procedure UpdateScriptScript(const Script: String);
136     procedure mnURLClick(Sender: TObject);
137     procedure ExtractURLs(Script: String; Result: TStrings);
138     function GetDefaultFileName(const Name: String; const Ext: String): String;
139     function BottleLogTitled(const LogName: String): TBottleLogList;
140     procedure DrawSingleLineScript(LogItem: TLogItem; Rect: TRect;
141       Item: TListItem);
142     procedure PreviewStyleChange;
143     procedure DrawListViewDragBorder(const Rect: TRect);
144   protected
145     procedure CreateParams(var Params: TCreateParams); override;
146   public
147     { Public \90é\8c¾ }
148     function SelectedBottleLog: TBottleLogList;
149     property BottleLogList: TObjectList read FBottleLogList;
150     procedure AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String);
151     procedure AddCurrentSystemLog(const LogName, MessageString: String);
152     procedure VoteLog(const MID: String; const Vote: integer);
153     procedure AgreeLog(const MID: String; const Agree: integer);
154     procedure SetBottleState(const MID: String; State: TLogState);
155     procedure AllBottleOpened;
156     procedure LogLoaded(Sender: TObject);
157     procedure LogLoadFailure(Sender: TObject; const Message: String);
158     procedure LogLoadWork(Sender: TObject);
159     procedure UpdateTab;
160     procedure UpdateWindow;
161     procedure SelAndFocusMessage(const MID: String);
162   end;
163
164   TBottleLogDragObject = class(TDragObjectEx);
165
166 var
167   frmLog: TfrmLog;
168
169 const
170   IconBottle    = 17;
171   IconOpened    = 30;
172   IconPlaying   = 31;
173   IconSystemLog = 26;
174   IconURL       = 43;
175   SubChannel    = 0;
176   SubGhost      = 1;
177   SubVotes      = 2;
178   SubAgrees     = 3;
179   SubScript     = 4;
180
181 implementation
182
183 uses MainForm;
184
185 {$R *.DFM}
186
187 { TfrmLog }
188
189 procedure TfrmLog.AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String);
190 var Sel: integer;
191 begin
192   BottleLogTitled(LogName).AddScriptLog(Script, Channel, MID, Ghost);
193   if SelectedBottleLog <> BottleLogTitled(LogName) then Exit;
194   lvwLog.OnChange := nil; //\83C\83x\83\93\83g\94­\90¶(\82¢\82ë\82¢\82ë\8dÄ\95`\89æ\82ª\8bN\82«\82é)\82Ì\97}\90§
195   if lvwLog.Selected <> nil then Sel := lvwLog.Selected.Index else Sel := -1;
196   lvwLog.Items.Count := SelectedBottleLog.Count;
197   UpdateWindow;
198   if Sel >= 0 then begin
199     lvwLog.Selected := lvwLog.Items[Sel + 1];
200     lvwLog.Selected.Focused := true;
201   end;
202   if not lvwLog.Focused then
203     ListView_Scroll(lvwLog.Handle, 0, High(integer));
204   lvwLog.OnChange := lvwLogChange;
205 end;
206
207 procedure TfrmLog.AddCurrentSystemLog(const LogName, MessageString: String);
208 var Sel: integer;
209 begin
210   BottleLogTitled(LogName).AddSystemLog(MessageString);
211   if SelectedBottleLog <> BottleLogTitled(LogName) then Exit;
212   lvwLog.OnChange := nil;
213   if lvwLog.Selected <> nil then Sel := lvwLog.Selected.Index else Sel := -1;
214   lvwLog.Items.Count := SelectedBottleLog.Count;
215   UpdateWindow;
216   if Sel >= 0 then begin
217     lvwLog.Selected := lvwLog.Items[Sel + 1];
218     lvwLog.Selected.Focused := true;
219   end;
220   if not lvwLog.Focused then
221     ListView_Scroll(lvwLog.Handle, 0, High(integer));
222   lvwLog.OnChange := lvwLogChange;
223 end;
224
225
226
227 procedure TfrmLog.tbtnClearClick(Sender: TObject);
228 begin
229   if SelectedBottleLog = nil then Exit;
230   FBottleLogList.Delete(tabBottleLog.TabIndex);
231   tabBottleLog.TabIndex := 0;
232   UpdateTab;
233   UpdateWindow;
234   lvwLogChange(Self, nil, ctState);
235 end;
236
237 procedure TfrmLog.FormCreate(Sender: TObject);
238 var i: integer;
239 begin
240   FLVDragDest := -1; // \83\8a\83X\83g\83r\83\85\81[\82Ì\83h\83\89\83b\83O\92\86\82Å\82Í\82È\82¢
241   FBottleLogList := TObjectList.Create;
242
243   SsParser.TagPattern.Assign(frmSender.SsParser.TagPattern);
244   SsParser.MetaPattern.Assign(frmSender.SsParser.MetaPattern);
245
246   with Pref.LogWindowPosition do begin
247     Self.Left   := Left;
248     Self.Top    := Top;
249     Self.Width  := Right - Left + 1;
250     Self.Height := Bottom - Top + 1;
251   end;
252   lvwLog.DoubleBuffered := true;
253   pnlPreviewArea.Height := Pref.LogWindowDividerPos;
254
255   i := 0;
256   while Token(Pref.LogWindowColumnWidth, ',', i) <> '' do begin
257     lvwLog.Columns[i].Width := StrToIntDef(Token(Pref.LogWindowColumnWidth, ',', i), 100);
258     Inc(i);
259   end;
260
261   SsParserForTalkShow.TagPattern.Assign(SsParser.TagPattern);
262   SsParserForTalkShow.MetaPattern.Assign(SsParser.MetaPattern);
263   SsParserForTalkShow.EscapeInvalidMeta := false;
264   SsParserForTalkShow.LeaveEscape := false;
265   TalkShowFrame.SsParser := self.SsParserForTalkShow;
266
267   TalkShowFrame.SetPreviewFont(edtScript.Font);
268   TalkShowFrame.PrevControl := lvwLog;
269
270   PreviewStyleChange;
271   UpdateWindow; // Reset window color and enabled status of some buttons
272 end;
273
274 procedure TfrmLog.FormDestroy(Sender: TObject);
275 var i: integer;
276     WidthStr: String;
277 begin
278   WidthStr := '';
279   for i := 0 to lvwLog.Columns.Count-1 do begin
280     if i > 0 then WidthStr := WidthStr + ',';
281     WidthStr := WidthStr + IntToStr(lvwLog.Column[i].Width);
282   end;
283   Pref.LogWindowColumnWidth := WidthStr;
284
285   with Pref.LogWindowPosition do begin
286     Left   := Self.Left;
287     Top    := Self.Top;
288     Right  := Self.Left + Self.Width - 1;
289     Bottom := Self.Top + Self.Height - 1;
290   end;
291   Pref.LogWindowDividerPos := pnlPreviewArea.Height;
292
293   FreeAndNil(FBottleLogList);
294 end;
295
296 procedure TfrmLog.lvwLogChange(Sender: TObject; Item: TListItem;
297   Change: TItemChange);
298 var Script: String;
299     Log: TLogItem;
300 begin
301   if SelectedBottleLog <> nil then begin
302     StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
303     if Change = ctState then begin
304       Script := '';
305       if lvwLog.Selected <> nil then begin
306         Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
307         if (Log.LogType = ltBottle) and not frmSender.Connecting then begin
308           Script := Log.Script;
309           frmSender.actVoteMessage.Enabled := true;
310           frmSender.actAgreeMessage.Enabled := true;
311           frmSender.actSendEditor.Enabled := true;
312           frmSender.actInsertCue.Enabled := true;
313           mnPopUpCopyScript.Enabled := true;
314           StatusBar.Panels[1].Text := Format('%d\83o\83C\83g - \83_\83u\83\8b\83N\83\8a\83b\83N\82Å\8dÄ\90¶', [Length(Log.Script)]);
315           if Pref.LogWindowPreviewStyle = psImageConversation then
316             TalkShowFrame.View(Log)
317           else
318             UpdateScript(Script);
319         end else begin
320           frmSender.actVoteMessage.Enabled := false;
321           frmSender.actAgreeMessage.Enabled := false;
322           frmSender.actSendEditor.Enabled := false;
323           frmSender.actInsertCue.Enabled := false;
324           mnPopUpCopyScript.Enabled := false;
325           StatusBar.Panels[1].Text := '';
326           UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\82ð\83N\83\8a\83A
327         end;
328       end else begin
329         frmSender.actVoteMessage.Enabled := false;
330         frmSender.actAgreeMessage.Enabled := false;
331         frmSender.actSendEditor.Enabled := false;
332         frmSender.actInsertCue.Enabled := false;
333         mnPopUpCopyScript.Enabled := false;
334         StatusBar.Panels[1].Text := '';
335         UpdateScript(Script); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
336       end;
337     end;
338     tbtnSaveLog.Enabled := lvwLog.Items.Count > 0;
339   end else begin
340     frmSender.actVoteMessage.Enabled := false;
341     frmSender.actAgreeMessage.Enabled := false;
342     frmSender.actSendEditor.Enabled := false;
343     frmSender.actInsertCue.Enabled := false;
344     mnPopUpCopyScript.Enabled := false;
345     StatusBar.Panels[0].Text := '';
346     UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
347   end;
348 end;
349
350 procedure TfrmLog.lvwLogDblClick(Sender: TObject);
351 var Script, ErrorMes: String;
352     Log, CueItem: TLogItem;
353     Res: integer;
354 begin
355   if lvwLog.Selected = nil then
356     Exit;
357   Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
358   if Log = nil then Exit;
359   if Log.LogType <> ltBottle then
360     Exit;
361   Script := frmSender.ScriptTransForSSTP(Log.Script, ErrorMes);
362   if ErrorMes <> '' then
363   begin
364     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+
365         ErrorMes + #13#10 +
366         '\8b­\90§\93I\82É\8dÄ\90\82µ\82Ü\82·\82©?'#13#10,
367         mtWarning, mbOkCancel, 0
368       );
369     if Res = mrCancel then
370       Exit;
371   end;
372
373   CueItem := TLogItem.Create(Log);
374   try
375     CueItem.Script := Script;
376     frmSender.BottleSstp.Unshift(CueItem);
377   except
378     CueItem.Free;
379   end;
380 end;
381
382 procedure TfrmLog.UpdateScriptConversationColor(const Script: String);
383 var i: integer;
384     scr: String;
385     UnyuTalking, Talked, InSynchronized: boolean;
386 begin
387   scr := Script;
388   frmSender.DoTrans(scr, [toConvertURL]);
389   SsParser.LeaveEscape := false;
390   SsParser.InputString := scr;
391   UnyuTalking := false;
392   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
393   InSynchronized := false;
394   edtScript.Text := '';
395   edtScript.Color := Pref.BgColor;
396   for i := 0 to SsParser.Count-1 do begin
397     if (SsParser[i] = '\_s') and not InSynchronized then begin
398       InSynchronized := true;
399       if Talked then begin
400         edtScript.SelText := #13#10;
401         Talked := false;
402       end;
403     end else if (SsParser[i] = '\_s') and InSynchronized then begin
404       InSynchronized := false;
405       if Talked then begin
406         edtScript.SelText := #13#10;
407         Talked := false;
408       end;
409     end;
410     if (SsParser[i] = '\u') and not UnyuTalking then begin
411       UnyuTalking := true;
412       if Talked then begin
413         edtScript.SelText := #13#10;
414         Talked := false;
415       end;
416     end;
417     if (SsParser[i] = '\h') and UnyuTalking then begin
418       UnyuTalking := false;
419       if Talked then begin
420         edtScript.SelText := #13#10;
421         Talked := false;
422       end;
423     end;
424     if SsParser.MarkUpType[i] = mtStr then begin
425       if InSynchronized then
426         edtScript.SelAttributes.Color := Pref.TalkColorS
427       else if UnyuTalking then
428         edtScript.SelAttributes.Color := Pref.TalkColorU
429       else
430         edtScript.SelAttributes.Color := Pref.TalkColorH;
431       edtScript.SelText := SsParser[i];
432       Talked := true;
433     end;
434     if SsParser.MarkUpType[i] = mtMeta then begin
435       edtScript.SelAttributes.Color := Pref.MetaWordColor;
436       edtScript.SelText := SsParser[i];
437       Talked := true;
438     end;
439   end;
440 end;
441
442 procedure TfrmLog.lvwLogKeyPress(Sender: TObject; var Key: Char);
443 begin
444   if Key = #13 then lvwLogDblClick(Sender);
445 end;
446
447 procedure TfrmLog.CreateParams(var Params: TCreateParams);
448 begin
449   inherited;
450   Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
451 end;
452
453 procedure TfrmLog.lvwLogClick(Sender: TObject);
454 begin
455   //\89E\83N\83\8a\83b\83N\82Å\83\81\83j\83\85\81[\8fo\82·\82Æ\82«\82É\94­\90\82·\82é\95s\8bï\8d\87\91Î\8dô
456   with lvwLog do
457     Selected := Selected;
458 end;
459
460 procedure TfrmLog.lvwLogColumnClick(Sender: TObject; Column: TListColumn);
461 var SortType: TBottleLogSortType;
462     SelectedMID: String;
463     SortColumn: integer;
464 begin
465   if SelectedBottleLog = nil then
466     Exit;
467   if lvwLog.Selected <> nil then
468     SelectedMID := SelectedBottleLog.Bottles[lvwLog.Selected.Index].MID
469   else
470     SelectedMID := '';
471
472   SortColumn := Column.Index;
473   case SortColumn-1 of
474     -1: SortType := stLogTime;
475     subChannel: SortType := stChannel;
476     subGhost:   SortType := stGhost;
477     subVotes:   SortType := stVote;
478     subAgrees:  SortType := stAgree;
479     subScript:  SortType := stScript;
480   else
481     SortType := stLogTime;
482   end;
483
484   SelectedBottleLog.SortBottles(SortType);
485   lvwLog.Invalidate;
486   if Length(SelectedMID) > 0 then
487     SelAndFocusMessage(SelectedMID);
488 end;
489
490
491 procedure TfrmLog.mnPopUpCopyScriptClick(Sender: TObject);
492 var
493   Log: TLogItem;
494   Clip: TClipBoard;
495 begin
496   Log := SelectedBottleLog.Bottles[frmLog.lvwLog.Selected.Index];
497   if Log = nil then Exit;
498   Clip := ClipBoard();
499   Clip.SetTextBuf(PChar(Log.Script));
500 end;
501
502 procedure TfrmLog.SetBottleState(const MID: String; State: TLogState);
503 var i: integer;
504     Bottle: TLogItem;
505 begin
506   for i := 0 to FBottleLogList.Count-1 do begin
507     Bottle := (FBottleLogList[i] as TBottleLogList).Bottle(MID);
508     if Bottle <> nil then begin
509       Bottle.State := State;
510       lvwLog.OnChange := nil;
511       lvwLog.Invalidate;
512       lvwLog.OnChange := lvwLogChange;
513     end;
514   end;
515 end;
516
517 procedure TfrmLog.mnSaveLogClick(Sender: TObject);
518 begin
519   if SelectedBottleLog = nil then Exit;
520   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
521   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
522   SaveDialog.DefaultExt := 'log';
523   SaveDialog.FilterIndex := 1;
524   if SaveDialog.Execute then
525     SelectedBottleLog.SaveToSstpLog(SaveDialog.FileName, false);
526 end;
527
528 procedure TfrmLog.mnSaveLogChannelClick(Sender: TObject);
529 begin
530   if SelectedBottleLog = nil then Exit;
531   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
532   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
533   SaveDialog.DefaultExt := 'log';
534   SaveDialog.FilterIndex := 1;
535   if SaveDialog.Execute then
536     SelectedBottleLog.SaveToSstpLog(SaveDialog.FileName, true);
537 end;
538
539 procedure TfrmLog.mnSaveLogScriptClick(Sender: TObject);
540 begin
541   if SelectedBottleLog = nil then Exit;
542   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.txt');
543   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
544   SaveDialog.DefaultExt := 'txt';
545   SaveDialog.FilterIndex := 2;
546   if SaveDialog.Execute then
547     SelectedBottleLog.SaveToText(SaveDialog.FileName);
548 end;
549
550 procedure TfrmLog.mnSaveLogXMLClick(Sender: TObject);
551 begin
552   if SelectedBottleLog = nil then Exit;
553   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.xml');
554   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
555   SaveDialog.DefaultExt := 'xml';
556   SaveDialog.FilterIndex := 3;
557   if SaveDialog.Execute then
558     SelectedBottleLog.SaveToXmlFile(SaveDialog.FileName);
559 end;
560
561 procedure TfrmLog.lvwLogData(Sender: TObject; Item: TListItem);
562 var i: integer;
563     Log: TLogItem;
564 begin
565   if Item = nil then Exit;
566   i := Item.Index;
567   Log := SelectedBottleLog.Bottles[i];
568   with Item do begin
569     Caption := FormatDateTime('yy/mm/dd hh:nn:ss', Log.LogTime);
570     SubItems.Clear;
571     SubItems.Add(Log.Channel);
572     SubItems.Add(Log.Ghost);
573     if Log.LogType = ltBottle then begin
574       if Log.Votes > 0 then
575         SubItems.Add(IntToStr(Log.Votes))
576       else
577         SubItems.Add('');
578       if Log.Agrees > 0 then
579         SubItems.Add(IntToStr(Log.Agrees))
580       else
581         SubItems.Add('');
582     end else begin
583       // \83V\83X\83e\83\80\83\8d\83O\82È\82Ç\82Í\93\8a\95[\81E\93¯\88Ó\82ð\95\\8e¦\82µ\82È\82¢
584       SubItems.Add('');
585       SubItems.Add('');
586     end;
587     SubItems.Add(Log.Script);
588
589     if Log.LogType = ltBottle then begin
590       case Log.State of
591         lsUnopened: ImageIndex := IconBottle;
592         lsPlaying:  ImageIndex := IconPlaying;
593         lsOpened:   ImageIndex := IconOpened;
594       end;
595     end else
596       ImageIndex := IconSystemLog;
597   end;
598 end;
599
600 procedure TfrmLog.UpdateWindow;
601 var EnabledFlag: boolean;
602 begin
603   if true then begin // ColorScript
604     if lvwLog.Color <> Pref.BgColor then lvwLog.Color := Pref.BgColor;
605     if lvwLog.Font.Color <> Pref.TalkColorH then lvwLog.Font.Color := Pref.TalkColorH;
606   end else begin
607     if lvwLog.Color <> clWindow then lvwLog.Color := clWindow;
608     if lvwLog.Font.Color <> clWindowText then lvwLog.Font.Color := clWindowText;
609   end;
610   if SelectedBottleLog <> nil then begin
611     Caption := '\83\8d\83O - ' + SelectedBottleLog.Title;
612     StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
613     lvwLog.Items.Count := SelectedBottleLog.Count;
614   end else begin
615     Caption := '\83\8d\83O';
616     StatusBar.Panels[0].Text := '';
617     StatusBar.Panels[1].Text := '';
618     lvwLog.Items.Count := 0;
619   end;
620
621   EnabledFlag := SelectedBottleLog <> nil;
622   tbtnClear.Enabled := EnabledFlag;
623   tbtnSaveLog.Enabled := EnabledFlag;
624   tbtnFindBottle.Enabled := EnabledFlag;
625
626   lvwLog.Invalidate;
627 end;
628
629 procedure TfrmLog.PopupMenuListViewPopup(Sender: TObject);
630 var Log: TLogItem;
631     Child: TMenuItem;
632     Urls: TStringList;
633     i: integer;
634 begin
635   for i := mnJumpURL.Count-1 downto 0 do begin
636     mnJumpURL.Items[i].Free;
637   end;
638   mnJumpURL.Enabled := false;
639   if lvwLog.Selected = nil then Exit;
640   Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
641   if Log = nil then Exit;
642   Urls := TStringList.Create;
643   try
644     ExtractURLs(Log.Script, Urls);
645     for i := 0 to Urls.Count-1 do begin
646       Child := TMenuItem.Create(Self);
647       with Child do begin
648         Caption := Format('(&%d) %s', [i+1, StringReplace(Urls[i], '&', '&&', [rfReplaceAll])]);
649         Tag := i;
650         OnClick := mnURLClick;
651         AutoHotkeys := maManual;
652         mnJumpURL.Add(Child);
653       end;
654     end;
655     mnJumpURL.Enabled := Urls.Count > 0;
656   finally
657     Urls.Free;
658   end;
659 end;
660
661 procedure TfrmLog.mnURLClick(Sender: TObject);
662 var LogItem: TLogItem;
663     URL: String;
664     Urls: TStringList;
665 begin
666   if (lvwLog.Selected = nil) or (SelectedBottleLog = nil) then Exit;
667   LogItem := SelectedBottleLog[lvwLog.Selected.Index] as TLogItem;
668   Urls := TStringList.Create;
669   try
670     ExtractURLs(LogItem.Script, Urls);
671     URL := Urls[(Sender as TMenuItem).Tag];
672     ShellExecute(Handle, 'open', PChar(URL), nil, nil, SW_SHOW);
673   finally
674     Urls.Free;
675   end;
676 end;
677
678 procedure TfrmLog.ExtractURLs(Script: String; Result: TStrings);
679 var i, u, j: integer;
680     s: String;
681 begin
682   Result.Clear;
683   SsParser.InputString := Script;
684   for i := 0 to SsParser.Count-1 do begin
685     if (SsParser.Match(SsParser[i], '\URL%b') > 0) then begin
686       for u := 7 downto 1 do begin
687         if (SsParser.Match(SsParser[i],
688             '\URL%b'+StringReplace(StringOfChar('-', u*2),
689             '-', '%b', [rfReplaceAll]))) > 0 then begin
690           for j := 1 to u do begin
691             s := SsParser.GetParam(SsParser[i], j*2);
692             if Pos('http://', s) > 0 then Result.Add(s);
693           end;
694           Break;
695         end;
696       end;
697       if SsParser.Match(SsParser[i], '\URL%b%b') = 0 then begin //\8aÈ\88Õ\94ÅURL\95Ï\8a·
698         //\8aÈ\88Õ\8c`\8e®\URL\83^\83O\95Ï\8a·
699         s := SsParser.GetParam(SsParser[i], 1);
700         if Pos('http://', s) > 0 then Result.Add(s);
701       end;
702     end;
703   end;
704 end;
705
706 procedure TfrmLog.SelAndFocusMessage(const MID: String);
707 var i: integer;
708     Log: TLogItem;
709 begin
710   for i := 0 to SelectedBottleLog.Count-1 do begin
711     Log := SelectedBottleLog.Items[i] as TLogItem;
712     if Log.MID = MID then begin
713       lvwLog.Items[i].Selected := true;
714       lvwLog.Items[i].Focused := true;
715     end;
716   end;
717 end;
718
719 procedure TfrmLog.lvwLogCustomDrawItem(Sender: TCustomListView;
720   Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
721 begin
722   //
723 end;
724
725 procedure TfrmLog.UpdateScript(const Script: String);
726 begin
727   if Script <> FLastScript then begin
728     if Pref.LogWindowPreviewStyle = psConversation then begin
729       UpdateScriptConversationColor(Script);
730     end else begin
731       UpdateScriptScript(Script);
732     end;
733     SendMessage(edtScript.Handle, EM_LINESCROLL, Low(integer), Low(integer)); //\83X\83N\83\8d\81[\83\8b\96ß\82µ
734     FLastScript := Script;
735   end;
736 end;
737
738 procedure TfrmLog.PopupMenuPreviewStylePopup(Sender: TObject);
739 var i: integer;
740 begin
741   with PopupMenuPreviewStyle do
742     for i := 0 to Items.Count-1 do
743       Items[i].Checked := Items[i].Tag = Ord(Pref.LogWindowPreviewStyle)
744 end;
745
746 procedure TfrmLog.mnPreviewStyleClick(Sender: TObject);
747 var i: integer;
748 begin
749   with PopupMenuPreviewStyle do
750     for i := 0 to Items.Count-1 do
751       Items[i].Checked := (Sender as TMenuItem).Tag = Items[i].Tag;
752   Pref.LogWindowPreviewStyle := TLogWindowPreviewStyle((Sender as TMenuItem).Tag);
753   PreviewStyleChange;
754   FLastScript := '';
755   lvwLogChange(self, lvwLog.Selected, ctState);
756 end;
757
758 procedure TfrmLog.UpdateScriptScript(const Script: String);
759 var
760   UnyuTalking, InSynchronized: boolean;
761   i: integer;
762 begin
763   edtScript.Color := Pref.BgColor;
764   SsParser.LeaveEscape := true;
765   SsParser.InputString := Script;
766   edtScript.Text := '';
767   edtScript.SelAttributes.Color := clWindowText;
768   UnyuTalking := false;
769   InSynchronized := false;
770   for i := 0 to SsParser.Count-1 do begin
771     case SsParser.MarkUpType[i] of
772       mtStr: begin
773         if InSynchronized then
774           edtScript.SelAttributes.Color := Pref.TalkColorS
775         else if UnyuTalking then
776           edtScript.SelAttributes.Color := Pref.TalkColorU
777         else
778           edtScript.SelAttributes.Color := Pref.TalkColorH;
779       end;
780       mtTag: begin
781         edtScript.SelAttributes.Color := Pref.MarkUpColor;
782         if SsParser[i] = '\h' then
783           UnyuTalking := false
784         else if SsParser[i] = '\u' then
785           UnyuTalking := true
786         else if SsParser[i] = '\_s' then
787           InSynchronized := not InSynchronized;
788       end;
789       mtMeta:   edtScript.SelAttributes.Color := Pref.MetaWordColor;
790       mtTagErr: edtScript.SelAttributes.Color := Pref.MarkErrorColor;
791     end;
792     edtScript.SelText := SsParser[i];
793     if (SsParser[i] = '\n') and (Pref.LogWindowPreviewStyle = psScriptWithLineBreak) then
794       edtScript.SelText := #13#10;
795   end;
796 end;
797
798 procedure TfrmLog.tbtnPreviewStyleClick(Sender: TObject);
799 var sel: integer;
800 begin
801   sel := Ord(Pref.LogWindowPreviewStyle);
802   sel := sel + 1;
803   if sel > Ord(High(TLogWindowPreviewStyle)) then sel := 0;
804   Pref.LogWindowPreviewStyle := TLogWindowPreviewStyle(sel);
805   FLastScript := '';
806   PreviewStyleChange;
807   lvwLogChange(self, lvwLog.Selected, ctState);
808 end;
809
810 function TfrmLog.SelectedBottleLog: TBottleLogList;
811 begin
812   if tabBottleLog.TabIndex >= 0 then
813     Result := FBottleLogList.Items[tabBottleLog.TabIndex] as TBottleLogList
814   else
815     Result := nil;
816 end;
817
818 procedure TfrmLog.tabBottleLogChange(Sender: TObject);
819 begin
820   UpdateWindow;
821   if SelectedBottleLog.SelectedIndex >= 0 then begin
822     lvwLog.Items[SelectedBottleLog.SelectedIndex].Selected := true;
823     if lvwLog.Focused then lvwLog.Selected.Focused := true;
824   end;
825   lvwLogChange(Self, nil, ctState);
826 end;
827
828 procedure TfrmLog.LogLoaded(Sender: TObject);
829 begin
830   if SelectedBottleLog = Sender then begin
831     UpdateWindow;
832   end;
833 end;
834
835 procedure TfrmLog.UpdateTab;
836 var i, cur: integer;
837 begin
838   cur := tabBottleLog.tabIndex;
839   tabBottleLog.Tabs.Clear;
840   for i := 0 to FBottleLogList.Count - 1 do begin
841     tabBottleLog.Tabs.Add((FBottleLogList[i] as TBottleLogList).Title);
842   end;
843   if FBottleLogList.Count > 0 then begin
844     if cur < FBottleLogList.Count then
845       tabBottleLog.TabIndex := cur
846     else
847       tabBottleLog.TabIndex := FBottleLogList.Count-1;
848   end;
849 end;
850
851 procedure TfrmLog.LogLoadFailure(Sender: TObject; const Message: String);
852 begin
853   Beep;
854   ShowMessage(Message);
855   if Sender = SelectedBottleLog then UpdateWindow;
856 end;
857
858 procedure TfrmLog.AgreeLog(const MID: String; const Agree: integer);
859 var i: integer;
860     flag: boolean;
861 begin
862   flag := false;
863   for i := 0 to FBottleLogList.Count - 1 do begin
864     if (FBottleLogList[i] as TBottleLogList).Bottle(MID) <> nil then begin
865       (FBottleLogList[i] as TBottleLogList).Bottle(MID).Agrees := Agree;
866       flag := true;
867     end;
868   end;
869   if flag then lvwLog.Invalidate;
870 end;
871
872 procedure TfrmLog.VoteLog(const MID: String; const Vote: integer);
873 var i: integer;
874     flag: boolean;
875 begin
876   flag := false;
877   for i := 0 to FBottleLogList.Count - 1 do begin
878     if (FBottleLogList[i] as TBottleLogList).Bottle(MID) <> nil then begin
879       (FBottleLogList[i] as TBottleLogList).Bottle(MID).Votes := Vote;
880       flag := true;
881     end;
882   end;
883   if flag then lvwLog.Invalidate;
884 end;
885
886 procedure TfrmLog.tabBottleLogChanging(Sender: TObject;
887   var AllowChange: Boolean);
888 begin
889   // \8c»\8dÝ\91I\91ð\82³\82ê\82Ä\82¢\82é\83\8d\83O\82Ì\91I\91ð\8fó\91Ô\82ð\95Û\91
890   if SelectedBottleLog = nil then Exit;
891   if lvwLog.Selected <> nil then
892     SelectedBottleLog.SelectedIndex := lvwLog.Selected.Index
893   else
894     SelectedBottleLog.SelectedIndex := -1;
895 end;
896
897 procedure TfrmLog.tabBottleLogContextPopup(Sender: TObject;
898   MousePos: TPoint; var Handled: Boolean);
899 begin
900   with tabBottleLog do begin
901     Tag := IndexOfTabAt(MousePos.X, MousePos.Y);
902     if Tag < 0 then Handled := true;
903   end;
904 end;
905
906 procedure TfrmLog.mnCloseTabClick(Sender: TObject);
907 var PrevSelection: TBottleLogList; // \95Â\82\82½\82Æ\82«\83^\83u\82ª\82¸\82ê\82È\82¢\82æ\82¤\82É\82·\82é\8f\88\97\9d\97p
908     i: integer;
909 begin
910   PrevSelection := SelectedBottleLog;
911   FBottleLogList.Delete(tabBottleLog.Tag);
912   UpdateTab;
913   // \83^\83u\82¸\82ê\96h\8e~\8f\88\97\9d
914   for i := 0 to FBottleLogList.Count-1 do
915     if FBottleLogList[i] = PrevSelection then
916       tabBottleLog.TabIndex := i;
917   UpdateWindow;
918   lvwLogChange(Self, nil, ctState);
919 end;
920
921 procedure TfrmLog.tbtnFindBottleClick(Sender: TObject);
922 var Query: String;
923     ResultLog: TBottleLogList;
924     Item1, Item2: TLogItem;
925     i, matched: integer;
926 begin
927   if SelectedBottleLog = nil then Exit;
928   if SelectedBottleLog.Count = 0 then begin
929     ShowMessage('\8c\9f\8dõ\91Î\8fÛ\82ª\8bó\82Å\82·\81B');
930     Exit;
931   end;
932   Query := '';
933   matched := 0;
934   if InputQuery('\83X\83N\83\8a\83v\83g\96{\95\82ð\8c\9f\8dõ', '\8c\9f\8dõ\95\8e\9a\97ñ', Query) then begin
935     if Query = '' then Exit;
936     ResultLog := TBottleLogList.Create('\8c\9f\8dõ\8c\8b\89Ê');
937     for i := 0 to SelectedBottleLog.Count-1 do begin
938       Item1 := SelectedBottleLog.Items[i] as TLogItem;
939       if AnsiContainsText(Item1.Script, Query) and (Item1.LogType = ltBottle) then begin
940         matched := matched + 1;
941         Item2 := TLogItem.Create(ltBottle, Item1.MID, Item1.Channel,
942           Item1.Script, Item1.Ghost, Item1.LogTime);
943         Item2.State := lsOpened;
944         Item2.Votes := Item1.Votes;
945         Item2.Agrees := Item1.Agrees;
946         ResultLog.Add(Item2);
947       end;
948     end;
949     if matched = 0 then
950       ResultLog.AddSystemLog('\8c©\82Â\82©\82è\82Ü\82¹\82ñ\82Å\82µ\82½');
951     BottleLogList.Add(ResultLog);
952     UpdateTab;
953     tabBottleLog.TabIndex := BottleLogList.Count-1;
954     UpdateWindow;
955   end;
956 end;
957
958 procedure TfrmLog.tbtnOpenLogClick(Sender: TObject);
959 var BottleLog: TBottleLogList;
960     i, Index: integer;
961 begin
962   Index := -1;
963   if OpenDialog.Execute then begin
964     for i := 0 to OpenDialog.Files.Count-1 do begin
965       BottleLog := TBottleLogList.Create(ExtractFileName(OpenDialog.Files[i]));
966       try
967         with BottleLog do
968         begin
969           OnLoaded := LogLoaded;
970           OnLoadFailure := LogLoadFailure;
971           OnLoadWork := LogLoadWork;
972           BottleLog.LoadFromXMLFile(OpenDialog.Files[i]);
973         end;
974         Index := BottleLogList.Add(BottleLog); // \8dÅ\8cã\82É\8aJ\82¢\82½\83\8d\83O\82Ì\88Ê\92u\82ð\8bL\89¯
975       except
976         BottleLog.Free;
977       end;
978     end;
979     UpdateTab;
980     if Index >= 0 then tabBottleLog.TabIndex := Index;
981     UpdateWindow;
982   end;
983 end;
984
985 function TfrmLog.GetDefaultFileName(const Name, Ext: String): String;
986 begin
987   Result := StringReplace(Name, '/', '', [rfReplaceAll]);
988   Result := StringReplace(Result, ' ', '', [rfReplaceAll]);
989   Result := ChangeFileExt(Result, Ext);
990 end;
991
992 function TfrmLog.BottleLogTitled(const LogName: String): TBottleLogList;
993 var i: integer;
994 begin
995   for i := 0 to FBottleLogList.Count-1 do begin
996     if (FBottleLogList[i] as TBottleLogList).Title = LogName then begin
997       Result := (FBottleLogList[i] as TBottleLogList);
998       Exit;
999     end;
1000   end;
1001   // \8c©\82Â\82©\82ç\82È\82¢\8fê\8d\87
1002   Result := TBottleLogList.Create(LogName); // \90V\82µ\82­\8dì\82é
1003   FBottleLogList.Add(Result);
1004   UpdateTab;
1005   if FBottleLogList.Count = 1 then tabBottleLog.TabIndex := 0;
1006 end;
1007
1008 procedure TfrmLog.AllBottleOpened;
1009 var i, j: integer;
1010     Log: TBottleLogList;
1011 begin
1012   for i := 0 to FBottleLogList.Count-1 do begin
1013     Log  := FBottleLogList[i] as TBottleLogList;
1014     for j := 0 to Log.Count-1 do begin
1015       Log.Bottles[j].State := lsOpened;
1016     end;
1017   end;
1018 end;
1019
1020 procedure TfrmLog.tabBottleLogMouseDown(Sender: TObject;
1021   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
1022 var Index: integer;
1023 begin
1024   with tabBottleLog do begin
1025     Index := IndexOfTabAt(X, Y);
1026     if Index = -1 then Exit; //\83^\83u\82ª\82È\82¢\82Ì\82Å\83h\83\89\83b\83O\82Å\82«\82È\82¢
1027     if Button = mbLeft then begin
1028       FDragTabIndex := Index; //\83h\83\89\83b\83O\82·\82é\83^\83u\82Ì\83C\83\93\83f\83b\83N\83X\82ð\95Û\91
1029       BeginDrag(False);
1030       FDragTabDest := -1;     //\83h\83\89\83b\83O\98g\90ü\95`\89æ\83t\83\89\83O\83N\83\8a\83A\82Ì\82½\82ß
1031     end;
1032   end;
1033 end;
1034
1035 procedure TfrmLog.tabBottleLogDragOver(Sender, Source: TObject; X,
1036   Y: Integer; State: TDragState; var Accept: Boolean);
1037 var TargetRect: TRect;
1038     OldDest: integer;
1039 begin
1040   Accept := Source = tabBottleLog;
1041   if not Accept then Exit;
1042   with tabBottleLog do begin
1043     OldDest := FDragTabDest;
1044     FDragTabDest := IndexOfTabAt(X, Y);
1045     if FDragTabDest = -1 then begin
1046       Accept := false; //\82±\82Ì\8fê\8d\87\82Í\83h\83\8d\83b\83v\82ð\94F\82ß\82È\82¢
1047       Exit;
1048     end;
1049     with Canvas do begin
1050       Pen.Mode := pmNot;
1051       Pen.Width := 3;
1052     end;
1053     if (OldDest <> FDragTabDest) and (OldDest >= 0) then begin
1054       //\88È\91O\82Ì\98g\90ü\8fÁ\8b\8e
1055       TargetRect := TabRect(OldDest);
1056       with Canvas do begin
1057         Brush.Style := bsClear;
1058         Rectangle(TargetRect.Left, TargetRect.Top,
1059                   TargetRect.Right, TargetRect.Bottom);
1060       end;
1061     end;
1062     if (OldDest <> FDragTabDest) then begin
1063       //\90V\82µ\82¢\98g\90ü\95`\89æ
1064       TargetRect := TabRect(FDragTabDest);
1065       with Canvas do begin
1066         Brush.Style := bsClear;
1067         Rectangle(TargetRect.Left, TargetRect.Top,
1068                   TargetRect.Right, TargetRect.Bottom);
1069       end;
1070     end;
1071   end;
1072 end;
1073
1074 procedure TfrmLog.tabBottleLogDragDrop(Sender, Source: TObject; X,
1075   Y: Integer);
1076 var DestIndex: integer;
1077 begin
1078   with tabBottleLog do begin
1079     DestIndex := IndexOfTabAt(X, Y);
1080     Tabs.Move(FDragTabIndex, DestIndex);
1081     FBottleLogList.Move(FDragTabIndex, DestIndex);
1082   end;
1083 end;
1084
1085 procedure TfrmLog.tabBottleLogEndDrag(Sender, Target: TObject; X,
1086   Y: Integer);
1087 begin
1088   //\8b­\90§\93I\82É\83^\83u\82ð\8dÄ\95`\89æ\82³\82¹\82é\81B\98g\90ü\8fÁ\82µ\91Î\8dô
1089   tabBottleLog.Tabs.BeginUpdate;
1090   tabBottleLog.Tabs.EndUpdate;
1091 end;
1092
1093 procedure TfrmLog.LogLoadWork(Sender: TObject);
1094 begin
1095   if Sender = SelectedBottleLog then lvwLog.Invalidate;
1096 end;
1097
1098 procedure TfrmLog.lvwLogDrawItem(Sender: TCustomListView; Item: TListItem;
1099   Rect: TRect; State: TOwnerDrawState);
1100 var
1101   DestRect: TRect;
1102   Script: String;
1103   Ico: TIcon;
1104   sub, Ex: integer;
1105   Bottle: TLogItem;
1106   DummyStr: TStringList;
1107 begin
1108   Bottle := SelectedBottleLog.Bottles[Item.Index];
1109   if Bottle.HasURL = huUndefined then
1110   begin
1111     DummyStr := TStringList.Create;
1112     try
1113       ExtractURLs(Bottle.Script, DummyStr);
1114       if DummyStr.Count > 0 then
1115         Bottle.HasURL := huYes
1116       else
1117         Bottle.HasURL := huNo;
1118     finally
1119       DummyStr.Free;
1120     end;
1121   end;
1122
1123   // \94w\8ci\8fÁ\8b\8e
1124   ListView_GetItemRect(lvwLog.Handle, Item.Index, DestRect, LVIR_BOUNDS);
1125
1126   // \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è
1127   lvwLog.Canvas.Brush.Style := bsSolid;
1128   if Item.Selected then begin
1129     if lvwLog.Focused then
1130       lvwLog.Canvas.Brush.Color := clHighlight
1131     else
1132       lvwLog.Canvas.Brush.Color := clBtnFace;
1133   end else begin
1134     lvwLog.Canvas.Brush.Color := Pref.BgColor;
1135   end;
1136   lvwLog.Canvas.FillRect(DestRect);
1137   lvwLog.Canvas.Brush.Style := bsClear;
1138   // \83t\83H\81[\83J\83X\82ª\82 \82é\8fê\8d\87\82É\82Í\83t\83H\81[\83J\83X\82Ì\98g\90ü\82ð\88ø\82­
1139   if Item.Focused and lvwLog.Focused then
1140     lvwLog.Canvas.DrawFocusRect(DestRect);
1141
1142   // \83h\83\89\83b\83O\92\86\82È\82ç\98g\90ü\82ð\95`\89æ\82·\82é
1143   if FLVDragDest = Item.Index then
1144   begin
1145     DestRect := Item.DisplayRect(drBounds);
1146     DrawListViewDragBorder(DestRect);
1147   end;
1148
1149   if Item.Selected then
1150   begin
1151     if lvwLog.Focused then
1152       lvwLog.Canvas.Font.Color := clHighlightText
1153     else
1154       lvwLog.Canvas.Font.Color := clWindowText;
1155   end else
1156     lvwLog.Canvas.Font.Color := Pref.TalkColorH;
1157   lvwLog.Canvas.Refresh;
1158
1159   // \83L\83\83\83v\83V\83\87\83\93(\93ú\95t)
1160   ListView_GetItemRect(lvwLog.Handle, Item.Index, DestRect, LVIR_LABEL);
1161   Inc(DestRect.Left, 2);
1162   Inc(DestRect.Top, 2);
1163   Dec(DestRect.Right, 2);
1164   DrawTextEx(lvwLog.Canvas.Handle, PChar(Item.Caption), -1, DestRect,
1165     DT_SINGLELINE or DT_END_ELLIPSIS, nil);
1166   ListView_GetItemRect(lvwLog.Handle, Item.Index, DestRect, LVIR_ICON);
1167   Ico := TIcon.Create;
1168   try
1169     lvwLog.SmallImages.GetIcon(Item.ImageIndex, Ico);
1170     lvwLog.Canvas.Draw(DestRect.Left, DestRect.Top, Ico);
1171   finally
1172     Ico.Free;
1173   end;
1174   // \83L\83\83\83v\83V\83\87\83\93\82Å\82à\83X\83N\83\8a\83v\83g\82Å\82à\82È\82¢\82à\82Ì
1175   for sub := 0 to Item.SubItems.Count-1 do
1176   begin
1177     if sub = SubScript then Continue;
1178     ListView_GetSubItemRect(lvwLog.Handle, Item.Index, sub + 1,
1179       LVIR_BOUNDS, @DestRect);
1180     Inc(DestRect.Left, 2);
1181     Inc(DestRect.Top, 2);
1182     Dec(DestRect.Right, 2);
1183     Ex := DT_NOPREFIX or DT_SINGLELINE or DT_END_ELLIPSIS;
1184     if lvwLog.Columns[sub+1].Alignment = taRightJustify then
1185       Ex := Ex or DT_RIGHT;
1186     DrawTextEx(lvwLog.Canvas.Handle, PChar(Item.SubItems[sub]), -1, DestRect,
1187       Ex, nil);
1188   end;
1189   // \83X\83N\83\8a\83v\83g
1190   ListView_GetSubItemRect(lvwLog.Handle, Item.Index, SubScript + 1,
1191     LVIR_BOUNDS, @DestRect);
1192   Script := Item.SubItems[SubScript];
1193   DrawSingleLineScript(Bottle, DestRect, Item);
1194
1195 end;
1196
1197 procedure TfrmLog.DrawSingleLineScript(LogItem: TLogItem;
1198   Rect: TRect; Item: TListItem);
1199 var
1200   i, x, w: integer;
1201   UnyuTalking, Synchronized, Spaced: boolean;
1202   Mark: TSsMarkUpType;
1203   Script: String;
1204   Ico: TIcon;
1205   procedure ScopeChange;
1206   begin
1207     if (not Spaced) and (Pref.LogListPreviewStyle = psTagStripped) then
1208     begin
1209       Inc(x, 7);
1210       Spaced := true;
1211     end;
1212   end;
1213 begin
1214   Script := LogItem.Script;
1215   x := 3;
1216
1217   if LogItem.HasURL = huYes then
1218   begin
1219     Ico := TIcon.Create;
1220     try
1221       lvwLog.SmallImages.GetIcon(IconURL, Ico);
1222       lvwLog.Canvas.Draw(Rect.Left + x, Rect.Top, Ico);
1223       Inc(x, 20);
1224     finally
1225       Ico.Free;
1226     end;
1227   end;
1228
1229   if Pref.LogListPreviewStyle = psNoColor then
1230   begin
1231     Inc(Rect.Left, x);
1232     Inc(Rect.Top, 2);
1233     Dec(Rect.Right, 2);
1234     DrawTextEx(lvwLog.Canvas.Handle, PChar(Script), -1, Rect,
1235       DT_SINGLELINE or DT_END_ELLIPSIS or DT_NOPREFIX, nil);
1236     Exit;
1237   end;
1238
1239   SsParser.LeaveEscape := Pref.LogListPreviewStyle = psNormal;
1240   SsParser.InputString := Script;
1241
1242   UnyuTalking := false;
1243   Synchronized := false;
1244   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¢
1245                   // \82½\82ß\82Ì\83t\83\89\83O
1246   for i := 0 to SsParser.Count - 1 do begin
1247     if SsParser[i] = '\h' then
1248     begin
1249       UnyuTalking := false;
1250       ScopeChange;
1251     end else if SsParser[i] = '\u' then
1252     begin
1253       UnyuTalking := true;
1254       ScopeChange;
1255     end else if SsParser[i] = '\_s' then
1256     begin
1257       Synchronized := not Synchronized;
1258       ScopeChange;
1259     end else if (Pos('\n', SsParser[i]) = 1) or (SsParser[i] = '\c') then
1260     begin
1261       ScopeChange;
1262     end;
1263     Mark := SsParser.MarkUpType[i];
1264     case Mark of
1265       mtMeta:
1266         begin
1267           lvwLog.Canvas.Font.Color := Pref.MetaWordColor;
1268           Spaced := false;
1269         end;
1270       mtTag:
1271         if Pref.LogListPreviewStyle = psNormal then
1272           lvwLog.Canvas.Font.Color := Pref.MarkUpColor
1273         else
1274         begin
1275           Continue;
1276         end;
1277       mtTagErr:
1278         lvwLog.Canvas.Font.Color := Pref.MarkErrorColor;
1279       else begin
1280         Spaced := false;
1281         if Synchronized then
1282           lvwLog.Canvas.Font.Color := Pref.TalkColorS
1283         else if UnyuTalking then
1284           lvwLog.Canvas.Font.Color := Pref.TalkColorU
1285         else
1286           lvwLog.Canvas.Font.Color := Pref.TalkColorH;
1287       end;
1288     end;
1289     if Item.Selected then
1290     begin
1291       if lvwLog.Focused then
1292         lvwLog.Canvas.Font.Color := clHighlightText
1293       else
1294         lvwLog.Canvas.Font.Color := clWindowText;
1295     end;
1296     lvwLog.Canvas.Refresh;
1297     w := lvwLog.Canvas.TextWidth(SsParser[i]);
1298     lvwLog.Canvas.TextRect(Rect, Rect.Left + x, Rect.Top + 2, SsParser[i]);
1299     x := x + w;
1300     if Rect.Right - Rect.Left < x then Break;
1301   end;
1302 end;
1303
1304 procedure TfrmLog.mnListPreviewStyleClick(Sender: TObject);
1305 var i: integer;
1306 begin
1307   with PopupMenuListPreviewStyle do
1308     for i := 0 to Items.Count-1 do
1309       Items[i].Checked := (Sender as TMenuItem).Tag = Items[i].Tag;
1310   Pref.LogListPreviewStyle := TLogListPreviewStyle((Sender as TMenuItem).Tag);
1311   lvwLog.Invalidate;
1312 end;
1313
1314 procedure TfrmLog.tbtnListPreviewStyleClick(Sender: TObject);
1315 var sel: integer;
1316 begin
1317   sel := Ord(Pref.LogListPreviewStyle);
1318   sel := sel + 1;
1319   if sel > Ord(High(TLogListPreviewStyle)) then sel := 0;
1320   Pref.LogListPreviewStyle := TLogListPreviewStyle(sel);
1321   lvwLog.Invalidate;
1322 end;
1323
1324 procedure TfrmLog.PopupMenuListPreviewStylePopup(Sender: TObject);
1325 var i: integer;
1326 begin
1327   with PopupMenuListPreviewStyle do
1328     for i := 0 to Items.Count-1 do
1329       Items[i].Checked := Items[i].Tag = Ord(Pref.LogListPreviewStyle)
1330 end;
1331
1332 procedure TfrmLog.PreviewStyleChange;
1333 begin
1334   if Pref.LogWindowPreviewStyle = psImageConversation then
1335   begin
1336     if Spps.Count = 0 then
1337       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');
1338     edtScript.Visible := false;
1339     TalkShowFrame.Visible := true;
1340   end else
1341   begin
1342     edtScript.Visible := true;
1343     TalkShowFrame.Visible := false;
1344   end;
1345 end;
1346
1347 procedure TfrmLog.lvwLogDragOver(Sender, Source: TObject; X, Y: Integer;
1348   State: TDragState; var Accept: Boolean);
1349 var
1350   Target: TListItem;
1351   OldDest: integer;
1352   Rec: TRect; // \83_\83~\81[\81B
1353 begin
1354   Accept := False;
1355   if not (Source is TBottleLogDragObject) then
1356     Exit;
1357
1358   Target := lvwLog.GetItemAt(X, Y);
1359
1360   // \82±\82ê\88È\91O\82É\95`\89æ\82³\82ê\82Ä\82¢\82½\98g\82Ì\83C\83\93\83f\83b\83N\83X
1361   OldDest := FLVDragDest;
1362
1363   // \83h\83\8d\83b\83v\88Ê\92u\82É Item \82ª\82 \82ê\82Î\83h\83\8d\83b\83v\82ð\8b\96\89Â\82·\82é
1364   if Target <> nil then
1365   begin
1366     Accept := True;
1367     FLVDragDest := Target.Index;
1368   end else
1369   begin
1370     FLVDragDest := -1;
1371   end;
1372
1373   // \88È\91O\82Ì\98g\90ü\82ð\8dí\8f\9c
1374   if (OldDest > -1) and (FLVDragDest <> OldDest) then
1375   begin
1376     Rec := lvwLog.Items[OldDest].DisplayRect(drBounds);
1377     DrawListViewDragBorder(Rec);
1378   end;
1379   // \83h\83\89\83b\83O\90æ\82Ì\98g\90ü\82ð\95`\89æ
1380   if (Target <> nil) and (FLVDragDest <> OldDest) then
1381   begin
1382     Rec := Target.DisplayRect(drBounds);
1383     DrawListViewDragBorder(Rec);
1384   end;
1385
1386   if (lvwLog.topItem <> nil) and (Y - lvwLog.TopItem.Top < 10) then
1387   begin
1388     FLVScrollDir := lvScrollDown;
1389     if not timScrollTimer.Enabled then
1390       timScrollTimer.Enabled := true;
1391   end else if (lvwLog.Height - Y) < 10 then
1392   begin
1393     FLVScrollDir := lvScrollUp;
1394     if not timScrollTimer.Enabled then
1395       timScrollTimer.Enabled := true;
1396   end
1397   else
1398     timScrollTimer.Enabled := false;
1399 end;
1400
1401 procedure TfrmLog.lvwLogDragDrop(Sender, Source: TObject; X, Y: Integer);
1402 var
1403   TargetItem: integer;
1404 begin
1405   timScrollTimer.Enabled := false;
1406   TargetItem := lvwLog.GetItemAt(X, Y).Index;
1407
1408   lvwLog.Items.BeginUpdate; // \83h\83\8d\83b\83v\92\86\82Í\95\\8e¦\82ð\97}\8e~\82·\82é\81@\8fd\97v\81I
1409   try
1410     // \83h\83\8d\83b\83v\88Ê\92u\82É Item \82ð\88Ú\93®\82·\82é
1411     SelectedBottleLog.Move(lvwLog.Selected.Index, TargetItem);
1412     lvwLog.Items[TargetItem].Selected := true;
1413     lvwLog.Items[TargetItem].Focused := true;
1414     FLVDragDest := -1; // \83h\83\89\83b\83O\98g\82Ì\8fÁ\8b\8e
1415   finally
1416     lvwLog.Items.EndUpdate;
1417   end;
1418 end;
1419
1420 procedure TfrmLog.timScrollTimerTimer(Sender: TObject);
1421 var
1422   ScrollHeight: Integer;
1423 begin
1424   // \83X\83N\83\8d\81[\83\8b\97Ê\82ð\8b\81\82ß\82é
1425   ScrollHeight := 0;
1426   if lvwLog.Items.Count > 2 then
1427   begin
1428     ScrollHeight := lvwLog.Items[1].Top - lvwLog.Items[0].Top;
1429   end;
1430
1431   case FLVScrollDir of
1432     lvScrollUp: lvwLog.Scroll(0, ScrollHeight);
1433     lvSCrollDown: lvwLog.Scroll(0, -ScrollHeight);
1434   end;
1435   lvwLog.Invalidate;  // \8dÅ\90V\82Ì\8fó\91Ô\82É\8dÄ\95`\89æ\82·\82é
1436   lvwLog.Update;
1437 end;
1438
1439 procedure TfrmLog.mnChangeTabNameClick(Sender: TObject);
1440 var Name: String;
1441 begin
1442   Name := (FBottleLogList[tabBottleLog.Tag] as TBottleLogList).Title;
1443   InputQuery('\96¼\91O\82Ì\95Ï\8dX', '\90V\82µ\82¢\83^\83u\82Ì\96¼\91O', Name);
1444   (FBottleLogList[tabBottleLog.Tag] as TBottleLogList).Title := Name;
1445   UpdateTab;
1446 end;
1447
1448 procedure TfrmLog.lvwLogStartDrag(Sender: TObject;
1449   var DragObject: TDragObject);
1450 begin
1451   // \92Ê\8fí\82ÌListView\97p\82Ì\83h\83\89\83b\83O\83I\83u\83W\83F\83N\83g\82Í
1452   // 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
1453   // \82±\82ê\82Í\82¢\82ë\82¢\82ë\82Æ\8e×\96\82\82È\82Ì\82Å\8aO\82·\82½\82ß\82É\81A\89½\82Ì\95Ï\93N\82à\82È\82¢
1454   // 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¤\81B
1455   DragObject := TBottleLogDragObject.Create;
1456 end;
1457
1458 procedure TfrmLog.DrawListViewDragBorder(const Rect: TRect);
1459 var Rec: TRect;
1460 begin
1461   Rec := Rect;
1462   InflateRect(Rec, -1, -1);
1463   with lvwLog.Canvas do
1464   begin
1465     Pen.Mode := pmNot;
1466     Pen.Width := 3;
1467     Brush.Style := bsClear;
1468     Refresh; // \95K\97v
1469     Rectangle(Rec);
1470   end;
1471 end;
1472
1473 end.