unit fViewOperations; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Forms, Controls, Graphics, StdCtrls, ExtCtrls, LCLType, ComCtrls, Buttons, LCLIntf, Menus, uFileSourceOperation, uOperationsManager, Themes; type TViewBaseItem = class; TViewBaseItemClick = procedure(Item: TViewBaseItem; Button: TMouseButton; Shift: TShiftState; const Pt: TPoint) of object; TViewBaseItemContextMenu = procedure(Item: TViewBaseItem; const Point: TPoint) of object; TViewBaseItemSelected = procedure(Item: TViewBaseItem) of object; TViewOperationsStatusIcon = (vosiPlay, vosiPause, vosiHourglass); { TViewBaseItem } TViewBaseItem = class private FOnClick: TViewBaseItemClick; FOnContextMenu: TViewBaseItemContextMenu; FOnSelected: TViewBaseItemSelected; FTreeNode: TTreeNode; FText: String; FTextRect: TRect; procedure CalculateSizes(Canvas: TCanvas; NeedsStatusIcon, NeedsProgress: Boolean); procedure DrawProgress(Canvas: TCanvas; NodeRect: TRect; Progress: Double); procedure DrawStatusIcon(Canvas: TCanvas; NodeRect: TRect; Icon: TViewOperationsStatusIcon); procedure DrawThemedBackground(Canvas: TCanvas; Element: TThemedTreeview; ARect: TRect); procedure DrawThemedText(Canvas: TCanvas; Element: TThemedTreeview; NodeRect: TRect; Center: Boolean; AText: String); function GetStatusIconRect(NodeRect: TRect): TRect; function GetTextIndent: Integer; virtual; abstract; procedure UpdateView(Canvas: TCanvas); virtual; abstract; public constructor Create(ANode: TTreeNode); virtual; procedure Click(const Pt: TPoint; Button: TMouseButton; Shift: TShiftState); virtual; procedure Draw(Canvas: TCanvas; NodeRect: TRect); virtual; abstract; function GetBackgroundColor: TColor; virtual; abstract; procedure KeyDown(var Key: Word; Shift: TShiftState); virtual; procedure Selected; virtual; procedure StartPause; virtual; abstract; procedure Stop; virtual; abstract; property OnClick: TViewBaseItemClick read FOnClick write FOnClick; property OnContextMenu: TViewBaseItemContextMenu read FOnContextMenu write FOnContextMenu; property OnSelected: TViewBaseItemSelected read FOnSelected write FOnSelected; end; { TViewQueueItem } TViewQueueItem = class(TViewBaseItem) private FQueueIdentifier: TOperationsManagerQueueIdentifier; function GetTextIndent: Integer; override; procedure UpdateView(Canvas: TCanvas); override; public constructor Create(ANode: TTreeNode; AQueueId: TOperationsManagerQueueIdentifier); reintroduce; procedure Click(const Pt: TPoint; Button: TMouseButton; Shift: TShiftState); override; procedure Draw(Canvas: TCanvas; NodeRect: TRect); override; function GetBackgroundColor: TColor; override; procedure StartPause; override; procedure Stop; override; end; TViewOperationItem = class; { TViewOperationItem } TViewOperationItem = class(TViewBaseItem) private FOperationHandle: TOperationHandle; FProgress: Double; function GetTextIndent: Integer; override; procedure UpdateView(Canvas: TCanvas); override; public constructor Create(ANode: TTreeNode; AOperationHandle: TOperationHandle); reintroduce; procedure Click(const Pt: TPoint; Button: TMouseButton; Shift: TShiftState); override; procedure Draw(Canvas: TCanvas; NodeRect: TRect); override; function GetBackgroundColor: TColor; override; procedure StartPause; override; procedure Stop; override; end; { TfrmViewOperations } TfrmViewOperations = class(TForm) btnStop: TBitBtn; btnStartPause: TBitBtn; cbAlwaysOnTop: TCheckBox; lblUseDragDrop: TLabel; mnuCancelQueue: TMenuItem; mnuNewQueue: TMenuItem; mnuCancelOperation: TMenuItem; mnuPutFirstInQueue: TMenuItem; mnuPutLastInQueue: TMenuItem; mnuOperationShowDetached: TMenuItem; mnuQueue2: TMenuItem; mnuQueue3: TMenuItem; mnuQueue5: TMenuItem; mnuQueue4: TMenuItem; mnuQueue1: TMenuItem; mnuQueue0: TMenuItem; mnuQueue: TMenuItem; mnuQueueShowDetached: TMenuItem; pnlHeader: TPanel; pmQueuePopup: TPopupMenu; pnlTopHeader: TPanel; pmOperationPopup: TPopupMenu; tbPauseAll: TToggleBox; tvOperations: TTreeView; UpdateTimer: TTimer; procedure btnStopClick(Sender: TObject); procedure cbAlwaysOnTopChange(Sender: TObject); procedure FormClose(Sender: TObject; var CloseAction: TCloseAction); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure mnuCancelOperationClick(Sender: TObject); procedure mnuCancelQueueClick(Sender: TObject); procedure mnuNewQueueClick(Sender: TObject); procedure mnuPutFirstInQueueClick(Sender: TObject); procedure mnuPutLastInQueueClick(Sender: TObject); procedure mnuOperationShowDetachedClick(Sender: TObject); procedure mnuQueueShowDetachedClick(Sender: TObject); procedure OnOperationItemContextMenu(Item: TViewBaseItem; const Point: TPoint); procedure OnOperationItemSelected(Item: TViewBaseItem); procedure OnQueueContextMenu(Item: TViewBaseItem; const Point: TPoint); procedure OnQueueItemSelected(Item: TViewBaseItem); procedure OnUpdateTimer(Sender: TObject); procedure btnStartPauseClick(Sender: TObject); procedure mnuQueueNumberClick(Sender: TObject); procedure tbPauseAllChange(Sender: TObject); procedure tvOperationsCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean); procedure tvOperationsDeletion(Sender: TObject; Node: TTreeNode); procedure tvOperationsDragDrop(Sender, Source: TObject; X, Y: Integer); procedure tvOperationsDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure tvOperationsKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure tvOperationsMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure tvOperationsResize(Sender: TObject); procedure tvOperationsSelectionChanged(Sender: TObject); private FDraggedOperation: TOperationHandle; FMenuOperation: TOperationHandle; FMenuQueueIdentifier: TOperationsManagerQueueIdentifier; procedure CreateNodes; function GetFocusedItem: TViewBaseItem; procedure MoveWithinQueue(MoveToTop: Boolean); procedure SetFocusItem(AOperationHandle: TOperationHandle); procedure SetFocusItem(AQueueIdentifier: TOperationsManagerQueueIdentifier); procedure SetNewQueue(Item: TViewOperationItem; NewQueue: TOperationsManagerQueueIdentifier); procedure SetStartPauseCaption(SetPause: Boolean); procedure UpdateView(Item: TOperationsManagerItem; Event: TOperationManagerEvent); procedure UpdateSizes; end; procedure ShowOperationsViewer; procedure ShowOperationsViewer(AOperationHandle: TOperationHandle); procedure ShowOperationsViewer(AQueueIdentifier: TOperationsManagerQueueIdentifier); implementation {$R *.lfm} uses GraphMath, GraphType, Math, fFileOpDlg, uLng, uGlobs, uFileSourceOperationMisc; const ExpandSignSize = 9; StatusIconPlay: array[0..2] of TPoint = ((x: 5; y: 1), (x: 13; y: 9), (x: 5; y: 17)); StatusIconPause1: array[0..3] of TPoint = ((x: 3; y: 2), (x: 3; y: 16), (x: 7; y: 16), (x: 7; y: 2)); StatusIconPause2: array[0..3] of TPoint = ((x: 10; y: 2), (x: 10; y: 16), (x: 14; y: 16), (x: 14; y: 2)); StatusIconHourglass1: array[0..2] of TPoint = ((x: 3; y: 4), (x: 13; y: 4), (x: 8; y: 10)); StatusIconHourglass2: array[0..2] of TPoint = ((x: 8; y: 10), (x: 13; y: 15), (x: 3; y: 15)); StatusIconFrame: TRect = (Left: 0; Top: 0; Right: 18; Bottom: 19); StatusIconRightMargin = 5; ProgressHeight = 16; ProgressWidth = 150; ProgressHorizontalMargin = 5; FreeOperationTextIndent = 3; MarginTopBottom = 2; var frmViewOperations: TfrmViewOperations = nil; ProgressRight: Integer; procedure ShowOperationsViewer; begin if not Assigned(frmViewOperations) then frmViewOperations := TfrmViewOperations.Create(Application); frmViewOperations.ShowOnTop; end; procedure ShowOperationsViewer(AOperationHandle: TOperationHandle); begin ShowOperationsViewer; if AOperationHandle <> InvalidOperationHandle then frmViewOperations.SetFocusItem(AOperationHandle); end; procedure ShowOperationsViewer(AQueueIdentifier: TOperationsManagerQueueIdentifier); begin ShowOperationsViewer; frmViewOperations.SetFocusItem(AQueueIdentifier); end; procedure ApplyProgress(var ARect: TRect; Progress: Double); begin ARect.Right := ARect.Left + Round((ARect.Right - ARect.Left) * Progress); end; function MoveRect(aRect: TRect; DeltaX, DeltaY: Integer): TRect; begin Result.Left := aRect.Left + DeltaX; Result.Right := aRect.Right + DeltaX; Result.Top := aRect.Top + DeltaY; Result.Bottom := aRect.Bottom + DeltaY; end; procedure DrawMovePolygon(Canvas: TCanvas; const Points: array of TPoint; DeltaX, DeltaY: Integer); var CopyPoints: PPoint; i: Integer; begin CopyPoints := GetMem(SizeOf(TPoint) * Length(Points)); for i := 0 to Length(Points) - 1 do begin CopyPoints[i].x := Points[i].x + DeltaX; CopyPoints[i].y := Points[i].y + DeltaY; end; Canvas.Polygon(CopyPoints, Length(Points), True); FreeMem(CopyPoints); end; { TViewBaseItem } procedure TViewBaseItem.DrawThemedBackground(Canvas: TCanvas; Element: TThemedTreeview; ARect: TRect); var Details: TThemedElementDetails; begin Details := ThemeServices.GetElementDetails(Element); if ThemeServices.HasTransparentParts(Details) then begin Canvas.Brush.Color := GetBackgroundColor; Canvas.FillRect(ARect); end; ThemeServices.DrawElement(Canvas.Handle, Details, ARect, nil); end; procedure TViewBaseItem.DrawProgress(Canvas: TCanvas; NodeRect: TRect; Progress: Double); var Details: TThemedElementDetails; begin if Progress > 0 then begin NodeRect.Right := NodeRect.Right - ProgressRight; NodeRect.Left := NodeRect.Right - ProgressWidth; NodeRect.Top := NodeRect.Top + (NodeRect.Bottom - NodeRect.Top - ProgressHeight) div 2; NodeRect.Bottom := NodeRect.Top + ProgressHeight; if ThemeServices.ThemesEnabled then begin Details := ThemeServices.GetElementDetails(tpBar); ThemeServices.DrawElement(Canvas.Handle, Details, NodeRect, nil); Details := ThemeServices.GetElementDetails(tpChunk); InflateRect(NodeRect, -2, -2); ApplyProgress(NodeRect, Progress); ThemeServices.DrawElement(Canvas.Handle, Details, NodeRect, nil); end else begin Canvas.Pen.Color := clWindowText; Canvas.Brush.Color := clForm; Canvas.RoundRect(NodeRect, 3, 3); Canvas.Brush.Color := clHighlight; ApplyProgress(NodeRect, Progress); Canvas.RoundRect(NodeRect, 3, 3); end; end; end; procedure TViewBaseItem.DrawStatusIcon(Canvas: TCanvas; NodeRect: TRect; Icon: TViewOperationsStatusIcon); var IconRect: TRect; begin Canvas.Brush.Color := GetBackgroundColor; Canvas.Pen.Color := clWindowText; IconRect := MoveRect(GetStatusIconRect(NodeRect), NodeRect.Left, NodeRect.Top); Canvas.Rectangle(IconRect); case Icon of vosiPlay: // Paint "Play" triangle begin Canvas.Brush.Color := RGBToColor(0, 200, 0); DrawMovePolygon(Canvas, StatusIconPlay, IconRect.Left, IconRect.Top); end; vosiPause: // Paint "Pause" double line begin Canvas.Brush.Color := RGBToColor(0, 0, 200); DrawMovePolygon(Canvas, StatusIconPause1, IconRect.Left, IconRect.Top); DrawMovePolygon(Canvas, StatusIconPause2, IconRect.Left, IconRect.Top); end; else // Paint "Hourglass" begin Canvas.Brush.Color := RGBToColor(255, 255, 255); DrawMovePolygon(Canvas, StatusIconHourglass1, IconRect.Left, IconRect.Top); DrawMovePolygon(Canvas, StatusIconHourglass2, IconRect.Left, IconRect.Top); end; end; end; procedure TViewBaseItem.DrawThemedText(Canvas: TCanvas; Element: TThemedTreeview; NodeRect: TRect; Center: Boolean; AText: String); var Details: TThemedElementDetails; Flags: Cardinal = DT_WORDBREAK or DT_NOPREFIX; begin Details := ThemeServices.GetElementDetails(Element); if Center then Flags := Flags + DT_VCENTER; ThemeServices.DrawText(Canvas, Details, AText, NodeRect, Flags, 0); end; function TViewBaseItem.GetStatusIconRect(NodeRect: TRect): TRect; begin Result := MoveRect(StatusIconFrame, (NodeRect.Right - NodeRect.Left) - (StatusIconFrame.Right - StatusIconFrame.Left) - StatusIconRightMargin, ((NodeRect.Bottom - NodeRect.Top) - (StatusIconFrame.Bottom - StatusIconFrame.Top)) div 2); end; constructor TViewBaseItem.Create(ANode: TTreeNode); begin FTreeNode := ANode; end; procedure TViewBaseItem.CalculateSizes(Canvas: TCanvas; NeedsStatusIcon, NeedsProgress: Boolean); var NodeRect: TRect; NeededHeight: Integer; begin // Calculate available width for text. NodeRect := FTreeNode.DisplayRect(False); FTextRect := Rect(0, 0, 0, 0); FTextRect.Right := (NodeRect.Right - NodeRect.Left) - GetTextIndent - (ProgressRight + ProgressWidth + ProgressHorizontalMargin); // Calculate text height. DrawText(Canvas.Handle, PChar(FText), Length(FText), FTextRect, DT_NOPREFIX + DT_CALCRECT + DT_WORDBREAK); // Take max of text, progress and status icon. NeededHeight := FTextRect.Bottom - FTextRect.Top; if NeedsProgress then NeededHeight := Max(NeededHeight, ProgressHeight); if NeedsStatusIcon then NeededHeight := Max(NeededHeight, StatusIconFrame.Bottom - StatusIconFrame.Top); Inc(NeededHeight, 2 * MarginTopBottom); FTextRect := MoveRect(FTextRect, NodeRect.Left + GetTextIndent, NodeRect.Top); FTreeNode.Height := NeededHeight; end; procedure TViewBaseItem.Click(const Pt: TPoint; Button: TMouseButton; Shift: TShiftState); var Handled: Boolean = False; begin case Button of mbRight: if Assigned(FOnContextMenu) then begin OnContextMenu(Self, Pt); Handled := True; end; end; if not Handled and Assigned(FOnClick) then FOnClick(Self, Button, Shift, Pt); end; procedure TViewBaseItem.KeyDown(var Key: Word; Shift: TShiftState); var Rect: TRect; Point: TPoint; begin case Key of VK_APPS: if Assigned(FOnContextMenu) then begin Rect := FTreeNode.DisplayRect(False); Point.x := Rect.Left + (Rect.Right - Rect.Left) div 2; Point.y := Rect.Top + (Rect.Bottom - Rect.Top) div 2; OnContextMenu(Self, Point); Key := 0; end; VK_SPACE: begin StartPause; Key := 0; end; VK_DELETE, VK_BACK: begin Stop; Key := 0; end; end; end; procedure TViewBaseItem.Selected; begin if Assigned(FOnSelected) then FOnSelected(Self); end; { TViewOperationItem } constructor TViewOperationItem.Create(ANode: TTreeNode; AOperationHandle: TOperationHandle); begin FOperationHandle := AOperationHandle; inherited Create(ANode); end; procedure TViewOperationItem.Click(const Pt: TPoint; Button: TMouseButton; Shift: TShiftState); var Handled: Boolean = False; OpManItem: TOperationsManagerItem; NodeRect: TRect; begin OpManItem := OperationsManager.GetItemByHandle(FOperationHandle); if Assigned(OpManItem) then begin case Button of mbLeft: if OpManItem.Queue.IsFree then begin NodeRect := FTreeNode.DisplayRect(False); if ((ssDouble in Shift) or PtInRect(GetStatusIconRect(NodeRect), Pt)) then begin StartPause; Handled := True; end; end; end; end; if not Handled then inherited Click(Pt, Button, Shift); end; procedure TViewOperationItem.Draw(Canvas: TCanvas; NodeRect: TRect); var OpManItem: TOperationsManagerItem; Element: TThemedTreeview; Icon: TViewOperationsStatusIcon; begin if FTreeNode.Selected then Element := ttItemSelected else Element := ttItemNormal; DrawThemedBackground(Canvas, Element, NodeRect); OpManItem := OperationsManager.GetItemByHandle(FOperationHandle); if Assigned(OpManItem) then begin DrawThemedText(Canvas, Element, FTextRect, True, FText); if OpManItem.Queue.IsFree then begin case OpManItem.Operation.State of fsosRunning: Icon := vosiPlay; fsosPaused: Icon := vosiPause; else Icon := vosiHourglass; end; DrawStatusIcon(Canvas, NodeRect, Icon); end; DrawProgress(Canvas, NodeRect, FProgress); end; end; function TViewOperationItem.GetBackgroundColor: TColor; begin Result := FTreeNode.TreeView.BackgroundColor; end; procedure TViewOperationItem.StartPause; var OpManItem: TOperationsManagerItem; begin OpManItem := OperationsManager.GetItemByHandle(FOperationHandle); if Assigned(OpManItem) and OpManItem.Queue.IsFree then OpManItem.Operation.TogglePause; end; procedure TViewOperationItem.Stop; var OpManItem: TOperationsManagerItem; begin OpManItem := OperationsManager.GetItemByHandle(FOperationHandle); if Assigned(OpManItem) then OpManItem.Operation.Stop; end; procedure TViewOperationItem.UpdateView(Canvas: TCanvas); var OpManItem: TOperationsManagerItem; begin OpManItem := OperationsManager.GetItemByHandle(FOperationHandle); if Assigned(OpManItem) then begin FText := IntToStr(OpManItem.Handle) + ': ' + OpManItem.Operation.GetDescription(fsoddJobAndTarget); FProgress := OpManItem.Operation.Progress; if FProgress > 0 then FText := FText + ' - ' + GetProgressString(FProgress); FText := FText + GetOperationStateString(OpManItem.Operation.State); CalculateSizes(Canvas, OpManItem.Queue.IsFree, FProgress > 0); end else begin FText := ''; FProgress := 0; FTreeNode.Height := 10; end; end; function TViewOperationItem.GetTextIndent: Integer; begin Result := FTreeNode.DisplayExpandSignLeft; if FTreeNode.Level = 0 then Inc(Result, FreeOperationTextIndent) else Dec(Result, TTreeView(FTreeNode.TreeView).Indent * (FTreeNode.Level - 1)); end; { TViewQueueItem } procedure TViewQueueItem.Click(const Pt: TPoint; Button: TMouseButton; Shift: TShiftState); var Handled: Boolean = False; NodeRect: TRect; begin case Button of mbLeft: begin NodeRect := FTreeNode.DisplayRect(False); if (ssDouble in Shift) or PtInRect(GetStatusIconRect(NodeRect), Pt) then begin StartPause; Handled := True; end; end; end; if not Handled then inherited Click(Pt, Button, Shift); end; constructor TViewQueueItem.Create(ANode: TTreeNode; AQueueId: TOperationsManagerQueueIdentifier); begin FQueueIdentifier := AQueueId; inherited Create(ANode); end; procedure TViewQueueItem.Draw(Canvas: TCanvas; NodeRect: TRect); var Element: TThemedTreeview; Queue: TOperationsManagerQueue; Icon: TViewOperationsStatusIcon; begin if FTreeNode.Selected then Element := ttItemSelected else Element := ttItemSelectedNotFocus; DrawThemedBackground(Canvas, Element, NodeRect); Queue := OperationsManager.QueueByIdentifier[FQueueIdentifier]; if Assigned(Queue) then begin DrawThemedText(Canvas, Element, FTextRect, True, FText); if Queue.Paused then Icon := vosiPause else Icon := vosiPlay; DrawStatusIcon(Canvas, NodeRect, Icon); end; end; function TViewQueueItem.GetBackgroundColor: TColor; begin Result := FTreeNode.TreeView.BackgroundColor; end; function TViewQueueItem.GetTextIndent: Integer; var ATreeView: TCustomTreeView; begin Result := FTreeNode.DisplayExpandSignLeft; ATreeView := FTreeNode.TreeView; if ATreeView is TTreeView then Inc(Result, TTreeView(ATreeView).Indent); end; procedure TViewQueueItem.StartPause; var Queue: TOperationsManagerQueue; begin Queue := OperationsManager.QueueByIdentifier[FQueueIdentifier]; if Assigned(Queue) then Queue.TogglePause; end; procedure TViewQueueItem.Stop; var Queue: TOperationsManagerQueue; begin Queue := OperationsManager.QueueByIdentifier[FQueueIdentifier]; if Assigned(Queue) then begin Queue.Stop; end; end; procedure TViewQueueItem.UpdateView(Canvas: TCanvas); var Queue: TOperationsManagerQueue; begin Queue := OperationsManager.QueueByIdentifier[FQueueIdentifier]; if Assigned(Queue) then begin FText := Queue.GetDescription(False); if Queue.Paused then FText := FText + GetOperationStateString(fsosPaused); CalculateSizes(Canvas, True, False); end else begin FText := ''; FTreeNode.Height := 10; end; end; { TfrmViewOperations } procedure TfrmViewOperations.FormCreate(Sender: TObject); begin InitPropStorage(Self); cbAlwaysOnTopChange(nil); FMenuOperation := InvalidOperationHandle; tvOperations.DoubleBuffered := True; DoubleBuffered := True; CreateNodes; OperationsManager.AddEventsListener( [omevOperationAdded, omevOperationRemoved, omevOperationMoved], @UpdateView); tvOperations.OnResize := @tvOperationsResize; end; procedure TfrmViewOperations.btnStopClick(Sender: TObject); var Item: TViewBaseItem; begin Item := GetFocusedItem; if Assigned(Item) then Item.Stop; end; procedure TfrmViewOperations.cbAlwaysOnTopChange(Sender: TObject); begin if cbAlwaysOnTop.Checked then begin FormStyle := fsStayOnTop; ShowInTaskBar := stDefault; end else begin FormStyle := fsNormal; ShowInTaskBar := stAlways; end; end; procedure TfrmViewOperations.FormClose(Sender: TObject; var CloseAction: TCloseAction); begin CloseAction := caFree; frmViewOperations := nil; end; procedure TfrmViewOperations.FormDestroy(Sender: TObject); begin OperationsManager.RemoveEventsListener( [omevOperationAdded, omevOperationRemoved, omevOperationMoved], @UpdateView); end; procedure TfrmViewOperations.mnuNewQueueClick(Sender: TObject); var Item: TViewBaseItem; OpManItem: TOperationsManagerItem; begin Item := GetFocusedItem; if Assigned(Item) and (Item is TViewOperationItem) then begin OpManItem := OperationsManager.GetItemByHandle(TViewOperationItem(Item).FOperationHandle); if Assigned(OpManItem) then OpManItem.MoveToNewQueue; end; end; procedure TfrmViewOperations.mnuPutFirstInQueueClick(Sender: TObject); begin MoveWithinQueue(True); end; procedure TfrmViewOperations.mnuPutLastInQueueClick(Sender: TObject); begin MoveWithinQueue(False); end; procedure TfrmViewOperations.mnuOperationShowDetachedClick(Sender: TObject); var OpManItem: TOperationsManagerItem; begin OpManItem := OperationsManager.GetItemByHandle(FMenuOperation); if Assigned(OpManItem) and OpManItem.Queue.IsFree then TfrmFileOp.ShowFor(OpManItem.Handle, [opwoIfExistsBringToFront]); end; procedure TfrmViewOperations.OnOperationItemContextMenu(Item: TViewBaseItem; const Point: TPoint); var i: Integer; PopupPoint: TPoint; OpManItem: TOperationsManagerItem; begin FMenuOperation := (Item as TViewOperationItem).FOperationHandle; OpManItem := OperationsManager.GetItemByHandle(FMenuOperation); if Assigned(OpManItem) then begin for i := 0 to mnuQueue.Count - 1 do if i = OpManItem.Queue.Identifier then mnuQueue.Items[i].Checked := True else mnuQueue.Items[i].Checked := False; mnuOperationShowDetached.Enabled := OpManItem.Queue.IsFree; PopupPoint := tvOperations.ClientToScreen(Point); pmOperationPopup.PopUp(PopupPoint.x, PopupPoint.y); end; end; procedure TfrmViewOperations.OnOperationItemSelected(Item: TViewBaseItem); var OpManItem: TOperationsManagerItem; begin OpManItem := OperationsManager.GetItemByHandle(TViewOperationItem(Item).FOperationHandle); if Assigned(OpManItem) then begin SetStartPauseCaption(OpManItem.Operation.State in [fsosStarting, fsosRunning, fsosWaitingForConnection]); btnStartPause.Enabled := OpManItem.Queue.IsFree; btnStop.Enabled := True; end else begin btnStartPause.Enabled := False; btnStop.Enabled := False; end; end; procedure TfrmViewOperations.OnQueueContextMenu(Item: TViewBaseItem; const Point: TPoint); var PopupPoint: TPoint; begin FMenuQueueIdentifier := (Item as TViewQueueItem).FQueueIdentifier; PopupPoint := tvOperations.ClientToScreen(Point); pmQueuePopup.PopUp(PopupPoint.x, PopupPoint.y); end; procedure TfrmViewOperations.OnQueueItemSelected(Item: TViewBaseItem); var Queue: TOperationsManagerQueue; begin Queue := OperationsManager.QueueByIdentifier[TViewQueueItem(Item).FQueueIdentifier]; if Assigned(Queue) then begin SetStartPauseCaption(not Queue.Paused); btnStartPause.Enabled := True; end else begin btnStartPause.Enabled := False; end; btnStop.Enabled := btnStartPause.Enabled; end; procedure TfrmViewOperations.OnUpdateTimer(Sender: TObject); begin UpdateSizes; tvOperationsSelectionChanged(tvOperations); tvOperations.Invalidate; end; procedure TfrmViewOperations.mnuQueueNumberClick(Sender: TObject); var NewQueueNumber: integer; Item: TViewBaseItem; begin if TryStrToInt(Copy((Sender as TMenuItem).Name, 9, 1), NewQueueNumber) then begin Item := GetFocusedItem; if Assigned(Item) and (Item is TViewOperationItem) then SetNewQueue(TViewOperationItem(Item), NewQueueNumber); end; end; procedure TfrmViewOperations.mnuQueueShowDetachedClick(Sender: TObject); var Queue: TOperationsManagerQueue; begin Queue := OperationsManager.QueueByIdentifier[FMenuQueueIdentifier]; if Assigned(Queue) then TfrmFileOp.ShowFor(Queue.Identifier, [opwoIfExistsBringToFront]); end; procedure TfrmViewOperations.tbPauseAllChange(Sender: TObject); begin if tbPauseAll.State = cbChecked then OperationsManager.PauseAll else OperationsManager.UnPauseAll; end; procedure TfrmViewOperations.tvOperationsCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean); var Item: TViewBaseItem; NodeRect: TRect; procedure DrawExpandSign(MidX, MidY: integer; CollapseSign: boolean); const ExpandSignColor = clWindowText; var HalfSize, ALeft, ATop, ARight, ABottom: integer; Points: array [0..2] of TPoint; R: TRect; begin with Sender.Canvas do begin Brush.Color := clWindow; Pen.Color := ExpandSignColor; Pen.Style := psSolid; HalfSize := ExpandSignSize shr 1; if not Odd(ExpandSignSize) then dec(HalfSize); ALeft := MidX - HalfSize; ATop := MidY - HalfSize; ARight := ALeft + ExpandSignSize; ABottom := ATop + ExpandSignSize; // draw an arrow. down for collapse and right for expand R := Rect(ALeft, ATop, ARight, ABottom); if CollapseSign then begin // draw an arrow down Points[0] := Point(R.Left, MidY); Points[1] := Point(R.Right - 1, MidY); Points[2] := Point(MidX, R.Bottom - 1); end else begin // draw an arrow right Points[0] := Point(MidX - 1, ATop); Points[1] := Point(R.Right - 2, MidY); Points[2] := Point(MidX - 1, R.Bottom - 1); end; Polygon(Points, False); end; end; var VertMid: Integer; begin if not Assigned(Node.Data) then Exit; Item := TViewBaseItem(Node.Data); NodeRect := Node.DisplayRect(False); Item.Draw(Sender.Canvas, NodeRect); if tvOperations.ShowButtons and Node.HasChildren and ((tvoShowRoot in tvOperations.Options) or (Node.Parent <> nil)) then begin VertMid := (NodeRect.Top + NodeRect.Bottom) div 2; DrawExpandSign(Node.DisplayExpandSignLeft + tvOperations.Indent shr 1, VertMid, Node.Expanded); end; // draw separator if (tvoShowSeparators in tvOperations.Options) then begin Sender.Canvas.Pen.Color:=tvOperations.SeparatorColor; Sender.Canvas.MoveTo(NodeRect.Left,NodeRect.Bottom-1); Sender.Canvas.LineTo(NodeRect.Right,NodeRect.Bottom-1); end; DefaultDraw := False; end; procedure TfrmViewOperations.tvOperationsDeletion(Sender: TObject; Node: TTreeNode); var Item: TViewBaseItem; begin Item := TViewBaseItem(Node.Data); Node.Data := nil; Item.Free; end; procedure TfrmViewOperations.tvOperationsDragDrop(Sender, Source: TObject; X, Y: Integer); var TargetNode: TTreeNode; NodeRect: TRect; TargetItem: TViewBaseItem; QueueItem: TViewQueueItem; OperItem: TViewOperationItem; SourceOpManItem, TargetOpManItem: TOperationsManagerItem; TargetQueue: TOperationsManagerQueue; TargetQueueId: TOperationsManagerQueueIdentifier; HitTopPart: Boolean; begin if Source = tvOperations then begin SourceOpManItem := OperationsManager.GetItemByHandle(FDraggedOperation); if Assigned(SourceOpManItem) then begin TargetNode := tvOperations.GetNodeAt(X, Y); if not Assigned(TargetNode) then begin SourceOpManItem.MoveToNewQueue; end else begin NodeRect := TargetNode.DisplayRect(False); TargetItem := TViewBaseItem(TargetNode.Data); HitTopPart := Y - NodeRect.Top < (NodeRect.Bottom - NodeRect.Top) div 2; if TargetItem is TViewQueueItem then begin QueueItem := TViewQueueItem(TargetItem); if HitTopPart and (TargetNode = tvOperations.Items.GetFirstNode) and (QueueItem.FQueueIdentifier <> FreeOperationsQueueId) then begin // There are no free operations and item was dropped at the top of the list // on some queue. Create a free operations queue and move to it. TargetQueueId := FreeOperationsQueueId; TargetQueue := OperationsManager.GetOrCreateQueue(TargetQueueId); end else begin TargetQueueId := QueueItem.FQueueIdentifier; TargetQueue := OperationsManager.QueueByIdentifier[TargetQueueId]; end; SourceOpManItem.SetQueue(TargetQueue); end else if (TargetItem is TViewOperationItem) and (FDraggedOperation <> TViewOperationItem(TargetItem).FOperationHandle) then begin OperItem := TViewOperationItem(TargetItem); TargetOpManItem := OperationsManager.GetItemByHandle(OperItem.FOperationHandle); if Assigned(TargetOpManItem) then SourceOpManItem.Move(TargetOpManItem.Handle, HitTopPart); end; end; end; end; end; procedure TfrmViewOperations.tvOperationsDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); begin Accept := True; end; procedure TfrmViewOperations.tvOperationsKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var Item: TViewBaseItem; begin Item := GetFocusedItem; if Assigned(Item) then Item.KeyDown(Key, Shift); end; procedure TfrmViewOperations.tvOperationsMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Node: TTreeNode; NodeRect: TRect; begin FDraggedOperation := InvalidOperationHandle; Node := tvOperations.GetNodeAt(X, Y); if Assigned(Node) then begin NodeRect := Node.DisplayRect(False); TViewBaseItem(Node.Data).Click(Point(X - NodeRect.Left, Y - NodeRect.Top), Button, Shift); if TViewBaseItem(Node.Data) is TViewOperationItem then FDraggedOperation := TViewOperationItem(Node.Data).FOperationHandle; end; end; procedure TfrmViewOperations.tvOperationsResize(Sender: TObject); begin UpdateSizes; end; procedure TfrmViewOperations.tvOperationsSelectionChanged(Sender: TObject); var Node: TTreeNode; begin Node := tvOperations.Selected; if Assigned(Node) then TViewBaseItem(Node.Data).Selected; end; function TfrmViewOperations.GetFocusedItem: TViewBaseItem; var Node: TTreeNode; begin Node := tvOperations.Selected; if Assigned(Node) then Result := TViewBaseItem(Node.Data) else Result := nil; end; procedure TfrmViewOperations.mnuCancelOperationClick(Sender: TObject); var OpManItem: TOperationsManagerItem; begin OpManItem := OperationsManager.GetItemByHandle(FMenuOperation); if Assigned(OpManItem) then OpManItem.Operation.Stop; end; procedure TfrmViewOperations.mnuCancelQueueClick(Sender: TObject); var Queue: TOperationsManagerQueue; begin Queue := OperationsManager.QueueByIdentifier[FMenuQueueIdentifier]; if Assigned(Queue) then Queue.Stop; end; procedure TfrmViewOperations.MoveWithinQueue(MoveToTop: Boolean); var Item: TViewBaseItem; OpManItem: TOperationsManagerItem; begin Item := GetFocusedItem; if Assigned(Item) and (Item is TViewOperationItem) then begin OpManItem := OperationsManager.GetItemByHandle(TViewOperationItem(Item).FOperationHandle); if Assigned(OpManItem) then begin if OpManItem.Queue.Identifier <> FreeOperationsQueueId then begin if MoveToTop then OpManItem.MoveToTop else OpManItem.MoveToBottom; end; end; end; end; procedure TfrmViewOperations.SetFocusItem(AOperationHandle: TOperationHandle); var Node: TTreeNode; begin for Node in tvOperations.Items do begin if (TViewBaseItem(Node.Data) is TViewOperationItem) and (TViewOperationItem(Node.Data).FOperationHandle = AOperationHandle) then begin Node.Selected := True; Exit; end; end; end; procedure TfrmViewOperations.SetFocusItem(AQueueIdentifier: TOperationsManagerQueueIdentifier); var Node: TTreeNode; begin for Node in tvOperations.Items do begin if (TViewBaseItem(Node.Data) is TViewQueueItem) and (TViewQueueItem(Node.Data).FQueueIdentifier = AQueueIdentifier) then begin Node.Selected := True; Exit; end; end; end; procedure TfrmViewOperations.SetNewQueue(Item: TViewOperationItem; NewQueue: TOperationsManagerQueueIdentifier); var OpManItem: TOperationsManagerItem; begin OpManItem := OperationsManager.GetItemByHandle(Item.FOperationHandle); if Assigned(OpManItem) then OpManItem.SetQueue(OperationsManager.GetOrCreateQueue(NewQueue)); end; procedure TfrmViewOperations.SetStartPauseCaption(SetPause: Boolean); begin if SetPause then btnStartPause.Caption := rsDlgOpPause else btnStartPause.Caption := rsDlgOpStart; end; procedure TfrmViewOperations.btnStartPauseClick(Sender: TObject); var Item: TViewBaseItem; begin Item := GetFocusedItem; if Assigned(Item) then Item.StartPause; end; procedure TfrmViewOperations.CreateNodes; procedure AddOperations(Queue: TOperationsManagerQueue; QueueNode: TTreeNode); var OperIndex: Integer; OpManItem: TOperationsManagerItem; OperNode: TTreeNode; Item: TViewBaseItem; begin for OperIndex := 0 to Queue.Count - 1 do begin OpManItem := Queue.Items[OperIndex]; OperNode := tvOperations.Items.AddChild(QueueNode, ''); Item := TViewOperationItem.Create(OperNode, OpManItem.Handle); OperNode.Data := Item; Item.UpdateView(tvOperations.Canvas); Item.OnContextMenu := @OnOperationItemContextMenu; Item.OnSelected := @OnOperationItemSelected; end; end; var QueueIndex: Integer; Queue: TOperationsManagerQueue; QueueNode: TTreeNode; Item: TViewBaseItem; begin tvOperations.Items.Clear; // First add all free operations. Queue := OperationsManager.QueueByIdentifier[FreeOperationsQueueId]; if Assigned(Queue) then AddOperations(Queue, nil); for QueueIndex := 0 to OperationsManager.QueuesCount - 1 do begin Queue := OperationsManager.QueueByIndex[QueueIndex]; if Queue.Identifier <> FreeOperationsQueueId then begin QueueNode := tvOperations.Items.AddChild(nil, ''); Item := TViewQueueItem.Create(QueueNode, Queue.Identifier); QueueNode.Data := Item; Item.UpdateView(tvOperations.Canvas); Item.OnContextMenu := @OnQueueContextMenu; Item.OnSelected := @OnQueueItemSelected; AddOperations(Queue, QueueNode); end; end; end; procedure TfrmViewOperations.UpdateSizes; var Node: TTreeNode; begin Node := tvOperations.Items.GetFirstNode; while Assigned(Node) do begin if Assigned(Node.Data) then TViewBaseItem(Node.Data).UpdateView(tvOperations.Canvas); Node := Node.GetNext; end; end; procedure TfrmViewOperations.UpdateView(Item: TOperationsManagerItem; Event: TOperationManagerEvent); begin CreateNodes; tvOperations.Invalidate; end; initialization ProgressRight := ProgressHorizontalMargin + StatusIconRightMargin + (StatusIconFrame.Right - StatusIconFrame.Left); end.