vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
--[[ ItemRack 1.975 7/27/06 Gello ]]--

--[[ SavedVariables ]]--

ItemRack_Users = {} -- per-user bar settings (position, orientation, scale, locked status, bar contents)

ItemRack_Settings = {                   -- These settings are for all users:
        TooltipFollow = "OFF",          -- whether tooltip shows at pointer or default position
        CooldownNumbers = "OFF",        -- whether cooldowns show a number overlay
        Soulbound = "OFF",                      -- whether menu limits items to soulbound only
        Bindings = "OFF",                       -- whether key bindings is displayed
        MenuShift = "OFF",                      -- whether Shift key needs held to open the menu
        Notify = "OFF",                         -- whether to notify when cooldowns finished on used items
        ShowEmpty = "ON",                       -- whether to show empty slot in the menu
        FlipMenu = "OFF",                       -- whether to display menu on the opposite side
        RightClick = "OFF",                     -- whether right click sends to second slot
        TinyTooltip = "OFF",            -- whether to only display name, durability and cooldown in tooltip
        ShowTooltips = "ON",            -- whether to display tooltip at all
        RotateMenu = "OFF",                     -- whether menu is rotated (temporary setting)
        ShowIcon = "ON",                        -- whether to show the minimap button
        DisableToggle = "ON",           -- whether left-clicking disables the minimap button
        FlipBar = "OFF",                        -- whether control appears on bottom or right bar grows other direction
        EnableEvents = "OFF",           -- whether automated event scrips run
        CompactList = "OFF",            -- whether saved sets list is compacted or not
        NotifyThirty = "ON",            -- whether notify happens at 30 seconds
        ShowAllEvents = "OFF",          -- whether to show all classes' events
        AllowHidden = "OFF",            -- whether to hide menu items with ALT+click
        LargeFont = "OFF",                      -- whether event script font is large or small
        SquareMinimap = "OFF",          -- whether minimap button should go around square minimap
        BigCooldown = "OFF",            -- whether cooldown numbers are huge (like Cooldown Count)
        SetLabels = "ON",                       -- whether labels show on set icons
        AutoToggle = "OFF",                     -- whether sets automatically toggle when chosen
}

-- all event scripts are stored globally in this saved variable.  Defaults are in localization.lua
ItemRack_Events = {}

ItemRack_Version = 1.975

--[[ Local Variables ]]--

-- some mount textures share non-mount buff textures, if you run across one put it here
local problem_mounts = {
        ["Interface\\Icons\\Ability_Mount_PinkTiger"] = 1,
        ["Interface\\Icons\\Ability_Mount_WhiteTiger"] = 1,
        ["Interface\\Icons\\Spell_Nature_Swiftness"] = 1,
        ["Interface\\Icons\\INV_Misc_Foot_Kodo"] = 1,
        ["Interface\\Icons\\Ability_Mount_JungleTiger"] =1,
}

local current_events_version = 1.975 -- use to control when to upgrade events

-- defaults for ItemRack_Users
local ItemRackOpt_Defaults = {
        MainOrient = "HORIZONTAL",  -- direction of main bar, "HORIZONTAL" or "VERTICAL", menu orient always opposite
        MainScale = 1,                          -- scale 0-1 of main bar and menu bar
        XPos = 400,                                     -- left position of main bar
        YPos = 350,                                     -- top position of main bar
        Locked = "OFF",                         -- lock status of main bar (for all intents menu always locked)
        Visible = "ON"                          -- whether the bar should be drawn on the screen
}

local user = "default"  -- "Gello of Hyjal" or "Gello of Deathwing ", defined at PLAYER_LOGIN

ItemRack = {}

ItemRack.FrameToScale = nil -- holds frame being scaled for ScaleUpdate

ItemRack.BaggedItems = {} -- table containing items in bags to show in menu
ItemRack.NumberOfItems = 0 -- number of items in the menu
ItemRack.MaxItems = 30 -- maximum number of items that can display in the menu
ItemRack.InvOpen = nil -- which inventory slot has its menu open

ItemRack.TooltipOwner = nil -- (this) when tooltip created
ItemRack.TooltipType = nil -- "BAG" or "INVENTORY"
ItemRack.TooltipBag = nil -- bag number
ItemRack.TooltipSlot = nil -- bag or inventory slot number

ItemRack.CurrentTime = GetTime()

ItemRack.MainDock = "" -- "TOPLEFT" "BOTTOMRIGHT" etc
ItemRack.MenuDock = ""

ItemRack.Queue = {} -- [slot]="ItemName" if an item in queue, ie [13]="Arcanite Dragonling", [slot]=nil for no item in queue
ItemRack.NotifyList = {} -- ["item name"] = { bag=0-4, slot=1-x, inv=0-19, hadcooldown=true/nil }
ItemRack.AmmoCounts = {} -- ["Heavy Shot"]=200, ["Thorium Arrow"]=982, etc
ItemRack.TrinketsPaired = false -- true if the two trinkets are next to each other

ItemRack.KeyBindingsSettled = false

ItemRack.MenuDockedTo = nil -- "SET" for set window, "CHARACTERSHEET" for PaperDollFrame, nil for rack

ItemRack.SelectedEvent = 0 -- index in list of event selected

ItemRack.CanWearOneHandOffHand = nil -- whether player can wear one-hand in offhand (warrior, rogue, hunter)

ItemRack.Buffs = {} -- table indexed by buff names, whether a buff is on or not
ItemRack.BankedItems = {} -- items in the bank indexed by itemID
ItemRack.BankSlots = { -1,5,6,7,8,9,10 }

local eventList = {} -- note the departure from using the table for local values -- mod will be rewritten to a consistent style
local eventListSize = 1

local scratchTable = { {}, {} } -- for secondary sorts
local scratchTableSize = { 1, 1 }

--[[ reference tables ]]--

ItemRack.OptInfo = {
        ["ItemRack_Control_Rotate"] = { text=ItemRackText.CONTROL_ROTATE_TEXT, tooltip=ItemRackText.CONTROL_ROTATE_TOOLTIP },
        ["ItemRack_Control_Lock"] = { text=ItemRackText.CONTROL_LOCK_TEXT, tooltip=ItemRackText.CONTROL_LOCK_TOOLTIP },
        ["ItemRack_Control_Options"] = { text=ItemRackText.CONTROL_OPTIONS_TEXT, tooltip=ItemRackText.CONTROL_OPTIONS_TOOLTIP },
        ["ItemRack_Opt_TooltipFollow"] = { text=ItemRackText.OPT_TOOLTIPFOLLOW_TEXT, tooltip=ItemRackText.OPT_TOOLTIPFOLLOW_TOOLTIP, type="Check", info="TooltipFollow" },
        ["ItemRack_Opt_CooldownNumbers"] = { text=ItemRackText.OPT_COOLDOWNNUMBERS_TEXT, tooltip=ItemRackText.OPT_COOLDOWNNUMBERS_TOOLTIP, type="Check", info="CooldownNumbers" },
        ["ItemRack_Opt_Soulbound"] = { text=ItemRackText.OPT_SOULBOUND_TEXT, tooltip=ItemRackText.OPT_SOULBOUND_TOOLTIP, type="Check", info="Soulbound" },
        ["ItemRack_Opt_Bindings"] = { text=ItemRackText.OPT_BINDINGS_TEXT, tooltip=ItemRackText.OPT_BINDINGS_TOOLTIP, type="Check", info="Bindings" },
        ["ItemRack_Opt_MenuShift"] = { text=ItemRackText.OPT_MENUSHIFT_TEXT, tooltip=ItemRackText.OPT_MENUSHIFT_TOOLTIP, type="Check", info="MenuShift" },
        ["ItemRack_Opt_Close"] = { text=ItemRackText.OPT_CLOSE_TEXT, tooltip=ItemRackText.OPT_CLOSE_TOOLTIP },
        ["ItemRack_InvFrame_Resize"] = { text=ItemRackText.INVFRAME_RESIZE_TEXT, tooltip=ItemRackText.INVFRAME_RESIZE_TOOLTIP },
        ["ItemRack_Opt_ShowEmpty"] = { text=ItemRackText.OPT_SHOWEMPTY_TEXT, tooltip=ItemRackText.OPT_SHOWEMPTY_TOOLTIP, type="Check", info="ShowEmpty" },
        ["ItemRack_Opt_FlipMenu"] = { text=ItemRackText.OPT_FLIPMENU_TEXT, tooltip=ItemRackText.OPT_FLIPMENU_TOOLTIP, type="Check", info="FlipMenu" },
        ["ItemRack_Opt_RightClick"] = { text=ItemRackText.OPT_RIGHTCLICK_TEXT, tooltip=ItemRackText.OPT_RIGHTCLICK_TOOLTIP, type="Check", info="RightClick" },
        ["ItemRack_Opt_TinyTooltip"] = { text=ItemRackText.OPT_TINYTOOLTIP_TEXT, tooltip=ItemRackText.OPT_TINYTOOLTIP_TOOLTIP, type="Check", info="TinyTooltip" },
        ["ItemRack_Opt_ShowTooltips"] = { text=ItemRackText.OPT_SHOWTOOLTIPS_TEXT, tooltip=ItemRackText.OPT_SHOWTOOLTIPS_TOOLTIP, type="Check", info="ShowTooltips" },
        ["ItemRack_Opt_Notify"] = { text=ItemRackText.OPT_NOTIFY_TEXT, tooltip=ItemRackText.OPT_NOTIFY_TOOLTIP, type="Check", info="Notify" },
        ["ItemRack_Opt_RotateMenu"] = { text=ItemRackText.OPT_ROTATEMENU_TEXT, tooltip=ItemRackText.OPT_ROTATEMENU_TOOLTIP, type="Check", info="RotateMenu" },
        ["ItemRack_Sets_Close"] = { text=ItemRackText.SETS_CLOSE_TEXT, tooltip=ItemRackText.SETS_CLOSE_TOOLTIP },
        ["ItemRack_Sets_NameLabel"] = { text=ItemRackText.SETS_NAMELABEL_TEXT, type="Label" },
        ["ItemRack_Sets_HideSet"] = { text=ItemRackText.SETS_HIDESET_TEXT, tooltip=ItemRackText.SETS_HIDESET_TOOLTIP, type="Check" },
        ["ItemRack_Sets_Tab1"] = { text=ItemRackText.SETS_TAB1_TEXT, tooltip=ItemRackText.SETS_TAB1_TOOLTIP, type="Tab" },
        ["ItemRack_Sets_Tab2"] = { text=ItemRackText.SETS_TAB2_TEXT, tooltip=ItemRackText.SETS_TAB2_TOOLTIP, type="Tab" },
        ["ItemRack_Sets_Tab3"] = { text=ItemRackText.SETS_TAB3_TEXT, tooltip=ItemRackText.SETS_TAB3_TOOLTIP, type="Tab" },
        ["ItemRack_Opt_ShowIcon"] = { text=ItemRackText.OPT_SHOWICON_TEXT, tooltip=ItemRackText.OPT_SHOWICON_TOOLTIP, type="Check", info="ShowIcon" },
        ["ItemRack_Opt_DisableToggle"] = { text=ItemRackText.OPT_DISABLETOGGLE_TEXT, tooltip=ItemRackText.OPT_DISABLETOGGLE_TOOLTIP, type="Check", info="DisableToggle" },
        ["ItemRack_Sets_Lock"] = { text=ItemRackText.CONTROL_LOCK_TEXT, tooltip=ItemRackText.CONTROL_LOCK_TOOLTIP },
        ["ItemRack_Sets_BindButton"] = { text=ItemRackText.SETS_BINDBUTTON_TEXT, tooltip=ItemRackText.SETS_BINDBUTTON_TOOLTIP, type="Label" },
        ["ItemRack_Sets_SaveButton"] = { text=ItemRackText.SETS_SAVEBUTTON_TEXT, tooltip=ItemRackText.SETS_SAVEBUTTON_TOOLTIP, type="Label" },
        ["ItemRack_Sets_RemoveButton"] = { text=ItemRackText.SETS_REMOVEBUTTON_TEXT, tooltip=ItemRackText.SETS_REMOVEBUTTON_TOOLTIP, type="Label" },
        ["ItemRack_Opt_FlipBar"] = { text=ItemRackText.OPT_FLIPBAR_TEXT, tooltip=ItemRackText.OPT_FLIPBAR_TOOLTIP, type="Check", info="FlipBar" },
        ["ItemRack_Opt_EnableEvents"] = { text=ItemRackText.OPT_ENABLEEVENTS_TEXT, tooltip=ItemRackText.OPT_ENABLEEVENTS_TOOLTIP, type="Check", info="EnableEvents" },
        ["ItemRack_Opt_CompactList"] = { text=ItemRackText.OPT_COMPACTLIST_TEXT, tooltip=ItemRackText.OPT_COMPACTLIST_TOOLTIP, type="Check", info="CompactList" },
        ["ItemRack_SavedSets_Close"] = { text=ItemRackText.OPT_SAVEDSETSCLOSE_TEXT, tooltip=ItemRackText.OPT_SAVEDSETSCLOSE_TOOLTIP },
        ["ItemRack_Events_DeleteButton"] = { text=ItemRackText.EVENTSDELETE_TEXT, tooltip=ItemRackText.EVENTSDELETE_TOOLTIP },
        ["ItemRack_Events_EditButton"] = { text=ItemRackText.EVENTSEDIT_TEXT, tooltip=ItemRackText.EVENTSEDIT_TOOLTIP },
        ["ItemRack_Events_NewButton"] = { text=ItemRackText.EVENTSNEW_TEXT, tooltip=ItemRackText.EVENTSNEW_TOOLTIP },
        ["ItemRack_EditEvent_Save"] = { text=ItemRackText.EVENTSSAVE_TEXT, tooltip=ItemRackText.EVENTSSAVE_TOOLTIP },
        ["ItemRack_EditEvent_Test"] = { text=ItemRackText.EVENTSTEST_TEXT, tooltip=ItemRackText.EVENTSTEST_TOOLTIP },
        ["ItemRack_EditEvent_Cancel"] = { text=ItemRackText.EVENTSCANCEL_TEXT, tooltip=ItemRackText.EVENTSCANCEL_TOOLTIP },
        ["ItemRack_EventName"] = { text=ItemRackText.EVENTNAME_TEXT, tooltip=ItemRackText.EVENTNAME_TOOLTIP },
        ["ItemRack_EventTrigger"] = { text=ItemRackText.EVENTTRIGGER_TEXT, tooltip=ItemRackText.EVENTTRIGGER_TOOLTIP },
        ["ItemRack_EventDelay"] = { text=ItemRackText.EVENTDELAY_TEXT, tooltip=ItemRackText.EVENTDELAY_TOOLTIP },
        ["ItemRack_Opt_NotifyThirty"] = { text=ItemRackText.OPT_NOTIFYTHIRTY_TEXT, tooltip=ItemRackText.OPT_NOTIFYTHIRTY_TOOLTIP, type="Check", info="NotifyThirty" },
        ["ItemRack_Opt_ShowAllEvents"] = { text=ItemRackText.OPT_SHOWALLEVENTS_TEXT, tooltip=ItemRackText.OPT_SHOWALLEVENTS_TOOLTIP, type="Check", info="ShowAllEvents" },
        ["ItemRack_ResetButton"] = { text=ItemRackText.RESETBUTTON_TEXT, tooltip=ItemRackText.RESETBUTTON_TOOLTIP },
        ["ItemRack_Opt_AllowHidden"] = { text=ItemRackText.OPT_ALLOWHIDDEN_TEXT, tooltip=ItemRackText.OPT_ALLOWHIDDEN_TOOLTIP, type="Check", info="AllowHidden" },
        ["ItemRack_Opt_LargeFont"] = { text=ItemRackText.OPT_LARGEFONT_TEXT, tooltip=ItemRackText.OPT_LARGEFONT_TOOLTIP, type="Check", info="LargeFont" },
        ["ItemRack_ResetEventsButton"] = { text=ItemRackText.RESETEVENTSBUTTON_TEXT, tooltip=ItemRackText.RESETEVENTSBUTTON_TOOLTIP },
        ["ItemRack_Opt_SquareMinimap"] = { text=ItemRackText.OPT_SQUAREMINIMAP_TEXT, tooltip=ItemRackText.OPT_SQUAREMINIMAP_TOOLTIP, info="SquareMinimap" },
        ["ItemRack_Opt_BigCooldown"] = { text=ItemRackText.OPT_BIGCOOLDOWN_TEXT, tooltip=ItemRackText.OPT_BIGCOOLDOWN_TOOLTIP, info="BigCooldown" },
        ["ItemRack_ShowHelmText"] = { text="Helm", type="Label" },
        ["ItemRack_ShowCloakText" ] = { text="Cloak", type="Label" },
        ["ItemRack_Opt_SetLabels"] = { text=ItemRackText.OPT_SETLABELS_TEXT, tooltip=ItemRackText.OPT_SETLABELS_TOOLTIP, info="SetLabels" },
        ["ItemRack_Opt_AutoToggle"] = { text=ItemRackText.OPT_AUTOTOGGLE_TEXT, tooltip=ItemRackText.OPT_AUTOTOGGLE_TOOLTIP, info="AutoToggle" },
}

-- numerically indexed list of options for scrollable options window
ItemRack.OptScroll = {
        { idx="ItemRack_Opt_ShowTooltips" },
        { idx="ItemRack_Opt_TooltipFollow", dependency="ItemRack_Opt_ShowTooltips" },
        { idx="ItemRack_Opt_TinyTooltip", dependency="ItemRack_Opt_ShowTooltips" },
        { idx="ItemRack_Opt_ShowIcon" },
        { idx="ItemRack_Opt_DisableToggle", dependency="ItemRack_Opt_ShowIcon" },
        { idx="ItemRack_Opt_SquareMinimap", dependency="ItemRack_Opt_ShowIcon" },
        { idx="ItemRack_Opt_Bindings" },
        { idx="ItemRack_Opt_SetLabels" },
        { idx="ItemRack_Opt_CooldownNumbers" },
        { idx="ItemRack_Opt_BigCooldown", dependency="ItemRack_Opt_CooldownNumbers" },
        { idx="ItemRack_Opt_Notify" },
        { idx="ItemRack_Opt_NotifyThirty", dependency="ItemRack_Opt_Notify" },
        { idx="ItemRack_Opt_MenuShift" },
        { idx="ItemRack_Opt_AutoToggle" },
        { idx="ItemRack_Opt_ShowEmpty" },
        { idx="ItemRack_Opt_AllowHidden" },
        { idx="ItemRack_Opt_Soulbound" },
        { idx="ItemRack_Opt_RightClick" },
        { idx="ItemRack_Opt_FlipMenu" },
        { idx="ItemRack_Opt_RotateMenu" },
        { idx="ItemRack_Opt_FlipBar" }
}

-- paperdoll_slot=frame name of the slot on the paperdoll frame (for alt+click purposes)
-- ["SlotName"]=1 for each allowable slot
-- swappable=1 for slots that can be swapped in combat
-- ignore_soulbound = ignore soulbound flag for this slot
ItemRack.Indexes = {
        [0] = { name=AMMOSLOT, paperdoll_slot="CharacterAmmoSlot", keybind="Use Ammo Item", ignore_soulbound=1, swappable=1, INVTYPE_AMMO=1 },
        [1] = { name=INVTYPE_HEAD, paperdoll_slot="CharacterHeadSlot", keybind="Use Head Item", INVTYPE_HEAD=1 },
        [2] = { name=INVTYPE_NECK, paperdoll_slot="CharacterNeckSlot", keybind="Use Neck Item", INVTYPE_NECK=1 },
        [3] = { name=INVTYPE_SHOULDER, paperdoll_slot="CharacterShoulderSlot", keybind="Use Shoulder Item", INVTYPE_SHOULDER=1 },
        [4] = { name=INVTYPE_BODY, paperdoll_slot="CharacterShirtSlot", keybind="Use Shirt Item", ignore_soulbound=1, INVTYPE_BODY=1 },
        [5] = { name=INVTYPE_CHEST, paperdoll_slot="CharacterChestSlot", keybind="Use Chest Item", INVTYPE_CHEST=1, INVTYPE_ROBE=1 },
        [6] = { name=INVTYPE_WAIST, paperdoll_slot="CharacterWaistSlot", keybind="Use Waist Item", INVTYPE_WAIST=1 },
        [7] = { name=INVTYPE_LEGS, paperdoll_slot="CharacterLegsSlot", keybind="Use Legs Item", INVTYPE_LEGS=1 },
        [8] = { name=INVTYPE_FEET, paperdoll_slot="CharacterFeetSlot", keybind="Use Feet Item", INVTYPE_FEET=1 },
        [9] = { name=INVTYPE_WRIST, paperdoll_slot="CharacterWristSlot", keybind="Use Wrist Item", INVTYPE_WRIST=1 },
        [10]= { name=INVTYPE_HAND, paperdoll_slot="CharacterHandsSlot", keybind="Use Hands Item", INVTYPE_HAND=1 },
        [11]= { name=INVTYPE_FINGER, paperdoll_slot="CharacterFinger0Slot", keybind="Use Top Finger Item", INVTYPE_FINGER=1 },
        [12]= { name=INVTYPE_FINGER, paperdoll_slot="CharacterFinger1Slot", keybind="Use Bottom Finger Item", INVTYPE_FINGER=1 },
        [13]= { name=INVTYPE_TRINKET, paperdoll_slot="CharacterTrinket0Slot", ignore_soulbound=1, keybind="Use Top Trinket Item", INVTYPE_TRINKET=1 },
        [14]= { name=INVTYPE_TRINKET, paperdoll_slot="CharacterTrinket1Slot", ignore_soulbound=1, keybind="Use Bottom Trinket Item", INVTYPE_TRINKET=1 },
        [15]= { name=INVTYPE_CLOAK, paperdoll_slot="CharacterBackSlot", keybind="Use Back Item", INVTYPE_CLOAK=1 },
        [16]= { name=INVTYPE_WEAPONMAINHAND, paperdoll_slot="CharacterMainHandSlot", keybind="Use Main-Hand Item", swappable=1, INVTYPE_WEAPONMAINHAND=1, INVTYPE_2HWEAPON=1, INVTYPE_WEAPON=1 },
        [17]= { name=INVTYPE_WEAPONOFFHAND, paperdoll_slot="CharacterSecondaryHandSlot", keybind="Use Off-Hand Item", swappable=1, INVTYPE_WEAPONOFFHAND=1, INVTYPE_SHIELD=1, INVTYPE_HOLDABLE=1, INVTYPE_WEAPON=1 },
        [18]= { name=INVTYPE_RANGED, paperdoll_slot="CharacterRangedSlot", keybind="Use Range Item", swappable=1, INVTYPE_RANGED=1, INVTYPE_RANGEDRIGHT=1, INVTYPE_THROWN=1, INVTYPE_RELIC=1 },
        [19]= { name=INVTYPE_TABARD, paperdoll_slot="CharacterTabardSlot", keybind="Use Tabard Item", ignore_soulbound=1, INVTYPE_TABARD=1 }
}

-- "add" or "remove" a frame from UISpecialFrames
local function make_escable(frame,add)
        local found
        for i in UISpecialFrames do
                if UISpecialFrames[i]==frame then
                        found = i
                end
        end
        if not found and add=="add" then
                table.insert(UISpecialFrames,frame)
        elseif found and add=="remove" then
                table.remove(UISpecialFrames,found)
        end
end

local _,_,durability_pattern = string.find(DURABILITY_TEMPLATE,"(.+) .+/.+")
durability_pattern = durability_pattern or ""

-- dock-dependant offset and directions: MainDock..MenuDock
-- x/yoff   = offset MenuFrame is positioned to InvFrame
-- x/ydir   = direction items are added to menu
-- x/ystart = starting offset when building a menu, relativePoint MenuDock
-- mx/y     = offset MenuFrame is positioned to contents of InvFrame
local dock_stats = { ["TOPRIGHTTOPLEFT"] =               { xoff=-4, yoff=0,  xdir=1,  ydir=-1, xstart=8,   ystart=-8,  mx=3,  my=8 },
                                         ["BOTTOMRIGHTBOTTOMLEFT"] = { xoff=-4, yoff=0,  xdir=1,  ydir=1,  xstart=8,   ystart=44,  mx=3,  my=-8 },
                                         ["TOPLEFTTOPRIGHT"] =           { xoff=4,  yoff=0,  xdir=-1, ydir=-1, xstart=-44, ystart=-8,  mx=-2, my=8 },
                                         ["BOTTOMLEFTBOTTOMRIGHT"] = { xoff=4,  yoff=0,  xdir=-1, ydir=1,  xstart=-44, ystart=44,  mx=-2, my=-8 },
                                         ["TOPRIGHTBOTTOMRIGHT"] =   { xoff=0,  yoff=-4, xdir=-1, ydir=1,  xstart=-44,  ystart=44, mx=8,  my=3 },
                                         ["BOTTOMRIGHTTOPRIGHT"] =   { xoff=0,  yoff=4,  xdir=-1, ydir=-1, xstart=-44,  ystart=-8, mx=8,  my=-3 },
                                         ["TOPLEFTBOTTOMLEFT"] =         { xoff=0,  yoff=-4, xdir=1,  ydir=1,  xstart=8,   ystart=44,  mx=-8, my=2 },
                                         ["BOTTOMLEFTTOPLEFT"] =         { xoff=0,  yoff=4,  xdir=1,  ydir=-1, xstart=8,   ystart=-8,  mx=-8, my=-2 } }

-- returns info depending on current docking. ie: dock_info("xoff")
local function dock_info(which)

        local anchor = ItemRack.MainDock..ItemRack.MenuDock

        if dock_stats[anchor] and which and dock_stats[anchor][which] then
                return dock_stats[anchor][which]
        else
                return 0
        end
end

