view Devian.lua @ 14:5254d1ba6013 v1.2-r15

Multiple output windows can be created for different output prefixes, and prefixes can be designated to an existing or new "console" with /dvn <prefix> <console> Frame objects are generated directly from XML data, and their state information is stored in the object itself to resolve issues with GetWidth() returning old info in some cases. StackFrames and DistributeFrames methods can be invoked from /script to arrange the buffers.
author Nenue
date Sun, 20 Dec 2015 00:46:42 -0500
parents 080dfa4990fb
children 48a1d9c14af5
line wrap: on
line source
-- User: Krakyn
-- Created: 11/30/2015 7:46 AM
if not LibStub then
  print('Something has happened...')
end
Devian = LibStub("AceAddon-3.0"):NewAddon("Devian", "AceConsole-3.0", "AceEvent-3.0")
local MAJOR, MINOR = 'Devian-1.3', 'r@project-revision@'
local D = _G.Devian
local STATE_LOW, STATE_HIGH = 1, 2
local PLAYER_REALM = UnitName("player") .. '-' .. GetRealmName()
local DEVIAN_FRAME = 'DevianConsole'
local print = function(...)
  _G.print('Dvn', ...)
end
local db
local defaults = {
  ['global'] = {[STATE_LOW] = {}, [STATE_HIGH] = {}},
  ['tags'] = {},
  ['channels'] = {[1] = {signature = 'Dvn', name = 'Main', header = "%n [%t]", x = 100, y = 800, height = 500, width = 600, enabled = true}},
  primary_channel = 1,
  current_channel = 1,
  toggle = true,
  dnd_status = true,
  dnd_message = "Debugging. Your messages may get eaten.",
  font = [[Interface\Addons\Devian\font\SourceCodePro-Regular.ttf]],
  fontsize = 13,
  fontoutline = 'NONE',
  backdrop = {1,1,1,0.2},
  backgrad = {'VERTICAL', 0.1, 0.1, 0.1, 0.3, 0, 0, 0, 0.5},
  backblend = 'BLEND',
  frontdrop = {1,1,1,1},
  frontgrad = {'VERTICAL', 0.1, 0.1, 0.1, 0.9, 0, 0, 0, 0.9},
  frontblend = 'BLEND'
}


local function ScanAddOnList(cmd, ...)
  local list_state

  local args = {}
  local arg, n = D:GetArgs(cmd, 1)
  while arg do
    table.insert(args, arg)
    arg, n = D:GetArgs(cmd,1,n)
  end
  local mode, tag, dest = unpack(args)


  -- no args, toggle ui
  if mode == nil then
    list_state = db.enabled and STATE_LOW or STATE_HIGH
    db.enabled = (db.enabled == false) and true or false
    print(list_state, db.enabled)

    if list_state == STATE_LOW then
    end
  elseif mode == 'stack' then
    return D:StackFrames()
  elseif mode == 'grid' then
    return D:DistributeFrames()
  elseif mode == 'tag' then -- tagging
    if tag ~= nil and dest ~= nil then
      if not D.console[dest] and not D.sig[dest] then
        local sig = dest:match('%a')
        local id = dest:match('%d')
        if not id then
          id = self.last_channel + 1
        end
        if not sig then
          sig = tag
        end
        D:SetChannel(sig, id)
      end
      D:Print('Assigning |cFFFFFF00'..tag..'|r to |cFF00FFFF'.. dest .. '|r')
    else
      D:Print('Usage: /dvn tag <prefix> <console name or number>')
    end
    return
  elseif mode ~= nil then
    mode = tonumber(mode)
    if mode > 2 then
      print('Something has happened.')
      return
    end
    list_state = mode == STATE_LOW and STATE_LOW or STATE_HIGH
  end
  local char_list, global_list = db[PLAYER_REALM][list_state], db.global[list_state]

  local playername = UnitName("player")

  for i = 1, GetNumAddOns() do
    local name = GetAddOnInfo(i)
    local enableState, globalState = GetAddOnEnableState(playername, i), GetAddOnEnableState(nil, i)

    if mode == STATE_LOW or mode == STATE_HIGH then
      char_list[name] = enableState
      global_list[name] = globalState
    else
      if char_list[name] or global_list[name] then

      if char_list[name] ~= 0 and global_list[name] ~= 0 then
        local value = false
        if char_list[name] == 2 and global_list[name] == 1 then
          value = UnitName("player")
        elseif global_list[name] == 2 then
          value = true
        end
        print('EnableAddOn(', i, ',', value,')')
        EnableAddOn(i, value)
      else
        local value = true
        if char_list[name] == 2 and global_list[name] == 1 then
          value = UnitName("player")
        end
        print('DisableAddOn(', i, ',', value,')')
        DisableAddOn(i,value)
      end
      end

    end
  end

  if mode == nil then
    ReloadUI()
  end
  if mode == STATE_LOW then
    D:Print('Developement AddOn list saved.')
  else
    D:Print('Standard AddOn list saved.')
  end
