OSDN Git Service

actInsertCue added
[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;
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, StrUtils;
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           UpdateScript(Script);
268         end else begin
269           frmSender.actVoteMessage.Enabled := false;
270           frmSender.actAgreeMessage.Enabled := false;
271           frmSender.actInsertCue.Enabled := false;
272           mnPopUpCopyScript.Enabled := false;
273           UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\82ð\83N\83\8a\83A
274         end;
275       end else begin
276         frmSender.actVoteMessage.Enabled := false;
277         frmSender.actAgreeMessage.Enabled := false;
278         frmSender.actInsertCue.Enabled := false;
279         mnPopUpCopyScript.Enabled := false;
280         UpdateScript(Script); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
281       end;
282     end;
283     tbtnSaveLog.Enabled := lvwLog.Items.Count > 0;
284   end else begin
285     StatusBar.Panels[0].Text := '\83\8d\83O\82ª\82 \82è\82Ü\82¹\82ñ';
286     frmSender.actVoteMessage.Enabled := false;
287     frmSender.actAgreeMessage.Enabled := false;
288     mnPopUpCopyScript.Enabled := false;
289     UpdateScript(''); // \83\8d\83O\83v\83\8c\83r\83\85\81[\95\94\83N\83\8a\83A
290   end;
291 end;
292
293 procedure TfrmLog.lvwLogDblClick(Sender: TObject);
294 var Script: String;
295     Opt: TScriptTransOptions;
296     SOpt: TSstpSendOptions;
297     Ghost: String;
298     Log: TLogItem;
299 begin
300   if lvwLog.Selected = nil then Exit;
301   //Log := TLogItem(lvwLog.Selected.Data);
302   Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
303   if Log = nil then Exit;
304   if Log.LogType <> ltBottle then Exit;
305   Script := Log.Script;
306   Opt := [toConvertURL, toWaitScriptEnd];
307   if Pref.NoTransUrl then Opt := Opt + [toNoChoice];
308   if Pref.IgnoreFrequentYenS then Opt := Opt + [toIgnoreFrequentYenS];
309   if Pref.FixMessySurface then Opt := Opt + [toFixMessySurface];
310   if Pref.HUTagTo01Tag then Opt := Opt + [toHUTagTo01Tag];
311   frmSender.DoTrans(Script, Opt);
312
313   if ChannelList.Channel[Log.Channel] <> nil then
314     Ghost := ChannelList.Channel[Log.Channel].Ghost;
315   //\96Ú\95W\83S\81[\83X\83g\8c\88\92è
316   if Log.Ghost <> '' then Ghost := Log.Ghost;
317   //\83^\81[\83Q\83b\83g\83S\81[\83X\83g\8am\92è
318   Ghost := frmSender.SetHWndToFavoriteGhost(Ghost);
319   frmSender.DirectSstp.SstpSender := 'SSTP Bottle -\81y\83\8d\83O\8dÄ\90\81z';
320   if Pref.NoTranslate then SOpt := [soNoTranslate] else SOpt := [];
321   frmSender.DirectSstp.SstpSEND(Script, SOpt, frmSender.GhostNameToSetName(Ghost));
322 end;
323
324 procedure TfrmLog.UpdateScriptConversationColor(const Script: String);
325 var i: integer;
326     scr: String;
327     UnyuTalking, Talked, InSynchronized: boolean;
328 begin
329   scr := Script;
330   frmSender.DoTrans(scr, [toConvertURL]);
331   SsParser.LeaveEscape := false;
332   SsParser.InputString := scr;
333   SsParser.LeaveEscape := true;
334   UnyuTalking := false;
335   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
336   InSynchronized := false;
337   edtScript.Text := '';
338   edtScript.Color := Pref.BgColor;
339   for i := 0 to SsParser.Count-1 do begin
340     if (SsParser[i] = '\_s') and not InSynchronized then begin
341       InSynchronized := true;
342       if Talked then begin
343         edtScript.SelText := #13#10;
344         Talked := false;
345       end;
346     end else if (SsParser[i] = '\_s') and InSynchronized then begin
347       InSynchronized := false;
348       if Talked then begin
349         edtScript.SelText := #13#10;
350         Talked := false;
351       end;
352     end;
353     if (SsParser[i] = '\u') and not UnyuTalking then begin
354       UnyuTalking := true;
355       if Talked then begin
356         edtScript.SelText := #13#10;
357         Talked := false;
358       end;
359     end;
360     if (SsParser[i] = '\h') and UnyuTalking then begin
361       UnyuTalking := false;
362       if Talked then begin
363         edtScript.SelText := #13#10;
364         Talked := false;
365       end;
366     end;
367     if SsParser.MarkUpType[i] = mtStr then begin
368       if InSynchronized then
369         edtScript.SelAttributes.Color := Pref.TalkColorS
370       else if UnyuTalking then
371         edtScript.SelAttributes.Color := Pref.TalkColorU
372       else
373         edtScript.SelAttributes.Color := Pref.TalkColorH;
374       edtScript.SelText := SsParser[i];
375       Talked := true;
376     end;
377     if SsParser.MarkUpType[i] = mtMeta then begin
378       edtScript.SelAttributes.Color := Pref.MetaWordColor;
379       edtScript.SelText := SsParser[i];
380       Talked := true;
381     end;
382   end;
383 end;
384
385 procedure TfrmLog.UpdateScriptConversationNoColor(const Script: String);
386 var Scr: String;
387     i: integer;
388     UnyuTalking, Talked, LastUnyuTalked, InSynchronize, LastInSynchronize: boolean;
389 begin
390   Scr := Script;
391   frmSender.DoTrans(Scr, [toConvertURL]);
392   SsParser.LeaveEscape := false;
393   SsParser.InputString := Scr;
394   SsParser.LeaveEscape := true;
395   edtScript.Text := '';
396   edtScript.Color := clWindow;
397   edtScript.DefAttributes.Color := clWindowText;
398   edtScript.SelAttributes.Color := clWindowText;
399   Talked := false;
400   UnyuTalking := false;
401   LastUnyuTalked := false;
402   InSynchronize := false;
403   LastInSynchronize := false;
404   for i := 0 to SsParser.Count-1 do begin
405     if SsParser[i] = '\u' then UnyuTalking := true;
406     if SsParser[i] = '\h' then UnyuTalking := false;
407     if SsParser[i] = '\_s' then InSynchronize := not InSynchronize;
408     if SsParser.MarkUpType[i] in [mtStr, mtMeta] then begin
409       if not Talked then begin // \83X\83N\83\8a\83v\83g\8dÅ\8f\89\82Ì\83Z\83\8a\83t
410         if InSynchronize then Scr := '\97¼\81F'
411         else if UnyuTalking then Scr := '\82¤:' else Scr := '\82³:';
412       end;
413       if Talked and ((UnyuTalking <> LastUnyuTalked) or (InSynchronize <> LastInSynchronize)) then begin
414         Scr := Scr + #13#10;
415         if InSynchronize then Scr := Scr + '\97¼\81F'
416         else if UnyuTalking then Scr := Scr + '\82¤:' else Scr := Scr + '\82³:';
417       end;
418       Scr := Scr + SsParser[i];
419       Talked := true;
420       LastInSynchronize := InSynchronize;
421       LastUnyuTalked := UnyuTalking;
422     end;
423   end;
424   edtScript.Text := Scr;
425 end;
426
427 procedure TfrmLog.lvwLogKeyPress(Sender: TObject; var Key: Char);
428 begin
429   if Key = #13 then lvwLogDblClick(Sender);
430 end;
431
432 procedure TfrmLog.CreateParams(var Params: TCreateParams);
433 begin
434   inherited;
435   Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
436 end;
437
438 procedure TfrmLog.lvwLogClick(Sender: TObject);
439 begin
440   //\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ô
441   with lvwLog do
442     Selected := Selected;
443 end;
444
445 procedure TfrmLog.lvwLogColumnClick(Sender: TObject; Column: TListColumn);
446 var SortType: TBottleLogSortType;
447     SelectedMID: String;
448     SortColumn: integer;
449 begin
450   if lvwLog.Selected <> nil then
451     SelectedMID := SelectedBottleLog.Bottles[lvwLog.Selected.Index].MID;
452
453   SortColumn := Column.Index;
454   case SortColumn-1 of
455     -1: SortType := stLogTime;
456     subChannel: SortType := stChannel;
457     subGhost:   SortType := stGhost;
458     subVotes:   SortType := stVote;
459     subAgrees:  SortType := stAgree;
460     subScript:  SortType := stScript;
461   else SortType := stLogTime;
462   end;
463
464   SelectedBottleLog.SortBottles(SortType);
465   lvwLog.Invalidate;
466   SelAndFocusMessage(SelectedMID);
467 end;
468
469
470 procedure TfrmLog.mnPopUpCopyScriptClick(Sender: TObject);
471 var
472   Log: TLogItem;
473   Clip: TClipBoard;
474 begin
475   Log := SelectedBottleLog.Bottles[frmLog.lvwLog.Selected.Index];
476   if Log = nil then Exit;
477   Clip := ClipBoard();
478   Clip.SetTextBuf(PChar(Log.Script));
479 end;
480
481 procedure TfrmLog.SetBottleState(const MID: String; State: TLogState);
482 var i: integer;
483     Bottle: TLogItem;
484 begin
485   for i := 0 to FBottleLogList.Count-1 do begin
486     Bottle := (FBottleLogList[i] as TBottleLogList).Bottle(MID);
487     if Bottle <> nil then begin
488       Bottle.State := State;
489       lvwLog.OnChange := nil;
490       lvwLog.Invalidate;
491       lvwLog.OnChange := lvwLogChange;
492     end;
493   end;
494 end;
495
496 procedure TfrmLog.mnSaveLogClick(Sender: TObject);
497 begin
498   if SelectedBottleLog = nil then Exit;
499   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
500   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
501   SaveDialog.DefaultExt := 'log';
502   SaveDialog.FilterIndex := 1;
503   if SaveDialog.Execute then
504     SelectedBottleLog.SaveToSstpLog(SaveDialog.FileName, false);
505 end;
506
507 procedure TfrmLog.mnSaveLogChannelClick(Sender: TObject);
508 begin
509   if SelectedBottleLog = nil then Exit;
510   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.log');
511   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
512   SaveDialog.DefaultExt := 'log';
513   SaveDialog.FilterIndex := 1;
514   if SaveDialog.Execute then
515     SelectedBottleLog.SaveToSstpLog(SaveDialog.FileName, true);
516 end;
517
518 procedure TfrmLog.mnSaveLogScriptClick(Sender: TObject);
519 begin
520   if SelectedBottleLog = nil then Exit;
521   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.txt');
522   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
523   SaveDialog.DefaultExt := 'txt';
524   SaveDialog.FilterIndex := 2;
525   if SaveDialog.Execute then
526     SelectedBottleLog.SaveToText(SaveDialog.FileName);
527 end;
528
529 procedure TfrmLog.mnSaveLogXMLClick(Sender: TObject);
530 begin
531   if SelectedBottleLog = nil then Exit;
532   SaveDialog.FileName := GetDefaultFileName(SelectedBottleLog.Title, '.xml');
533   SaveDialog.InitialDir := ExtractFileDir(Application.ExeName);
534   SaveDialog.DefaultExt := 'xml';
535   SaveDialog.FilterIndex := 3;
536   if SaveDialog.Execute then
537     SelectedBottleLog.SaveToXmlFile(SaveDialog.FileName, XMLDocument);
538 end;
539
540 procedure TfrmLog.lvwLogData(Sender: TObject; Item: TListItem);
541 var i: integer;
542     Log: TLogItem;
543 begin
544   if Item = nil then Exit;
545   i := Item.Index;
546   Log := SelectedBottleLog.Bottles[i];
547   with Item do begin
548     Caption := FormatDateTime('yy/mm/dd hh:nn:ss', Log.LogTime);
549     SubItems.Clear;
550     SubItems.Add(Log.Channel);
551     SubItems.Add(Log.Ghost);
552     if Log.LogType = ltBottle then begin
553       if Log.Votes > 0 then
554         SubItems.Add(IntToStr(Log.Votes))
555       else
556         SubItems.Add('');
557       if Log.Agrees > 0 then
558         SubItems.Add(IntToStr(Log.Agrees))
559       else
560         SubItems.Add('');
561     end else begin
562       // \83V\83X\83e\83\80\83\8d\83O\82È\82Ç\82Í\93\8a\95[\81E\93¯\88Ó\82ð\95\\8e¦\82µ\82È\82¢
563       SubItems.Add('');
564       SubItems.Add('');
565     end;
566     SubItems.Add(Log.Script);
567
568     if Log.LogType = ltBottle then begin
569       case Log.State of
570         lsUnopened: ImageIndex := IconBottle;
571         lsPlaying:  ImageIndex := IconPlaying;
572         lsOpened:   ImageIndex := IconOpened;
573       end;
574     end else
575       ImageIndex := IconSystemLog;
576   end;
577 end;
578
579 procedure TfrmLog.UpdateWindow;
580 var EnabledFlag: boolean;
581 begin
582   if Pref.ColorScript then begin
583     if lvwLog.Color <> Pref.BgColor then lvwLog.Color := Pref.BgColor;
584     if lvwLog.Font.Color <> Pref.TalkColorH then lvwLog.Font.Color := Pref.TalkColorH;
585   end else begin
586     if lvwLog.Color <> clWindow then lvwLog.Color := clWindow;
587     if lvwLog.Font.Color <> clWindowText then lvwLog.Font.Color := clWindowText;
588   end;
589   if SelectedBottleLog <> nil then begin
590     StatusBar.Panels[0].Text := IntToStr(SelectedBottleLog.Count) + '\8c\8f';
591     lvwLog.Items.Count := SelectedBottleLog.Count;
592   end else begin
593     StatusBar.Panels[0].Text := '\83\8d\83O\82ª\82 \82è\82Ü\82¹\82ñ';
594     lvwLog.Items.Count := 0;
595   end;
596
597   EnabledFlag := SelectedBottleLog <> nil;
598   tbtnClear.Enabled := EnabledFlag;
599   tbtnSaveLog.Enabled := EnabledFlag;
600   tbtnFindBottle.Enabled := EnabledFlag;
601
602   lvwLog.Invalidate;
603 end;
604
605 procedure TfrmLog.PopupMenuListViewPopup(Sender: TObject);
606 var Log: TLogItem;
607     Child: TMenuItem;
608     Urls: TStringList;
609     i: integer;
610 begin
611   for i := mnJumpURL.Count-1 downto 0 do begin
612     mnJumpURL.Items[i].Free;
613   end;
614   mnJumpURL.Enabled := false;
615   if lvwLog.Selected = nil then Exit;
616   Log := SelectedBottleLog.Bottles[lvwLog.Selected.Index];
617   if Log = nil then Exit;
618   Urls := nil;
619   try
620     Urls := TStringList.Create;
621     ExtractURLs(Log.Script, Urls);
622     for i := 0 to Urls.Count-1 do begin
623       Child := TMenuItem.Create(Self);
624       with Child do begin
625         Caption := Format('(&%d) %s', [i+1, Urls[i]]);
626         OnClick := mnURLClick;
627         AutoHotkeys := maManual;
628         mnJumpURL.Add(Child);
629       end;
630     end;
631     mnJumpURL.Enabled := Urls.Count > 0;
632   finally
633     Urls.Free;
634   end;
635 end;
636
637 procedure TfrmLog.mnURLClick(Sender: TObject);
638 var URL: String;
639 begin
640   URL := (Sender as TMenuItem).Caption;
641   RegExp.Subst('s/^\(&?\d\) //', URL);
642   ShellExecute(Handle, 'open', PChar(URL), nil, nil, SW_SHOW);
643 end;
644
645 procedure TfrmLog.ExtractURLs(Script: String; Result: TStrings);
646 var i, u, j: integer;
647     s: String;
648 begin
649   Result.Clear;
650   SsParser.LeaveEscape := false;
651   SsParser.InputString := Script;
652   SsParser.LeaveEscape := true;
653   for i := 0 to SsParser.Count-1 do begin
654     if (SsParser.Match(SsParser[i], '\URL%b') > 0) then begin
655       for u := 7 downto 1 do begin
656         if (SsParser.Match(SsParser[i],
657             '\URL%b'+StringReplace(StringOfChar('-', u*2),
658             '-', '%b', [rfReplaceAll]))) > 0 then begin
659           for j := 1 to u do begin
660             s := SsParser.GetParam(SsParser[i], j*2);
661             if Pos('http://', s) > 0 then Result.Add(s);
662           end;
663           Break;
664         end;
665       end;
666       if SsParser.Match(SsParser[i], '\URL%b%b') = 0 then begin //\8aÈ\88Õ\94ÅURL\95Ï\8a·
667         //\8aÈ\88Õ\8c`\8e®\URL\83^\83O\95Ï\8a·
668         s := SsParser.GetParam(SsParser[i], 1);
669         if Pos('http://', s) > 0 then Result.Add(s);
670       end;
671     end;
672   end;
673 end;
674
675 procedure TfrmLog.SelAndFocusMessage(const MID: String);
676 var i: integer;
677     Log: TLogItem;
678 begin
679   for i := 0 to SelectedBottleLog.Count-1 do begin
680     Log := SelectedBottleLog.Items[i] as TLogItem;
681     if Log.MID = MID then begin
682       lvwLog.Items[i].Selected := true;
683       lvwLog.Items[i].Focused := true;
684     end;
685   end;
686 end;
687
688 procedure TfrmLog.lvwLogCustomDrawItem(Sender: TCustomListView;
689   Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
690 begin
691   //
692 end;
693
694 procedure TfrmLog.lvwLogCustomDrawSubItem(Sender: TCustomListView;
695   Item: TListItem; SubItem: Integer; State: TCustomDrawState;
696   var DefaultDraw: Boolean);
697 {var
698   DestRect: TRect;
699   Script: String;
700   i, x, w: integer;
701   SavedDC: integer;
702   Mark: TSsMarkUpType;}
703 begin
704   Exit // !!
705   {if (SubItem <> SubScript+1) or (not Pref.ColorScript) then Exit; // DefaultDraw = true
706   // Custom Script Coloring
707   DefaultDraw := false;
708   SavedDC := SaveDC(lvwLog.Canvas.Handle);
709   try
710     ListView_GetSubItemRect(lvwLog.Handle, Item.Index, SubScript+1, LVIR_BOUNDS, @DestRect);
711
712     lvwLog.Canvas.Brush.Style := bsSolid;
713     if cdsSelected in State then begin
714       lvwLog.Canvas.Brush.Color := clHighlight
715     end else begin
716       lvwLog.Canvas.Brush.Color := Pref.BgColor;
717     end;
718     lvwLog.Canvas.FillRect(DestRect);
719     lvwLog.Canvas.Brush.Style := bsClear;
720
721     Script := Item.SubItems[SubScript];
722     // DrawTextEx(lvwLog.Canvas.Handle, PChar(Script), -1, DestRect, DT_END_ELLIPSIS, nil);
723     SsParser.InputString := Script;
724     x := 6;
725     for i := 0 to SsParser.Count - 1 do begin
726       Mark := SsParser.MarkUpType[i];
727       case Mark of
728         mtMeta:   lvwLog.Canvas.Font.Color := Pref.MetaWordColor;
729         mtTag:    lvwLog.Canvas.Font.Color := Pref.MarkUpColor;
730         mtTagErr: lvwLog.Canvas.Font.Color := Pref.MarkErrorColor;
731         else begin
732           lvwLog.Canvas.Font.Color := Pref.TalkColorH;
733         end;
734       end;
735       w := lvwLog.Canvas.TextWidth(SsParser[i]);
736       lvwLog.Canvas.TextRect(DestRect, DestRect.Left + x, DestRect.Top + 2, SsParser[i]);
737       x := x + w;
738       if DestRect.Right - DestRect.Left < x then Break;
739     end;
740   finally
741     RestoreDC(lvwLog.Canvas.Handle, SavedDC);
742   end;}
743 end;
744
745 procedure TfrmLog.UpdateScript(const Script: String);
746 begin
747   if Script <> FLastScript then begin
748     if Pref.LogWindowPreviewStyle = psConversation then begin
749       if Pref.ColorScript then begin
750         UpdateScriptConversationColor(Script);
751       end else begin
752         UpdateScriptConversationNoColor(Script);
753       end;
754     end else begin
755       UpdateScriptScript(Script);
756     end;
757     SendMessage(edtScript.Handle, EM_LINESCROLL, Low(integer), Low(integer)); //\83X\83N\83\8d\81[\83\8b\96ß\82µ
758     FLastScript := Script;
759   end;
760 end;
761
762 procedure TfrmLog.PopupMenuPreviewStylePopup(Sender: TObject);
763 var i: integer;
764 begin
765   with PopupMenuPreviewStyle do
766     for i := 0 to Items.Count-1 do
767       Items[i].Checked := Items[i].Tag = Ord(Pref.LogWindowPreviewStyle)
768 end;
769
770 procedure TfrmLog.mnPreviewStyleClick(Sender: TObject);
771 var i: integer;
772 begin
773   with PopupMenuPreviewStyle do
774     for i := 0 to Items.Count-1 do
775       Items[i].Checked := (Sender as TMenuItem).Tag = Items[i].Tag;
776   Pref.LogWindowPreviewStyle := TLogWindowPreviewStyle((Sender as TMenuItem).Tag);
777   FLastScript := '';
778   lvwLogChange(self, lvwLog.Selected, ctState);
779 end;
780
781 procedure TfrmLog.UpdateScriptScript(const Script: String);
782 var
783   UnyuTalking, InSynchronized: boolean;
784   i: integer;
785 begin
786   if Pref.ColorScript then begin
787     edtScript.Color := Pref.BgColor;
788   end else begin
789     edtScript.Color := clWindow;
790     edtScript.DefAttributes.Color := clWindowText;
791     edtScript.SelAttributes.Color := clWindowText;
792   end;
793   SsParser.LeaveEscape := true;
794   SsParser.InputString := Script;
795   edtScript.Text := '';
796   edtScript.SelAttributes.Color := clWindowText;
797   UnyuTalking := false;
798   InSynchronized := false;
799   for i := 0 to SsParser.Count-1 do begin
800     if Pref.ColorScript then begin
801       case SsParser.MarkUpType[i] of
802         mtStr: begin
803           if InSynchronized then
804             edtScript.SelAttributes.Color := Pref.TalkColorS
805           else if UnyuTalking then
806             edtScript.SelAttributes.Color := Pref.TalkColorU
807           else
808             edtScript.SelAttributes.Color := Pref.TalkColorH;
809         end;
810         mtTag: begin
811           edtScript.SelAttributes.Color := Pref.MarkUpColor;
812           if SsParser[i] = '\h' then
813             UnyuTalking := false
814           else if SsParser[i] = '\u' then
815             UnyuTalking := true
816           else if SsParser[i] = '\_s' then
817             InSynchronized := not InSynchronized;
818         end;
819         mtMeta:   edtScript.SelAttributes.Color := Pref.MetaWordColor;
820         mtTagErr: edtScript.SelAttributes.Color := Pref.MarkErrorColor;
821       end;
822     end;
823     edtScript.SelText := SsParser[i];
824     if (SsParser[i] = '\n') and (Pref.LogWindowPreviewStyle = psScriptWithLineBreak) then
825       edtScript.SelText := #13#10;
826   end;
827 end;
828
829 procedure TfrmLog.tbtnPreviewStyleClick(Sender: TObject);
830 var sel: integer;
831 begin
832   sel := Ord(Pref.LogWindowPreviewStyle);
833   sel := sel + 1;
834   if sel > Ord(High(TLogWindowPreviewStyle)) then sel := 0;
835   Pref.LogWindowPreviewStyle := TLogWindowPreviewStyle(sel);
836   FLastScript := '';
837   lvwLogChange(self, lvwLog.Selected, ctState);
838 end;
839
840 function TfrmLog.SelectedBottleLog: TBottleLogList;
841 begin
842   if tabBottleLog.TabIndex >= 0 then
843     Result := FBottleLogList.Items[tabBottleLog.TabIndex] as TBottleLogList
844   else
845     Result := nil;
846 end;
847
848 procedure TfrmLog.tabBottleLogChange(Sender: TObject);
849 begin
850   UpdateWindow;
851   if SelectedBottleLog.SelectedIndex >= 0 then begin
852     lvwLog.Items[SelectedBottleLog.SelectedIndex].Selected := true;
853     if lvwLog.Focused then lvwLog.Selected.Focused := true;
854   end;
855   lvwLogChange(Self, nil, ctState);
856 end;
857
858 procedure TfrmLog.LogLoaded(Sender: TObject);
859 begin
860   if SelectedBottleLog = Sender then begin
861     UpdateWindow;
862   end;
863 end;
864
865 procedure TfrmLog.UpdateTab;
866 var i, cur: integer;
867 begin
868   cur := tabBottleLog.tabIndex;
869   tabBottleLog.Tabs.Clear;
870   for i := 0 to FBottleLogList.Count - 1 do begin
871     tabBottleLog.Tabs.Add((FBottleLogList[i] as TBottleLogList).Title);
872   end;
873   if FBottleLogList.Count > 0 then begin
874     if cur < FBottleLogList.Count then
875       tabBottleLog.TabIndex := cur
876     else
877       tabBottleLog.TabIndex := FBottleLogList.Count-1;
878   end;
879 end;
880
881 procedure TfrmLog.LogLoadFailure(Sender: TObject; const Message: String);
882 begin
883   Beep;
884   ShowMessage(Message);
885   (Sender as TBottleLogList).AddSystemLog(Message);
886   lvwLog.Invalidate;
887 end;
888
889 procedure TfrmLog.AgreeLog(const MID: String; const Agree: integer);
890 var i: integer;
891     flag: boolean;
892 begin
893   flag := false;
894   for i := 0 to FBottleLogList.Count - 1 do begin
895     if (FBottleLogList[i] as TBottleLogList).Bottle(MID) <> nil then begin
896       (FBottleLogList[i] as TBottleLogList).Bottle(MID).Agrees := Agree;
897       flag := true;
898     end;
899   end;
900   if flag then lvwLog.Invalidate;
901 end;
902
903 procedure TfrmLog.VoteLog(const MID: String; const Vote: integer);
904 var i: integer;
905     flag: boolean;
906 begin
907   flag := false;
908   for i := 0 to FBottleLogList.Count - 1 do begin
909     if (FBottleLogList[i] as TBottleLogList).Bottle(MID) <> nil then begin
910       (FBottleLogList[i] as TBottleLogList).Bottle(MID).Votes := Vote;
911       flag := true;
912     end;
913   end;
914   if flag then lvwLog.Invalidate;
915 end;
916
917 procedure TfrmLog.tabBottleLogChanging(Sender: TObject;
918   var AllowChange: Boolean);
919 begin
920   // \8c»\8dÝ\91I\91ð\82³\82ê\82Ä\82¢\82é\83\8d\83O\82Ì\91I\91ð\8fó\91Ô\82ð\95Û\91
921   if SelectedBottleLog = nil then Exit;
922   if lvwLog.Selected <> nil then
923     SelectedBottleLog.SelectedIndex := lvwLog.Selected.Index
924   else
925     SelectedBottleLog.SelectedIndex := -1;
926 end;
927
928 procedure TfrmLog.tabBottleLogContextPopup(Sender: TObject;
929   MousePos: TPoint; var Handled: Boolean);
930 begin
931   with tabBottleLog do begin
932     Tag := IndexOfTabAt(MousePos.X, MousePos.Y);
933     if Tag < 0 then Handled := true;
934   end;
935 end;
936
937 procedure TfrmLog.mnCloseTabClick(Sender: TObject);
938 begin
939   FBottleLogList.Delete(tabBottleLog.Tag);
940   UpdateTab;
941   UpdateWindow;
942   lvwLogChange(Self, nil, ctState);
943 end;
944
945 procedure TfrmLog.tbtnFindBottleClick(Sender: TObject);
946 var Query: String;
947     ResultLog: TBottleLogList;
948     Item1, Item2: TLogItem;
949     i, matched: integer;
950 begin
951   if SelectedBottleLog = nil then Exit;
952   if SelectedBottleLog.Count = 0 then begin
953     ShowMessage('\8c\9f\8dõ\91Î\8fÛ\82ª\8bó\82Å\82·\81B');
954     Exit;
955   end;
956   Query := '';
957   matched := 0;
958   if InputQuery('\83X\83N\83\8a\83v\83g\96{\95\82ð\8c\9f\8dõ', '\8c\9f\8dõ\95\8e\9a\97ñ', Query) then begin
959     if Query = '' then Exit;
960     ResultLog := TBottleLogList.Create('\8c\9f\8dõ\8c\8b\89Ê');
961     for i := 0 to SelectedBottleLog.Count-1 do begin
962       Item1 := SelectedBottleLog.Items[i] as TLogItem;
963       if AnsiContainsText(Item1.Script, Query) and (Item1.LogType = ltBottle) then begin
964         matched := matched + 1;
965         Item2 := TLogItem.Create(ltBottle, Item1.MID, Item1.Channel,
966           Item1.Script, Item1.Ghost, Item1.LogTime);
967         Item2.State := lsOpened;
968         Item2.Votes := Item1.Votes;
969         Item2.Agrees := Item1.Agrees;
970         ResultLog.Add(Item2);
971       end;
972     end;
973     if matched = 0 then
974       ResultLog.AddSystemLog('\8c©\82Â\82©\82è\82Ü\82¹\82ñ\82Å\82µ\82½');
975     BottleLogList.Add(ResultLog);
976     UpdateTab;
977     tabBottleLog.TabIndex := BottleLogList.Count-1;
978     UpdateWindow;
979   end;
980 end;
981
982 procedure TfrmLog.tbtnOpenLogClick(Sender: TObject);
983 var BottleLog: TBottleLogList;
984     i, Index: integer;
985 begin
986   Index := -1;
987   if OpenDialog.Execute then begin
988     for i := 0 to OpenDialog.Files.Count-1 do begin
989       BottleLog := TBottleLogList.Create(ExtractFileName(OpenDialog.Files[i]));
990       try
991         BottleLog.LoadFromXMLFile(OpenDialog.Files[i], XMLDocument);
992       except
993         on E: EXMLFileOpenException do begin
994           Beep;
995           ShowMessage(E.Message);
996           FreeAndNil(BottleLog);
997         end;
998       end;
999       if BottleLog <> nil then Index := BottleLogList.Add(BottleLog); // \8dÅ\8cã\82É\8aJ\82¢\82½\83\8d\83O\82Ì\88Ê\92u\82ð\8bL\89¯
1000     end;
1001     UpdateTab;
1002     if Index >= 0 then tabBottleLog.TabIndex := Index;
1003     UpdateWindow;
1004   end;
1005 end;
1006
1007 function TfrmLog.GetDefaultFileName(const Name, Ext: String): String;
1008 begin
1009   Result := StringReplace(Name, '/', '', [rfReplaceAll]);
1010   Result := StringReplace(Result, ' ', '', [rfReplaceAll]);
1011   Result := ChangeFileExt(Result, Ext);
1012 end;
1013
1014 function TfrmLog.BottleLogTitled(const LogName: String): TBottleLogList;
1015 var i: integer;
1016 begin
1017   for i := 0 to FBottleLogList.Count-1 do begin
1018     if (FBottleLogList[i] as TBottleLogList).Title = LogName then begin
1019       Result := (FBottleLogList[i] as TBottleLogList);
1020       Exit;
1021     end;
1022   end;
1023   // \8c©\82Â\82©\82ç\82È\82¢\8fê\8d\87
1024   Result := TBottleLogList.Create(LogName); // \90V\82µ\82­\8dì\82é
1025   FBottleLogList.Add(Result);
1026   UpdateTab;
1027   if FBottleLogList.Count = 1 then tabBottleLog.TabIndex := 0;
1028 end;
1029
1030 procedure TfrmLog.AllBottleOpened;
1031 var i, j: integer;
1032     Log: TBottleLogList;
1033 begin
1034   for i := 0 to FBottleLogList.Count-1 do begin
1035     Log  := FBottleLogList[i] as TBottleLogList;
1036     for j := 0 to Log.Count-1 do begin
1037       Log.Bottles[j].State := lsOpened;
1038     end;
1039   end;
1040 end;
1041
1042 procedure TfrmLog.tabBottleLogMouseDown(Sender: TObject;
1043   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
1044 var Index: integer;
1045 begin
1046   with tabBottleLog do begin
1047     Index := IndexOfTabAt(X, Y);
1048     if Index = -1 then Exit; //\83^\83u\82ª\82È\82¢\82Ì\82Å\83h\83\89\83b\83O\82Å\82«\82È\82¢
1049     if Button = mbLeft then begin
1050       FDragTabIndex := Index; //\83h\83\89\83b\83O\82·\82é\83^\83u\82Ì\83C\83\93\83f\83b\83N\83X\82ð\95Û\91
1051       BeginDrag(False);
1052       FDragTabDest := -1;     //\83h\83\89\83b\83O\98g\90ü\95`\89æ\83t\83\89\83O\83N\83\8a\83A\82Ì\82½\82ß
1053     end;
1054   end;
1055 end;
1056
1057 procedure TfrmLog.tabBottleLogDragOver(Sender, Source: TObject; X,
1058   Y: Integer; State: TDragState; var Accept: Boolean);
1059 var TargetRect: TRect;
1060     OldDest: integer;
1061 begin
1062   Accept := Source = tabBottleLog;
1063   if not Accept then Exit;
1064   with tabBottleLog do begin
1065     OldDest := FDragTabDest;
1066     FDragTabDest := IndexOfTabAt(X, Y);
1067     if FDragTabDest = -1 then begin
1068       Accept := false; //\82±\82Ì\8fê\8d\87\82Í\83h\83\8d\83b\83v\82ð\94F\82ß\82È\82¢
1069       Exit;
1070     end;
1071     with Canvas do begin
1072       Pen.Mode := pmNot;
1073       Pen.Width := 3;
1074     end;
1075     if (OldDest <> FDragTabDest) and (OldDest >= 0) then begin
1076       //\88È\91O\82Ì\98g\90ü\8fÁ\8b\8e
1077       TargetRect := TabRect(OldDest);
1078       with Canvas do begin
1079         Brush.Style := bsClear;
1080         Rectangle(TargetRect.Left, TargetRect.Top,
1081                   TargetRect.Right, TargetRect.Bottom);
1082       end;
1083     end;
1084     if (OldDest <> FDragTabDest) then begin
1085       //\90V\82µ\82¢\98g\90ü\95`\89æ
1086       TargetRect := TabRect(FDragTabDest);
1087       with Canvas do begin
1088         Brush.Style := bsClear;
1089         Rectangle(TargetRect.Left, TargetRect.Top,
1090                   TargetRect.Right, TargetRect.Bottom);
1091       end;
1092     end;
1093   end;
1094 end;
1095
1096 procedure TfrmLog.tabBottleLogDragDrop(Sender, Source: TObject; X,
1097   Y: Integer);
1098 var DestIndex: integer;
1099 begin
1100   with tabBottleLog do begin
1101     DestIndex := IndexOfTabAt(X, Y);
1102     Tabs.Move(FDragTabIndex, DestIndex);
1103     FBottleLogList.Move(FDragTabIndex, DestIndex);
1104   end;
1105 end;
1106
1107 procedure TfrmLog.tabBottleLogEndDrag(Sender, Target: TObject; X,
1108   Y: Integer);
1109 begin
1110   //\8b­\90§\93I\82É\83^\83u\82ð\8dÄ\95`\89æ\82³\82¹\82é\81B\98g\90ü\8fÁ\82µ\91Î\8dô
1111   tabBottleLog.Tabs.BeginUpdate;
1112   tabBottleLog.Tabs.EndUpdate;
1113 end;
1114
1115 procedure TfrmLog.LogLoadWork(Sender: TObject);
1116 begin
1117   if Sender = SelectedBottleLog then lvwLog.Invalidate;
1118 end;
1119
1120 end.