-- returns info depending on where the window is currently
-- since frame scales and positions can be nil at the most inconvenient times, it approximates
-- its corner based on settings and makes no assumptions that even UIParent exists
-- no argument : return corner window is in
-- "LEFTRIGHT" : return "LEFT" or "RIGHT"
-- "TOPBOTTOM" : return "TOP" or "BOTTOM"
local function corner_info(which)

        local length,cx,cy,xpoint,ypoint
        local vertside,horzside = "TOP","LEFT"
        local info

        if table.getn(ItemRack_Users[user].Bar)>0 then
                length = table.getn(ItemRack_Users[user].Bar)*40 + 32
                if ItemRack_Users[user].MainOrient=="HORIZONTAL" then
                        cx = length
                        cy = 55
                else
                        cx = 55
                        cy = length
                end
                xpoint = cx/2+(ItemRack_Users[user].XPos*ItemRack_Users[user].MainScale)
                ypoint = (ItemRack_Users[user].YPos*ItemRack_Users[user].MainScale)-cy/2

                if xpoint<(UIParent and UIParent:GetWidth()/2 or 512) then
                        horzside = "LEFT"
                else
                        horzside = "RIGHT"
                end

                if ypoint<(UIParent and UIParent:GetHeight()/2 or 386) then
                        vertside = "BOTTOM"
                else
                        vertside = "TOP"
                end
        end

        if which=="LEFTRIGHT" then
                info = horzside
        elseif which=="TOPBOTTOM" then
                info = vertside
        else
                info = vertside..horzside
        end

        return info

end

local inv_dock = {
        [0]  = { orient="HORIZONTAL", maindock="BOTTOMLEFT", menudock="TOPLEFT" },
        [1]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
        [2]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
        [3]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
        [4]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
        [5]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
        [6]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
        [7]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
        [8]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
        [9]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
        [10]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
        [11]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
        [12]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
        [13]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
        [14]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
        [15] = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
        [16] = { orient="HORIZONTAL", maindock="BOTTOMLEFT", menudock="TOPLEFT" },
        [17] = { orient="HORIZONTAL", maindock="BOTTOMLEFT", menudock="TOPLEFT" },
        [18] = { orient="HORIZONTAL", maindock="BOTTOMLEFT", menudock="TOPLEFT" },
        [19] = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" }
}

-- places the menu against the invslot
-- setframe = true when docking to set frame
function ItemRack_DockMenu(invslot,relativeTo)

        local attachTo = ((not relativeTo) and "ItemRackInv"..invslot) or (relativeTo=="TITAN" and "TitanPanelItemRackButton") or (relativeTo=="SET" and "ItemRack_Sets_Inv"..invslot) or (relativeTo=="MINIMAP" and "ItemRack_IconFrame") or ItemRack.Indexes[invslot].paperdoll_slot
        local item = getglobal(attachTo)
        local noflip = ItemRack_Settings.FlipMenu=="OFF"
        local corner=corner_info()
        local mainorient = ItemRack_Users[user].MainOrient
        local ynudge -- amount if any to nudge the y offset

        if relativeTo then
                ItemRack.MenuDockedTo = relativeTo
        end

        if relativeTo=="MINIMAP" then
                if (ItemRack_IconFrame:GetTop() or 0)<(UIParent:GetHeight() or 0)/2 then
                        ItemRack.MainDock = "TOPLEFT"
                        ItemRack.MenuDock = "BOTTOMLEFT"
                        ynudge = -12
                else
                        ItemRack.MainDock = "BOTTOMRIGHT"
                        ItemRack.MenuDock = "TOPRIGHT"
                        ynudge = 12
                end
        elseif relativeTo=="TITAN" then
                local xpos, ypos = GetCursorPosition()
                if ypos<400 then
                        ItemRack.MainDock = "TOPLEFT"
                        ItemRack.MenuDock = "BOTTOMLEFT"
                        ynudge = 0
                else
                        ItemRack.MainDock = "BOTTOMLEFT"
                        ItemRack.MenuDock = "TOPLEFT"
                        ynudge = 0
                end
        elseif mainorient=="HORIZONTAL" then
                if corner=="BOTTOMLEFT" then
                        ItemRack.MainDock = noflip and "TOPLEFT" or "BOTTOMLEFT"
                        ItemRack.MenuDock = noflip and "BOTTOMLEFT" or "TOPLEFT"
                elseif corner=="BOTTOMRIGHT" then
                        ItemRack.MainDock = noflip and "TOPRIGHT" or "BOTTOMRIGHT"
                        ItemRack.MenuDock = noflip and "BOTTOMRIGHT" or "TOPRIGHT"
                elseif corner=="TOPLEFT" then
                        ItemRack.MainDock = noflip and "BOTTOMLEFT" or "TOPLEFT"
                        ItemRack.MenuDock = noflip and "TOPLEFT" or "BOTTOMLEFT"
                else -- "TOPRIGHT"
                        ItemRack.MainDock = noflip and "BOTTOMRIGHT" or "TOPRIGHT"
                        ItemRack.MenuDock = noflip and "TOPRIGHT" or "BOTTOMRIGHT"
                end
        else
                if corner=="BOTTOMLEFT" then
                        ItemRack.MainDock = noflip and "BOTTOMRIGHT" or "BOTTOMLEFT"
                        ItemRack.MenuDock = noflip and "BOTTOMLEFT" or "BOTTOMRIGHT"
                elseif corner=="BOTTOMRIGHT" then
                        ItemRack.MainDock = noflip and "BOTTOMLEFT" or "BOTTOMRIGHT"
                        ItemRack.MenuDock = noflip and "BOTTOMRIGHT" or "BOTTOMLEFT"
                elseif corner=="TOPLEFT" then
                        ItemRack.MainDock = noflip and "TOPRIGHT" or "TOPLEFT"
                        ItemRack.MenuDock = noflip and "TOPLEFT" or "TOPRIGHT"
                else -- "TOPRIGHT"
                        ItemRack.MainDock = noflip and "TOPLEFT" or "TOPRIGHT"
                        ItemRack.MenuDock = noflip and "TOPRIGHT" or "TOPLEFT"
                end
        end

        if relativeTo=="SET" then
                ItemRack_MenuFrame:SetScale(ItemRack_SetsFrame:GetScale())
                ItemRack.MainDock = inv_dock[invslot].maindock
                ItemRack.MenuDock = inv_dock[invslot].menudock
        elseif relativeTo=="CHARACTERSHEET" then
                ItemRack_MenuFrame:SetScale(getglobal(ItemRack.Indexes[1].paperdoll_slot):GetScale())
                ItemRack.MainDock = inv_dock[invslot].maindock
                ItemRack.MenuDock = inv_dock[invslot].menudock
                if ItemRack.MainDock == "TOPLEFT" then
                        -- horizontal menus always go to right on character sheet
                        ItemRack.MainDock = "TOPRIGHT"
                        ItemRack.MenuDock = "TOPLEFT"
                end
        else
                ItemRack_MenuFrame:SetScale(ItemRack_Users[user].MainScale)
        end

        ItemRack_MenuFrame:ClearAllPoints()
        ItemRack_MenuFrame:SetPoint(ItemRack.MenuDock,attachTo,ItemRack.MainDock,dock_info("mx"),dock_info("my")+ (ynudge and ynudge or 0))
end

-- v1="Left1" or "Right1" up to "Left30" or "Right30"
local function is_red(v1)

        local its_red,r,g,b = false

        r,g,b = getglobal("Rack_TooltipScanText"..v1):GetTextColor()
        if r>.9 and g<.2 and b<.2 then
                its_red = true
        end

        return its_red
end

-- returns true if the player can wear this item (no red text on its tooltip)
-- separated from get_item_info because tooltip scanning should be done only at utmost need
local function player_can_wear(bag,slot,invslot)

        local found,i,txt = false

        for i=2,15 do
                -- ClearLines doesn't remove colors, manually remove them
                getglobal("Rack_TooltipScanTextLeft"..i):SetTextColor(0,0,0)
                getglobal("Rack_TooltipScanTextRight"..i):SetTextColor(0,0,0)
        end
        Rack_TooltipScan:SetBagItem(bag,slot)

        for i=2,15 do
                txt = getglobal("Rack_TooltipScanTextLeft"..i):GetText()
                -- if either left or right text is red and this isn't a Durability x/x line, this item can't be worn
                if (is_red("Left"..i) or is_red("Right"..i)) and not string.find(txt,durability_pattern) and not string.find(txt,"^Requires") then
                        found = true
                end
        end

        local _,_,_,itemType = Rack.GetItemInfo(bag,slot)
        if itemType=="INVTYPE_WEAPON" and invslot==17 and not ItemRack.CanWearOneHandOffHand then
                found = true
        end

        return not found
end

-- the old central info gatherer, now a wrapper to Rack.GetItemInfo
local function get_item_info(bag,slot)

        local texture,name,equipslot,soulbound,count

        if bag==20 then
                -- if querying set slot, return current set texture and name
                name = Rack.CurrentSet()
                texture = "Interface\\AddOns\\ItemRack\\ItemRack-Icon"
                if name and Rack_User[user].Sets[name] and not string.find(name,"^ItemRack") and not string.find(name,"^Rack-") then
                        texture = Rack_User[user].Sets[name].icon
                else
                        name = nil
                end
                return texture,name
        end

        texture,_,name,equipslot = Rack.GetItemInfo(bag,slot)
        if slot then
                _,count = GetContainerItemInfo(bag,slot)
        end
        if ItemRack_Settings.Soulbound=="ON" and name then
                local text
                if slot then
                        Rack_TooltipScan:SetBagItem(bag,slot)
                else
                        Rack_TooltipScan:SetInventoryItem("player",bag)
                end
                for i=2,5 do
                        text = getglobal("Rack_TooltipScanTextLeft"..i):GetText() or ""
                        if text==ITEM_SOULBOUND or text==ITEM_BIND_QUEST or text==ITEM_CONJURED then
                                soulbound = true
                        end
                end
        end

        return texture,name,equipslot,soulbound,count
end

local function cursor_empty()
        return not (CursorHasItem() or CursorHasMoney() or CursorHasSpell())
end

-- updates cooldown spinners in the menu
local function update_menu_cooldowns()

        local start, duration, enable

        if ItemRack.InvOpen then
                for i=1,ItemRack.NumberOfItems do
                        if ItemRack.BaggedItems[i].bag then
                                start, duration, enable = GetContainerItemCooldown(ItemRack.BaggedItems[i].bag,ItemRack.BaggedItems[i].slot)
                                CooldownFrame_SetTimer(getglobal("ItemRackMenu"..i.."Cooldown"), start, duration, enable)
                        else
                                getglobal("ItemRackMenu"..i.."Time"):SetText("")
                        end
                end
        end
end

-- updates cooldown spinners in the main bar
local function update_inv_cooldowns()

        local i, start, duration, enable

        if table.getn(ItemRack_Users[user].Bar)>0 then
                for i=1,table.getn(ItemRack_Users[user].Bar) do
                        start, duration, enable = GetInventoryItemCooldown("player",ItemRack_Users[user].Bar[i])
                        CooldownFrame_SetTimer(getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i].."Cooldown"), start, duration, enable)
                end
        end

        update_menu_cooldowns()
end

-- call this when window has changed and cooldowns need redrawn
local function cooldowns_need_updating()
        Rack.StartTimer("CooldownUpdate",.25)
        ItemRack.CooldownsNeedUpdating = true
end

local function populate_baggeditems(idx,bag,slot,name,texture)

        if not ItemRack.BaggedItems[idx] then
                ItemRack.BaggedItems[idx] = {}
        end
        ItemRack.BaggedItems[idx].bag = bag
        ItemRack.BaggedItems[idx].slot = slot
        ItemRack.BaggedItems[idx].name = name
        ItemRack.BaggedItems[idx].texture = texture
end

-- to minimize garbage creation, tables are manipulated by copying values instead of tables
local function copy_baggeditems(source,dest)

        if not ItemRack.BaggedItems[dest] then
                ItemRack.BaggedItems[dest] = {}
        end
        ItemRack.BaggedItems[dest].bag = ItemRack.BaggedItems[source].bag
        ItemRack.BaggedItems[dest].slot = ItemRack.BaggedItems[source].slot
        ItemRack.BaggedItems[dest].name = ItemRack.BaggedItems[source].name
        ItemRack.BaggedItems[dest].texture = ItemRack.BaggedItems[source].texture
end

-- sorts menu up to stop_point, which is idx+1 usually (sort uses stop_point as a temp spot for swapping)
local function sort_menu(stop_point)

        local done,i=false

        if stop_point>2 then
                while not done do
                        done = true
                        for i=1,stop_point-2 do
                                if ItemRack.BaggedItems[i].name > ItemRack.BaggedItems[i+1].name then
                                        copy_baggeditems(i,stop_point)
                                        copy_baggeditems(i+1,i)
                                        copy_baggeditems(stop_point,i+1)

                                        done = false
                                end
                        end
                end
        end

end

function ItemRack_CurrentSet()
        return Rack.CurrentSet()
end

-- builds a menu outward from invslot (0-19)
-- setframe = true if this is to dock to the set frame
function ItemRack_BuildMenu(invslot,relativeTo)

        local idx,i,j,k,item,texture,name,equipslot,soulbound,found = 1
        local mainorient = ItemRack_Users[user].MainOrient
        local bagStart,bagEnd = 0,4

        if relativeTo=="SET" or relativeTo=="CHARACTERSHEET" then
                -- if displaying to a set, then 
                mainorient = "VERTICAL"
                if invslot==0 or invslot==16 or invslot==17 or invslot==18 then
                        mainorient = "HORIZONTAL"
                end
        elseif relativeTo=="MINIMAP" then
                mainorient = "HORIZONTAL"
        elseif relativeTo=="TITAN" then
                mainorient = "HORIZONTAL"
        end
        ItemRack_DockMenu(invslot,relativeTo)

        for i=1,table.getn(ItemRack_Users[user].Bar) do
                if invslot~=ItemRack_Users[user].Bar[i] then
                        getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i]):UnlockHighlight()
                else
                        getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i]):LockHighlight()
                end
        end

        if invslot==0 then
                -- if this is an ammo slot, clear totals
                for i in ItemRack.AmmoCounts do
                        ItemRack.AmmoCounts[i] = 0
                end
        end

        if invslot<20 then

                if ItemRack.BankIsOpen then
                        bagStart,bagEnd = -1,10
                end

                -- go through bags and gather items into .BaggedItems
                for i=bagStart,bagEnd do
                        for j=1,GetContainerNumSlots(i) do
                                texture,name,equipslot,soulbound,count = get_item_info(i,j)
                                soulbound = soulbound or ItemRack.Indexes[invslot].ignore_soulbound -- pretend item soulbound if flagged to ignore_soulbound
                                if (equipslot and ItemRack.Indexes[invslot][equipslot]) and (soulbound or ItemRack_Settings.Soulbound=="OFF") then
                                        if ItemRack_Settings.AllowHidden=="ON" and ItemRack_Users[user].Ignore[name] and not IsAltKeyDown() then
                                                -- skip items that are on ignore list
                                        elseif player_can_wear(i,j,invslot) then
                                                if invslot==0 and count then
                                                        -- if this is an ammo slot menu
                                                        ItemRack.AmmoCounts[name] = (ItemRack.AmmoCounts[name] or 0) + count
                                                        found = false
                                                        for k=1,(idx-1) do
                                                                if ItemRack.BaggedItems[k].name==name then
                                                                        found=true
                                                                end
                                                        end
                                                        if not found then
                                                                populate_baggeditems(idx,i,j,name,texture)
                                                                idx = idx + 1
                                                        end                                     
                                                else
                                                        populate_baggeditems(idx,i,j,name,texture)
                                                        idx = idx + 1
                                                end
                                        end
                                end
                        end
                end
                sort_menu(idx)

                if ItemRack_Settings.ShowEmpty=="ON" and GetInventoryItemLink("player",invslot) and not (ItemRack_Settings.RightClick=="ON" and (invslot==13 or invslot==14)) then
                        -- add an empty slot to the menu
                        _,_,i = string.find(ItemRack.Indexes[invslot].keybind,"Use (.+) Item")
                        _,j = GetInventorySlotInfo(string.gsub(ItemRack.Indexes[invslot].paperdoll_slot,"Character",""))
                        populate_baggeditems(idx,nil,nil,"(empty)",j)
                        idx = idx + 1
                end

        else
                -- this is a menu for sets
                -- go through sets and gather them into .BaggedItems
                for i in Rack_User[user].Sets do
                        if not string.find(i,"^ItemRack") and not string.find(i,"^Rack-") and (not Rack_User[user].Sets[i].hide or IsAltKeyDown()) then
                                populate_baggeditems(idx,nil,nil,i,Rack_User[user].Sets[i].icon)
                                idx = idx + 1
                        end
                end
                sort_menu(idx)
        end

        ItemRack.NumberOfItems = math.min(idx-1,ItemRack.MaxItems)

        if ItemRack.NumberOfItems<1 then
                -- user has no bagged items for this type
                ItemRack_MenuFrame:Hide()
        else
                -- display items outward from docking point
                local col,row,xpos,ypos = 0,0,dock_info("xstart"),dock_info("ystart")
                local max_cols = 1

                if ItemRack.NumberOfItems>24 then
                        max_cols = 5
                elseif ItemRack.NumberOfItems>18 then
                        max_cols = 4
                elseif ItemRack.NumberOfItems>12 then
                        max_cols = 3
                elseif ItemRack.NumberOfItems>4 then
                        max_cols = 2
                end

                for i=1,ItemRack.NumberOfItems do

                        local item = getglobal("ItemRackMenu"..i.."Icon")
                        item:SetTexture(ItemRack.BaggedItems[i].texture)
                        -- grey menu item if it's on the ignore list (ALT key is down if it made it to BaggedItems)
                        if ItemRack_Settings.AllowHidden=="ON" and (ItemRack_Users[user].Ignore[ItemRack.BaggedItems[i].name] or (Rack_User[user].Sets[ItemRack.BaggedItems[i].name] and Rack_User[user].Sets[ItemRack.BaggedItems[i].name].hide)) then
                                SetDesaturation(item,1)
                        else
                                SetDesaturation(item,nil)
                        end

                        local item = getglobal("ItemRackMenu"..i)
                        item:SetPoint("TOPLEFT","ItemRack_MenuFrame",ItemRack.MenuDock,xpos,ypos)

                        if (mainorient=="HORIZONTAL" and ItemRack_Settings.RotateMenu=="OFF") or (mainorient=="VERTICAL" and ItemRack_Settings.RotateMenu=="ON") then
                                xpos = xpos + dock_info("xdir")*40
                                col = col + 1
                                if col==max_cols then
                                        xpos = dock_info("xstart")
                                        col = 0
                                        ypos = ypos + dock_info("ydir")*40
                                        row = row + 1
                                end
                                item:Show()
                        else
                                ypos = ypos + dock_info("ydir")*40
                                col = col + 1
                                if col==max_cols then
                                        ypos = dock_info("ystart")
                                        col = 0
                                        xpos = xpos + dock_info("xdir")*40
                                        row = row + 1
                                end
                                item:Show()
                        end
                end
                for i=(ItemRack.NumberOfItems+1),ItemRack.MaxItems do
                        getglobal("ItemRackMenu"..i):Hide()
                end
                if col==0 then
                        row = row-1
                end

                if (mainorient=="HORIZONTAL" and ItemRack_Settings.RotateMenu=="OFF") or (mainorient=="VERTICAL" and ItemRack_Settings.RotateMenu=="ON") then
                        ItemRack_MenuFrame:SetWidth(12+(max_cols*40))
                        ItemRack_MenuFrame:SetHeight(12+((row+1)*40))
                else
                        ItemRack_MenuFrame:SetWidth(12+((row+1)*40))
                        ItemRack_MenuFrame:SetHeight(12+(max_cols*40))
                end

                -- apply slot-dependant overlays, ammo count, set name and key bindings
                if invslot==0 then -- if this is an ammo slot, show counts
                        for i=1,ItemRack.NumberOfItems do
                                if ItemRack.AmmoCounts[ItemRack.BaggedItems[i].name] then
                                        getglobal("ItemRackMenu"..i.."Count"):SetText(ItemRack.AmmoCounts[ItemRack.BaggedItems[i].name])
                                end
                        end
                elseif invslot==20 then -- if this is a set slot, show names and bindings
                        for i=1,ItemRack.NumberOfItems do
                                name = ItemRack.BaggedItems[i].name
                                if ItemRack.BankIsOpen and Rack.SetHasBanked(name) then
                                        getglobal("ItemRackMenu"..i.."Border"):Show()
                                        getglobal("ItemRackMenu"..i.."Icon"):SetVertexColor(.5,.5,.5)
                                else
                                        getglobal("ItemRackMenu"..i.."Border"):Hide()
                                        getglobal("ItemRackMenu"..i.."Icon"):SetVertexColor(1,1,1)
                                end
                                        
                                item = getglobal("ItemRackMenu"..i.."Name")
                                if ItemRack_Settings.SetLabels=="ON" then
                                        item:SetText(name)
                                        item:Show()
                                else
                                        item:Hide()
                                end
                                item = getglobal("ItemRackMenu"..i.."HotKey")
                                if Rack_User[user].Sets[name].key and ItemRack_Settings.Bindings=="ON" then
                                        _,_,j,k = string.find(Rack_User[user].Sets[name].key or "","(.).+(-.)")
                                        item:SetText((j or "")..(k or ""))
                                        item:Show()
                                else
                                        item:Hide()
                                end
                        end
                else -- normal slot (1-19) has no overlays
                        for i=1,ItemRack.NumberOfItems do
                                getglobal("ItemRackMenu"..i.."Name"):SetText("")
                                getglobal("ItemRackMenu"..i.."Count"):SetText("")
                                getglobal("ItemRackMenu"..i.."HotKey"):SetText("")

                                if ItemRack.BankedItems[ItemRack.BaggedItems[i].name] then
                                        getglobal("ItemRackMenu"..i.."Border"):Show()
                                        getglobal("ItemRackMenu"..i.."Icon"):SetVertexColor(.5,.5,.5)
                                else
                                        getglobal("ItemRackMenu"..i.."Border"):Hide()
                                        getglobal("ItemRackMenu"..i.."Icon"):SetVertexColor(1,1,1)
                                end
                        end
                end

                ItemRack.InvOpen = invslot
                ItemRack_MenuFrame:Show()
                update_menu_cooldowns()
                Rack.StartTimer("CooldownUpdate",0) -- immediate cooldown update
                Rack.StartTimer("MenuFrame")

        end
end

-- for use with main/menu frames with UIParent parent when relocated by the mod, to register for layout-cache.txt
local function really_setpoint(frame,point,relativeTo,relativePoint,xoff,yoff)
        frame:SetPoint(point,relativeTo,relativePoint,xoff,yoff)
        ItemRack_Users[user].XPos = xoff
        ItemRack_Users[user].YPos = yoff
end

-- updates the image inside the minimap button depending on current set and disable toggle option ("Minimap set menu")
local function draw_minimap_icon()
        local setname = Rack.CurrentSet()

        if setname and Rack_User[user].Sets[setname] and ItemRack_Settings.DisableToggle=="ON" then
                ItemRack_IconFrame_Icon:SetTexture(Rack_User[user].Sets[setname].icon)
        else
                ItemRack_IconFrame_Icon:SetTexture("Interface\\AddOns\\ItemRack\\ItemRack-Icon")
        end
end

