// ==================================================================== // Class: GUITreeList // // The GUITree is a part of a tree control. // // Written by Bruce Bickar // (Cloned from GUIList) // (c) 2002, 2003, Epic Games, Inc. All Rights Reserved // ==================================================================== class GUITreeList extends GUIVertList Native; #exec OBJ LOAD FILE=// if _RO_ // else //#exec OBJ LOAD FILE=InterfaceContent.utx // end if _RO_InterfaceContent.utx cpptext { INT FindParentIndex( INT ChildIndex ) const; void UpdateVisibleCount(); void Draw(UCanvas* Canvas); protected: virtual void DrawItem(UCanvas* Canvas, INT Item, FLOAT X, FLOAT Y, FLOAT W, FLOAT HT, UBOOL bSelected, UBOOL bPending); public: } var() eTextAlign TextAlign; var() editconstarray editconst noexport array Elements; var() editconstarray editconst noexport array SelectedElements; var() editconst const noexport int VisibleCount; // How many elements are currently visible var() const editconst float PrefixWidth, SelectedPrefixWidth; var() bool bAllowParentSelection, bAllowDuplicateCaption; var() bool bGroupItems; // really bad hack to get onslaught link setups working native final function UpdateVisibleCount(); native final function SortList(); // Used by SortList. delegate int CompareItem(GUITreeNode ElemA, GUITreeNode ElemB) { return StrCmp(ElemA.Caption, ElemB.Caption); } function int AddItem(string Caption, string Value, optional string ParentCaption, optional bool bEnabled, optional string ExtraInfo) { local int i; local int idx; if ( !bAllowEmptyItems && Caption == "" && ParentCaption == "" ) return -1; if ( !bAllowDuplicateCaption && FindIndex(Caption) != -1 ) return -1; if ( ParentCaption == "" ) idx = HardInsert( Elements.Length, Caption, Value, "", 0, True, ExtraInfo ); else { // find parent if ( bGroupItems ) { i = FindIndex(ParentCaption,true); if ( i == -1 ) i = FindIndex(ParentCaption); // Implicitly create the entry for the parent, if it wasn't found if ( i == -1 ) i = HardInsert( Elements.Length, ParentCaption, "", "", 0, True ); } else if ( ParentCaption != "" ) i = HardInsert(Elements.Length, ParentCaption, Value, "", 0, True, ExtraInfo); idx = HardInsert( i+1, Caption, Value, ParentCaption, Elements[i].Level + 1, bEnabled, ExtraInfo ); } if (Elements.Length == 1 && bInitializeList) SetIndex(0); else if ( bNotify ) CheckLinkedObjects(Self); UpdateVisibleCount(); if (MyScrollBar != None) MyScrollBar.AlignThumb(); return idx; } function int AddElement( GUITreeNode Node ) { return AddItem(Node.Caption, Node.Value, Node.ParentCaption, Node.bEnabled, Node.ExtraInfo); } function Replace(int i, string NewItem, string NewValue, optional string ParentCaption, optional bool bNoSort, optional string ExtraInfo) { if ( !IsValidIndex(i) ) AddItem(NewItem,NewValue,ParentCaption); else { if ( !bAllowEmptyItems && NewItem == "" && NewValue == "" && ParentCaption == "" ) return; Elements[i].Caption = NewItem; Elements[i].Value = NewValue; Elements[i].ExtraInfo = ExtraInfo; if ( bSorted && !bNoSort ) Sort(); SetIndex(Index); } } function int InsertItem( int idx, string Caption, string Value, string ParentCaption, int Level, bool bEnabled, optional string ExtraInfo ) { local int ParentIndex, ChildIndex; if ( !IsValidIndex(idx) ) return AddItem(Caption, Value, ParentCaption, bEnabled, ExtraInfo); if ( bGroupItems ) { ParentIndex = FindParentIndex(idx); if ( IsValidIndex(ParentIndex) && Elements[ParentIndex].Caption == ParentCaption ) { ChildIndex = FindAvailableChildIndex(ParentIndex); if ( idx > ParentIndex && idx <= ChildIndex ) return HardInsert(idx, Caption, Value, ParentCaption, Elements[ParentIndex].Level + 1, bEnabled, ExtraInfo); else return HardInsert(ChildIndex, Caption, Value, ParentCaption, Elements[ParentIndex].Level + 1, bEnabled, ExtraInfo); } else if ( ParentCaption == "" ) { idx = FindNextAvailableRootIndex(idx); return HardInsert(idx,Caption,Value,ParentCaption,Level,True,ExtraInfo); } } else { ParentIndex = FindNextAvailableRootIndex(idx); if ( ParentCaption != "" ) idx = HardInsert(idx,ParentCaption,Value,"",Level++,True,ExtraInfo); return HardInsert(idx+1, Caption, Value, ParentCaption, Level, True, ExtraInfo); } return AddItem(Caption, Value, ParentCaption, bEnabled); } protected function int HardInsert( int idx, string Caption, string Value, string ParentCaption, int Level, bool bEnabled, optional string ExtraInfo ) { Elements.Insert(idx,1); Elements[idx].Caption = Caption; Elements[idx].Value = Value; Elements[idx].ParentCaption = ParentCaption; Elements[idx].ExtraInfo = ExtraInfo; Elements[idx].Level = Level; Elements[idx].bEnabled = bEnabled; ItemCount = Elements.Length; return idx; } function LoadFrom(GUITreeList Source, optional bool bClearFirst) { local int i, Level; local byte bEnabled; local string Caption, Value, ParentCaption, ExtraInfo; if (bClearfirst) Clear(); for ( i = 0; i < Source.ItemCount; i++ ) { if ( Source.ValidSelectionAt(i) ) { Source.GetAtIndex(i,Caption,Value,ParentCaption,Level,bEnabled, ExtraInfo); AddItem(Caption, Value, ParentCaption, bool(bEnabled), ExtraInfo); } } } function int RemoveSilent( string Caption ) { local int i; bNotify = False; i = RemoveItem(Caption); bNotify = True; return i; } function int RemoveItem(string Caption) { local int i; i = FindIndex(Caption, True); if ( i == -1 ) i = FindIndex(Caption); return RemoveItemAt(i); } function int RemoveItemAt( int idx, optional bool bNoSort, optional bool bSkipCleanup ) { local int Level, ParentIndex; if ( IsValidIndex(idx) ) { Level = Elements[idx].Level + 1; ParentIndex = FindParentIndex(idx); // Remove all children of this element Elements.Remove(idx,1); ItemCount--; while ( idx < ItemCount && Elements[idx].Level == Level ) { if ( RemoveItemAt(idx,True,True) == -2 ) break; } // If parent selection isn't allowed, we should remove the entry for the parent if we're the last child // bSkipCleanup should probably only be used when recursing within this function if ( !bAllowParentSelection && !bSkipCleanup && IsValidIndex(ParentIndex) && !HasChildren(ParentIndex) ) RemoveItemAt(ParentIndex,True); UpdateVisibleCount(); if ( bSorted && !bNoSort ) Sort(); // In case we now have an invalid index SetIndex(Index); if ( MyScrollBar != None ) MyScrollBar.AlignThumb(); return Index; } return -2; } function int RemoveElement( GUITreeNode Node, optional int Count, optional bool bNoSort ) { local int i; // TODO Hook up count Count = Max(Count, 1); for ( i = 0; i < Elements.Length; i++ ) if ( Elements[i] == Node ) return RemoveItemAt(i, bNoSort); return -1; } function Clear() { if (Elements.Length == 0) return; Elements.Remove(0,Elements.Length); ItemCount = 0; UpdateVisibleCount(); Super.Clear(); } event bool Swap(int IndexA, int IndexB) { local int ParentIndexA, ParentIndexB; ParentIndexA = FindParentIndex(IndexA); ParentIndexB = FindParentIndex(IndexB); // TODO handle cases of attempting to swap elements at different levels if ( bGroupItems ) { if ( IsValidIndex(ParentIndexA) ) { if ( IsValidIndex(ParentIndexB) ) { if ( ParentIndexA != ParentIndexB ) return false; } else return false; } else if ( IsValidIndex(ParentIndexB) ) return false; } // TODO Support swapping elements that contain children if ( IsValidIndex(IndexA) && IsValidIndex(IndexB) ) { if ( !bGroupItems ) { if ( IndexA > IndexB ) { if ( Elements[IndexA].Level > 0 ) IndexA = ParentIndexA; if ( Elements[IndexB].Level > 0 ) IndexB = ParentIndexB; if ( IndexA == IndexB ) { IndexA = FindNextAvailableRootIndex(IndexB-1); if ( !IsValidIndex(IndexA) ) return False; } } else { if ( Elements[IndexA].Level > 0 ) IndexA = ParentIndexA; if ( Elements[IndexB].Level > 0 ) IndexB = ParentIndexB; if ( IndexA == IndexB ) IndexB = FindAvailableChildIndex(IndexA); if ( !IsValidIndex(IndexB) ) return False; } } HardSwap(IndexA,IndexB); return True; } return False; } protected function HardSwap( int IndexA, int IndexB ) { local array chIdxA, chIdxB; local int i; local array NodesA, NodesB; NodesA[NodesA.Length] = Elements[IndexA]; NodesB[NodesB.Length] = Elements[IndexB]; if ( HasChildren(IndexA) ) { chIdxA = GetChildIndexList(IndexA); for ( i = 0; i < chIdxA.Length; i++ ) NodesA[NodesA.Length] = Elements[chIdxA[i]]; } if ( HasChildren(IndexB) ) { chIdxB = GetChildIndexList(IndexB); for ( i = 0; i < chIdxB.Length; i++ ) NodesB[NodesB.Length] = Elements[chIdxB[i]]; } if ( IndexA > IndexB ) { Elements.Remove( IndexA, NodesA.Length ); Elements.Insert( IndexA, NodesB.Length ); for ( i = 0; i < NodesB.Length; i++ ) Elements[IndexA + i] = NodesB[i]; Elements.Remove( IndexB, NodesB.Length ); Elements.Insert( IndexB, NodesA.Length ); for ( i = 0; i < NodesB.Length; i++ ) Elements[IndexB + i] = NodesA[i]; } else { Elements.Remove( IndexB, NodesB.Length ); Elements.Insert( IndexB, NodesA.Length ); for ( i = 0; i < NodesA.Length; i++ ) Elements[IndexB + i] = NodesA[i]; Elements.Remove( IndexA, NodesA.Length ); Elements.Insert( IndexA, NodesB.Length ); for ( i = 0; i < NodesB.Length; i++ ) Elements[IndexA + i] = NodesB[i]; } if ( bNotify ) CheckLinkedObjects(Self); if ( bSorted ) Sort(); } // ===================================================================================================================== // ===================================================================================================================== // Query Functions // ===================================================================================================================== // ===================================================================================================================== function string GetCaption() { return GetCaptionAtIndex(Index); } function string GetParentCaption() { return GetParentCaptionAtIndex(Index); } function string GetValue() { return GetValueAtIndex(Index); } function int GetLevel() { return GetLevelAtIndex(Index); } function string GetExtra() { return GetExtraAtIndex(Index); } function string GetCaptionAtIndex(int i) { if (!IsValidIndex(i)) return ""; return Elements[i].Caption; } function string GetParentCaptionAtIndex(int idx) { if ( !IsValidIndex(idx) ) return ""; // TODO this is not a good way to do this...gah if ( Elements[idx].ParentCaption == "" ) return Elements[idx].Caption; return Elements[idx].ParentCaption; } function string GetValueAtIndex(int i) { if (!IsValidIndex(i)) return ""; return Elements[i].Value; } function int GetLevelAtIndex(int i) { if (!IsValidIndex(i)) return -1; return Elements[i].Level; } function string GetExtraAtIndex( int idx ) { if ( !IsValidIndex(idx) ) return ""; return Elements[idx].ExtraInfo; } function bool GetElementAtIndex( int i, out GUITreeNode Node ) { if ( !IsValidIndex(i) ) return False; Node = Elements[i]; return True; } function bool GetAtIndex(int i, out string Caption, out string Value, out string ParentCaption, out int Level, out byte bEnabled, out string ExtraInfo) { if (!IsValidIndex(i)) return false; Caption = Elements[i].Caption; Value = Elements[i].Value; Level = Elements[i].Level; ParentCaption = Elements[i].ParentCaption; bEnabled = byte(Elements[i].bEnabled); ExtraInfo = Elements[i].ExtraInfo; return True; } function array GetIndexList() { local array Indexes; local int i; for ( i = 0; i < ItemCount; i++ ) if ( ValidSelectionAt(i) ) Indexes[Indexes.Length] = i; return Indexes; } function array GetChildIndexList( int idx, optional bool bNoRecurse ) { local array Indexes; local int Level; if ( IsValidIndex(idx) ) { Level = Elements[idx].Level + 1; while ( ++idx < ItemCount ) { if ( Elements[idx].Level < Level ) break; if ( bNoRecurse && Elements[idx].Level > Level ) break; Indexes[Indexes.Length] = idx; } } return Indexes; } event bool ValidSelection() { return ValidSelectionAt(Index); } event bool ValidSelectionAt( int idx ) { if ( !IsValidIndex(idx) ) return False; return bAllowParentSelection || !HasChildren(idx); } event bool HasChildren( int ParentIndex ) { if ( !IsValidIndex(ParentIndex) ) return False; if ( ParentIndex < ItemCount - 1 ) return Elements[ParentIndex+1].Level > Elements[ParentIndex].Level; return False; } event bool IsExpanded( int ParentIndex ) { if ( !HasChildren(ParentIndex) ) return True; return Elements[ParentIndex+1].bEnabled; } // ===================================================================================================================== // ===================================================================================================================== // Assignment functions // ===================================================================================================================== // ===================================================================================================================== function SetCaptionAtIndex(int i, string NewCaption) { if (!IsValidIndex(i)) return; Elements[i].Caption = NewCaption; } function SetValueAtIndex(int i, string NewValue) { if (!IsValidIndex(i)) return; Elements[i].Value = NewValue; } function SetLevelAtIndex(int i, int NewLevel) { if (!IsValidIndex(i)) return; Elements[i].Level = NewLevel; } function bool Expand(int idx, optional bool bRecursive) { local int i; local array Indexes; if (!IsValidIndex(idx)) return false; // Make sure all other elements on this level, as well as all parent items, are also visible Expand(FindParentIndex(idx)); // Then enable all children, perhaps with recursion Indexes = GetChildIndexList(idx, !bRecursive); for ( i = 0; i < Indexes.Length; i++ ) Elements[Indexes[i]].bEnabled = True; return True; } function bool Collapse(int idx) { local int i; local array Indexes; if (!IsValidIndex(idx)) return false; Indexes = GetChildIndexList(idx); for ( i = 0; i < Indexes.Length; i++ ) Elements[Indexes[i]].bEnabled = False; return True; } function ToggleExpand(int idx, optional bool bRecursive) { if (!IsValidIndex(idx)) return; if ( IsExpanded(idx) ) Collapse(idx); else Expand(idx, bRecursive); } function bool IsToggleClick(int idx) { local float PrefixOffset, CaptionOffset; if ( !IsValidIndex(idx) ) return false; if ( !HasChildren(idx) ) return false; // Calculate the position of the plus/minus PrefixOffset = ClientBounds[0] + (SelectedPrefixWidth * GetLevelAtIndex(idx)); CaptionOffset = PrefixOffset + SelectedPrefixWidth; // if we were clicking on the plus/minus, toggle the item if ( Controller.MouseX >= PrefixOffset && Controller.MouseX <= CaptionOffset ) return true; return false; } function bool InternalOnClick( GUIComponent Sender ) { local bool bResult; bResult = Super.InternalOnClick(Sender); if ( bResult && IsToggleClick(Index) ) ToggleExpand(Index); return bResult; } function bool InternalDblClick( GUIComponent Sender ) { ToggleExpand(Index); return True; } // ===================================================================================================================== // ===================================================================================================================== // Search functions // ===================================================================================================================== // ===================================================================================================================== function int FindIndex(string Caption, optional bool bExact) { local int i; for ( i = 0; i < Elements.Length; i++) if ( (bExact && Elements[i].Caption == Caption) || (!bExact && Elements[i].Caption ~= Caption) ) return i; return -1; } function int FindFullIndex( string Caption, string Value, string ParentCaption ) { local int i; for ( i = 0; i < Elements.Length; i++ ) if ( Elements[i].Caption == Caption && Elements[i].Value == Value && Elements[i].ParentCaption == ParentCaption ) return i; return -1; } function int FindParentIndex( int ChildIndex ) { local int Level; if ( !IsValidIndex(ChildIndex) ) return -1; if ( Elements[ChildIndex].Level == 0 ) return -1; Level = Elements[ChildIndex].Level - 1; while ( --ChildIndex >= 0 && Elements[ChildIndex].Level > Level ); return ChildIndex; } function int FindNextAvailableRootIndex( int Target ) { if ( !IsValidIndex(Target) ) return Elements.Length; if ( Elements[Target].Level == 0 ) return Target; while ( --Target > 0 && Elements[Target].Level > 0 ); return Target; } // This function calculates the appropriate index to use for adding a child node function int FindAvailableChildIndex( int ParentIndex ) { local int ParentLevel; if ( IsValidIndex(ParentIndex) ) { ParentLevel = Elements[ParentIndex].Level + 1; while ( ++ParentIndex < ItemCount && Elements[ParentIndex].Level >= ParentLevel ); return ParentIndex; } return -1; } function int FindIndexByValue(string Value, optional bool bExact) { local int i; for ( i = 0; i < Elements.Length; i++) if ( (bExact && Elements[i].Value == Value) || (!bExact && Elements[i].Value ~= Value) ) return i; return -1; } function int FindElement( string Caption, string Value, int Level, optional bool bCaseSensitive ) { local int i; if ( bCaseSensitive ) for ( i = 0; i < Elements.Length; i++ ) if ( Elements[i].Caption == Caption && Elements[i].Value == Value && Elements[i].Level == Level ) return i; else for ( i = 0; i < Elements.Length; i++ ) if ( Elements[i].Caption ~= Caption && Elements[i].Value ~= Value && Elements[i].Level == Level ) return i; return -1; } // Find the node index that is distance away from node specified by idx, considering only visible nodes function int FindVisibleIndex( int Idx, int Distance ) { local int Count, i, increment; if ( Distance == 0 ) return Idx; else if ( Distance < 0 ) increment = -1; else increment = 1; for ( i = Idx; IsValidIndex(i); i += increment ) { if ( Elements[i].bEnabled ) Count++; if ( Count >= Abs(Distance) ) break; } return i; } // ===================================================================================================================== // ===================================================================================================================== // GUIListBase Interface // ===================================================================================================================== // ===================================================================================================================== event int CalculateIndex( optional bool bRequireValidIndex ) { local int Row, NewIndex; // Figure out which Item we're clicking on Row = Ceil( (Controller.MouseY - ClientBounds[1]) / ItemHeight ); NewIndex = FindVisibleIndex(Top, Row); return Min( NewIndex, ItemCount - 1 ); } function Sort() { SortList(); } function int SetIndex(int NewIndex) { if ( IsValidIndex(NewIndex) && !Elements[NewIndex].bEnabled ) { Expand(NewIndex); UpdateVisibleCount(); } return Super.SetIndex(NewIndex); } function SetTopItem(int Item) { Top = Item; if ( Top < 0 ) Top = 0; else if ( FindVisibleIndex(Top,ItemsPerPage) >= ItemCount ) Top = FindVisibleIndex(ItemCount - 1, -ItemsPerPage); if ( ItemCount <= 0 ) Top = 0; else Top = Clamp(Top, 0, ItemCount - 1); if ( bNotify ) CheckLinkedObjects(Self); OnAdjustTop(Self); if ( MyScrollBar != None ) MyScrollBar.AlignThumb(); } function bool Up() { local int NewIndex; if ( ItemCount < 2 || Index == 0 ) return True; NewIndex = Index; while ( --NewIndex >= 0 && !Elements[NewIndex].bEnabled ); SetIndex( Max(0, NewIndex) ); if ( MyScrollBar != None ) MyScrollBar.AlignThumb(); return true; } function bool Down() { local int NewIndex; if ( ItemCount < 2 || Index == ItemCount - 1 ) return true; // Scan down until an enabled element is found NewIndex = Index; while ( ++NewIndex < ItemCount && !Elements[NewIndex].bEnabled ); SetIndex(Min(NewIndex, ItemCount - 1)); if ( MyScrollBar != None ) MyScrollBar.AlignThumb(); return true; } function End() { local int NewIndex; if ( ItemCount < 2 ) return; // Scan up from bottom until enabled element found NewIndex = FindVisibleIndex(ItemCount - 1, 1); if ( IsValidIndex(NewIndex) ) { SetIndex(NewIndex); if ( MyScrollBar != None ) MyScrollBar.AlignThumb(); } } function PgUp() { if ( ItemCount < 2 ) return; UpdateVisibleCount(); Super.PgUp(); } function PgDn() { if ( ItemCount < 2 ) return; UpdateVisibleCount(); Super.PgDn(); } // ===================================================================================================================== // ===================================================================================================================== // Drag-n-drop interface // ===================================================================================================================== // ===================================================================================================================== // Called on the drop source when when an Item has been dropped. bAccepted tells it whether // the operation was successful or not. function InternalOnEndDrag(GUIComponent Accepting, bool bAccepted) { local int i; // log(Name@"InternalOnEndDrag Accepting:"$Accepting@"bAccepted:"$bAccepted); if (bAccepted && Accepting != None) { GetPendingElements(); if ( Accepting != Self ) { for ( i = 0; i < SelectedElements.Length; i++ ) RemoveElement(SelectedElements[i]); } bRepeatClick = False; } // Simulate repeat click if the operation was a failure to prevent InternalOnMouseRelease from clearing // the SelectedItems array // This way we don't lose the items we clicked on if (Accepting == None) bRepeatClick = True; SetOutlineAlpha(255); if ( bNotify ) CheckLinkedObjects(Self); } // Called on the drop target when the mouse is released - Sender is always DropTarget function bool InternalOnDragDrop(GUIComponent Sender) { local array NewItem; local GUITreeList SourceTree; local int i; // log(Name@"InternalOnDragDrop Sender:"$ Sender); if (Controller.DropTarget == Self) { if (Controller.DropSource != None && GUITreeList(Controller.DropSource) != None) { SourceTree = GUITreeList(Controller.DropSource); NewItem = SourceTree.GetPendingElements(); // Special case for drag-n-drop between the same list. if ( Controller.DropSource == Self ) { for ( i = NewItem.Length - 1; i >= 0; i-- ) RemoveElement(NewItem[i],,True); } if ( !IsValidIndex(DropIndex) ) DropIndex = ItemCount; for ( i = NewItem.Length-1; i >= 0; i-- ) InsertItem(DropIndex, NewItem[i].Caption, NewItem[i].Value, NewItem[i].ParentCaption, NewItem[i].Level, NewItem[i].bEnabled, NewItem[i].ExtraInfo); SetIndex(DropIndex); return true; } } return false; } function ClearPendingElements() { Super.ClearPendingElements(); if ( SelectedItems.Length == 0 ) SelectedElements.Remove(0, SelectedElements.Length); } function array GetPendingElements(optional bool bGuarantee) { local int i; if ( (DropState == DRP_Source && Controller.DropSource == Self) || bGuarantee ) { if ( SelectedElements.Length == 0 ) { for (i = 0; i < SelectedItems.Length; i++) if (IsValidIndex(SelectedItems[i]) && ValidSelectionAt(SelectedItems[i])) SelectedElements[SelectedElements.Length] = Elements[SelectedItems[i]]; if ( SelectedElements.Length == 0 && IsValid() && ValidSelection() ) { SelectedElements.Length = SelectedElements.Length + 1; GetElementAtIndex(Index,SelectedElements[0]); } } return SelectedElements; } } defaultproperties { bAllowDuplicateCaption=True bGroupItems=True }