end


local function Console_MinMax(self)
  if self.minimized then
    self:Maximize()
  else
    self:Minimize()
  end
end

local function Console_Minimize(self)
  self:SetHeight(20)
  self:SetMaxResize(GetScreenWidth(),20)
  self.minimized = true
  self.out:Hide()
  self:Save()
end

local function Console_Maximize(self)
  local db = db.channels[self.index]
  self:SetHeight(db.height)
  self:SetMaxResize(GetScreenWidth(),GetScreenHeight())
  self.minimized = nil
  self.out:Show()
  self:Save()
end


local function Console_Save(self)
  local db = db.channels[self.index]
  if self.x then
    db.x = self.x
  else
    db.x = self:GetLeft()
  end

  if self.y then
    db.y = self.y
  else
    db.y = (self:GetTop() - GetScreenHeight())
  end

  if self.width then
    db.width = self.width
  else
    db.width = self:GetWidth()
  end

  if not self.minimized then
    if self.height then
      db.height = self.height
    else
      db.height = self:GetHeight()
    end
    self:SetHeight(db.height)
  end

  db.minimized = self.minimized and true or nil
  db.enabled = self:IsVisible() and true or nil
  db.active = self.active and true or nil
  print('save:', db.signature, 'min=', db.minimized, ' enabled=', db.enabled, ' active = ', db.active, 'x=', db.x, 'y=', db.y, 'h=', db.height, 'w=', db.width)
  self:SetPoint('TOPLEFT', UIParent, 'TOPLEFT', db.x, db.y)
  self:SetWidth(db.width)
end

-- Console frame toggler
-- @paramsig [...]
-- @param ... one or more space-seperated channel keys
local function Console_Toggle(input)
  local search = {}
  local key, n = D:GetArgs(input, 1)
  if key then
    repeat
      if D.sig[key] then
        table.insert(search, D.sig[key])
      elseif D.console[key] then
        table.insert(search, D.console[key])
      end
      key, n = D:GetArgs(input,1,n)
    until n == 1e9
  else
    search = D.console
  end

  db.toggle = not db.toggle and true or nil
  for _, c in ipairs(search) do
    if db.toggle then
      c:Show()
    else
      c.enabled = nil
      c.minimized = nil
      c:Maximize()
      c:Hide()
    end
  end

  if db.toggle then
    D:Print('toggled on?')
  else
    D:Print('toggled off?')
    end
end

-- Bring console to the front
local function Console_ToFront(c)
  --print(D.raise_ct, 'Raising', c.signature)
  --print(unpack(db.frontdrop))
  --print(unpack(db.frontgrad))
  --print(db.frontblend)
  D.raise_ct = D.raise_ct + 1
  c:Raise()
  c.out.backdrop:SetTexture(unpack(db.frontdrop))
  c.out.backdrop:SetGradientAlpha(unpack(db.frontgrad))
  c.out.backdrop:SetBlendMode(db.frontblend)

  for id, bc in pairs(D.console) do
    if id ~= c.index then
      --print(D.raise_ct, 'Lowering', bc.signature)
      --print(unpack(db.backdrop))
      --print(unpack(db.backgrad))
      --print(db.backblend)
      bc.out.backdrop:SetTexture(unpack(db.backdrop))
      bc.out.backdrop:SetGradientAlpha(unpack(db.backgrad))
      bc.out.backdrop:SetBlendMode(db.backblend)
      end
  end

