view Devian.lua @ 33:e6650821a2c0

Debugging pass on console code.
author Nenue
date Sun, 27 Dec 2015 02:30:46 -0500
parents c6a2c2df4790
children 7441f3bce940
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 DEVIAN_DOCK_FRAME = 'DevianDockFrame'
local MSG_NEED_DEV_MODE = 'Must be in development mode to use this function.'
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, -- the channel to which default output is sent
  current_channel = 1, -- the front channel
  max_channel = 1, -- the highest created channel id
  enable = true, -- allow enabled consoles to appear
  load_message = "Defaults loaded.", -- messages to be displayed after reload
  font = [[Interface\Addons\Devian\font\SourceCodePro-Regular.ttf]], -- font info
  fontsize = 13,
  fontoutline = 'NONE',
  headergrad = {'VERTICAL', 0, 0, 0, 0.5, 0.1, 0.1, 0.1, 0.3}, -- header info
  headerdrop = {1,1,1,1},
  headeralpha = 1,
  backdrop = {0,0,0,1},                                        -- background frame info
  backgrad = {'VERTICAL', 0.1, 0.1, 0.1, 0.3, 0, 0, 0, 0.5},
  backblend = 'MOD',
  backalpha = 0.7,
  backborder = {0,0,1,0.75},
  frontdrop = {0,0,0,1},                                       -- foreground frame info
  frontgrad = {'VERTICAL', 0.1, 0.1, 0.1, 0.9, 0, 0, 0, 0.9},
  frontblend = 'MOD',
  frontalpha = 1,
  frontborder = {1,0,0,1},
  tagcolor = {},   -- tag color repository
  workspace = 1,   -- current profile
  last_workspace = 2 -- default workspace to alternate with when just "/dvn" is issued
}


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 db.workspace == 1 then
      D:Print('Must be in dev mode to use this.')
      return
    end
    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
    if db.workspace == 1 then
      return D:Print(MSG_NEED_DEV_MODE)
    end
    return D:StackFrames()
  elseif mode == 'grid' then
    if db.workspace == 1 then
      return D:Print(MSG_NEED_DEV_MODE)
    end
    return D:DistributeFrames()
  elseif mode == 'tag' then -- tagging
    if db.workspace == 1 then
      return D:Print(MSG_NEED_DEV_MODE)
    end

    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
      if list_state ~= 1 then
        db.last_workspace = list_state
      end

    end
  elseif mode == nil then
    list_state = (db.workspace == 1) and db.last_workspace or 1
    db.workspace = list_state
    db.load_message = "quick swapped workspace #"..list_state
  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
      elseif mode ~= 'save' then
        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)
  if db.workspace == 1 then
    return D:Print(MSG_NEED_DEV_MODE)
  end
  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.enabled = (not db.enabled) and true or nil
  for i, c in ipairs(search) do
    --print(i,c.index)
    if db.enabled then
      c.enabled = true
      c:Show()
      if db.current_channel == c.index then
        c:ToFront()
      end
      c:Save()
    else
      c:Hide()
    end
  end

  if db.enabled 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 not db.enabled 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 tonumber(i) == nil or math.floor(i) ~= i then
    error('Non-integer index value.')
  end
  if not vars then
    vars = db.channels[i] and db.channels[i] or db.channels[db.primary_channel]
  end
  local f
  if vars.docked then
    f = CreateFrame('Frame','DevianDockFrame' .. i, DEVIAN_DOCK_FRAME)
  else
    f= CreateFrame('Frame', 'DevianChannelFrame' .. i, UIParent, DEVIAN_FRAME)
  end
  --@debug@
  --print(f:GetName())

  --print('create(2)')
  for k,v in pairs(vars) do
    f[k] = v
    --@debug@
    --print(' f['..type(k)..' '..tostring(k)..'] = '..type(v)..' '..tostring(v))
  end

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

  if vars.minimized then
    f:Minimize()
  else
    f:Maximize()
  end
  if db.enabled and f.enabled then
      f:Show()
  end

  return f
end

