{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is: JvProviderTreeListFrame.pas, released on --.

The Initial Developer of the Original Code is Marcel Bestebroer
Portions created by Marcel Bestebroer are Copyright (C) 2002 - 2003 Marcel
Bestebroer
All Rights Reserved.

Contributor(s):

You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.sourceforge.net

Known Issues:
-----------------------------------------------------------------------------}
// $Id: JvProviderTreeListFrame.pas 10610 2006-05-19 13:35:08Z elahn $

unit JvProviderTreeListFrame;

{$I jvcl.inc}

interface

uses
  SysUtils, Classes,
  Windows, Messages, Graphics, Controls, Forms, Dialogs, ComCtrls,
  JvJCLUtils, JvDataProvider, JvDataProviderIntf;

type
  TGetVirtualRootEvent = procedure(Sender: TObject; var AVirtualRoot: IJvDataItem) of object;

  TMasterConsumer = class(TJvDataConsumer)
  private
    FSlave: TJvDataConsumer;
  protected
    OldOnChanged: TJvDataConsumerChangeEvent;
    procedure SetSlave(Value: TJvDataConsumer);
    procedure LimitSubServices(Sender: TJvDataConsumer;
      var SubSvcClass: TJvDataConsumerAggregatedObjectClass);
    procedure SlaveChanged(Sender: TJvDataConsumer; Reason: TJvDataConsumerChangeReason);
  public
    constructor Create(AOwner: TComponent);
    destructor Destroy; override;
    function ProviderIntf: IJvDataProvider; override;
    procedure SetProviderIntf(Value: IJvDataProvider); override;
    function ContextIntf: IJvDataContext; override;
    procedure SetContextIntf(Value: IJvDataContext); override;
    function GetInterface(const IID: TGUID; out Obj): Boolean; override;

    property Slave: TJvDataConsumer read FSlave write SetSlave;
  end;
  
  TfmeJvProviderTreeList = class(TFrame)
    lvProvider: TListView;
    procedure lvProviderCustomDrawItem(Sender: TCustomListView;
      Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
    procedure lvProviderData(Sender: TObject; Item: TListItem);
    procedure lvProviderDblClick(Sender: TObject);
    procedure lvProviderMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure lvProviderResize(Sender: TObject);
    procedure lvProviderSelectItem(Sender: TObject; Item: TListItem;
      Selected: Boolean);
  private
    FConsumerSvc: TMasterConsumer;
    FOnGetVirtualRoot: TGetVirtualRootEvent;
    FOnItemSelect: TNotifyEvent;
    FUseVirtualRoot: Boolean;
    FLastSelectIdx: Integer;
  protected
    FVirtualRoot: IJvDataItem;
    function DoGetVirtualRoot: IJvDataItem;
    procedure DoItemSelect;
    procedure SetUseVirtualRoot(Value: Boolean);
    function GetViewList: IJvDataConsumerViewList;
    function UsingVirtualRoot: Boolean;
    procedure UpdateColumnSize;
    procedure NotifyConsumerItemSelect;
    procedure UpdateSelectedItem; virtual;
    procedure ConsumerChanged(Sender: TJvDataConsumer; Reason: TJvDataConsumerChangeReason); virtual;
    procedure GenerateVirtualRoot; dynamic;
    property LastSelectIdx: Integer read FLastSelectIdx write FLastSelectIdx;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetDataItem(Index: Integer): IJvDataItem; virtual;
    function LocateID(ID: string): Integer; virtual;
    procedure SelectItemID(ID: string);
    function GetSelectedIndex: Integer;
    procedure UpdateViewList; virtual;
    property OnGetVirtualRoot: TGetVirtualRootEvent read FOnGetVirtualRoot write FOnGetVirtualRoot;
    property OnItemSelect: TNotifyEvent read FOnItemSelect write FOnItemSelect;
    property Provider: TMasterConsumer read FConsumerSvc;
    property UseVirtualRoot: Boolean read FUseVirtualRoot write SetUseVirtualRoot;
  end;

implementation

uses
  {$IFDEF VCL}
  CommCtrl,
  {$ENDIF VCL}
  JvDsgnConsts, JvConsts;

{$R *.dfm}

function GetItemIndexAt(LV: TListView; X, Y: Integer): Integer;
var
  {$IFDEF VCL}
  Info: TLVHitTestInfo;
  {$ENDIF VCL}
  {$IFDEF VisualCLX}
  Item: TListItem;
  {$ENDIF VisualCLX}
begin
  if LV.HandleAllocated then
  begin
    {$IFDEF VCL}
    Info.pt := Point(X, Y);
    Result := ListView_HitTest(LV.Handle, Info);
    if Result >= LV.Items.Count then
      Result := -1;
    {$ENDIF VCL}
    {$IFDEF VisualCLX}
    Item := LV.GetItemAt(X, Y);
    if Item <> nil then
      Result := Item.Index
    else
      Result := -1;
    {$ENDIF VisualCLX}
  end
  else
    Result := -1;
end;

{$IFDEF VisualCLX}
function GetVisibleRowCount(LV: TListView): Integer;
var
  Item1, Item2: TListItem;
begin
  Result := LV.Items.Count;
  Item1 := LV.TopItem;
  if Item1 <> nil then
  begin
    Item2 := LV.GetItemAt(LV.Width div 2, LV.ClientHeight - 1);
    if Item2 <> nil then
      Result := Item2.Index - Item1.Index + 1;
  end;
end;
{$ENDIF VisualCLX}

//=== { TMasterConsumer } ====================================================

constructor TMasterConsumer.Create(AOwner: TComponent);
begin
  inherited Create(AOwner, [DPA_ConsumerDisplaysList, DPA_RenderDisabledAsGrayed]);
  BeforeCreateSubSvc := LimitSubServices;
end;

destructor TMasterConsumer.Destroy;
begin
  Slave := nil;
  inherited Destroy;
end;

procedure TMasterConsumer.SetSlave(Value: TJvDataConsumer);
var
  CtxList: IJvDataContexts;
begin
  if Value <> Slave then
  begin
    ProviderChanging;
    Changing(ccrProviderSelect);
    if Slave <> nil then
      Slave.OnChanged := OldOnChanged;
    FSlave := Value;
    if Slave <> nil then
    begin
      OldOnChanged := Slave.OnChanged;
      Slave.OnChanged := SlaveChanged;
    end;
    if NeedContextFixup then
      FixupContext
    else
    begin
      if Supports(ProviderIntf, IJvDataContexts, CtxList) and (CtxList.GetCount >0 ) then
        SetContextIntf(CtxList.GetContext(0))
      else
        SetContextIntf(nil);
    end;
    ProviderChanged;
    if NeedExtensionFixups then
      FixupExtensions;
    ViewChanged(nil);
    Changed(ccrProviderSelect);
  end;
end;

procedure TMasterConsumer.LimitSubServices(Sender: TJvDataConsumer;
  var SubSvcClass: TJvDataConsumerAggregatedObjectClass);
begin
  if (Slave <> nil) and not SubSvcClass.InheritsFrom(TJvDataConsumerContext) and
    not SubSvcClass.InheritsFrom(TJvDataConsumerItemSelect) and
    not SubSvcClass.InheritsFrom(TJvCustomDataConsumerViewList) then
    SubSvcClass := nil;
end;

procedure TMasterConsumer.SlaveChanged(Sender: TJvDataConsumer;
  Reason: TJvDataConsumerChangeReason);
begin
  if Reason = ccrViewChange then
    ViewChanged(nil);
  Changed(Reason);
end;

function TMasterConsumer.ProviderIntf: IJvDataProvider;
begin
  if Slave <> nil then
    Result := Slave.ProviderIntf
  else
    Result := inherited ProviderIntf;
end;

procedure TMasterConsumer.SetProviderIntf(Value: IJvDataProvider);
begin
  if Slave <> nil then
    Slave.SetProviderIntf(Value)
  else
    inherited SetProviderIntf(Value);
end;

function TMasterConsumer.ContextIntf: IJvDataContext;
begin
  if Slave <> nil then
    Result := Slave.ContextIntf
  else
    Result := inherited ContextIntf;
end;

procedure TMasterConsumer.SetContextIntf(Value: IJvDataContext);
begin
  if Slave <> nil then
    Slave.SetContextIntf(Value)
  else
    inherited SetContextIntf(Value);
end;

function TMasterConsumer.GetInterface(const IID: TGUID; out Obj): Boolean;
begin
  Result := inherited GetInterface(IID, Obj);
  if not Result and not IsEqualGUID(IID, IJvDataConsumerItemSelect) and (Slave <> nil) then
    Result := Slave.GetInterface(IID, Obj);
end;

//=== { TfmeJvProviderTreeList } =============================================

constructor TfmeJvProviderTreeList.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FConsumerSvc := TMasterConsumer.Create(Self);
  FConsumerSvc.OnChanged := ConsumerChanged;
end;

destructor TfmeJvProviderTreeList.Destroy;
begin
  FreeAndNil(FConsumerSvc);
  inherited Destroy;
end;

function TfmeJvProviderTreeList.DoGetVirtualRoot: IJvDataItem;
begin
  Result := nil;
  if Assigned(FOnGetVirtualRoot) then
    FOnGetVirtualRoot(Self, Result);
end;

procedure TfmeJvProviderTreeList.DoItemSelect;
begin
  if Assigned(FOnItemSelect) then
    FOnItemSelect(Self);
end;

procedure TfmeJvProviderTreeList.SetUseVirtualRoot(Value: Boolean);
begin
  if Value <> UseVirtualRoot then
  begin
    FUseVirtualRoot := Value;
    if not Value then
      FVirtualRoot := nil
    else
      GenerateVirtualRoot;
    ConsumerChanged(Provider, ccrViewChange);
  end;
end;

function TfmeJvProviderTreeList.GetViewList: IJvDataConsumerViewList;
begin
  Supports(Provider as IJvDataConsumer, IJvDataConsumerViewList, Result);
end;

function TfmeJvProviderTreeList.UsingVirtualRoot: Boolean;
begin
  Result := UseVirtualRoot and (FVirtualRoot <> nil);
end;

procedure TfmeJvProviderTreeList.UpdateColumnSize;
begin
  lvProvider.Columns[0].Width := lvProvider.ClientWidth;
  lvProvider.Invalidate;
end;

procedure TfmeJvProviderTreeList.NotifyConsumerItemSelect;
var
  Item: IJvDataItem;
begin
  if lvProvider.Selected <> nil then
    Item := GetDataItem(lvProvider.Selected.Index)
  else
    Item := nil;
  if Provider.Slave <> nil then
    Provider.Slave.ItemSelected(Item);
  Provider.ItemSelected(Item);
end;

procedure TfmeJvProviderTreeList.UpdateSelectedItem;
begin
  NotifyConsumerItemSelect;
  DoItemSelect;
end;

procedure TfmeJvProviderTreeList.ConsumerChanged(Sender: TJvDataConsumer;
  Reason: TJvDataConsumerChangeReason);
begin
  if csDestroying in ComponentState then
    Exit;
  if UseVirtualRoot and not UsingVirtualRoot then
    GenerateVirtualRoot;
  if Reason in [ccrProviderSelect, ccrViewChange] then
    UpdateViewList;
  if (lvProvider.Items.Count > 0) and (Reason = ccrViewChange) then
    {$IFDEF VCL}
    with lvProvider do
      UpdateItems(TopItem.Index, TopItem.Index + VisibleRowCount);
    {$ENDIF VCL}
    {$IFDEF VisualCLX}
    with lvProvider do
      UpdateItems(TopItem.Index, TopItem.Index + GetVisibleRowCount(lvProvider));
    {$ENDIF VisualCLX}
  lvProvider.Invalidate;
end;

procedure TfmeJvProviderTreeList.GenerateVirtualRoot;
begin
  FVirtualRoot := DoGetVirtualRoot;
end;

function TfmeJvProviderTreeList.GetDataItem(Index: Integer): IJvDataItem;
begin
  Provider.Enter;
  try
    if UsingVirtualRoot and (Index = 0) then
      Result := FVirtualRoot
    else
    if (Index >= Ord(UsingVirtualRoot)) and ((Index - Ord(UsingVirtualRoot)) < GetViewList.Count) then
      Result := GetViewList.Item(Index - Ord(UsingVirtualRoot));
  finally
    Provider.Leave;
  end;
end;

function TfmeJvProviderTreeList.LocateID(ID: string): Integer;
begin
  Provider.Enter;
  try
    if UsingVirtualRoot and AnsiSameText(ID, FVirtualRoot.GetID) then
      Result := 0
    else
    begin
      Result := GetViewList.IndexOfID(ID);
      if UsingVirtualRoot and (Result >= 0) then
        Inc(Result);
    end;
  finally
    Provider.Leave;
  end;
end;

procedure TfmeJvProviderTreeList.SelectItemID(ID: string);
var
  I: Integer;
begin
  if ID = '' then
    I := -1
  else
    I := LocateID(ID);
  if I > -1 then
  {$IFDEF VCL}
    ListView_SetItemState(lvProvider.Handle, I, LVIS_SELECTED or LVIS_FOCUSED,
      LVIS_SELECTED or LVIS_FOCUSED)
  {$ENDIF VCL}
  {$IFDEF VisualCLX}
  begin
    lvProvider.Items[I].Selected := True;
    lvProvider.Items[I].Focused := True;
  end
  {$ENDIF VisualCLX}
  else
    lvProvider.Selected := nil;
  UpdateSelectedItem;
end;

function TfmeJvProviderTreeList.GetSelectedIndex: Integer;
begin
  if lvProvider.Selected = nil then
    Result := -1
  else
    Result := lvProvider.Selected.Index;
end;

procedure TfmeJvProviderTreeList.UpdateViewList;
{$IFDEF VCL}
var
  ViewList: IJvDataConsumerViewList;
{$ENDIF VCL}
begin
  {$IFDEF VCL}
  ViewList := GetViewList;
  if ViewList <> nil then
    lvProvider.Items.Count := ViewList.Count + Ord(UsingVirtualRoot)
  else
    lvProvider.Items.Count := 0;
  {$ENDIF VCL}
end;

procedure TfmeJvProviderTreeList.lvProviderCustomDrawItem(
  Sender: TCustomListView; Item: TListItem; State: TCustomDrawState;
  var DefaultDraw: Boolean);
var
  ACanvas: TCanvas;
  ARect: TRect;
  BtnWdth: Integer;
  MidX, MidY: Integer;
begin
  {$IFDEF VCL}
  ARect := Item.DisplayRect(drBounds);
  ARect.Right := Sender.ClientRect.Right;

  ACanvas := Sender.Canvas;
  {$ENDIF VCL}
  {$IFDEF VisualCLX}
  ARect := Item.DisplayRect;
  ARect.Right := Sender.ClientRect.Right;
  ACanvas := TControlCanvas.Create;
  TControlCanvas(ACanvas).Control := Sender;
  try
  ACanvas.Start;
  {$ENDIF VisualCLX}
  DefaultDraw := False;
  if Item.Selected then
  begin
    ACanvas.Brush.Color := clHighlight;
    ACanvas.Brush.Style := bsSolid;
    ACanvas.Font.Color := clHighlightText;
    ACanvas.FillRect(ARect);
  end
  else
  begin
    ACanvas.Brush.Color := clWindow;
    ACanvas.Brush.Style := bsSolid;
    ACanvas.Font.Color := clWindowText;
    ACanvas.FillRect(ARect);
  end;
  BtnWdth := Succ(ARect.Bottom - ARect.Top) + 2;
  {$IFDEF VCL}
  ARect.Left := ARect.Left + (BtnWdth * Item.Indent);
  {$ENDIF VCL}
  {$IFDEF VisualCLX}
  ARect.Left := ARect.Left + (BtnWdth * 1);
  {$ENDIF VisualCLX}
  if (UsingVirtualRoot and (Item.Index = 0)) or
    GetViewList.ItemHasChildren(Item.Index - Ord(UsingVirtualRoot)) then
  begin
    with ACanvas do
    begin
      MidX := ARect.Left + (BtnWdth - 3) div 2;
      MidY := ARect.Top + (BtnWdth - 3) div 2;
      Pen.Color := ACanvas.Font.Color;
      Pen.Style := psSolid;
      Pen.Width := 1;
      MoveTo(ARect.Left + 3, ARect.Top + 3);
      LineTo(ARect.Left + BtnWdth - 6, ARect.Top + 3);
      LineTo(ARect.Left + BtnWdth - 6, ARect.Top + BtnWdth - 6);
      LineTo(ARect.Left + 3, ARect.Top + BtnWdth - 6);
      LineTo(ARect.Left + 3, ARect.Top + 3);

      MoveTo(ARect.Left + 5, MidY);
      LineTo(ARect.Left + BtnWdth - 7, MidY);

      if (not UsingVirtualRoot or (Item.Index <> 0)) and
        not GetViewList.ItemIsExpanded(Item.Index - Ord(UsingVirtualRoot)) then
      begin
        MoveTo(MidX, ARect.Top + 5);
        LineTo(MidX, ARect.Top + BtnWdth - 7);
      end;
    end;
  end;
  ARect.Left := ARect.Left + BtnWdth;
  DrawText(ACanvas.Handle, TCaption(Item.Caption), Length(Item.Caption), ARect,
    DT_SINGLELINE + DT_LEFT + DT_END_ELLIPSIS);
  {$IFDEF VisualCLX}
    ACanvas.Stop;
  finally
    ACanvas.Free;
  end;
  {$ENDIF VisualCLX}
end;

procedure TfmeJvProviderTreeList.lvProviderData(Sender: TObject;
  Item: TListItem);
var
  DataItem: IJvDataItem;
  ItemText: IJvDataItemText;
begin
  if (Provider.ProviderIntf = nil) or
      ((Item.Index - Ord(UsingVirtualRoot)) >= GetViewList.Count) then
    Exit;
  Provider.Enter;
  try
    if UsingVirtualRoot and (Item.Index = 0) then
    begin
      DataItem := FVirtualRoot;
      {$IFDEF VCL}
      Item.Indent := 0;
      {$ENDIF VCL}
    end
    else
    begin
      DataItem := GetViewList.Item(Item.Index - Ord(UsingVirtualRoot));
      {$IFDEF VCL}
      Item.Indent := GetViewList.ItemLevel(Item.Index - Ord(UsingVirtualRoot)) + Ord(UsingVirtualRoot);
      {$ENDIF VCL}
    end;
    if DataItem <> nil then
    begin
      if Supports(DataItem, IJvDataItemText, ItemText) then
        Item.Caption := ItemText.Caption
      else
      begin
        if DataItem = FVirtualRoot then
          Item.Caption := RsDataItemRootCaption
        else
          Item.Caption := RsDataItemNoTextIntf;
      end;
    end
  finally
    Provider.Leave;
  end;
end;

procedure TfmeJvProviderTreeList.lvProviderDblClick(Sender: TObject);
begin
  Provider.Enter;
  try
    if lvProvider.Selected <> nil then
      if lvProvider.Selected.Index >= Ord(UsingVirtualRoot) then
        GetViewList.ToggleItem(lvProvider.Selected.Index - Ord(UsingVirtualRoot));
  finally
    Provider.Leave;
  end;
end;

procedure TfmeJvProviderTreeList.lvProviderMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  Item: Integer;
  ItemLevel: Integer;
  TmpRect: TRect;
begin
  Provider.Enter;
  try
    Item := GetItemIndexAt(lvProvider, X, Y);
    if Item <> -1 then
    begin
      if UsingVirtualRoot and (Item = 0) then
        ItemLevel := 0
      else
        ItemLevel := GetViewList.ItemLevel(Item - Ord(UsingVirtualRoot)) + Ord(UsingVirtualRoot);
      {$IFDEF VCL}
      ListView_GetItemRect(lvProvider.Handle, Item, TmpRect, LVIR_BOUNDS);
      {$ENDIF VCL}
      {$IFDEF VisualCLX}
      TmpRect := lvProvider.Items[Item].DisplayRect;
      {$ENDIF VisualCLX}
      TmpRect.Right := TmpRect.Left + (Succ((TmpRect.Bottom - TmpRect.Top) + 2) * Succ(ItemLevel));
      if (X < TmpRect.Right) and (X > TmpRect.Right - ((TmpRect.Bottom - TmpRect.Top) + 2)) then
        if Item >= Ord(UsingVirtualRoot) then
          GetViewList.ToggleItem(Item - Ord(UsingVirtualRoot));
    end;
  finally
    Provider.Leave;
  end;
end;

procedure TfmeJvProviderTreeList.lvProviderResize(Sender: TObject);
begin
  UpdateColumnSize;
end;

procedure TfmeJvProviderTreeList.lvProviderSelectItem(Sender: TObject;
  Item: TListItem; Selected: Boolean);
begin
  UpdateSelectedItem;
  if Selected then
    FLastSelectIdx := Item.Index
  else
    FLastSelectIdx := -1;
end;

end.
