TB2Merge, Version 1.1b

Zurück zur Übersicht

Datei: TB2Merge.pas

{
  TB2Merge.pas

  Utility unit for Toolbar2000 and derived components like TBX or SpTBX to
  merge two sets of toolbar items together. See the included file INFO.txt
  for information on how to use it.

  Version 1.1b - always find the most current version at
  http://flocke.vssd.de/prog/code/pascal/tb2merge/

  Copyright (C) 2005, 2006 Volker Siebert <flocke@vssd.de>
  All rights reserved.

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the "Software"),
  to deal in the Software without restriction, including without limitation
  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  and/or sell copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.
}

unit TB2Merge;

interface

uses
  Classes, SysUtils, Menus, Controls, ImgList, TB2Dock, TB2Item;

type
  { TTBMergeError is the exception all functions in this module throw.
  }
  TTBMergeError = class(Exception);

  { TTBUnmergeItem holds a single atomic operation for an "unmerge". This is
    done by simply moving the item back to its old parent and position.
  }
  PTBUnmergeItem = ^TTBUnmergeItem;
  TTBUnmergeItem = record
    Next: PTBUnmergeItem;     // Single linked list
    Item: TTBCustomItem;      // Which item
    Parent: TTBCustomItem;    // Old parent
    ParentIndex: Integer;     // Old parent index
  end;

  { These are the options for the merge process. They tell TBMergeItems
    what to do with a block of items having the same GroupIndex.
  }
  TTBMergeOption = (
    moKeepTarget,             // Keep non-matching target entries
    moRecursive,              // Recurse into matching items' submenus
    moMatchByCaption,         // Identify matching items by their caption
    moMatchByTag,             // Identify matching items by their tag
    moSeparatorAfterItems     // Separators shall belong to their preceding items
  );

  TTBMergeOptions = set of TTBMergeOption;

function  TBMergeItems(Target, Source: TTBCustomItem; Options: TTBMergeOptions): PTBUnmergeItem;
procedure TBUnmergeItems(Target, Source: TTBCustomItem; List: PTBUnmergeItem);
procedure TBFreeUnmergeItems(List: PTBUnmergeItem);
procedure TBFixImageList(Items: TTBCustomItem; Images: TCustomImageList);

type
  { TTBMergedItems is just an OOP wrapper for the functions above. Upon
    creation, the two sets of items are merged and they are automatically
    unmerged when the object is deleted.
  }
  TTBMergedItems = class(TObject)
  private
    FTarget: TComponent;
    FSource: TComponent;
    FUnmergeItems: PTBUnmergeItem;
    FOptions: TTBMergeOptions;

  public
    constructor Create(ATarget, ASource: TComponent; Options: TTBMergeOptions);
    destructor Destroy; override;

    procedure Merge;
    procedure Unmerge;
    procedure Dispose;

    property Target: TComponent read FTarget;
    property Source: TComponent read FSource;
    property UnmergeItems: PTBUnmergeItem read FUnmergeItems;
    property Options: TTBMergeOptions read FOptions;
  end;

type
  { Don't use a TTBToolbarMerger component directly, use the one returned
    by the function "ToolbarMerger".
  }
  TTBToolbarMerger = class(TComponent)
  private
    FMergedItems: TList;

    function GetCount: Integer;
    function GetItem(Index: Integer): TTBMergedItems;

  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Merge(Target, Source: TComponent; Options: TTBMergeOptions);
    procedure Unmerge(Target, Source: TComponent);
    procedure UnmergeAll(Comp: TComponent);
    procedure Delete(Index: Integer);
    function  IsMerged(Target: TComponent): Integer;
    function  IsMergedWith(Source: TComponent): Integer;
    procedure Clear;

    property Count: Integer read GetCount stored false;
    property Items[Index: Integer]: TTBMergedItems read GetItem stored false;
  end;

function ToolbarMerger: TTBToolbarMerger;

implementation

{$B-,R-}

uses
  Windows, Forms;

{######################################################################
 Misc. declarations
 ######################################################################
}

{ TTBItemAccess is just a hack to get access to the protected members of
  TTBCustomItem, especially: "tbisSubmenu in TTBItemAccess(i).ItemStyle".
}
type
  TTBItemAccess = class(TTBCustomItem);

