view Devian.lua @ 28:31ca76e04766 v1.4.4

/dvn tag and /dvc commands will respond to all of their arguments Index assignment algorithm should stop breaking new channels. current channel's ToFront is invoked after the initializer loop
author Nenue
date Fri, 25 Dec 2015 03:32:56 -0500
parents b0e8bd30575f
children 42e74b72726d
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',
  backalpha = 0.5,
  backdrop = {0,0,0,0.4},
  backgrad = {'VERTICAL', 0.1, 0.1, 0.1, 0.3, 0, 0, 0, 0.5},
  backblend = 'BLEND',
  frontdrop = {0,0,0,1},
  frontgrad = {'VERTICAL', 0.1, 0.1, 0.1, 0.9, 0, 0, 0, 0.9},
  frontblend = 'BLEND',
  frontalpha = 1,
  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 n = 0
  if D:GetArgs(input,1) then
    repeat
      key, n = D:GetArgs(input,1,n)
      if D.sig[key] then
        table.insert(search, D.sig[key])
      elseif D.console[key] then
        table.insert(search, D.console[key])
      end
    until n == 1e9
  else
    search = D.console
  end

  db.toggle = not db.toggle and true or nil
  for i, c in ipairs(search) do
    print(i,c.index)
    if db.toggle then
      c.enabled = true
      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:SetAlpha(db.frontalpha)
  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:SetAlpha(db.backalpha)
      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]
  local signame

  -- is cinfo a table or signature?
  if type(cinfo) == 'string' then
    signame = tostring(cinfo)
    t_info.signature = signame
  elseif type(cinfo) ~= 'table' then
    cinfo = {}
  end

  -- did we get a signature string?
  if not (cinfo.signature or t_info.signature) then
    t_info.signature = 'Console'
  end

  -- was an index given?
  if not i then
    i = D.num_channels + 1
    t_info.index = i
    -- make a new one
  else
    i = tonumber(i)
    -- is it valid?
    if db.channels[i] then
      dbvars = db.channels[i]
      -- use that as our base vars
    else
      if db.sig[t_info.signature] then
        local sigvar = t_info.signature
        local j = 2
        while D.sig[sigvar] do
          sigvar = signame .. j
          j = j + 1
        end
        t_info.signature = sigvar
      end

      i = D.num_channels + 1
      t_info.index = i
      -- make a new index number and fix the signature
    end
  end

  -- can proceed to fill in from base vars here
  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

  -- we're working with a fresh channel right?
  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
    -- set its position just off of the base vars and store it
  end

  -- can proceed to display something from here
  if not self.console[i] then
    self.console[i] = CreateConsole(i, t_info)
    -- if it isn't already spawned, create the frame
  end
  local channel = self.console[i]
  self.sig[t_info.signature] = channel
  self.sigID[t_info.signature] = i
  self.IDsig[i] = t_info.signature

  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()
    -- bring the current channel to the front
  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