mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
606 lines
12 KiB
Lua
606 lines
12 KiB
Lua
--[[
|
|
| 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" )
|