view Comments.lua @ 275:ac6163ed8330

Resolve unit details while building the comment subject menu so units which are no longer valid afterward can still be commented on while the menu is up.
author James D. Callahan III <jcallahan@curse.com>
date Tue, 26 Mar 2013 22:57:18 -0500
parents e9670d6c6bf1
children a43a9444659e
line wrap: on
line source
-- LUA API ------------------------------------------------------------

local _G = getfenv(0)

local table = _G.table

local next = _G.next
local pairs = _G.pairs

-- ADDON NAMESPACE ----------------------------------------------------

local ADDON_NAME, private = ...

local LibStub = _G.LibStub
local WDP = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
local Dialog = LibStub("LibDialog-1.0")
local LQT = LibStub("LibQTip-1.0")

local ParseGUID = private.ParseGUID
local ItemLinkToID = private.ItemLinkToID
local DBEntry = private.DBEntry

-- CONSTANTS ----------------------------------------------------------

local EDIT_MAXCHARS = 3000
local EDIT_DESCRIPTION_FORMAT = "Enter your comment below, being as descriptive as possible. Comments are limited to %s characters, including newlines and spaces."
local LINK_COMMENT_TOOLTIP = "Click here to create a link to the comment page on WoWDB."
local LINK_EDITBOX_DESC_FORMAT = "Copy the highlighted text and paste it into your browser to visit the comments for |cffffd200%s|r."

local URL_BASE = "http://www.wowdb.com/"

local URL_TYPE_MAP = {
    ACHIEVEMENT = "achievements",
    ITEM = "items",
    OBJECT = "objects",
    NPC = "npcs",
    QUEST = "quests",
    SPELL = "spells",
    VEHICLE = "npcs",
}

Dialog:Register("WDP_CommentLink", {
    text = "",
    editboxes = {
        {
            text = _G.UNKNOWN,
            on_escape_pressed = function(self)
                self:ClearFocus()
            end,
            on_enter_pressed = function(self)
                self:GetParent().buttons[1]:Click()
            end,
        },
    },
    buttons = {
        {
            text = _G.OKAY,
        }
    },
    show_while_dead = true,
    hide_on_escape = true,
    is_exclusive = true,
    on_show = function(self, data)
        local editbox = self.editboxes[1]
        editbox:SetWidth(self:GetWidth() - 20)
        editbox:SetText(("%s%s/%d#related:comments"):format(URL_BASE, URL_TYPE_MAP[data.type_name], data.id))
        editbox:HighlightText()
        editbox:SetFocus()

        self.text:SetJustifyH("LEFT")
        self.text:SetFormattedText(LINK_EDITBOX_DESC_FORMAT:format(data.label))
    end,
})

-- VARIABLES ----------------------------------------------------------

local comment_subject = {}
local comment_frame
local comment_units = {}

-- HELPERS ------------------------------------------------------------

local function NewComment(type_name, label, id)
    comment_subject.id = id
    comment_subject.label = label
    comment_subject.type_name = type_name

    comment_frame.subject_name:SetText(label)
    comment_frame.subject_data:SetFormattedText("(%s #%d)", type_name, id)
    comment_frame.scroll_frame.edit_box:SetText("")
    _G.ShowUIPanel(comment_frame)
end

local function CreateUnitComment(unit_id, is_command)
    local unit_type, unit_idnum

    if is_command then
        if not _G.UnitExists(unit_id) then
            WDP:Printf("Unit '%s' does not exist.", unit_id)
            return
        end
        unit_type, unit_idnum = ParseGUID(_G.UnitGUID(unit_id))
    else
        local unit_data = comment_units[unit_id]

        if not unit_data then
            WDP:Printf("Unit '%s' does not exist.", unit_id)
            return
        end
        unit_type = unit_data.type
        unit_idnum = unit_data.idnum
    end

    if not unit_idnum then
        WDP:Printf("Unable to determine unit from '%s'", unit_id)
        return
    end
    local type_name = private.UNIT_TYPE_NAMES[unit_type + 1]
    local unit_name = is_command and _G.UnitName(unit_id) or comment_units[unit_id].name

    table.wipe(comment_units)
    NewComment(type_name, unit_name, unit_idnum)
end

local DATA_TYPE_MAPPING = {
    merchant = "ITEM",
}

