--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] local PANEL = {} AccessorFunc( PANEL, "m_bDirty", "Dirty", FORCE_BOOL ) AccessorFunc( PANEL, "m_bSortable", "Sortable", FORCE_BOOL ) AccessorFunc( PANEL, "m_iHeaderHeight", "HeaderHeight" ) AccessorFunc( PANEL, "m_iDataHeight", "DataHeight" ) AccessorFunc( PANEL, "m_bMultiSelect", "MultiSelect" ) AccessorFunc( PANEL, "m_bHideHeaders", "HideHeaders" ) Derma_Hook( PANEL, "Paint", "Paint", "ListView" ) function PANEL:Init() self:SetSortable( true ) self:SetMouseInputEnabled( true ) self:SetMultiSelect( true ) self:SetHideHeaders( false ) self:SetPaintBackground( true ) self:SetHeaderHeight( 16 ) self:SetDataHeight( 17 ) self.Columns = {} self.Lines = {} self.Sorted = {} self:SetDirty( true ) self.pnlCanvas = vgui.Create( "Panel", self ) self.VBar = vgui.Create( "DVScrollBar", self ) self.VBar:SetZPos( 20 ) end function PANEL:DisableScrollbar() if ( IsValid( self.VBar ) ) then self.VBar:Remove() end self.VBar = nil end function PANEL:GetLines() return self.Lines end function PANEL:GetInnerTall() return self:GetCanvas():GetTall() end function PANEL:GetCanvas() return self.pnlCanvas end function PANEL:AddColumn( strName, iPosition ) if ( iPosition ) then if ( iPosition <= 0 ) then ErrorNoHaltWithStack( "Attempted to insert column at invalid position ", iPosition ) return end if ( IsValid( self.Columns[ iPosition ] ) ) then ErrorNoHaltWithStack( "Attempted to insert duplicate column." ) return end end local pColumn = nil if ( self.m_bSortable ) then pColumn = vgui.Create( "DListView_Column", self ) else pColumn = vgui.Create( "DListView_ColumnPlain", self ) end pColumn:SetName( strName ) pColumn:SetZPos( 10 ) if ( iPosition ) then table.insert( self.Columns, iPosition, pColumn ) local i = 1 for id, pnl in pairs( self.Columns ) do pnl:SetColumnID( i ) i = i + 1 end else local ID = table.insert( self.Columns, pColumn ) pColumn:SetColumnID( ID ) end self:InvalidateLayout() return pColumn end function PANEL:RemoveLine( LineID ) local Line = self:GetLine( LineID ) local SelectedID = self:GetSortedID( LineID ) self.Lines[ LineID ] = nil table.remove( self.Sorted, SelectedID ) self:SetDirty( true ) self:InvalidateLayout() Line:Remove() end function PANEL:ColumnWidth( i ) local ctrl = self.Columns[ i ] if ( !ctrl ) then return 0 end return ctrl:GetWide() end function PANEL:FixColumnsLayout() local NumColumns = table.Count( self.Columns ) if ( NumColumns == 0 ) then return end local AllWidth = 0 for k, Column in pairs( self.Columns ) do AllWidth = AllWidth + math.ceil( Column:GetWide() ) end local ChangeRequired = self.pnlCanvas:GetWide() - AllWidth local ChangePerColumn = math.floor( ChangeRequired / NumColumns ) local Remainder = ChangeRequired - ( ChangePerColumn * NumColumns ) for k, Column in pairs( self.Columns ) do local TargetWidth = math.ceil( Column:GetWide() ) + ChangePerColumn Remainder = Remainder + ( TargetWidth - Column:SetWidth( TargetWidth ) ) end local TotalMaxWidth = 0 -- If there's a remainder, try to palm it off on the other panels, equally while ( Remainder != 0 ) do local PerPanel = math.floor( Remainder / NumColumns ) for k, Column in pairs( self.Columns ) do Remainder = math.Approach( Remainder, 0, PerPanel ) local TargetWidth = math.ceil( Column:GetWide() ) + PerPanel Remainder = Remainder + ( TargetWidth - Column:SetWidth( TargetWidth ) ) if ( Remainder == 0 ) then break end TotalMaxWidth = TotalMaxWidth + math.ceil( Column:GetMaxWidth() ) end -- Total max width of all the columns is less than the width of the DListView, abort! if ( TotalMaxWidth < self.pnlCanvas:GetWide() ) then break end Remainder = math.Approach( Remainder, 0, 1 ) end -- Set the positions of the resized columns local x = 0 for k, Column in pairs( self.Columns ) do Column.x = x x = x + math.ceil( Column:GetWide() ) Column:SetTall( math.ceil( self:GetHeaderHeight() ) ) Column:SetVisible( !self:GetHideHeaders() ) end end function PANEL:PerformLayout() -- Do Scrollbar local Wide = self:GetWide() local YPos = 0 if ( IsValid( self.VBar ) ) then self.VBar:SetPos( self:GetWide() - 16, 0 ) self.VBar:SetSize( 16, self:GetTall() ) self.VBar:SetUp( self.VBar:GetTall() - self:GetHeaderHeight(), self.pnlCanvas:GetTall() ) YPos = self.VBar:GetOffset() if ( self.VBar.Enabled ) then Wide = Wide - 16 end end if ( self.m_bHideHeaders ) then self.pnlCanvas:SetPos( 0, YPos ) else self.pnlCanvas:SetPos( 0, YPos + self:GetHeaderHeight() ) end self.pnlCanvas:SetSize( Wide, self.pnlCanvas:GetTall() ) self:FixColumnsLayout() -- -- If the data is dirty, re-layout -- if ( self:GetDirty() ) then self:SetDirty( false ) local y = self:DataLayout() self.pnlCanvas:SetTall( y ) -- Layout again, since stuff has changed.. self:InvalidateLayout( true ) end end function PANEL:OnScrollbarAppear() self:SetDirty( true ) self:InvalidateLayout() end function PANEL:OnRequestResize( SizingColumn, iSize ) -- Find the column to the right of this one local Passed = false local RightColumn = nil for k, Column in pairs( self.Columns ) do if ( Passed ) then RightColumn = Column break end if ( SizingColumn == Column ) then Passed = true end end -- Alter the size of the column on the right too, slightly if ( RightColumn ) then local SizeChange = SizingColumn:GetWide() - iSize RightColumn:SetWide( RightColumn:GetWide() + SizeChange ) end SizingColumn:SetWide( iSize ) self:SetDirty( true ) -- Invalidating will munge all the columns about and make it right self:InvalidateLayout() end function PANEL:DataLayout() local y = 0 local h = self.m_iDataHeight local alt = false for k, Line in ipairs( self.Sorted ) do if ( !Line:IsVisible() ) then continue end Line:SetPos( 1, y ) Line:SetSize( self:GetWide() - 2, h ) Line:DataLayout( self ) Line:SetAltLine( alt ) alt = !alt y = y + Line:GetTall() end return y end function PANEL:AddLine( ... ) self:SetDirty( true ) self:InvalidateLayout() local Line = vgui.Create( "DListView_Line", self.pnlCanvas ) local ID = table.insert( self.Lines, Line ) Line:SetListView( self ) Line:SetID( ID ) -- This assures that there will be an entry for every column for k, v in pairs( self.Columns ) do Line:SetColumnText( k, "" ) end for k, v in pairs( {...} ) do Line:SetColumnText( k, v ) end -- Make appear at the bottom of the sorted list local SortID = table.insert( self.Sorted, Line ) if ( SortID % 2 == 1 ) then Line:SetAltLine( true ) end return Line end function PANEL:OnMouseWheeled( dlta ) if ( !IsValid( self.VBar ) ) then return end return self.VBar:OnMouseWheeled( dlta ) end function PANEL:ClearSelection( dlta ) for k, Line in pairs( self.Lines ) do Line:SetSelected( false ) end end function PANEL:GetSelectedLine() for k, Line in pairs( self.Lines ) do if ( Line:IsSelected() ) then return k, Line end end end function PANEL:GetLine( id ) return self.Lines[ id ] end function PANEL:GetSortedID( line ) for k, v in pairs( self.Sorted ) do if ( v:GetID() == line ) then return k end end end function PANEL:OnClickLine( Line, bClear ) local bMultiSelect = self:GetMultiSelect() if ( !bMultiSelect && !bClear ) then return end -- -- Control, multi select -- if ( bMultiSelect && input.IsKeyDown( KEY_LCONTROL ) ) then bClear = false end -- -- Shift block select -- if ( bMultiSelect && input.IsKeyDown( KEY_LSHIFT ) ) then local Selected = self:GetSortedID( self:GetSelectedLine() ) if ( Selected ) then local LineID = self:GetSortedID( Line:GetID() ) local First = math.min( Selected, LineID ) local Last = math.max( Selected, LineID ) -- Fire off OnRowSelected for each non selected row for id = First, Last do local line = self.Sorted[ id ] if ( !line:IsLineSelected() ) then self:OnRowSelected( line:GetID(), line ) end line:SetSelected( true ) end -- Clear the selection and select only the required rows if ( bClear ) then self:ClearSelection() end for id = First, Last do local line = self.Sorted[ id ] line:SetSelected( true ) end return end end -- -- Check for double click -- if ( Line:IsSelected() && Line.m_fClickTime && ( !bMultiSelect || bClear ) ) then local fTimeDistance = SysTime() - Line.m_fClickTime if ( fTimeDistance < 0.3 ) then self:DoDoubleClick( Line:GetID(), Line ) return end end -- -- If it's a new mouse click, or this isn't -- multiselect we clear the selection -- if ( !bMultiSelect || bClear ) then self:ClearSelection() end if ( Line:IsSelected() ) then return end Line:SetSelected( true ) Line.m_fClickTime = SysTime() self:OnRowSelected( Line:GetID(), Line ) end function PANEL:SortByColumns( c1, d1, c2, d2, c3, d3, c4, d4 ) table.sort( self.Sorted, function( a, b ) if ( !IsValid( a ) ) then return true end if ( !IsValid( b ) ) then return false end if ( c1 && a:GetColumnText( c1 ) != b:GetColumnText( c1 ) ) then if ( d1 ) then a, b = b, a end return a:GetColumnText( c1 ) < b:GetColumnText( c1 ) end if ( c2 && a:GetColumnText( c2 ) != b:GetColumnText( c2 ) ) then if ( d2 ) then a, b = b, a end return a:GetColumnText( c2 ) < b:GetColumnText( c2 ) end if ( c3 && a:GetColumnText( c3 ) != b:GetColumnText( c3 ) ) then if ( d3 ) then a, b = b, a end return a:GetColumnText( c3 ) < b:GetColumnText( c3 ) end if ( c4 && a:GetColumnText( c4 ) != b:GetColumnText( c4 ) ) then if ( d4 ) then a, b = b, a end return a:GetColumnText( c4 ) < b:GetColumnText( c4 ) end return true end ) self:SetDirty( true ) self:InvalidateLayout() end function PANEL:SortByColumn( ColumnID, Desc ) table.sort( self.Sorted, function( a, b ) if ( Desc ) then a, b = b, a end local aval = a:GetSortValue( ColumnID ) || a:GetColumnText( ColumnID ) local bval = b:GetSortValue( ColumnID ) || b:GetColumnText( ColumnID ) -- Maintain nicer sorting for numbers if ( isnumber( aval ) && isnumber( bval ) ) then return aval < bval end return tostring( aval ) < tostring( bval ) end ) self:SetDirty( true ) self:InvalidateLayout() end function PANEL:SelectItem( Item ) if ( !Item ) then return end Item:SetSelected( true ) self:OnRowSelected( Item:GetID(), Item ) end function PANEL:SelectFirstItem() self:ClearSelection() self:SelectItem( self.Sorted[ 1 ] ) end function PANEL:DoDoubleClick( LineID, Line ) -- For Override end function PANEL:OnRowSelected( LineID, Line ) -- For Override end function PANEL:OnRowRightClick( LineID, Line ) -- For Override end function PANEL:Clear() for k, v in pairs( self.Lines ) do v:Remove() end self.Lines = {} self.Sorted = {} self:SetDirty( true ) end function PANEL:GetSelected() local ret = {} for k, v in pairs( self.Lines ) do if ( v:IsLineSelected() ) then table.insert( ret, v ) end end return ret end function PANEL:SizeToContents() self:SetHeight( self.pnlCanvas:GetTall() + self:GetHeaderHeight() ) end function PANEL:GenerateExample( ClassName, PropertySheet, Width, Height ) local ctrl = vgui.Create( ClassName ) ctrl:AddColumn( "Address" ) local Col2 = ctrl:AddColumn( "Port" ) Col2:SetFixedWidth( 30 ) for i = 1, 128 do ctrl:AddLine( "192.168.0." .. i, "80" ) end ctrl:SetSize( 300, 200 ) PropertySheet:AddSheet( ClassName, ctrl, nil, true, true ) end derma.DefineControl( "DListView", "Data View", PANEL, "DPanel" )