-- draws the inventory bar
local function draw_inv()

        local oldx = ItemRack_InvFrame:GetLeft() or ItemRack_Users[user].XPos
        local oldy = ItemRack_InvFrame:GetTop() or ItemRack_Users[user].YPos
        local oldcx = ItemRack_InvFrame:GetWidth() or 0
        local oldcy = ItemRack_InvFrame:GetHeight() or 0
        local bar = ItemRack_Users[user].Bar

        if not oldx or not oldy then
                return -- frame isn't fully defined yet, leave now
        end

        -- for a left-to-right horizontal configuration
        local cx,cy,i,item,texture,xspacer,yspacer = 56,56

        if ItemRack_Users[user].MainOrient=="HORIZONTAL" then
                -- horizontal from left to right
                xdir,ydir,corner,cornerTo,cornerStart,xdirStart,ydirStart,xadd,yadd = 4,0,"TOPRIGHT","TOPLEFT","TOPLEFT",10,-10,40,0

                if ItemRack_Settings.FlipBar=="ON" then
                        -- horizontal from right to left
                        xdir,ydir,corner,cornerTo,cornerStart,xdirStart,ydirStart,xadd,yadd = -4,0,"TOPLEFT","TOPRIGHT","TOPRIGHT",-10,-10,-40,0
                end

        else
                -- vertical from top to bottom
                xdir,ydir,corner,cornerTo,cornerStart,xdirStart,ydirStart,xadd,yadd = 0,-4,"BOTTOMLEFT","TOPLEFT","TOPLEFT",10,-10,0,40
                
                if ItemRack_Settings.FlipBar=="ON" then
                        -- vertical from bottom to top
                        xdir,ydir,corner,cornerTo,cornerStart,xdirStart,ydirStart,xadd,yadd = 0,4,"TOPLEFT","BOTTOMLEFT","BOTTOMLEFT",10,10,0,-40
                end
        end

        for i=0,20 do
                getglobal("ItemRackInv"..i):Hide()
        end
        ItemRack.TrinketsPaired = false -- changes to true if two trinkets are beside each other

        if table.getn(bar)>0 then
                item = getglobal("ItemRackInv"..bar[1])
                item:ClearAllPoints()
                item:SetPoint(cornerStart,"ItemRack_InvFrame",cornerStart,xdirStart,ydirStart)
                getglobal("ItemRackInv"..bar[1].."Icon"):SetTexture(get_item_info(bar[1]))
                item:Show()
                if ItemRack_Settings.RightClick=="ON" and (bar[1]==13 and bar[2]==14) then
                        ItemRack.TrinketsPaired = true
                end
                for i=2,table.getn(bar) do
                        xspacer,yspacer = 0,0
                        if ItemRack_Settings.RightClick=="ON" and ((bar[i]==13 and bar[i+1] and bar[i+1]==14) or 
                                (bar[i-1]==14 and bar[i-2] and bar[i-2]==13)) then
                                ItemRack.TrinketsPaired = true
                        end
                        if ItemRack_Users[user].Spaces[bar[i-1]] then
                                xspacer = xdir*2
                                yspacer = ydir*2
                        end
                        item = getglobal("ItemRackInv"..bar[i])
                        item:ClearAllPoints()
                        item:SetPoint(cornerTo,"ItemRackInv"..bar[i-1],corner,xdir+xspacer,ydir+yspacer)
                        getglobal("ItemRackInv"..bar[i].."Icon"):SetTexture(get_item_info(bar[i]))
                        item:Show()
                        cx = cx + math.abs(xadd) + math.abs(xspacer)
                        cy = cy + math.abs(yadd) + math.abs(yspacer) -- was minus yspacer
                end
                ItemRack_InvFrame:SetWidth(cx)
                ItemRack_InvFrame:SetHeight(cy)

                if ItemRack_Settings.FlipBar=="ON" and oldcx>32 and oldcy>32 then
                        -- if bar size changed (after being drawn before), and we're flipped, we need to shift it over
                        ItemRack_InvFrame:ClearAllPoints()
                        if ItemRack_Users[user].MainOrient=="HORIZONTAL" then
                                oldx = oldx + (oldcx-cx)
                        else
                                oldy = oldy + (cy-oldcy)
                        end
                        really_setpoint(ItemRack_InvFrame,"TOPLEFT","UIParent","BOTTOMLEFT",oldx,oldy)
                end

                if ItemRack_Users[user].Visible~="OFF" then
                        ItemRack_InvFrame:Show()
                end
                cooldowns_need_updating()
                Rack.StartTimer("CooldownUpdate",0)

                if ItemRack_Settings.SetLabels=="ON" then
                        local currentset = Rack.CurrentSet()
                        if currentset and Rack_User[user].Sets[currentset] then
                                ItemRackInv20Name:SetText(currentset)
                        else
                                ItemRackInv20Name:SetText(ItemRackText.EMPTYSET)
                        end
                else
                        ItemRackInv20Name:SetText("")
                end
        else
                ItemRack_Users[user].Visible="OFF"
                ItemRack_InvFrame:Hide()
        end
        draw_minimap_icon()

        if ItemRack_UpdatePlugins then
                -- update plugin if it exists
                local setname = Rack.CurrentSet()
                if setname and ItemRack_Users[user].Sets[setname] then
                        ItemRack_UpdatePlugins(setname,ItemRack_Users[user].Sets[setname].icon)
                else
                        ItemRack_UpdatePlugins(nil,"Interface\\AddOns\\ItemRack\\ItemRack-Icon")
                end
        end

end

local function unlocked()
        return ItemRack_Users[user].Locked~="ON"
end

-- sets window lock "ON" or "OFF"
local function set_lock(arg1)

        ItemRack_Users[user].Locked = arg1

        if arg1=="ON" then
                ItemRack_InvFrame:SetBackdropColor(0,0,0,0)
                ItemRack_InvFrame:SetBackdropBorderColor(0,0,0,0)
                ItemRack_InvFrame_Resize:Hide()
                ItemRack_Control_Rotate:SetAlpha(.4)
                ItemRack_Control_Rotate:Disable()
        else
                ItemRack_InvFrame:SetBackdropColor(1,1,1,1)
                ItemRack_InvFrame:SetBackdropBorderColor(1,1,1,1)
                ItemRack_InvFrame_Resize:Show()
                ItemRack_Control_Rotate:SetAlpha(1)
                ItemRack_Control_Rotate:Enable()
                ItemRack_ControlFrame:Show()
                Rack.StartTimer("ControlFrame")
        end
end

-- called at startup, UPDATE_BINDINGS and option change to show/hide key bindings
local function update_keybindings()

        local i,modifier,key,text

        -- update bindings for inventory slots
        for i=0,19 do
                if ItemRack.Indexes[i].keybind and ItemRack_Settings.Bindings=="ON" then
                        text = GetBindingKey(ItemRack.Indexes[i].keybind)
                        _,_,modifier,key = string.find(text or "","(.).+(-.)")
                        if modifier and key then
                                text = modifier..key
                        end
                        getglobal("ItemRackInv"..i.."HotKey"):SetText(text)
                else
                        getglobal("ItemRackInv"..i.."HotKey"):SetText("")
                end
        end

        -- update bindings for items on the rack
        ItemRack_AgreeOnKeyBindings()

end

local function move_control()

        local item = ItemRack_InvFrame

        ItemRack_Control_Rotate:ClearAllPoints()
        ItemRack_Control_Lock:ClearAllPoints()
        ItemRack_Control_Options:ClearAllPoints()

        if ItemRack_Users[user].MainOrient=="HORIZONTAL" then
                
                if ItemRack_Settings.FlipBar=="OFF" then
                        ItemRack_Control_Rotate:SetPoint("TOPLEFT","ItemRack_InvFrame","TOPRIGHT",-2,-3)
                else
                        ItemRack_Control_Rotate:SetPoint("TOPRIGHT","ItemRack_InvFrame","TOPLEFT",2,-3)
                end
                ItemRack_Control_Lock:SetPoint("TOPLEFT","ItemRack_Control_Rotate","BOTTOMLEFT")
                ItemRack_Control_Options:SetPoint("TOPLEFT","ItemRack_Control_Lock","BOTTOMLEFT")
        else
                if ItemRack_Settings.FlipBar=="OFF" then
                        ItemRack_Control_Rotate:SetPoint("BOTTOMLEFT","ItemRack_InvFrame","TOPLEFT",3,-2)
                else
                        ItemRack_Control_Rotate:SetPoint("TOPLEFT","ItemRack_InvFrame","BOTTOMLEFT",3,2)
                end
                ItemRack_Control_Lock:SetPoint("TOPLEFT","ItemRack_Control_Rotate","TOPRIGHT")
                ItemRack_Control_Options:SetPoint("TOPLEFT","ItemRack_Control_Lock","TOPRIGHT")
        end
end

-- moves the minimap icon to last position in settings or default angle of 45
local function move_icon()

        local xpos,ypos
        local angle = ItemRack_Settings.IconPos or 0

        if ItemRack_Settings.SquareMinimap=="ON" then
                -- brute force method until trig solution figured out - min/max a point on a circle beyond square
                xpos = 110 * cos(angle)
                ypos = 110 * sin(angle)
                xpos = math.max(-82,math.min(xpos,84))
                ypos = math.max(-86,math.min(ypos,82))
        else
                xpos = 80*cos(angle)
                ypos = 80*sin(angle)
        end

        ItemRack_IconFrame:SetPoint("TOPLEFT","Minimap","TOPLEFT",52-xpos,ypos-52)

end

local function initialize_data()

        -- if EquipSet isn't defined by some other mod, use it as an alias for ItemRack_EquipSet, since macros are limited by space
        EquipSet = EquipSet or ItemRack_EquipSet -- ItemRack_EquipSet
        SaveSet = SaveSet or ItemRack_SaveSet
        LoadSet = LoadSet or ItemRack_LoadSet
        ToggleSet = ToggleSet or ItemRack_ToggleSet
        IsSetEquipped = IsSetEquipped or ItemRack_IsSetEquipped

        -- create new user if one doesn't exist
        if not ItemRack_Users[user] then
                ItemRack_Users[user] = {} -- create new per-user setting
                ItemRack_Users[user].Inv = {} -- nil or 1, whether inv slot visible
                ItemRack_Users[user].Bar = {} -- 1-number of inventory bars on screen at once
                ItemRack_Users[user].Spaces = {} -- nil or true, whether a space should appear after this slot
                ItemRack_Users[user].Ignore = {} -- list of items to ignore on bar
                for i in ItemRackOpt_Defaults do
                        ItemRack_Users[user][i] = ItemRackOpt_Defaults[i]
                end
        end
        -- upgrade old version data
        ItemRack_Settings.ShowEmpty = ItemRack_Settings.ShowEmpty or "OFF" -- 1.1
        ItemRack_Settings.FlipMenu = ItemRack_Settings.FlipMenu or "OFF" -- 1.1
        ItemRack_Settings.RightClick = ItemRack_Settings.RightClick or "OFF" -- 1.1
        ItemRack_Settings.TinyTooltip = ItemRack_Settings.TinyTooltip or "OFF" -- 1.2
        ItemRack_Settings.ShowTooltips = ItemRack_Settings.ShowTooltips or "ON" -- 1.2
        ItemRack_Settings.RotateMenu = ItemRack_Settings.RotateMenu or "OFF" -- 1.3
        ItemRack_Users[user].Spaces = ItemRack_Users[user].Spaces or {} -- 1.3
        ItemRack_Users[user].Sets = ItemRack_Users[user].Sets or {} -- 1.4
        ItemRack_Settings.ShowIcon = ItemRack_Settings.ShowIcon or "ON" -- 1.4
        ItemRack_Settings.DisableToggle = ItemRack_Settings.DisableToggle or "ON" -- 1.4
        ItemRack_Settings.FlipBar = ItemRack_Settings.FlipBar or "OFF" -- 1.5
        ItemRack_Users[user].Ignore = ItemRack_Users[user].Ignore or {} -- 1.5
        ItemRack_Settings.EnableEvents = ItemRack_Settings.EnableEvents or "OFF" -- 1.5
        ItemRack_Users[user].Events = ItemRack_Users[user].Events or {} -- 1.7
        ItemRack_Settings.CompactList = ItemRack_Settings.CompactList or "OFF" -- 1.7
        ItemRack_Settings.NotifyThirty = ItemRack_Settings.NotifyThirty or "OFF" -- 1.7
        ItemRack_Settings.ShowAllEvents = ItemRack_Settings.ShowAllEvents or "OFF" -- 1.7
        ItemRack_Settings.AllowHidden = ItemRack_Settings.AllowHidden or "OFF" -- 1.82
        ItemRack_Settings.LargeFont = ItemRack_Settings.LargeFont or "OFF" -- 1.9
        ItemRack_Settings.SquareMinimap = ItemRack_Settings.SquareMinimap or "OFF" -- 1.9
        ItemRack_Settings.BigCooldown = ItemRack_Settings.BigCooldown or "OFF" -- 1.9
        ItemRack_Settings.SetLabels = ItemRack_Settings.SetLabels or "ON" -- 1.91
        ItemRack_Settings.AutoToggle = ItemRack_Settings.AutoToggle or "OFF" -- 1.91

        local _,class = UnitClass("player")
        if class=="WARRIOR" or class=="ROGUE" or class=="HUNTER" then
                ItemRack.CanWearOneHandOffHand = 1
        end
end

local function initialize_display()

        -- set scale and position to last saved setting
        ItemRack_InvFrame:SetScale(ItemRack_Users[user].MainScale or 1)
        ItemRack_MenuFrame:SetScale(ItemRack_Users[user].MainScale or 1)
        ItemRack_InvFrame:ClearAllPoints()
        really_setpoint(ItemRack_InvFrame,"TOPLEFT","UIParent","BOTTOMLEFT",ItemRack_Users[user].XPos,ItemRack_Users[user].YPos)
        set_lock(ItemRack_Users[user].Locked)
        update_keybindings()

        ItemRackInv0Count:SetText(CharacterAmmoSlotCount:IsShown() and CharacterAmmoSlotCount:GetText() or "")

        for i in ItemRack.OptInfo do
                if ItemRack.OptInfo[i].type=="Check" and getglobal(i) then
                        item = getglobal(i.."Text")
                        item:SetText(ItemRack.OptInfo[i].text)
                        item:SetTextColor(1,1,1)
                        if ItemRack.OptInfo[i].info and ItemRack_Settings[ItemRack.OptInfo[i].info]=="ON" then
                                getglobal(i):SetChecked(1)
                        else
                                getglobal(i):SetChecked(0)
                        end
                end
                if ItemRack.OptInfo[i].type=="Label" then
                        getglobal(i):SetText(ItemRack.OptInfo[i].text)
                end
        end

        if ItemRack_Users[user].Visible=="OFF" then
                ItemRack_InvFrame:Hide()
        end

        move_control() -- docks control buttons on edge of bar
        move_icon() -- moves minimap button

        if ItemRack_Settings.ShowIcon=="OFF" then
                ItemRack_IconFrame:Hide()
        else
                ItemRack_IconFrame:Show()
        end

        make_escable("ItemRack_SetsFrame","add")

        ItemRack_ChangeEventFont()

        for i=1,30 do
                getglobal("ItemRackMenu"..i.."Border"):SetVertexColor(.15,.25,1,1)
                getglobal("ItemRackMenu"..i.."Border"):Hide()
        end

        draw_inv() -- construct the bar
        ItemRack_SetAllCooldownFonts()
        Rack.StartTimer("CooldownUpdate")
end

-- returns true if no swaps are pending
local function all_items_unlocked()

        local i,j,bagged_item_locked,locked
        local bagStart,bagEnd = 0,4

        for i=0,19 do
                locked = locked or IsInventoryItemLocked(i)
        end

        for i=bagStart,bagEnd do
                for j=1,GetContainerNumSlots(i) do
                        _,_,bagged_item_locked = GetContainerItemInfo(i,j)
                        locked = locked or bagged_item_locked
                end
        end

        return not locked
end

-- displays a quick tooltip note under the sets window
local function sets_message(msg)

        local tooltip = ItemRack_Sets_Message

        tooltip:SetOwner(ItemRack_SetsFrame, "ANCHOR_NONE")
        tooltip:SetPoint("TOP", "ItemRack_SetsFrame", "BOTTOM", 0, 0)
        tooltip:AddLine(msg)
        tooltip:Show()
        tooltip:FadeOut()
end


--[[ Frame functions ]]--

function ItemRack_OnLoad()

        -- hook for ALT+click of inventory slots (to add inventory slots to rack)
        oldItemRack_PaperDollItemSlotButton_OnClick = PaperDollItemSlotButton_OnClick
        PaperDollItemSlotButton_OnClick = newItemRack_PaperDollItemSlotButton_OnClick

        -- hook for ALT+click of character model (to add set slot to rack)
        oldItemRack_CharacterModelFrame_OnMouseUp = CharacterModelFrame_OnMouseUp
        CharacterModelFrame_OnMouseUp = newItemRack_CharacterModelFrame_OnMouseUp

        -- hook for mouseover of character sheet item slots (to display menu)
        oldItemRack_PaperDollItemSlotButton_OnEnter = PaperDollItemSlotButton_OnEnter
        PaperDollItemSlotButton_OnEnter = newItemRack_PaperDollItemSlotButton_OnEnter

        -- hook for character sheet hiding (to hide menu)
        oldItemRack_PaperDollFrame_OnHide = PaperDollFrame_OnHide
        PaperDollFrame_OnHide = newItemRack_PaperDollFrame_OnHide

        oldItemRack_UseInventoryItem = UseInventoryItem
        UseInventoryItem = newItemRack_UseInventoryItem

        oldItemRack_UseAction = UseAction
        UseAction = newItemRack_UseAction

        this:RegisterEvent("PLAYER_LOGIN")
end

local function initialize_events(v1)

        local i,j

        ItemRack_DisableAllEvents()

        if v1 then
                ItemRack_Events = {}
                -- go through and remove all old items from sets
                for i in Rack_User do
                        if Rack_User[i].Sets then
                                for j in Rack_User[i].Sets do
                                        for k=0,19 do
                                                if Rack_User[i].Sets[j][k] then
                                                        Rack_User[i].Sets[j][k].old = nil
                                                end
                                        end
                                end
                        end
                end
        end

        -- if the events doesn't have a version or it's an old events version, load defaults
        if not tonumber(ItemRack_Events.events_version) or ItemRack_Events.events_version<current_events_version then
                ItemRack_Events.events_version = current_events_version
                for i in ItemRack_DefaultEvents do
                        ItemRack_Events[i] = {}
                        ItemRack_Events[i].trigger = ItemRack_DefaultEvents[i].trigger
                        ItemRack_Events[i].delay = ItemRack_DefaultEvents[i].delay
                        ItemRack_Events[i].script = ItemRack_DefaultEvents[i].script
                end
        end

        -- if an event is removed, remove the associated sets from the user
        for i in ItemRack_Users do
                if ItemRack_Users[i].Events then
                        for j in ItemRack_Users[i].Events do
                                if not ItemRack_Events[j] then
                                        ItemRack_Users[i].Events[j] = nil
                                end
                        end
                end
        end

        if TITAN_RIDER_ID and (not TitanRider_EquipToggle or (TitanGetVar and TitanGetVar(TITAN_RIDER_ID,"EquipItems"))) then
                if ItemRack_Users[user].Events["Mount"] and ItemRack_Users[user].Events["Mount"].enabled then
                        ItemRack_Users[user].Events["Mount"] = nil
                end
        end

        ItemRack_Events_ScrollFrameScrollBar:SetValue(0)
        ItemRack_Build_eventList()
        ItemRack_EnableAllEvents()
end

function ItemRack_OnEvent(event)

        if event=="UNIT_INVENTORY_CHANGED" then
                if arg1=="player" then
                        Rack.StartTimer("InvUpdate")
                end
        elseif event=="PLAYER_AURAS_CHANGED" then
                ItemRack_BuffsChanged()

        elseif event=="UPDATE_BINDINGS" then
                update_keybindings()

        elseif event=="BANKFRAME_OPENED" then
                Rack.BankOpened()

        elseif event=="BANKFRAME_CLOSED" then
                Rack.BankClosed()

        elseif event=="BAG_UPDATE" then -- only enabled when bank is open
                Rack.PopulateBank()
                if ItemRack_MenuFrame:IsVisible() and ItemRack.InvOpen then
                        ItemRack_BuildMenu(ItemRack.InvOpen,ItemRack.MenuDockedTo)
                        if ItemRack.InvOpen==20 then
                                local id = GetMouseFocus() and GetMouseFocus():GetID() or ""
                                local menuItem = ItemRack.BaggedItems[id or ""]
                                if menuItem and menuItem.name and Rack_User[user].Sets[menuItem.name] then
                                        ItemRack_Sets_Tooltip(menuItem.name,1)
                                end
                        end
                end

        elseif event=="PLAYER_LOGIN" then

                user = UnitName("player").." of "..GetRealmName()

                SlashCmdList["ItemRackCOMMAND"] = ItemRack_SlashHandler
                SLASH_ItemRackCOMMAND1 = "/itemrack"

                Rack.Initialize()

                initialize_data()
                initialize_events()

                ItemRack_InitializeKeyBindings() -- create key bindings for this user
                initialize_display()

                this:RegisterEvent("UNIT_INVENTORY_CHANGED")
                this:RegisterEvent("UPDATE_BINDINGS")

                RackFrame:RegisterEvent("PLAYER_REGEN_ENABLED") -- leaving combat
                RackFrame:RegisterEvent("PLAYER_UNGHOST") -- leaving ghost
                RackFrame:RegisterEvent("PLAYER_ALIVE") -- leaving death

                this:RegisterEvent("BANKFRAME_OPENED")
                this:RegisterEvent("BANKFRAME_CLOSED")

                if not ItemRack_Settings.Minimap then
                        ItemRack_Settings.Minimap = {}
                end
        end
end

function ItemRack_SlashHandler(arg1)

        if arg1 and string.find(arg1,"equip") then
                local _,_,set = string.find(arg1,"equip (.+)")
                if not set then
                        DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00Usage: /itemrack equip (set name)")
                        DEFAULT_CHAT_FRAME:AddMessage("ie, /itemrack equip pvp gear")
                else
                        ItemRack_EquipSet(set)
                end
                return
        elseif arg1 and string.find(arg1,"toggle") then
                local _,_,set = string.find(arg1,"toggle (.+)")
                if not set then
                        DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00Usage: /itemrack toggle (set name)")
                        DEFAULT_CHAT_FRAME:AddMessage("ie, /itemrack toggle pvp gear")
                else
                        ItemRack_ToggleSet(set)
                end
                return
        end

        arg1 = string.lower(arg1)

        if not string.find(arg1,".+") then
                ItemRack_Toggle()
        elseif arg1=="reset everything" then
                StaticPopupDialogs["ITEMRACKRESET"] = {
                        text = "Are you sure you want to wipe all ItemRack sets, events and settings?\nThis will restore to default and then Reload the UI.",
                        button1 = "Yes", button2 = "No", showAlert=1, timeout = 0, whileDead = 1,
                        OnAccept = function() ItemRack_Users=nil ItemRack_Events=nil ItemRack_Settings=nil Rack_User=nil ReloadUI() end,
                }
                StaticPopup_Show("ITEMRACKRESET")
        elseif string.find(arg1,"^reset") then
                if string.find(arg1,"reset event") then
                        -- /itemrack reset event will initialize events
                        initialize_events("reset")
                        ItemRack_Events_ScrollFrameScrollBar:SetValue(0)
                        if ItemRack_SetsFrame:IsVisible() then
                                sets_message("Default events reset.")
                        end
                else
                        -- /itemrack reset will reset the bar
                        ItemRack_Reset()
                end
        elseif arg1=="lock" then
                set_lock("ON")
        elseif arg1=="unlock" then
                set_lock("OFF")
        elseif string.find(arg1,"scale") then
                local _,_,newscale = string.find(arg1,"scale (.+)")
                if not tonumber(newscale) then
                        DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00Usage: /itemrack scale (number)")
                        DEFAULT_CHAT_FRAME:AddMessage("ie, /itemrack scale 0.85")
                else
                        ItemRack.FrameToScale = ItemRack_InvFrame
                        ItemRack_ScaleFrame(newscale)
                        ItemRack_Users[user].MainScale = ItemRack_InvFrame:GetScale()
                        cooldowns_need_updating()
                end
        elseif string.find(arg1,"^opt") then
                ItemRack_Sets_Toggle()
        elseif arg1=="debug" then
                ItemRack_ListEvents()
                DEFAULT_CHAT_FRAME:AddMessage("Events version: "..tostring(ItemRack_Events.events_version))
                DEFAULT_CHAT_FRAME:AddMessage("ItemRack version: "..tostring(ItemRack_Version))
        else
                DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00ItemRack usage:")
                DEFAULT_CHAT_FRAME:AddMessage("/itemrack : toggle the window")
                DEFAULT_CHAT_FRAME:AddMessage("/itemrack reset : reset window")
                DEFAULT_CHAT_FRAME:AddMessage("/itemrack lock or unlock : toggles window lock")
                DEFAULT_CHAT_FRAME:AddMessage("/itemrack scale (number) : sets an exact scale")
                DEFAULT_CHAT_FRAME:AddMessage("/itemrack equip (set name) : equips a set")
                DEFAULT_CHAT_FRAME:AddMessage("/itemrack toggle (set name) : equips/unequips set")
                DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00Alt+click item slots in the character window to add/remove items.\nWhile locked, hold ALT over the window to access control buttons.")
        end
end

--[[ Main bar add/remove functions ]]--

function ItemRack_Reset()

        local i
        -- restore user settings to default (but keep bar contents)
        for i in ItemRackOpt_Defaults do
                ItemRack_Users[user][i] = ItemRackOpt_Defaults[i]
        end
        initialize_data()
        initialize_display()
        initialize_events()
        ItemRack_SetsFrame:ClearAllPoints()
        ItemRack_SetsFrame:SetPoint("CENTER","UIParent","CENTER")
end

local function remove_inv(id)

        local i

        getglobal("ItemRackInv"..id):Hide()
        ItemRack_Users[user].Inv[id] = nil
        for i=1,table.getn(ItemRack_Users[user].Bar) do
                if ItemRack_Users[user].Bar[i]==id then
                        table.remove(ItemRack_Users[user].Bar,i)
                end
        end
        draw_inv()
end

--[[ Hooked functions ]]--

-- if action is currently equipped, then reflect its use to the mod
function newItemRack_UseAction(slot,checkCursor,onSelf)

        if IsEquippedAction(slot) and cursor_empty() then
                Rack_TooltipScan:SetAction(slot)
                local usedName = Rack_TooltipScanTextLeft1:GetText()

                local i,name,foundSlot

                -- look for a worn item with same name as clicked item
                for i=1,20 do
                        Rack_TooltipScan:SetInventoryItem("player",i)
                        name = Rack_TooltipScanTextLeft1:GetText()
                        if name==usedName then
                                foundSlot = i -- found this item in slot i
                                i = 21 -- whimpy break
                        end
                end

                if foundSlot and GetActionCooldown(slot)==0 then
                        ItemRack_ReactUseInventoryItem(foundSlot)
                end

        end

        oldItemRack_UseAction(slot,checkCursor,onSelf)

end

-- Inv slots are added by ALT+clicking the paper doll
function newItemRack_PaperDollItemSlotButton_OnClick(button, ignoreShift)

        if IsAltKeyDown() then
                local item = string.gsub(tostring(this:GetName()),"Character","")
                local id,texture = GetInventorySlotInfo(item)

                if ItemRack_Users[user].Inv[id] then
                        remove_inv(id)
                else
                        ItemRack_Users[user].Visible="ON"
                        ItemRack_Users[user].Inv[id] = 1
                        getglobal("ItemRackInv"..id.."Icon"):SetTexture(texture)
                        table.insert(ItemRack_Users[user].Bar,id)
                        draw_inv()
                end
        else
                oldItemRack_PaperDollItemSlotButton_OnClick(button, ignoreShift)
        end
        
end