--- Updates console information and returns the handle of the channel object that was worked on.
-- When key is nil or not a valid handle, a new channel is created using whatever signature can be found in cinfo.
-- The signature can be passed as a string, or as a table entry under the key 'signature'
-- If the signature of a new channel is also a tag, the channel will be added to that tag
-- @param cinfo string signature of a new channel, or a table of config variables to be imposed on the channel
-- @param key string signature or index number of channel to operate on
-- @usage channel = D:SetChannel('new', nil) -- creates a new channel
-- @usage channel = D:SetChannel({x = 200, y = 100}, 4) -- updates channel #4
function D:SetChannel(cinfo, key)
  local t_info = {}
  local channel, isNew, id, sig, t_id
  -- obtain source data
  if tonumber(key) ~= nil and  db.channels[key] then
    id = tonumber(key)
  elseif D.sigID[tostring(key)] then
    id = D.sigID[tostring(key)]
  else
    id = db.primary_channel
    isNew = true
  end
  local dbvars = db.channels[id]
  t_id = id           -- overridden later if new
  t_info.index = t_id --
  --@debug@
  --print('setchan(1) cinfo, key, id=', cinfo, key, id)--@end-debug@


  -- obtain config info
  if type(cinfo) == 'string' then
    sig = cinfo
    cinfo = {signature = sig}
  elseif type(cinfo) ~= 'table' then -- stop here if a table wans't passed
    error('Expecting table of string as arg1')
  elseif cinfo.signature then -- new sig
    sig = cinfo.signature
  elseif isNew then -- new channel sig
    sig = 'Ch'
  else -- old sig
    sig = db.channels[id].signature
  end
  t_info.signature = sig
  --@debug@
  --print('setchan(2) sig,id,isNew=', sig, id, isNew)--@end-debug@

  for k,v in pairs(cinfo) do -- allow all cinfo to pass
    t_info[k] = v
  end

  local blocked = {          -- ignore these vars:
    ['docked'] = true,       -- table
    ['dockedTo'] = true,     -- table-related
    ['signature'] = true}    -- already determined
  for k,v in pairs(dbvars) do
    if not t_info[k] and not blocked[k] then -- already set or blocked?
      t_info[k] = v
    end
  end
  -- new channel overrides
  if isNew then
    if D.sigID[sig]then -- find a non-clashing signature
    local result, i = sig, 1
    while D.sigID[result] do
      result = sig .. i
      i = i + 1
    end
    t_info.signature = result
    end
    t_id = db.max_channel + 1
    t_info.index = t_id
    --@debug@
    --print('setchan(3a) isNew, sig, t_info.signature=', isNew, sig, t_info.signature)--@end-debug@
  else
    --@debug@
    --print('setchan(3b) isNew, sig, t_info.signature=', isNew, sig, t_info.signature)--@end-debug@
  end

  local channel
  if not self.console[t_id] then -- create a frame
    if isNew then -- position the channel frame
      t_info.x = t_info.x + 20
      t_info.y = t_info.y - 20
      db.channels[t_id] = t_info
      --@debug@
      print('setchan(4a)', 't_id, x, y=', t_id, t_info.x, t_info.y)--@end-debug@
    end
    channel = CreateConsole(t_id, t_info)
    self.console[t_id] = channel
    self.sig[t_info.signature] = channel
    self.sigID[t_info.signature] = t_id
    self.IDsig[t_id] = t_info.signature

  end
  channel = self.console[t_id]
  if channel.minimized then
    channel:Minimize()
  else
    channel:Maximize()
  end

  if channel.enabled and db.enabled then -- hide or show last since Min/Max mess with visibility
    print('setchan(5a) enable')
    channel:Show()
  else
    print('setchan(5a) disable')
    channel:Hide()
  end
  --@debug@
  --print('setchan(end); c:IsVisible(), c.enabled, db.enabled=', channel:IsVisible(), channel.enabled, db.enabled)--@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()
  print(MAJOR, MINOR)

  if db.unlisted and #db.unlisted > 0 then
    D:Print('New AddOns have been found since the last profile update: '.. table.concat(db.unlisted, ', '))
    table.wipe(db.unlisted)
  end

  if db.workspace == 1 then
    D:Print('Gameplay mode active (list #'..db.workspace..'). Print handling turned |cFFFFFF00OFF|r.')
  else
    D:Print('Development mode active (list #'..db.workspace..'). Print handling |cFF00FF00ON|r.')
  end

end

function D:OnInitialize()
  -- commands
  self:RegisterChatCommand("cleandvn", function(args)
    DevianDB = nil
    DevianDB = {
      load_message = "All SavedVars wiped."
    }
    ReloadUI()
    end)
  self:RegisterChatCommand("resetdvn", function(args)
    for k,v in pairs(DevianDB) do
      if k ~= 'global' then
        DevianDB[k] = nil
      end
    end

    DevianDB.load_message = "Non-user SavedVars have been reset."
    for k,v in pairs(defaults) do
      DevianDB[k] = v
    end
    ReloadUI()
  end)
  local cmdlist = {
    ['dvn'] = ScanAddOnList,
    ['devian'] = ScanAddOnList,
    ['dvc'] = Console_Toggle,
  }
  for cmd, func in pairs(cmdlist) do
    self:RegisterChatCommand(cmd, func, true)
  end

  -- pull defaults
  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

  -- Stop here in game mode
  if db.workspace == 1 then
    return
  end

  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
  setprinthandler(Message)
  print = function(...)
    _G.print('Dvn', ...)
  end
end