{ Notify the item that it's going to be updated (a lot)
}
procedure BeginUpdateItem(Item: TTBCustomItem);
begin
  if Item <> nil then
  begin
    if Item.ParentComponent <> nil then
      if Item.ParentComponent is TTBCustomDockableWindow then
        TTBCustomDockableWindow(Item.ParentComponent).BeginUpdate;

    Item.ViewBeginUpdate;
  end;
end;

{ Notify the item that all updates are finished
}
procedure EndUpdateItem(Item: TTBCustomItem);
begin
  if Item <> nil then
  begin
    Item.ViewEndUpdate;

    if Item.ParentComponent <> nil then
      if Item.ParentComponent is TTBCustomDockableWindow then
        TTBCustomDockableWindow(Item.ParentComponent).EndUpdate;
  end;
end;

{ Get the root item for the given component
}
function GetComponentItems(Comp: TComponent): TTBCustomItem;
var
  Intf: ITBItems;
begin
  if Comp = nil then
    Result := nil
  else if Comp is TTBCustomItem then
    Result := TTBCustomItem(Comp)
  else if Comp.GetInterface(ITBItems, Intf) then
    Result := Intf.GetItems
  else
    Result := nil;
end;

{######################################################################
 Procedure/function interface (non OOP)
 ######################################################################
}

{ Sets the "Images" Property of *ALL* toolbar items to the one
  specified as parameter. }
procedure TBFixImageList(Items: TTBCustomItem; Images: TCustomImageList);
var
  Index: Integer;
  Item: TTBCustomItem;
begin
  if Images <> nil then
    for Index := Items.Count - 1 downto 0 do
    begin
      Item := Items.Items[Index];
      Item.Images := Images;
      if tbisSubmenu in TTBItemAccess(Item).ItemStyle then
        TBFixImageList(Item, Images);
    end;
end;

{ Free all memory associated with the given list of UnmergeItems.
}
procedure TBFreeUnmergeItems(List: PTBUnmergeItem);
var
  Head: PTBUnmergeItem;
begin
  while List <> nil do
  begin
    Head := List;
    List := Head^.Next;

    if (Head^.Item <> nil) and (Head^.Item.Parent = nil) then
      Head^.Item.Free;

    Dispose(Head);
  end;
end;

{ Unmerge the subitems of target and source, using the given list of
  UnmergeItems. While doing this, the list will be freed. The parameters
  Target and Source are same as for the TBMergeItems call that produced
  the list of PTBUnmergeItems.
}
procedure TBUnmergeItems(Target, Source: TTBCustomItem; List: PTBUnmergeItem);
var
  Op: PTBUnmergeItem;
begin
  if (Target <> nil) and (csDestroying in Target.ComponentState) then
    Target := nil;
  if (Source <> nil) and (csDestroying in Source.ComponentState) then
    Source := nil;

  BeginUpdateItem(Target);
  try
    BeginUpdateItem(Source);
    try
      try
        while List <> nil do
        begin
          Op := List;
          List := Op^.Next;

          if Target = nil then
          begin
            // Target was deleted in the meanwhile, source items are lost
            if Op^.Item.Parent = nil then
              Op^.Item.Free;
          end
          else if Op^.Item.Parent <> nil then
          begin
            // Source that was moved to target
            Op^.Item.Parent.Remove(Op^.Item);

            if Source <> nil then
              Op^.Parent.Insert(Op^.ParentIndex, Op^.Item)
            else
              Op^.Item.Free;
          end
          else
          begin
            // Target was hidden (moved to this queue)
            Op^.Parent.Insert(Op^.ParentIndex, Op^.Item);
          end;

          Dispose(Op);
        end;
      except
        TBFreeUnmergeItems(List);
        raise;
      end;
    finally
      EndUpdateItem(Source);
    end;
  finally
    EndUpdateItem(Target);
  end;
end;

