view Devian.lua @ 32:c6a2c2df4790

v2 work
author Nenue
date Sat, 26 Dec 2015 21:51:57 -0500
parents 6fcfe60bbd0f
children e6650821a2c0
line wrap: on
line source
--- ${PACKAGE_NAME}
-- @file-author@
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
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 WORKSPACE_ON, WORKSPACE_OFF = 1, 2
local PLAYER_REALM = UnitName("player") .. '-' .. GetRealmName()
local DEVIAN_FRAME = 'DevianConsole'
local print = _G.print
local db
local defaults = {
  ['global'] = {{}, {}},
  ['tags'] = {},
  ['channels'] = {[1] = {signature = 'Main', index = 1, x = 100, y = 800, height = 500, width = 600, enabled = true}},
  primary_channel = 1,
  current_channel = 1,
  max_channel = 1,
  toggle = true,
  load_message = "Defaults loaded.",
  font = [[Interface\Addons\Devian\font\SourceCodePro-Regular.ttf]],
  fontsize = 13,
  fontoutline = 'NONE',
  headergrab = {'VERTICAL', 0, 0, 0, 0.5, 0.1, 0.1, 0.1, 0.3},
  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},
  tagcolor = {},
  workspace = 1,
}


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 == 'dock' then
    if #args <= 2 then
      D:Print("Not enough arguments for dock command.")
      return
    end

    local target
    local worklist = {}
    for i = 2, #args do
      local ch
      local k = tostring(args[i])
      local j = tonumber(args[i])
      if db.channels[j] then
        ch = db.channels[j]
      elseif D.sig[k] then
        ch = D.sig[k]
      elseif D.sigID[k] then
        ch = db.channels[D.sigID[k]]
      elseif db.tags[k] and db.tags[k][1] then
        ch = db.channels[db.tags[j][1]]
        -- last resort
      else
        D:Print('No entry for argument #'..i..': '..tostring(args[i]))
        return
      end
      oldprint(i, '->', ch.index, '-', ch.signature)
      if i > 2 then
        table.insert(worklist, ch.index)
      else
        target = ch

        oldprint('arg1', args[2], target)
      end
    end
    D:Print("Docking |cFF88FFFF"..table.concat(worklist, "|r, |cFF88FFFF").."|r with |cFFFFFF00"..target.index..', '..target.signature.."|r.")
    return D:DockFrame(target.index, unpack(worklist))


  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
      -- convert to ID
      if tonumber(dest) == nil and D.sigID[dest] then
        dest = db.channels[D.sigID[dest]].index
      end

      -- make a new channel?
      if not db.channels[dest] then
        dest = db.max_channel + 1
        D:Print('Creating a new channel for '.. tag)
        D:SetChannel(tag, dest)
      end

      if db.tags[tag] and db.tags[tag][dest] then
        db.tags[tag][dest] = nil
        D:Print('Hiding |cFF88FFFF'..tag..'|r messages in |cFFFFFF00'..db.channels[dest].index ..':'.. db.channels[dest].index)
      else
        if not db.tags[tag] then
          db.tags[tag] = {}
        end
        db.tags[tag][dest] = dest
        D:Print('Showing |cFF88FFFF'..tag..'|r messages in |cFFFFFF00'..db.channels[dest].index ..':'.. db.channels[dest].index)
      end
    else
      D:Print('Usage: /dvn tag <prefix> <console name or number>')
    end
    return
  elseif tonumber(mode) ~= nil or mode == 'save' then
    -- iterating for something
    if mode == 'save' then
      if tonumber(tag) == nil then
        T:Print('Save ID is invalid:', tag)
      end
      list_state = tonumber(tag)
    else
      list_state = tonumber(mode)
      db.workspace = list_state
    end
  elseif mode == nil then
    list_state = db.last_workspace and db.last_workspace or 1
  else
    return D:PrintHelp()
  end

  -- start the iterating
  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 == 'save' 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
      else
        if type(db.unlisted) ~= 'table' then
          db.unlisted = {}
        end
        table.insert(db.unlisted, name)
      end

    end
  end

  if mode ~= 'save' then
    db.load_message = "AddOn profile ".. list_state .." was loaded."
    ReloadUI()
  else
      D:Print('Profile #'.. (list_state)..' saved.')
    if list_state == 1 then
      D:Print('This will be your main AddOn list.')
    elseif list_state == db.default_list then
      db.last_workspace = list_state
      D:Print('This will be your default workspace')
    end
  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.dockedTo = self.dockedTo
  db.docked = self.docked

  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

local function Console_MouseDown(self, button, up)
  if button == 'LeftButton' then
    if up then
      self:StopMovingOrSizing()
      self:ToFront()
      self.x = nil
      self.y = nil
      self.width = nil
      self.height = nil
      self:Save()
    elseif self.out.grip:IsMouseOver() then
      self:StartSizing()
    else
      self:StartMoving()
    end
  else
    if up then
      self:MinMax()
    end
  end
