Nenue@50: --- Devian - Devian.lua Nenue@32: -- @file-author@ Nenue@32: -- @project-revision@ @project-hash@ Nenue@32: -- @file-revision@ @file-hash@ Nenue@66: Nenue@91: --GLOBALS: Devian, DevianLoadMessage, DEVIAN_WORKSPACE, DEVIAN_PNAME, DEVIAN_PID Nenue@91: Nenue@100: SLASH_RL1 = "/rl" Nenue@100: SlashCmdList.RL = function() ReloadUI() end Nenue@91: DEVIAN_WORKSPACE = false Nenue@91: DEVIAN_PNAME = 'Dvn' Nenue@91: DEVIAN_PID = 0 Nenue@79: Nenue@66: local ADDON, D = ... Nenue@79: local MAJOR, MINOR = 'Devian-2.0', 'r@project-revision@' Nenue@98: local D_INITIALIZED Nenue@98: local next = next Nenue@72: local sub, GetTime, print, _G = string.sub, GetTime, print, _G Nenue@72: local format, setmetatable, getprinthandler, setprinthandler = string.format, setmetatable, getprinthandler, setprinthandler Nenue@79: local tinsert, tremove, rawset = tinsert, tremove, rawset Nenue@66: local currentProfile Nenue@66: local playerName = UnitName("player") Nenue@66: local playerRealm = playerName .. '-' .. GetRealmName() Nenue@66: local num_dock_tabs = 0 Nenue@93: local charStates ={} Nenue@79: Nenue@100: local channels_report = {} Nenue@98: local registeredTags = {} Nenue@98: Nenue@98: Nenue@98: DevianCore = {} Nenue@98: Nenue@98: function DevianCore:OnLoad () Nenue@98: self:RegisterEvent('ADDON_LOADED') Nenue@98: self:RegisterEvent('PLAYER_LOGIN') Nenue@107: Nenue@98: self:SetShown(true) Nenue@98: end Nenue@98: Nenue@98: function DevianCore:OnEvent(event, arg) Nenue@98: if event == 'ADDON_LOADED' or event == 'PLAYER_LOGIN' then Nenue@98: --print(event, arg, DevianDB) Nenue@98: if (arg == 'Devian') and not D_INITIALIZED then Nenue@98: D_INITIALIZED = true Nenue@98: self:Initialize() Nenue@98: self:UnregisterAllEvents() Nenue@98: end Nenue@98: end Nenue@98: end Nenue@98: Nenue@79: DevianLoadMessage = setmetatable({}, { Nenue@79: __call = function(t, msg) Nenue@79: rawset(t, #t+1, msg) Nenue@79: end, Nenue@79: __index = function(t) Nenue@79: return #t Nenue@79: end Nenue@79: }) Nenue@79: Nenue@66: --@debug@ Nenue@66: D.debugmode = true Nenue@66: --@end-debug@ Nenue@66: D.print = function(...) Nenue@66: if currentProfile and not currentProfile.workspace then Nenue@98: return nop Nenue@66: end Nenue@66: Nenue@66: if D.debugmode then Nenue@66: return print('Dvn', ...) Nenue@66: else Nenue@93: return nop Nenue@66: end Nenue@0: end Nenue@66: local print = D.print Nenue@66: Nenue@66: D.L = setmetatable({}, { Nenue@66: __index= function(t,k) Nenue@98: return tostring(k) Nenue@66: end, Nenue@66: __call = function(t,k,...) Nenue@98: return format((t[k]) , ...) Nenue@66: end Nenue@66: }) Nenue@66: D.oldprint = getprinthandler() Nenue@66: if not _G.oldprint then _G.oldprint = D.oldprint end Nenue@66: Nenue@47: local pairs, tostring, tonumber, ipairs, type = pairs, tostring, tonumber, ipairs, type Nenue@66: local max, rand, format, print = max, math.random, string.format, print Nenue@47: local insert, wipe, concat = table.insert, table.wipe, table.concat Nenue@47: local select, unpack = select, unpack Nenue@47: local GetNumAddOns, GetAddOnInfo, GetAddOnEnableState, EnableAddOn = GetNumAddOns, GetAddOnInfo, GetAddOnEnableState, EnableAddOn Nenue@47: local UnitName, DisableAddOn = UnitName, DisableAddOn Nenue@66: Nenue@98: local db, L Nenue@13: local defaults = { Nenue@66: global = {{}, {}}, Nenue@66: default_channel = { Nenue@55: signature = 'Main', Nenue@98: x = 100, y = -200, Nenue@55: height = 500, width = 600, Nenue@66: enabled = true}, Nenue@66: current_profile = 1, Nenue@66: main_profile = 1, Nenue@66: last_profile = 1, Nenue@66: profilesName = {}, Nenue@66: profiles = { Nenue@45: }, Nenue@33: font = [[Interface\Addons\Devian\font\SourceCodePro-Regular.ttf]], -- font info Nenue@13: fontsize = 13, Nenue@13: fontoutline = 'NONE', Nenue@45: Nenue@45: headergrad = {'VERTICAL', 0, 0, 0, 1, Nenue@45: 1, 0.1, 0.1, 1}, -- header info Nenue@33: headerdrop = {1,1,1,1}, Nenue@45: headerblend = 'BLEND', Nenue@33: headeralpha = 1, Nenue@45: headerfontcolor = {1,1,1,1}, Nenue@45: Nenue@45: backdrop = {1,1,1,1}, -- background frame info Nenue@56: backgrad = {'VERTICAL', 0, 0, 0, .75, 0,0,0, .65}, Nenue@55: backblend = 'BLEND', Nenue@45: backalpha = 1, Nenue@45: backborder = {.5,.5,.5,1}, Nenue@45: backheader = {.25,.25,.25,1}, Nenue@45: Nenue@45: frontdrop = {1,1,1,1}, -- foreground frame info Nenue@56: frontgrad = {'VERTICAL', 0, 0, 0, 1, 0,0,0, 0.95}, Nenue@55: frontblend = 'BLEND', Nenue@28: frontalpha = 1, Nenue@45: frontborder = {.07,.47,1,1}, Nenue@45: frontheader = {1,1,1,1}, Nenue@33: tagcolor = {}, -- tag color repository Nenue@36: workspace = 2, -- current profile Nenue@47: last_workspace = 2, -- default workspace to alternate with when just "/dvn" is issued Nenue@47: Nenue@47: dock_onshow_fade_time = 2.5, Nenue@47: dock_onshow_fade_from = 1, Nenue@47: dock_onshow_fade_to = 0.2, Nenue@47: Nenue@47: dock_alpha_on = 1, Nenue@47: dock_alpha_off = 0.2, Nenue@47: dock_fade_in = 0.15, Nenue@47: dock_fade_out = 0.45, Nenue@47: dock_button_alpha_on = 1, Nenue@47: dock_button_alpha_off = 0.2, Nenue@50: dock_button_fade_in = 0.075, Nenue@55: dock_button_fade_out = 0.075, Nenue@47: Nenue@55: movement_fade = true, Nenue@55: movement_fade_time = 0.15, Nenue@60: movement_fade_from = 1, Nenue@60: movement_fade_to = 0, Nenue@60: movement_translation_x = 25, Nenue@60: movement_translation_y = 25, Nenue@13: } Nenue@9: Nenue@66: D.console = {} Nenue@66: D.max_channel = 0 Nenue@0: Nenue@67: D.InWorkspace = function () Nenue@67: return db.profiles[db.current_profile].workspace Nenue@67: end Nenue@67: Nenue@86: local profileTemplate = { Nenue@86: name = function(id, name) return name end, Nenue@86: workspace = function(id, name) return (id ~= 1) end, Nenue@86: current_channel = 1, Nenue@86: default_channel = 1, Nenue@86: num_channels = 1, Nenue@86: max_channel = 1, -- the highest created channel id Nenue@86: enabled = true, -- allow enabled consoles to appear Nenue@86: channels = { Nenue@86: { Nenue@86: index = 1, Nenue@86: signature = 'Main', Nenue@98: x = 100, y = -200, Nenue@86: height = 500, width = 600, Nenue@86: enabled = true Nenue@86: } Nenue@86: }, Nenue@86: loadouts = {}, Nenue@86: global = {}, Nenue@86: tags = {}, Nenue@86: char = { Nenue@86: [playerRealm] = {} Nenue@86: }, Nenue@86: unlisted = {} Nenue@86: } Nenue@86: Nenue@86: --- Applies complex template tables Nenue@86: -- If he value is a function, then it will invoke f(...) and use whatever gets returned Nenue@86: function D.DeepCopy(src, dest, ...) Nenue@86: Nenue@86: for k,v in pairs(src) do Nenue@86: if not dest[k] then Nenue@93: --oldprint('Rebuilding conf value', k) Nenue@86: if type(v) == 'table' then Nenue@86: dest[k] = {} Nenue@86: D.DeepCopy(v, dest[k], ...) Nenue@86: Nenue@86: else Nenue@86: if type(v) == 'function' then Nenue@86: v = v(...) Nenue@86: end Nenue@86: dest[k] = v Nenue@86: end Nenue@86: end Nenue@86: end Nenue@86: end Nenue@86: Nenue@98: D.FixProfile = function(forced) Nenue@98: Nenue@98: local numChannels = 0 Nenue@98: local minChannel = 400 Nenue@98: local sortedChannels = {} Nenue@98: local sortedTags = {} Nenue@98: local maxChannel = 0 Nenue@98: for k,v in pairs(currentProfile.channels) do Nenue@98: numChannels = numChannels + 1 Nenue@98: maxChannel = max(tonumber(k), maxChannel) Nenue@98: minChannel = min(tonumber(k), minChannel) Nenue@98: tinsert(sortedChannels, v) Nenue@98: end Nenue@98: if (maxChannel > numChannels) or forced then Nenue@98: oldprint('fixing channels data') Nenue@98: table.sort(sortedChannels, function(a,b) Nenue@98: return (b.index > a.index) Nenue@98: end) Nenue@98: for i, info in ipairs(sortedChannels) do Nenue@99: Nenue@99: info.tags = info.tags or {} Nenue@98: for tag, tagSet in pairs(currentProfile.tags) do Nenue@98: for _, index in pairs(tagSet) do Nenue@98: if index == info.index then Nenue@98: sortedTags[tag] = sortedTags[tag] or {} Nenue@98: sortedTags[tag][i] = i Nenue@99: tinsert(info.tags, tag) Nenue@98: end Nenue@98: end Nenue@98: end Nenue@99: print('Set tags:', table.concat(info.tags, ', ')) Nenue@98: Nenue@98: info.index = i Nenue@98: end Nenue@98: currentProfile.channels = sortedChannels Nenue@98: currentProfile.tags = sortedTags Nenue@98: else Nenue@98: minChannel = 2 Nenue@98: end Nenue@98: currentProfile.lastUpdateFix = MINOR Nenue@98: end Nenue@98: Nenue@66: D.Profile = function (id, name) Nenue@66: Nenue@66: if name and not id and db.profilesName[name] then Nenue@66: id = db.profilesName[name] Nenue@66: print('ID located by name, |cFF00FF00'..name..'|r is |cFFFFFF00'.. id..'|r') Nenue@66: end Nenue@66: Nenue@66: if not id or not db.profiles[id] then Nenue@66: if not id then Nenue@66: id = #db.profiles+1 Nenue@66: print('Generated profile ID: |cFFFFFF00'.. id .. '|r') Nenue@66: end Nenue@66: Nenue@66: if not name or db.profilesName[name] then Nenue@66: local newName = name or (id == 1 and 'Main' or 'Profile') Nenue@66: local prefix = newName Nenue@66: local i = 2 Nenue@66: while db.profilesName[newName] do Nenue@66: i = i + 1 Nenue@66: newName = prefix .. i Nenue@66: end Nenue@66: name = newName Nenue@66: print('Generated profile name: |cFF00FF00'..newName..'|r') Nenue@66: end Nenue@86: Nenue@86: Nenue@86: Nenue@66: print('Creating profile') Nenue@66: db.profilesName[name] = id Nenue@86: db.profiles[id] = {} Nenue@66: end Nenue@66: Nenue@86: Nenue@86: Nenue@66: D.currentProfile = db.profiles[id] Nenue@66: currentProfile = D.currentProfile Nenue@86: Nenue@86: D.DeepCopy(profileTemplate, currentProfile, id, name) Nenue@86: Nenue@86: Nenue@66: currentProfile.char[playerRealm] = currentProfile.char[playerRealm] or {} Nenue@66: if currentProfile.workspace then Nenue@79: DEVIAN_WORKSPACE = true Nenue@91: DEVIAN_PNAME = currentProfile.name Nenue@89: DEVIAN_PID = id Nenue@66: setprinthandler(D.Message) Nenue@66: else Nenue@79: DEVIAN_WORKSPACE = false Nenue@93: DEVIAN_PNAME = nil Nenue@93: print = nop Nenue@66: end Nenue@89: DEVIAN_PID =id Nenue@66: Nenue@98: Nenue@98: -- Attempt to fix bad data Nenue@98: --@debug@ Nenue@98: MINOR = 70100 Nenue@98: --@end-debug@ Nenue@98: if (currentProfile.lastUpdateFix or 0) < MINOR then Nenue@98: D.FixProfile(true) Nenue@98: end Nenue@98: Nenue@98: Nenue@66: D.unlisted = currentProfile.unlisted Nenue@66: D.channels = currentProfile.channels Nenue@66: D.tags = currentProfile.tags Nenue@66: D.channelinfo = currentProfile.channels Nenue@66: D.char = currentProfile.char[playerRealm] Nenue@66: D.global = currentProfile.global Nenue@66: D.num_channels = currentProfile.num_channels Nenue@66: D.enabled = currentProfile.enabled Nenue@66: D.sig = {} Nenue@66: D.sigID = {} Nenue@66: D.IDsig = {} Nenue@72: D.dock = _G.DevianDock Nenue@66: D.dock.buttons = D.dock.buttons or {} Nenue@66: Nenue@98: Nenue@98: Nenue@66: return id, name Nenue@66: end Nenue@66: Nenue@66: local targetGlobal, targetChar Nenue@86: D.Command = function (cmd) Nenue@66: local list_id, scan_func, reload Nenue@86: Nenue@14: local args = {} Nenue@86: if cmd then Nenue@86: local i, j = 0, 0 Nenue@86: repeat Nenue@86: i, j = cmd:find("%S+", j+1) Nenue@86: if i and j then Nenue@86: tinsert(args, cmd:sub(i, j)) Nenue@86: end Nenue@86: Nenue@86: until not(i or j) Nenue@14: end Nenue@14: local mode, tag, dest = unpack(args) Nenue@0: Nenue@86: Nenue@14: -- no args, toggle ui Nenue@86: if mode == 'rc' then Nenue@93: return D:ResetChannels(tag) Nenue@86: elseif mode == 'stack' then Nenue@14: return D:StackFrames() Nenue@14: elseif mode == 'grid' then Nenue@14: return D:DistributeFrames() Nenue@14: elseif mode == 'tag' then -- tagging Nenue@93: return D:Tag(tag, dest) Nenue@73: elseif mode == 'new' then Nenue@93: return D:New(tag) Nenue@93: elseif mode == 'dock' then Nenue@93: D.db.dockPoint = tag Nenue@93: return D:UpdateDock() Nenue@73: elseif mode == 'remove' then Nenue@93: return D:Remove(tag) Nenue@89: elseif mode ~= nil then Nenue@89: -- profile selector or save command Nenue@66: if mode == 'save' then Nenue@66: list_id = tonumber(tag) Nenue@89: else Nenue@89: list_id = tonumber(mode) Nenue@89: end Nenue@89: Nenue@89: if not list_id then Nenue@89: if db.profilesName[tostring(list_id)] then Nenue@89: list_id = db.profilesName[tostring(list_id)] Nenue@89: else Nenue@89: Nenue@89: D:Print(L('Unable to resolve profile ID/name', list_id, dest)) Nenue@89: return Nenue@66: end Nenue@89: end Nenue@89: Nenue@89: Nenue@89: if mode == 'save' then Nenue@66: D.Profile(list_id, dest) Nenue@66: scan_func = D.Save Nenue@89: Nenue@89: Nenue@89: local name = currentProfile.name Nenue@89: if dest then Nenue@89: dest = dest:gsub("$%s+", ''):gsub("%s+^", '') Nenue@89: if dest then Nenue@89: if name then Nenue@89: db.profilesName[name] = nil Nenue@89: end Nenue@89: db.profiles[list_id].name = dest Nenue@89: db.profilesName[dest] = list_id Nenue@92: Nenue@92: name = dest Nenue@89: end Nenue@89: end Nenue@89: Nenue@89: Nenue@89: D:Print("Profile |cFFFFFF00".. list_id .."|r:|cFF00FFFF".. name .."|r saved.") Nenue@66: else Nenue@32: Nenue@89: if db.profiles[list_id] then Nenue@89: D.LoadMessage ("Switched profiles.") Nenue@89: if list_id ~= db.main_profile then Nenue@89: db.last_profile = list_id Nenue@89: end Nenue@89: db.current_profile = list_id Nenue@89: scan_func = D.Load Nenue@89: else Nenue@89: return D:PrintHelp() Nenue@89: end Nenue@36: Nenue@0: end Nenue@32: elseif mode == nil then Nenue@66: list_id = (db.current_profile ~= db.main_profile) and db.main_profile or db.last_profile Nenue@77: D.LoadMessage ("Switched between main and recent profile ("..db.current_profile..' and '..list_id..')') Nenue@66: db.current_profile = list_id Nenue@66: scan_func = D.Load Nenue@32: else Nenue@89: Nenue@89: Nenue@32: return D:PrintHelp() Nenue@0: end Nenue@77: Nenue@77: if not db.profiles[list_id] then Nenue@77: db.profiles[list_id] = {global = {}, char = {} } Nenue@77: D.LoadMessage ("Starting profile #|cFF00FFFF".. list_id..'|r') Nenue@77: end Nenue@67: if not db.profiles[list_id].char[playerRealm] then Nenue@67: db.profiles[list_id].char[playerRealm] = {} Nenue@67: end Nenue@67: Nenue@66: targetGlobal = db.profiles[list_id].global Nenue@66: targetChar = db.profiles[list_id].char[playerRealm] Nenue@32: Nenue@67: Nenue@66: if scan_func then Nenue@93: wipe(charStates) Nenue@66: for id, name, enableState, globalState in D.Addons() do Nenue@66: scan_func(id, name, enableState, globalState) Nenue@0: end Nenue@0: end Nenue@0: Nenue@66: if scan_func == D.Load then Nenue@72: _G.ReloadUI() Nenue@93: if AddonList_Update then Nenue@93: AddonList_Update() Nenue@93: end Nenue@93: elseif (scan_func == D.Save) then Nenue@93: print('reckoning') Nenue@93: local updated = {} Nenue@93: for addon, newState in pairs(charStates) do Nenue@93: for character, addons in pairs(db.profiles[list_id].char) do Nenue@93: if addons[addon] then Nenue@93: print(addon, addons[addon], '::', newState) Nenue@93: if (addons[addon] ~= newState) then Nenue@93: addons[addon] = newState Nenue@93: updated[character] = (updated[character] or 0) + 1 Nenue@93: end Nenue@93: Nenue@93: end Nenue@93: Nenue@93: end Nenue@93: end Nenue@93: for character, numAddons in pairs(updated) do Nenue@93: print(character, numAddons, 'settings') Nenue@93: end Nenue@93: Nenue@66: end Nenue@93: Nenue@93: Nenue@66: D.Profile(db.current_profile) Nenue@66: end Nenue@66: Nenue@66: D.Addons = function() Nenue@66: local playername = UnitName("player") Nenue@66: return function(n, i) Nenue@66: if i >= n then Nenue@66: return nil Nenue@66: end Nenue@66: Nenue@66: i = i + 1 Nenue@66: local name = GetAddOnInfo(i) Nenue@66: local enableState, globalState = GetAddOnEnableState(playername, i), GetAddOnEnableState(nil, i) Nenue@66: return i, name, enableState, globalState Nenue@66: end, GetNumAddOns(), 0 Nenue@66: end Nenue@66: Nenue@66: D.Load = function(id, name, charState, globalState) Nenue@93: print('load', tostring(name), tostring(charState), tostring(globalState)) Nenue@93: if targetGlobal[name] == 2 then Nenue@93: EnableAddOn(id, true) Nenue@93: elseif targetChar[name] == 2 then Nenue@93: EnableAddOn(id, playerName) Nenue@93: elseif targetGlobal[name] == 0 then Nenue@93: DisableAddOn(id, true) Nenue@93: elseif targetChar[name] == 0 then Nenue@93: DisableAddOn(id, playerName) Nenue@93: end Nenue@93: Nenue@93: if not (targetChar[name] or targetGlobal[name]) then Nenue@66: tinsert(D.unlisted, name) Nenue@66: end Nenue@93: Nenue@66: end Nenue@93: Nenue@93: Nenue@66: D.Save = function(id, name, charState, globalState) Nenue@93: if (charState ~= 0) or (globalState ~= 0) then Nenue@93: print('save', id, name, playerRealm .. ': '.. charState, 'All: '.. globalState) Nenue@93: end Nenue@93: Nenue@66: targetGlobal[name] = globalState Nenue@66: targetChar[name] = charState Nenue@93: Nenue@93: -- if enabling/disabling globally Nenue@93: if globalState ~= 1 then Nenue@93: charStates[name] = globalState Nenue@93: end Nenue@66: end Nenue@57: Nenue@98: D.UpdateTags = function() Nenue@98: wipe(registeredTags) Nenue@99: for index, channel in ipairs(D.channels) do Nenue@99: for _, tag in ipairs(channel.tags) do Nenue@99: registeredTags[tag] = registeredTags[tag] or {} Nenue@99: tinsert(registeredTags[tag], D.console[index]) Nenue@98: end Nenue@98: end Nenue@98: end Nenue@98: Nenue@99: D.Tag = function(self, tag, id) Nenue@66: local sig Nenue@99: if tag and id then Nenue@66: --@debug@ Nenue@66: --print(tag, dest) Nenue@66: --@end-debug@ Nenue@66: Nenue@66: -- convert to ID Nenue@99: local channel, sig Nenue@99: if tonumber(id) == nil then Nenue@99: sig = id Nenue@99: if D.sigID[id] then Nenue@99: id = D.sigID[id] Nenue@99: channel = D.channels[id] Nenue@66: end Nenue@57: else Nenue@99: id = tonumber(id) Nenue@99: channel = D.channels[id] Nenue@32: end Nenue@66: Nenue@99: -- if channel is still nil, create one Nenue@99: if not channel then Nenue@99: id = #D.channels + 1 Nenue@99: D:Print(L('New channel created', (sig and (id..':'..sig)) or id)) Nenue@99: channel = D:GetOrCreateChannel(id, sig) Nenue@66: else Nenue@99: sig = channel.signature Nenue@66: end Nenue@66: --@debug@ Nenue@66: --print('3 tag,dest,channel.sig=',tag, dest, channel.signature)--@end-debug@ Nenue@66: Nenue@66: if not currentProfile.tags[tag] then -- no tag table? Nenue@99: currentProfile.tags[tag] = {} Nenue@66: end Nenue@66: Nenue@99: local existingTag = tContains(channel.tags, tag) Nenue@99: if existingTag then -- is tag set? Nenue@99: Nenue@99: for i, tag in ipairs(channel.tags) do Nenue@99: if tag == tag then Nenue@99: tremove(channel.tags, i) Nenue@99: D:Print(L('Tag removed from channel', tag, channel.index, channel.signature)) Nenue@99: break Nenue@99: end Nenue@99: end Nenue@66: else Nenue@99: tinsert(channel.tags, tag) Nenue@98: D:Print(L('Tag added to channel', tag, channel.index, channel.signature)) Nenue@66: end Nenue@98: D.UpdateTags() Nenue@98: DevianDock:Update() Nenue@66: else Nenue@66: D:Print(L['Command tag help']) Nenue@0: end Nenue@0: end Nenue@0: Nenue@86: D.ResetChannels = function(self, profile) Nenue@86: currentProfile.current_channel = 1 Nenue@86: currentProfile.primary_channel = 1 Nenue@86: currentProfile.channels = {} Nenue@86: D.DeepCopy(profileTemplate.channels, currentProfile.channels) Nenue@86: currentProfile.tags = {} Nenue@86: D.LoadMessage('Profile reset.') Nenue@86: ReloadUI() Nenue@86: end Nenue@86: Nenue@73: D.New = function(self, tag) Nenue@73: if tag and not self.sigID[tag] then Nenue@73: local id = D.max_channel + 1 Nenue@73: D.SetChannel(tag, id) Nenue@73: end Nenue@73: end Nenue@0: Nenue@73: D.Remove = function(self, dest) Nenue@73: dest = D.sigID[dest] or tonumber(dest) Nenue@73: if D.console[dest] and D.channels[dest] then Nenue@73: for tag, tagDest in pairs(D.tags) do Nenue@73: for i = #tagDest, 0 do Nenue@73: -- work downward so we aren't skipping entries Nenue@73: if tagDest[i] == dest then Nenue@73: tremove(tagDest, i) Nenue@73: end Nenue@73: end Nenue@73: end Nenue@73: D.console[dest]:Hide() Nenue@73: D.channels[dest] = nil Nenue@73: tremove(D.console, dest) Nenue@93: D.dock.buttons[dest]:SetShown(false) Nenue@108: D.dock:Update() Nenue@73: D:Print('Removed channel #'..dest) Nenue@73: end Nenue@73: end Nenue@13: Nenue@77: --- Queue up a message to appear after UI reload Nenue@77: function D.LoadMessage(msg) Nenue@77: Nenue@77: tinsert(_G.DevianLoadMessage, msg) Nenue@77: end Nenue@77: Nenue@18: --- Creates a Devian-style output. Nenue@18: -- The first argument describes the channel to output on, and the remaining arguments are concatenated in a manner similar to default print() Nenue@18: -- This becomes the print handler when development mode is active. The original print() function is assigned to oldprint(). Nenue@18: -- @param Tag, signature, or numeric index of the channel to output on. Defaults to primary channel. Nenue@18: -- @param ... Output contents. Nenue@98: local default_sendq = {} Nenue@66: function D.Message(prefix, ...) Nenue@67: if not currentProfile.workspace then Nenue@23: return D.oldprint(prefix, ...) Nenue@23: end Nenue@98: local print = D.oldprint Nenue@57: prefix = tostring(prefix) Nenue@57: if prefix == nil then Nenue@55: prefix = 'nil*' Nenue@1: end Nenue@9: Nenue@98: local sendq = default_sendq Nenue@30: local tag, id, tagged Nenue@13: local byName = true Nenue@98: Nenue@98: if registeredTags[prefix] then Nenue@98: sendq = registeredTags[prefix] Nenue@98: else Nenue@98: if D.sig[prefix] then Nenue@98: sendq[D.sig[prefix].index] = D.sig[prefix] Nenue@98: elseif not tagged then Nenue@98: sendq[1] = D.console[D.primary_channel] Nenue@18: end Nenue@18: end Nenue@18: Nenue@98: Nenue@18: Nenue@9: -- color me timbers Nenue@9: local pcolor Nenue@18: if (not db.tagcolor[prefix]) and byName then Nenue@86: -- numbers, use white Nenue@86: if prefix:match('^%d+%.%d+') then Nenue@73: pcolor = 'FFFFFF' Nenue@73: else Nenue@73: local c = { Nenue@73: rand(64,255), rand(64,255), rand(64,255) Nenue@73: } Nenue@73: if c[1] > 223 and c[2] > 223 and c[3] > 223 then Nenue@73: c[rand(1,3)] = rand(64,223) Nenue@73: end Nenue@73: db.tagcolor[prefix] = format('%02X%02X%02X', unpack(c)) Nenue@73: pcolor = db.tagcolor[prefix] Nenue@0: end Nenue@73: else Nenue@73: pcolor = db.tagcolor[prefix] Nenue@0: end Nenue@0: Nenue@55: local buffer = {} Nenue@0: for i = 1, select('#',...) do Nenue@0: local var = select(i, ...) Nenue@0: Nenue@0: if type(var) == 'table' then Nenue@20: if type(var.GetName) == 'function' then Nenue@72: var = '[table:'..tostring(var:GetName())..']' Nenue@20: else Nenue@32: var = '<'..tostring(var)..'>' Nenue@20: end Nenue@20: Nenue@0: elseif type(var) == 'boolean' then Nenue@75: var = var and 'true' or 'false' Nenue@0: elseif type(var) == 'function' then Nenue@72: var = '['..tostring(var)..']' Nenue@0: elseif type(var) == 'nil' then Nenue@75: var = 'nil' Nenue@60: else Nenue@60: var = tostring(var) Nenue@0: end Nenue@0: Nenue@47: insert(buffer, var) Nenue@0: end Nenue@47: local message = concat(buffer, ' ') Nenue@18: for id, channel in pairs(sendq) do Nenue@55: if channel.width < 250 then Nenue@72: prefix = sub(prefix, 0,2) Nenue@55: end Nenue@98: channel.out:AddMessage('|cFF'.. pcolor..prefix ..'|r ' .. message, 0.8, 0.8, 0.8) Nenue@100: if not channel.newMessage then Nenue@100: channel.newMessage = true Nenue@100: if channel.Dock then Nenue@100: channel.Dock:Update() Nenue@100: end Nenue@58: end Nenue@108: Nenue@108: Nenue@18: end Nenue@47: wipe(buffer) Nenue@0: end Nenue@0: Nenue@14: Nenue@32: Nenue@14: Nenue@32: function D:PrintHelp() Nenue@32: D:Print("|cFFFFFF00/dvn|r", Nenue@32: "\n |cFFFFFF00|r - Loads a saved addon list. List 1 is treated as a gameplay profile and consoles will be disabled by default.") Nenue@47: D:Print("|cFFFFFF00/dvc|r [, ...]", "- Hides and show consoles. A list of channel keys can be passed to specify which ones get toggled.") Nenue@32: Nenue@32: D:Print("|cFFFFFF00/resetdvn|r", "- Resets all but profile data SavedVariables.") Nenue@32: D:Print("|cFFFFFF00/cleandvn|r", "- Fully resets SavedVariables, profiles and all.") Nenue@32: end Nenue@32: Nenue@66: local blocked = {profiles = true, debugmode = true} Nenue@66: D.SetDefaults = function() Nenue@72: local DevianDB = _G.DevianDB Nenue@66: for k,v in pairs(DevianDB) do Nenue@66: if not blocked[k] then Nenue@66: DevianDB[k] = nil Nenue@60: end Nenue@60: end Nenue@66: for k,v in pairs(defaults) do Nenue@66: if not blocked[k] then Nenue@66: DevianDB[k] = v Nenue@60: end Nenue@60: end Nenue@73: Nenue@77: D.LoadMessage "Non-user SavedVars have been reset." Nenue@72: _G.ReloadUI() Nenue@60: end Nenue@66: D.SetDefaultsAll = function () Nenue@72: _G.DevianDB = nil Nenue@77: D.LoadMessage "All SavedVars wiped." Nenue@72: _G.ReloadUI() Nenue@66: end Nenue@73: Nenue@73: D.UnsetColors = function() Nenue@73: db.tagcolor = {} Nenue@73: D:Print('Tag color cache cleared.') Nenue@73: end Nenue@45: Nenue@33: Nenue@89: function D.ConsoleCommand (cmd) Nenue@100: DevianDock:ToggleAll() Nenue@89: end Nenue@87: Nenue@66: Nenue@98: function DevianCore:Initialize() Nenue@81: L = D.L Nenue@13: Nenue@33: -- pull defaults Nenue@98: if not DevianDB then Nenue@98: DevianDB = defaults Nenue@9: end Nenue@98: D.db = _G.DevianDB Nenue@9: db = _G.DevianDB Nenue@0: Nenue@66: --- Nenue@77: if #_G.DevianLoadMessage >= 1 then Nenue@77: for i, msg in ipairs(_G.DevianLoadMessage) do Nenue@77: D:Print(msg) Nenue@77: end Nenue@77: Nenue@77: table.wipe(_G.DevianLoadMessage) Nenue@0: end Nenue@0: Nenue@66: Nenue@89: -- commands Nenue@89: local cmdlist = { Nenue@89: ['dvn'] = "Command", Nenue@89: ['devian'] = "Command", Nenue@89: ['dvc'] = "ConsoleCommand", Nenue@89: ['dvncolors'] = "UnsetColors", Nenue@89: ['cleandvn'] = "SetDefaultsAll", Nenue@89: ['resetdvn'] = "SetDefaults", Nenue@89: } Nenue@89: for cmd, func in pairs(cmdlist) do Nenue@89: local CMD = cmd:upper() Nenue@89: _G['SLASH_' .. CMD .. '1'] = "/"..cmd Nenue@89: Nenue@89: if type(func == 'string') then Nenue@93: --print('SLASH_' .. CMD .. '1','/'.. cmd, func) Nenue@89: SlashCmdList[CMD] = D[func] Nenue@89: else Nenue@93: --print('SLASH_' .. CMD .. '1','/'.. cmd, func) Nenue@89: SlashCmdList[CMD] = func Nenue@89: end Nenue@89: end Nenue@89: Nenue@66: --- initialize the current profile Nenue@66: local id, name = D.Profile(db.current_profile or 1) Nenue@66: if currentProfile.workspace then Nenue@100: tinsert(channels_report, 'Profile |cFFFFFF00'.. id ..'|r: |cFF00FF00'..currentProfile.name.. '|r') Nenue@86: if D.channels[currentProfile.default_channel] then Nenue@100: tinsert(channels_report, 'Primary: |cFFFFFF00#'..currentProfile.default_channel..'|r |cFF00FFFF'.. D.channels[currentProfile.default_channel].signature..'|r') Nenue@86: end Nenue@0: end Nenue@13: Nenue@86: Nenue@98: Nenue@98: for index, cinfo in ipairs(D.channels) do Nenue@98: --oldprint(index, cinfo.signature) Nenue@66: if not D.primary_channel then Nenue@98: D.primary_channel = index Nenue@14: end Nenue@98: D:GetOrCreateChannel(index) Nenue@98: D.num_channels = #D.channels Nenue@32: end Nenue@100: if #channels_report >= 1 then Nenue@100: D:Print(concat(channels_report, ', ')) Nenue@100: end Nenue@100: Nenue@86: D.primary_channel = D.primary_channel or 1 Nenue@66: D.max_channel = max(D.max_channel, currentProfile.max_channel) Nenue@66: if currentProfile.max_channel < D.max_channel then Nenue@66: for i = currentProfile.max_channel, D.max_channel do Nenue@66: D.console[i]:Hide() Nenue@47: end Nenue@36: end Nenue@36: Nenue@98: D.UpdateTags() Nenue@98: Nenue@66: if currentProfile.workspace then Nenue@66: if D.console[currentProfile.current_channel] then Nenue@69: --print('bringing', D.console[currentProfile.current_channel].signature, 'to the front') Nenue@66: D.console[currentProfile.current_channel]:ToFront() Nenue@66: -- bring the current channel to the front Nenue@47: end Nenue@98: DevianDock:Update() Nenue@36: end Nenue@66: end Nenue@89: Nenue@98: function D:Print (...) Nenue@98: local msg = '|cFF00FF44Devian|r:' Nenue@98: for i = 1, select('#', ...) do Nenue@98: msg = msg .. ' ' .. tostring(select(i, ...)) Nenue@98: end Nenue@98: DEFAULT_CHAT_FRAME:AddMessage(msg) Nenue@98: end Nenue@98: Nenue@98: function D:GetActiveChannel() Nenue@98: return D.console[currentProfile.current_channel] Nenue@98: end Nenue@98: Nenue@98: function D:GetOrCreateChannel(id, name) Nenue@99: id = id or (#D.channels + 1) Nenue@99: Nenue@98: local info = D.channels[id] Nenue@99: if not info then Nenue@99: --print('new channel') Nenue@99: name = name or ('Channel ' .. id) Nenue@99: info = { Nenue@99: index = id, Nenue@99: signature = name, Nenue@99: tags = {} Nenue@99: } Nenue@99: D.DeepCopy(defaults.default_channel, info) Nenue@99: D.channels[id] = info Nenue@99: elseif not info.tags then Nenue@99: -- fix old data? Nenue@99: info.tags = {info.signature} Nenue@99: oldprint(D.db) Nenue@99: for tag, tagSet in pairs(D.tags) do Nenue@99: for _, index in pairs(tagSet) do Nenue@99: if index == id then Nenue@99: tinsert(info.tags, tag) Nenue@99: end Nenue@99: end Nenue@99: end Nenue@99: end Nenue@99: Nenue@98: local frame = D.console[id] Nenue@100: Nenue@98: if not frame then Nenue@98: Nenue@99: if DEVIAN_WORKSPACE then Nenue@100: tinsert(channels_report, (info.index) .. ':' .. (info.signature) .. ' (|cFF00FFFF'.. concat(info.tags, '|r; |cFF00FFFF')..'|r)') Nenue@99: end Nenue@98: frame = CreateFrame('Frame', 'DevianConsole'..id, Devian, 'DevianConsoleTemplate') Nenue@98: frame:SetID(id) Nenue@98: D.console[id] = frame Nenue@99: D.sigID[info.signature] = id Nenue@98: end Nenue@98: Nenue@98: frame:Setup(info) Nenue@98: return frame Nenue@98: end