{ Merge the subitems of target and source, using the given options. The return
  value is a list of "unmerge" UnmergeItems that will revert all modifications.
}
function TBMergeItems(Target, Source: TTBCustomItem; Options: TTBMergeOptions): PTBUnmergeItem;
var
  List: PTBUnmergeItem;

  { Find the end of the group of items from "Items" starting at index "Start".
    Upon exit, the parameter "Stop" is set to the index of the first item
    *AFTER* the found group and "Group" to the found "GroupIndex".

    If "Start" points to a valid item in the list, the value of "Group" is
    "Items.Items[Start].GroupIndex" and "Stop" is at least "Start+1".

    If "Start" points beyond the list, "Stop" is set equal to "Start".
  }
  procedure FindEndOfGroup(Items: TTBCustomItem; var Start: Integer; out Stop, Group: Integer);
  begin
    if not (moSeparatorAfterItems in Options) then
    begin
      // First skip over all separator items, i.e., find the "real" start
      Stop := Start;
      while (Stop < Items.Count) and
            (tbisSeparator in TTBItemAccess(Items.Items[Stop]).ItemStyle) do
        inc(Stop);

      if Stop = Items.Count then
        Group := 0
      else
      begin
        // We found the first item with a real group index
        Group := Items.Items[Stop].GroupIndex;
        inc(Stop);

        // Now collect all items having at least this group index
        while (Stop < Items.Count) and (Items.Items[Stop].GroupIndex <= Group) do
          inc(Stop);

        // If we did not reach the end, remove all trailing separator items
        if Stop < Items.Count then
          while (Stop > Start) and
                (tbisSeparator in TTBItemAccess(Items.Items[Stop - 1]).ItemStyle) do
            dec(Stop);
      end;
    end
    else if Start < Items.Count then
    begin
      Group := Items.Items[Start].GroupIndex;
      Stop := Start + 1;
      while (Stop < Items.Count) and (Items.Items[Stop].GroupIndex <= Group) do
        inc(Stop);
    end
    else
    begin
      Group := 0;
      Stop := Start;
    end;
  end;

  { Move one item from OldParent.Items[OldIndex] to its new position at
    NewParent.Items[NewIndex] and store the corresponding "unmerge"
    operation in our List. Returns true upon successful completion.
  }
  function MoveItem(NewParent: TTBCustomItem; NewIndex: Integer;
                    OldParent: TTBCustomItem; OldIndex: Integer): Boolean;
  var
    Op: PTBUnmergeItem;
  begin
    Result := (OldIndex >= 0) and (OldIndex < OldParent.Count);
    if Result then
    begin
      New(Op);
      try
        Op^.Next := List;
        Op^.Item := OldParent.Items[OldIndex];
        Op^.Parent := OldParent;
        Op^.ParentIndex := OldIndex;

        OldParent.Delete(OldIndex);

        if NewParent <> nil then
          try
            NewParent.Insert(NewIndex, Op^.Item);
          except
            OldParent.Insert(OldIndex, Op^.Item);
            raise;
          end;

        List := Op;
      except
        Dispose(Op);
        raise;
      end;
    end;
  end;

  { Move all items between SrcStart and SrcStop (excluding) to the target
    side, adjusting the index parameters accordingly.
  }
  procedure MoveItems(TgtItems: TTBCustomItem; var TgtStart, TgtStop: Integer;
                      SrcItems: TTBCustomItem; var SrcStart, SrcStop: Integer);
  begin
    while (SrcStart < SrcStop) and
          MoveItem(TgtItems, TgtStart, SrcItems, SrcStart) do
    begin
      dec(SrcStop);
      inc(TgtStart);
      inc(TgtStop);
    end;
  end;

  { Hide all items between SrcStart and SrcStop (excluding).
  }
  procedure HideItems(SrcItems: TTBCustomItem; var SrcStart, SrcStop: Integer);
  begin
    while (SrcStart < SrcStop) and
          MoveItem(nil, 0, SrcItems, SrcStart) do
      dec(SrcStop);
  end;

  { Returns the string used to identify a matching item.
  }
  function GetItemIdent(Item: TTBCustomItem): string;
  begin
    if moMatchByCaption in Options then
      Result := StripHotkey(Item.Caption)
    else if moMatchByTag in Options then
      Result := IntToStr(Item.Tag)
    else
      Result := Item.Name;
  end;

  { Look for a matching item for "Find" in the given range of items.
  }
  function FindMatchingItem(Items: TTBCustomItem; Start, Stop: Integer;
                            Find: TTBCustomItem): Integer;
  var
    LookFor: string;
  begin
    LookFor := GetItemIdent(Find);

    Result := -1;

    while (Start < Stop) and (Result < 0) do
    begin
      if CompareText(LookFor, GetItemIdent(Items.Items[Start])) = 0 then
        Result := Start;

      inc(Start);
    end;
  end;

  { Merge the subitems of TgtItems (the target) and SrcItems (the source)
  }
  procedure MergeItems(TgtItems, SrcItems: TTBCustomItem);
  var
    TgtStart, TgtStop, TgtGroup: Integer;
    SrcStart, SrcStop, SrcGroup: Integer;
    Match: Integer;
  begin
    TgtStart := 0;
    FindEndOfGroup(TgtItems, TgtStart, TgtStop, TgtGroup);

    SrcStart := 0;
    FindEndOfGroup(SrcItems, SrcStart, SrcStop, SrcGroup);

    while (TgtStart < TgtStop) and (SrcStart < SrcStop) do
    begin
      if TgtGroup < SrcGroup then
      begin
        // Target block's "GroupIndex" is less than the source block's, the
        // target block of items will be kept (no modification).
        TgtStart := TgtStop;
      end
      else if SrcGroup < TgtGroup then
      begin
        // Source block's "GroupIndex" is less than the target block's, the
        // source block will be inserted completely.
        MoveItems(TgtItems, TgtStart, TgtStop, SrcItems, SrcStart, SrcStop);
      end
      else
      begin
        if moKeepTarget in Options then
        begin
          // Add new entries from source to target and replace them resp.
          // merge the subitems of matching ones.
          while TgtStart < TgtStop do
          begin
            // Find match for the next item on the target side
            Match := FindMatchingItem(SrcItems, SrcStart, SrcStop, TgtItems.Items[TgtStart]);
            if Match < 0 then
            begin
              // No match found
              inc(TgtStart);
            end
            else if (moRecursive in Options) and
                    (tbisSubmenu in TTBItemAccess(SrcItems.Items[SrcStart]).ItemStyle) and
                    (tbisSubmenu in TTBItemAccess(TgtItems.Items[TgtStart]).ItemStyle) then
            begin
              // Recurse into both lists of subitems
              MergeItems(TgtItems.Items[TgtStart], SrcItems.Items[SrcStart]);
              inc(SrcStart);
              inc(TgtStart);
            end
            else
            begin
              // Hide old target item
              MoveItem(nil, 0, TgtItems, TgtStart);
              // Move source to target
              MoveItem(TgtItems, TgtStart, SrcItems, Match);
              dec(SrcStop);
              inc(TgtStart);
            end;
          end;
        end;

        // Replace target with source, i.e. hide target items and move source
        // items to the target side. For moKeepTarget, the target side is
        // already empty (TgtStart = TgtStop) and the remainder of the source
        // side must be copied completely.
        HideItems(TgtItems, TgtStart, TgtStop);
        MoveItems(TgtItems, TgtStart, TgtStop, SrcItems, SrcStart, SrcStop);
      end;

      if TgtStart >= TgtStop then
        // Find next group on target side
        FindEndOfGroup(TgtItems, TgtStart, TgtStop, TgtGroup);

      if SrcStart >= SrcStop then
        // Find next group on source side
        FindEndOfGroup(SrcItems, SrcStart, SrcStop, SrcGroup);
    end;

    // Either the target or the source side reached its end. Just copy
    // the remainding source items over to the end of the target side.
    SrcStop := SrcItems.Count;
    MoveItems(TgtItems, TgtStart, TgtStop, SrcItems, SrcStart, SrcStop);
  end;

