dobrograd-13-06-2022/garrysmod/addons/feature-wire/lua/wire/client/wire_filebrowser.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

681 lines
18 KiB
Lua

// A file browser panel, used by the sound browser.
// Can be used for any file type, recommend for huge file numbers.
// Made by Grocel.
local PANEL = {}
AccessorFunc( PANEL, "m_strRootName", "RootName" ) // name of the root Root
AccessorFunc( PANEL, "m_strRootPath", "RootPath" ) // path of the root Root
AccessorFunc( PANEL, "m_strWildCard", "WildCard" ) // "GAME", "DATA" etc.
AccessorFunc( PANEL, "m_tFilter", "FileTyps" ) // "*.wav", "*.mdl", {"*.vmt", "*.vtf"} etc.
AccessorFunc( PANEL, "m_strOpenPath", "OpenPath" ) // open path
AccessorFunc( PANEL, "m_strOpenFile", "OpenFile" ) // open path+file
AccessorFunc( PANEL, "m_strOpenFilename", "OpenFilename" ) // open file
AccessorFunc( PANEL, "m_nListSpeed", "ListSpeed" ) // how many items to list an once
AccessorFunc( PANEL, "m_nMaxItemsPerPage", "MaxItemsPerPage" ) // how may items per page
AccessorFunc( PANEL, "m_nPage", "Page" ) // Page to show
local invalid_chars = {
["\n"] = "",
["\r"] = "",
["\\"] = "/",
["//"] = "/",
//["/.svn"] = "", // Disallow access to .svn folders. (Not needed.)
//["/.git"] = "", // Disallow access to .git folders. (Not needed.)
}
local function ConnectPathes(path1, path2)
local path = ""
if (isstring(path1) and path1 ~= "") then
path = path1
if (isstring(path2) and path2 ~= "") then
path = path1.."/"..path2
end
else
if (isstring(path2) and path2 ~= "") then
path = path2
end
end
return path
end
local function PathFilter(Folder, TxtPanel, Root)
if (!isstring(Folder) or Folder == "") then return end
local ValidFolder = Folder
//local ValidFolder = string.lower(Folder) // for .svn and .git filters.
for k, v in pairs(invalid_chars) do
for i = 1, #string.Explode(k, ValidFolder) do
if (!string.match(ValidFolder, k)) then break end
ValidFolder = string.gsub(ValidFolder, k, v)
end
end
ValidFolder = string.Trim(ValidFolder)
/*if (string.sub(ValidFolder, 0, 4) == ".svn") then // Disallow access to .svn folders. (Not needed.)
ValidFolder = string.sub(ValidFolder, -4)
if (ValidFolder == ".svn") then
ValidFolder = ""
end
end*/
/*if (string.sub(ValidFolder, 0, 4) == ".git") then // Disallow access to .git folders. (Not needed.)
ValidFolder = string.sub(ValidFolder, -4)
if (ValidFolder == ".git") then
ValidFolder = ""
end
end*/
ValidFolder = string.Trim(ValidFolder, "/")
if (IsValid(TxtPanel)) then
TxtPanel:SetText(ValidFolder)
end
local Dirs = #string.Explode("/", ValidFolder)
for i = 1, Dirs do
if (!file.IsDir(ConnectPathes(Root, ValidFolder), "GAME")) then
ValidFolder = string.GetPathFromFilename(ValidFolder)
ValidFolder = string.Trim(ValidFolder, "/")
end
end
ValidFolder = string.Trim(ValidFolder, "/")
if (ValidFolder == "") then return end
return ValidFolder
end
local function EnableButton(button, bool)
button:SetEnabled(bool)
button:SetMouseInputEnabled(bool)
end
local function BuildFileList(path, filter, wildcard)
local files = {}
if (istable(filter)) then
for k, v in ipairs(filter) do
table.Add(files, file.Find(ConnectPathes(path, v), wildcard or "GAME"))
end
else
table.Add(files, file.Find(ConnectPathes(path, tostring(filter)), wildcard or "GAME"))
end
table.sort(files)
return files
end
local function NavigateToFolder(self, path)
if (!IsValid(self)) then return end
path = ConnectPathes(self.m_strRootPath, path)
local root = self.Tree:Root()
if (!IsValid(root)) then return end
if (!IsValid(root.ChildNodes)) then return end
local nodes = root.ChildNodes:GetChildren()
local lastnode = nil
local nodename = ""
self.NotUserPressed = true
local dirs = string.Explode("/", path)
for k, v in ipairs(dirs) do
if (nodename == "") then
nodename = string.lower(v)
else
nodename = nodename .. "/" .. string.lower(v)
if (!IsValid(lastnode)) then continue end
if (!IsValid(lastnode.ChildNodes)) then continue end
nodes = lastnode.ChildNodes:GetChildren()
end
local found = false
for _, node in pairs(nodes) do
if (!IsValid(node)) then continue end
local path = string.lower(node.m_strFolder)
if ( nodename == "" ) then break end
if ( path ~= nodename or found) then
node:SetExpanded(false)
continue
end
if (k == #dirs) then // just select the last one
self.Tree:SetSelectedItem(node)
end
node:SetExpanded(true)
lastnode = node
found = true
end
end
self.NotUserPressed = false
end
local function ShowFolder(self, path)
if (!IsValid(self)) then return end
self.m_strOpenPath = path
path = ConnectPathes(self.m_strRootPath, path)
self.oldpage = nil
self.Files = BuildFileList(path, self.m_tFilter, self.m_strWildCard)
self.m_nPage = 0
self.m_nPageCount = math.ceil(#self.Files / self.m_nMaxItemsPerPage)
self.PageMode = self.m_nPageCount > 1
self.PageChoosePanel:SetVisible(self.PageMode)
if (self.m_nPageCount <= 0 or !self.PageMode) then
self.m_nPageCount = 1
self:SetPage(1)
return
end
self.PageChooseNumbers:Clear(true)
self.PageChooseNumbers.Buttons = {}
for i=1, self.m_nPageCount do
self.PageChooseNumbers.Buttons[i] = self.PageChooseNumbers:Add("DButton")
local button = self.PageChooseNumbers.Buttons[i]
button:SetWide(self.PageButtonSize)
button:Dock(LEFT)
button:SetText(tostring(i))
button:SetVisible(false)
button:SetToolTip("Page " .. i .. " of " .. self.m_nPageCount)
button.DoClick = function(panel)
self:SetPage(i)
self:LayoutPages(true)
end
end
self:SetPage(1)
end
--[[---------------------------------------------------------
Name: Init
-----------------------------------------------------------]]
function PANEL:Init()
self.TimedpairsName = "wire_filebrowser_items_" .. tostring({})
self.PageButtonSize = 20
self:SetListSpeed(6)
self:SetMaxItemsPerPage(200)
self.m_nPageCount = 1
self.m_strOpenPath = nil
self.m_strOpenFile = nil
self.m_strOpenFilename = nil
self:SetDrawBackground(false)
self.FolderPathPanel = self:Add("DPanel")
self.FolderPathPanel:DockMargin(0, 0, 0, 3)
self.FolderPathPanel:SetTall(20)
self.FolderPathPanel:Dock(TOP)
self.FolderPathPanel:SetDrawBackground(false)
self.FolderPathText = self.FolderPathPanel:Add("DTextEntry")
self.FolderPathText:DockMargin(0, 0, 3, 0)
self.FolderPathText:Dock(FILL)
self.FolderPathText.OnEnter = function(panel)
self:SetOpenPath(panel:GetValue())
end
self.RefreshIcon = self.FolderPathPanel:Add("DImageButton") // The Folder Button.
self.RefreshIcon:SetImage("icon16/arrow_refresh.png")
self.RefreshIcon:SetWide(20)
self.RefreshIcon:Dock(RIGHT)
self.RefreshIcon:SetToolTip("Refresh")
self.RefreshIcon:SetStretchToFit(false)
self.RefreshIcon.DoClick = function()
self:Refresh()
end
self.FolderPathIcon = self.FolderPathPanel:Add("DImageButton") // The Folder Button.
self.FolderPathIcon:SetImage("icon16/folder_explore.png")
self.FolderPathIcon:SetWide(20)
self.FolderPathIcon:Dock(RIGHT)
self.FolderPathIcon:SetToolTip("Open Folder")
self.FolderPathIcon:SetStretchToFit(false)
self.FolderPathIcon.DoClick = function()
self.FolderPathText:OnEnter()
end
self.NotUserPressed = false
self.Tree = vgui.Create( "DTree" )
self.Tree:SetClickOnDragHover(false)
self.Tree.OnNodeSelected = function( parent, node )
local path = node.m_strFolder
if ( !path ) then return end
path = string.sub(path, #self.m_strRootPath+1)
path = string.Trim(path, "/")
if (!self.NotUserPressed) then
self.FolderPathText:SetText(path)
end
if (self.m_strOpenPath == path) then return end
ShowFolder(self, path)
end
self.PagePanel = vgui.Create("DPanel")
self.PagePanel:SetDrawBackground(false)
self.PageChoosePanel = self.PagePanel:Add("DPanel")
self.PageChoosePanel:DockMargin(0, 0, 0, 0)
self.PageChoosePanel:SetTall(self.PageButtonSize)
self.PageChoosePanel:Dock(BOTTOM)
self.PageChoosePanel:SetDrawBackground(false)
self.PageChoosePanel:SetVisible(false)
self.PageLastLeftButton = self.PageChoosePanel:Add("DButton")
self.PageLastLeftButton:SetWide(self.PageButtonSize)
self.PageLastLeftButton:Dock(LEFT)
self.PageLastLeftButton:SetText("<<")
self.PageLastLeftButton.DoClick = function(panel)
self:SetPage(1)
end
self.PageLastRightButton = self.PageChoosePanel:Add("DButton")
self.PageLastRightButton:SetWide(self.PageButtonSize)
self.PageLastRightButton:Dock(RIGHT)
self.PageLastRightButton:SetText(">>")
self.PageLastRightButton.DoClick = function(panel)
self:SetPage(self.m_nPageCount)
end
self.PageLeftButton = self.PageChoosePanel:Add("DButton")
self.PageLeftButton:SetWide(self.PageButtonSize)
self.PageLeftButton:Dock(LEFT)
self.PageLeftButton:SetText("<")
self.PageLeftButton.DoClick = function(panel)
if (self.m_nPage <= 1 or !self.PageMode) then
self.m_nPage = 1
return
end
self:SetPage(self.m_nPage - 1)
end
self.PageRightButton = self.PageChoosePanel:Add("DButton")
self.PageRightButton:SetWide(self.PageButtonSize)
self.PageRightButton:Dock(RIGHT)
self.PageRightButton:SetText(">")
self.PageRightButton.DoClick = function(panel)
if (self.m_nPage >= self.m_nPageCount or !self.PageMode) then
self.m_nPage = self.m_nPageCount
return
end
self:SetPage(self.m_nPage + 1)
end
self.PageChooseNumbers = self.PageChoosePanel:Add("DPanel")
self.PageChooseNumbers:DockMargin(0, 0, 0, 0)
self.PageChooseNumbers:SetSize(self.PageChoosePanel:GetWide()-60, self.PageChoosePanel:GetTall())
self.PageChooseNumbers:Center()
self.PageChooseNumbers:SetDrawBackground(false)
self.PageLoadingProgress = self.PagePanel:Add("DProgress")
self.PageLoadingProgress:DockMargin(0, 0, 0, 0)
self.PageLoadingProgress:SetTall(self.PageButtonSize)
self.PageLoadingProgress:Dock(BOTTOM)
self.PageLoadingProgress:SetVisible(false)
self.PageLoadingLabel = self.PageLoadingProgress:Add("DLabel")
self.PageLoadingLabel:SizeToContents()
self.PageLoadingLabel:Center()
self.PageLoadingLabel:SetText("")
self.PageLoadingLabel:SetPaintBackground(false)
self.PageLoadingLabel:SetDark(true)
self.List = self.PagePanel:Add( "DListView" )
self.List:Dock( FILL )
self.List:SetMultiSelect(false)
local Column = self.List:AddColumn("Name")
Column:SetMinWidth(150)
Column:SetWide(200)
self.List.OnRowSelected = function(parent, id, line)
local name = line.m_strFilename
local path = line.m_strPath
local file = line.m_strFile
self.m_strOpenFilename = name
self.m_strOpenFile = file
self:DoClick(file, path, name, parent, line)
end
self.List.DoDoubleClick = function(parent, id, line)
local name = line.m_strFilename
local path = line.m_strPath
local file = line.m_strFile
self.m_strOpenFilename = name
self.m_strOpenFile = file
self:DoDoubleClick(file, path, name, parent, line)
end
self.List.OnRowRightClick = function(parent, id, line)
local name = line.m_strFilename
local path = line.m_strPath
local file = line.m_strFile
self.m_strOpenFilename = name
self.m_strOpenFile = file
self:DoRightClick(file, path, name, parent, line)
end
self.SplitPanel = self:Add( "DHorizontalDivider" )
self.SplitPanel:Dock( FILL )
self.SplitPanel:SetLeft(self.Tree)
self.SplitPanel:SetRight(self.PagePanel)
self.SplitPanel:SetLeftWidth(200)
self.SplitPanel:SetLeftMin(150)
self.SplitPanel:SetRightMin(300)
self.SplitPanel:SetDividerWidth(3)
end
function PANEL:Refresh()
local file = self:GetOpenFile()
local page = self:GetPage()
self.bSetup = self:Setup()
self:SetOpenFile(file)
self:SetPage(page)
end
function PANEL:UpdatePageToolTips()
self.PageLeftButton:SetToolTip("Previous Page (" .. self.m_nPage - 1 .. " of " .. self.m_nPageCount .. ")")
self.PageRightButton:SetToolTip("Next Page (" .. self.m_nPage + 1 .. " of " .. self.m_nPageCount .. ")")
self.PageLastRightButton:SetToolTip("Last Page (" .. self.m_nPageCount .. " of " .. self.m_nPageCount .. ")")
self.PageLastLeftButton:SetToolTip("First Page (1 of " .. self.m_nPageCount .. ")")
end
function PANEL:LayoutPages(forcelayout)
if (!self.PageChoosePanel:IsVisible()) then
self.oldpage = nil
return
end
local x, y = self.PageRightButton:GetPos()
local Wide = x - self.PageLeftButton:GetWide()-40
if (Wide <= 0 or forcelayout) then
self.oldpage = nil
self:InvalidateLayout()
return
end
if (self.oldpage == self.m_nPage and self.oldpage and self.m_nPage) then return end
self.oldpage = self.m_nPage
if (self.m_nPage >= self.m_nPageCount) then
EnableButton(self.PageLeftButton, true)
EnableButton(self.PageRightButton, false)
EnableButton(self.PageLastLeftButton, true)
EnableButton(self.PageLastRightButton, false)
elseif (self.m_nPage <= 1) then
EnableButton(self.PageLeftButton, false)
EnableButton(self.PageRightButton, true)
EnableButton(self.PageLastLeftButton, false)
EnableButton(self.PageLastRightButton, true)
else
EnableButton(self.PageLeftButton, true)
EnableButton(self.PageRightButton, true)
EnableButton(self.PageLastLeftButton, true)
EnableButton(self.PageLastRightButton, true)
end
local ButtonCount = math.ceil(math.floor(Wide/self.PageButtonSize)/2)
local pagepos = math.Clamp(self.m_nPage, ButtonCount, self.m_nPageCount-ButtonCount+1)
local VisibleButtons = 0
for i=1, self.m_nPageCount do
local button = self.PageChooseNumbers.Buttons[i]
if (!IsValid(button)) then continue end
if (pagepos < i+ButtonCount and pagepos >= i-ButtonCount+1) then
button:SetVisible(true)
EnableButton(button, true)
VisibleButtons = VisibleButtons + 1
else
button:SetVisible(false)
EnableButton(button, false)
end
button.Depressed = false
end
local SelectButton = self.PageChooseNumbers.Buttons[self.m_nPage]
if (IsValid(SelectButton)) then
SelectButton.Depressed = true
SelectButton:SetMouseInputEnabled(false)
end
self.PageChooseNumbers:SetWide(VisibleButtons*self.PageButtonSize)
self.PageChooseNumbers:Center()
end
function PANEL:AddColumns(...)
local Column = {}
for k, v in ipairs({...}) do
Column[k] = self.List:AddColumn(v)
end
return Column
end
function PANEL:Think()
if (self.SplitPanel:GetDragging()) then
self.oldpage = nil
self:InvalidateLayout()
end
if ( !self.bSetup ) then
self.bSetup = self:Setup()
end
end
function PANEL:PerformLayout()
self:LayoutPages()
self.Tree:InvalidateLayout()
self.List:InvalidateLayout()
local minw = self:GetWide() - self.SplitPanel:GetRightMin() - self.SplitPanel:GetDividerWidth()
local oldminw = self.SplitPanel:GetLeftWidth(minw)
if (oldminw > minw) then
self.SplitPanel:SetLeftWidth(minw)
end
//Fixes scrollbar glitches on resize
self.Tree:OnMouseWheeled(0)
self.List:OnMouseWheeled(0)
if (!self.PageLoadingProgress:IsVisible()) then return end
self.PageLoadingLabel:SizeToContents()
self.PageLoadingLabel:Center()
end
function PANEL:Setup()
if (!self.m_strRootName) then return false end
if (!self.m_strRootPath) then return false end
WireLib.TimedpairsStop(self.TimedpairsName)
self.m_strOpenPath = nil
self.m_strOpenFile = nil
self.m_strOpenFilename = nil
self.oldpage = nil
self.Tree:Clear(true)
if (IsValid(self.Root)) then
self.Root:Remove()
end
self.Root = self.Tree.RootNode:AddFolder( self.m_strRootName, self.m_strRootPath, self.m_strWildCard or "GAME", false)
return true
end
function PANEL:SetOpenFilename(filename)
if(!isstring(filename)) then filename = "" end
self.m_strOpenFilename = filename
self.m_strOpenFile = ConnectPathes(self.m_strOpenPath, self.m_strOpenFilename)
end
function PANEL:SetOpenPath(path)
self.Root:SetExpanded(true)
path = PathFilter(path, self.FolderPathText, self.m_strRootPath) or ""
if (self.m_strOpenPath == path) then return end
self.oldpage = nil
NavigateToFolder(self, path)
self.m_strOpenPath = path
self.m_strOpenFile = ConnectPathes(self.m_strOpenPath, self.m_strOpenFilename)
end
function PANEL:SetOpenFile(file)
if(!isstring(file)) then file = "" end
self:SetOpenPath(string.GetPathFromFilename(file))
self:SetOpenFilename(string.GetFileFromFilename("/" .. file))
end
function PANEL:SetPage(page)
if (page < 1) then return end
if (page > self.m_nPageCount) then return end
if (page == self.m_nPage) then return end
WireLib.TimedpairsStop(self.TimedpairsName)
self.List:Clear(true)
self.m_nPage = page
self:UpdatePageToolTips()
local filepage
if(self.PageMode) then
filepage = {}
for i=1, self.m_nMaxItemsPerPage do
local index = i + self.m_nMaxItemsPerPage * (page - 1)
local value = self.Files[index]
if (!value) then break end
filepage[i] = value
end
else
filepage = self.Files
end
local Fraction = 0
local FileCount = #filepage
local ShowProgress = (FileCount > self.m_nListSpeed * 5)
self.PageLoadingProgress:SetVisible(ShowProgress)
if (FileCount <= 0) then
self.PageLoadingProgress:SetVisible(false)
return
end
self.PageLoadingProgress:SetFraction(Fraction)
self.PageLoadingLabel:SetText("0 of " .. FileCount .. " files found.")
self.PageLoadingLabel:SizeToContents()
self.PageLoadingLabel:Center()
self:InvalidateLayout()
WireLib.Timedpairs(self.TimedpairsName, filepage, self.m_nListSpeed, function(id, name, self)
if (!IsValid(self)) then return false end
if (!IsValid(self.List)) then return false end
local file = ConnectPathes(self.m_strOpenPath, name)
local args, bcontinue, bbreak = self:LineData(id, file, self.m_strOpenPath, name)
if (bcontinue) then return end // continue
if (bbreak) then return false end // break
local line = self.List:AddLine(name, unpack(args or {}))
if (!IsValid(line)) then return end
line.m_strPath = self.m_strOpenPath
line.m_strFilename = name
line.m_strFile = file
if (self.m_strOpenFile == file) then
self.List:SelectItem(line)
end
self:OnLineAdded(id, line, file, self.m_strOpenPath, name)
Fraction = id / FileCount
if (!IsValid(self.PageLoadingProgress)) then return end
if (!ShowProgress) then return end
self.PageLoadingProgress:SetFraction(Fraction)
self.PageLoadingLabel:SetText(id .. " of " .. FileCount .. " files found.")
self.PageLoadingLabel:SizeToContents()
self.PageLoadingLabel:Center()
end, function(id, name, self)
if (!IsValid(self)) then return end
Fraction = 1
if (!IsValid(self.PageLoadingProgress)) then return end
if (!ShowProgress) then return end
self.PageLoadingProgress:SetFraction(Fraction)
self.PageLoadingLabel:SetText(id .. " of " .. FileCount .. " files found.")
self.PageLoadingLabel:SizeToContents()
self.PageLoadingLabel:Center()
self.PageLoadingProgress:SetVisible(false)
self:InvalidateLayout()
end, self)
end
function PANEL:DoClick(file, path, name)
-- Override
end
function PANEL:DoDoubleClick(file, path, name)
-- Override
end
function PANEL:DoRightClick(file, path, name)
-- Override
end
function PANEL:LineData(id, file, path, name)
return // to override
end
function PANEL:OnLineAdded(id, line, file, path, name)
return // to override
end
vgui.Register("wire_filebrowser", PANEL, "DPanel")