view Devian.lua @ 27:6d1528a55514

Added tag v1.4.3 for changeset b0e8bd30575f
author Nenue
date Thu, 24 Dec 2015 03:00:16 -0500
parents b0e8bd30575f
children 31ca76e04766
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 = _G.print
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',
  frontborder = {1,0,0,1},
  backborder = {0,0,1,0.75},
}


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
      local channel = D:SetChannel(dest:match('%a'), dest:match('%d'))
      if not D.tags[tag] then
        D.tags[tag] = {}
      end
      if D.tags[tag][channel.index] then
        D.tags[tag][channel.index] = nil
        D:Print('Removed |cFFFFFF00'..tag..'|r from |cFF00FFFF'.. dest .. '|r')
      else
        D.tags[tag][channel.index] = channel.index
        D:Print('Assigning |cFFFFFF00'..tag..'|r to |cFF00FFFF'.. dest .. '|r')
      end

    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()
      if db.current_channel == c.index then
        c:ToFront()
      end
    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

--- Brings the console to the front.
-- Frame method used to bring a console frame to the front of the display stack.
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)
  db.current_channel = c.index

  for _, part in pairs(c.border) do
    part:SetTexture(unpack(db.frontborder))
  end

  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)

      for _, part in pairs(bc.border) do
        part:SetTexture(unpack(db.backborder))
      end
    end

  end

end

--- Constructs the frame object for a console channel
-- Initializes the console channel at a specified index.
-- Configuration data can be overridden by passing a desired settings table.
-- @param i Numeric index of the channel as it manifests in db.channels
-- @param vars Optional settings table to be used.
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)
  if (db.current_channel == i) then
    f.out.backdrop:SetTexture(unpack(db.frontdrop))
  else
    f.out.backdrop:SetTexture(unpack(db.backdrop))
  end

  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()
  else
    f:Maximize()
  end

  return f
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.
local function Message(prefix, ...)
  if db.enabled == true then
    return D.oldprint(prefix, ...)
  end

  if prefix == nil then
    prefix = 1
  end

  local sendq = {}
  local tag, id
  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]
      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]
  else
    sendq[D.primary_channel] = D.console[D.primary_channel]
  end

  -- color me timbers
  local pcolor
  if (not db.tagcolor[prefix]) and 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
    db.tagcolor[prefix] = string.format('%02X%02X%02X', unpack(c))
  end
  pcolor = db.tagcolor[prefix]

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

    if type(var) == 'table' then
      if type(var.GetName) == 'function' then
        var = '<table:'..(var:GetName() or '?')..'>'
      else
        var = '<table>'
      end

    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
  local message = table.concat(buffer, ' ')
  for id, channel in pairs(sendq) do
    channel.out:AddMessage(message)
  end
  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.
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

--- Updates a console "channel" entry, generating a new one if necessary.
-- Config data will be take from cinfo. If cinfo is a string, then only channel signature is set. The remaining variables are filled in from the primary channel.
-- i can be given to select a specific channel table entry to work on. Otherwise, it will just create a new channel and the frame associated with it.
-- @usage cinfo [, i]
-- @param cinfo Config variables table, or a string to be used as channel signature
-- @param i Console index. If valid, settings will be inherited from that channel.
function D:SetChannel(cinfo, i)
  --print('join:', i , cinfo)
  local t_info = {}
  local dbvars = db.channels[self.primary_channel]
  if type(cinfo) == 'string' then
    local signame = tostring(cinfo)
    t_info.signature = signame
  end

  if type(cinfo) ~= 'table' then
    cinfo = {}
  end

  if i then
    i = tonumber(i)
    if db.channels[i] then
      dbvars = db.channels[i]
    else
      -- if there is no channels[i], then we need to check for sig collision
      local sigvar = signame
      local j = 2
      while D.sig[sigvar] do
        sigvar = signame .. j
        j = j + 1
      end
      t_info.signature = sigvar
    end
  end

  if not (cinfo.signature or t_info.signature) then
    t_info.signature = 'Console'..i
  end

  for k,v in pairs(dbvars) do
    if not t_info[k] then
      if cinfo[k] then
        t_info[k] = cinfo[k]
      elseif db.channels[self.primary_channel][k] then
        t_info[k] = db.channels[self.primary_channel][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
  local channel = self.console[i]
  self.sig[t_info.signature] = channel
  self.sigID[t_info.signature] = i
  self.IDsig[i] = t_info.signature
  if i == db.current_channel then
    channel:ToFront()
  end

  return channel
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


  if not db.tags then
    db.tags = {}
  end
  self.tags = db.tags
  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

  if self.console[db.current_channel] then
    self.console[db.current_channel]:ToFront()
  end

  if db.enabled then
    for i, c in pairs(self.console) do
      self.console[i]:Hide()
    end
  end


  -- only do this in dev mode
  if db.enabled == false then
    setprinthandler(Message)
    print = function(...)
      _G.print('Dvn', ...)
    end
  end
  print(MAJOR, MINOR)
end