#CLIENTMENU

* Rewrite with a different data approach
This commit is contained in:
Applevangelist 2023-07-17 16:26:53 +02:00
parent bb579fff5b
commit 2c8adf58cb

View File

@ -3,12 +3,14 @@
-- **Main Features:** -- **Main Features:**
-- --
-- * For complex, non-static menu structures -- * For complex, non-static menu structures
-- * Separation of menu tree creation from pushing it to clients -- * Lightweigt implementation as alternative to MENU
-- * Separation of menu tree creation from menu on the clients's side
-- * Works with a SET_CLIENT set of clients -- * Works with a SET_CLIENT set of clients
-- * Allow manipulation of the shadow tree in various ways -- * Allow manipulation of the shadow tree in various ways
-- * Push to all or only one client -- * Push to all or only one client
-- * Change entries' menu text, even if they have a sub-structure -- * Change entries' menu text
-- * Option to make an entry usable once -- * Option to make an entry usable once only across all clients
-- * Auto appends GROUP and CLIENT objects to menu calls
-- --
-- === -- ===
-- --
@ -37,9 +39,10 @@
-- @field #table parentpath -- @field #table parentpath
-- @field #CLIENTMENU Parent -- @field #CLIENTMENU Parent
-- @field Wrapper.Client#CLIENT client -- @field Wrapper.Client#CLIENT client
-- @field #number GID -- @field #number GroupID Group ID
-- @field #number ID -- @field #number ID Entry ID
-- @field Wrapper.Group#GROUP group -- @field Wrapper.Group#GROUP group
-- @field #string UUID Unique ID based on path+name
-- @field #string Function -- @field #string Function
-- @field #table Functionargs -- @field #table Functionargs
-- @field #table Children -- @field #table Children
@ -54,12 +57,12 @@
CLIENTMENU = { CLIENTMENU = {
ClassName = "CLIENTMENUE", ClassName = "CLIENTMENUE",
lid = "", lid = "",
version = "0.0.1", version = "0.1.0",
name = nil, name = nil,
path = nil, path = nil,
group = nil, group = nil,
client = nil, client = nil,
GID = nil, GroupID = nil,
Children = {}, Children = {},
Once = false, Once = false,
Generic = false, Generic = false,
@ -87,20 +90,26 @@ function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
if Client then if Client then
self.group = Client:GetGroup() self.group = Client:GetGroup()
self.client = Client self.client = Client
self.GID = self.group:GetID() self.GroupID = self.group:GetID()
else else
self.Generic = true self.Generic = true
end end
self.name = Text or "unknown entry" self.name = Text or "unknown entry"
if Parent then if Parent then
self.parentpath = Parent:GetPath() if Parent:IsInstanceOf("MENU_BASE") then
Parent:AddChild(self) self.parentpath = Parent.MenuPath
else
self.parentpath = Parent:GetPath()
Parent:AddChild(self)
end
end end
self.Parent = Parent self.Parent = Parent
self.Function = Function self.Function = Function
self.Functionargs = arg self.Functionargs = arg or {}
table.insert(self.Functionargs,self.group)
table.insert(self.Functionargs,self.client)
if self.Functionargs and self.debug then if self.Functionargs and self.debug then
self:I({"Functionargs",self.Functionargs}) self:T({"Functionargs",self.Functionargs})
end end
if not self.Generic then if not self.Generic then
if Function ~= nil then if Function ~= nil then
@ -120,9 +129,9 @@ function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
self:Clear() self:Clear()
end end
end end
self.path = missionCommands.addCommandForGroup(self.GID,Text,self.parentpath, self.CallHandler) self.path = missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath, self.CallHandler)
else else
self.path = missionCommands.addSubMenuForGroup(self.GID,Text,self.parentpath) self.path = missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath)
end end
else else
if self.parentpath then if self.parentpath then
@ -131,8 +140,9 @@ function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
self.path = {} self.path = {}
end end
self.path[#self.path+1] = Text self.path[#self.path+1] = Text
self:T({self.path})
end end
self.UUID = table.concat(self.path,";")
self:T({self.UUID})
self.Once = false self.Once = false
-- Log id. -- Log id.
self.lid=string.format("CLIENTMENU %s | %s | ", self.ID, self.name) self.lid=string.format("CLIENTMENU %s | %s | ", self.ID, self.name)
@ -140,6 +150,21 @@ function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
return self return self
end end
--- Create a UUID
-- @param #CLIENTMENU self
-- @param #CLIENTMENU Parent The parent object if any
-- @param #string Text The menu entry text
-- @return #string UUID
function CLIENTMENU:CreateUUID(Parent,Text)
local path = {}
if Parent and Parent.path then
path = Parent.path
end
path[#path+1] = Text
local UUID = table.concat(path,";")
return UUID
end
--- Set the CLIENTMENUMANAGER for this entry. --- Set the CLIENTMENUMANAGER for this entry.
-- @param #CLIENTMENU self -- @param #CLIENTMENU self
-- @param #CLIENTMENUMANAGER Controller The controlling object. -- @param #CLIENTMENUMANAGER Controller The controlling object.
@ -163,8 +188,9 @@ end
-- @return #CLIENTMENU self -- @return #CLIENTMENU self
function CLIENTMENU:RemoveF10() function CLIENTMENU:RemoveF10()
self:T(self.lid.."RemoveF10") self:T(self.lid.."RemoveF10")
if not self.Generic then if self.GroupID then
missionCommands.removeItemForGroup(self.GID , self.path ) --self:I(self.lid.."Removing "..table.concat(self.path,";"))
missionCommands.removeItemForGroup(self.GroupID , self.path )
end end
return self return self
end end
@ -177,6 +203,14 @@ function CLIENTMENU:GetPath()
return self.path return self.path
end end
--- Get the UUID.
-- @param #CLIENTMENU self
-- @return #string UUID
function CLIENTMENU:GetUUID()
self:T(self.lid.."GetUUID")
return self.UUID
end
--- Link a child entry. --- Link a child entry.
-- @param #CLIENTMENU self -- @param #CLIENTMENU self
-- @param #CLIENTMENU Child The entry to link as a child. -- @param #CLIENTMENU Child The entry to link as a child.
@ -202,7 +236,7 @@ end
-- @return #CLIENTMENU self -- @return #CLIENTMENU self
function CLIENTMENU:RemoveSubEntries() function CLIENTMENU:RemoveSubEntries()
self:T(self.lid.."RemoveSubEntries") self:T(self.lid.."RemoveSubEntries")
--self:T({self.Children}) self:T({self.Children})
for _id,_entry in pairs(self.Children) do for _id,_entry in pairs(self.Children) do
self:T("Removing ".._id) self:T("Removing ".._id)
if _entry then if _entry then
@ -211,10 +245,10 @@ function CLIENTMENU:RemoveSubEntries()
if _entry.Parent then if _entry.Parent then
_entry.Parent:RemoveChild(self) _entry.Parent:RemoveChild(self)
end end
if self.Controller then --if self.Controller then
self.Controller:_RemoveByID(_entry.ID) --self.Controller:_RemoveByID(_entry.ID)
end --end
_entry = nil --_entry = nil
end end
end end
return self return self
@ -235,9 +269,9 @@ function CLIENTMENU:Clear()
if self.Parent then if self.Parent then
self.Parent:RemoveChild(self) self.Parent:RemoveChild(self)
end end
if self.Controller then --if self.Controller then
self.Controller:_RemoveByID(self.ID) --self.Controller:_RemoveByID(self.ID)
end --end
return self return self
end end
@ -256,9 +290,9 @@ end
-- @field #string version Version string -- @field #string version Version string
-- @field #string name Name -- @field #string name Name
-- @field Core.Set#SET_CLIENT clientset The set of clients this menu manager is for -- @field Core.Set#SET_CLIENT clientset The set of clients this menu manager is for
-- @field #table structure -- @field #table flattree
-- @field #table replacementstructure
-- @field #table rootentries -- @field #table rootentries
-- @field #table menutree
-- @field #number entrycount -- @field #number entrycount
-- @field #boolean debug -- @field #boolean debug
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
@ -310,27 +344,28 @@ end
-- --
-- ## Remove a single entry and also it's subtree -- ## Remove a single entry and also it's subtree
-- --
-- menumgr:Clear(mymenu_lv3a) -- menumgr:DeleteEntry(mymenu_lv3a)
-- --
-- ## Add a single entry -- ## Add a single entry
-- --
-- local baimenu = menumgr:NewEntry("BAI",mymenu_lv1b) -- local baimenu = menumgr:NewEntry("BAI",mymenu_lv1b)
-- menumgr:AddEntry(baimenu) --
-- menumgr:AddEntry(baimenu)
--
-- ## Add an entry with a function
--
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, Argument1, Argument1)
--
-- Now, the class will **automatically append the call with GROUP and CLIENT objects**, as this is can only be done when pushing the entry to the clients. So, the actual function implementation needs to look like this:
--
-- function TestFunction( Argument1, Argument2, Group, Client)
--
-- **Caveat is**, that you need to ensure your arguments are not **nil** or **false**, as LUA will optimize those away. You would end up having Group and Client in wrong places in the function call. Hence,
-- if you need/ want to send **nil** or **false**, send a place holder instead and ensure your function can handle this, e.g.
--
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, "nil", Argument1)
-- --
-- ## Prepare and push a partial replacement in the tree -- ## Change the text of a leaf entry in the menu tree
--
-- menumgr:PrepareNewReplacementStructure()
-- local submenu = menumgr:NewReplacementEntry("New Level 2 ba",mymenu_lv2a)
-- menumgr:NewReplacementEntry("New Level 2 bb",mymenu_lv2a)
-- menumgr:NewReplacementEntry("Deleted",mymenu_lv2a)
-- menumgr:NewReplacementEntry("New Level 2 bd",mymenu_lv2a)
-- menumgr:NewReplacementEntry("SubLevel 3 baa",submenu)
-- menumgr:NewReplacementEntry("SubLevel 3 bab",submenu)
-- menumgr:NewReplacementEntry("SubLevel 3 bac",submenu)
-- menumgr:NewReplacementEntry("SubLevel 3 bad",submenu)
-- menumgr:ReplaceEntries(mymenu_lv2a)
--
-- ## Change the text of an entry in the menu tree
-- --
-- menumgr:ChangeEntryTextForAll(mymenu_lv1b,"Attack") -- menumgr:ChangeEntryTextForAll(mymenu_lv1b,"Attack")
-- --
@ -346,31 +381,17 @@ end
CLIENTMENUMANAGER = { CLIENTMENUMANAGER = {
ClassName = "CLIENTMENUMANAGER", ClassName = "CLIENTMENUMANAGER",
lid = "", lid = "",
version = "0.0.1", version = "0.1.0",
name = nil, name = nil,
clientset = nil, clientset = nil,
--- menutree = {},
-- @field #CLIENTMENUMANAGER.Structure flattree = {},
structure = { playertree = {},
generic = {},
IDs = {},
},
---
-- #CLIENTMENUMANAGER.ReplacementStructure
replacementstructure = {
generic = {},
IDs = {},
},
entrycount = 0, entrycount = 0,
rootentries = {}, rootentries = {},
debug = true, debug = true,
} }
---
-- @type CLIENTMENUMANAGER.Structure
-- @field #table generic
-- @field #table IDs
--- Create a new ClientManager instance. --- Create a new ClientManager instance.
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @param Core.Set#SET_CLIENT ClientSet The set of clients to manage. -- @param Core.Set#SET_CLIENT ClientSet The set of clients to manage.
@ -384,7 +405,7 @@ function CLIENTMENUMANAGER:New(ClientSet, Alias)
-- Log id. -- Log id.
self.lid=string.format("CLIENTMENUMANAGER %s | %s | ", self.version, self.name) self.lid=string.format("CLIENTMENUMANAGER %s | %s | ", self.version, self.name)
if self.debug then if self.debug then
self:I(self.lid.."Created") self:T(self.lid.."Created")
end end
return self return self
end end
@ -400,271 +421,177 @@ function CLIENTMENUMANAGER:NewEntry(Text,Parent,Function,...)
self:T(self.lid.."NewEntry "..Text or "None") self:T(self.lid.."NewEntry "..Text or "None")
self.entrycount = self.entrycount + 1 self.entrycount = self.entrycount + 1
local entry = CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg)) local entry = CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg))
self.structure.generic[self.entrycount] = entry
self.structure.IDs[entry.ID] = self.entrycount
if not Parent then if not Parent then
self.rootentries[self.entrycount] = self.entrycount self.rootentries[self.entrycount] = entry
end end
local depth = #entry.path
if not self.menutree[depth] then self.menutree[depth] = {} end
table.insert(self.menutree[depth],entry.UUID)
self.flattree[entry.UUID] = entry
return entry return entry
end end
--- Find **first** matching entry in the generic structure by the menu text. --- Check matching entry in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @param #string Text Text of the F10 menu entry. -- @param #string UUID UUID of the menu entry.
-- @return #boolean Exists
function CLIENTMENUMANAGER:EntryUUIDExists(UUID)
local exists = self.flattree[UUID] and true or false
return exists
end
--- Find matching entry in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #string UUID UUID of the menu entry.
-- @return #CLIENTMENU Entry The #CLIENTMENU object found or nil. -- @return #CLIENTMENU Entry The #CLIENTMENU object found or nil.
-- @return #number GID GID The GID found or nil. function CLIENTMENUMANAGER:FindEntryByUUID(UUID)
function CLIENTMENUMANAGER:FindEntryByText(Text) self:T(self.lid.."FindEntryByUUID "..UUID or "None")
self:T(self.lid.."FindEntryByText "..Text or "None")
local entry = nil local entry = nil
local gid = nil for _gid,_entry in pairs(self.flattree) do
for _gid,_entry in UTILS.spairs(self.structure.generic) do
local Entry = _entry -- #CLIENTMENU local Entry = _entry -- #CLIENTMENU
if Entry and Entry.name == Text then if Entry and Entry.UUID == UUID then
entry = Entry entry = Entry
gid = _gid
end end
end end
return entry, gid
end
--- Find first matching entry in the generic structure by the GID.
-- @param #CLIENTMENUMANAGER self
-- @param #number GID The GID of the entry to find.
-- @return #CLIENTMENU Entry The #CLIENTMENU object found or nil.
function CLIENTMENUMANAGER:GetEntryByGID(GID)
self:T(self.lid.."GetEntryByGID "..GID or "None")
if GID and type(GID) == "number" then
return self.structure.generic[GID]
else
return nil
end
end
--- Alter the text of an entry in the generic structure and push to all clients.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The menu entry.
-- @param #string Text Text of the F10 menu entry.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ChangeEntryTextForAll(Entry,Text)
self:T(self.lid.."ChangeEntryTextForAll "..Text or "None")
for _,_client in pairs(self.clientset.Set) do
local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then
self:ChangeEntryText(Entry,Text, client)
end
end
return self
end
--- Alter the text of an entry in the generic structure and push to one specific client.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The menu entry.
-- @param #string Text Text of the F10 menu entry.
-- @param Wrapper.Client#CLIENT Client The client for whom to alter the entry
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ChangeEntryText(Entry,Text, Client)
self:T(self.lid.."ChangeEntryText "..Text or "None")
local text = Text or "none"
local oldtext = Entry.name
Entry.name = text
local newstructure = {}
local changed = 0
local function ChangePath(path,oldtext,newtext)
local newpath = {}
for _id,_text in UTILS.spairs(path) do
local txt = _text
if _text == oldtext then
txt = newtext
end
newpath[_id] = txt
end
return newpath
end
local function AlterPath(children)
for _,_entry in pairs(children) do
local entry = _entry -- #CLIENTMENU
local newpath = ChangePath(entry.path,oldtext,text)
local newparentpath = ChangePath(entry.parentpath,oldtext,text)
entry.path = nil
entry.parentpath = nil
entry.path = newpath
entry.parentpath = newparentpath
self:T({entry.ID})
--self:T({entry.parentpath})
newstructure[entry.ID] = UTILS.DeepCopy(entry)
changed = changed + 1
if entry.Children and #entry.Children > 0 then
AlterPath(entry.Children)
end
end
end
-- get the entry
local ID = Entry.ID
local GID = self.structure.IDs[ID]
local playername = Client:GetPlayerName()
local children = self.structure[playername][GID].Children
AlterPath(children)
self:T("Changed entries: "..changed)
local NewParent = self:NewEntry(Entry.name,Entry.Parent,Entry.Function,unpack(Entry.Functionargs))
for _,_entry in pairs(children) do
self:T("Changed parent for ".._entry.ID.." | GID ".._entry.GID)
local entry = _entry -- #CLIENTMENU
entry.Parent = NewParent
end
self:PrepareNewReplacementStructure()
for _,_entry in pairs(newstructure) do
self:T("Changed entry: ".._entry.ID.." | GID ".._entry.GID)
local entry = _entry -- #CLIENTMENU
self:NewReplacementEntry(entry.name,entry.Parent,entry.Function,unpack(entry.Functionargs))
end
self:AddEntry(NewParent)
self:ReplaceEntries(NewParent)
self:Clear(Entry)
return self
end
--- Create a new entry in the replacement structure.
-- @param #CLIENTMENUMANAGER self
-- @param #string Text Text of the F10 menu entry.
-- @param #CLIENTMENU Parent The parent menu entry.
-- @param #string Function (optional) Function to call when the entry is used.
-- @param ... (optional) Arguments for the Function, comma separated
-- @return #CLIENTMENU Entry
function CLIENTMENUMANAGER:NewReplacementEntry(Text,Parent,Function,...)
self:T(self.lid.."NewReplacementEntry "..Text or "None")
self.entrycount = self.entrycount + 1
local entry = CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg))
self.replacementstructure.generic[self.entrycount] = entry
self.replacementstructure.IDs[entry.ID] = self.entrycount
local pID = Parent and Parent.ID or "none"
if self.debug then
self:I("Entry ID = "..self.entrycount.." | Parent ID = "..tostring(pID))
end
if not Parent then
self.rootentries[self.entrycount] = self.entrycount
end
return entry return entry
end end
--- Prepare a new replacement structure. Deletes the previous one. --- Find matching entries by text in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @return #CLIENTMENUMANAGER self -- @param #string Text Text or partial text of the menu entry to find.
function CLIENTMENUMANAGER:PrepareNewReplacementStructure() -- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
self:T(self.lid.."PrepareNewReplacementStructure") -- @return #table Table of matching UUIDs of #CLIENTMENU objects
self.replacementstructure = nil -- #CLIENTMENUMANAGER.Structure -- @return #table Table of matching #CLIENTMENU objects
self.replacementstructure = { -- @return #number Number of matches
generic = {}, function CLIENTMENUMANAGER:FindUUIDsByText(Text,Parent)
IDs = {}, self:T(self.lid.."FindUUIDsByText "..Text or "None")
} local matches = {}
return self local entries = {}
end local n = 0
for _uuid,_entry in pairs(self.flattree) do
--- [Internal] Merge the replacement structure into the generic structure. local Entry = _entry -- #CLIENTMENU
-- @param #CLIENTMENUMANAGER self if Parent then
-- @return #CLIENTMENUMANAGER self if Entry and string.find(Entry.name,Text) and string.find(Entry.UUID,Parent.UUID) then
function CLIENTMENUMANAGER:_MergeReplacementData() table.insert(matches,_uuid)
self:T(self.lid.."_MergeReplacementData") table.insert(entries,Entry )
for _id,_entry in pairs(self.replacementstructure.generic) do n=n+1
self.structure.generic[_id] = _entry end
end else
for _id,_entry in pairs(self.replacementstructure.IDs) do if Entry and string.find(Entry.name,Text) then
self.structure.IDs[_id] = _entry table.insert(matches,_uuid)
end table.insert(entries,Entry )
self:_CleanUpPlayerStructure() n=n+1
return self end
end
--- Replace entries under the Parent entry with the Replacement structure created prior for all clients.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Parent The parent entry under which to replace with the new structure.
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ReplaceEntries(Parent,Client)
self:T(self.lid.."ReplaceEntries")
-- clear Parent substructure
local Set = self.clientset.Set
if Client then
Set = {Client}
else
self:RemoveSubEntries(Parent)
end
for _,_client in pairs(Set) do
local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then
local playername = client:GetPlayerName()
--self.structure[playername] = {}
for _id,_entry in UTILS.spairs(self.replacementstructure.generic) do
local entry = _entry -- #CLIENTMENU
local parent = Parent
self:T("Posted Parent = "..Parent.ID)
if entry.Parent and entry.Parent.name then
parent = self:_GetParentEntry(self.replacementstructure.generic,entry.Parent.name) or Parent
self:T("Found Parent = "..parent.ID)
end
self.structure[playername][_id] = CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs))
self.structure[playername][_id].Once = entry.Once
end
end end
end end
self:_MergeReplacementData() return matches, entries, n
end
--- Find matching entries in the generic structure by the menu text.
-- @param #CLIENTMENUMANAGER self
-- @param #string Text Text or partial text of the F10 menu entry.
-- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
-- @return #table Table of matching #CLIENTMENU objects.
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindEntriesByText(Text,Parent)
self:T(self.lid.."FindEntriesByText "..Text or "None")
local matches, objects, number = self:FindUUIDsByText(Text, Parent)
return objects, number
end
--- Find matching entries under a parent in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Parent Find entries under this parent entry.
-- @return #table Table of matching UUIDs of #CLIENTMENU objects
-- @return #table Table of matching #CLIENTMENU objects
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindUUIDsByParent(Parent)
self:T(self.lid.."FindUUIDsByParent")
local matches = {}
local entries = {}
local n = 0
for _uuid,_entry in pairs(self.flattree) do
local Entry = _entry -- #CLIENTMENU
if Parent then
if Entry and string.find(Entry.UUID,Parent.UUID) then
table.insert(matches,_uuid)
table.insert(entries,Entry )
n=n+1
end
end
end
return matches, entries, n
end
--- Find matching entries in the generic structure under a parent.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Parent Find entries under this parent entry.
-- @return #table Table of matching #CLIENTMENU objects.
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindEntriesByParent(Parent)
self:T(self.lid.."FindEntriesByParent")
local matches, objects, number = self:FindUUIDsByParent(Parent)
return objects, number
end
--- Alter the text of a leaf entry in the generic structure and push to one specific client's F10 menu.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The menu entry.
-- @param #string Text New Text of the F10 menu entry.
-- @param Wrapper.Client#CLIENT Client (optional) The client for whom to alter the entry, if nil done for all clients.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ChangeEntryText(Entry, Text, Client)
self:T(self.lid.."ChangeEntryText "..Text or "None")
local newentry = CLIENTMENU:NewEntry(nil,Text,Entry.Parent,Entry.Function,unpack(Entry.Functionargs))
self:DeleteF10Entry(Entry,Client)
self:DeleteGenericEntry(Entry)
if not Entry.Parent then
self.rootentries[self.entrycount] = newentry
end
local depth = #newentry.path
if not self.menutree[depth] then self.menutree[depth] = {} end
table.insert(self.menutree[depth],newentry.UUID)
self.flattree[newentry.UUID] = newentry
self:AddEntry(newentry,Client)
return self return self
end end
--- [Internal] Find a parent entry in a given structure by name. --- Push the complete menu structure to each of the clients in the set - refresh the menu tree of the clients.
-- @param #CLIENTMENUMANAGER self
-- @param #table Structure Table of entries.
-- @param #string Name Name to find.
-- @return #CLIENTMENU Entry
function CLIENTMENUMANAGER:_GetParentEntry(Structure,Name)
self:T(self.lid.."_GetParentEntry")
local found = nil
for _,_entry in pairs(Structure) do
local entry = _entry -- #CLIENTMENU
if entry.name == Name then
found = entry
break
end
end
return found
end
--- Push the complete menu structure to each of the clients in the set.
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @param Wrapper.Client#CLIENT Client (optional) If given, propagate only for this client. -- @param Wrapper.Client#CLIENT Client (optional) If given, propagate only for this client.
-- @return #CLIENTMENU Entry -- @return #CLIENTMENU Entry
function CLIENTMENUMANAGER:Propagate(Client) function CLIENTMENUMANAGER:Propagate(Client)
self:T(self.lid.."Propagate") self:T(self.lid.."Propagate")
self:T(Client)
local Set = self.clientset.Set local Set = self.clientset.Set
if Client then if Client then
Set = {Set} Set = {Client}
end end
self:ResetMenu(Client)
for _,_client in pairs(Set) do for _,_client in pairs(Set) do
local client = _client -- Wrapper.Client#CLIENT local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then if client and client:IsAlive() then
local playername = client:GetPlayerName() local playername = client:GetPlayerName()
self.structure[playername] = {} if not self.playertree[playername] then
for _id,_entry in pairs(self.structure.generic) do self.playertree[playername] = {}
local entry = _entry -- #CLIENTMENU end
local parent = nil for level,branch in pairs (self.menutree) do
if entry.Parent and entry.Parent.name then self:T("Building branch:" .. level)
parent = self:_GetParentEntry(self.structure[playername],entry.Parent.name) for _,leaf in pairs(branch) do
self:T("Building leaf:" .. leaf)
local entry = self:FindEntryByUUID(leaf)
if entry then
self:T("Found generic entry:" .. entry.UUID)
local parent = nil
if entry.Parent and entry.Parent.UUID then
parent = self.playertree[playername][entry.Parent.UUID] or self:FindEntryByUUID(entry.Parent.UUID)
end
self.playertree[playername][entry.UUID] = CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs))
self.playertree[playername][entry.UUID].Once = entry.Once
else
self:T("NO generic entry for:" .. leaf)
end
end end
self.structure[playername][_id] = CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs))
self.structure[playername][_id].Once = entry.Once
end end
end end
end end
@ -686,85 +613,79 @@ function CLIENTMENUMANAGER:AddEntry(Entry,Client)
local client = _client -- Wrapper.Client#CLIENT local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then if client and client:IsAlive() then
local playername = client:GetPlayerName() local playername = client:GetPlayerName()
local entry = Entry -- #CLIENTMENU if Entry then
local parent = nil self:T("Adding generic entry:" .. Entry.UUID)
if entry.Parent and entry.Parent.name then local parent = nil
parent = self:_GetParentEntry(self.structure[playername],entry.Parent.name) if not self.playertree[playername] then
end self.playertree[playername] = {}
self.structure[playername][Entry.ID] = CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs)) end
self.structure[playername][Entry.ID].Once = entry.Once if Entry.Parent and Entry.Parent.UUID then
parent = self.playertree[playername][Entry.Parent.UUID] or self:FindEntryByUUID(Entry.Parent.UUID)
end
self.playertree[playername][Entry.UUID] = CLIENTMENU:NewEntry(client,Entry.name,parent,Entry.Function,unpack(Entry.Functionargs))
self.playertree[playername][Entry.UUID].Once = Entry.Once
else
self:T("NO generic entry given")
end
end end
end end
return self return self
end end
--- Blank out the menu - remove **all root entries** and all entries below from the client's menus, leaving the generic structure untouched. --- Blank out the menu - remove **all root entries** and all entries below from the client's F10 menus, leaving the generic structure untouched.
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @param Wrapper.Client#CLIENT Client -- @param Wrapper.Client#CLIENT Client (optional) If given, remove only for this client.
-- @return #CLIENTMENUMANAGER self -- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ResetMenu(Client) function CLIENTMENUMANAGER:ResetMenu(Client)
self:T(self.lid.."ResetMenu") self:T(self.lid.."ResetMenu")
for _,_entry in pairs(self.rootentries) do for _,_entry in pairs(self.rootentries) do
local RootEntry = self.structure.generic[_entry] --local RootEntry = self.structure.generic[_entry]
if RootEntry then if _entry then
self:Clear(RootEntry,Client) self:DeleteF10Entry(_entry,Client)
end end
end end
return self return self
end end
--- Blank out the menu - remove **all root entries** and all entries below from all clients' menus, and **delete** the generic structure. --- Blank out the menu - remove **all root entries** and all entries below from all clients' F10 menus, and **delete** the generic structure.
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @return #CLIENTMENUMANAGER self -- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ResetMenuComplete() function CLIENTMENUMANAGER:ResetMenuComplete()
self:T(self.lid.."ResetMenuComplete") self:T(self.lid.."ResetMenuComplete")
for _,_entry in pairs(self.rootentries) do for _,_entry in pairs(self.rootentries) do
local RootEntry = self.structure.generic[_entry] --local RootEntry = self.structure.generic[_entry]
if RootEntry then if _entry then
self:Clear(RootEntry) self:DeleteF10Entry(_entry)
end end
end end
self.structure = nil self.playertree = nil
self.structure = { self.playertree = {}
generic = {},
IDs = {},
}
self.rootentries = nil self.rootentries = nil
self.rootentries = {} self.rootentries = {}
self.menutree = nil
self.menutree = {}
return self return self
end end
--- Remove the entry and all entries below the given entry from the client's menus and the generic structure. --- Remove the entry and all entries below the given entry from the client's F10 menus.
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry to remove -- @param #CLIENTMENU Entry The entry to remove
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched. -- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched.
-- @return #CLIENTMENUMANAGER self -- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:Clear(Entry,Client) function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client)
self:T(self.lid.."Clear") self:T(self.lid.."DeleteF10Entry")
local rid = self.structure.IDs[Entry.ID] local Set = self.clientset.Set
if rid then if Client then
local generic = self.structure.generic[rid] Set = {Client}
local Set = self.clientset.Set end
if Client then for _,_client in pairs(Set) do
Set = {Client} if _client and _client:IsAlive() then
end local playername = _client:GetPlayerName()
for _,_client in pairs(Set) do if self.playertree[playername] then
local client = _client -- Wrapper.Client#CLIENT local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
if client and client:IsAlive() then if centry then
local playername = client:GetPlayerName() --self:I("Match for "..Entry.UUID)
local entry = self.structure[playername][rid] -- #CLIENTMENU centry:Clear()
if entry then
entry:Clear()
self.structure[playername][rid] = nil
end
end
end
if not Client then
for _id,_entry in pairs(self.structure.generic) do
local entry = _entry -- #CLIENTMENU
if entry and entry.Parent and entry.Parent.ID and entry.Parent.ID == rid then
self.structure.IDs[entry.ID] = nil
entry = nil
end end
end end
end end
@ -772,115 +693,87 @@ function CLIENTMENUMANAGER:Clear(Entry,Client)
return self return self
end end
--- [Internal] Clean up player shadow structure --- Remove the entry and all entries below the given entry from the generic tree.
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry to remove
-- @return #CLIENTMENUMANAGER self -- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:_CleanUpPlayerStructure() function CLIENTMENUMANAGER:DeleteGenericEntry(Entry)
self:T(self.lid.."_CleanUpPlayerStructure") self:T(self.lid.."DeleteGenericEntry")
for _,_client in pairs(self.clientset.Set) do
local client = _client -- Wrapper.Client#CLIENT if Entry.Children and #Entry.Children > 0 then
if client and client:IsAlive() then self:RemoveGenericSubEntries(Entry)
local playername = client:GetPlayerName() end
local newstructure = {}
for _id, _entry in UTILS.spairs(self.structure[playername]) do local depth = #Entry.path
if self.structure.generic[_id] then local uuid = Entry.UUID
newstructure[_id] = _entry
end local tbl = UTILS.DeepCopy(self.menutree)
end
self.structure[playername] = nil if tbl[depth] then
self.structure[playername] = newstructure for i=depth,#tbl do
--self:I("Level = "..i)
for _id,_uuid in pairs(tbl[i]) do
self:T(_uuid)
if string.find(_uuid,uuid) or _uuid == uuid then
--self:I("Match for ".._uuid)
self.menutree[i][_id] = nil
self.flattree[_uuid] = nil
end
end
end end
end end
return self return self
end end
--- Remove all entries below the given entry from the clients' menus and the generic structure. --- Remove all entries below the given entry from the generic tree.
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The menu entry -- @param #CLIENTMENU Entry The entry where to start. This entry stays.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:RemoveGenericSubEntries(Entry)
self:T(self.lid.."RemoveGenericSubEntries")
local depth = #Entry.path + 1
local uuid = Entry.UUID
local tbl = UTILS.DeepCopy(self.menutree)
if tbl[depth] then
for i=depth,#tbl do
self:T("Level = "..i)
for _id,_uuid in pairs(tbl[i]) do
self:T(_uuid)
if string.find(_uuid,uuid) then
self:T("Match for ".._uuid)
self.menutree[i][_id] = nil
self.flattree[_uuid] = nil
end
end
end
end
return self
end
--- Remove all entries below the given entry from the client's F10 menus.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry where to start. This entry stays.
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched. -- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched.
-- @return #CLIENTMENUMANAGER self -- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:RemoveSubEntries(Entry,Client) function CLIENTMENUMANAGER:RemoveF10SubEntries(Entry,Client)
self:T(self.lid.."RemoveSubEntries") self:T(self.lid.."RemoveSubEntries")
local rid = self.structure.IDs[Entry.ID] local Set = self.clientset.Set
if rid then if Client then
local Set = self.clientset.Set Set = {Client}
if Client then end
Set = {Client} for _,_client in pairs(Set) do
end if _client and _client:IsAlive() then
for _,_client in pairs(Set) do local playername = _client:GetPlayerName()
local client = _client -- Wrapper.Client#CLIENT if self.playertree[playername] then
if client and client:IsAlive() then local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
local playername = client:GetPlayerName() centry:RemoveSubEntries()
local entry = self.structure[playername][rid] -- #CLIENTMENU
if entry then
entry:RemoveSubEntries()
end
end end
end end
if not Client then
for _id,_entry in pairs(self.structure.generic) do
local entry = _entry -- #CLIENTMENU
if entry and entry.Parent and entry.Parent.ID and entry.Parent.ID == rid then
self.structure.IDs[entry.ID] = nil
self.structure.generic[_id] = nil
end
end
end
end
self:_CleanUpPlayerStructure()
return self
end
--- Remove a specific entry by ID from the generic structure
-- @param #CLIENTMENUMANAGER self
-- @param #number ID
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:_RemoveByID(ID)
self:T(self.lid.."_RemoveByID "..ID or "none")
if ID then
local gid = self.structure.IDs[ID]
if gid then
self.structure.generic[gid] = nil
self.structure.IDs[ID] = nil
end
end
return self
end
--- [Internal] Dump structures to log for debug
-- @param #CLIENTMENUMANAGER self
-- @param #string Playername
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:_CheckStructures(Playername)
self:T(self.lid.."CheckStructures")
self:I("Generic Structure")
self:I("-----------------")
for _id,_entry in UTILS.spairs(self.structure.generic) do
local ID = "none"
if _entry and _entry.ID then
ID = _entry.ID
end
self:I("ID= ".._id.." | EntryID = "..ID)
if _id > 10 and _id < 14 then
self:I(_entry.name)
end
end
self:I("Reverse Structure")
self:I("-----------------")
for _id,_entry in UTILS.spairs(self.structure.IDs) do
self:I("EntryID= ".._id.." | ID = ".._entry)
end
if Playername then
self:I("Player Structure")
self:I("-----------------")
for _id,_entry in UTILS.spairs(self.structure[Playername]) do
local ID = "none"
if _entry and _entry.ID then
ID = _entry.ID
end
local _lid = _id or "none"
self:I("ID= ".._lid.." | EntryID = "..ID)
end
end end
return self return self
end end