-- set slot is added by ALT+clicking the paper doll character model
function newItemRack_CharacterModelFrame_OnMouseUp(button)

        if IsAltKeyDown() then
                if ItemRack_Users[user].Inv[20] then
                        remove_inv(20)
                else
                        ItemRack_Users[user].Visible="ON"
                        ItemRack_Users[user].Inv[20]=1
                        table.insert(ItemRack_Users[user].Bar,20)
                        draw_inv()
                end
        else
                oldItemRack_CharacterModelFrame_OnMouseUp(button)
        end

end

function newItemRack_PaperDollItemSlotButton_OnEnter()

        local id,i = this:GetName()

        oldItemRack_PaperDollItemSlotButton_OnEnter()

        if IsAltKeyDown() then
                for i=0,19 do
                        if ItemRack.Indexes[i].paperdoll_slot==id then
                                id = i
                                i = 20 -- whimpy break
                        end
                end

                if tonumber(id) and not InRepairMode() then
                        ItemRack_BuildMenu(id,"CHARACTERSHEET")
                end
        end
end

function newItemRack_PaperDollFrame_OnHide()

        if ItemRack.MenuDockedTo=="CHARACTERSHEET" then
                ItemRack_MenuFrame:Hide()
        end

        oldItemRack_PaperDollFrame_OnHide()
end

--[[ Inv Movement ]]--

function ItemRack_InvFrame_OnMouseDown(arg1)

        if arg1=="LeftButton" and ItemRack_Users[user].Locked=="OFF" then
                this:StartMoving()
        end
end

function ItemRack_InvFrame_OnMouseUp(arg1)

        if arg1=="LeftButton" then
                this:StopMovingOrSizing()
                ItemRack_Users[user].XPos = ItemRack_InvFrame:GetLeft()
                ItemRack_Users[user].YPos = ItemRack_InvFrame:GetTop()
                cooldowns_need_updating()
        end
end

function ItemRack_Toggle()
        if ItemRack_InvFrame:IsVisible() then
                ItemRack_Users[user].Visible="OFF"
                ItemRack_InvFrame:Hide()
        else
                ItemRack_Users[user].Visible="ON"
                ItemRack_InvFrame:Show()
        end
end

--[[ Inventory changes ]]--

-- any inventory changes will cause this OnUpdate to start counting.  After InvUpdateLimit, it processes the inventory change
function ItemRack_InvUpdate_OnUpdate()

        if SpellIsTargeting() then
                -- if we're in disenchant/enchant/applying poison/sharpening stone/etc, check back later. do nothing for now
                Rack.StartTimer("InvUpdate",1)
        else

                local i,j
                local bagStart,bagEnd = 0,4

                Rack.StopTimer("InvUpdate")
                ItemRack.Swapping = false

                draw_inv()

                if ItemRack_Users[user].Inv[0] then
                        -- update ammo count
                        ItemRackInv0Count:SetText(CharacterAmmoSlotCount:IsShown() and CharacterAmmoSlotCount:GetText() or "")
                end

                if table.getn(ItemRack_Users[user].Bar)>0 then
                        for i=1,table.getn(ItemRack_Users[user].Bar) do
                                getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i]):SetChecked(0)
                                SetDesaturation(getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i].."Icon"),nil)
                        end
                end

                -- if set builder is up, change the inventory to reflect the change
                if ItemRack_SetsFrame:IsVisible() then
                        for i=0,19 do
                                SetDesaturation(getglobal("ItemRack_Sets_Inv"..i.."Icon"),nil)
                        end
                        ItemRack_Sets_UpdateInventory()
                end

                if ItemRack.InvOpen then
                        if ItemRack_Settings.RightClick=="ON" and ItemRack.InvOpen==13 then
                                ItemRack.InvOpen = 14
                        end
                        ItemRack_BuildMenu(ItemRack.InvOpen)
                end

        end
end

--[[ Scaling ]]--

function ItemRack_StartScaling(arg1)
        if arg1=="LeftButton" and unlocked() then
                this:LockHighlight()
                ItemRack.FrameToScale = this:GetParent()
                ItemRack.ScalingWidth = this:GetParent():GetWidth()
                ItemRack_MenuFrame:Hide()
                Rack.StartTimer("ScaleUpdate")
        end
end

function ItemRack_StopScaling(arg1)
        if arg1=="LeftButton" then
                Rack.StopTimer("ScaleUpdate")
                ItemRack.FrameToScale = nil
                cooldowns_need_updating()
                this:UnlockHighlight()
                if this:GetParent():GetName() == "ItemRack_InvFrame" then
                        ItemRack_Users[user].MainScale = ItemRack_InvFrame:GetScale()
                end
        end
end

function ItemRack_ScaleFrame(scale)
        local frame = ItemRack.FrameToScale
        local oldscale = frame:GetScale() or 1
        local framex = (frame:GetLeft() or ItemRack_Users[user].XPos)* oldscale
        local framey = (frame:GetTop() or ItemRack_Users[user].YPos)* oldscale

        frame:SetScale(scale)
        really_setpoint(ItemRack_InvFrame,"TOPLEFT","UIParent","BOTTOMLEFT",framex/scale,framey/scale)
end

--[[ Clicks ]]--

-- uses inventory slot v1 (0-19) can be called from key binding and slot doesn't need to be on the bar
function ItemRack_UseItem(v1)

        if SpellIsTargeting() and v1~=0 then -- if poison or sharpening stone being applied (and not an ammo slot)
                PickupInventoryItem(v1)
        elseif v1 and not MerchantFrame:IsVisible() then
                UseInventoryItem(v1)
        end
end

function ItemRack_ReactUseInventoryItem(slot)

        if ItemRack_Users[user].Inv[slot] then
                getglobal("ItemRackInv"..slot):SetChecked(1)
        end
        Rack.StartTimer("InvUpdate",1.5) -- extra long wait

        _,_,item = string.find(GetInventoryItemLink("player",slot) or "","^.*%[(.*)%].*$")
        if ItemRack_Settings.Notify=="ON" and item then
                ItemRack.NotifyList[item] = { bag=nil, slot=nil, inv=slot }
        end
        if ItemRack_Settings.EnableEvents and item then
                local holdarg1,holdarg2 = arg1,arg2
                arg1 = item
                arg2 = slot
                ItemRack_RegisterFrame_OnEvent("ITEMRACK_ITEMUSED")
                arg1 = holdarg1
                arg2 = holdarg2
        end
end

-- hook for UseInventoryItem
function newItemRack_UseInventoryItem(slot)
        local cooldown = GetInventoryItemCooldown("player",slot)
        oldItemRack_UseInventoryItem(slot) -- call original UseInventoryItem
        if cooldown==0 then
                ItemRack_ReactUseInventoryItem(slot) -- tell the mod an item was used
        end
end

function ItemRack_Inv_OnClick(arg1)

        local id = this:GetID()

        this:SetChecked(0)

        if id==20 and not IsAltKeyDown() then
                if arg1=="RightButton" then
                        ItemRack_Sets_Toggle(2)
                else
                        local setname = Rack.CurrentSet()
                        if setname and Rack_User[user].Sets[setname] then
                                if IsShiftKeyDown() then
                                        Rack.UnequipSet(setname)
                                else
                                        ItemRack_EquipSet(setname)
                                end
                        end
                end
        elseif arg1=="RightButton" and IsAltKeyDown() and ItemRack_Users[user].Locked=="OFF" then
                -- toggle space after item
                ItemRack_Users[user].Spaces[id] = not ItemRack_Users[user].Spaces[id]
                draw_inv()
        elseif arg1=="LeftButton" and IsAltKeyDown() and ItemRack_Users[user].Locked=="OFF" then
                -- if Alt is down, remove the item from the bar
                if ItemRack_Users[user].Inv[id] then
                        remove_inv(id)
                        ItemRack_MenuFrame:Hide()
                end
        elseif arg1=="LeftButton" and IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then
                -- if Shift is down, link the item to chat
                ChatFrameEditBox:Insert(GetInventoryItemLink("player",id))
        else
                -- otherwise use the item
                ItemRack_UseItem(id)
        end
end

--[[ Menu ]]--

function ItemRack_Inv_OnEnter()
        local id = this:GetID()

        ItemRack_Inv_Tooltip()

        ItemRack.MenuDockedTo = nil
        if not Rack.TimerEnabled("ScaleUpdate") then
                if IsShiftKeyDown() or ItemRack_Settings.MenuShift=="OFF" then
                        if ItemRack_Settings.RightClick=="ON" and (id==13 or id==14) and ItemRack.TrinketsPaired then
                                if ItemRack_Users[user].MainOrient=="HORIZONTAL" and corner_info("LEFTRIGHT")=="LEFT" then
                                        id = 13
                                elseif ItemRack_Users[user].MainOrient=="HORIZONTAL" and corner_info("LEFTRIGHT")=="RIGHT" then
                                        id = 14
                                elseif ItemRack_Users[user].MainOrient=="VERTICAL" and corner_info("TOPBOTTOM")=="TOP" then
                                        id = 13
                                else
                                        id = 14
                                end
                        end
                        ItemRack_BuildMenu(id)
                end
                if IsAltKeyDown() then
                        ItemRack_ControlFrame:Show()
                        Rack.StartTimer("ControlFrame")
                end
        end
end