local CreateCursorComment
do
    local CURSOR_DATA_FUNCS = {
        item = function(type_name, id_num, data_subtype)
            local item_name = _G.GetItemInfo(id_num)
            NewComment(type_name, item_name, id_num)
        end,
        merchant = function(type_name, item_index)
            local item_link = _G.GetMerchantItemLink(item_index)
            local item_name = _G.GetItemInfo(item_link)
            NewComment(type_name, item_name, ItemLinkToID(item_link))
        end,
        spell = function(type_name, data, data_subtype, spell_id)
            local spell_name = _G.GetSpellInfo(spell_id)
            NewComment(type_name, spell_name, spell_id)
        end,
    }

    function CreateCursorComment()
        local data_type, data, data_subtype, subdata = _G.GetCursorInfo()
        local comment_func = CURSOR_DATA_FUNCS[data_type]

        if not comment_func then
            WDP:Print("Unable to determine comment subject from cursor.")
            return
        end
        comment_func(DATA_TYPE_MAPPING[data_type] or data_type:upper(), data, data_subtype, subdata)
    end
end -- do-block

local function CreateQuestComment()
    local index = _G.GetQuestLogSelection()

    if not index or not _G.QuestLogFrame:IsShown() then
        WDP:Print("You must select a quest from the Quest frame.")
        return
    end
    local title, _, tag, _, is_header, _, _, _, idnum = _G.GetQuestLogTitle(index)

    if is_header then
        WDP:Print("You must select a quest from the Quest frame.")
        return
    end
    NewComment("QUEST", title, idnum)
end

local function CreateAchievementComment()
    if not _G.AchievementFrame or not _G.AchievementFrame:IsShown() or not _G.AchievementFrameAchievements.selection then
        WDP:Print("You must select an achievement from the Achievement frame.")
        return
    end

    for _, button in next, _G.AchievementFrameAchievementsContainer.buttons do
        if button.selected then
            NewComment("ACHIEVEMENT", button.label:GetText(), button.id)
            break
        end
    end
end

local ShowPossibleSubjects
do
    local display
    local old_x, old_y, click_time

    _G.WorldFrame:HookScript("OnMouseDown", function(frame, ...)
        old_x, old_y = _G.GetCursorPosition()
        click_time = _G.GetTime()
    end)

    _G.WorldFrame:HookScript("OnMouseUp", function(frame, ...)
        if not display then
            return
        end
        local x, y = _G.GetCursorPosition()

        if not old_x or not old_y or not x or not y or not click_time then
            display = display:Release()
            return
        end

        if (_G.math.abs(x - old_x) + _G.math.abs(y - old_y)) <= 5 and _G.GetTime() - click_time < 1 then
            display = display:Release()
        end
    end)

    local function CreateComment(cell, func)
        if _G.type(func) == "table" then
            func[1](func[2])
        else
            func()
        end
        display = display:Release()
    end

    local CURSOR_NAME_FUNCS = {
        item = function(id_num)
            return _G.GetItemInfo(id_num)
        end,
        merchant = function(item_index)
            return _G.GetItemInfo(_G.GetMerchantItemLink(item_index))
        end,
        spell = function(data, data_subtype, spell_id)
            return _G.GetSpellInfo(spell_id)
        end,
    }

    local VALID_UNITS = {
        boss1 = true,
        boss2 = true,
        boss3 = true,
        boss4 = true,
        focus = true,
        mouseover = true,
        npc = true,
        target = true,
    }

    function ShowPossibleSubjects(anchor)
        if not display then
            display = LQT:Acquire(ADDON_NAME, 1, "LEFT")
            display:EnableMouse(true)
        end

        if anchor then
            display:SmartAnchorTo(anchor)
            display:SetAutoHideDelay(0.2, anchor)
        else
            display:SetPoint("CENTER", _G.UIParent, "CENTER", 0, 0)
        end
        display:Clear()
        display:AddHeader("Choose comment subject:", "CENTER")
        display:AddSeparator()
        display:AddSeparator()

        local line

        for unit_id in pairs(VALID_UNITS) do
            if _G.UnitExists(unit_id) then
                local unit_type, unit_idnum = ParseGUID(_G.UnitGUID(unit_id))
                local unit_name = _G.UnitName(unit_id)

                if unit_idnum then
                    comment_units[unit_id] = {
                        idnum = unit_idnum,
                        name = unit_name,
                        type = unit_type,
                    }
                    line = display:AddLine(("%s: %s"):format(unit_id:gsub("^%l", _G.string.upper), unit_name))
                    display:SetLineScript(line, "OnMouseUp", CreateComment, { CreateUnitComment, unit_id })
                end
            end
        end

        if _G.AchievementFrame and _G.AchievementFrame:IsShown() and _G.AchievementFrameAchievements.selection then
            for _, button in next, _G.AchievementFrameAchievementsContainer.buttons do
                if button.selected then
                    line = display:AddLine(("Achievement: %s"):format(button.label:GetText()))
                    display:SetLineScript(line, "OnMouseUp", CreateComment, CreateAchievementComment)
                    break
                end
            end
        end
        local data_type, data, data_subtype, subdata = _G.GetCursorInfo()
        local name_func = CURSOR_NAME_FUNCS[data_type]

        if name_func then
            line = display:AddLine(("Cursor: %s"):format(name_func(data, data_subtype, subdata)))
            display:SetLineScript(line, "OnMouseUp", CreateComment, CreateCursorComment)
        end

        local quest_index = _G.GetQuestLogSelection()

        if quest_index and _G.QuestLogFrame:IsShown() then
            local title, _, tag, _, is_header, _, _, _, idnum = _G.GetQuestLogTitle(quest_index)

            if not is_header then
                line = display:AddLine(("Quest: %s"):format(title))
                display:SetLineScript(line, "OnMouseUp", CreateComment, CreateQuestComment)
            end
        end

        if display:GetLineCount() == 3 then
            display = display:Release()
            WDP:Print("There are no possible comment subjects.")
            return
        end
        display:Show()
    end

    WDP.ShowPossibleSubjects = ShowPossibleSubjects -- For Keybinding.
