OSDN Git Service

actInsertCue is disabled when all tabs are closed
[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, xmldom, XMLIntf,
9   msxmldom, XMLDoc, StrUtils;
10
11 type
12   TSaveLogType = (stLog, stLogWithChannels, stText, stXML);
13
14   TfrmLog = class(TForm)
15     ToolBar: TToolBar;
16     tbtnClear: TToolButton;
17     pnlUpper: TPanel;
18     SsParser: TSsParser;
19     StatusBar: TStatusBar;
20     tbtnSaveLog: TToolButton;
21     PopupMenuPreview: TPopupMenu;
22     mnPopCopy: TMenuItem;
23     tbtnVoteMessage: TToolButton;
24     PopupMenuListView: TPopupMenu;
25     mnPopUpVoteMessage: TMenuItem;
26     SaveDialog: TSaveDialog;
27     pnlPanel: TPanel;
28     Splitter: TSplitter;
29     edtScript: TRichEdit;
30     mnPopUpCopyScript: TMenuItem;
31     PopupMenuSaveLog: TPopupMenu;
32     mnSaveLog: TMenuItem;
33     mnSaveLogChannel: TMenuItem;
34     mnSaveLogScript: TMenuItem;
35     mnSaveLogXML: TMenuItem;
36     ToolButton1: TToolButton;
37     mnJumpURL: TMenuItem;
38     mnPopUpAgreeMessage: TMenuItem;
39     tbtnAgreeMessage: TToolButton;
40     ToolButton2: TToolButton;
41     tbtnPreviewStyle: TToolButton;
42     PopupMenuPreviewStyle: TPopupMenu;
43     mnPreviewStyleConversation: TMenuItem;
44     mnPreviewStyleScript: TMenuItem;
45     mnPreviewStyleScriptWithLineBreak: TMenuItem;
46     Panel1: TPanel;
47     tabBottleLog: TTabControl;
48     lvwLog: TListView;
49     tbtnDownloadLog: TToolButton;
50     PopupMenuTab: TPopupMenu;
51     mnCloseTab: TMenuItem;
52     tbtnFindBottle: TToolButton;
53     XMLDocument: TXMLDocument;
54     tbtnOpenLog: TToolButton;
55     OpenDialog: TOpenDialog;
56     tbtnInsertCue: TToolButton;
57     mnInsertCue: TMenuItem;
58     procedure tbtnClearClick(Sender: TObject);
59     procedure FormCreate(Sender: TObject);
60     procedure lvwLogChange(Sender: TObject; Item: TListItem;
61       Change: TItemChange);
62     procedure lvwLogDblClick(Sender: TObject);
63     procedure lvwLogKeyPress(Sender: TObject; var Key: Char);
64     procedure FormDestroy(Sender: TObject);
65     procedure lvwLogClick(Sender: TObject);
66     procedure mnSaveLogClick(Sender: TObject);
67     procedure lvwLogColumnClick(Sender: TObject; Column: TListColumn);
68     procedure mnPopUpCopyScriptClick(Sender: TObject);
69     procedure mnSaveLogChannelClick(Sender: TObject);
70     procedure mnSaveLogScriptClick(Sender: TObject);
71     procedure mnSaveLogXMLClick(Sender: TObject);
72     procedure lvwLogData(Sender: TObject; Item: TListItem);
73     procedure PopupMenuListViewPopup(Sender: TObject);
74     procedure lvwLogCustomDrawItem(Sender: TCustomListView;
75       Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
76     procedure lvwLogCustomDrawSubItem(Sender: TCustomListView;
77       Item: TListItem; SubItem: Integer; State: TCustomDrawState;
78       var DefaultDraw: Boolean);
79     procedure PopupMenuPreviewStylePopup(Sender: TObject);
80     procedure mnPreviewStyleClick(Sender: TObject);
81     procedure tbtnPreviewStyleClick(Sender: TObject);
82     procedure tabBottleLogChange(Sender: TObject);
83     procedure tabBottleLogChanging(Sender: TObject;
84       var AllowChange: Boolean);
85     procedure tabBottleLogContextPopup(Sender: TObject; MousePos: TPoint;
86       var Handled: Boolean);
87     procedure mnCloseTabClick(Sender: TObject);
88     procedure tbtnFindBottleClick(Sender: TObject);
89     procedure tbtnOpenLogClick(Sender: TObject);
90     procedure tabBottleLogMouseDown(Sender: TObject; Button: TMouseButton;
91       Shift: TShiftState; X, Y: Integer);
92     procedure tabBottleLogDragOver(Sender, Source: TObject; X, Y: Integer;
93       State: TDragState; var Accept: Boolean);
94     procedure tabBottleLogDragDrop(Sender, Source: TObject; X, Y: Integer);
95     procedure tabBottleLogEndDrag(Sender, Target: TObject; X, Y: Integer);
96   private
97     { Private \90é\8c¾ }
98     FLastScript: String; //\83X\83N\83\8a\83v\83g\8dÄ\95`\89æ\97}\90§\97p
99     FBottleLogList: TObjectList;
100     //
101     FDragTabIndex: integer; //\83^\83u\83h\83\89\83b\83O\83h\83\8d\83b\83v\8aÖ\98A
102     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)
103     procedure UpdateScript(const Script: String);
104     procedure UpdateScriptConversationColor(const Script: String);
105     procedure UpdateScriptConversationNoColor(const Script: String);
106     procedure UpdateScriptScript(const Script: String);
107     procedure mnURLClick(Sender: TObject);
108     procedure ExtractURLs(Script: String; Result: TStrings);
109     function GetDefaultFileName(const Name: String; const Ext: String): String;
110     function BottleLogTitled(const LogName: String): TBottleLogList;
111   protected
112     procedure CreateParams(var Params: TCreateParams); override;
113   public
114     { Public \90é\8c¾ }
115     function SelectedBottleLog: TBottleLogList;
116     property BottleLogList: TObjectList read FBottleLogList;
117     procedure AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String);
118     procedure AddCurrentSystemLog(const LogName, MessageString: String);
119     procedure VoteLog(const MID: String; const Vote: integer);
120     procedure AgreeLog(const MID: String; const Agree: integer);
121     procedure SetBottleState(const MID: String; State: TLogState);
122     procedure AllBottleOpened;
123     procedure LogLoaded(Sender: TObject);
124     procedure LogLoadFailure(Sender: TObject; const Message: String);
125     procedure LogLoadWork(Sender: TObject);
126     procedure UpdateTab;
127     procedure UpdateWindow;
128     procedure SelAndFocusMessage(const MID: String);
129   end;
130
131
132 var
133   frmLog: TfrmLog;
134
135 const
136   IconBottle    = 17;
137   IconOpened    = 30;
138   IconPlaying   = 31;
139   IconSystemLog = 26;
140   SubChannel    = 0;
141   SubGhost      = 1;
142   SubVotes      = 2;
143   SubAgrees     = 3;
144   SubScript     = 4;
145
146 implementation
147
148 uses MainForm;
149
150 {$R *.DFM}
151
152 { TfrmLog }
153
154 procedure TfrmLog.AddCurrentScriptLog(const LogName, Script, Channel, MID, Ghost: String);
155 var Sel: integer;
156 begin
157   BottleLogTitled(LogName).AddScriptLog(Script, Channel, MID, Ghost);
158   if SelectedBottleLog <> BottleLogTitled(LogName) then Exit;
159   lvwLog.OnChange := nil; //\83C\83x\83\93\83g\94­\90¶(\82¢\82ë\82¢\82ë\8dÄ\95`\89æ\82ª\8bN\82«\82é)\82Ì\97}\90§
160   if lvwLog.Selected <> nil then Sel := lvwLog.Selected.Index else Sel := -1;
161   lvwLog.Items.Count := SelectedBottleLog.Count;
162   UpdateWindow;
163   if Sel >= 0 then begin
164     lvwLog.Selected := lvwLog.Items[Sel + 1];
165     lvwLog.Selected.Focused := true;
166   end;
167   if not lvwLog.Focused then
168     ListView_Scroll(lvwLog.Handle, 0, High(integer));
169   lvwLog.OnChange := lvwLogChange;
170 end;
171
172 procedure TfrmLog.AddCurrentSystemLog(const LogName, MessageString: String);
173 var Sel: integer;
174 begin
175   BottleLogTitled(LogName).AddSystemLog(MessageString);
176   if SelectedBottleLog <> BottleLogTitled(LogName) then Exit;
177   lvwLog.OnChange := nil;
178   if lvwLog.Selected <> nil then Sel := lvwLog.Selected.Index else Sel := -1;
179   lvwLog.Items.Count := SelectedBottleLog.Count;
180   UpdateWindow;
181   if Sel >= 0 then begin
182     lvwLog.Selected := lvwLog.Items[Sel + 1];
183     lvwLog.Selected.Focused := true;
184   end;
185   if not lvwLog.Focused then
186     ListView_Scroll(lvwLog.Handle, 0, High(integer));
187   lvwLog.OnChange := lvwLogChange;
188 end;
189
190
191
192 procedure TfrmLog.tbtnClearClick(Sender: TObject);
193 begin
194   if SelectedBottleLog = nil then Exit;
195   FBottleLogList.Delete(tabBottleLog.TabIndex);
196   tabBottleLog.TabIndex := 0;
197   UpdateTab;
198   UpdateWindow;
199   lvwLogChange(Self, nil, ctState);
200 end;
201
202 procedure TfrmLog.FormCreate(Sender: TObject);
203 var i: integer;
204 begin
205   FBottleLogList := TObjectList.Create;
206
207   SsParser.TagPattern.Assign(frmSender.SsParser.TagPattern);
208   SsParser.MetaPattern.Assign(frmSender.SsParser.MetaPattern);
209
210   with Pref.LogWindowPosition do begin
211     Self.Left   := Left;
212     Self.Top    := Top;
213     Self.Width  := Right - Left + 1;
214     Self.Height := Bottom - Top + 1;
215   end;
216   lvwLog.DoubleBuffered := true;
217   edtScript.Height := Pref.LogWindowDividerPos;
218
219   i := 0;
220   while Token(Pref.LogWindowColumnWidth, ',', i) <> '' do begin
221     lvwLog.Columns[i].Width := StrToIntDef(Token(Pref.LogWindowColumnWidth, ',', i), 100);
222     Inc(i);
223   end;
224
225   UpdateWindow; // Reset window color and enabled status of some buttons
226 end;
227
228 procedure TfrmLog.FormDestroy(Sender: TObject);
229 var i: integer;
230     WidthStr: String;
231 begin
232   WidthStr := '';
233   for i := 0 to lvwLog.Columns.Count-1 do begin
234     if i > 0 then WidthStr := WidthStr + ',';
235     WidthStr := WidthStr + IntToStr(lvwLog.Column[i].Width);
236   end;
237   Pref.LogWindowColumnWidth := WidthStr;
238
239   with Pref.LogWindowPosition do begin
240     Left   := Self.Left;
241     Top    := Self.Top;
242     Right  := Self.Left + Self.Width - 1;
243     Bottom := Self.Top + Self.Height - 1;
244   end;
245   Pref.LogWindowDividerPos := edtScript.Height;
246
247   FreeAndNil(FBottleLogList);
248 end;
249
250 procedure TfrmLog.lvwLogChange(Sender: TObject; Item: TListItem;
251   Change: TItemChange);
252 var Script: String;
253     Log: TLogItem;
254 begin
255   if SelectedBottleLog <> nil then begin
256     StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
257     if Change = ctState then begin
258       Script := '';
259       if lvwLog.Selected <> nil then begin
260         Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
261         if (Log.LogType = ltBottle) and not frmSender.Connecting then begin
262           Script := Log.Script;
263           frmSender.actVoteMessage.Enabled := true;
264           frmSender.actAgreeMessage.Enabled := true;
265           frmSender.actInsertCue.Enabled := true;
266           mnPopUpCopyScript.Enabled := true;
267           StatusBar.Panels[1].Text := Format('%d\83o\83C\83g - \83_\83u\83\8b\83N\83\8a\83b\83N\82Å\8dÄ\90¶', [Length(Log.Script)]);
268           UpdateScript(Script);
269         end else begin
270           frmSender.actVoteMessage.Enabled := false;
271           frmSender.actAgreeMessage.Enabled := false;
272           frmSender.actInsertCue.Enabled := false;
273           mnPopUpCopyScript.Enabled := false;
274           StatusBar.Panels[1].Text := '';
275           UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\82ð\83N\83\8a\83A
276         end;
277       end else begin
278         frmSender.actVoteMessage.Enabled := false;
279         frmSender.actAgreeMessage.Enabled := false;
280         frmSender.actInsertCue.Enabled := false;
281         mnPopUpCopyScript.Enabled := false;
282         StatusBar.Panels[1].Text := '';
283         UpdateScript(Script); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
284       end;
285     end;
286     tbtnSaveLog.Enabled := lvwLog.Items.Count > 0;
287   end else begin
288     frmSender.actVoteMessage.Enabled := false;
289     frmSender.actAgreeMessage.Enabled := false;
290     frmSender.actInsertCue.Enabled := false;
291     mnPopUpCopyScript.Enabled := false;
292     StatusBar.Panels[0].Text := '';
293     UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
294   end;
295 end;
296
297 procedure TfrmLog.lvwLogDblClick(Sender: TObject);
298 var Script: String;
299     SOpt: TSstpSendOptions;
300     Ghost: String;
301     Log: TLogItem;
302 begin
303   if lvwLog.Selected = nil then Exit;
304   //Log := TLogItem(lvwLog.Selected.Data);
305   Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
306   if Log = nil then Exit;
307   if Log.LogType <> ltBottle then Exit;
308   Script := frmSender.ScriptTransForSSTP(Log.Script);
309   if Script = '' then begin
310     ShowMessage('\96â\91è\82Ì\82 \82é\83X\83N\83\8a\83v\83g\82Å\82·\81B\8dÄ\90\82Å\82«\82Ü\82¹\82ñ\81B\83N\83\89\83C\83A\83\93\83g\82ð\8dÅ\90V\94Å\82É\82µ\82Ä\82Ý\82Ä\82­\82¾\82³\82¢\81B');
311     Exit;
312   end;
313
314   if ChannelList.Channel[Log.Channel] <> nil then
315     Ghost := ChannelList.Channel[Log.Channel].Ghost;
316   //\96Ú\95W\83S\81[\83X\83g\8c\88\92è
317   if Log.Ghost <> '' then Ghost := Log.Ghost;
318   //\83^\81[\83Q\83b\83g\83S\81[\83X\83g\8am\92è
319   Ghost := frmSender.SetHWndToFavoriteGhost(Ghost);
320   frmSender.DirectSstp.SstpSender := 'SSTP Bottle -\81y\83\8d\83O\8dÄ\90\81z';
321   if Pref.NoTranslate then SOpt := [soNoTranslate] else SOpt := [];
322   frmSender.DirectSstp.SstpSEND(Script, SOpt, frmSender.GhostNameToSetName(Ghost));
323 end;
324
325 procedure TfrmLog.UpdateScriptConversationColor(const Script: String);
326 var i: integer;
327     scr: String;
328     UnyuTalking, Talked, InSynchronized: boolean;
329 begin
330   scr := Script;
331   frmSender.DoTrans(scr, [toConvertURL]);
332   SsParser.LeaveEscape := false;
333   SsParser.InputString := scr;
334   SsParser.LeaveEscape := true;
335   UnyuTalking := false;
336   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
337   InSynchronized := false;
338   edtScript.Text := '';
339   edtScript.Color := Pref.BgColor;
340   for i := 0 to SsParser.Count-1 do begin
341     if (SsParser[i] = '\_s') and not InSynchronized then begin
342       InSynchronized := true;
343       if Talked then begin
344         edtScript.SelText := #13#10;
345         Talked := false;
346       end;
347     end else if (SsParser[i] = '\_s') and InSynchronized then begin
348       InSynchronized := false;
349       if Talked then begin
350         edtScript.SelText := #13#10;
351         Talked := false;
352       end;
353     end;
354     if (SsParser[i] = '\u') and not UnyuTalking then begin
355       UnyuTalking := true;
356       if Talked then begin
357         edtScript.SelText := #13#10;
358         Talked := false;
359       end;
360     end;
361     if (SsParser[i] = '\h') and UnyuTalking then begin
362       UnyuTalking := false;
363       if Talked then begin
364         edtScript.SelText := #13#10;
365         Talked := false;
366       end;
367     end;
368     if SsParser.MarkUpType[i] = mtStr then begin
369       if InSynchronized then
370         edtScript.SelAttributes.Color := Pref.TalkColorS
371       else if UnyuTalking then
372         edtScript.SelAttributes.Color := Pref.TalkColorU
373       else
374         edtScript.SelAttributes.Color := Pref.TalkColorH;
375       edtScript.SelText := SsParser[i];
376       Talked := true;
377     end;
378     if SsParser.MarkUpType[i] = mtMeta then begin
379       edtScript.SelAttributes.Color := Pref.MetaWordColor;
380       edtScript.SelText := SsParser[i];
381       Talked := true;
382     end;
383   end;
384 end;
385
386 procedure TfrmLog.UpdateScriptConversationNoColor(const Script: String);
387 var Scr: String;
388     i: integer;
389     UnyuTalking, Talked, LastUnyuTalked, InSynchronize, LastInSynchronize: boolean;
390 begin
391   Scr := Script;
392   frmSender.DoTrans(Scr, [toConvertURL]);
393   SsParser.LeaveEscape := false;
394   SsParser.InputString := Scr;
395   SsParser.LeaveEscape := true;
396   edtScript.Text := '';
397   edtScript.Color := clWindow;
398   edtScript.DefAttributes.Color := clWindowText;
399   edtScript.SelAttributes.Color := clWindowText;
400   Talked := false;
401   UnyuTalking := false;
402   LastUnyuTalked := false;
403   InSynchronize := false;
404   LastInSynchronize := false;
405   for i := 0 to SsParser.Count-1 do begin
406     if SsParser[i] = '\u' then UnyuTalking := true;
407     if SsParser[i] = '\h' then UnyuTalking := false;
408     if SsParser[i] = '\_s' then InSynchronize := not InSynchronize;
409     if SsParser.MarkUpType[i] in [mtStr, mtMeta] then begin
410       if not Talked then begin // \83X\83N\83\8a\83v\83g\8dÅ\8f\89\82Ì\83Z\83\8a\83t
411         if InSynchronize then Scr := '\97¼\81F'
412         else if UnyuTalking then Scr := '\82¤:' else Scr := '\82³:';
413       end;
414       if Talked and ((UnyuTalking <> LastUnyuTalked) or (InSynchronize <> LastInSynchronize)) then begin
415         Scr := Scr + #13#10;
416         if InSynchronize then Scr := Scr + '\97¼\81F'
417         else if UnyuTalking then Scr := Scr + '\82¤:' else Scr := Scr + '\82³:';
418       end;
419       Scr := Scr + SsParser[i];
420       Talked := true;
421       LastInSynchronize := InSynchronize;
422       LastUnyuTalked := UnyuTalking;
423     end;
424   end;
425   edtScript.Text := Scr;
426 end;
427
428 procedure TfrmLog.lvwLogKeyPress(Sender: TObject; var Key: Char);
429 begin
430   if Key = #13 then lvwLogDblClick(Sender);
431 end;
432
433 procedure TfrmLog.CreateParams(var Params: TCreateParams);
434 begin
435   inherited;
436   Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
437 end;
438
439 procedure TfrmLog.lvwLogClick(Sender: TObject);
440 begin
441   //\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ô
442   with lvwLog do
443     Selected := Selected;
444 end;
445
446 procedure TfrmLog.lvwLogColumnClick(Sender: TObject; Column: TListColumn);
447 var SortType: TBottleLogSortType;
448     SelectedMID: String;
449     SortColumn: integer;
450 begin
451   if lvwLog.Selected <> nil then
452     SelectedMID := SelectedBottleLog.Bottles[lvwLog.Selected.Index].MID;
453
454   SortColumn := Column.Index;
455   case SortColumn-1 of
456     -1: SortType := stLogTime;
457     subChannel: SortType := stChannel;
458     subGhost:   SortType := stGhost;
459     subVotes:   SortType := stVote;
460     subAgrees:  SortType := stAgree;
461     subScript:  SortType := stScript;
462   else SortType := stLogTime;
463   end;
464
465   SelectedBottleLog.SortBottles(SortType);
466   lvwLog.Invalidate;
467   SelAndFocusMessage(SelectedMID);
468 end;
469
470
471 procedure TfrmLog.mnPopUpCopyScriptClick(Sender: TObject);
472 var
473   Log: TLogItem;
474   Clip: TClipBoard;
475 begin
476   Log := SelectedBottleLog.Bottles[frmLog.lvwLog.Selected.Index];
477   if Log = nil then Exit;
478   Clip := ClipBoard();
479   Clip.SetTextBuf(PChar(Log.Script));
480 end;
481
482 procedure TfrmLog.SetBottleState(const MID: String; State: TLogState);
483 var i: integer;
484     Bottle: TLogItem;
485 begin
486   for i := 0 to FBottleLogList.Count-1 do begin
487     Bottle := (FBottleLogList[i] as TBottleLogList).Bottle(MID);
488     if Bottle <> nil then begin
489       Bottle.State := State;
490       lvwLog.OnChange := nil;
491       lvwLog.Invalidate;
492       lvwLog.OnChange := lvwLogChange;
493     end;
494   end;
495 end;
496
497 procedure TfrmLog.mnSaveLogClick(Sender: TObject);
498 begin
499   if SelectedBottleLog = nil then Exit;
500   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
501   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
502   SaveDialog.DefaultExt := 'log';
503   SaveDialog.FilterIndex := 1;
504   if SaveDialog.Execute then
505     SelectedBottleLog.SaveToSstpLog(SaveDialog.FileName, false);
506 end;
507
508 procedure TfrmLog.mnSaveLogChannelClick(Sender: TObject);
509 begin
510   if SelectedBottleLog = nil then Exit;
511   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
512   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
513   SaveDialog.DefaultExt := 'log';
514   SaveDialog.FilterIndex := 1;
515   if SaveDialog.Execute then
516     SelectedBottleLog.SaveToSstpLog(SaveDialog.FileName, true);
517 end;
518
519 procedure TfrmLog.mnSaveLogScriptClick(Sender: TObject);
520 begin
521   if SelectedBottleLog = nil then Exit;
522   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.txt');
523   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
524   SaveDialog.DefaultExt := 'txt';
525   SaveDialog.FilterIndex := 2;
526   if SaveDialog.Execute then
527     SelectedBottleLog.SaveToText(SaveDialog.FileName);
528 end;
529
530 procedure TfrmLog.mnSaveLogXMLClick(Sender: TObject);
531 begin
532   if SelectedBottleLog = nil then Exit;
533   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.xml');
534   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
535   SaveDialog.DefaultExt := 'xml';
536   SaveDialog.FilterIndex := 3;
537   if SaveDialog.Execute then
538     SelectedBottleLog.SaveToXmlFile(SaveDialog.FileName, XMLDocument);
539 end;
540
541 procedure TfrmLog.lvwLogData(Sender: TObject; Item: TListItem);
542 var i: integer;
543     Log: TLogItem;
544 begin
545   if Item = nil then Exit;
546   i := Item.Index;
547   Log := SelectedBottleLog.Bottles[i];
548   with Item do begin
549     Caption := FormatDateTime('yy/mm/dd hh:nn:ss', Log.LogTime);
550     SubItems.Clear;
551     SubItems.Add(Log.Channel);
552     SubItems.Add(Log.Ghost);
553     if Log.LogType = ltBottle then begin
554       if Log.Votes > 0 then
555         SubItems.Add(IntToStr(Log.Votes))
556       else
557         SubItems.Add('');
558       if Log.Agrees > 0 then
559         SubItems.Add(IntToStr(Log.Agrees))
560       else
561         SubItems.Add('');
562     end else begin
563       // \83V\83X\83e\83\80\83\8d\83O\82È\82Ç\82Í\93\8a\95[\81E\93¯\88Ó\82ð\95\\8e¦\82µ\82È\82¢
564       SubItems.Add('');
565       SubItems.Add('');
566     end;
567     SubItems.Add(Log.Script);
568
569     if Log.LogType = ltBottle then begin
570       case Log.State of
571         lsUnopened: ImageIndex := IconBottle;
572         lsPlaying:  ImageIndex := IconPlaying;
573         lsOpened:   ImageIndex := IconOpened;
574       end;
575     end else
576       ImageIndex := IconSystemLog;
577   end;
578 end;
579
580 procedure TfrmLog.UpdateWindow;
581 var EnabledFlag: boolean;
582 begin
583   if Pref.ColorScript then begin
584     if lvwLog.Color <> Pref.BgColor then lvwLog.Color := Pref.BgColor;
585     if lvwLog.Font.Color <> Pref.TalkColorH then lvwLog.Font.Color := Pref.TalkColorH;
586   end else begin
587     if lvwLog.Color <> clWindow then lvwLog.Color := clWindow;
588     if lvwLog.Font.Color <> clWindowText then lvwLog.Font.Color := clWindowText;
589   end;
590   if SelectedBottleLog <> nil then begin
591     Caption := '\83\8d\83O - ' + SelectedBottleLog.Title;
592     StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
593     lvwLog.Items.Count := SelectedBottleLog.Count;
594   end else begin
595     Caption := '\83\8d\83O';
596     StatusBar.Panels[0].Text := '';
597     StatusBar.Panels[1].Text := '';
598     lvwLog.Items.Count := 0;
599   end;
600
601   EnabledFlag := SelectedBottleLog <> nil;
602   tbtnClear.Enabled := EnabledFlag;
603   tbtnSaveLog.Enabled := EnabledFlag;
604   tbtnFindBottle.Enabled := EnabledFlag;
605
606   lvwLog.Invalidate;
607 end;
608
609 procedure TfrmLog.PopupMenuListViewPopup(Sender: TObject);
610 var Log: TLogItem;
611     Child: TMenuItem;
612     Urls: TStringList;
613     i: integer;
614 begin
615   for i := mnJumpURL.Count-1 downto 0 do begin
616     mnJumpURL.Items[i].Free;
617   end;
618   mnJumpURL.Enabled := false;
619   if lvwLog.Selected = nil then Exit;
620   Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
621   if Log = nil then Exit;
622   Urls := TStringList.Create;
623   try
624     ExtractURLs(Log.Script, Urls);
625     for i := 0 to Urls.Count-1 do begin
626       Child := TMenuItem.Create(Self);
627       with Child do begin
628         Caption := Format('(&%d) %s', [i+1, StringReplace(Urls[i], '&', '&&', [rfReplaceAll])]);
629         Tag := i;
630         OnClick := mnURLClick;
631         AutoHotkeys := maManual;
632         mnJumpURL.Add(Child);
633       end;
634     end;
635     mnJumpURL.Enabled := Urls.Count > 0;
636   finally
637     Urls.Free;
638   end;
639 end;
640
641 procedure TfrmLog.mnURLClick(Sender: TObject);
642 var LogItem: TLogItem;
643     URL: String;
644     Urls: TStringList;
645 begin
646   if (lvwLog.Selected = nil) or (SelectedBottleLog = nil) then Exit;
647   LogItem := SelectedBottleLog[lvwLog.Selected.Index] as TLogItem;
648   Urls := TStringList.Create;
649   try
650     ExtractURLs(LogItem.Script, Urls);
651     URL := Urls[(Sender as TMenuItem).Tag];
652     ShellExecute(Handle, 'open', PChar(URL), nil, nil, SW_SHOW);
653   finally
654     Urls.Free;
655   end;
656 end;
657
658 procedure TfrmLog.ExtractURLs(Script: String; Result: TStrings);
659 var i, u, j: integer;
660     s: String;
661 begin
662   Result.Clear;
663   SsParser.InputString := Script;
664   for i := 0 to SsParser.Count-1 do begin
665     if (SsParser.Match(SsParser[i], '\URL%b') > 0) then begin
666       for u := 7 downto 1 do begin
667         if (SsParser.Match(SsParser[i],
668             '\URL%b'+StringReplace(StringOfChar('-', u*2),
669             '-', '%b', [rfReplaceAll]))) > 0 then begin
670           for j := 1 to u do begin
671             s := SsParser.GetParam(SsParser[i], j*2);
672             if Pos('http://', s) > 0 then Result.Add(s);
673           end;
674           Break;
675         end;
676       end;
677       if SsParser.Match(SsParser[i], '\URL%b%b') = 0 then begin //\8aÈ\88Õ\94ÅURL\95Ï\8a·
678         //\8aÈ\88Õ\8c`\8e®\URL\83^\83O\95Ï\8a·
679         s := SsParser.GetParam(SsParser[i], 1);
680         if Pos('http://', s) > 0 then Result.Add(s);
681       end;
682     end;
683   end;
684 end;
685
686 procedure TfrmLog.SelAndFocusMessage(const MID: String);
687 var i: integer;
688     Log: TLogItem;
689 begin
690   for i := 0 to SelectedBottleLog.Count-1 do begin
691     Log := SelectedBottleLog.Items[i] as TLogItem;
692     if Log.MID = MID then begin
693       lvwLog.Items[i].Selected := true;
694       lvwLog.Items[i].Focused := true;
695     end;
696   end;
697 end;
698
699 procedure TfrmLog.lvwLogCustomDrawItem(Sender: TCustomListView;
700   Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
701 begin
702   //
703 end;
704
705 procedure TfrmLog.lvwLogCustomDrawSubItem(Sender: TCustomListView;
706   Item: TListItem; SubItem: Integer; State: TCustomDrawState;
707   var DefaultDraw: Boolean);
708 {var
709   DestRect: TRect;
710   Script: String;
711   i, x, w: integer;
712   SavedDC: integer;
713   Mark: TSsMarkUpType;}
714 begin
715   Exit // !!
716   {if (SubItem <> SubScript+1) or (not Pref.ColorScript) then Exit; // DefaultDraw = true
717   // Custom Script Coloring
718   DefaultDraw := false;
719   SavedDC := SaveDC(lvwLog.Canvas.Handle);
720   try
721     ListView_GetSubItemRect(lvwLog.Handle, Item.Index, SubScript+1, LVIR_BOUNDS, @DestRect);
722
723     lvwLog.Canvas.Brush.Style := bsSolid;
724     if cdsSelected in State then begin
725       lvwLog.Canvas.Brush.Color := clHighlight
726     end else begin
727       lvwLog.Canvas.Brush.Color := Pref.BgColor;
728     end;
729     lvwLog.Canvas.FillRect(DestRect);
730     lvwLog.Canvas.Brush.Style := bsClear;
731
732     Script := Item.SubItems[SubScript];
733     // DrawTextEx(lvwLog.Canvas.Handle, PChar(Script), -1, DestRect, DT_END_ELLIPSIS, nil);
734     SsParser.InputString := Script;
735     x := 6;
736     for i := 0 to SsParser.Count - 1 do begin
737       Mark := SsParser.MarkUpType[i];
738       case Mark of
739         mtMeta:   lvwLog.Canvas.Font.Color := Pref.MetaWordColor;
740         mtTag:    lvwLog.Canvas.Font.Color := Pref.MarkUpColor;
741         mtTagErr: lvwLog.Canvas.Font.Color := Pref.MarkErrorColor;
742         else begin
743           lvwLog.Canvas.Font.Color := Pref.TalkColorH;
744         end;
745       end;
746       w := lvwLog.Canvas.TextWidth(SsParser[i]);
747       lvwLog.Canvas.TextRect(DestRect, DestRect.Left + x, DestRect.Top + 2, SsParser[i]);
748       x := x + w;
749       if DestRect.Right - DestRect.Left < x then Break;
750     end;
751   finally
752     RestoreDC(lvwLog.Canvas.Handle, SavedDC);
753   end;}
754 end;
755
756 procedure TfrmLog.UpdateScript(const Script: String);
757 begin
758   if Script <> FLastScript then begin
759     if Pref.LogWindowPreviewStyle = psConversation then begin
760       if Pref.ColorScript then begin
761         UpdateScriptConversationColor(Script);
762       end else begin
763         UpdateScriptConversationNoColor(Script);
764       end;
765     end else begin
766       UpdateScriptScript(Script);
767     end;
768     SendMessage(edtScript.Handle, EM_LINESCROLL, Low(integer), Low(integer)); //\83X\83N\83\8d\81[\83\8b\96ß\82µ
769     FLastScript := Script;
770   end;
771 end;
772
773 procedure TfrmLog.PopupMenuPreviewStylePopup(Sender: TObject);
774 var i: integer;
775 begin
776   with PopupMenuPreviewStyle do
777     for i := 0 to Items.Count-1 do
778       Items[i].Checked := Items[i].Tag = Ord(Pref.LogWindowPreviewStyle)
779 end;
780
781 procedure TfrmLog.mnPreviewStyleClick(Sender: TObject);
782 var i: integer;
783 begin
784   with PopupMenuPreviewStyle do
785     for i := 0 to Items.Count-1 do
786       Items[i].Checked := (Sender as TMenuItem).Tag = Items[i].Tag;
787   Pref.LogWindowPreviewStyle := TLogWindowPreviewStyle((Sender as TMenuItem).Tag);
788   FLastScript := '';
789   lvwLogChange(self, lvwLog.Selected, ctState);
790 end;
791
792 procedure TfrmLog.UpdateScriptScript(const Script: String);
793 var
794   UnyuTalking, InSynchronized: boolean;
795   i: integer;
796 begin
797   if Pref.ColorScript then begin
798     edtScript.Color := Pref.BgColor;
799   end else begin
800     edtScript.Color := clWindow;
801     edtScript.DefAttributes.Color := clWindowText;
802     edtScript.SelAttributes.Color := clWindowText;
803   end;
804   SsParser.LeaveEscape := true;
805   SsParser.InputString := Script;
806   edtScript.Text := '';
807   edtScript.SelAttributes.Color := clWindowText;
808   UnyuTalking := false;
809   InSynchronized := false;
810   for i := 0 to SsParser.Count-1 do begin
811     if Pref.ColorScript then begin
812       case SsParser.MarkUpType[i] of
813         mtStr: begin
814           if InSynchronized then
815             edtScript.SelAttributes.Color := Pref.TalkColorS
816           else if UnyuTalking then
817             edtScript.SelAttributes.Color := Pref.TalkColorU
818           else
819             edtScript.SelAttributes.Color := Pref.TalkColorH;
820         end;
821         mtTag: begin
822           edtScript.SelAttributes.Color := Pref.MarkUpColor;
823           if SsParser[i] = '\h' then
824             UnyuTalking := false
825           else if SsParser[i] = '\u' then
826             UnyuTalking := true
827           else if SsParser[i] = '\_s' then
828             InSynchronized := not InSynchronized;
829         end;
830         mtMeta:   edtScript.SelAttributes.Color := Pref.MetaWordColor;
831         mtTagErr: edtScript.SelAttributes.Color := Pref.MarkErrorColor;
832       end;
833     end;
834     edtScript.SelText := SsParser[i];
835     if (SsParser[i] = '\n') and (Pref.LogWindowPreviewStyle = psScriptWithLineBreak) then
836       edtScript.SelText := #13#10;
837   end;
838 end;
839
840 procedure TfrmLog.tbtnPreviewStyleClick(Sender: TObject);
841 var sel: integer;
842 begin
843   sel := Ord(Pref.LogWindowPreviewStyle);
844   sel := sel + 1;
845   if sel > Ord(High(TLogWindowPreviewStyle)) then sel := 0;
846   Pref.LogWindowPreviewStyle := TLogWindowPreviewStyle(sel);
847   FLastScript := '';
848   lvwLogChange(self, lvwLog.Selected, ctState);
849 end;
850
851 function TfrmLog.SelectedBottleLog: TBottleLogList;
852 begin
853   if tabBottleLog.TabIndex >= 0 then
854     Result := FBottleLogList.Items[tabBottleLog.TabIndex] as TBottleLogList
855   else
856     Result := nil;
857 end;
858
859 procedure TfrmLog.tabBottleLogChange(Sender: TObject);
860 begin
861   UpdateWindow;
862   if SelectedBottleLog.SelectedIndex >= 0 then begin
863     lvwLog.Items[SelectedBottleLog.SelectedIndex].Selected := true;
864     if lvwLog.Focused then lvwLog.Selected.Focused := true;
865   end;
866   lvwLogChange(Self, nil, ctState);
867 end;
868
869 procedure TfrmLog.LogLoaded(Sender: TObject);
870 begin
871   if SelectedBottleLog = Sender then begin
872     UpdateWindow;
873   end;
874 end;
875
876 procedure TfrmLog.UpdateTab;
877 var i, cur: integer;
878 begin
879   cur := tabBottleLog.tabIndex;
880   tabBottleLog.Tabs.Clear;
881   for i := 0 to FBottleLogList.Count - 1 do begin
882     tabBottleLog.Tabs.Add((FBottleLogList[i] as TBottleLogList).Title);
883   end;
884   if FBottleLogList.Count > 0 then begin
885     if cur < FBottleLogList.Count then
886       tabBottleLog.TabIndex := cur
887     else
888       tabBottleLog.TabIndex := FBottleLogList.Count-1;
889   end;
890 end;
891
892 procedure TfrmLog.LogLoadFailure(Sender: TObject; const Message: String);
893 begin
894   Beep;
895   ShowMessage(Message);
896   if Sender = SelectedBottleLog then UpdateWindow;
897 end;
898
899 procedure TfrmLog.AgreeLog(const MID: String; const Agree: integer);
900 var i: integer;
901     flag: boolean;
902 begin
903   flag := false;
904   for i := 0 to FBottleLogList.Count - 1 do begin
905     if (FBottleLogList[i] as TBottleLogList).Bottle(MID) <> nil then begin
906       (FBottleLogList[i] as TBottleLogList).Bottle(MID).Agrees := Agree;
907       flag := true;
908     end;
909   end;
910   if flag then lvwLog.Invalidate;
911 end;
912
913 procedure TfrmLog.VoteLog(const MID: String; const Vote: integer);
914 var i: integer;
915     flag: boolean;
916 begin
917   flag := false;
918   for i := 0 to FBottleLogList.Count - 1 do begin
919     if (FBottleLogList[i] as TBottleLogList).Bottle(MID) <> nil then begin
920       (FBottleLogList[i] as TBottleLogList).Bottle(MID).Votes := Vote;
921       flag := true;
922     end;
923   end;
924   if flag then lvwLog.Invalidate;
925 end;
926
927 procedure TfrmLog.tabBottleLogChanging(Sender: TObject;
928   var AllowChange: Boolean);
929 begin
930   // \8c»\8dÝ\91I\91ð\82³\82ê\82Ä\82¢\82é\83\8d\83O\82Ì\91I\91ð\8fó\91Ô\82ð\95Û\91
931   if SelectedBottleLog = nil then Exit;
932   if lvwLog.Selected <> nil then
933     SelectedBottleLog.SelectedIndex := lvwLog.Selected.Index
934   else
935     SelectedBottleLog.SelectedIndex := -1;
936 end;
937
938 procedure TfrmLog.tabBottleLogContextPopup(Sender: TObject;
939   MousePos: TPoint; var Handled: Boolean);
940 begin
941   with tabBottleLog do begin
942     Tag := IndexOfTabAt(MousePos.X, MousePos.Y);
943     if Tag < 0 then Handled := true;
944   end;
945 end;
946
947 procedure TfrmLog.mnCloseTabClick(Sender: TObject);
948 begin
949   FBottleLogList.Delete(tabBottleLog.Tag);
950   UpdateTab;
951   UpdateWindow;
952   lvwLogChange(Self, nil, ctState);
953 end;
954
955 procedure TfrmLog.tbtnFindBottleClick(Sender: TObject);
956 var Query: String;
957     ResultLog: TBottleLogList;
958     Item1, Item2: TLogItem;
959     i, matched: integer;
960 begin
961   if SelectedBottleLog = nil then Exit;
962   if SelectedBottleLog.Count = 0 then begin
963     ShowMessage('\8c\9f\8dõ\91Î\8fÛ\82ª\8bó\82Å\82·\81B');
964     Exit;
965   end;
966   Query := '';
967   matched := 0;
968   if InputQuery('\83X\83N\83\8a\83v\83g\96{\95\82ð\8c\9f\8dõ', '\8c\9f\8dõ\95\8e\9a\97ñ', Query) then begin
969     if Query = '' then Exit;
970     ResultLog := TBottleLogList.Create('\8c\9f\8dõ\8c\8b\89Ê');
971     for i := 0 to SelectedBottleLog.Count-1 do begin
972       Item1 := SelectedBottleLog.Items[i] as TLogItem;
973       if AnsiContainsText(Item1.Script, Query) and (Item1.LogType = ltBottle) then begin
974         matched := matched + 1;
975         Item2 := TLogItem.Create(ltBottle, Item1.MID, Item1.Channel,
976           Item1.Script, Item1.Ghost, Item1.LogTime);
977         Item2.State := lsOpened;
978         Item2.Votes := Item1.Votes;
979         Item2.Agrees := Item1.Agrees;
980         ResultLog.Add(Item2);
981       end;
982     end;
983     if matched = 0 then
984       ResultLog.AddSystemLog('\8c©\82Â\82©\82è\82Ü\82¹\82ñ\82Å\82µ\82½');
985     BottleLogList.Add(ResultLog);
986     UpdateTab;
987     tabBottleLog.TabIndex := BottleLogList.Count-1;
988     UpdateWindow;
989   end;
990 end;
991
992 procedure TfrmLog.tbtnOpenLogClick(Sender: TObject);
993 var BottleLog: TBottleLogList;
994     i, Index: integer;
995 begin
996   Index := -1;
997   if OpenDialog.Execute then begin
998     for i := 0 to OpenDialog.Files.Count-1 do begin
999       BottleLog := TBottleLogList.Create(ExtractFileName(OpenDialog.Files[i]));
1000       try
1001         BottleLog.LoadFromXMLFile(OpenDialog.Files[i], XMLDocument);
1002       except
1003         on E: EXMLFileOpenException do begin
1004           Beep;
1005           ShowMessage(E.Message);
1006           FreeAndNil(BottleLog);
1007         end;
1008       end;
1009       if BottleLog <> nil then Index := BottleLogList.Add(BottleLog); // \8dÅ\8cã\82É\8aJ\82¢\82½\83\8d\83O\82Ì\88Ê\92u\82ð\8bL\89¯
1010     end;
1011     UpdateTab;
1012     if Index >= 0 then tabBottleLog.TabIndex := Index;
1013     UpdateWindow;
1014   end;
1015 end;
1016
1017 function TfrmLog.GetDefaultFileName(const Name, Ext: String): String;
1018 begin
1019   Result := StringReplace(Name, '/', '', [rfReplaceAll]);
1020   Result := StringReplace(Result, ' ', '', [rfReplaceAll]);
1021   Result := ChangeFileExt(Result, Ext);
1022 end;
1023
1024 function TfrmLog.BottleLogTitled(const LogName: String): TBottleLogList;
1025 var i: integer;
1026 begin
1027   for i := 0 to FBottleLogList.Count-1 do begin
1028     if (FBottleLogList[i] as TBottleLogList).Title = LogName then begin
1029       Result := (FBottleLogList[i] as TBottleLogList);
1030       Exit;
1031     end;
1032   end;
1033   // \8c©\82Â\82©\82ç\82È\82¢\8fê\8d\87
1034   Result := TBottleLogList.Create(LogName); // \90V\82µ\82­\8dì\82é
1035   FBottleLogList.Add(Result);
1036   UpdateTab;
1037   if FBottleLogList.Count = 1 then tabBottleLog.TabIndex := 0;
1038 end;
1039
1040 procedure TfrmLog.AllBottleOpened;
1041 var i, j: integer;
1042     Log: TBottleLogList;
1043 begin
1044   for i := 0 to FBottleLogList.Count-1 do begin
1045     Log  := FBottleLogList[i] as TBottleLogList;
1046     for j := 0 to Log.Count-1 do begin
1047       Log.Bottles[j].State := lsOpened;
1048     end;
1049   end;
1050 end;
1051
1052 procedure TfrmLog.tabBottleLogMouseDown(Sender: TObject;
1053   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
1054 var Index: integer;
1055 begin
1056   with tabBottleLog do begin
1057     Index := IndexOfTabAt(X, Y);
1058     if Index = -1 then Exit; //\83^\83u\82ª\82È\82¢\82Ì\82Å\83h\83\89\83b\83O\82Å\82«\82È\82¢
1059     if Button = mbLeft then begin
1060       FDragTabIndex := Index; //\83h\83\89\83b\83O\82·\82é\83^\83u\82Ì\83C\83\93\83f\83b\83N\83X\82ð\95Û\91
1061       BeginDrag(False);
1062       FDragTabDest := -1;     //\83h\83\89\83b\83O\98g\90ü\95`\89æ\83t\83\89\83O\83N\83\8a\83A\82Ì\82½\82ß
1063     end;
1064   end;
1065 end;
1066
1067 procedure TfrmLog.tabBottleLogDragOver(Sender, Source: TObject; X,
1068   Y: Integer; State: TDragState; var Accept: Boolean);
1069 var TargetRect: TRect;
1070     OldDest: integer;
1071 begin
1072   Accept := Source = tabBottleLog;
1073   if not Accept then Exit;
1074   with tabBottleLog do begin
1075     OldDest := FDragTabDest;
1076     FDragTabDest := IndexOfTabAt(X, Y);
1077     if FDragTabDest = -1 then begin
1078       Accept := false; //\82±\82Ì\8fê\8d\87\82Í\83h\83\8d\83b\83v\82ð\94F\82ß\82È\82¢
1079       Exit;
1080     end;
1081     with Canvas do begin
1082       Pen.Mode := pmNot;
1083       Pen.Width := 3;
1084     end;
1085     if (OldDest <> FDragTabDest) and (OldDest >= 0) then begin
1086       //\88È\91O\82Ì\98g\90ü\8fÁ\8b\8e
1087       TargetRect := TabRect(OldDest);
1088       with Canvas do begin
1089         Brush.Style := bsClear;
1090         Rectangle(TargetRect.Left, TargetRect.Top,
1091                   TargetRect.Right, TargetRect.Bottom);
1092       end;
1093     end;
1094     if (OldDest <> FDragTabDest) then begin
1095       //\90V\82µ\82¢\98g\90ü\95`\89æ
1096       TargetRect := TabRect(FDragTabDest);
1097       with Canvas do begin
1098         Brush.Style := bsClear;
1099         Rectangle(TargetRect.Left, TargetRect.Top,
1100                   TargetRect.Right, TargetRect.Bottom);
1101       end;
1102     end;
1103   end;
1104 end;
1105
1106 procedure TfrmLog.tabBottleLogDragDrop(Sender, Source: TObject; X,
1107   Y: Integer);
1108 var DestIndex: integer;
1109 begin
1110   with tabBottleLog do begin
1111     DestIndex := IndexOfTabAt(X, Y);
1112     Tabs.Move(FDragTabIndex, DestIndex);
1113     FBottleLogList.Move(FDragTabIndex, DestIndex);
1114   end;
1115 end;
1116
1117 procedure TfrmLog.tabBottleLogEndDrag(Sender, Target: TObject; X,
1118   Y: Integer);
1119 begin
1120   //\8b­\90§\93I\82É\83^\83u\82ð\8dÄ\95`\89æ\82³\82¹\82é\81B\98g\90ü\8fÁ\82µ\91Î\8dô
1121   tabBottleLog.Tabs.BeginUpdate;
1122   tabBottleLog.Tabs.EndUpdate;
1123 end;
1124
1125 procedure TfrmLog.LogLoadWork(Sender: TObject);
1126 begin
1127   if Sender = SelectedBottleLog then lvwLog.Invalidate;
1128 end;
1129
1130 end.