function ItemRack_Menu_OnClick(arg1)

        local id,i,unqueue = this:GetID()
        local name = ItemRack.BaggedItems[id].name

        this:SetChecked(0)

        if SpellIsTargeting() or CursorHasItem() then return end -- prohibit swaps while in spell target/disenchant mode

        if ItemRack.BankIsOpen then
                if ItemRack.InvOpen~=20 then
                        Rack.ClearLockList()
                        local bag,slot
                        if ItemRack.BankedItems[name] then
                                bag,slot = Rack.FindSpace()
                                if bag then
                                        PickupContainerItem(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
                                        PickupContainerItem(bag,slot)
                                else
                                        Rack.NoMoreRoom()
                                end
                        else
                                bag,slot = Rack.FindSpace(1)
                                if bag then
                                        PickupContainerItem(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
                                        PickupContainerItem(bag,slot)
                                else
                                        Rack.NoMoreRoom()
                                end
                                -- *** swap from bag to bank
                        end
                else
                        if Rack.SetHasBanked(name) then
                                Rack.PullSetFromBank(name)
                        else
                                Rack.PushSetToBank(name)
                        end
                end
                return
        end

        if ItemRack_Settings.RightClick=="ON" and arg1=="LeftButton" and ItemRack.InvOpen==14 then
                ItemRack.InvOpen = 13
        elseif ItemRack_Settings.RightClick=="ON" and arg1=="RightButton" and ItemRack.InvOpen==13 then
                ItemRack.InvOpen = 14
        end

        if (ItemRack_Settings.AllowHidden=="ON" or ItemRack.InvOpen==20) and IsAltKeyDown() and ItemRack.MenuDockedTo~="CHARACTERSHEET" then

                if ItemRack.InvOpen==20 then
                        -- sets ignore flag is with the set
                        if Rack_User[user].Sets[name].hide then
                                Rack_User[user].Sets[name].hide = nil
                        else
                                Rack_User[user].Sets[name].hide = 1
                        end
                elseif not ItemRack.BaggedItems[id].bag then
                        -- empty slot, do nothing
                elseif ItemRack_Users[user].Ignore[ItemRack.BaggedItems[id].name] then
                        ItemRack_Users[user].Ignore[ItemRack.BaggedItems[id].name] = nil
                else
                        ItemRack_Users[user].Ignore[ItemRack.BaggedItems[id].name] = 1
                end
                ItemRack_BuildMenu(ItemRack.InvOpen,ItemRack.MenuDockedTo)

        elseif arg1=="LeftButton" and IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then
                -- if linking a menu item with shift+left click
                ChatFrameEditBox:Insert(GetContainerItemLink(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot))

        elseif ItemRack.InvOpen==20 then
                -- if selecting a set menu item
                if ItemRack.BaggedItems[id].name then
                        if (not UnitAffectingCombat("player") and not Rack.IsPlayerReallyDead()) and (IsShiftKeyDown() or ItemRack_Settings.AutoToggle=="ON") then
                                -- toggle set if shift key is down or AutoToggle on
                                ItemRack_ToggleSet(ItemRack.BaggedItems[id].name)
                        else -- otherwise equip it
                                ItemRack_EquipSet(ItemRack.BaggedItems[id].name)
                        end
                        ItemRack_MenuFrame:Hide()
                        Rack.StartTimer("InvUpdate",1) -- extra long wait, force an update, set may not swap
                end

        elseif (UnitAffectingCombat("player") and not ItemRack.Indexes[ItemRack.InvOpen].swappable) or Rack.IsPlayerReallyDead() then
                -- if selecting a menu item while dead
                if ItemRack.BaggedItems[id].name=="(empty)" then
                        Rack.AddToCombatQueue(ItemRack.InvOpen,0)
                else
                        local _,itemID = Rack.GetItemInfo(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
                        Rack.AddToCombatQueue(ItemRack.InvOpen,itemID)
                end
                ItemRack_MenuFrame:Hide()

        elseif ItemRack.InvOpen and cursor_empty() and not SpellIsTargeting() then
                -- if this is a normal swap of a single item out of combat and the cursor is free/not doing anything

                ItemRack.Swapping = true
                if ItemRack.BaggedItems[id].bag then
                        -- find out if incoming item is two-hand or offhand that may leave a loose item in bags for later move
                        local _,_,equipslot = get_item_info(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
                        if equipslot=="INVTYPE_2HWEAPON" then
                                if GetInventoryItemLink("player",17) then
                                        -- something is in offhand slot, move it out
                                        Rack.ClearLockList()
                                        bag,slot = Rack.FindSpace()
                                        if not bag then
                                                UIErrorsFrame:AddMessage(ERR_INV_FULL,1,.1,.1,1,UIERRORS_HOLD_TIME)
                                        else
                                                PickupInventoryItem(17)
                                                PickupContainerItem(bag,slot)
                                        end
                                end
                        end
                        PickupContainerItem(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
                        PickupInventoryItem(ItemRack.InvOpen)
                else
                        local j,bag,slot
                        -- swapping to an empty slot, create freespace
                        Rack.ClearLockList()
                        bag,slot = Rack.FindSpace()
                        if not bag then
                                UIErrorsFrame:AddMessage(ERR_INV_FULL,1,.1,.1,1,UIERRORS_HOLD_TIME)
                        else
                                PickupInventoryItem(ItemRack.InvOpen)
                                PickupContainerItem(bag,slot)
                        end
                end
                SetDesaturation(getglobal("ItemRackInv"..ItemRack.InvOpen.."Icon"),1)

                if ItemRack_SetsFrame:IsVisible() then
                        SetDesaturation(getglobal("ItemRack_Sets_Inv"..ItemRack.InvOpen.."Icon"),1)
                end

                if not IsShiftKeyDown() or ItemRack_Settings.RightClick=="OFF" then
                        ItemRack_MenuFrame:Hide()
                end
                Rack.StartTimer("InvUpdate",1.25)

        end
end

function ItemRack_MenuFrame_OnShow()
        Rack.StartTimer("MenuFrame")
end

function ItemRack_MenuFrame_OnHide()

        Rack.StopTimer("MenuFrame")
        local i
        for i=0,20 do
                getglobal("ItemRackInv"..i):UnlockHighlight()
        end
        ItemRack.InvOpen = nil
end

--[[ Tooltips ]]--

function ItemRack_Inv_Tooltip()

        local id = this:GetID()

        if Rack.TimerEnabled("ScaleUpdate") or ItemRack_Settings.ShowTooltips=="OFF" then
                return
        end

        if id==20 then
                -- if mouseover set on bar, display current set tooltip
                ItemRack_Sets_Tooltip(Rack.CurrentSet())
        else
                -- otherwise set up tooltip for an inventory item
                ItemRack.TooltipOwner = this
                ItemRack.TooltipType = "INVENTORY"
                ItemRack.TooltipSlot = id

                ItemRack.TooltipBag = Rack.GetNameByID(Rack.CombatQueue[id])

                Rack.StartTimer("TooltipUpdate",0)
        end
end

function ItemRack_Menu_Tooltip()

        local id = this:GetID()

        if Rack.TimerEnabled("ScaleUpdate") or ItemRack_Settings.ShowTooltips=="OFF" then
                return
        end

        if ItemRack.InvOpen==20 then
                -- if a sets menu, display set tooltip
                ItemRack_Sets_Tooltip(ItemRack.BaggedItems[id].name)

        elseif ItemRack.BaggedItems[id].bag then
                -- otherwise set up tooltip for a bagged item
                ItemRack.TooltipOwner = this
                ItemRack.TooltipType = "BAG"
                ItemRack.TooltipBag = ItemRack.BaggedItems[id].bag
                ItemRack.TooltipSlot = ItemRack.BaggedItems[id].slot

                Rack.StartTimer("TooltipUpdate",0)
        end
end

function ItemRack_ClearTooltip()

        GameTooltip:Hide()
        ItemRack.TooltipType = nil
        Rack.StopTimer("TooltipUpdate")
        if not ItemRack.InvOpen then
                ItemRack_MenuFrame_OnHide()
        end
end

local function set_tooltip_anchor(owner)

        if ItemRack.MenuDockedTo=="CHARACTERSHEET" and ItemRack.InvOpen then
                -- if this is a tooltip of an item docked to character sheet, anchor it to the paperdoll_slot
                GameTooltip:SetOwner(owner,"ANCHOR_RIGHT")
        elseif ItemRack_Settings.TooltipFollow=="ON" then
                if (owner:GetLeft() or 0)<400 then
                        GameTooltip:SetOwner(owner,"ANCHOR_RIGHT")
                else
                        GameTooltip:SetOwner(owner,"ANCHOR_LEFT")
                end
        else
                GameTooltip_SetDefaultAnchor(GameTooltip,UIParent)
        end
end

-- takes the currently-built tooltip and rebuilds with just name, durability and cooldown
local function shrink_tooltip()

        local nameline_line,durability_line,cooldown_line,tooltip_line,item_color

        name_line = GameTooltipTextLeft1:GetText()

        if name_line then

                for i=2,30 do
                        tooltip_line = getglobal("GameTooltipTextLeft"..i):GetText() or ""
                        if string.find(tooltip_line,durability_pattern) then
                                durability_line = tooltip_line
                        elseif string.find(tooltip_line,COOLDOWN_REMAINING) then
                                cooldown_line = "|cFFFFFFFF"..tooltip_line
                        end
                end

                if ItemRack.TooltipType=="BAG" then
                        item_color = string.sub(GetContainerItemLink(ItemRack.TooltipBag,ItemRack.TooltipSlot) or "",1,10) or ""
                else
                        item_color = string.sub(GetInventoryItemLink("player",ItemRack.TooltipSlot) or "",1,10) or ""
                end

                set_tooltip_anchor(ItemRack.TooltipOwner)
                GameTooltip:ClearLines()
                GameTooltip:AddLine(item_color..name_line)
                GameTooltip:AddLine(durability_line)
                GameTooltip:AddLine(cooldown_line)
        end
end

--[[ Cooldowns ]]--

local function format_time(seconds)

        if seconds<60 then
                return math.floor(seconds+.5)..((ItemRack_Settings.BigCooldown=="ON") and "" or " s")
        else
                if seconds < 3600 then 
                        return math.ceil((seconds/60)).." m"
                else
                        return math.ceil((seconds/3600)).." h"
                end
        end

end

local function write_cooldown(where,start,duration)

        local cooldown = duration - (ItemRack.CurrentTime - start)

        if start==0 then
                where:SetText("")
        elseif cooldown<3 and not where:GetText() then
                -- this is a global cooldown. don't display it. not accurate but at least not annoying
        else
                where:SetText(format_time(cooldown))
        end
end

local function notify(v1)

        local text = string.format((ItemRack_Settings.NotifyThirty=="OFF") and ItemRackText.READY or ItemRackText.READYTHIRTY,v1 or "")

        if v1 then
                PlaySound("GnomeExploration")
                if SCT_Display then
                        -- send via SCT if it exists
                        SCT_Display(text,{r=.2,g=.7,b=.9})
                elseif SHOW_COMBAT_TEXT=="1" then
                        CombatText_AddMessage(text, CombatText_StandardScroll, .2, .7, .9) -- or default UI's SCT
                else
                        -- send vis UIErrorsFrame if SCT doesn't exit
                        UIErrorsFrame:AddMessage(text,.2,.7,.9,1,UIERRORS_HOLD_TIME)
                end
                DEFAULT_CHAT_FRAME:AddMessage("|cff33b2e5"..text)
                if ItemRack_Settings.EnableEvents then
                        local holdarg1= arg1
                        arg1 = v1
                        ItemRack_RegisterFrame_OnEvent("ITEMRACK_NOTIFY")
                        arg1 = holdarg1
                end
        end
end

-- populate ItemRack.NotifyList[v1] with the location of item named 'v1'
local function notify_find_item(v1)

        local found_inv,found_bag,found_slot = Rack.FindItem(nil,v1,"passive")

        if found_inv then
                ItemRack.NotifyList[v1].inv = found_inv
                ItemRack.NotifyList[v1].bag = nil
                ItemRack.NotifyList[v1].slot = nil
        elseif found_bag then
                ItemRack.NotifyList[v1].inv = nil
                ItemRack.NotifyList[v1].bag = found_bag
                ItemRack.NotifyList[v1].slot = found_slot
        else
                ItemRack.NotifyList[v1] = nil
        end
end

function ItemRack_CooldownUpdate_OnUpdate()

        if ItemRack.CooldownsNeedUpdating then
                ItemRack.CooldownsNeedUpdating = false
                update_inv_cooldowns()
        end

        if ItemRack_Settings.CooldownNumbers=="ON" then

                local i,start,duration
                ItemRack.CurrentTime = GetTime()

                if ItemRack_InvFrame:IsVisible() then
                        for i=1,table.getn(ItemRack_Users[user].Bar) do
                                start, duration = GetInventoryItemCooldown("player",ItemRack_Users[user].Bar[i])
                                write_cooldown(getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i].."Time"),start,duration)
                        end
                end

                if ItemRack_MenuFrame:IsVisible() and ItemRack.InvOpen then
                        for i=1,ItemRack.NumberOfItems do
                                if ItemRack.BaggedItems[i].bag then
                                        start, duration = GetContainerItemCooldown(ItemRack.BaggedItems[i].bag,ItemRack.BaggedItems[i].slot)
                                        write_cooldown(getglobal("ItemRackMenu"..i.."Time"),start,duration)
                                end
                        end
                end

        end

        if ItemRack_Settings.Notify=="ON" then

                local i,name,start,duration,cooldown
                ItemRack.CurrentTime = GetTime()

                -- go down notify list and check up on each item used
                for i in ItemRack.NotifyList do
                        if ItemRack.NotifyList[i].inv then
                                _,_,name=string.find(GetInventoryItemLink("player",ItemRack.NotifyList[i].inv) or "","^.*%[(.*)%].*$")
                        else
                                _,_,name=string.find(GetContainerItemLink(ItemRack.NotifyList[i].bag,ItemRack.NotifyList[i].slot) or "","^.*%[(.*)%].*$")
                        end
                        if i ~= name then
                                notify_find_item(i) -- item has moved, go find it!
                        end

                        if ItemRack.NotifyList[i] then -- if item still on person (wasn't banked)
                                if ItemRack.NotifyList[i].inv then -- if it has an inventory spot
                                        start, duration = GetInventoryItemCooldown("player",ItemRack.NotifyList[i].inv)
                                else -- otherwise it's in a bag
                                        start, duration = GetContainerItemCooldown(ItemRack.NotifyList[i].bag,ItemRack.NotifyList[i].slot)
                                end
                                cooldown = (start==0) and 0 or (duration - (ItemRack.CurrentTime - start))
                                        if cooldown>3 then
                                        ItemRack.NotifyList[i].hadcooldown = true
                                end
                                        if ItemRack.NotifyList[i].hadcooldown and (cooldown==0 or (ItemRack_Settings.NotifyThirty=="ON" and cooldown<30)) then
                                        notify(i)
                                        ItemRack.NotifyList[i] = nil
                                end
                        end
                end
        end

end

function ItemRack_CooldownUpdate_OnHide()

        local i

        for i=0,19 do
                getglobal("ItemRackInv"..i.."Time"):SetText("")
        end
        for i=1,30 do
                getglobal("ItemRackMenu"..i.."Time"):SetText("")
        end
end

-- changes the cooldown number on button named "button" to big/small depending on .BigCooldown
function ItemRack_SetCooldownFont(button)

        local item = getglobal(button.."Time")

        if ItemRack_Settings.BigCooldown=="ON" then
                item:SetFont("Fonts\\FRIZQT__.TTF",16,"OUTLINE")
                item:SetTextColor(1,.82,0,1)
                item:ClearAllPoints()
                item:SetPoint("CENTER",button,"CENTER")
        else
                item:SetFont("Fonts\\ARIALN.TTF",14,"OUTLINE")
                item:SetTextColor(1,1,1,1)
                item:ClearAllPoints()
                item:SetPoint("BOTTOM",button,"BOTTOM")
        end
end

-- changes all cooldown fonts
function ItemRack_SetAllCooldownFonts()

        for i=0,20 do
                ItemRack_SetCooldownFont("ItemRackInv"..i)
        end
        for i=1,30 do
                ItemRack_SetCooldownFont("ItemRackMenu"..i)
        end
end

--[[ Options ]]--

function ItemRack_OnTooltip(v1,v2)

        if ItemRack_Settings.ShowTooltips=="ON" then
                set_tooltip_anchor(this)
                GameTooltip:AddLine(v1)
                GameTooltip:AddLine(v2,.8,.8,.8,1)
                GameTooltip:Show()
        end
end

function ItemRack_Opt_OnEnter()

        local id = this:GetName()

        if ItemRack.OptInfo[id] and not Rack.TimerEnabled("ScaleUpdate") then
                ItemRack_OnTooltip(ItemRack.OptInfo[id].text,ItemRack.OptInfo[id].tooltip)
        end
end

function ItemRack_Control_OnClick()

        local id = this:GetName()

        if id=="ItemRack_Control_Rotate" and ItemRack_Users[user].Locked=="OFF" then
                -- rotate the window
                ItemRack_Users[user].MainOrient = ItemRack_Users[user].MainOrient=="HORIZONTAL" and "VERTICAL" or "HORIZONTAL"
                ItemRack_MenuFrame:Hide()
                draw_inv()
                move_control()
        elseif id=="ItemRack_Control_Lock" or id=="ItemRack_Sets_Lock" then
                -- lock/unlock the window
                ItemRack_Users[user].Locked = ItemRack_Users[user].Locked=="ON" and "OFF" or "ON"
                set_lock(ItemRack_Users[user].Locked)
        elseif id=="ItemRack_Control_Options" then
                ItemRack_Sets_Toggle()
        end
end

function ItemRack_Opt_OnClick(overrideID)

        local id = overrideID or this:GetName()

        if ItemRack.OptInfo[id] and ItemRack.OptInfo[id].info then
                local info = ItemRack.OptInfo[id].info

                if this:GetChecked() then
                        ItemRack_Settings[info] = "ON"
                        PlaySound("igMainMenuOptionCheckBoxOn")
                else
                        ItemRack_Settings[info] = "OFF"
                        PlaySound("igMainMenuOptionCheckBoxOff")
                end

                if id=="ItemRack_Opt_Bindings" then
                        update_keybindings()
                elseif id=="ItemRack_Opt_CooldownNumbers" then
                        ItemRack_CooldownUpdate_OnHide()
                        Rack.StartTimer("CooldownUpdate",0)
                elseif id=="ItemRack_Opt_RightClick" then
                        draw_inv()
                elseif id=="ItemRack_Opt_ShowIcon" then
                        if ItemRack_Settings[info]=="ON" then
                                ItemRack_IconFrame:Show()
                        else
                                ItemRack_IconFrame:Hide()
                        end
                elseif id=="ItemRack_Opt_FlipBar" then
                        move_control()
                        draw_inv()
                elseif id=="ItemRack_Opt_CompactList" then
                        ItemRack_Sets_SavedScrollFrameScrollBar:SetValue(0)
                        ItemRack_Sets_SavedScrollFrame_Update()
                elseif id=="ItemRack_Opt_EnableEvents" then
                        sets_message((ItemRack_Settings.EnableEvents=="ON") and "Events enabled" or "Events disabled")
                elseif id=="ItemRack_Opt_DisableToggle" then
                        draw_minimap_icon()
                elseif id=="ItemRack_Opt_ShowAllEvents" then
                        ItemRack_Events_ScrollFrameScrollBar:SetValue(0)
                        ItemRack_Build_eventList()
                elseif id=="ItemRack_Opt_LargeFont" then
                        ItemRack_ChangeEventFont()
                elseif id=="ItemRack_Opt_SquareMinimap" then
                        move_icon()
                elseif id=="ItemRack_Opt_BigCooldown" then
                        ItemRack_SetAllCooldownFonts()
                elseif id=="ItemRack_Opt_SetLabels" then
                        draw_inv()
                end
        end
end

function ItemRack_OptList_ScrollFrame_Update()

        local i, idx, item, optinfo, optscroll, opttext, optbutton
        local offset = FauxScrollFrame_GetOffset(ItemRack_OptList_ScrollFrame)

        FauxScrollFrame_Update(ItemRack_OptList_ScrollFrame, table.getn(ItemRack.OptScroll), 11, 19 )

        for i=1,11 do
                item = getglobal("ItemRackOptList"..i)
                idx = offset+i
                if idx<=table.getn(ItemRack.OptScroll) then
                        optscroll = ItemRack.OptScroll[idx]
                        optinfo = ItemRack.OptInfo[optscroll.idx]
                        opttext = getglobal("ItemRackOptList"..i.."CheckButtonText")
                        optbutton = getglobal("ItemRackOptList"..i.."CheckButton")
                        opttext:SetText(optinfo.text)
                        opttext:SetTextColor(1,1,1,1)
                        optbutton:Enable()
                        if optscroll.dependency then
                                item:SetWidth(128)
                                if ItemRack_Settings[ItemRack.OptInfo[optscroll.dependency].info]=="OFF" then
                                        opttext:SetTextColor(.5,.5,.5,1)
                                        optbutton:Disable()
                                end
                        else
                                item:SetWidth(142)
                        end
                        if ItemRack_Settings[optinfo.info]=="ON" then
                                optbutton:SetChecked(1)
                        else
                                optbutton:SetChecked(0)
                        end
                        item:Show()
                else
                        item:Hide()
                end
        end

end

-- tooltip for scrolling options
function ItemRack_OptList_OnEnter()
        local idx = FauxScrollFrame_GetOffset(ItemRack_OptList_ScrollFrame) + this:GetParent():GetID()
        local optinfo = ItemRack.OptInfo[ItemRack.OptScroll[idx].idx]
        ItemRack_OnTooltip(optinfo.text,optinfo.tooltip)
end

-- onclick for scrolling options, gets name of option and sends to _Opt_OnClick to process
function ItemRack_OptList_OnClick()
        local idx = FauxScrollFrame_GetOffset(ItemRack_OptList_ScrollFrame) + this:GetParent():GetID()
        local optinfo = ItemRack.OptInfo[ItemRack.OptScroll[idx].idx]
        ItemRack_Opt_OnClick(ItemRack.OptScroll[idx].idx)
        ItemRack_OptList_ScrollFrame_Update()
end

-- sets the state of a checkbutton to nil, 0 or 1
function ItemRack_TriStateCheck_SetState(button,value)
        local label = getglobal(button:GetName().."Text")
        button.tristate = value
        if not value then
                button:SetCheckedTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
                button:SetChecked(1)
                label:SetTextColor(.5,.5,.5)
        elseif value==0 then
                button:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check")
                button:SetChecked(0)
                label:SetTextColor(1,1,1)
        elseif value==1 then
                button:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check")
                button:SetChecked(1)
                label:SetTextColor(1,1,1)
        end
end

-- rotates a checkbutton from indeterminate->unchecked->checked (for show helm/cloak)
function ItemRack_TriStateCheck_OnClick()
        if not this.tristate then
                ItemRack_TriStateCheck_SetState(this,0)
        elseif this.tristate==0 then
                ItemRack_TriStateCheck_SetState(this,1)
        elseif this.tristate==1 then
                ItemRack_TriStateCheck_SetState(this,nil)
        end
        ItemRack_TriStateCheck_Tooltip()
end

-- initializes tristate buttons to be indeterminate
function ItemRack_TriStateCheck_OnLoad()
        ItemRack_TriStateCheck_SetState(this,nil)
end

function ItemRack_TriStateCheck_Tooltip()
        local tristate_names = { ["nil"] = "Ignore", ["0"] = "Hide", ["1"] = "Show" }
        local which = (this==ItemRack_ShowHelm) and "Helm" or "Cloak"
        ItemRack_OnTooltip(which..": "..tristate_names[tostring(this.tristate)],"This determines if the "..string.lower(which).." is shown or hidden when equipped.")
end

--[[ Sets ]]--

ItemRack.SetBuild = {}

local function build_icon()

        local setname = ItemRack_Sets_Name:GetText()

        ItemRack_Sets_ChosenIcon:SetNormalTexture(ItemRack.SelectedIcon)
        ItemRack_Sets_ChosenIconName:SetText(setname)
        if Rack_User[user].Sets[setname] then
                local _,_,modifier,basekey = string.find(Rack_User[user].Sets[setname].key or "","(.).+(-.)")
                ItemRack_Sets_ChosenIconHotKey:SetText((modifier or "")..(basekey or ""))
        else
                ItemRack_Sets_ChosenIconHotKey:SetText("")
        end
end

-- initializes .SetIcons mostly, the list of icons for the set
function ItemRack_Sets_Initialize()

        local i

        ItemRack.SetIcons = {}

        for i=0,19 do
                -- add 20 spaces at start of list - this list is constructed once and only first 20 slots change
                table.insert(ItemRack.SetIcons,"")
        end

        for i=1,table.getn(ItemRackExtraIcons) do
                -- add ExtraIcons defined in ItemRackExtraIcons.lua
                table.insert(ItemRack.SetIcons,ItemRackExtraIcons[i])
        end
        for i=1,GetNumMacroIcons() do
                -- add the macro icons to choose from
                table.insert(ItemRack.SetIcons,GetMacroIconInfo(i))
        end

end

local function highlight_set_item(v1)

        if ItemRack.SetBuild[v1]==1 then
                getglobal("ItemRack_Sets_Inv"..v1.."Icon"):SetVertexColor(1,1,1,1)
                getglobal("ItemRack_Sets_Inv"..v1):SetAlpha(1)
                getglobal("ItemRack_Sets_Inv"..v1):LockHighlight()
        else
                getglobal("ItemRack_Sets_Inv"..v1.."Icon"):SetVertexColor(.4,.4,.4,1)
                getglobal("ItemRack_Sets_Inv"..v1):SetAlpha(.5)
                getglobal("ItemRack_Sets_Inv"..v1):UnlockHighlight()
        end
end

function ItemRack_Sets_UpdateInventory()

        local i,texture

        if not ItemRack.SetIcons then
                ItemRack_Sets_Initialize()
        end

        for i=0,19 do
                texture = GetInventoryItemTexture("player",i)
                if not texture then
                        _,texture = GetInventorySlotInfo(string.gsub(ItemRack.Indexes[i].paperdoll_slot,"Character",""))
                end
                getglobal("ItemRack_Sets_Inv"..i.."Icon"):SetTexture(texture)
                ItemRack.SetIcons[i+1] = texture
                highlight_set_item(i)
        end
        ItemRack_Sets_ScrollFrame_Update()
end

function ItemRack_Sets_NewSet()

        local i,texture

        ItemRack_Sets_UpdateInventory()

        for i=0,19 do
                texture = GetInventoryItemTexture("player",i)
                if not texture then
                        _,texture = GetInventorySlotInfo(string.gsub(ItemRack.Indexes[i].paperdoll_slot,"Character",""))
                end
                ItemRack.SetBuild[i] = ItemRack_Users[user].Inv[i] or 0
                highlight_set_item(i)
                ItemRack.SetIcons[i+1] = texture
        end

        ItemRack_Sets_Saved:Hide()
        ItemRack_Sets_Icons:Show() -- start out showing icons

        ItemRack.SelectedName = ""
        ItemRack_Sets_Name:SetText("")
        ItemRack.SelectedIcon = ItemRack.SetIcons[math.random(1,table.getn(ItemRack.SetIcons))]
        build_icon()

        ItemRack_TriStateCheck_SetState(ItemRack_ShowHelm,nil)
        ItemRack_TriStateCheck_SetState(ItemRack_ShowCloak,nil)

        for i=1,25 do
                getglobal("ItemRack_Sets_Icon"..i):UnlockHighlight()
        end

        ItemRack_Sets_NameLabel:SetText(ItemRackText.SETS_NAMELABEL_TEXT)
        ItemRack_Tab() -- flip to tab 2 (sets)

        ItemRack_SetsFrame:Show()
        ItemRack_Sets_Name:ClearFocus()

end

local function validate_set_buttons()

        local setname,found = ItemRack_Sets_Name:GetText()

        if Rack_User[user].Sets[setname] then
                ItemRack_Sets_RemoveButton:Enable()
                ItemRack_Sets_BindButton:Enable()
                ItemRack_Sets_HideSet:Enable()
                ItemRack_Sets_HideSetText:SetTextColor(1,1,1)
        else
                ItemRack_Sets_RemoveButton:Disable()
                ItemRack_Sets_BindButton:Disable()
                ItemRack_Sets_HideSet:Disable()
                ItemRack_Sets_HideSetText:SetTextColor(.4,.4,.4)
        end

        for i=0,19 do
                found = found or (ItemRack.SetBuild[i]==1)
        end
        setname = (found and setname) or nil

        if setname and string.len(setname)>0 then
                ItemRack_Sets_SaveButton:Enable()
        else
                ItemRack_Sets_SaveButton:Disable()
        end

        build_icon()
end

function ItemRack_Sets_InvToggle()

        local id = this:GetID()

        this:SetChecked(0)
        ItemRack.SetBuild[id] = 1-(ItemRack.SetBuild[id] or 0)

        highlight_set_item(id)

        if IsAltKeyDown() then

                if ItemRack_Users[user].Inv[id] and ItemRack.SetBuild[id]==0 then
                        remove_inv(id)
                elseif not ItemRack_Users[user].Inv[id] and ItemRack.SetBuild[id]==1 then
                        ItemRack_Users[user].Visible="ON"
                        ItemRack_Users[user].Inv[id] = 1
                        getglobal("ItemRackInv"..id.."Icon"):SetTexture(get_item_info(id))
                        table.insert(ItemRack_Users[user].Bar,id)
                        draw_inv()
                end
        end

        if ItemRack.SetBuild[id]==1 then
                ItemRack_BuildMenu(id,"SET")
        end

        validate_set_buttons()
end

function ItemRack_Sets_ScrollFrame_Update()

        local i, item, texture, idx
        local offset = FauxScrollFrame_GetOffset(ItemRack_Sets_ScrollFrame)

        FauxScrollFrame_Update(ItemRack_Sets_ScrollFrame, ceil(table.getn(ItemRack.SetIcons) / 5) , 5, 24 )
        
        for i=1,25 do
                item = getglobal("ItemRack_Sets_Icon"..i)
                idx = (offset*5) + i
                if idx<=table.getn(ItemRack.SetIcons) then
                        texture = ItemRack.SetIcons[idx]
                        item:SetNormalTexture(texture)
                        item:SetPushedTexture(texture)
                        item:Show()
                else
                        item:Hide()
                end
                if ItemRack.SetIcons[idx]==ItemRack.SelectedIcon then
                        item:LockHighlight()
                else
                        item:UnlockHighlight()
                end
        end
end

function ItemRack_Sets_Icon_OnClick()

        local id = this:GetID()
        local offset = FauxScrollFrame_GetOffset(ItemRack_Sets_ScrollFrame)
        local idx = offset*5+id

        if idx<=table.getn(ItemRack.SetIcons) then
                ItemRack.SelectedIcon = ItemRack.SetIcons[idx]
                ItemRack_Sets_ScrollFrame_Update()
        end
        build_icon()
end

function ItemRack_Sets_Name_OnTextChanged()

        ItemRack.SelectedName = ItemRack_Sets_Name:GetText()
        ItemRack_Sets_ChosenIconName:SetText(ItemRack.SelectedName)
        validate_set_buttons()
end

-- save button clicked
function ItemRack_Sets_Save_OnClick()

        local setname = ItemRack_Sets_Name:GetText()
        local itemcount, itemname, itemslot = 0
        local oldkey, oldkeyindex

        ItemRack_Sets_Name:ClearFocus()

        -- if a 2H weapon in mainhand, ignore offhand (don't try to equip empty slot to offhand)
        _,_,itemslot = get_item_info(16)
        if itemslot=="INVTYPE_2HWEAPON" then
                ItemRack.SetBuild[17] = nil
        end

        if string.len(setname)>0 then
                if Rack_User[user].Sets[setname] then
                        -- grab old key binding before recreating set
                        oldkey = Rack_User[user].Sets[setname].key
                        oldkeyindex = Rack_User[user].Sets[setname].keyindex
                end
                Rack_User[user].Sets[setname] = { icon=ItemRack.SelectedIcon, key=oldkey, keyindex=oldkeyindex }
                for i=0,19 do
                        if ItemRack.SetBuild[i]==1 then
                                Rack_User[user].Sets[setname][i] = {}
                                itemcount = itemcount + 1
                                _,itemname = get_item_info(i)
                                Rack_User[user].Sets[setname][i].name = itemname or "(empty)"
                                _,Rack_User[user].Sets[setname][i].id = Rack.GetItemInfo(i)
                        end
                end
                sets_message(string.format(ItemRackText.SAVED,setname,itemcount))
                Rack_User[user].Sets[setname].hide = ItemRack_Sets_HideSet:GetChecked()
                Rack_User[user].Sets[setname].showhelm = ItemRack_ShowHelm.tristate
                Rack_User[user].Sets[setname].showcloak = ItemRack_ShowCloak.tristate
        end
        draw_minimap_icon()
        validate_set_buttons()
end

-- equips a set by name, usable in macros. now a wrapper to Rack.EquipSet()
function ItemRack_EquipSet(setname)

        if not setname and ItemRack.EventSetName then
                setname = ItemRack.EventSetName
        end

        Rack.EquipSet(setname)
end

-- hitting the dropdown button toggles between icons and savedsets
function ItemRack_Sets_DropDownButton_OnClick()

        ItemRack_Sets_Name:ClearFocus()
        ItemRack_Sets_Build_Dropdown()
        ItemRack_Sets_SubFrame2:Hide()
        ItemRack_Sets_SubFrame4:Hide()
        ItemRack_Sets_SetSelect:Show()
        ItemRack_Sets_Saved:Show()
end

-- clicking one of the drop-down saved sets
function ItemRack_Sets_Saved_OnClick(arg1)

        local id = this:GetID()
        local idx = FauxScrollFrame_GetOffset(ItemRack_Sets_SavedScrollFrame) + id
        local i,setname

        ItemRack_Sets_SetSelect:Hide()

        if ItemRack.SetsListSize<2 then
                -- do nothing
        elseif ItemRack.SelectedTab==2 then
                -- chose a set from the Sets tab (SubFrame2)
                setname = ItemRack.SetsList[idx].Name
                ItemRack_Sets_Name:SetText(setname)
                ItemRack.SelectedIcon = ItemRack.SetsList[idx].Icon
                ItemRack.SelectedName = setname
                build_icon()
                for i=0,19 do
                        ItemRack.SetBuild[i] = Rack_User[user].Sets[setname][i] and 1 or 0
                        highlight_set_item(i)
                end
                ItemRack_Sets_HideSet:SetChecked(Rack_User[user].Sets[setname].hide)
                ItemRack_TriStateCheck_SetState(ItemRack_ShowHelm,Rack_User[user].Sets[setname].showhelm)
                ItemRack_TriStateCheck_SetState(ItemRack_ShowCloak,Rack_User[user].Sets[setname].showcloak)
                ItemRack_EquipSet(setname)
        elseif ItemRack.SelectedTab==4 then
                -- chose a set from the Events tab (SubFrame4)
                setname = ItemRack.SetsList[idx].Name
                if not ItemRack_Users[user].Events[eventList[ItemRack.SelectedEvent].name] then
                        ItemRack_Users[user].Events[eventList[ItemRack.SelectedEvent].name] = { setname=setname, enabled=1 }
                else
                        ItemRack_Users[user].Events[eventList[ItemRack.SelectedEvent].name].setname = setname
                end
                ItemRack_Build_eventList()
        end

end

-- gather all sets into a numerically-indexes list to be used by SavedScrollFrame
function ItemRack_Sets_Build_Dropdown()

        local idx,i,j,count=1

        ItemRack.SetsList = ItemRack.SetsList or {}

        for i in Rack_User[user].Sets do
                if not string.find(i,"^ItemRack-") and not string.find(i,"^Rack-") then -- skip special sets (ItemRack-Queue and ItemRack-Normal)
                        count = 0
                        ItemRack.SetsList[idx] = ItemRack.SetsList[idx] or {}
                        ItemRack.SetsList[idx].Icon = Rack_User[user].Sets[i].icon
                        ItemRack.SetsList[idx].Name = i
                        for j=0,19 do
                                if Rack_User[user].Sets[i][j] then
                                        count = count + 1
                                end
                        end
                        ItemRack.SetsList[idx].Count = string.format(ItemRackText.COUNTFORMAT,count)
                        ItemRack.SetsList[idx].Key = Rack_User[user].Sets[i].key
                        ItemRack.SetsList[idx].Hide = Rack_User[user].Sets[i].hide

                        idx = idx +1
                end
        end
        ItemRack.SetsListSize = idx

        -- sort drop-down list alphabetically
        table.sort(ItemRack.SetsList,function(e1,e2) if e1 and e2 and (e1.Name<e2.Name) then return true else return false end end)
        ItemRack_Sets_SavedScrollFrameScrollBar:SetValue(0)
        ItemRack_Sets_SavedScrollFrame_Update()

end

-- scrollbar update for saved sets (dropdown)
function ItemRack_Sets_SavedScrollFrame_Update()

        local i, idx, item
        local offset = FauxScrollFrame_GetOffset(ItemRack_Sets_SavedScrollFrame)

        local listsize,listheight,liststub,compact = 8,28,"ItemRack_Sets_Saved",nil

        for i=1,8 do
                getglobal("ItemRack_Sets_Saved"..i):Hide()
        end
        for i=1,11 do
                getglobal("ItemRack_Sets_Compact"..i):Hide()
        end

        if ItemRack_Settings.CompactList=="ON" then
                listsize,listheight,liststub,compact = 11,21,"ItemRack_Sets_Compact",1
        end

        FauxScrollFrame_Update(ItemRack_Sets_SavedScrollFrame, ItemRack.SetsListSize-1, listsize, listheight )

        for i=1,listsize do
                idx = offset + i
                if idx<ItemRack.SetsListSize then
                        getglobal(liststub..i.."Name"):SetText(ItemRack.SetsList[idx].Name)
                        getglobal(liststub..i.."Icon"):SetTexture(ItemRack.SetsList[idx].Icon)
                        if not compact then
                                getglobal(liststub..i.."Count"):SetText(ItemRack.SetsList[idx].Count)
                                getglobal(liststub..i.."Key"):SetText(ItemRack.SetsList[idx].Key)
                        end
                        if Rack_User[user].Sets[ItemRack.SetsList[idx].Name].hide then
                                getglobal(liststub..i.."Name"):SetTextColor(.5,.5,.5)
                                if not compact then
                                        getglobal(liststub..i.."Count"):SetTextColor(.5,.5,.5)
                                        getglobal(liststub..i.."Key"):SetTextColor(.5,.5,.5)
                                end
                        else
                                getglobal(liststub..i.."Name"):SetTextColor(1,1,1)
                                if not compact then
                                        getglobal(liststub..i.."Count"):SetTextColor(1,1,1)
                                        getglobal(liststub..i.."Key"):SetTextColor(1,1,1)
                                end
                        end
                        getglobal(liststub..i):Show()
                else
                        getglobal(liststub..i):Hide()
                end
        end

        if ItemRack.SetsListSize<=1 then
                ItemRack_Sets_Saved1Icon:SetTexture("")
                ItemRack_Sets_Saved1Name:SetText(ItemRackText.NOSAVEDSETS)
                ItemRack_Sets_Saved1Count:SetText("")
                ItemRack_Sets_Saved1Key:SetText("")
                ItemRack_Sets_Saved1:Show()
                for i=2,8 do
                        getglobal("ItemRack_Sets_Saved"..i):Hide()
                end
                for i=1,11 do
                        getglobal("ItemRack_Sets_Compact"..i):Hide()
                end
        end

end

-- displays a tooltip of a set's contents, set show_contents to override TinyTooltip option
function ItemRack_Sets_Tooltip(setname,show_contents)

        local count,missing,r,g,b,i,name,inv,bag = 0,0

        setname = setname or ItemRack.SelectedName

        if setname and Rack_User[user].Sets[setname] then

                set_tooltip_anchor(this)
                GameTooltip:AddLine(string.format(ItemRackText.SETTOOLTIPFORMAT,setname))
                for i=0,19 do
                        if Rack_User[user].Sets[setname][i] then
                                name = Rack_User[user].Sets[setname][i].name or "(empty)"
                                count = count + 1
                                inv,bag = Rack.FindItem(nil,name,"passive")
                                if ItemRack.BankedItems[name] then
                                        r,g,b = .3,.5,1
                                elseif inv or bag or name=="(empty)" then
                                        r,g,b = .85,.85,.85
                                else
                                        r,g,b = 1,.1,.1 -- mark missing items red
                                        missing = missing + 1
                                end
                                if ItemRack_Settings.TinyTooltip=="OFF" or show_contents then
                                        GameTooltip:AddLine(ItemRack.Indexes[i].name..": "..tostring(name),r,g,b)
                                end
                        end
                end
                if ItemRack_Settings.TinyTooltip=="ON" and not show_contents then
                        -- if Tiny Tooltip enabled, just show a count
                        GameTooltip:AddLine(string.format(ItemRackText.SETTOOLTIPCOUNT,count),.85,.85,.85)
                        if missing>0 then
                                GameTooltip:AddLine(string.format(ItemRackText.SETTOOLTIPMISSING,missing),1,.1,.1)
                        end
                end
                GameTooltip:Show()
        end
end     

-- tooltips displaying contents of a set in the dropdown list
function ItemRack_Sets_Saved_OnEnter()

        if ItemRack.SetsListSize>1 then
                local id = this:GetID()
                local idx = FauxScrollFrame_GetOffset(ItemRack_Sets_SavedScrollFrame) + id
                local setname = ItemRack.SetsList[idx].Name
                ItemRack_Sets_Tooltip(setname,1) -- ,1 forces contents to show in tooltip
        end
end

--[[ Key bindings ]]--

-- returns a number 1-10 (or MaxKeyBindings) of an available key binding, nil if none are free
local function get_free_binding()

        local i,j,found,freeindex

        for i=1,ItemRackText.MaxKeyBindings do
                found = false
                for j in Rack_User[user].Sets do
                        if Rack_User[user].Sets[j].keyindex == i then
                                found = true
                        end
                end
                if not found then
                        freeindex = i
                        i = ItemRackText.MaxKeyBindings+1 -- whimpy break
                end
        end

        return freeindex
end

-- removes a key by index, 1-10 (or .MaxKeyBindings)
local function unbind_key_index(idx)

        if idx then
                local oldkey = GetBindingKey("EQUIPSET"..idx)
                if oldkey then
                        SetBinding(oldkey)
                        setglobal("BINDING_NAME_EQUIPSET"..idx,string.format(ItemRackText.BINDINGFORMAT,idx))
                end
        end
end

-- removes old key bindings, for initialization and preparation to bind a key
-- if no setname given, then all keys are unbound
local function unbind_keys(setname)

        local idx

        if not setname then
                for idx=1,ItemRackText.MaxKeyBindings do
                        unbind_key_index(idx)
                end
        else
                idx = Rack_User[user].Sets[setname].keyindex
                unbind_key_index(idx)
        end
        SaveBindings(GetCurrentBindingSet())

end     

-- this takes the current key bindings and updates the .Sets info -- usually as a result of user changing bindings outside the mod
function ItemRack_AgreeOnKeyBindings()
        local i,oldkey,setname

        if ItemRack.KeyBindingsSettled then
                -- don't do this at startup, wait until we had a chance to initialize this player's keys

                for i in Rack_User[user].Sets do
                        Rack_User[user].Sets[i].key = nil
                        Rack_User[user].Sets[i].keyindex = nil
                end

                for i=1,10 do
                        oldkey = GetBindingKey("EQUIPSET"..i)
                        if oldkey then
                                _,_,setname = string.find(getglobal("BINDING_NAME_EQUIPSET"..i) or "",ItemRackText.BINDINGSEARCH)
                                if setname and Rack_User[user].Sets[setname] then
                                        Rack_User[user].Sets[setname].key = oldkey
                                        Rack_User[user].Sets[setname].keyindex = i
                                end
                        end
                end
        end
end

-- removes the currently selected set
function ItemRack_Sets_Remove_OnClick()

        if Rack_User[user].Sets[ItemRack.SelectedName] then
                unbind_keys(ItemRack.SelectedName)
                Rack_User[user].Sets[ItemRack.SelectedName] = nil
                sets_message(string.format(ItemRackText.SETREMOVE,ItemRack.SelectedName))
                local i
                -- if an event has this set, remove the association
                for i in ItemRack_Users[user].Events do
                        if ItemRack_Users[user].Events[i].setname==ItemRack.SelectedName then
                                ItemRack_Users[user].Events[i] = nil
                        end
                end
                ItemRack.SelectedName=nil
                ItemRack_Sets_Name:SetText("")
                build_icon()
                validate_set_buttons()
                ItemRack_Build_eventList()
        end
end

function ItemRack_UseSetBinding(v1)

        local i,setname

        -- look for sets with this key index
        for i in Rack_User[user].Sets do
                if Rack_User[user].Sets[i].keyindex == v1 then
                        setname = i
                end
        end

        -- if we found a set with this key index, equip it
        if setname then
                ItemRack_EquipSet(setname)
        end
end

local function bind_key_to_set(key,setname)

        -- check if another set has this key binding
        local i
        for i in Rack_User[user].Sets do
                if Rack_User[user].Sets[i].key == key then
                        unbind_keys(i) -- remove the other set's key binding if so
                        Rack_User[user].Sets[i].keyindex = nil -- remove them from our table
                        Rack_User[user].Sets[i].key = nil
                end
        end

        -- get the next free key binding
        local idx = Rack_User[user].Sets[setname].keyindex or get_free_binding()

        if idx then -- if we previously had a key binding or there's space for a new one, continue
                        -- associate the key and index (1-10) with the set
                Rack_User[user].Sets[setname].key = key
                Rack_User[user].Sets[setname].keyindex = idx
                -- release any binding this key previously had
                unbind_keys(setname)

                -- finally bind the key
                ItemRack.KeyBindingsSettled = nil -- don't do an agree on key bindings until set
                local success = SetBinding(key,"EQUIPSET"..idx)
                if success then
                        setglobal("BINDING_NAME_EQUIPSET"..idx,string.format(ItemRackText.BINDINGFORMAT,setname))
                        sets_message(string.format(ItemRackText.BINDSET,key))
                else
                        -- couldn't bind the key, forget we tried (should never get to this part)
                        Rack_User[user].Sets[setname].key = nil
                        Rack_User[user].Sets[setname].keyindex = nil
                end
                ItemRack.KeyBindingsSettled = 1
                SaveBindings(GetCurrentBindingSet())
                ItemRack_KeyBindFrame:Hide()
        end
end

-- hitting a key when "Press a key" mode up
function ItemRack_Sets_KeyBind_OnKeyDown(arg1)

        local setname = ItemRack.SelectedName

        if arg1=="ESCAPE" then
                sets_message(ItemRackText.BINDCLEAR)
                ItemRack_KeyBindFrame:Hide()

                -- if this set has a key, restore its name and release the key binding
                unbind_keys(setname)
                Rack_User[user].Sets[setname].key = nil
                Rack_User[user].Sets[setname].keyindex = nil

        elseif arg1~="SHIFT" and arg1~="ALT" and arg1~="CTRL" then
                local key = arg1
                if IsShiftKeyDown() then
                        key = "SHIFT-"..key
                elseif IsAltKeyDown() then
                        key = "ALT-"..key
                elseif IsControlKeyDown() then
                        key = "CTRL-"..key
                end

                local action
                action = GetBindingAction(key)
                if action and action~="" then
                        StaticPopupDialogs["ITEMRACKKEYCONFIRM"] = {
                                text = key.." is already used for "..tostring(getglobal("BINDING_NAME_"..action)).."\n\nOverwrite?",
                                button1 = "Yes",
                                button2 = "No",
                                OnAccept = function() bind_key_to_set(key,setname) end,
                                OnCancel = function() sets_message("No key binding set.") end,
                                timeout = 0,
                                whileDead = 1
                        }
                        StaticPopup_Show("ITEMRACKKEYCONFIRM")
                else
                        bind_key_to_set(key,setname)
                end
        end

        build_icon()

end

function ItemRack_InitializeKeyBindings()

        local i, keyidx

        unbind_keys() -- remove all 10 keybindings (from previous users perhaps)

        for i in Rack_User[user].Sets do
                keyidx = Rack_User[user].Sets[i].keyindex
                if keyidx then
                        setglobal("BINDING_NAME_EQUIPSET"..keyidx,string.format(ItemRackText.BINDINGFORMAT,i))
                        SetBinding(Rack_User[user].Sets[i].key,"EQUIPSET"..keyidx)
                end
        end
        SaveBindings(GetCurrentBindingSet())
        ItemRack.KeyBindingsSettled = true
end

function ItemRack_Sets_Inv_OnEnter()

        local id = this:GetID()

        if ItemRack.SetBuild and ItemRack.SetBuild[id]==1 then
                ItemRack_BuildMenu(id,"SET")
        end

end

function ItemRack_Sets_ChosenIcon_OnClick()

        if IsAltKeyDown() then

                if ItemRack_Users[user].Inv[20] then
                        remove_inv(20)
                elseif not ItemRack_Users[user].Inv[20] then
                        ItemRack_Users[user].Visible="ON"
                        ItemRack_Users[user].Inv[20] = 1
                        ItemRackInv20Icon:SetTexture(get_item_info(20))
                        table.insert(ItemRack_Users[user].Bar,20)
                        draw_inv()
                end
        end

        validate_set_buttons()
end

--[[ Minimap button ]]--

function ItemRack_Sets_Toggle(v1)

        if v1 then
                if not getglobal("ItemRack_Sets_SubFrame"..v1):IsVisible() then
                        ItemRack_SetsFrame:Hide()
                end
        end

        if ItemRack_SetsFrame:IsVisible() then
                ItemRack_SetsFrame:Hide()
        else
                ItemRack_Sets_NewSet()
        end
        if v1 then
                ItemRack_Tab(v1)
        end
end

-- when user clicks the minimap button
function ItemRack_IconFrame_OnClick(arg1)

        if arg1=="LeftButton" and ItemRack_Settings.DisableToggle=="OFF" then
                -- toggle bar
                ItemRack_Toggle()
        elseif arg1=="LeftButton" then
                -- toggle menu          
                if ItemRack_MenuFrame:IsVisible() then
                        ItemRack_MenuFrame:Hide()
                else
                        ItemRack_BuildMenu(20,"MINIMAP")
                end
        else
                ItemRack_Sets_Toggle()
        end

end

--[[ Tabs ]]--

function ItemRack_Tab(v1)

        ItemRack_Sets_SetSelect:Hide()
        ItemRack_EditEvent:Hide()
        ItemRack.SelectedTab = (v1 or ItemRack.SelectedTab) or 1
        for i=1,4 do
                getglobal("ItemRack_Sets_Tab"..i):UnlockHighlight()
                getglobal("ItemRack_Sets_SubFrame"..i):Hide()
        end
        getglobal("ItemRack_Sets_Tab"..ItemRack.SelectedTab):LockHighlight()
        getglobal("ItemRack_Sets_SubFrame"..ItemRack.SelectedTab):Show()
end

-- when set chooser dropdown shown
function ItemRack_SetSelect_OnShow()
  -- remove ItemRack_SetsFrame from UISpecialFrames and add ItemRack_Sets_SetSelect
        make_escable("ItemRack_SetsFrame","remove")
        make_escable("ItemRack_Sets_SetSelect","add")
end

-- when set chooser dropdown hidden
function ItemRack_SetSelect_OnHide()
        -- remove ItemRack_Sets_SetSelect from UISpecialFrames and add ItemRack_SetsFrame
        make_escable("ItemRack_Sets_SetSelect","remove")
        make_escable("ItemRack_SetsFrame","add")
        getglobal("ItemRack_Sets_SubFrame"..ItemRack.SelectedTab):Show()
end

-- when event editor shown
function ItemRack_EditEvent_OnShow()
        ItemRack_Sets_SubFrame4:Hide()
        make_escable("ItemRack_SetsFrame","remove")
        make_escable("ItemRack_EditEvent","add")
end

-- when event editor hidden
function ItemRack_EditEvent_OnHide()
        make_escable("ItemRack_EditEvent","remove")
        make_escable("ItemRack_SetsFrame","add")
        ItemRack_Sets_SubFrame4:Show()
end

-- when the sets frame is hidden, enable all events if events are on
function ItemRack_SetsFrame_OnHide()
        ItemRack_MenuFrame:Hide()
        ItemRack_EnableAllEvents()
end

-- when the sets frame is shown, disable all events
function ItemRack_SetsFrame_OnShow()
        if not ItemRack.SetIcons then
                ItemRack_Sets_Initialize()
        end
        ItemRack_DisableAllEvents()
        if ItemRack_Settings.EnableEvents=="ON" then
                sets_message(ItemRackText.EVENTSSUSPENDED)
        end
end

--[[ Events ]]--

local function check_for_titanrider()

        if TITAN_RIDER_ID and (not TitanRider_EquipToggle or (TitanGetVar and TitanGetVar(TITAN_RIDER_ID,"EquipItems"))) then
                StaticPopupDialogs["ITEMRACK_TITANRIDER"] = {
                        text = "It appears you have Titan Rider (a module of Titan Panel) enabled.  Do not use this Mount event with Titan Rider enabled or it will create a mess (items on cursor, gear swapping back).",
                        button1 = "Ok", timeout = 0, whileDead = 1, showAlert = 1, hideOnEscape = 1 }
                StaticPopup_Show("ITEMRACK_TITANRIDER")
                return 1
        else
                return nil
        end
end

-- changes the font size in the event edit window
function ItemRack_ChangeEventFont()
        if ItemRack_Settings.LargeFont=="ON" then
                ItemRack_EventScript:SetFont("Fonts\\ARIALN.TTF",15)
        else
                ItemRack_EventScript:SetFont("Fonts\\FRIZQT__.TTF",11)
        end
end

-- builds eventList, a numerically-indexed list of events and their associated sets
function ItemRack_Build_eventList()

        local i
        eventListSize = 1
        local oldeventname,eventname

        if ItemRack.SelectedEvent>0 then
                -- remember old selected event, it may move in the list
                oldeventname = eventList[ItemRack.SelectedEvent].name
        end

        scratchTableSize[1] = 1 -- size of each table for secondary sort
        scratchTableSize[2] = 1

        -- problems with secondary sort, so doing one manually - first split events into two tables: ones with a setname, ones without
        for i in ItemRack_Events do
                -- first split 
                if i~="events_version" then
                        _,_,class = string.find(i,"^(.+)%:")
                        if ItemRack_Settings.ShowAllEvents=="ON" or (not class or class==UnitClass("player")) then
                                if ItemRack_Users[user].Events[i] and Rack_User[user].Sets[ItemRack_Users[user].Events[i].setname] then
                                        scratchTable[1][scratchTableSize[1]] = i
                                        scratchTableSize[1] = scratchTableSize[1] + 1
                                else
                                        scratchTable[2][scratchTableSize[2]] = i
                                        scratchTableSize[2] = scratchTableSize[2] + 1
                                end
                        end
                end
        end
        scratchTable[1][scratchTableSize[1]] = nil
        scratchTable[2][scratchTableSize[2]] = nil

        -- sort each half
        table.sort(scratchTable[1],function(e1,e2) return e1 and e2 and e1<e2 end)
        table.sort(scratchTable[2],function(e1,e2) return e1 and e2 and e1<e2 end)

        -- merge the halves
        eventListSize = 1
        for i=1,scratchTableSize[1]-1 do
                eventname = scratchTable[1][i]
                eventList[eventListSize] = eventList[eventListSize] or {}
                eventList[eventListSize].name = eventname
                eventList[eventListSize].trigger = ItemRack_Events[eventname].trigger
                eventList[eventListSize].delay = ItemRack_Events[eventname].delay
                eventList[eventListSize].script = ItemRack_Events[eventname].script
                eventList[eventListSize].setname = ItemRack_Users[user].Events[eventname].setname
                eventList[eventListSize].texture = Rack_User[user].Sets[eventList[eventListSize].setname].icon
                eventList[eventListSize].enabled = ItemRack_Users[user].Events[eventname].enabled
                eventListSize = eventListSize + 1
        end
        for i=1,scratchTableSize[2]-1 do
                eventname = scratchTable[2][i]
                eventList[eventListSize] = eventList[eventListSize] or {}
                eventList[eventListSize].name = eventname
                eventList[eventListSize].trigger = ItemRack_Events[eventname].trigger
                eventList[eventListSize].delay = ItemRack_Events[eventname].delay
                eventList[eventListSize].script = ItemRack_Events[eventname].script
                eventList[eventListSize].setname = nil
                eventList[eventListSize].texture = nil
                eventList[eventListSize].enabled = nil
                eventListSize = eventListSize + 1
        end

        ItemRack.SelectedEvent=0
        for i=1,eventListSize-1 do
                if eventList[i].name==oldeventname then
                        ItemRack.SelectedEvent = i
                end
        end

        ItemRack_Validate_EventList_Buttons()
        ItemRack_Events_ScrollFrame_Update()
end

-- update for the list of events scrollframe
function ItemRack_Events_ScrollFrame_Update()

        local i, texture, idx, button, icon, enable, name
        local offset = FauxScrollFrame_GetOffset(ItemRack_Events_ScrollFrame)

        FauxScrollFrame_Update(ItemRack_Events_ScrollFrame, eventListSize-1, 7, 26 )
        
        for i=1,7 do
                idx = offset + i
                button = getglobal("ItemRack_Event"..i)
                if idx<eventListSize then
                        if ItemRack_Settings.ShowAllEvents=="ON" then
                                getglobal("ItemRack_Event"..i.."Name"):SetText(eventList[idx].name)
                        else
                                _,_,name = string.find(eventList[idx].name,"^.+%:(.+)")
                                name = name or eventList[idx].name
                                getglobal("ItemRack_Event"..i.."Name"):SetText(name)
                        end
                        icon = getglobal("ItemRack_Event"..i.."Icon")
                        enable = getglobal("ItemRack_Event"..i.."Enable")
                        if eventList[idx].setname then
                                icon:SetNormalTexture(eventList[idx].texture)
                                icon:SetPushedTexture(eventList[idx].texture)
                                enable:SetChecked(eventList[idx].enabled)
                                enable:Show()
                        else
                                icon:SetNormalTexture("Interface\\Icons\\INV_Misc_QuestionMark")
                                icon:SetPushedTexture("Interface\\Icons\\INV_Misc_QuestionMark")
                                enable:Hide()
                        end
                        button:Show()
                else
                        button:Hide()
                end
                if idx==ItemRack.SelectedEvent then
                        button:LockHighlight()
                else
                        button:UnlockHighlight()
                end
        end
end

-- onclick for events in the list, to lock highlight
function ItemRack_EventsList_OnClick(arg1)

        local idx = this:GetID() + FauxScrollFrame_GetOffset(ItemRack_Events_ScrollFrame)

        if idx<eventListSize then
                ItemRack.SelectedEvent = (ItemRack.SelectedEvent==idx) and 0 or idx
        end
        ItemRack_Validate_EventList_Buttons()
        ItemRack_Events_ScrollFrame_Update()
end

-- returns the eventList index for the *parent* of "this"
local function event_idx()
        return this:GetParent():GetID() + FauxScrollFrame_GetOffset(ItemRack_Events_ScrollFrame)
end

function ItemRack_EventsList_EnableOnClick()

        local idx = event_idx()

        if idx<eventListSize then
                local eventname = eventList[idx].name
                if not ItemRack_Users[user].Events[eventname] then
                        ItemRack_Users[user].Events[eventname] = {}
                end
                ItemRack_Users[user].Events[eventname].enabled = this:GetChecked()
                if eventname=="Mount" and check_for_titanrider() then
                        ItemRack_Users[user].Events[eventname].enabled = nil
                end
        end
        ItemRack_Build_eventList()
end

-- onenter of an event icon either a tooltip for an undefined event or the contents of the set
function ItemRack_EventsListIcon_OnEnter()

        local idx = event_idx()

        if idx<eventListSize then
                local setname = eventList[idx].setname
                if not setname then
                        ItemRack_OnTooltip(ItemRackText.UNDEFINEDEVENT_TEXT,ItemRackText.UNDEFINEDEVENT_TOOLTIP)
                else
                        ItemRack_Sets_Tooltip(setname)
                end
        end
end

function ItemRack_Validate_EventList_Buttons()

        if ItemRack.SelectedEvent==0 then
                ItemRack_Events_DeleteButton:Disable()
                ItemRack_Events_EditButton:Disable()
        else
                ItemRack_Events_DeleteButton:Enable()
                ItemRack_Events_EditButton:Enable()
        end
end

-- clicking event icon summons set selection window
function ItemRack_EventsListIcon_OnClick()

        local idx = event_idx()

        if idx<eventListSize then
                if eventList[idx].name=="Mount" and check_for_titanrider() then
                        return
                else
                        ItemRack.SelectedEvent = idx
                        ItemRack_Events_ScrollFrame_Update()
                        ItemRack_Validate_EventList_Buttons()
                        ItemRack_Sets_DropDownButton_OnClick()
                end
        end
end

-- handler for buttons clicked in the events pane
function ItemRack_EventButtons(v1)

        local i
        local others = ""

        if v1=="Delete" then
                local eventname = eventList[ItemRack.SelectedEvent].name

                ItemRack_DisableEvent(eventname)

                i = ItemRack_Users[user].Events[eventname]
                ItemRack_Users[user].Events[eventname] = nil

                if not i then

                        for i in ItemRack_Users do
                                if ItemRack_Users[i].Events and ItemRack_Users[i].Events[eventname] then
                                        others = others..i..", "
                                end
                        end

                        if others=="" then
                                sets_message("\""..eventname.."\" removed completely.")
                                ItemRack_Events[eventname] = nil
                        else
                                others = string.gsub(others,", $","")
                                sets_message("\""..eventname.."\" is used by: "..others)
                        end
                else
                        sets_message("\""..eventname.."\" disassociated.")
                end
                ItemRack_Events_ScrollFrameScrollBar:SetValue(0)
        elseif v1=="New" then
                ItemRack_EventScript:SetText("")
                ItemRack_EventName:SetText("")
                ItemRack_EventTrigger:SetText("")
                ItemRack_EventDelay:SetText("")
                ItemRack_EditEvent:Show()
                ItemRack_EventName:SetFocus()
        elseif v1=="Edit" then
                if ItemRack.SelectedEvent==0 then
                        -- if we're here and SelectedEvent==0, it was a double-click to a selected event. Reselect it
                        ItemRack_EventsList_OnClick("LeftButton")
                end
                -- if SelectedEvent defined, then either edit button or double-click to previously unselected event
                local eventname = eventList[ItemRack.SelectedEvent].name

                ItemRack_EventScript:SetText(ItemRack_Events[eventname].script or "")
                ItemRack_EventName:SetText(eventname)
                ItemRack_EventTrigger:SetText(ItemRack_Events[eventname].trigger or "")
                ItemRack_EventDelay:SetText(ItemRack_Events[eventname].delay or "")
                ItemRack_EditEvent:Show()
                ItemRack_EventScript:SetFocus()

        elseif v1=="Save" then
                local eventname = eventList[ItemRack.SelectedEvent] and eventList[ItemRack.SelectedEvent].name
                local name = ItemRack_EventName:GetText()
                if name and string.len(name)>0 then
                        if not ItemRack_Events[name] then
                                ItemRack_Events[name] = {}
                        end
                        ItemRack_Events[name].script = ItemRack_EventScript:GetText()
                        ItemRack_Events[name].trigger = ItemRack_EventTrigger:GetText()
                        ItemRack_Events[name].delay = tonumber(ItemRack_EventDelay:GetText()) or 0
                        sets_message("Event \""..name.."\" saved.")
                        ItemRack_EditEvent:Hide()
                else
                        sets_message("Event not saved.  Need a name at least.")
                end
        elseif v1=="Test" then
                RunScript(ItemRack_EventScript:GetText() or "")
        end
        ItemRack_Build_eventList()
end

--[[ Event Registration ]]--

ItemRack.Register = {} -- game events (UNIT_AURA, etc) are stored here

-- debug function, to list registered game events and the mod events they are for
function ItemRack_ListEvents()
        local i,j,foundtrigger,foundevent

        for i in ItemRack.Register do
                foundtrigger=1
                foundevent=nil
                DEFAULT_CHAT_FRAME:AddMessage("__"..i.."__")
                for j in ItemRack.Register[i] do
                        foundevent=1
                        DEFAULT_CHAT_FRAME:AddMessage(j)
                end
                if not foundevent then
                        DEFAULT_CHAT_FRAME:AddMessage("none")
                end
        end
        if not foundtrigger then
                DEFAULT_CHAT_FRAME:AddMessage("No events registered.")
        end
end

-- enables a specific eventname ("Riding","Warrior:Berserk",etc)
function ItemRack_EnableEvent(eventname)

        if not ItemRack_Events[eventname] then return end

        local trigger = ItemRack_Events[eventname].trigger

        if not ItemRack.Register[trigger] then
                ItemRack_RegisterFrame:RegisterEvent(trigger)
                ItemRack.Register[trigger] = {}
        end
        ItemRack.Register[trigger][eventname] = 1
end

-- disables a sepcific eventname ("Riding","Warrior:Berserk",etc)
function ItemRack_DisableEvent(eventname)

        if not ItemRack_Events[eventname] then return end

        local trigger = ItemRack_Events[eventname].trigger
        local has_event,i

        if ItemRack.Register[trigger] then
                ItemRack.Register[trigger][eventname] = nil
                for i in ItemRack.Register[trigger] do has_event=1 end
                if not has_event then
                        ItemRack_RegisterFrame:UnregisterEvent(trigger)
                        ItemRack.Register[trigger] = nil
                end
        else
                ItemRack_RegisterFrame:UnregisterEvent(trigger)
        end
end

-- use this to initialize, enable or refresh Register
function ItemRack_EnableAllEvents()
        local i

        if ItemRack_Settings.Notify=="OFF" then
                -- if notify is off, see if any ITEMRACK_NOTIFY events are registered and turn on notify
                for i in ItemRack_Users[user].Events do
                        if ItemRack_Users[user].Events[i].enabled and ItemRack_Events[i].trigger=="ITEMRACK_NOTIFY" then
                                ItemRack_Settings.Notify="ON"
--                              ItemRack_Opt_Notify:SetChecked(1)
                                DEFAULT_CHAT_FRAME:AddMessage("ItemRack: Notify has been turned on for Event: |cFFFFFF00"..i)
                        end
                end
        end

        if ItemRack_Settings.EnableEvents=="ON" then
                for i in ItemRack_Users[user].Events do
                        if ItemRack_Users[user].Events[i].enabled then
                                ItemRack_EnableEvent(i)
                        else
                                ItemRack_DisableEvent(i)
                        end
                end
                ItemRackFrame:RegisterEvent("PLAYER_AURAS_CHANGED")
        else
                ItemRack_DisableAllEvents()
        end
end

-- use this to trun off all event watching.
function ItemRack_DisableAllEvents()
        local i

        for i in ItemRack_Users[user].Events do
                ItemRack_DisableEvent(i)
        end
        ItemRackFrame:UnregisterEvent("PLAYER_AURAS_CHANGED")
end

--[[ Event Processing ]]--

ItemRack.EventQueue = {} -- indexed by events ("Riding", "Warrior:Battle") of GetTime()+delay to run

-- these two holders of arg1 and arg2 are separate for minimal processing to happen
-- a loop holding arg1-9 in a table takes 3.1 seconds for 100k iterations
-- storing just the first two values and moving on drops processing to 0.28 seconds
ItemRack.EventQueueArg1 = {} -- indexed by events also, values of arg1
ItemRack.EventQueueArg2 = {} -- indexed by events also, values of arg2

-- runs the eventname script, event = "Riding", "Warrior:Battle", etc
local function run_event_script(eventname)

        if eventname and ItemRack_Users[user].Events[eventname] then
                ItemRack.EventSetName = ItemRack_Users[user].Events[eventname].setname
                ItemRack.EventEventName = eventname
                if ItemRack.EventSetName then
                        RunScript(ItemRack_Events[eventname].script)
                        ItemRack.EventSetName = nil
                        ItemRack.EventEventName = nil
                end
        end
end

-- events("triggers") defined in game go through here
function ItemRack_RegisterFrame_OnEvent(event)
        local i,j

        if ItemRack.Register[event] then
                for i in ItemRack.Register[event] do
                        if ItemRack_Events[i].delay==0 then
                                -- EventSetName is the name of the set to use for EquipSet(), it's the set associated with the event
                                run_event_script(i)
                        else
                                ItemRack.EventQueue[i] = GetTime()+ItemRack_Events[i].delay
                                ItemRack.EventQueueArg1[i] = arg1
                                ItemRack.EventQueueArg2[i] = arg2
                                ItemRack_RegisterFrame:Show() -- turn on OnUpdate
                        end
                end
        end
end

local register_timer = 0
function ItemRack_RegisterFrame_OnUpdate()

        local i

        -- check every .25 seconds if time has elapsed for events with a delay
        register_timer = register_timer + arg1
        if register_timer > .25 then
                register_timer = 0
                local current_time = GetTime()
                local queue_exists = nil
                for i in ItemRack.EventQueue do
                        queue_exists = 1 -- something is in the queue
                        if ItemRack.EventQueue[i]<current_time then
                                local holdarg1,holdarg2 = arg1,arg2
                                arg1 = ItemRack.EventQueueArg1[i]
                                arg2 = ItemRack.EventQueueArg2[i]
                                run_event_script(i)
                                arg1 = holdarg1
                                arg2 = holdarg2
                                ItemRack.EventQueue[i] = nil
                        end
                end
                if not queue_exists then
                        ItemRack_RegisterFrame:Hide() -- shut down OnUpdates when nothing left to process
                end
        end
end

-- saves the items currently worn that EventSetName defines into a set named "ItemRack-Saved"
-- a parameter passed will use the passed setname, for use in macros, ie SaveSet("pvp")
function ItemRack_SaveSet(v1)
-- NO LONGER NEEDED: EquipSet saves what was worn previously
end

-- equips the items previous saved in SaveSet(). it only equips items in the slots associated with the set
-- a parameter passed will use the passed setname, for use in macros, ie LoadSet("pvp")
function ItemRack_LoadSet(setname)

        if not setname and ItemRack.EventSetName then
                setname = ItemRack.EventSetName
        end

        Rack.UnequipSet(setname)
end

function ItemRack_ToggleEvents()

        if ItemRack_Settings.EnableEvents=="OFF" then
                ItemRack_Settings.EnableEvents="ON"
                ItemRack_Opt_EnableEvents:SetChecked(1)
                ItemRack_EnableAllEvents()
                DEFAULT_CHAT_FRAME:AddMessage("|cFF66AAFFItemRack Automatic Events are now |cFF55FF55ON")
        else
                ItemRack_Settings.EnableEvents="OFF"
                ItemRack_Opt_EnableEvents:SetChecked(nil)
                ItemRack_DisableAllEvents()
                DEFAULT_CHAT_FRAME:AddMessage("|cFF66AAFFItemRack Automatic Events are now |cFFFF5555OFF")
        end
end

function ItemRack_EventsList_OnEnter()
        local idx,notes = this:GetID() + FauxScrollFrame_GetOffset(ItemRack_Events_ScrollFrame)

        if eventList[idx].name and ItemRack_Settings.ShowTooltips=="ON" then
                set_tooltip_anchor(this)
                GameTooltip:AddLine(eventList[idx].name)
                if eventList[idx].setname then
                        GameTooltip:AddLine("Set: "..eventList[idx].setname)
                end
                _,_,notes = string.find(eventList[idx].script or "","--%[%[(.+)%]%]")
                if notes then
                        GameTooltip:AddLine(notes,.8,.8,.8,1)
                end
                GameTooltip:Show()
        end
end

-- toggles a set, remembering what it wore previous to equipping the set
function ItemRack_ToggleSet(setname)
        Rack.ToggleSet(setname)
end

--[[ Event script helper functions

        These are not necessary.  They can be completely encapsulated in the scripts themselves.  They're here for convenience. ]]

-- this is a special function to use for mount events. returns true if player is mounted, nil otherwise
-- pass a non-nil value for v1 to do a slow/thorough scan
function ItemRack_PlayerMounted(v1)

        local i,buff,mounted

        for i=1,24 do
                buff = UnitBuff("player",i)
                if buff then
                        if problem_mounts[buff] or v1 or string.find(buff,"QirajiCrystal_") then
                                -- hunter could be in group, could be warlock epic mount etc, check if this is truly a mount
                                -- or if v1 is set to true, always check every buff. sigh this is slow but really no way around it without more data from users
                                Rack_TooltipScan:SetUnitBuff("player",i)
                                if string.find(Rack_TooltipScanTextLeft2:GetText() or "",ItemRackText.MOUNTCHECK) then
                                        mounted = true
                                        i = 25
                                end
                        elseif string.find(buff,"Mount_") then
                                mounted = true
                                i = 25
                        end
                else
                        i = 25
                end
        end

        return mounted
end

-- returns the name of the form the player is in
function ItemRack_GetForm()

        local i,name,form

        for i=1,GetNumShapeshiftForms() do
                _,name,is_active = GetShapeshiftFormInfo(i)
                if is_active then
                        form = name
                end
        end
        return form
end

-- gathers active buffs into ItemRack.Buffs and sends it via RegisterFrame for events
function ItemRack_BuffsChanged()
        local buffName,buffTexture
        for i in ItemRack.Buffs do
                ItemRack.Buffs[i] = nil
        end
        for i=1,24 do
                Rack_TooltipScan:SetUnitBuff("player",i)
                buffName = Rack_TooltipScanTextLeft1:GetText()
                buffTexture = UnitBuff("player",i)
                if buffTexture then
                        ItemRack.Buffs[buffName] = 1
                        ItemRack.Buffs[buffTexture] = 1
                else
                        break
                end
        end
        local oldarg1 = arg1
        arg1=ItemRack.Buffs
        ItemRack_RegisterFrame_OnEvent("ITEMRACK_BUFFS_CHANGED")
        arg1 = oldarg1
end

-- returns 1 if all pieces of setname are equipped, nil otherwise
function ItemRack_IsSetEquipped(setname)
        return Rack.IsSetEquipped(setname)
end

-- returns three parameters: table of sets, current set name, current set texture
function ItemRack_GetUserSets()
        local texture = "Interface\\AddOns\\ItemRack\\ItemRack-Icon"
        local setname = ItemRack_CurrentSet()
        if setname and Rack_User[user].Sets[setname] and not string.find(setname,"^Rack") and not string.find(setname,"^ItemRack") then
                texture = Rack_User[user].Sets[setname].icon
        end
        return Rack_User[user].Sets, setname, texture
end

--[[ Rack 2.0 code begins here ]]--

--[[ There is some redundancy because everything that follows is a part of ItemRack 2.0.
         Everything above this section will be scrapped for 2.0 ]]

Rack = {
        version = 1.9,
        debug = nil,

        TimerPool = {}, -- timer tables added here ["InvUpdate"]={timer,limit,func,rep}

        SetSwapping = nil, -- name of a set currently being swapped
        SwapList = {}, -- individual item swap details go here
        LockList = {}, -- tables of bags where slots are locked (to be skipped in FindItem and FindSpace)
        CombatQueue = {} -- table of items to swap in when dropping out of combat/death
}

Rack.SlotInfo = {
        [0] = { name="AmmoSlot", swappable=1, INVTYPE_AMMO=1 },
        [1] = { name="HeadSlot", INVTYPE_HEAD=1 },
        [2] = { name="NeckSlot", INVTYPE_NECK=1 },
        [3] = { name="ShoulderSlot", INVTYPE_SHOULDER=1 },
        [4] = { name="ShirtSlot", INVTYPE_BODY=1 },
        [5] = { name="ChestSlot", INVTYPE_CHEST=1, INVTYPE_ROBE=1 },
        [6] = { name="WaistSlot", INVTYPE_WAIST=1 },
        [7] = { name="LegsSlot", INVTYPE_LEGS=1 },
        [8] = { name="FeetSlot", INVTYPE_FEET=1 },
        [9] = { name="WristSlot", INVTYPE_WRIST=1 },
        [10] = { name="HandsSlot", INVTYPE_HAND=1 },
        [11] = { name="Finger0Slot", INVTYPE_FINGER=1 },
        [12] = { name="Finger1Slot", INVTYPE_FINGER=1 },
        [13] = { name="Trinket0Slot", INVTYPE_TRINKET=1 },
        [14] = { name="Trinket1Slot", INVTYPE_TRINKET=1 },
        [15] = { name="BackSlot", INVTYPE_CLOAK=1 },
        [16] = { name="MainHandSlot", swappable=1, INVTYPE_WEAPONMAINHAND=1, INVTYPE_2HWEAPON=1, INVTYPE_WEAPON=1 },
        [17] = { name="SecondaryHandSlot", swappable=1, INVTYPE_WEAPON=1, INVTYPE_WEAPONOFFHAND=1, INVTYPE_SHIELD=1, INVTYPE_HOLDABLE=1 },
        [18] = { name="RangedSlot", swappable=1, INVTYPE_RANGED=1, INVTYPE_THROWN=1, INVTYPE_RANGEDRIGHT=1 },
        [19] = { name="TabardSlot", INVTYPE_TABARD=1 },
}

--[[ Initialization ]]

function Rack.Initialize()

        for i=0,19 do Rack.SwapList[i]={} end -- create blank SwapList table
        for i=-2,10 do Rack.LockList[i]={} end -- create a blank Locked table

        if not Rack_User then
                Rack_User = {}
                Rack.ConvertOldSets()
        end

        if not Rack_User[user] then
                Rack_User[user] = { Sets={}, CurrentSet=nil }
        end

        Rack_User[user].Sets["Rack-CombatQueue"] = {}
        for i=0,19 do
                Rack_User[user].Sets["Rack-CombatQueue"][i] = { id=nil, name=nil }
        end

        -- note: these timers are added to a pool, they don't affect processing time unless started
        Rack.CreateTimer("WaitToIterate",Rack.IterateWait,1) -- itemlock iterate pause
        Rack.CreateTimer("IconDragging",Rack.IconDragging,0,1) -- minimap button dragging
        Rack.CreateTimer("ScaleUpdate",Rack.ScaleUpdate,.1,1) -- scaling
        Rack.CreateTimer("TooltipUpdate",Rack.TooltipUpdate,1,1) -- tooltip update
        Rack.CreateTimer("ControlFrame",Rack.ControlFrame,.75,1) -- controls on bar
        Rack.CreateTimer("CooldownUpdate",ItemRack_CooldownUpdate_OnUpdate,1,1) -- cooldown/notify
        Rack.CreateTimer("MenuFrame",Rack.MenuFrame,.25,1) -- menu mouseover check
        Rack.CreateTimer("InvUpdate",ItemRack_InvUpdate_OnUpdate,.25,1) -- inventory throttle
end

function Rack.OnEvent()

        if event=="ITEM_LOCK_CHANGED" then
                Rack.OnItemLockChanged()
        elseif (event=="PLAYER_REGEN_ENABLED" or event=="PLAYER_UNGHOST" or event=="PLAYER_ALIVE") and (not Rack.IsPlayerReallyDead() and not UnitAffectingCombat("player")) then
                -- player is coming out of combat or being res'ed.  EquipSet the CombatQueue
                local somethingQueued
                for i=0,19 do
                        if Rack.CombatQueue[i] then
                                Rack_User[user].Sets["Rack-CombatQueue"][i].id=Rack.CombatQueue[i]
                                Rack.CombatQueue[i] = nil
                                somethingQueued = 1
                        else
                                Rack_User[user].Sets["Rack-CombatQueue"][i].id = nil
                                Rack_User[user].Sets["Rack-CombatQueue"][i].name = nil
                        end
                        getglobal("ItemRackInv"..i.."Queue"):Hide()
                        getglobal(ItemRack.Indexes[i].paperdoll_slot.."Queue"):Hide()
                end
                if somethingQueued then
                        Rack.EquipSet("Rack-CombatQueue")
                end
        end
end

--[[ Item information ]]

-- returns itemTexture, itemID, itemName, itemSlot of an item in container(bag,slot) or inventory("player",bag)
function Rack.GetItemInfo(bag,slot)
        local i,j,id,itemLink,itemID,itemSlot,itemTexture,itemName

        if slot then -- this is a container item
                itemLink = GetContainerItemLink(bag,slot)
        else
                itemLink = GetInventoryItemLink("player",bag)
        end

        if itemLink then
                _,_,id = string.find(itemLink,"(item:%d+:%d+:%d+:%d+)")
                _,_,itemID = string.find(id or "","item:(%d+:%d+:%d+):%d+")
                itemName,_,_,_,_,_,_,itemSlot,itemTexture = GetItemInfo(id)
        elseif not slot then -- if no link and this is an inventory slot, missing or ammo
                _,itemTexture = GetInventorySlotInfo(Rack.SlotInfo[bag].name) -- get paperdoll texture
                itemID = 0 -- assume empty slot
                if bag==0 then -- this is an ammo slot
                        local ammoTexture = GetInventoryItemTexture("player",0)
                        if ammoTexture then
                                Rack_TooltipScan:SetInventoryItem("player",0)
                                itemName = Rack_TooltipScanTextLeft1:GetText()
                                for i=0,4 do -- look through containers to get itemID of this ammo
                                        for j=1,GetContainerNumSlots(i) do
                                                if itemName==Rack.GetContainerItemName(i,j) then
                                                        _,itemID,_,itemSlot = Rack.GetItemInfo(i,j)
                                                        i,j = 99,99
                                                end
                                        end
                                end
                                itemTexture = ammoTexture
                        end
                end
        end

        return itemTexture, itemID, itemName, itemSlot
end

-- returns the name of an item in bag,slot
function Rack.GetContainerItemName(bag,slot)
        local _,_,name = string.find(GetContainerItemLink(bag,slot) or "","%[(.+)%]")
        return name
end

-- converts a name to an itemID by searching through inventory, bags and bank for the item
function Rack.GetItemID(itemName)

        local inv,bag,slot = Rack.FindItem(nil,itemName)
        local itemID

        if inv then
                _,itemID = Rack.GetItemInfo(inv)
        elseif bag and slot then
                _,itemID = Rack.GetItemInfo(bag,slot)
        end

        return itemID
end

-- returns the name and texture of an item by its itemID
function Rack.GetNameByID(itemID)
        local name,texture
        local _,_,id = string.find(itemID or "","(%d+):%d+:%d+")
        name,_,_,_,_,_,_,_,texture = GetItemInfo(id or "")
        if itemID==0 then
                name = "(empty)"
                texture = "Interface\\PaperDoll\\UI-Backpack-EmptySlot"
        end
        return name,texture
end

-- returns true if the bagid (0-4) is a normal "Container", as opposed to quivers and ammo pouches
function Rack.ValidBag(bagid)

        local linkid,bagtype,legal

        if bagid==0 or bagid==-1 then
                legal = true
        else
                local invID = ContainerIDToInventoryID(bagid)
                _,_,linkid = string.find(GetInventoryItemLink("player",invID) or "","item:(%d+)")
                if linkid then
                        _,_,_,_,_,bagtype = GetItemInfo(linkid)
                        if bagtype==ItemRackText.INVTYPE_CONTAINER then -- "Bag" for enUS clients, "Container" for other clients
                                legal = true -- this is a true container
                        end
                end
        end

        return legal
end

function Rack.FindSpaceInBag(bag)
        if Rack.ValidBag(bag) then
                for j=1,GetContainerNumSlots(bag) do
                        if not Rack.LockList[bag][j] and not GetContainerItemLink(bag,j) then
                                return j
                        end
                end
        end
end

-- pass a value for bank to find a free bank slot
function Rack.FindSpace(bank)
        local slot
        if bank and ItemRack.BankIsOpen then -- search bank
                for _,i in ItemRack.BankSlots do
                        slot = Rack.FindSpaceInBag(i)
                        if slot then
                                Rack.LockList[i][slot] = 1
                                return i,slot
                        end
                end
        else
                for i=4,0,-1 do
                        slot = Rack.FindSpaceInBag(i)
                        if slot then
                                Rack.LockList[i][slot] = 1
                                return i,slot
                        end
                end
        end
end

-- clears locks on all bag slots
function Rack.ClearLockList(bag,slot)
        if not bag then
                for i=-2,10 do
                        for j in Rack.LockList[i] do
                                Rack.LockList[i][j] = nil
                        end
                end
        else
                Rack.LockList[bag][slot] = nil
        end
end

-- returns inv,bag,slot of an itemID, or itemName if itemID doesn't exist
function Rack.FindItem(itemID,itemName,passive)

        local bagStart,bagEnd,i,j,id,name = 0,4
        local inv,bag,slot

        if itemID then

                for i=bagStart,bagEnd do -- check bags for itemID
                        for j=1,GetContainerNumSlots(i) do
                                if not Rack.LockList[i][j] or passive then
                                        _,id = Rack.GetItemInfo(i,j)
                                        if id==itemID then
                                                bag,slot = i,j
                                                i,j = 99,99
                                        end
                                end
                        end
                end

                if not bag then
                        for i=0,19 do -- if not in bags, check on person for itemID
                                if not Rack.LockList[-2][i] or passive then
                                        _,id = Rack.GetItemInfo(i)
                                        if id==itemID then
                                                inv = i
                                                i = 99
                                        end
                                end
                        end
                end
        end

        -- if an exact itemID match wasn't found, search by name
        if not inv and not bag and not slot then
                itemName = itemName or Rack.GetNameByID(itemID)
                if itemName then

                        for i=bagStart,bagEnd do -- check bags for itemID
                                for j=1,GetContainerNumSlots(i) do
                                        if not Rack.LockList[i][j] or passive then
                                                _,_,name = Rack.GetItemInfo(i,j)
                                                if name==itemName then
                                                        bag,slot = i,j
                                                        i,j = 99,99
                                                end
                                        end
                                end
                        end

                        if not bag then
                                for i=0,19 do -- check on person for itemName
                                        if not Rack.LockList[-2][i] or passive then
                                                _,_,name = Rack.GetItemInfo(i)
                                                if name==itemName then
                                                        inv = i
                                                        i = 99
                                                end
                                        end
                                end
                        end
                end
        end

        return inv,bag,slot
end

-- performs a FindItem on a set entry ([0]-[19]) and updates itemid if one wasn't there
function Rack.FindSetItem(setslot)
        local inv,bag,slot = Rack.FindItem(setslot.id,setslot.name)
        if not setslot.id and setslot.name and (inv or bag or slot) then
                setslot.id = Rack.GetItemID(setslot.name)
        end
        return inv,bag,slot
end

function Rack.InvToInvSwap(inv1,inv2)

        local swap = Rack.SwapList

        if swap[inv1].sourceInv==inv2 and swap[inv2].sourceInv==inv1 then
                PickupInventoryItem(inv1)
                PickupInventoryItem(inv2)
                Rack.ClearSwapListEntry(inv1)
                Rack.ClearSwapListEntry(inv2)
        end
end


--[[ Queue maintenance

        To prevent garbage creation in creating/destroying multi-level tables, queue entries are reused and only
        the index to that queue entry is created/destroyed.

        .SwapQueue is where the tables sit, indexed arbitrarily by the first available one returned by GetFreeQueueEntry
        .SwapQueueOrder is a numerically-indexed table of indexes into .SwapQueue in the order the swap should happen
]]

Rack.SwapQueue = { [1]={ direction = "END" } } -- numerically-indexed queue of swaps to perform
Rack.SwapQueueOrder = {} -- numerically-indexed queue of numbers in the order they're to be performed

-- wipes out an entry without creating garbage
function Rack.ClearQueueEntry(idx)

        Rack.SwapQueue[idx].direction = nil
        Rack.SwapQueue[idx].setname = nil
        for i=0,19 do
                Rack.SwapQueue[idx][i].id = nil
                Rack.SwapQueue[idx][i].fromBag = nil
                Rack.SwapQueue[idx][i].fromSlot = nil
        end
end

-- creates a new table and sub-table queue entries, only call when entry didn't exist before
function Rack.ConstructQueueEntry(idx)
        Rack.SwapQueue[idx] = {}
        for i=0,19 do
                Rack.SwapQueue[idx][i] = {}
        end
end

-- get first available index into SwapQueue for use for a set. to avoid garbage creation these are reused
function Rack.GetFreeQueueEntry()
        local i,found = 1,1

        while found do
                found = Rack.SwapQueue[i]
                if found and not found.direction then
                        return i
                end
                i = i + 1
        end
        i = i - 1 -- backtrack to last available entry

        Rack.ConstructQueueEntry(i)
        return i
end

-- adds the .SwapQueue idx to the end of the QueueOrder
function Rack.AddQueueEntry(idx)
        table.insert(Rack.SwapQueueOrder,idx)
end

-- removes the .SwapQueue idx from QueueOrder and clears the .SwapQueue entry
function Rack.RemoveQueueEntry(idx)
        for i=1,table.getn(Rack.SwapQueueOrder) do
                if Rack.SwapQueueOrder[i]==idx then
                        table.remove(Rack.SwapQueueOrder,i)
                        Rack.ClearQueueEntry(idx)
                        return
                end
        end
end

-- this sorts the queue, moving all NEWSETs to the end
function Rack.SortQueue()

        if table.getn(Rack.SwapQueueOrder)>1 then
                local found,temp = 1
                local queue = Rack.SwapQueueOrder
                -- simple shell sort (this is a small, 5-6 max typically) table of indexes that must remain in order otherwise
                while found do
                        found = nil
                        for i=1,(table.getn(queue)-1) do
                                if Rack.SwapQueue[queue[i]].direction=="NEWSET" and Rack.SwapQueue[queue[i+1]].direction~="NEWSET" then
                                        temp = queue[i]
                                        queue[i] = queue[i+1]
                                        queue[i+1] = temp
                                        found = 1
                                end
                        end
                end
        end
end

--[[ Set Maintenance

        Sets are tables in Rack_User[user].Sets[setname] structured as:
        { hide=1/nil, icon="Interface\\Icons\\etc", ["0"]={id=string,name=string,old=string} }
        id = itemID for the item to wear in the set, 0 for (empty) (can be nil)
        name = name of the item to wear in the set, (empty) for empty
        old = itemID for the item worn in this slot prior to this set equipped, usually nil
]]

-- this converts all sets from ItemRack_Users
function Rack.ConvertOldSets()

        local old,new

        DEFAULT_CHAT_FRAME:AddMessage("Performing one-time conversion of ItemRack sets.")
        for u in ItemRack_Users do
                if not Rack_User[u] then
                        Rack_User[u] = { Sets={} }
                end
                Rack_User[u].CurrentSet = ItemRack_Users[u].CurrentSet
                for sets in ItemRack_Users[u].Sets do
                        if not Rack_User[u].Sets[sets] then
                                Rack_User[u].Sets[sets] = {}
                        end
                        old = ItemRack_Users[u].Sets[sets]
                        new = Rack_User[u].Sets[sets]

                        for i=0,19 do
                                if old[i] then
                                        new[i] = { name=old[i] }
                                        if old[i]=="(empty)" then
                                                new[i].id = 0
                                        else
                                                new[i].id = Rack.GetItemID(old[i]) or nil
                                        end
                                end
                        end
                        new.icon = old.icon
                        new.hide = old.hide
                        new.key = old.key
                        new.keyindex = old.keyindex
                end
        end
end

function Rack.ClearSwapListEntry(idx)
        Rack.SwapList[idx].needsSwap = nil
        Rack.SwapList[idx].sourceInv = nil
        Rack.SwapList[idx].sourceBag = nil
        Rack.SwapList[idx].sourceSlot = nil
        Rack.SwapList[idx].direction = nil
        Rack.SwapList[idx].desiredName = nil
        Rack.SwapList[idx].needsEmptied = nil
end

-- returns the currently worn set, or last set worn
function Rack.CurrentSet()
        return Rack_User[user].CurrentSet
end

--[[ EquipSet
        This function takes a setname and then equips the set, saving what it's replacing within the set.
]]

function Rack.EquipSet(setname)

        local i,bag,slot,id,swap,idx
        local mustWait -- flag that this swap must happen in stages (INV needs to move out of the way)
        local set = Rack_User[user].Sets[setname]
        local hasTWOHAND, hasINVTOBAG, hasINVTOINV, hasBAGTOINV, hasAMMO
        local missing = "ItemRack could not find: "
        local invStart,invEnd = 1,19 -- changes to 16,18 if in combat

        if not set then
                DEFAULT_CHAT_FRAME:AddMessage("Rack: Set \""..setname.."\" doesn't exist.")
                return
        end

        if Rack.IsSetEquipped(setname) then
                if not string.find(setname,"^Rack-") and not string.find(setname,"^ItemRack") then
                        Rack_User[user].CurrentSet = setname
                end
                Rack.StartTimer("InvUpdate")
                return
        end

        Rack.ClearLockList()

        if Rack.SetSwapping==setname then
                Rack.OnItemLockChanged() -- if trying to swap this already, move along in queue
                return
        end

        if (Rack.SetSwapping or Rack.IsPlayerReallyDead()) and not UnitAffectingCombat("player") then
                -- come back later, an EquipSet is in progress
                idx = Rack.GetFreeQueueEntry()
                Rack.AddQueueEntry(idx)
                Rack.SwapQueue[idx].direction = "NEWSET"
                Rack.SwapQueue[idx].setname = setname
                return
        end

        -- pre-scan for items that don't need to move
        for i=0,19 do
                _,id = Rack.GetItemInfo(i)

                if set[i] and (set[i].id or set[i].name) then
                        Rack.FindSetItem(set[i])
                        if set[i].id==id then
                                Rack.LockList[-2][i] = 1
                        else
                                set[i].old = id -- store what was there previously
                        end
                end
                Rack.ClearSwapListEntry(i)
        end

        if UnitAffectingCombat("player") then -- player is in combat
                for i=1,19 do
                        if set[i] and set[i].id and not Rack.SlotInfo[i].swappable then
                                Rack.AddToCombatQueue(i,set[i].id)
                        end
                end
                invStart,invEnd = 16,18 -- restrict swap to weapons only
        elseif Rack.IsPlayerReallyDead() then -- player is dead
                for i=0,19 do
                        if set[i] and set[i].id then
                                Rack.AddToCombatQueue(i,set[i].id)
                        end
                end
                return
        end

        -- determine what needs swapped and populate Rack.SwapList
        -- skipping ammo slot the black sheep of inventory slots
        for i=invStart,invEnd do
                if set[i] and set[i].id then
                        _,id = Rack.GetItemInfo(i)
                        if id~=set[i].id then
                                swap = Rack.SwapList[i]
                                swap.needsSwap = 1
                                swap.desiredName = Rack.GetNameByID(set[i].id)
                                if set[i].id==0 then -- empty slot
                                        swap.direction="INVTOBAG"
                                        swap.needsEmptied = 1
                                        hasINVTOBAG = 1
                                else
                                        inv,bag,slot = Rack.FindSetItem(set[i])
                                        if inv then -- found it in another inventory slot
                                                Rack.LockList[-2][inv] = 1
                                                _,_,_,sourceEquipSlot = Rack.GetItemInfo(inv)
                                                _,_,_,destEquipSlot = Rack.GetItemInfo(i)
                                                swap.direction="INVTOINV" -- if the item can exist in both slots
                                                swap.sourceInv = inv
                                                hasINVTOINV = 1
                                                if destEquipSlot and sourceEquipSlot and not (Rack.SlotInfo[i][sourceEquipSlot] and Rack.SlotInfo[inv][destEquipSlot]) then
                                                        swap.needsEmptied = 1
                                                        hasINVTOBAG = 1
                                                end
                                        elseif bag and slot then -- found it in a bag slot
                                                Rack.LockList[bag][slot] = 1
                                                swap.direction="BAGTOINV"
                                                swap.sourceBag = bag
                                                swap.sourceSlot = slot
                                                if i==16 then -- this is a mainhand weapon
                                                        _,_,_,slot = Rack.GetItemInfo(swap.sourceBag,swap.sourceSlot)
                                                        if slot=="INVTYPE_2HWEAPON" and GetInventoryItemLink("player",17) then
                                                                hasTWOHAND = 1 -- flag a 2h swap in this set if there's an offhand equipped
                                                                if not set[17] then
                                                                        set[17] = {}
                                                                end
                                                                set[17].id = 0
                                                                Rack.SwapList[17].needsEmptied = 1
                                                        end
                                                end
                                                hasBAGTOINV = 1
                                        else -- couldn't find it
                                                missing = missing..tostring(swap.desiredName)..", "
                                                swap.needsSwap = nil
                                        end
                                end
                        end
                elseif set[i] and set[i].name then -- item has no id yet, not seen since conversion
                        missing = missing..tostring(set[i].name)..", "
                end
        end

        if missing~="ItemRack could not find: " then
                DEFAULT_CHAT_FRAME:AddMessage(string.gsub(missing,", $",""))
        end

        -- at this stage, Rack.SwapList[0]-[19] is populated

        -- INVTOBAG and .needsEmptied swaps first
        if hasINVTOBAG then
                idx = Rack.GetFreeQueueEntry()
                Rack.AddQueueEntry(idx)
                Rack.SwapQueue[idx].direction = "INVTOBAG"
                Rack.SwapQueue[idx].setname = setname
                for i=0,19 do
                        if Rack.SwapList[i].needsEmptied then
                                Rack.SwapQueue[idx][i].id = 0
                        end
                end
        end

        -- INVTOINV swaps next
        if hasINVTOINV then
                idx = Rack.GetFreeQueueEntry()
                Rack.AddQueueEntry(idx)
                Rack.SwapQueue[idx].direction = "INVTOINV"
                Rack.SwapQueue[idx].setname = setname
                for i=0,19 do
                        if Rack.SwapList[i].direction=="INVTOINV" then
                                Rack.SwapQueue[idx][i].id = set[i].id
                                Rack.SwapQueue[idx][i].fromSlot = Rack.SwapList[i].sourceInv
                        end
                end
        end

        -- BAGTOINV swaps last (90% of swaps this is only bit that queues)
        if hasBAGTOINV then
                idx = Rack.GetFreeQueueEntry()
                Rack.AddQueueEntry(idx)
                Rack.SwapQueue[idx].direction = "BAGTOINV"
                Rack.SwapQueue[idx].setname = setname
                for i=0,19 do
                        if Rack.SwapList[i].direction=="BAGTOINV" then
                                Rack.SwapQueue[idx][i].id = set[i].id
                                Rack.SwapQueue[idx][i].fromBag = Rack.SwapList[i].sourceBag
                                Rack.SwapQueue[idx][i].fromSlot = Rack.SwapList[i].sourceSlot
                        end
                end
        end

        -- now deal with ammo why oh why can't this slot be normal
        -- perform ammo swaps directly. since it never takes up a new bag slot it's ok to do pickups and move on
        if set[0] and set[0].id then
                _,id = Rack.GetItemInfo(0)
                if id~=set[0].id then
                        if set[0].id==0 then -- unequip ammo
                                bag,slot = Rack.FindSpace()
                                if bag then
                                        PickupInventoryItem(0)
                                        PickupContainerItem(bag,slot)
                                end
                        else
                                _,bag,slot = Rack.FindSetItem(set[0])
                                if bag then
                                        PickupContainerItem(bag,slot)
                                        PickupInventoryItem(0)
                                end
                        end
                end
        end

        Rack_User[user].Sets[setname].oldsetname = Rack_User[user].CurrentSet

        if Rack_User[user].Sets[setname].showhelm then
                ShowHelm(Rack_User[user].Sets[setname].showhelm)
        end
        if Rack_User[user].Sets[setname].showcloak then
                ShowCloak(Rack_User[user].Sets[setname].showcloak)
        end

        -- at last, perform the swaps by iterating over the queue
        Rack.IterateSwapQueue()

end

-- waits for some timed reason to do another iteration. for now just to check SpellIsTargeting once a second
function Rack.IterateWait()

        if SpellIsTargeting() or CursorHasItem() then
                Rack.StartTimer("WaitToIterate",1)
        else
                Rack.StopTimer("WaitToIterate")
                Rack.IterateSwapQueue()
        end
end

function Rack.ShutdownQueue()
        RackFrame:UnregisterEvent("ITEM_LOCK_CHANGED")
        Rack.SetSwapping = nil
        for i=1,table.getn(Rack.SwapQueueOrder) do
                Rack.RemoveQueueEntry(Rack.SwapQueueOrder[i])
        end
end

-- this function grabs the next swap QueueEntry and performs the swap
-- swaps only happen one direction at a time: INVTOBAG->INVTOINV->BAGTOINV
-- complex swaps can require running this a few times
function Rack.IterateSwapQueue()

        if table.getn(Rack.SwapQueueOrder)<1 then
                -- if queue is empty, unregister and leave
                Rack.SetSwapping = nil
                RackFrame:UnregisterEvent("ITEM_LOCK_CHANGED")
                return
        
        elseif SpellIsTargeting() or CursorHasItem() then
                -- check if in Spell Targeting mode to prevent disenchants/enchants
                Rack.StartTimer("WaitToIterate")
                return

        elseif Rack.IsPlayerReallyDead() then
                -- if player is dead, they can't swap anything, leave for now
                return

        else

                Rack.SortQueue() -- move NEWSETs to end of queue

                local bag,slot,id
                local idx = Rack.SwapQueueOrder[1]
                local queue = Rack.SwapQueue[idx]

                if queue.direction == "NEWSET" then
                        Rack.SetSwapping = nil
                        local setname = queue.setname
                        Rack.RemoveQueueEntry(idx)
                        RackFrame:UnregisterEvent("ITEM_LOCK_CHANGED")
                        Rack.EquipSet(setname)

                else

                        -- something to process
                        RackFrame:RegisterEvent("ITEM_LOCK_CHANGED")

                        Rack.SetSwapping = Rack.SwapQueue[idx].setname

                        Rack.ClearLockList()

                        if queue.direction == "INVTOBAG" then
                                for i=0,19 do
                                        if queue[i].id==0 then
                                                bag,slot = Rack.FindSpace()
                                                if bag then
                                                        queue[i].fromBag = bag
                                                        queue[i].fromSlot = slot
                                                        PickupInventoryItem(i)
                                                        PickupContainerItem(bag,slot)
                                                else
                                                        Rack.NoMoreRoom()
                                                        queue[i].id = nil
                                                        queue[i].fromBag = nil
                                                        queue[i].fromSlot = nil
                                                        Rack.ShutdownQueue() -- we ran out of room, stop all swaps now
                                                end
                                        end
                                end
                        elseif queue.direction == "INVTOINV" then
                                for i=0,19 do
                                        if queue[i].fromSlot then
                                                PickupInventoryItem(queue[i].fromSlot)
                                                PickupInventoryItem(i)
                                        end
                                end
                        elseif queue.direction == "BAGTOINV" then
                                for i=0,19 do
                                        if queue[i].fromBag then
                                                _,id = Rack.GetItemInfo(queue[i].fromBag,queue[i].fromSlot)
                                                if id == queue[i].id then
                                                        PickupContainerItem(queue[i].fromBag,queue[i].fromSlot)
                                                        PickupInventoryItem(i)
                                                else
                                                        -- didn't find it at same bag spot when queued
                                                        _,bag,slot = Rack.FindSetItem(queue[i])
                                                        if bag then
                                                                queue[i].fromBag = bag
                                                                queue[i].fromSlot = slot
                                                                PickupContainerItem(bag,slot)
                                                                PickupInventoryItem(i)
                                                        else
                                                                queue[i].id = nil -- forget we tried
                                                                queue[i].fromBag = nil
                                                                queue[i].fromSlot = nil
                                                        end
                                                end
                                        end
                                end
                        end
                end
        end

end

--[[ Debug ]]--

function Rack.PrintSet(setname)

        local i,j
        for i=0,19 do
                j=Rack_User[user].Sets[setname]
                if j and j[i] then DEFAULT_CHAT_FRAME:AddMessage(Rack.SlotInfo[i].name..": "..tostring(j[i].id).." : "..tostring(Rack.GetNameByID(j[i].id)).." old:"..tostring(j[i].old)) end
        end
end

function Rack.PrintQueue()

        local txt = ""

        local function debug(msg)
                DEFAULT_CHAT_FRAME:AddMessage(msg)
        end

        debug("__ Rack.PrintQueue() __")
        debug("table.getn(Rack.SwapQueueOrder)="..tostring(table.getn(Rack.SwapQueueOrder)))
        txt = "Rack.SwapQueueOrder = { "
        for i=1,table.getn(Rack.SwapQueueOrder) do
                txt = txt.."["..i.."]="..tostring(Rack.SwapQueueOrder[i])..", "
        end
        txt = txt.."}"
        debug(txt)
        txt = "Rack.SwapQueue = { "
        for i in Rack.SwapQueue do
                txt = txt.."["..i.."]= { direction="..tostring(Rack.SwapQueue[i].direction)..", "
                txt = txt.."setname="..tostring(Rack.SwapQueue[i].setname)..", "
                for j=0,19 do
                        if Rack.SwapQueue[i][j] and Rack.SwapQueue[i][j].id then
                                txt = txt.."["..j.."]="..Rack.SwapQueue[i][j].id..", "
                                txt = txt.."fromBag="..tostring(Rack.SwapQueue[i][j].fromBag)..", "
                                txt = txt.."fromSlot="..tostring(Rack.SwapQueue[i][j].fromSlot)..", "
                        end
                end
                txt = txt .."} "
        end
        txt = txt.."}"
        debug(txt)

end

--[[ Locks ]]--

-- returns true if anything is locked
function Rack.AnyLocked()

        local locked,isLocked

        for i=0,19 do
                if IsInventoryItemLocked(i) then
                        locked = 1
                        i = 99
                end
        end

        if not locked then
                for i=0,4 do
                        for j=1,GetContainerNumSlots(i) do
                                _,_,isLocked = GetContainerItemInfo(i,j)
                                if isLocked then
                                        locked = 1
                                        i,j=99,99
                                end
                        end
                end
        end

        return locked
end

-- when an iteration begins, on each ITEM_LOCK_CHANGED check if the swaps are done
-- if so, remove the current queue entry and go to the next one
-- this is where swaps end
function Rack.OnItemLockChanged()

        if not Rack.SwapQueueOrder[1] then
                Rack.SetSwapping = nil
                RackFrame:UnregisterEvent("ITEM_LOCK_CHANGED")
                return
        end

        if not Rack.AnyLocked() then
                local setname = Rack.SwapQueue[Rack.SwapQueueOrder[1]].setname
                if not string.find(setname,"^Rack-") and not string.find(setname,"^ItemRack") then
                        Rack_User[user].CurrentSet = setname
                end
                Rack.RemoveQueueEntry(Rack.SwapQueueOrder[1])
                Rack.IterateSwapQueue()
        end     
end

--[[ Combat/Death Queue Processing ]]

function Rack.IsPlayerReallyDead()
        local dead = UnitIsDeadOrGhost("player")
        local _,class = UnitClass("player")
        if class~="HUNTER" then
                return dead
        end
        for i=1,24 do
                if UnitBuff("player",i)=="Interface\\Icons\\Ability_Rogue_FeignDeath" then
                        dead = nil -- player is just FD, not really dead
                end
        end
        return dead
end

-- adds an item 'id' to 'slot' queue for post-combat/death swap
function Rack.AddToCombatQueue(slot,id)
        local button = getglobal("ItemRackInv"..slot.."Queue")
        local paperdoll = getglobal(ItemRack.Indexes[slot].paperdoll_slot.."Queue")
        local _,wornId = Rack.GetItemInfo(slot)
        if Rack.CombatQueue[slot]==id or id==wornId then
                Rack.CombatQueue[slot] = nil
                button:Hide()
                paperdoll:Hide()
        elseif id and id~=wornId then
                Rack.CombatQueue[slot] = id
                local _,texture = Rack.GetNameByID(id)
                button:SetTexture(texture)
                button:Show()
                paperdoll:SetTexture(texture)
                paperdoll:Show()
        end
end

--[[ EquipSet wrappers ]]--

-- returns true if the entire set is currently worn
function Rack.IsSetEquipped(setname)

        local set,missing,id = Rack_User[user].Sets[setname or ""]

        if set then
                for i=0,19 do
                        if set[i] then
                                -- if item queued, check queued item instead of GetItemInfo
                                if Rack.CombatQueue[i] then
                                        id = Rack.CombatQueue[i]
                                else
                                        _,id = Rack.GetItemInfo(i)
                                end
                                if set[i].id ~= id then
                                        missing = 1
                                end
                        end
                end
        end
        return set and not missing
end

-- equips a set if it's worn, unequips it if not
function Rack.ToggleSet(setname)

        if Rack_User[user].Sets[setname or ""] then
                if Rack.IsSetEquipped(setname) then
                        Rack.UnequipSet(setname)
                else
                        Rack.EquipSet(setname)
                end
        end
end

-- This function creates a set of all the items replaced by setname, and then does does an .EquipSet()
function Rack.UnequipSet(setname)

--      if not Rack.IsSetEquipped(setname) then
--              return
--      end

        local unequip_setname = "Rack-Unequip-"..setname
        Rack_User[user].Sets[unequip_setname] = {}
        local old = Rack_User[user].Sets[setname]
        local new = Rack_User[user].Sets[unequip_setname]

        for i=0,19 do
                if old[i] and old[i].old then
                        new[i] = { id=old[i].old }
                        old[i].old = nil -- comment this line when a mechanism to catch enchant changes
                end
        end

        Rack.EquipSet(unequip_setname)
        if old.oldsetname and not string.find(old.oldsetname,"^Rack") and not string.find(old.oldsetname,"^ItemRack") then
                Rack_User[user].CurrentSet = Rack_User[user].Sets[setname].oldsetname
        end
        Rack_User[user].Sets[setname].oldsetname = nil
end

--[[ Timer maintenance

        The goal is to reduce the processing of timer checks to as little as possible.  One OnUpdate is a central
        repository of this mod's timers.
]]


function Rack.CreateTimer(name,func,limit,rep)
        Rack.TimerPool[name] = { func=func, limit=limit, timer=limit, rep=rep, enabled=nil }
end

function Rack.StartTimer(name,timer)
        Rack.TimerPool[name].timer = timer or Rack.TimerPool[name].limit
        Rack.TimerPool[name].enabled = 1
        RackFrame:Show()
end

function Rack.StopTimer(name)
        Rack.TimerPool[name].enabled = nil
        Rack.ClearStoppedTimers()
end

function Rack.ClearStoppedTimers()
        local stuffLeft
        for i in Rack.TimerPool do
                stuffLeft = stuffLeft or Rack.TimerPool[i].enabled
        end
        if not stuffLeft then
                RackFrame:Hide()
        end
end

function Rack.TimerEnabled(name)
        if Rack.TimerPool[name] and Rack.TimerPool[name].enabled then
                return 1
        else
                return nil
        end
end

function Rack.OnUpdate()

        local clock,stopped
        local elapse = tonumber(arg1) or 0.1

        for i in Rack.TimerPool do
                clock = Rack.TimerPool[i]
                if clock.enabled then
                        clock.timer = clock.timer - elapse
                        if clock.timer<0 then
                                if clock.rep then
                                        clock.timer = clock.limit -- rewind to start if 'rep' set
                                else
                                        clock.enabled = nil
                                        stopped = 1
                                end
                                clock.func()
                        end
                end
        end
        if stopped then
                Rack.ClearStoppedTimers()
        end

end

--[[ old OnUpdates : now gathered under Rack.Timers ]]

-- formerly ItemRack_IconDraggingFrame_OnUpdate
function Rack.IconDragging()
        local xpos,ypos = GetCursorPosition()
        local xmin,ymin = Minimap:GetLeft(), Minimap:GetBottom()

        xpos = xmin-xpos/Minimap:GetEffectiveScale()+70
        ypos = ypos/Minimap:GetEffectiveScale()-ymin-70

        ItemRack_Settings.IconPos = math.deg(math.atan2(ypos,xpos))
        move_icon()
end
ItemRack_IconDraggingFrame_OnUpdate = Rack.IconDragging -- legacy

-- formerly ItemRack_ScaleUpdate_OnUpdate
function Rack.ScaleUpdate()
        local frame = ItemRack.FrameToScale
        local oldscale = frame:GetEffectiveScale()
        local framex, framey, cursorx, cursory = frame:GetLeft()*oldscale, frame:GetTop()*oldscale, GetCursorPosition()

        if (cursorx-framex)>32 then
                local newscale = (cursorx-framex)/ItemRack.ScalingWidth
                ItemRack_ScaleFrame(newscale)
        end
end

-- formerly ItemRack_TooltipUpdate_OnUpdate
function Rack.TooltipUpdate()
        if ItemRack.TooltipType then

                local cooldown

                set_tooltip_anchor(ItemRack.TooltipOwner)

                if ItemRack.TooltipType=="BAG" then
                        if ItemRack.TooltipBag==-1 then
                                local _,_,id = string.find(GetContainerItemLink(ItemRack.TooltipBag,ItemRack.TooltipSlot) or "","item:(%d+)")
                                if id then
                                        _,id = GetItemInfo(id)
                                        if id then
                                                -- :SetBagItem doesn't appear to work for -1 bank slot
                                                GameTooltip:SetHyperlink(id)
                                        end
                                end
                        else
                                GameTooltip:SetBagItem(ItemRack.TooltipBag,ItemRack.TooltipSlot)
                        end
                        cooldown = GetContainerItemCooldown(ItemRack.TooltipBag,ItemRack.TooltipSlot)
                        if ItemRack_Settings.TinyTooltip=="ON" and not IsAltKeyDown() then
                                shrink_tooltip()
                        end
                elseif ItemRack.TooltipType=="INVENTORY" then
                        GameTooltip:SetInventoryItem("player",ItemRack.TooltipSlot)
                        cooldown = GetInventoryItemCooldown("player",ItemRack.TooltipSlot)
                        if ItemRack_Settings.TinyTooltip=="ON" and not IsAltKeyDown() then
                                shrink_tooltip()
                        end
                        if ItemRack.TooltipBag then
                                GameTooltip:AddLine(string.format(ItemRackText.QUEUED,ItemRack.TooltipBag))
                        end
                end
                GameTooltip:Show()
                if cooldown==0 then
                        -- stop updates if this item has no cooldown
                        Rack.StopTimer("TooltipUpdate")
                end
        end
end

-- formerly ItemRack_ControlFrame_OnUpdate
function Rack.ControlFrame()
        if ItemRack_Users[user].Locked=="ON" and not IsAltKeyDown() then
                Rack.StopTimer("ControlFrame")
                ItemRack_ControlFrame:Hide()
        end
end

Rack.MenuFrameSources = { "ItemRack_InvFrame", "ItemRack_MenuFrame", "ItemRack_IconFrame",
                                                  "TitanPanelItemRackButton" }

-- formerly ItemRack_MenuFrame_OnUpdate
function Rack.MenuFrame()

        local over,frame
        
        for i=1,table.getn(Rack.MenuFrameSources) do
                frame = getglobal(Rack.MenuFrameSources[i])
                over = over or (frame and MouseIsOver(frame))
        end

        if MouseIsOver(ItemRack_SetsFrame) and
          (string.sub(GetMouseFocus():GetName() or "",1,17)=="ItemRack_Sets_Inv") and
          GetMouseFocus():GetAlpha()>.5 then
                over = 1
        end

        if not over then
                ItemRack_MenuFrame:Hide()
        end
end

--[[ Bank support ]]

function Rack.PopulateBank()
        Rack.UnpopulateBank()
        local itemLink,itemID,itemName,equipLoc
        for i=1,table.getn(ItemRack.BankSlots) do
                for j=1,GetContainerNumSlots(ItemRack.BankSlots[i]) do
                        itemLink = GetContainerItemLink(ItemRack.BankSlots[i],j) or ""
                        _,_,itemID = string.find(itemLink,"item:(%d+)")
                        if itemID then
                                itemName,_,_,_,_,_,_,equipLoc = GetItemInfo(itemID)
                                if equipLoc and equipLoc~="" then
                                        ItemRack.BankedItems[itemName] = 1
                                end
                        end
                end
        end
end

function Rack.UnpopulateBank()
        for i in ItemRack.BankedItems do
                ItemRack.BankedItems[i] = nil
        end
end

function Rack.BankOpened()
        Rack.PopulateBank()
        ItemRack.BankIsOpen = 1
        ItemRackFrame:RegisterEvent("BAG_UPDATE")
end

function Rack.BankClosed()
        ItemRack.BankIsOpen = nil
        ItemRack_MenuFrame:Hide()
        Rack.UnpopulateBank()
        ItemRackFrame:UnregisterEvent("BAG_UPDATE")
end

function Rack.SetHasBanked(setname)
        if not setname or not Rack_User[user].Sets[setname] then return end
        local name,item
        for i=0,19 do
                item = Rack_User[user].Sets[setname][i]
                if item and item.name and ItemRack.BankedItems[item.name] then
                        return 1
                end
        end
end

function Rack.FindBankedItem(name)
        for _,i in ItemRack.BankSlots do
                for j=1,GetContainerNumSlots(i) do
                        if strfind(GetContainerItemLink(i,j) or "",name,1,1) then
                                return i,j
                        end
                end
        end
end

function Rack.PullSetFromBank(setname)
        Rack.ClearLockList()
        local set = Rack_User[user].Sets[setname]
        if not set or SpellIsTargeting() or CursorHasItem() then return end
        local bag,slot,freeBag,freeSlot
        for i=0,19 do
                if set[i] then
                        if ItemRack.BankedItems[set[i].name] then
                                bag,slot = Rack.FindBankedItem(set[i].name)
                                if bag then
                                        freeBag,freeSlot = Rack.FindSpace()
                                        if freeBag then
                                                PickupContainerItem(bag,slot)
                                                PickupContainerItem(freeBag,freeSlot)
                                        else
                                                Rack.NoMoreRoom()
                                                return
                                        end
                                end
                        end
                end
        end
end

function Rack.PushSetToBank(setname)
        Rack.ClearLockList()
        local set = Rack_User[user].Sets[setname]
        if not set or SpellIsTargeting() or CursorHasItem() then return end
        local bag,slot,freeBag,freeSlot
        for i=0,19 do
                if set[i] then
                        freeBag,freeSlot = Rack.FindSpace(1)
                        if freeBag then
                                inv,bag,slot = Rack.FindItem(set[i].id,set[i].name)
                                if inv then
                                        PickupInventoryItem(inv)
                                        PickupContainerItem(freeBag,freeSlot)
                                elseif bag then
                                        PickupContainerItem(bag,slot)
                                        PickupContainerItem(freeBag,freeSlot)
                                end
                        else
                                Rack.NoMoreRoom()
                                return
                        end
                end
        end
end

function Rack.NoMoreRoom()
        DEFAULT_CHAT_FRAME:AddMessage("ItemRack: Not enough room to complete the swap.")
end