view Devian.lua @ 84:3faa1f470cdd v2.1.91

TOC v = 70000
author Nenue
date Tue, 19 Jul 2016 13:49:44 -0400
parents c85459c5bb16
children 6e27274da4d9
line wrap: on
line source
--- Devian - Devian.lua
-- @file-author@
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@

--GLOBALS: Devian, DevCon, DevianLoadMessage, DEVIAN_WORKSPACE

local ADDON, D = ...
local MAJOR, MINOR = 'Devian-2.0', 'r@project-revision@'
local D =  LibStub("AceAddon-3.0"):NewAddon(D, "Devian", "AceConsole-3.0", "AceEvent-3.0")
local L = D.L
Devian = D
local sub, GetTime, print, _G = string.sub, GetTime, print, _G
local format, setmetatable, getprinthandler, setprinthandler = string.format, setmetatable, getprinthandler, setprinthandler
local tinsert, tremove, rawset = tinsert, tremove, rawset
local currentProfile
local playerName = UnitName("player")
local playerRealm = playerName .. '-' .. GetRealmName()
local num_dock_tabs = 0
DEVIAN_WORKSPACE = false


DevianLoadMessage = setmetatable({}, {
  __call = function(t, msg)
    rawset(t, #t+1, msg)
  end,
  __index = function(t)
    return #t
  end
})

--@debug@
D.debugmode = true
--@end-debug@
D.print = function(...)
  if currentProfile and not currentProfile.workspace then
    return
  end

  if D.debugmode then
    return print('Dvn', ...)
  else
    return function() end
  end
end
local print = D.print

D.L = setmetatable({}, {
  __index= function(t,k)
    return k
  end,
  __call = function(t,k,...)
    return format((t[k] or k) , ...)
  end
})
D:SetDefaultModuleState(false)
D.oldprint = getprinthandler()
if not _G.oldprint then _G.oldprint = D.oldprint end

local pairs, tostring, tonumber, ipairs, type = pairs, tostring, tonumber, ipairs, type
local max, rand, format, print = max, math.random, string.format, print
local insert, wipe, concat = table.insert, table.wipe, table.concat
local select, unpack = select, unpack
local GetNumAddOns, GetAddOnInfo, GetAddOnEnableState, EnableAddOn = GetNumAddOns, GetAddOnInfo, GetAddOnEnableState, EnableAddOn
local UnitName, DisableAddOn = UnitName, DisableAddOn

local db
local defaults = {
  global = {{}, {}},
  default_channel = {
    signature = 'Main',
    x = 100, y = 800,
    height = 500, width = 600,
    enabled = true},
  current_profile = 1,
  main_profile = 1,
  last_profile = 1,
  profilesName = {},
  profiles = {
  },
  font = [[Interface\Addons\Devian\font\SourceCodePro-Regular.ttf]], -- font info
  fontsize = 13,
  fontoutline = 'NONE',

  headergrad = {'VERTICAL', 0, 0, 0, 1,
                            1, 0.1, 0.1, 1}, -- header info
  headerdrop = {1,1,1,1},
  headerblend = 'BLEND',
  headeralpha = 1,
  headerfontcolor = {1,1,1,1},

  backdrop = {1,1,1,1},                                      -- background frame info
  backgrad = {'VERTICAL', 0, 0, 0, .75, 0,0,0, .65},
  backblend = 'BLEND',
  backalpha = 1,
  backborder = {.5,.5,.5,1},
  backheader = {.25,.25,.25,1},

  frontdrop = {1,1,1,1},                                     -- foreground frame info
  frontgrad = {'VERTICAL', 0, 0, 0, 1, 0,0,0,  0.95},
  frontblend = 'BLEND',
  frontalpha = 1,
  frontborder = {.07,.47,1,1},
  frontheader = {1,1,1,1},
  tagcolor = {},   -- tag color repository
  workspace = 2,   -- current profile
  last_workspace = 2, -- default workspace to alternate with when just "/dvn" is issued

  dock_onshow_fade_time = 2.5,
  dock_onshow_fade_from = 1,
  dock_onshow_fade_to = 0.2,

  dock_alpha_on = 1,
  dock_alpha_off = 0.2,
  dock_fade_in = 0.15,
  dock_fade_out = 0.45,
  dock_button_alpha_on = 1,
  dock_button_alpha_off = 0.2,
  dock_button_fade_in = 0.075,
  dock_button_fade_out = 0.075,

  movement_fade = true,
  movement_fade_time = 0.15,
  movement_fade_from = 1,
  movement_fade_to  = 0,
  movement_translation_x = 25,
  movement_translation_y = 25,
}

D.console = {}
D.max_channel = 0

D.InWorkspace = function ()
  return db.profiles[db.current_profile].workspace
end

D.Profile = function (id, name)

  if name and not id and db.profilesName[name] then
    id = db.profilesName[name]
    print('ID located by name, |cFF00FF00'..name..'|r is |cFFFFFF00'.. id..'|r')
  end

  if not id or not db.profiles[id] then
    if not id then
      id = #db.profiles+1
      print('Generated profile ID: |cFFFFFF00'.. id .. '|r')
    end

    if not name or db.profilesName[name] then
      local newName = name or (id == 1 and 'Main' or 'Profile')
      local prefix = newName
      local i = 2
      while db.profilesName[newName] do
        i = i + 1
        newName = prefix .. i
      end
      name = newName
      print('Generated profile name: |cFF00FF00'..newName..'|r')
    end
    print('Creating profile')
    db.profilesName[name] = id
    db.profiles[id] = {
      name = name,
      workspace = (id ~= 1),
      current_channel = 1,
      default_channel = 1,
      num_channels = 1,
      max_channel = 1, -- the highest created channel id
      enabled = true, -- allow enabled consoles to appear
      channels = {
        {
          index = 1,
          signature = 'Main',
          x = 100, y = 800,
          height = 500, width = 600,
          enabled = true
        }
      },
      loadouts = {},
      global = {},
      tags = {},
      char = {
        [playerRealm] = {}
      },
      unlisted = {}
    }
  end

  D.currentProfile = db.profiles[id]
  currentProfile = D.currentProfile
  currentProfile.char[playerRealm] = currentProfile.char[playerRealm] or {}
  if currentProfile.workspace then
    DEVIAN_WORKSPACE = true
    setprinthandler(D.Message)
  else
    DEVIAN_WORKSPACE = false
    print = function() end
  end

  D.unlisted = currentProfile.unlisted
  D.channels = currentProfile.channels
  D.tags = currentProfile.tags
  D.channelinfo = currentProfile.channels
  D.char = currentProfile.char[playerRealm]
  D.global = currentProfile.global
  D.num_channels = currentProfile.num_channels
  D.enabled = currentProfile.enabled
  D.sig = {}
  D.sigID = {}
  D.IDsig = {}
  D.dock = _G.DevianDock
  D.dock.buttons = D.dock.buttons or {}

  return id, name
end

local targetGlobal, targetChar
D.Command = function (self, cmd, ...)
  local list_id, scan_func, reload
  local args = {}
  local arg, n = D:GetArgs(cmd, 1)
  while arg do
    insert(args, arg)
    arg, n = D:GetArgs(cmd,1,n)
  end
  local mode, tag, dest = unpack(args)

  -- no args, toggle ui
  if  mode == 'stack' then
    return D:StackFrames()
  elseif mode == 'grid' then
    return D:DistributeFrames()
  elseif mode == 'tag' then -- tagging
    return D.Tag(self, tag, dest)
  elseif mode == 'new' then
    return D.New(self, tag)
  elseif mode == 'remove' then
    return D.Remove(self, tag)
  elseif tonumber(mode) ~= nil or mode == 'save' then
    -- either a number of the save command
    if mode == 'save' then
      list_id = tonumber(tag)
      if list_id == nil then
        D:Print(L('Invalid ID from arg', tag))
      end
      D.Profile(list_id, dest)
      scan_func = D.Save
      D:Print("Profile |cFFFFFF00".. list_id .."|r saved.")
    else

      D.LoadMessage "Switched profiles."

      list_id = tonumber(mode)
      if list_id ~= db.main_profile then
        db.last_profile = list_id
      end
      db.current_profile = list_id
      scan_func = D.Load
    end
  elseif mode == nil then
    list_id = (db.current_profile ~= db.main_profile) and db.main_profile or db.last_profile
    D.LoadMessage ("Switched between main and recent profile ("..db.current_profile..' and '..list_id..')')
    db.current_profile = list_id
    scan_func = D.Load
  else
    return D:PrintHelp()
  end

  if not db.profiles[list_id] then
    db.profiles[list_id] = {global = {}, char = {} }
    D.LoadMessage ("Starting profile #|cFF00FFFF".. list_id..'|r')
  end
  if not db.profiles[list_id].char[playerRealm] then
    db.profiles[list_id].char[playerRealm] = {}
  end

  targetGlobal = db.profiles[list_id].global
  targetChar = db.profiles[list_id].char[playerRealm]


  if scan_func then
    for id, name, enableState, globalState in D.Addons() do
      scan_func(id, name, enableState, globalState)
    end
  end

  if scan_func == D.Load then
    _G.ReloadUI()
  end
  D.Profile(db.current_profile)
end

D.Addons = function()
  local playername = UnitName("player")
  return function(n, i)
    if i >= n then
      return nil
    end

    i = i + 1
    local name = GetAddOnInfo(i)
    local enableState, globalState = GetAddOnEnableState(playername, i), GetAddOnEnableState(nil, i)
    return i, name, enableState, globalState
  end, GetNumAddOns(), 0
end

D.Load = function(id, name, charState, globalState)
  if targetChar[name] or targetGlobal[name] then
    if targetGlobal[name] == 2 then
      EnableAddOn(id, true)
    elseif targetChar[name] == 2 then
      EnableAddOn(id, playerName)
    else
      if targetChar[name] == 2 and targetGlobal[name] ~= 2 then
        DisableAddOn(id)
      else
        DisableAddOn(id, true)
      end
    end
    --print('load', name, 'global =', targetGlobal[name], 'char =', targetChar[name])
  else
    tinsert(D.unlisted, name)
  end
end
D.Save = function(id, name, charState, globalState)
  --print('save', id, name, charState, globalState)
  targetGlobal[name] = globalState
  targetChar[name] = charState
end

D.Tag = function(self, tag, dest)
  local sig
  if tag ~= nil and dest ~= nil then
    --@debug@
    --print(tag, dest)
    --@end-debug@

    -- convert to ID
    if tonumber(dest) == nil then
      if D.sigID[dest] then
        dest = currentProfile.channels[D.sigID[dest]].index
      else
        sig = dest
      end
    else
      dest = tonumber(dest)
    end
    --@debug@
    --print('2 tag,dest,sig', tag, dest, sig)--@end-debug@

    -- make a new channel?
    local channel
    if not currentProfile.channels[dest] then
      dest = D.max_channel + 1
      D:Print(L('New channel created', sig and (dest..':'..sig) or dest ))
      channel = D:SetChannel(sig or tag,dest)
    else
      channel = D.channels[dest]
    end
    --@debug@
    --print('3 tag,dest,channel.sig=',tag, dest, channel.signature)--@end-debug@

    if not currentProfile.tags[tag] then -- no tag table?
    currentProfile.tags[tag] = {}
    end

    if currentProfile.tags[tag][dest] then -- is tag set?
    currentProfile.tags[tag][dest] = nil
    D:Print(L('Tag removed from channel', tag, currentProfile.channels[dest].index, currentProfile.channels[dest].signature))
    else
      currentProfile.tags[tag][dest] = dest
      D:Print(L('Tag added to channel', tag, currentProfile.channels[dest].index, currentProfile.channels[dest].signature))
    end
    D:UpdateDock()
  else
    D:Print(L['Command tag help'])
  end
end

D.New = function(self, tag)
  if tag and not self.sigID[tag] then
    local id = D.max_channel + 1
    D.SetChannel(tag, id)
  end
end

D.Remove = function(self, dest)
  dest = D.sigID[dest] or tonumber(dest)
  if D.console[dest] and D.channels[dest] then
    for tag, tagDest in pairs(D.tags) do
      for i = #tagDest, 0 do
        -- work downward so we aren't skipping entries
        if tagDest[i] == dest then
          tremove(tagDest, i)
        end
      end
    end
    D.console[dest]:Hide()
    D.channels[dest] = nil
    tremove(D.console, dest)
    tremove(D.dock.buttons, dest)
    D:UpdateDock()
    D:Print('Removed channel #'..dest)
  end
end

--- Queue up a message to appear after UI reload
function D.LoadMessage(msg)

  tinsert(_G.DevianLoadMessage, msg)
end

--- Creates a Devian-style output.
-- The first argument describes the channel to output on, and the remaining arguments are concatenated in a manner similar to default print()
-- This becomes the print handler when development mode is active. The original print() function is assigned to oldprint().
-- @param Tag, signature, or numeric index of the channel to output on. Defaults to primary channel.
-- @param ... Output contents.

function D.Message(prefix, ...)
  if not currentProfile.workspace then
    return D.oldprint(prefix, ...)
  end
  prefix =  tostring(prefix)
  if prefix == nil then
    prefix = 'nil*'
  end

  local sendq = {}
  local tag, id, tagged
  local byName = true
  if D.tags[prefix] then
    for _, id in pairs(D.tags[prefix]) do
      if D.console[id] then
        sendq[id] = D.console[id]
        tagged = true
      end
    end
  end

  if D.sig[prefix] then
    sendq[D.sig[prefix].index] = D.sig[prefix]
  elseif D.console[prefix] then
    sendq[D.console[prefix]] = D.console[prefix]
  elseif not tagged then
    sendq[D.primary_channel] = D.console[D.primary_channel]
  end

  -- color me timbers
  local pcolor
  if (not db.tagcolor[prefix]) and byName then
    if prefix:match('^%d+%.%d+$') then
      pcolor = 'FFFFFF'
    else
      local c = {
        rand(64,255), rand(64,255), rand(64,255)
      }
      if c[1] > 223 and c[2] > 223 and c[3] > 223 then
        c[rand(1,3)] = rand(64,223)
      end
      db.tagcolor[prefix] = format('%02X%02X%02X', unpack(c))
      pcolor = db.tagcolor[prefix]
    end
  else
    pcolor = db.tagcolor[prefix]
  end

  local buffer = {}
  for i = 1, select('#',...) do
    local var = select(i, ...)

    if type(var) == 'table' then
      if type(var.GetName) == 'function' then
        var = '[table:'..tostring(var:GetName())..']'
      else
        var = '<'..tostring(var)..'>'
      end

    elseif type(var) == 'boolean' then
      var = var and 'true' or 'false'
    elseif type(var) == 'function' then
      var = '['..tostring(var)..']'
    elseif type(var) == 'nil' then
      var = 'nil'
    else
      var = tostring(var)
    end

    insert(buffer, var)
  end
  local message = concat(buffer, ' ')
  for id, channel in pairs(sendq) do
    if channel.width < 250 then
      prefix = sub(prefix, 0,2)
    end
    --currentProfile.last_channel = channel.index
    channel.out:AddMessage('|cFF'.. pcolor..prefix ..'|r ' .. message, 0.8, 0.8, 0.8, nil, nil, prefix, GetTime())
    if not D.dock.buttons[id].newMessage then
      D.dock.buttons[id].newMessage = true
      D.UpdateBeacon(D.dock.buttons[id])
    end
  end
  wipe(buffer)
end




function D:PrintHelp()
  D:Print("|cFFFFFF00/dvn|r",
    "\n |cFFFFFF00<number>|r - Loads a saved addon list. List 1 is treated as a gameplay profile and consoles will be disabled by default.")
  D:Print("|cFFFFFF00/dvc|r [<key>, ...]", "- Hides and show consoles. A list of channel keys can be passed to specify which ones get toggled.")

  D:Print("|cFFFFFF00/resetdvn|r", "- Resets all but profile data SavedVariables.")
  D:Print("|cFFFFFF00/cleandvn|r", "- Fully resets SavedVariables, profiles and all.")
end

local blocked = {profiles = true, debugmode = true}
D.SetDefaults = function()
  local DevianDB = _G.DevianDB
  for k,v in pairs(DevianDB) do
    if not blocked[k] then
      DevianDB[k] = nil
    end
  end
  for k,v in pairs(defaults) do
    if not blocked[k] then
      DevianDB[k] = v
    end
  end

  D.LoadMessage "Non-user SavedVars have been reset."
  _G.ReloadUI()
end
D.SetDefaultsAll = function ()
  _G.DevianDB = nil
  D.LoadMessage "All SavedVars wiped."
  _G.ReloadUI()
end

D.UnsetColors = function()
  db.tagcolor = {}
  D:Print('Tag color cache cleared.')
end
D.ConfigCommand = function(self, input)
  if not self.config then
    self.config = DevCon
    self:EnableModule("Config")
  end
  self.modules.Config:ChatCommand(input)
end

function D:OnEnable()

  -- commands
  local cmdlist = {
    ['dfs'] = 'FrameHelper_Update',
    ['dvn'] = "Command",
    ['devian'] = "Command",
    ['dvc'] = "Console_Toggle",
    ['dvncolors'] = "UnsetColors",
    ['cleandvn'] = "SetDefaultsAll",
    ['resetdvn'] = "SetDefaults",
    ['dvg'] = "ConfigCommand"
  }
  for cmd, func in pairs(cmdlist) do
    self:RegisterChatCommand(cmd, func, true)
  end
end

function D:OnInitialize()
  L = D.L

  -- pull defaults
  if not _G.DevianDB then
    _G.DevianDB = defaults
  end
  db = _G.DevianDB
  self.db = db

  ---
  if #_G.DevianLoadMessage >= 1 then
    for i, msg in ipairs(_G.DevianLoadMessage) do
    D:Print(msg)
    end

    table.wipe(_G.DevianLoadMessage)
  end


  --- initialize the current profile
  local id, name = D.Profile(db.current_profile or 1)
  D:Print('Using profile |cFFFFFF00'.. id ..'|r: |cFF00FF00'..currentProfile.name..'|r')
  if  currentProfile.workspace then
    D:Print('Workspace: '.. (#currentProfile.channels) .. ' channels, ' .. #currentProfile.tags .. ' tags.')
    D:Print('Default channel: |cFFFFFF00'..currentProfile.default_channel..'|r: |cFF00FFFF'.. D.channels[currentProfile.default_channel].signature..'|r')
  end

  for i, cinfo in pairs(D.channels) do
    i = tonumber(i)
    if not D.primary_channel then
      D.primary_channel = i
    end
    D:SetChannel(cinfo, i)
    D.num_channels = D.num_channels + 1
  end

  D.max_channel = max(D.max_channel, currentProfile.max_channel)
  if currentProfile.max_channel < D.max_channel then
    for i = currentProfile.max_channel, D.max_channel do
      D.console[i]:Hide()
    end
  end

  if currentProfile.workspace then
    if D.console[currentProfile.current_channel] then
      --print('bringing', D.console[currentProfile.current_channel].signature, 'to the front')
      D.console[currentProfile.current_channel]:ToFront()
      -- bring the current channel to the front
    end
    _G.DevianDock:Show()
    D:UpdateDock()
  end
end