end -- do-block

-- METHODS ------------------------------------------------------------

function private.ProcessCommentCommand(arg)
    if not arg or arg == "" then
        ShowPossibleSubjects(nil)
        return
    end

    if arg == "achievement" then
        CreateAchievementComment()
        return
    elseif arg == "cursor" then
        CreateCursorComment()
        return
    elseif arg == "quest" then
        CreateQuestComment()
        return
    end
    CreateUnitComment(arg, true)
end

function private.InitializeCommentSystem()
    local panel = _G.CreateFrame("Frame", "WDP_CommentFrame", _G.UIParent, "TranslucentFrameTemplate")
    panel:SetSize(480, 350)
    panel:SetPoint("CENTER", _G.UIParent, "CENTER")
    panel:SetFrameStrata("DIALOG")
    panel.Bg:SetTexture([[Interface\FrameGeneral\UI-Background-Rock]], true, true)
    panel.Bg:SetHorizTile(true)
    panel.Bg:SetVertTile(true)
    panel:EnableMouse(true)
    panel:SetMovable(true)
    panel:Hide()
    comment_frame = panel

    table.insert(_G.UISpecialFrames, panel:GetName())

    local streaks = panel:CreateTexture("$parentTopTileStreaks", "BORDER", "_UI-Frame-TopTileStreaks", -6)
    streaks:SetPoint("TOPLEFT", 13, -13)
    streaks:SetPoint("BOTTOMRIGHT", panel, "TOPRIGHT", -13, -35)

    local header = _G.CreateFrame("Frame", "$parentHeader", panel, "TranslucentFrameTemplate")
    header:SetSize(128, 64)
    header:SetPoint("CENTER", panel, "TOP", 0, -8)
    header.Bg:SetTexture([[Interface\FrameGeneral\UI-Background-Marble]])
    header.Bg:SetHorizTile(true)
    header.Bg:SetVertTile(true)
    header:SetMovable(true)

    header:SetScript("OnMouseDown", function()
        panel:StartMoving()
    end)

    header:SetScript("OnMouseUp", function()
        panel:StopMovingOrSizing()
    end)

    panel.header = header

    local logo = header:CreateTexture(nil, "ARTWORK")
    logo:SetTexture([[Interface\AddOns\WoWDBProfiler\wowdb-logo]])
    logo:SetPoint("TOPLEFT", header, 10, -10)
    logo:SetPoint("BOTTOMRIGHT", header, -10, 10)

    local subject_name = panel:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    subject_name:SetPoint("TOP", header, "BOTTOM", 0, -10)
    panel.subject_name = subject_name

    local subject_data = panel:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    subject_data:SetPoint("TOP", subject_name, "BOTTOM", 0, -3)
    panel.subject_data = subject_data

    local close = _G.CreateFrame("Button", nil, panel, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -7, -7)

    local scroll_frame = _G.CreateFrame("ScrollFrame", "$parentScrollFrame", panel)
    scroll_frame:SetSize(435, 150)
    scroll_frame:SetPoint("BOTTOM", 0, 70)

    scroll_frame:SetScript("OnScrollRangeChanged", function(self, x, y)
        _G.ScrollFrame_OnScrollRangeChanged(self, x, y)
    end)

    scroll_frame:SetScript("OnVerticalScroll", function(self, offset)
        local scrollbar = self.ScrollBar
        scrollbar:SetValue(offset)

        local min, max = scrollbar:GetMinMaxValues()

        if offset == 0 then
            scrollbar.ScrollUpButton:Disable()
        else
            scrollbar.ScrollUpButton:Enable()
        end

        if (scrollbar:GetValue() - max) == 0 then
            scrollbar.ScrollDownButton:Disable()
        else
            scrollbar.ScrollDownButton:Enable()
        end
    end)

    scroll_frame:SetScript("OnMouseWheel", function(self, delta)
        _G.ScrollFrameTemplate_OnMouseWheel(self, delta)
    end)

    -- This is needed because the EditBox starts with a height of a single line, so getting focus on click would require finding the EditBox blindly.
    scroll_frame:SetScript("OnMouseUp", function(self)
        _G.EditBox_SetFocus(self.edit_box)
    end)

    panel.scroll_frame = scroll_frame

    local edit_container = _G.CreateFrame("Frame", nil, scroll_frame)
    edit_container:SetPoint("TOPLEFT", scroll_frame, -7, 7)
    edit_container:SetPoint("BOTTOMRIGHT", scroll_frame, 7, -7)
    edit_container:SetFrameLevel(scroll_frame:GetFrameLevel() - 1)
    edit_container:SetBackdrop({
        bgFile = [[Interface\Tooltips\UI-Tooltip-Background]],
        edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]],
        tile = true,
        tileSize = 16,
        edgeSize = 16,
        insets = {
            left = 5,
            right = 5,
            top = 5,
            bottom = 5
        }
    })

    edit_container:SetBackdropBorderColor(_G.TOOLTIP_DEFAULT_COLOR.r, _G.TOOLTIP_DEFAULT_COLOR.g, _G.TOOLTIP_DEFAULT_COLOR.b)
    edit_container:SetBackdropColor(0, 0, 0)

    local link_button = _G.CreateFrame("Button", "$parentLinkButton", panel)
    link_button:SetSize(32, 16)
    link_button:SetPoint("TOPRIGHT", edit_container, "BOTTOMRIGHT", 5, 0)

    link_button:SetNormalTexture([[Interface\TradeSkillFrame\UI-TradeSkill-LinkButton]])
    link_button:GetNormalTexture():SetTexCoord(0, 1, 0, 0.5)

    link_button:SetHighlightTexture([[Interface\TradeSkillFrame\UI-TradeSkill-LinkButton]])
    link_button:GetHighlightTexture():SetTexCoord(0, 1, 0.5, 1)

    link_button:SetScript("OnClick", function(self)
        Dialog:Spawn("WDP_CommentLink", { type_name = comment_subject.type_name, id = comment_subject.id, label = comment_subject.label })
    end)

    link_button:SetScript("OnEnter", function(self)
        _G.GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT")
        _G.GameTooltip:SetText(LINK_COMMENT_TOOLTIP, nil, nil, nil, nil, 1)
        _G.GameTooltip:Show()
    end)

    link_button:SetScript("OnLeave", _G.GameTooltip_Hide)

    local edit_description = edit_container:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    edit_description:SetHeight(36)
    edit_description:SetPoint("BOTTOMLEFT", edit_container, "TOPLEFT", 5, 3)
    edit_description:SetPoint("BOTTOMRIGHT", edit_container, "TOPRIGHT", 5, 3)
    edit_description:SetFormattedText(EDIT_DESCRIPTION_FORMAT, _G.BreakUpLargeNumbers(EDIT_MAXCHARS))
    edit_description:SetWordWrap(true)
    edit_description:SetJustifyH("LEFT")

    local edit_box = _G.CreateFrame("EditBox", "$parentEditBox", scroll_frame)
    edit_box:SetMultiLine(true)
    edit_box:SetMaxLetters(EDIT_MAXCHARS)
    edit_box:EnableMouse(true)
    edit_box:SetAutoFocus(false)
    edit_box:SetFontObject("ChatFontNormal")
    edit_box:SetSize(420, 220)
    edit_box:HighlightText(0)
    edit_box:SetFrameLevel(scroll_frame:GetFrameLevel() + 1)

    edit_box:SetScript("OnCursorChanged", _G.ScrollingEdit_OnCursorChanged)
    edit_box:SetScript("OnEscapePressed", _G.EditBox_ClearFocus)
    edit_box:SetScript("OnShow", function(self)
        _G.EditBox_SetFocus(self)

        if self:GetNumLetters() > 0 then
            panel.submitButton:Enable()
        else
            panel.submitButton:Disable()
        end
    end)

    edit_box:SetScript("OnTextChanged", function(self, user_input)
        local parent = self:GetParent()
        local num_letters = self:GetNumLetters()
        _G.ScrollingEdit_OnTextChanged(self, parent)
        parent.charCount:SetFormattedText(_G.BreakUpLargeNumbers(self:GetMaxLetters() - num_letters))

        if num_letters > 0 then
            panel.submitButton:Enable();
        else
            panel.submitButton:Disable()
        end
    end)

    edit_box:SetScript("OnUpdate", function(self, elapsed)
        _G.ScrollingEdit_OnUpdate(self, elapsed, self:GetParent())
    end)

    scroll_frame.edit_box = edit_box
    scroll_frame:SetScrollChild(edit_box)

    local char_count = scroll_frame:CreateFontString(nil, "OVERLAY", "GameFontDisableLarge")
    char_count:SetPoint("BOTTOMRIGHT", -15, 0)
    scroll_frame.charCount = char_count

    local scroll_bar = _G.CreateFrame("Slider", "$parentScrollBar", scroll_frame, "UIPanelScrollBarTemplate")
    scroll_bar:SetPoint("TOPLEFT", scroll_frame, "TOPRIGHT", -13, -16)
    scroll_bar:SetPoint("BOTTOMLEFT", scroll_frame, "BOTTOMRIGHT", -13, 16)
    scroll_frame.ScrollBar = scroll_bar

    _G.ScrollFrame_OnLoad(scroll_frame)

    local submit = _G.CreateFrame("Button", "$parentSubmit", panel, "GameMenuButtonTemplate")
    submit:SetSize(160, 30)
    submit:SetPoint("BOTTOM", 0, 15)
    submit:SetText(_G.SUBMIT)
    submit:Enable(false)

    submit:SetScript("OnClick", function()
        local entry = DBEntry(URL_TYPE_MAP[comment_subject.type_name], comment_subject.id)

        if not entry then
            WDP:Print("An error has occurred; please report at http://wow.curseforge.com/addons/wowdb-profiler/create-ticket/")
            return
        end
        entry.comments = entry.comments or {}
        entry.comments[#entry.comments + 1] = edit_box:GetText()

        edit_box:SetText("")
        _G.HideUIPanel(panel)
    end)
    panel.submitButton = submit

    --    local data_obj = LibStub("LibDataBroker-1.1"):NewDataObject(ADDON_NAME, {
    --        type = "data source",
    --        label = ADDON_NAME,
    --        text = " ",
    --        icon = [[Interface\CHATFRAME\UI-ChatIcon-Chat-Up]],
    --        OnClick = function(self, button, down)
    --            ShowPossibleSubjects(self)
    --        end,
    --        OnTooltipShow = function(self)
    --            self:AddLine(_G.CLICK_TO_ENTER_COMMENT)
    --        end,
    --    })
    --
    --    private.data_obj = data_obj
    --    LibStub("LibDBIcon-1.0"):Register(ADDON_NAME, data_obj, private.db.global.config.minimap_icon)

    _G["BINDING_HEADER_WOWDB_PROFILER"] = "WoWDB Profiler"
end