begin
  // Do not merge components that are about to be destroyed
  if (Target = nil) or (csDestroying in Target.ComponentState) or
     (Source = nil) or (csDestroying in Source.ComponentState) then
  begin
    Result := nil;
    exit;
  end;

  BeginUpdateItem(Target);
  try
    BeginUpdateItem(Source);
    try
      List := nil;
      try
        if moRecursive in Options then
          include(Options, moKeepTarget);

        MergeItems(Target, Source);
      except
        TBUnmergeItems(Target, Source, List);
        raise;
      end;
    finally
      EndUpdateItem(Source);
    end;
  finally
    EndUpdateItem(Target);
  end;

  Result := List;
end;

{######################################################################
 TTBMergedItems
 ######################################################################
}

constructor TTBMergedItems.Create(ATarget, ASource: TComponent; Options: TTBMergeOptions);
begin
  inherited Create;

  FTarget := ATarget;
  FSource := ASource;
  FOptions := Options;
  FUnmergeItems := nil;

  Merge;
end;

destructor TTBMergedItems.Destroy;
begin
  Unmerge;

  inherited;
end;

procedure TTBMergedItems.Merge;
begin
  if FUnmergeItems = nil then
    FUnmergeItems := TBMergeItems(GetComponentItems(FTarget),
                                  GetComponentItems(FSource),
                                  FOptions);
