1{ $Id: watchesdlg.pp 60850 2019-04-06 08:12:17Z mattias $ }
2{               ----------------------------------------------
3                 watchesdlg.pp  -  Overview of watches
4                ----------------------------------------------
5
6 @created(Fri Dec 14st WET 2001)
7 @lastmod($Date: 2019-04-06 10:12:17 +0200 (Sa, 06 Apr 2019) $)
8 @author(Shane Miller)
9 @author(Marc Weustink <marc@@dommelstein.net>)
10
11 This unit contains the watches dialog.
12
13
14 ***************************************************************************
15 *                                                                         *
16 *   This source is free software; you can redistribute it and/or modify   *
17 *   it under the terms of the GNU General Public License as published by  *
18 *   the Free Software Foundation; either version 2 of the License, or     *
19 *   (at your option) any later version.                                   *
20 *                                                                         *
21 *   This code is distributed in the hope that it will be useful, but      *
22 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
23 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
24 *   General Public License for more details.                              *
25 *                                                                         *
26 *   A copy of the GNU General Public License is available on the World    *
27 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
28 *   obtain it by writing to the Free Software Foundation,                 *
29 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
30 *                                                                         *
31 ***************************************************************************
32}
33
34unit WatchesDlg;
35
36{$mode objfpc}{$H+}
37
38interface
39
40uses
41  Classes, Forms, Controls, math, sysutils, LazLoggerBase, Clipbrd,
42  IDEWindowIntf, Menus, ComCtrls, ActnList, ExtCtrls, StdCtrls, LCLType,
43  IDEImagesIntf, LazarusIDEStrConsts, DebuggerStrConst, Debugger, DebuggerDlg,
44  DbgIntfBaseTypes, DbgIntfDebuggerBase, DbgIntfMiscClasses, SynEdit,
45  BaseDebugManager;
46
47type
48
49  TWatchesDlgStateFlags = set of (
50    wdsfUpdating,
51    wdsfNeedDeleteAll,
52    wdsfNeedDeleteCurrent
53  );
54
55  { TWatchesDlg }
56
57  TWatchesDlg = class(TDebuggerDlg)
58    actDeleteAll: TAction;
59    actDeleteSelected: TAction;
60    actDisableAll: TAction;
61    actDisableSelected: TAction;
62    actEnableAll: TAction;
63    actEnableSelected: TAction;
64    actAddWatch: TAction;
65    actAddWatchPoint: TAction;
66    actCopyName: TAction;
67    actCopyValue: TAction;
68    actInspect: TAction;
69    actEvaluate: TAction;
70    actToggleInspectSite: TAction;
71    actToggleCurrentEnable: TAction;
72    actPower: TAction;
73    ActionList1: TActionList;
74    actProperties: TAction;
75    InspectLabel: TLabel;
76    lvWatches: TListView;
77    InspectMemo: TMemo;
78    MenuItem1: TMenuItem;
79    nbInspect: TNotebook;
80    Page1: TPage;
81    popInspect: TMenuItem;
82    popEvaluate: TMenuItem;
83    popCopyName: TMenuItem;
84    popCopyValue: TMenuItem;
85    N3: TMenuItem;
86    popAddWatchPoint: TMenuItem;
87    mnuPopup: TPopupMenu;
88    popAdd: TMenuItem;
89    N1: TMenuItem; //--------------
90    popProperties: TMenuItem;
91    popEnabled: TMenuItem;
92    popDelete: TMenuItem;
93    N2: TMenuItem; //--------------
94    popDisableAll: TMenuItem;
95    popEnableAll: TMenuItem;
96    popDeleteAll: TMenuItem;
97    InspectSplitter: TSplitter;
98    ToolBar1: TToolBar;
99    ToolButtonInspectView: TToolButton;
100    ToolButtonProperties: TToolButton;
101    ToolButtonAdd: TToolButton;
102    ToolButtonPower: TToolButton;
103    ToolButton10: TToolButton;
104    ToolButton2: TToolButton;
105    ToolButtonEnable: TToolButton;
106    ToolButtonDisable: TToolButton;
107    ToolButtonTrash: TToolButton;
108    ToolButton6: TToolButton;
109    ToolButtonEnableAll: TToolButton;
110    ToolButtonDisableAll: TToolButton;
111    ToolButtonTrashAll: TToolButton;
112    procedure actAddWatchPointExecute(Sender: TObject);
113    procedure actCopyNameExecute(Sender: TObject);
114    procedure actCopyValueExecute(Sender: TObject);
115    procedure actDisableSelectedExecute(Sender: TObject);
116    procedure actEnableSelectedExecute(Sender: TObject);
117    procedure actEvaluateExecute(Sender: TObject);
118    procedure actInspectExecute(Sender: TObject);
119    procedure actPowerExecute(Sender: TObject);
120    procedure actToggleInspectSiteExecute(Sender: TObject);
121    procedure FormDestroy(Sender: TObject);
122    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
123    procedure FormShow(Sender: TObject);
124    procedure lvWatchesDblClick(Sender: TObject);
125    procedure lvWatchesDragDrop(Sender, Source: TObject; {%H-}X, {%H-}Y: Integer);
126    procedure lvWatchesDragOver(Sender, Source: TObject; {%H-}X, {%H-}Y: Integer;
127      {%H-}State: TDragState; var Accept: Boolean);
128    procedure lvWatchesSelectItem(Sender: TObject; {%H-}AItem: TListItem; {%H-}Selected: Boolean);
129    procedure popAddClick(Sender: TObject);
130    procedure popPropertiesClick(Sender: TObject);
131    procedure popEnabledClick(Sender: TObject);
132    procedure popDeleteClick(Sender: TObject);
133    procedure popDisableAllClick(Sender: TObject);
134    procedure popEnableAllClick(Sender: TObject);
135    procedure popDeleteAllClick(Sender: TObject);
136  private
137    function GetWatches: TIdeWatches;
138    procedure ContextChanged(Sender: TObject);
139    procedure SnapshotChanged(Sender: TObject);
140  private
141    FWatchesInView: TIdeWatches;
142    FPowerImgIdx, FPowerImgIdxGrey: Integer;
143    FUpdateAllNeeded, FUpdatingAll: Boolean;
144    FStateFlags: TWatchesDlgStateFlags;
145    function GetSelected: TCurrentWatch;
146    function  GetThreadId: Integer;
147    function  GetSelectedThreads(Snap: TSnapshot): TIdeThreads;
148    function GetStackframe: Integer;
149    procedure WatchAdd(const {%H-}ASender: TIdeWatches; const AWatch: TIdeWatch);
150    procedure WatchUpdate(const ASender: TIdeWatches; const AWatch: TIdeWatch);
151    procedure WatchRemove(const {%H-}ASender: TIdeWatches; const AWatch: TIdeWatch);
152
153    procedure UpdateInspectPane;
154    procedure UpdateItem(const AItem: TListItem; const AWatch: TIdeWatch);
155    procedure UpdateAll;
156    procedure DisableAllActions;
157    function  GetSelectedSnapshot: TSnapshot;
158    property Watches: TIdeWatches read GetWatches;
159  protected
160    procedure DoEndUpdate; override;
161    procedure DoWatchesChanged; override;
162    function  ColSizeGetter(AColId: Integer; var ASize: Integer): Boolean;
163    procedure ColSizeSetter(AColId: Integer; ASize: Integer);
164  public
165    constructor Create(AOwner: TComponent); override;
166    property WatchesMonitor;
167    property ThreadsMonitor;
168    property CallStackMonitor;
169    property BreakPoints;
170    property SnapshotManager;
171  end;
172
173
174implementation
175
176{$R *.lfm}
177
178var
179  DBG_DATA_MONITORS: PLazLoggerLogGroup;
180  WatchWindowCreator: TIDEWindowCreator;
181const
182  COL_WATCH_EXPR  = 1;
183  COL_WATCH_VALUE = 2;
184  COL_SPLITTER_INSPECT = 3;
185  COL_WIDTHS: Array[0..2] of integer = ( 100,  200, 200);
186
187function WatchesDlgColSizeGetter(AForm: TCustomForm; AColId: Integer; var ASize: Integer): Boolean;
188begin
189  Result := AForm is TWatchesDlg;
190  if Result then
191    Result := TWatchesDlg(AForm).ColSizeGetter(AColId, ASize);
192end;
193
194procedure WatchesDlgColSizeSetter(AForm: TCustomForm; AColId: Integer; ASize: Integer);
195begin
196  if AForm is TWatchesDlg then
197    TWatchesDlg(AForm).ColSizeSetter(AColId, ASize);
198end;
199
200{ TWatchesDlg }
201
202constructor TWatchesDlg.Create(AOwner: TComponent);
203begin
204  inherited Create(AOwner);
205  FWatchesInView := nil;
206  FStateFlags := [];
207  nbInspect.Visible := False;
208
209  WatchesNotification.OnAdd       := @WatchAdd;
210  WatchesNotification.OnUpdate    := @WatchUpdate;
211  WatchesNotification.OnRemove    := @WatchRemove;
212  ThreadsNotification.OnCurrent   := @ContextChanged;
213  CallstackNotification.OnCurrent := @ContextChanged;
214  SnapshotNotification.OnCurrent  := @SnapshotChanged;
215
216  ActionList1.Images := IDEImages.Images_16;
217  ToolBar1.Images := IDEImages.Images_16;
218  mnuPopup.Images := IDEImages.Images_16;
219
220  FPowerImgIdx := IDEImages.LoadImage('debugger_power');
221  FPowerImgIdxGrey := IDEImages.LoadImage('debugger_power_grey');
222  actPower.ImageIndex := FPowerImgIdx;
223  actPower.Caption := lisDbgWinPower;
224  actPower.Hint := lisDbgWinPowerHint;
225
226  actAddWatch.Caption:=lisBtnAdd;
227  actAddWatch.ImageIndex := IDEImages.LoadImage('laz_add');
228
229  actToggleCurrentEnable.Caption := lisBtnEnabled;
230
231  actEnableSelected.Caption := lisDbgItemEnable;
232  actEnableSelected.Hint    := lisDbgItemEnableHint;
233  actEnableSelected.ImageIndex := IDEImages.LoadImage('debugger_enable');
234
235  actDisableSelected.Caption := lisDbgItemDisable;
236  actDisableSelected.Hint    := lisDbgItemDisableHint;
237  actDisableSelected.ImageIndex := IDEImages.LoadImage('debugger_disable');
238
239  actDeleteSelected.Caption := lisBtnDelete;
240  actDeleteSelected.Hint    := lisDbgItemDeleteHint;
241  actDeleteSelected.ImageIndex := IDEImages.LoadImage('laz_delete');
242
243  actEnableAll.Caption := liswlENableAll; //lisDbgAllItemEnable;
244  actEnableAll.Hint    := lisDbgAllItemEnableHint;
245  actEnableAll.ImageIndex := IDEImages.LoadImage('debugger_enable_all');
246
247  actDisableAll.Caption := liswlDIsableAll; //lisDbgAllItemDisable;
248  actDisableAll.Hint    := lisDbgAllItemDisableHint;
249  actDisableAll.ImageIndex := IDEImages.LoadImage('debugger_disable_all');
250
251  actDeleteAll.Caption := liswlDeLeteAll; //lisDbgAllItemDelete;
252  actDeleteAll.Hint    := lisDbgAllItemDeleteHint;
253  actDeleteAll.ImageIndex := IDEImages.LoadImage('menu_clean');
254
255  actProperties.Caption:= liswlProperties;
256  actProperties.ImageIndex := IDEImages.LoadImage('menu_environment_options');
257
258  actToggleInspectSite.Caption:= liswlInspectPane;
259  actToggleInspectSite.ImageIndex := IDEImages.LoadImage('debugger_inspect');
260
261  actAddWatchPoint.Caption := lisWatchToWatchPoint;
262
263  actCopyName.Caption := lisLocalsDlgCopyName;
264  actCopyValue.Caption := lisLocalsDlgCopyValue;
265
266  actInspect.Caption := lisInspect;
267  actEvaluate.Caption := lisEvaluateModify;
268
269  Caption:=liswlWatchList;
270
271  lvWatches.Columns[0].Caption:=liswlExpression;
272  lvWatches.Columns[1].Caption:=dlgValueColor;
273
274  lvWatches.Column[0].Width := COL_WIDTHS[COL_WATCH_EXPR];
275  lvWatches.Column[1].Width := COL_WIDTHS[COL_WATCH_VALUE];
276end;
277
278function TWatchesDlg.GetSelected: TCurrentWatch;
279var
280  Item: TListItem;
281begin
282  Item := lvWatches.Selected;
283  if Item = nil
284  then Result := nil
285  else Result := TCurrentWatch(Item.Data);
286end;
287
288function TWatchesDlg.GetThreadId: Integer;
289var
290  Threads: TIdeThreads;
291begin
292  Result := -1;
293  if (ThreadsMonitor = nil) then exit;
294  Threads := GetSelectedThreads(GetSelectedSnapshot);
295  if Threads <> nil
296  then Result := Threads.CurrentThreadId
297  else Result := 1;
298end;
299
300function TWatchesDlg.GetSelectedThreads(Snap: TSnapshot): TIdeThreads;
301begin
302  if ThreadsMonitor = nil then exit(nil);
303  if Snap = nil
304  then Result := ThreadsMonitor.CurrentThreads
305  else Result := ThreadsMonitor.Snapshots[Snap];
306end;
307
308function TWatchesDlg.GetStackframe: Integer;
309var
310  Snap: TSnapshot;
311  Threads: TIdeThreads;
312  tid: LongInt;
313  Stack: TIdeCallStack;
314begin
315  if (CallStackMonitor = nil) or (ThreadsMonitor = nil)
316  then begin
317    Result := 0;
318    exit;
319  end;
320
321  Snap := GetSelectedSnapshot;
322  Threads := GetSelectedThreads(Snap);
323  if Threads <> nil
324  then tid := Threads.CurrentThreadId
325  else tid := 1;
326
327  if (Snap <> nil)
328  then Stack := CallStackMonitor.Snapshots[Snap].EntriesForThreads[tid]
329  else Stack := CallStackMonitor.CurrentCallStackList.EntriesForThreads[tid];
330
331  if Stack <> nil
332  then Result := Stack.CurrentIndex
333  else Result := 0;
334end;
335
336procedure TWatchesDlg.lvWatchesSelectItem(Sender: TObject; AItem: TListItem; Selected: Boolean);
337var
338  ItemSelected: Boolean;
339  Watch: TCurrentWatch;
340  SelCanEnable, SelCanDisable: Boolean;
341  AllCanEnable, AllCanDisable: Boolean;
342  i: Integer;
343begin
344  if FUpdatingAll then exit;
345  if GetSelectedSnapshot <> nil then begin
346    actToggleCurrentEnable.Enabled := False;
347    actToggleCurrentEnable.Checked := False;
348    actEnableSelected.Enabled := False;
349    actDisableSelected.Enabled := False;
350    actDeleteSelected.Enabled := False;
351    actEnableAll.Enabled := False;
352    actDisableAll.Enabled := False;
353    actDeleteAll.Enabled := False;
354    actProperties.Enabled := False;
355    actAddWatch.Enabled := False;
356    actPower.Enabled := False;
357    actAddWatchPoint.Enabled := False;
358    actEvaluate.Enabled := False;
359    actInspect.Enabled := False;
360    UpdateInspectPane;
361    exit;
362  end;
363
364  ItemSelected := lvWatches.Selected <> nil;
365  if ItemSelected then
366    Watch:=TCurrentWatch(lvWatches.Selected.Data)
367  else
368    Watch:=nil;
369  SelCanEnable := False;
370  SelCanDisable := False;
371  AllCanEnable := False;
372  AllCanDisable := False;
373  for i := 0 to lvWatches.Items.Count - 1 do begin
374    if lvWatches.Items[i].Data = nil then
375      continue;
376    if lvWatches.Items[i].Selected then begin
377      SelCanEnable := SelCanEnable or not TCurrentWatch(lvWatches.Items[i].Data).Enabled;
378      SelCanDisable := SelCanDisable or TCurrentWatch(lvWatches.Items[i].Data).Enabled;
379    end;
380    AllCanEnable := AllCanEnable or not TCurrentWatch(lvWatches.Items[i].Data).Enabled;
381    AllCanDisable := AllCanDisable or TCurrentWatch(lvWatches.Items[i].Data).Enabled;
382  end;
383
384  actToggleCurrentEnable.Enabled := ItemSelected;
385  actToggleCurrentEnable.Checked := ItemSelected and Watch.Enabled;
386
387  actEnableSelected.Enabled := SelCanEnable;
388  actDisableSelected.Enabled := SelCanDisable;
389  actDeleteSelected.Enabled := ItemSelected;
390
391  actAddWatchPoint.Enabled := ItemSelected;
392  actEvaluate.Enabled := ItemSelected;
393  actInspect.Enabled := ItemSelected;
394
395  actEnableAll.Enabled := AllCanEnable;
396  actDisableAll.Enabled := AllCanDisable;
397  actDeleteAll.Enabled := lvWatches.Items.Count > 0;
398
399  actCopyName.Enabled := ItemSelected;
400  actCopyValue.Enabled := ItemSelected;
401
402  actProperties.Enabled := ItemSelected;
403  actAddWatch.Enabled := True;
404  actPower.Enabled := True;
405
406  actToggleInspectSite.Enabled := True;
407
408  UpdateInspectPane;
409end;
410
411procedure TWatchesDlg.lvWatchesDblClick(Sender: TObject);
412begin
413  if GetSelectedSnapshot <> nil then exit;
414  if lvWatches.SelCount >= 0 then
415    popPropertiesClick(Sender)
416  else
417    popAddClick(Sender);
418end;
419
420procedure TWatchesDlg.lvWatchesDragDrop(Sender, Source: TObject; X, Y: Integer);
421var
422  s: String;
423  NewWatch: TCurrentWatch;
424begin
425  s := '';
426  if (Source is TSynEdit) then s := TSynEdit(Source).SelText;
427  if (Source is TCustomEdit) then s := TCustomEdit(Source).SelText;
428
429  if s <> '' then begin
430    NewWatch := DebugBoss.Watches.CurrentWatches.Add(s);
431    NewWatch.DisplayFormat := wdfDefault;
432    NewWatch.EvaluateFlags := [defClassAutoCast];
433    NewWatch.Enabled       := True;
434  end;
435end;
436
437procedure TWatchesDlg.lvWatchesDragOver(Sender, Source: TObject; X, Y: Integer;
438  State: TDragState; var Accept: Boolean);
439begin
440  Accept := ( (Source is TSynEdit) and (TSynEdit(Source).SelAvail) ) or
441            ( (Source is TCustomEdit) and (TCustomEdit(Source).SelText <> '') );
442end;
443
444procedure TWatchesDlg.FormDestroy(Sender: TObject);
445begin
446  //DebugLn('TWatchesDlg.FormDestroy ',DbgSName(Self));
447end;
448
449procedure TWatchesDlg.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
450var
451  s: String;
452  NewWatch: TCurrentWatch;
453begin
454  if (Shift * [ssShift, ssAlt, ssAltGr, ssCtrl] = [ssCtrl]) and (Key = VK_V)
455  then begin
456    Key := 0;
457    s := Clipboard.AsText;
458    if s <> '' then begin
459      NewWatch := DebugBoss.Watches.CurrentWatches.Add(s);
460      NewWatch.DisplayFormat := wdfDefault;
461      NewWatch.EvaluateFlags := [defClassAutoCast];
462      NewWatch.Enabled       := True;
463    end;
464
465    exit;
466  end;
467
468  inherited FormKeyDown(Sender, Key, Shift);
469end;
470
471procedure TWatchesDlg.FormShow(Sender: TObject);
472begin
473  UpdateAll;
474end;
475
476procedure TWatchesDlg.actPowerExecute(Sender: TObject);
477begin
478  if ToolButtonPower.Down
479  then begin
480    actPower.ImageIndex := FPowerImgIdx;
481    ToolButtonPower.ImageIndex := FPowerImgIdx;
482    UpdateAll;
483  end
484  else begin
485    actPower.ImageIndex := FPowerImgIdxGrey;
486    ToolButtonPower.ImageIndex := FPowerImgIdxGrey;
487  end;
488end;
489
490procedure TWatchesDlg.actToggleInspectSiteExecute(Sender: TObject);
491begin
492  InspectSplitter.Visible := ToolButtonInspectView.Down;
493  nbInspect.Visible := ToolButtonInspectView.Down;
494  InspectSplitter.Left :=  nbInspect.Left - 1;
495  if ToolButtonInspectView.Down then
496    UpdateInspectPane;
497end;
498
499procedure TWatchesDlg.ContextChanged(Sender: TObject);
500begin
501  DebugLn(DBG_DATA_MONITORS, ['DebugDataWindow: TWatchesDlg.ContextChanged ',  DbgSName(Sender), '  Upd:', IsUpdating]);
502  UpdateAll;
503end;
504
505procedure TWatchesDlg.actEnableSelectedExecute(Sender: TObject);
506var
507  n: Integer;
508  Item: TListItem;
509begin
510  try
511    DisableAllActions;
512    for n := 0 to lvWatches.Items.Count -1 do
513    begin
514      Item := lvWatches.Items[n];
515      if Item.Selected then
516        TCurrentWatch(Item.Data).Enabled := True;
517    end;
518  finally
519    lvWatchesSelectItem(nil, nil, False);
520  end;
521end;
522
523procedure TWatchesDlg.actEvaluateExecute(Sender: TObject);
524begin
525  DebugBoss.EvaluateModify(lvWatches.Selected.Caption);
526end;
527
528procedure TWatchesDlg.actInspectExecute(Sender: TObject);
529begin
530  DebugBoss.Inspect(lvWatches.Selected.Caption);
531end;
532
533procedure TWatchesDlg.actDisableSelectedExecute(Sender: TObject);
534var
535  n: Integer;
536  Item: TListItem;
537begin
538  try
539    DisableAllActions;
540    for n := 0 to lvWatches.Items.Count -1 do
541    begin
542      Item := lvWatches.Items[n];
543      if Item.Selected then
544        TCurrentWatch(Item.Data).Enabled := False;
545    end;
546  finally
547    lvWatchesSelectItem(nil, nil, False);
548  end;
549end;
550
551procedure TWatchesDlg.actAddWatchPointExecute(Sender: TObject);
552var
553  NewBreakpoint: TIDEBreakPoint;
554  Watch: TCurrentWatch;
555begin
556  Watch := GetSelected;
557  if Watch = nil then Exit;
558  NewBreakpoint := BreakPoints.Add(Watch.Expression, wpsGlobal, wpkWrite);
559  if DebugBoss.ShowBreakPointProperties(NewBreakpoint) <> mrOk then
560    ReleaseRefAndNil(NewBreakpoint);
561end;
562
563procedure TWatchesDlg.actCopyNameExecute(Sender: TObject);
564begin
565  Clipboard.Open;
566  Clipboard.AsText := lvWatches.Selected.Caption;
567  Clipboard.Close;
568end;
569
570procedure TWatchesDlg.actCopyValueExecute(Sender: TObject);
571begin
572  Clipboard.Open;
573  Clipboard.AsText := lvWatches.Selected.SubItems[0];
574  Clipboard.Close;
575end;
576
577procedure TWatchesDlg.popAddClick(Sender: TObject);
578begin
579  try
580    DisableAllActions;
581    DebugBoss.ShowWatchProperties(nil);
582  finally
583    lvWatchesSelectItem(nil, nil, False);
584  end;
585end;
586
587procedure TWatchesDlg.popDeleteAllClick(Sender: TObject);
588var
589  n: Integer;
590begin
591  Include(FStateFlags, wdsfNeedDeleteAll);
592  if wdsfUpdating in FStateFlags then exit;
593  Exclude(FStateFlags, wdsfNeedDeleteAll);
594  try
595    DisableAllActions;
596    for n := lvWatches.Items.Count - 1 downto 0 do
597      TCurrentWatch(lvWatches.Items[n].Data).Free;
598  finally
599    lvWatchesSelectItem(nil, nil, False);
600  end;
601end;
602
603procedure TWatchesDlg.SnapshotChanged(Sender: TObject);
604var
605  NewWatches: TIdeWatches;
606begin
607  DebugLn(DBG_DATA_MONITORS, ['DebugDataWindow: TWatchesDlg.SnapshotChanged ',  DbgSName(Sender), '  Upd:', IsUpdating]);
608  lvWatches.BeginUpdate;
609  // prevent lvWatchesSelectItem when deleting the itews. Snapsot may have been deleted
610  FUpdatingAll := True; // will be reset by UpdateAll
611  try
612    NewWatches := Watches;
613    if FWatchesInView <> NewWatches
614    then lvWatches.Items.Clear;
615    FWatchesInView := NewWatches;
616    UpdateAll;
617  finally
618    FUpdatingAll := False; // wan reset by UpdateAll anyway
619    lvWatches.EndUpdate;
620  end;
621end;
622
623function TWatchesDlg.GetWatches: TIdeWatches;
624var
625  Snap: TSnapshot;
626begin
627  Result := nil;
628  if WatchesMonitor = nil then exit;
629
630  Snap := GetSelectedSnapshot;
631
632  if Snap <> nil
633  then Result := WatchesMonitor.Snapshots[Snap]
634  else Result := WatchesMonitor.CurrentWatches;
635end;
636
637procedure TWatchesDlg.DoEndUpdate;
638begin
639  inherited DoEndUpdate;
640  if FUpdateAllNeeded then begin
641    FUpdateAllNeeded := False;
642    UpdateAll;
643  end;
644end;
645
646procedure TWatchesDlg.DoWatchesChanged;
647begin
648  UpdateAll;
649end;
650
651function TWatchesDlg.ColSizeGetter(AColId: Integer; var ASize: Integer): Boolean;
652begin
653  if AColId = COL_SPLITTER_INSPECT then begin
654    ASize := nbInspect.Width;
655    Result := ASize <> COL_WIDTHS[AColId - 1];
656  end
657  else
658  if (AColId - 1 >= 0) and (AColId - 1 < lvWatches.ColumnCount) then begin
659    ASize := lvWatches.Column[AColId - 1].Width;
660    Result := ASize <> COL_WIDTHS[AColId - 1];
661  end
662  else
663    Result := False;
664end;
665
666procedure TWatchesDlg.ColSizeSetter(AColId: Integer; ASize: Integer);
667begin
668  case AColId of
669    COL_WATCH_EXPR:  lvWatches.Column[0].Width := ASize;
670    COL_WATCH_VALUE: lvWatches.Column[1].Width := ASize;
671    COL_SPLITTER_INSPECT: nbInspect.Width := ASize;
672  end;
673end;
674
675procedure TWatchesDlg.popDeleteClick(Sender: TObject);
676var
677  Item: TCurrentWatch;
678begin
679  Include(FStateFlags, wdsfNeedDeleteCurrent);
680  if (wdsfUpdating in FStateFlags) then exit;
681  Exclude(FStateFlags, wdsfNeedDeleteCurrent);
682  try
683    DisableAllActions;
684    repeat
685      Item := GetSelected;
686      Item.Free;
687    until Item = nil;
688    //GetSelected.Free;
689  finally
690    lvWatchesSelectItem(nil, nil, False);
691  end;
692end;
693
694procedure TWatchesDlg.popDisableAllClick(Sender: TObject);
695var
696  n: Integer;
697  Item: TListItem;
698begin
699  try
700    DisableAllActions;
701    for n := 0 to lvWatches.Items.Count - 1 do
702    begin
703      Item := lvWatches.Items[n];
704      if Item.Data <> nil
705      then TCurrentWatch(Item.Data).Enabled := False;
706    end;
707  finally
708    lvWatchesSelectItem(nil, nil, False);
709  end;
710end;
711
712procedure TWatchesDlg.popEnableAllClick(Sender: TObject);
713var
714  n: Integer;
715  Item: TListItem;
716begin
717  try
718    DisableAllActions;
719    for n := 0 to lvWatches.Items.Count - 1 do
720    begin
721      Item := lvWatches.Items[n];
722      if Item.Data <> nil
723      then TCurrentWatch(Item.Data).Enabled := True;
724    end;
725  finally
726    lvWatchesSelectItem(nil, nil, False);
727  end;
728end;
729
730procedure TWatchesDlg.popEnabledClick(Sender: TObject);
731var
732  Watch: TCurrentWatch;
733begin
734  try
735    DisableAllActions;
736    Watch := GetSelected;
737    if Watch = nil then Exit;
738    popEnabled.Checked := not popEnabled.Checked;
739    Watch.Enabled := popEnabled.Checked;
740  finally
741    lvWatchesSelectItem(nil, nil, False);
742  end;
743end;
744
745procedure TWatchesDlg.popPropertiesClick(Sender: TObject);
746begin
747  try
748    DisableAllActions;
749    DebugBoss.ShowWatchProperties(GetSelected);
750  finally
751    lvWatchesSelectItem(nil, nil, False);
752  end;
753end;
754
755procedure TWatchesDlg.UpdateInspectPane;
756var
757  Watch: TCurrentWatch;
758  i: integer;
759  d: TIdeWatchValue;
760  t: TDBGType;
761begin
762  if not nbInspect.Visible then exit;
763  DebugBoss.LockCommandProcessing;
764  try
765
766  InspectMemo.Clear;
767  InspectLabel.Caption := '...';
768  if (lvWatches.Selected = nil) then begin
769    exit;
770  end;
771
772  Watch:=TCurrentWatch(lvWatches.Selected.Data);
773  InspectLabel.Caption := Watch.Expression;
774
775  d := Watch.Values[GetThreadId, GetStackframe];
776  if d = nil then begin
777    InspectMemo.WordWrap := True;
778    InspectMemo.Text := '<evaluating>';
779    exit;
780  end;
781
782  t := d.TypeInfo;
783
784  if (t <> nil) and (t.Fields <> nil) and (t.Fields.Count > 0) and
785     (d.DisplayFormat in [wdfDefault, wdfStructure])
786  then begin
787    InspectMemo.WordWrap := False;
788    InspectMemo.Lines.BeginUpdate;
789    try
790      for i := 0 to t.Fields.Count - 1 do
791        case t.Fields[i].DBGType.Kind of
792          skSimple:
793            begin
794              if t.Fields[i].DBGType.Value.AsString='$0' then begin
795                if t.Fields[i].DBGType.TypeName='ANSISTRING' then
796                  InspectMemo.Append(t.Fields[i].Name + ': ''''')
797                else
798                  InspectMemo.Append(t.Fields[i].Name + ': nil');
799              end
800              else
801                InspectMemo.Append(t.Fields[i].Name + ': ' + t.Fields[i].DBGType.Value.AsString);
802            end;
803          skProcedure, skFunction: ;
804          else
805            InspectMemo.Append(t.Fields[i].Name + ': ' + t.Fields[i].DBGType.Value.AsString);
806        end;
807    finally
808      InspectMemo.Lines.EndUpdate;
809    end;
810    exit;
811  end;
812
813  InspectMemo.WordWrap := True;
814  InspectMemo.Text := d.Value;
815  finally
816    DebugBoss.UnLockCommandProcessing;
817  end;
818end;
819
820procedure TWatchesDlg.UpdateItem(const AItem: TListItem; const AWatch: TIdeWatch);
821  function ClearMultiline(const AValue: ansistring): ansistring;
822  var
823    j: SizeInt;
824    ow: SizeInt;
825    NewLine: Boolean;
826  begin
827    ow:=0;
828    SetLength(Result,Length(AValue));
829    NewLine:=true;
830    for j := 1 to Length(AValue) do begin
831      if (AValue[j]=#13) or (AValue[j]=#10) then begin
832        NewLine:=true;
833        inc(ow);
834        Result[ow]:=#32; // insert one space instead of new line
835      end
836      else if Avalue[j] in [#9,#32] then begin
837        if not NewLine then begin // strip leading spaces after new line
838          inc(ow);
839          Result[ow]:=#32;
840        end;
841      end else begin
842        inc(ow);
843        Result[ow]:=AValue[j];
844        NewLine:=false;
845      end;
846    end;
847    If ow>255 then begin
848      //Limit watch to 255 chars in length
849      Result:=Copy(Result,1,252)+'...';
850    end else begin
851      SetLength(Result,ow);
852    end;
853  end;
854var
855  WatchValue: TIdeWatchValue;
856  WatchValueStr: string;
857begin
858  DebugBoss.LockCommandProcessing;
859  try
860// Expression
861// Result
862  if (not ToolButtonPower.Down) or (not Visible) then exit;
863  if (ThreadsMonitor = nil) or (CallStackMonitor = nil) then exit;
864  if GetStackframe < 0 then exit; // TODO need dedicated validity property
865
866  include(FStateFlags, wdsfUpdating);
867  AItem.Caption := AWatch.Expression;
868  WatchValue := AWatch.Values[GetThreadId, GetStackframe];
869  if (WatchValue <> nil) and
870     ( (GetSelectedSnapshot = nil) or not(WatchValue.Validity in [ddsUnknown, ddsEvaluating, ddsRequested]) )
871  then begin
872    WatchValueStr := ClearMultiline(DebugBoss.FormatValue(WatchValue.TypeInfo, WatchValue.Value));
873    if (WatchValue.TypeInfo <> nil) and
874       (WatchValue.TypeInfo.Attributes * [saArray, saDynArray] <> []) and
875       (WatchValue.TypeInfo.Len >= 0)
876    then AItem.SubItems[0] := Format(drsLen, [WatchValue.TypeInfo.Len]) + WatchValueStr
877    else AItem.SubItems[0] := WatchValueStr;
878  end
879  else
880    AItem.SubItems[0] := '<not evaluated>';
881  exclude(FStateFlags, wdsfUpdating);
882  if wdsfNeedDeleteCurrent in FStateFlags then
883    popDeleteClick(nil);
884  if wdsfNeedDeleteAll in FStateFlags then
885    popDeleteAllClick(nil);
886  finally
887    DebugBoss.UnLockCommandProcessing;
888  end;
889end;
890
891procedure TWatchesDlg.UpdateAll;
892var
893  i, l: Integer;
894  Snap: TSnapshot;
895begin
896  if Watches = nil then exit;
897  if IsUpdating then begin
898    DebugLn(DBG_DATA_MONITORS, ['DebugDataWindow: TWatchesDlg.UpdateAll: TWatchesDlg.UpdateAll  in IsUpdating:']);
899    FUpdateAllNeeded := True;
900    exit;
901  end;
902  try DebugLnEnter(DBG_DATA_MONITORS, ['DebugDataWindow: TWatchesDlg.UpdateAll: >>ENTER: TWatchesDlg.UpdateAll ']);
903
904  Snap := GetSelectedSnapshot;
905  if Snap <> nil
906  then Caption:= liswlWatchList + ' (' + Snap.LocationAsText + ')'
907  else Caption:= liswlWatchList;
908
909  FUpdatingAll := True;
910  DebugBoss.LockCommandProcessing;
911  lvWatches.BeginUpdate;
912  try
913    l := Watches.Count;
914    i := 0;
915    while i < l do begin
916      WatchUpdate(Watches, Watches.Items[i]);
917      if l <> Watches.Count then begin
918        i := Max(0, i - Max(0, Watches.Count - l));
919        l := Watches.Count;
920      end;
921      inc(i);
922    end;
923  finally
924    FUpdatingAll := False;
925    lvWatches.EndUpdate;
926    DebugBoss.UnLockCommandProcessing;
927    lvWatchesSelectItem(nil, nil, False);
928  end;
929  finally DebugLnExit(DBG_DATA_MONITORS, ['DebugDataWindow: TWatchesDlg.UpdateAll: <<EXIT: TWatchesDlg.UpdateAll ']); end;
930end;
931
932procedure TWatchesDlg.DisableAllActions;
933var
934  i: Integer;
935begin
936  for i := 0 to ActionList1.ActionCount - 1 do
937    (ActionList1.Actions[i] as TAction).Enabled := False;
938end;
939
940function TWatchesDlg.GetSelectedSnapshot: TSnapshot;
941begin
942  Result := nil;
943  if (SnapshotManager <> nil) and (SnapshotManager.SelectedEntry <> nil)
944  then Result := SnapshotManager.SelectedEntry;
945end;
946
947procedure TWatchesDlg.WatchAdd(const ASender: TIdeWatches; const AWatch: TIdeWatch);
948var
949  Item: TListItem;
950begin
951  Item := lvWatches.Items.FindData(AWatch);
952  if Item = nil
953  then begin
954    Item := lvWatches.Items.Add;
955    Item.Data := AWatch;
956    Item.SubItems.Add('');
957    Item.Selected := True;
958  end;
959
960  UpdateItem(Item, AWatch);
961  lvWatchesSelectItem(nil, nil, False);
962end;
963
964procedure TWatchesDlg.WatchUpdate(const ASender: TIdeWatches; const AWatch: TIdeWatch);
965var
966  Item: TListItem;
967begin
968  if AWatch = nil then Exit; // TODO: update all
969  if AWatch.Collection <> FWatchesInView then exit;
970  try DebugLnEnter(DBG_DATA_MONITORS, ['DebugDataWindow: TWatchesDlg.WatchUpdate  Upd:', IsUpdating, '  Watch=',AWatch.Expression]);
971
972  Item := lvWatches.Items.FindData(AWatch);
973  if Item = nil
974  then WatchAdd(ASender, AWatch)
975  else UpdateItem(Item, AWatch);
976
977  // TODO: if AWatch <> Selected, then prevent UpdateInspectPane
978  // Selected may update later, and calling UpdateInspectPane will schedule an unsucesful attemptn to fetch data
979  if not FUpdatingAll
980  then lvWatchesSelectItem(nil, nil, False);
981  finally DebugLnExit(DBG_DATA_MONITORS, ['DebugDataWindow: TWatchesDlg.WatchUpdate']); end;
982end;
983
984procedure TWatchesDlg.WatchRemove(const ASender: TIdeWatches; const AWatch: TIdeWatch);
985begin
986  lvWatches.Items.FindData(AWatch).Free;
987  lvWatchesSelectItem(nil, nil, False);
988end;
989
990initialization
991
992  WatchWindowCreator := IDEWindowCreators.Add(DebugDialogNames[ddtWatches]);
993  WatchWindowCreator.OnCreateFormProc := @CreateDebugDialog;
994  WatchWindowCreator.OnSetDividerSize := @WatchesDlgColSizeSetter;
995  WatchWindowCreator.OnGetDividerSize := @WatchesDlgColSizeGetter;
996  WatchWindowCreator.DividerTemplate.Add('ColumnWatchExpr',  COL_WATCH_EXPR,  @drsColWidthExpression);
997  WatchWindowCreator.DividerTemplate.Add('ColumnWatchValue', COL_WATCH_VALUE, @drsColWidthValue);
998  WatchWindowCreator.DividerTemplate.Add('SplitterWatchInspect', COL_SPLITTER_INSPECT, @drsWatchSplitterInspect);
999  WatchWindowCreator.CreateSimpleLayout;
1000
1001  DBG_DATA_MONITORS := DebugLogger.FindOrRegisterLogGroup('DBG_DATA_MONITORS' {$IFDEF DBG_DATA_MONITORS} , True {$ENDIF} );
1002
1003end.
1004
1005