end

-- Generate a console frame
-- @paramsig id, vars
-- @param id channel number
-- @param vars alternative config, else uses db.channels[id]
local function CreateConsole(i, vars)

  if not vars then
    vars = db.channels[i]
  end

  print('make:', vars.signature, '(', vars.x, vars.y, ')', vars.width, 'x', vars.height)
  local f = CreateFrame('Frame', 'DevianChannelFrame' .. tostring(i), UIParent, DEVIAN_FRAME)
  f:SetPoint('TOPLEFT', UIParent, 'TOPLEFT', vars.x, vars.y)
  f:SetSize(vars.width, vars.height)
  f:Lower()
  f.out:SetFont(db.font, db.fontsize, db.fontoutline)
  f.Save = Console_Save
  f.Minimize = Console_Minimize
  f.Maximize = Console_Maximize
  f.MinMax = Console_MinMax
  f.ToFront = Console_ToFront
  f.Toggle = D.Console_Toggle
  f.name = vars.name
  f.index = i
  f.signature = vars.signature
  f.format = vars.header
  f.x = vars.x
  f.y = vars.y
  f.width = vars.width
  f.height = vars.height

  if vars.enabled  then
    f.enabled = true
    if db.toggle then
      f:Show()
    end
  end
  if vars.minimized then
    f:Minimize()
  end

  return f
end

-- Print to Devian output
-- @paramsig tag, ...
-- @param tag channel signature or number used to select console
-- @param ... print arguments
local function Message(prefix, ...)
  if prefix == nil then
    prefix = 1
  end

  local channel
  local byName = true
  if D.sig[prefix] then
    channel = D.sig[prefix]
  elseif D.console[prefix] then
    channel = D.console[prefix]
    byName = nil
  else
    channel = D.console[1]
  end
  -- color me timbers
  local pcolor
  if D.tags[prefix] then
    pcolor = db.tags[prefix]
  elseif byName then
    local c  = {0, 0, 0 }
    local max = string.len(prefix)
    for i = 1, max, 3 do
        for k, v in ipairs(c) do
          local j = i + (k - 1)
          c[k] = c[k] + (j <= max and string.byte(prefix,j) or 0)
        end
    end
    for k,v in ipairs(c) do
      c[k] = c[k] % 255
      if c[k] < 64 then
        c[k] = 0
      elseif c[k] > 127 then
        c[k] = 255
      end
    end
    D.tags[prefix] = string.format('%02X%02X%02X', unpack(c))
  end

  local buffer = {'|cFF'.. D.tags[prefix]..prefix ..'|r'}
  for i = 1, select('#',...) do
    local var = select(i, ...)

    if type(var) == 'table' then
      var = '<table>'
    elseif type(var) == 'boolean' then
      var = var and 'true' or 'false'
    elseif type(var) == 'function' then
      var = '<funcref>'
    elseif type(var) == 'nil' then
      var = 'nil'
    end

    table.insert(buffer, var)
  end
  channel.out:AddMessage(table.concat(buffer, ' '))
  table.wipe(buffer)
end

-- Spaces each frame evenly across the screen
function D:DistributeFrames() --
  print('frame grid:', max, num_side)
  local max = self.num_channels
  local num_side = math.ceil(math.sqrt(max))
  local w = GetScreenWidth() / num_side
  local h = GetScreenHeight() / num_side
  for i, frame in pairs(D.console) do
    local dx = (i-1) % num_side
    local dy = math.floor((i-1) / num_side)

    print('move:', frame.signature, 'dx=', dx, 'dy=', dy)
    print('move:', frame.signature, '                                       x=', dx * w, 'y=', -(dy * h), 'h=', h, 'w=', w)
    frame.width = w
    frame.height = h
    frame.x = dx * w
    frame.y = -(dy * h)
    frame:Save()
  end