end;

procedure TTBMergedItems.Unmerge;
var
  List: PTBUnmergeItem;
begin
  if FUnmergeItems <> nil then
  begin
    List := FUnmergeItems;
    FUnmergeItems := nil;
    TBUnmergeItems(GetComponentItems(FTarget),
                   GetComponentItems(FSource),
                   List);
  end;
end;

procedure TTBMergedItems.Dispose;
var
  List: PTBUnmergeItem;
begin
  if FUnmergeItems <> nil then
  begin
    List := FUnmergeItems;
    FUnmergeItems := nil;
    TBFreeUnmergeItems(List);
  end;
end;

{######################################################################
 TTBToolbarMerger
 ######################################################################
}

constructor TTBToolbarMerger.Create(AOwner: TComponent);
begin
  inherited;

  FMergedItems := TList.Create;
end;

destructor TTBToolbarMerger.Destroy;
begin
  Clear;
  FreeAndNil(FMergedItems);

  inherited;
end;

procedure TTBToolbarMerger.Notification(AComponent: TComponent; Operation: TOperation);
var
  Index: Integer;
  OldItem: TTBMergedItems;

  function ObjName(Obj: TComponent): string;
  begin
    if Obj = nil then
      Result := 'nil'
    else if Obj.Name <> '' then
      Result := Obj.Name
    else
      Result := Obj.ClassName + Format('(%p)', [Pointer(Obj)]);
  end;

  function ObjFullName(Obj: TComponent): string;
  begin
    if Obj.Owner = nil then
      Result := ObjName(Obj)
    else
      Result := ObjName(Obj.Owner) + '.' + ObjName(Obj);
  end;

  procedure GiveWarning(Item: TTBMergedItems);
  var
    s: string;
  resourcestring
    S_Warning = 'WARNING';
    S_Header = '{@}:'#13#10#13#10'Component {C} destroyed with'#13#10;
    S_ItemsInto = 'items still merged into {T}.';
    S_ItemsFrom = 'items still merged from {S}.';
    S_FormInfo = #13#10#13#10'You probably forgot to unmerge them in your'#13#10'{O}.FormDestroy event.';
  begin
    s := S_Header;
    if AComponent = Item.Source then
      s := s + S_ItemsInto
    else
      s := s + S_ItemsFrom;

    s := StringReplace(s, '{@}', ObjFullName(Self), [rfReplaceAll]);
    s := StringReplace(s, '{C}', ObjFullName(AComponent), [rfReplaceAll]);
    s := StringReplace(s, '{T}', ObjFullName(Item.Target), [rfReplaceAll]);
    s := StringReplace(s, '{S}', ObjFullName(Item.Source), [rfReplaceAll]);

    if OldItem.Source.Owner is TCustomForm then
      s := s + StringReplace(S_FormInfo, '{O}', ObjName(Item.Source.Owner), [rfReplaceAll]);

    { Raising an exception here *MAY* cause a complete crash.
    }
    Windows.MessageBox(0, PChar(s), PChar(S_Warning),
      MB_OK or MB_ICONWARNING or MB_SERVICE_NOTIFICATION);
  end;

begin
  inherited;

  if Operation <> opRemove then
    exit;

  for Index := Count - 1 downto 0 do
  begin
    OldItem := Items[Index];
    if (AComponent = OldItem.Target) or
       (AComponent = OldItem.Source) then
    begin
      if (not (csDestroying in OldItem.Target.ComponentState)) or
         (not (csDestroying in OldItem.Source.ComponentState)) then
        // At least one of both is not being destroyed
        GiveWarning(OldItem);

      Delete(Index);
    end;
  end;
end;

function TTBToolbarMerger.GetCount: Integer;
begin
  Result := FMergedItems.Count;
end;

function TTBToolbarMerger.GetItem(Index: Integer): TTBMergedItems;
begin
  Result := TTBMergedItems(FMergedItems[Index]);
end;

function TTBToolbarMerger.IsMerged(Target: TComponent): Integer;
begin
  Result := Count - 1;
  while (Result >= 0) and (Items[Result].Target <> Target) do
    dec(Result);
end;