end
local function Console_MouseUp(self, button)
  return Console_MouseDown(self, button, true)
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, 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
    local c = {
      math.random(64,255), math.random(64,255), math.random(64,255)
    }
    if c[1] > 223 and c[2] > 223 and c[3] > 223 then
      c[math.random(1,3)] = math.random(64,223)
    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()..'>'
      else
        var = '<'..tostring(var)..'>'
      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


--- 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
  f.docked = vars.docked
  f.dockedTo = vars.dockedTo

  f:SetScript('OnMouseDown', Console_MouseDown)
  f:SetScript('OnMouseUp', Console_MouseUp)
  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

--- 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)
  -- try to resolve from arguments
  local dbvars
  local t_info = {}
  local channel
  local isNew
  if type(i) =='number' and  db.channels[i] then
      dbvars = db.channels[i]
  elseif type(i) == 'string' and D.sig[i] then
    dbvars = db.channels[D.sig[i].index]
  else
    dbvars = db.channels[db.primary_channel]
    isNew = true
  end

  --@debug@
  print('setchan(1)', cinfo, i, isNew)--@end-debug@

  if type(cinfo) == 'string' and not db.sig[cinfo] then
    t_info.signature = cinfo
    cinfo = {}
  elseif type(cinfo) ~= 'table' then
    error('Expecting table of string as arg1')
  end

  --@debug@
  print('setchan(2)', cinfo, i, isNew)--@end-debug@

  --TODO: figure out why tag assignments are getting eaten
  -- is cinfo a table or signature?



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

  --@debug@
  print('setchan(3)', cinfo, i, isNew, t_info.signature)--@end-debug@
  -- look for existing sigs
  if D.sig[t_info.signature] then
    local sigvar = t_info.signature
    local j = 2
    while D.sig[sigvar] do
      sigvar = sigvar .. j
      j = j + 1
    end
    t_info.signature = sigvar
  end

  --@debug@
  print('setchan(4)', cinfo, i, isNew, t_info.signature)--@end-debug@
  -- 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]
        --@debug@
        print('setchan(5a)', 'cinfo', k)--@end-debug@
      elseif db.channels[self.primary_channel][k] then
        t_info[k] = db.channels[self.primary_channel][k]
        --@debug@
        print('setchan(5b)', 'db', self.primary_channel, k)--@end-debug@
      end
    end
  end

  -- we're working with a fresh channel right?
  if isNew then
    i = D.num_channels + 1
    t_info.index = i
    t_info.x = t_info.x + 20
    t_info.y = t_info.y - 20
    db.channels[i] = t_info
    --@debug@
    print('setchan(6)', 'new index', i)--@end-debug@
  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
    --@debug@
    print('setchan(7)', 'new console', i)--@end-debug@
  end
  local channel = self.console[i]
  self.sig[t_info.signature] = channel
  self.sigID[t_info.signature] = i
  self.IDsig[i] = t_info.signature

  --@debug@
  print('setchan(8)', 'end', self.sig[t_info.signature], self.sigID[t_info.signature], self.IDsig[i], self.docked)--@end-debug@
  return channel
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/resetdvn|r", "- Resets all but profile data SavedVariables.")
  D:Print("|cFFFFFF00/cleandvn|r", "- Fully resets SavedVariables, profiles and all.")
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.workspace == 1 then
    D:Print('Gameplay mode active. Print handling turned |cFFFFFF00OFF|r..')
  else
    D:Print('Development mode active (list #'..db.workspace..'). Print handling |cFF00FF00ON|r.')
  end

end

function D:OnInitialize()
  -- emergency button
  self:RegisterChatCommand("cleandvn", function(args)
    DevianDB = nil
    ReloadUI()
    end)
  self:RegisterChatCommand("resetdvn", function(args)
    for k,v in pairs(DevianDB) do
      if k ~= 'global' then
        DevianDB[k] = nil
      end
    end

    for k,v in pairs(defaults) do
      DevianDB[k] = v
    end
    ReloadUI()
  end)

  -- savedvars
  local cherry = false
  if not _G.DevianDB then
    _G.DevianDB = defaults
  end
  db = _G.DevianDB
  self.tags = db.tags
  self.channelinfo = db.channels

  if not db[PLAYER_REALM] then
    db[PLAYER_REALM] = {[WORKSPACE_ON] = {}, [WORKSPACE_OFF] = {}}
  end

  if db.load_message then
    D:Print(db.load_message)
    db.load_message = nil
  end
  D.oldprint = getprinthandler()
  if not _G.oldprint then
    _G.oldprint = D.oldprint
  end

  --self.raise_ct = 0
  self.max_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)
    self.max_channel = math.max(i, self.max_channel)
    self.num_channels = self.num_channels + 1
  end

  for i, channel in pairs(db.channels) do
    if type(channel.docked) == 'table' then
      oldprint('docking',i, unpack(channel.docked))
      self.DockFrame(i, unpack(channel.docked))
    end
  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.workspace > 1 then

    setprinthandler(Message)
    print = function(...)
      _G.print('Dvn', ...)
    end
  end
  print(MAJOR, MINOR)
end