end

-- Place all frames stacked beneath the primary frame
-- @paramsig
function D:StackFrames()
  local last
  for i, frame in pairs(self.console) do
    if last then
      frame.x = last.x
      frame.y = last.y - 20
    else
      frame.x = (GetScreenWidth()-frame:GetWidth())/2
      frame.y = 0
    end
    frame:Save()
    last = frame
  end
end

-- Creates or updates a console frame
-- @paramsig cinfo [, i]
-- @param cinfo an array from db.channels[x] or the desired string signature
-- @param id when set, the console at that index will be assigned all parameters in cinfo
function D:SetChannel(cinfo, i)
  print('join:', i , cinfo)
  local t_info = {}
  if type(cinfo) ~= 'table' then
    t_info.signature = tostring(cinfo)
    cinfo = {}
  end
  local srcdb = db.channels[self.primary_channel]
  if i ~= nil then
    i = tonumber(i)
    if db.channels[i] then
      print('pull vars from '..db.channels[i].signature)
      cinfo = db.channels[i]
      srcdb = cinfo
    end
  end

  for k,v in pairs(srcdb) do
    if not t_info[k] then
      if cinfo[k] then
        print('- pulling', k..':',v)
        t_info[k] = cinfo[k]
      end
    end
  end

  if not db.channels[i] then
    t_info.x = t_info.x + 20
    t_info.y = t_info.y - 20
    db.channels[i] = t_info
  end
  if not self.console[i] then
    self.console[i] = CreateConsole(i, t_info)
  end

  self.sig[cinfo.signature] = self.console[i]
  self.sigID[cinfo.signature] = i
  self.IDsig[i] = cinfo.signature
end

function D:OnEnable()
  -- commands
  local cmdlist = {
    ['dvn'] = ScanAddOnList,
    ['devian'] = ScanAddOnList,
    ['dvc'] = Console_Toggle,
  }
  for cmd, func in pairs(cmdlist) do
    self:RegisterChatCommand(cmd, func, true)
  end

  if db.enabled == true then
    D:Print('Standard AddOn list active. Type /dvn to switch to development mode.')
  else
    D:Print('Development AddOn list active. Type /dvn to revert to regular operation.')
  end

end

function D:OnInitialize()
  -- emergency button
  self:RegisterChatCommand("cleandvn", function(args)
    DevianDB = nil
    ReloadUI()
    end)

  -- savedvars
  local cherry = false
  if not _G.DevianDB then
    _G.DevianDB = defaults
    cherry = "Type /dvnsave to snapshot your current UI"
  end
  db = _G.DevianDB

  if not db[PLAYER_REALM] then
    db[PLAYER_REALM] = {[STATE_LOW] = {}, [STATE_HIGH] = {}}
    if not cherry then
      cherry = "This character didn't have an AddOn table."
    end
  end

  -- copy tags
  self.tags = {}
  for n, c in pairs(db.tags) do
    self.tags[n] = c
  end
  if cherry then
    D:Print(cherry)
  end
  D.oldprint = getprinthandler()
  if not _G.oldprint then
    _G.oldprint = D.oldprint
  end

  self.raise_ct = 0
  self.last_channel = 0
  self.num_channels = 0
  self.console = {}
  self.sig = {}
  self.sigID = {}
  self.IDsig = {}
  for i, cinfo in pairs(db.channels) do
    i = tonumber(i)
    if not self.primary_channel then
      self.primary_channel = i
    end
    self:SetChannel(cinfo, i)
    if i > self.last_channel then
      self.last_channel = i
    end
    self.num_channels = self.num_channels + 1
  end
  setprinthandler(Message)
  print(MAJOR, MINOR)
end