function TTBToolbarMerger.IsMergedWith(Source: TComponent): Integer;
begin
  Result := Count - 1;
  while (Result >= 0) and (Items[Result].Source <> Source) do
    dec(Result);
end;

procedure TTBToolbarMerger.Merge(Target, Source: TComponent; Options: TTBMergeOptions);
var
  NewItem: TTBMergedItems;
resourcestring
  SSourceUsed = 'TTBToolbarMerger.Merge: Source is target of another merge operation';
  STargetUsed = 'TTBToolbarMerger.Merge: Target is source of another merge operation';
begin
  // Some sanity-checks
  if Application.Terminated or
     (Target = nil) or (csDestroying in Target.ComponentState) or
     (Source = nil) or (csDestroying in Source.ComponentState) or
     (Target = Source) then
    exit;

  // Check if source is target of another operation
  if IsMerged(Source) >= 0 then
    raise TTBMergeError.Create(SSourceUsed);

  // Check if target is source of another operation
  if IsMergedWith(Target) >= 0 then
    raise TTBMergeError.Create(STargetUsed);

  // If source is already source of another operation, unmerge that operation
  Unmerge(nil, Source);

  // If target is already target of another operation, unmerge that operation
  Unmerge(Target, nil);

  // Merge the NewItem
  NewItem := TTBMergedItems.Create(Target, Source, Options);

  if NewItem.UnmergeItems = nil then
    // Nothing done, no need to store it
    FreeAndNil(NewItem)
  else
  begin
    // Ask for notifications
    Target.FreeNotification(Self);
    Source.FreeNotification(Self);

    // Add it to the list
    FMergedItems.Add(NewItem);
  end;
end;

procedure TTBToolbarMerger.Delete(Index: Integer);
var
  OldItem: TTBMergedItems;
begin
  // Extract item from the list
  OldItem := Items[Index];
  FMergedItems.Delete(Index);

  // Remove the notifications we have set
  OldItem.Target.RemoveFreeNotification(Self);
  OldItem.Source.RemoveFreeNotification(Self);

  // This will also unmerge the item
  FreeAndNil(OldItem);
end;

procedure TTBToolbarMerger.Unmerge(Target, Source: TComponent);
var
  Index: Integer;
begin
  // Step through all unmerge sets
  for Index := Count - 1 downto 0 do
    if ((Target = nil) or (Items[Index].Target = Target)) and
       ((Source = nil) or (Items[Index].Source = Source)) then
      Delete(Index);
end;

procedure TTBToolbarMerger.UnmergeAll(Comp: TComponent);
var
  Index: Integer;
begin
  // Step through all unmerge sets
  for Index := Count - 1 downto 0 do
    if (Items[Index].Target = Comp) or (Items[Index].Source = Comp) then
      Delete(Index);
end;

procedure TTBToolbarMerger.Clear;
begin
  while Count > 0 do
    Delete(Count - 1);
end;

{######################################################################
 ToolbarMerger
 ######################################################################
}

type
  TGlobalToolbarMerger = class(TTBToolbarMerger)
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

{ The global variable we use as toolbar merger for all operations.

  Note: since GlobalToolbarMerger is owned by the global application object,
  there's no need to free it in a "finalization" block. It will be freed
  automatically as soon as the global application object is being freed.
}
var
  GlobalToolbarMerger: TTBToolbarMerger;

constructor TGlobalToolbarMerger.Create(AOwner: TComponent);
begin
  { NOTE: This is put before (and not after) the call to the inherited
    constructor to avoid double creation of the instance in some cases.
  }
  GlobalToolbarMerger := Self;
  inherited;
end;

destructor TGlobalToolbarMerger.Destroy;
begin
  inherited;
  { NOTE: This is put *AFTER* the inherited destructor to avoid
    re-creation of the instance in some rare cases.
  }
  GlobalToolbarMerger := nil;
end;

function ToolbarMerger: TTBToolbarMerger;
begin
  if GlobalToolbarMerger <> nil then
    Result := GlobalToolbarMerger
  else
    Result := TGlobalToolbarMerger.Create(Application);
end;

end.
Flocke's Garage
Valid HTML 4.01 Transitional Valid CSS!
(C) 2005-2018 Volker Siebert.
Creative Commons-LizenzvertragDer gesamte Inhalt dieser Webseite steht unter einer Creative Commons-Lizenz (sofern nicht anders angegeben).