Push for 0.9b

Khaibit [08-11-14 - 19:54]
Push for 0.9b

  Further rewrite of part of the scanning routines to be more accurate
  Some small tweaks to the time display routines (will go up to 90 seconds before saying 1 minute, 90 minutes before 1 hour, etc.)
  Fixes to on-screen alerts to avoid 'missing' multiple identical alerts
  GUILD TRADER SUPPORT! Buyer names now have a gold bag icon next to them if they are not in the guild (i.e. bought at your guild's trader kiosk)
  Stats Window now also shows you percentage of sales made at the guild trader
  Other minor tweaks and optimizations as we push towards a fully-translated, fully-functional 1.0 release!
Filename
Shopkeeper.lua
Shopkeeper.txt
Shopkeeper.xml
Shopkeeper_Namespace_Init.lua
Shopkeeper_UI.lua
Shopkeeper_Util.lua
i18n/DE.lua
i18n/EN.lua
i18n/FR.lua
readme
diff --git a/Shopkeeper.lua b/Shopkeeper.lua
index d477a48..d379860 100644
--- a/Shopkeeper.lua
+++ b/Shopkeeper.lua
@@ -1,501 +1,9 @@
 -- Shopkeeper Main Addon File
--- Last Updated August 8, 2014
+-- Last Updated August 10, 2014
 -- Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
 -- Released under terms in license accompanying this file.
 -- Distribution without license is prohibited!

--- Workaround because GetDisplayName() is broken
--- Maybe not needed anymore, will test
-function Shopkeeper.GetAccountName()
-  local acctName = GetDisplayName()
-  if acctName == nil or acctName == "" then
-    -- Hopefully, they're in a guild and we can use GetPlayerGuildMemberIndex
-    if GetNumGuilds() > 0 then
-      acctName = GetGuildMemberInfo(GetGuildId(1), GetPlayerGuildMemberIndex(GetGuildId(1)))
-    -- But if they're in no guilds, we have to use the AccountName CVar, which only works
-    -- if they saved their account name on the login page.  Best we can do with patch 1.2.3
-    -- breaking GetDisplayName(), but if they aren't in any guilds, why are they using this
-    -- addon anyway?
-    else
-      acctName = "@" .. GetCVar("AccountName")
-    end
-  end
-  return acctName
-end
-
--- Translate from the i18n table
-function Shopkeeper.translate(stringName)
-  local result = Shopkeeper.i18n.localized[stringName]
-  assert(result, ("The id %q was not found in the current locale"):format(stringName))
-  return result
-end
-
-function Shopkeeper.localizedNumber(numberValue)
-  local stringPrice = numberValue
-  -- Insert thousands separators for the price
-  -- local stringPrice = numberValue
-  local subString = "%1" .. Shopkeeper.translate("thousandsSep") .."%2"
-  while true do
-    stringPrice, k = string.gsub(stringPrice, "^(-?%d+)(%d%d%d)", subString)
-    if (k == 0) then break end
-  end
-
-  return stringPrice
-end
-
--- Create a textual representation of a time interval
--- (X and Y) or Z in LUA is the equivalent of C-style
--- ternary syntax X ? Y : Z so long as Y is not false or nil
-function Shopkeeper.textTimeSince(theTime, useLowercase)
-  local secsSince = GetTimeStamp() - theTime
-  if secsSince < 60 then
-    return ((useLowercase and zo_strformat(Shopkeeper.translate('timeSecondsAgoLC'), secsSince)) or
-             zo_strformat(Shopkeeper.translate('timeSecondsAgo'), secsSince))
-  elseif secsSince < 3600 then
-    return ((useLowercase and zo_strformat(Shopkeeper.translate('timeMinutesAgoLC'), math.floor(secsSince / 60.0))) or
-             zo_strformat(Shopkeeper.translate('timeMinutesAgo'), math.floor(secsSince / 60.0)))
-  elseif secsSince < 86400 then
-    return ((useLowercase and zo_strformat(Shopkeeper.translate('timeHoursAgoLC'), math.floor(secsSince / 3600.0))) or
-             zo_strformat(Shopkeeper.translate('timeHoursAgo'), math.floor(secsSince / 3600.0)))
-  else
-    return ((useLowercase and zo_strformat(Shopkeeper.translate('timeDaysAgoLC'), math.floor(secsSince / 86400.0))) or
-             zo_strformat(Shopkeeper.translate('timeDaysAgo'), math.floor(secsSince / 86400.0)))
-  end
-end
-
--- A utility function to grab all the keys of the sound table
--- to populate the options dropdown
-function Shopkeeper.soundKeys()
-  local keyList = {}
-  local keyIndex = 0
-  for i = 1, #Shopkeeper.alertSounds do
-    keyIndex = keyIndex + 1
-    keyList[keyIndex] = Shopkeeper.alertSounds[i].name
-  end
-
-  return keyList
-end
-
--- A utility function to find the key associated with a given value in
--- the sounds table.  Best we can do is a linear search unfortunately,
--- but it's a small table.
-function Shopkeeper.searchSounds(sound)
-  for i, theSound in ipairs(Shopkeeper.alertSounds) do
-    if theSound.sound == sound then return theSound.name end
-  end
-
-  -- If we hit this point, we didn't find what we were looking for
-  return nil
-end
-
-function Shopkeeper.searchSoundNames(name)
-  for i,theSound in ipairs(Shopkeeper.alertSounds) do
-    if theSound.name == name then return theSound.sound end
-  end
-end
-
--- Handle the OnMoveStop event for the window
-function Shopkeeper.OnWindowMoveStop()
-  Shopkeeper.savedVariables.winLeft = ShopkeeperWindow:GetLeft()
-  Shopkeeper.savedVariables.winTop = ShopkeeperWindow:GetTop()
-  Shopkeeper.savedVariables.miniWinLeft = ShopkeeperMiniWindow:GetLeft()
-  Shopkeeper.savedVariables.miniWinTop = ShopkeeperMiniWindow:GetTop()
-end
-
-function Shopkeeper.OnStatsWindowMoveStop()
-  Shopkeeper.savedVariables.statsWinLeft = ShopkeeperStatsWindow:GetLeft()
-  Shopkeeper.savedVariables.statsWinTop = ShopkeeperStatsWindow:GetTop()
-end
-
--- Restore the window position from saved vars
-function Shopkeeper:RestoreWindowPosition()
-  local left = self.savedVariables.winLeft
-  local top = self.savedVariables.winTop
-  local statsLeft = self.savedVariables.statsWinLeft
-  local statsTop = self.savedVariables.statsWinTop
-  local miniLeft = self.savedVariables.miniWinLeft
-  local miniTop = self.savedVariables.miniWinTop
-
-  ShopkeeperWindow:ClearAnchors()
-  ShopkeeperStatsWindow:ClearAnchors()
-  ShopkeeperMiniWindow:ClearAnchors()
-  ShopkeeperWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, left, top)
-  ShopkeeperStatsWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, statsLeft, statsTop)
-  ShopkeeperMiniWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, miniLeft, miniTop)
-end
-
--- Handle the changing of main window font settings
-function Shopkeeper:UpdateFonts()
-  local LMP = LibStub("LibMediaProvider-1.0")
-  if LMP then
-    local font = LMP:Fetch('font', Shopkeeper.savedVariables.windowFont)
-    local look = string.format('%s|16', font)
-    local titleLook = string.format('%s|22', font)
-    local headerLook = string.format('%s|20', font)
-    local miniLook = string.format('%s|12', font)
-    local miniTitleLook = string.format('%s|18', font)
-    local miniHeaderLook = string.format('%s|16', font)
-    ShopkeeperWindowSearchLabel:SetFont(look)
-    ShopkeeperWindowSearchBox:SetFont(look)
-    ShopkeeperWindowTitle:SetFont(titleLook)
-    ShopkeeperWindowBuyer:SetFont(headerLook)
-    ShopkeeperWindowGuild:SetFont(headerLook)
-    ShopkeeperWindowItemName:SetFont(headerLook)
-    ShopkeeperWindowSellTime:SetFont(headerLook)
-    ShopkeeperWindowPrice:SetFont(headerLook)
-    ShopkeeperSwitchViewButton:SetFont(look)
-    ShopkeeperPriceSwitchButton:SetFont(look)
-    ShopkeeperResetButton:SetFont(look)
-    ShopkeeperRefreshButton:SetFont(look)
-    ShopkeeperMiniWindowSearchLabel:SetFont(miniLook)
-    ShopkeeperMiniWindowSearchBox:SetFont(miniLook)
-    ShopkeeperMiniWindowTitle:SetFont(miniTitleLook)
-    ShopkeeperMiniWindowGuild:SetFont(miniHeaderLook)
-    ShopkeeperMiniWindowItemName:SetFont(miniHeaderLook)
-    ShopkeeperMiniWindowSellTime:SetFont(miniHeaderLook)
-    ShopkeeperMiniWindowPrice:SetFont(miniHeaderLook)
-    ShopkeeperMiniSwitchViewButton:SetFont(miniLook)
-    ShopkeeperMiniPriceSwitchButton:SetFont(miniLook)
-    ShopkeeperMiniResetButton:SetFont(miniLook)
-    ShopkeeperMiniRefreshButton:SetFont(miniLook)
-
-    ShopkeeperStatsWindowTitle:SetFont(titleLook)
-    ShopkeeperStatsWindowItemsSoldLabel:SetFont(look)
-    ShopkeeperStatsWindowTotalGoldLabel:SetFont(look)
-    ShopkeeperStatsWindowBiggestSaleLabel:SetFont(look)
-    ShopkeeperStatsWindowSliderSettingLabel:SetFont(look)
-    ShopkeeperStatsWindowSliderLabel:SetFont(look)
-
-    for i = 1, #Shopkeeper.DataRows do
-      local dataRow = Shopkeeper.DataRows[i]
-      dataRow:GetNamedChild("Buyer"):SetFont(look)
-      dataRow:GetNamedChild("Guild"):SetFont(look)
-      dataRow:GetNamedChild("ItemName"):SetFont(look)
-      dataRow:GetNamedChild("Quantity"):SetFont(look)
-      dataRow:GetNamedChild("SellTime"):SetFont(look)
-      dataRow:GetNamedChild("Price"):SetFont(look)
-      if i <= #Shopkeeper.MiniDataRows then
-        local miniDataRow = Shopkeeper.MiniDataRows[i]
-        miniDataRow:GetNamedChild("Guild"):SetFont(miniLook)
-        miniDataRow:GetNamedChild("ItemName"):SetFont(miniLook)
-        miniDataRow:GetNamedChild("Quantity"):SetFont(miniLook)
-        miniDataRow:GetNamedChild("SellTime"):SetFont(miniLook)
-        miniDataRow:GetNamedChild("Price"):SetFont(miniLook)
-      end
-    end
-  end
-end
-
--- Item tooltips
-function Shopkeeper:ShowToolTip(itemName, itemButton)
-  InitializeTooltip(ItemTooltip, itemButton)
-  ItemTooltip:SetLink(itemName)
-end
-
--- Clear a given row's data
-function Shopkeeper.ClearDataRow(index)
-  if index < 1 or index > 15 then
-    return
-  end
-
-  local dataRow = Shopkeeper.DataRows[index]
-  dataRow:GetNamedChild("Buyer"):SetText("")
-  dataRow:GetNamedChild("Buyer"):SetHandler("OnMouseDoubleClick", nil)
-  dataRow:GetNamedChild("Guild"):SetText("")
-  dataRow:GetNamedChild("ItemIcon"):SetTexture(nil)
-  dataRow:GetNamedChild("ItemIcon"):SetHidden(true)
-  local itemCell = dataRow:GetNamedChild("ItemName")
-  itemCell:SetText("")
-  itemCell:SetHandler("OnMouseDoubleClick", nil)
-  itemCell:SetHandler("OnMouseEnter", nil)
-  dataRow:GetNamedChild("Quantity"):SetText("")
-  dataRow:GetNamedChild("SellTime"):SetText("")
-  dataRow:GetNamedChild("Price"):SetText("")
-end
-
-function Shopkeeper.ClearMiniDataRow(index)
-  if index < 1 or index > 8 then
-    return
-  end
-
-  local dataRow = Shopkeeper.MiniDataRows[index]
-  dataRow:GetNamedChild("Guild"):SetText("")
-  dataRow:GetNamedChild("ItemIcon"):SetTexture(nil)
-  dataRow:GetNamedChild("ItemIcon"):SetHidden(true)
-  local itemCell = dataRow:GetNamedChild("ItemName")
-  itemCell:SetText("")
-  itemCell:SetHandler("OnMouseDoubleClick", nil)
-  itemCell:SetHandler("OnMouseEnter", nil)
-  dataRow:GetNamedChild("Quantity"):SetText("")
-  dataRow:GetNamedChild("SellTime"):SetText("")
-  dataRow:GetNamedChild("Price"):SetText("")
-end
-
--- Fill out a row with the given data
-function Shopkeeper.SetDataRow(index, buyer, guild, itemName, icon, quantity, sellTime, price, seller)
-  if index < 1 or index > 15 then return end
-
-  local dataRow = Shopkeeper.DataRows[index]
-
-  -- Some extra stuff for the Buyer cell to handle double-click and color changes
-  local buyerCell = dataRow:GetNamedChild("Buyer")
-  buyerCell:SetText(buyer)
-  -- If the seller is the player, color the buyer green.  Otherwise, blue.
-  local acctName = Shopkeeper.GetAccountName()
-  if seller == acctName then
-    buyerCell:SetNormalFontColor(0.18, 0.77, 0.05, 1)
-    buyerCell:SetPressedFontColor(0.18, 0.77, 0.05, 1)
-    buyerCell:SetMouseOverFontColor(0.32, 0.90, 0.18, 1)
-  else
-    buyerCell:SetNormalFontColor(0.21, 0.54, 0.94, 1)
-    buyerCell:SetPressedFontColor(0.21, 0.54, 0.94, 1)
-    buyerCell:SetMouseOverFontColor(0.34, 0.67, 1, 1)
-  end
-  buyerCell:SetHandler("OnMouseDoubleClick", function()
-    if SCENE_MANAGER.currentScene.name == "mailSend" then
-      ZO_MailSendToField:SetText("")
-      ZO_MailSendToField:SetText(ZO_MailSendToField:GetText() .. buyer)
-    else
-      ZO_ChatWindowTextEntryEditBox:SetText("/w " .. buyer .. " " .. ZO_ChatWindowTextEntryEditBox:GetText())
-    end
-  end)
-  local buyerCellLabel = buyerCell:GetLabelControl()
-  buyerCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
-
-  -- Guild cell
-  dataRow:GetNamedChild("Guild"):SetText(guild)
-  local guildCellLabel = dataRow:GetNamedChild("Guild"):GetLabelControl()
-  guildCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
-
-  -- Item Icon
-  dataRow:GetNamedChild("ItemIcon"):SetHidden(false)
-  dataRow:GetNamedChild("ItemIcon"):SetTexture(icon)
-
-  -- Item name cell
-  local itemCell = dataRow:GetNamedChild("ItemName")
-  itemCell:SetText(zo_strformat("<<t:1>>", itemName))
-  -- Insert the item link into the chat box, with a quick substitution so brackets show up
-  itemCell:SetHandler("OnMouseDoubleClick", function()
-    ZO_ChatWindowTextEntryEditBox:SetText(ZO_ChatWindowTextEntryEditBox:GetText() .. string.gsub(itemName, "|H0", "|H1"))
-  end)
-  itemCell:SetHandler("OnMouseEnter", function() Shopkeeper:ShowToolTip(itemName, itemCell) end)
-  itemCell:SetHandler("OnMouseExit", function() ClearTooltip(ItemTooltip) end)
-  local itemCellLabel = itemCell:GetLabelControl()
-  itemCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
-
-  -- Quantity cell
-  dataRow:GetNamedChild("Quantity"):SetText("x" .. quantity)
-
-  -- Sale time cell
-  dataRow:GetNamedChild("SellTime"):SetText(sellTime)
-
-  -- Handle the setting of whether or not to show pre-cut sale prices
-  -- math.floor(number + 0.5) is a quick shorthand way to round for
-  -- positive values.
-  local dispPrice = price
-  if Shopkeeper.savedVariables.showFullPrice then
-    if Shopkeeper.savedVariables.showUnitPrice and quantity > 0 then
-      dispPrice = math.floor((dispPrice / quantity) + 0.5)
-    end
-  else
-    local cutPrice = price * (1 - (GetTradingHouseCutPercentage() / 100))
-    if Shopkeeper.savedVariables.showUnitPrice and quantity > 0 then
-      cutPrice = cutPrice / quantity
-    end
-    dispPrice = math.floor(cutPrice + 0.5)
-  end
-
-  -- Insert thousands separators for the price
-  local stringPrice = Shopkeeper.localizedNumber(dispPrice)
-
-  -- Finally, set the price
-  dataRow:GetNamedChild("Price"):SetText(stringPrice .. " " .. string.format("|t16:16:%s|t","EsoUI/Art/currency/currency_gold.dds"))
-end
-
--- Fill out a row with the given data
-function Shopkeeper.SetMiniDataRow(index, guild, itemName, icon, quantity, sellTime, price, seller)
-  if index < 1 or index > 8 then return end
-
-  local dataRow = Shopkeeper.MiniDataRows[index]
-
-  -- Guild cell
-  dataRow:GetNamedChild("Guild"):SetText(guild)
-  local guildCellLabel = dataRow:GetNamedChild("Guild"):GetLabelControl()
-  guildCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
-
-  -- Item Icon
-  dataRow:GetNamedChild("ItemIcon"):SetHidden(false)
-  dataRow:GetNamedChild("ItemIcon"):SetTexture(icon)
-
-  -- Item name cell
-  local itemCell = dataRow:GetNamedChild("ItemName")
-  itemCell:SetText(zo_strformat("<<t:1>>", itemName))
-  -- Insert the item link into the chat box, with a quick substitution so brackets show up
-  itemCell:SetHandler("OnMouseDoubleClick", function()
-    ZO_ChatWindowTextEntryEditBox:SetText(ZO_ChatWindowTextEntryEditBox:GetText() .. string.gsub(itemName, "|H0", "|H1"))
-  end)
-  itemCell:SetHandler("OnMouseEnter", function() Shopkeeper:ShowToolTip(itemName, itemCell) end)
-  itemCell:SetHandler("OnMouseExit", function() ClearTooltip(ItemTooltip) end)
-  local itemCellLabel = itemCell:GetLabelControl()
-  itemCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
-
-  -- Quantity cell
-  dataRow:GetNamedChild("Quantity"):SetText("x" .. quantity)
-
-  -- Sale time cell
-  dataRow:GetNamedChild("SellTime"):SetText(sellTime)
-
-  -- Handle the setting of whether or not to show pre-cut sale prices
-  -- math.floor(number + 0.5) is a quick shorthand way to round for
-  -- positive values.
-  local dispPrice = price
-  if Shopkeeper.savedVariables.showFullPrice then
-    if Shopkeeper.savedVariables.showUnitPrice and quantity > 0 then
-      dispPrice = math.floor((dispPrice / quantity) + 0.5)
-    end
-  else
-    local cutPrice = price * (1 - (GetTradingHouseCutPercentage() / 100))
-    if Shopkeeper.savedVariables.showUnitPrice and quantity > 0 then
-      cutPrice = cutPrice / quantity
-    end
-    dispPrice = math.floor(cutPrice + 0.5)
-  end
-
-  -- Insert thousands separators for the price
-  local stringPrice = Shopkeeper.localizedNumber(dispPrice)
-
-  -- Finally, set the price
-  dataRow:GetNamedChild("Price"):SetText(stringPrice .. " " .. string.format("|t16:16:%s|t","EsoUI/Art/currency/currency_gold.dds"))
-end
-
--- Build the data rows based on the position of the slider
-function Shopkeeper.DisplayRows()
-  if Shopkeeper.savedVariables.viewSize == "full" then
-    local startIndex = Shopkeeper.shopSlider:GetValue()
-    if startIndex + #Shopkeeper.DataRows > #Shopkeeper.SearchTable then
-      startIndex = #Shopkeeper.SearchTable - #Shopkeeper.DataRows
-    end
-
-    if startIndex < 1 then startIndex = 0 end
-
-    -- Hide the slider if there's less than a full page of results
-    Shopkeeper.shopSlider:SetHidden(#Shopkeeper.SearchTable < 16)
-
-    for i = 1, #Shopkeeper.DataRows do
-      local rowIndex = i + startIndex
-      if rowIndex > #Shopkeeper.SearchTable then
-        Shopkeeper.ClearDataRow(i)
-        Shopkeeper.shopSlider:SetHidden(true)
-      else
-        local scanResult = Shopkeeper.SearchTable[rowIndex]
-
-        Shopkeeper.SetDataRow(i, scanResult[1], scanResult[2], scanResult[3], scanResult[4], scanResult[5], Shopkeeper.textTimeSince(scanResult[6], false), scanResult[7], scanResult[8])
-      end
-    end
-
-    -- Scale the slider's range to the number of items we have minus the number of rows
-    local sliderMax = 0
-    local tableToUse = Shopkeeper.ScanResults
-    if Shopkeeper.viewMode == "self" then tableToUse = Shopkeeper.SelfSales end
-    if #tableToUse > 15 then sliderMax = (#tableToUse - 15) end
-    Shopkeeper.shopSlider:SetMinMax(0, sliderMax)
-  else
-    local startIndex = Shopkeeper.miniShopSlider:GetValue()
-    if startIndex + #Shopkeeper.MiniDataRows > #Shopkeeper.SearchTable then
-      startIndex = #Shopkeeper.SearchTable - #Shopkeeper.MiniDataRows
-    end
-
-    if startIndex < 1 then startIndex = 0 end
-
-    -- Hide the slider if there's less than a full page of results
-    Shopkeeper.miniShopSlider:SetHidden(#Shopkeeper.SearchTable < 9)
-
-    for i = 1, #Shopkeeper.MiniDataRows do
-      local rowIndex = i + startIndex
-      if rowIndex > #Shopkeeper.SearchTable then
-        Shopkeeper.ClearMiniDataRow(i)
-        Shopkeeper.miniShopSlider:SetHidden(true)
-      else
-        local scanResult = Shopkeeper.SearchTable[rowIndex]
-
-        Shopkeeper.SetMiniDataRow(i, scanResult[2], scanResult[3], scanResult[4], scanResult[5], Shopkeeper.textTimeSince(scanResult[6], false), scanResult[7], scanResult[8])
-      end
-    end
-
-    -- Scale the slider's range to the number of items we have minus the number of rows
-    local sliderMax = 0
-    local tableToUse = Shopkeeper.ScanResults
-    if Shopkeeper.viewMode == "self" then tableToUse = Shopkeeper.SelfSales end
-    if #tableToUse > 8 then sliderMax = (#tableToUse - 8) end
-    Shopkeeper.miniShopSlider:SetMinMax(0, sliderMax)
-  end
-end
-
-function Shopkeeper.ToggleViewMode()
-  if Shopkeeper.savedVariables.viewSize == "full" then
-    Shopkeeper.savedVariables.viewSize = "half"
-    ShopkeeperWindow:SetHidden(true)
-    Shopkeeper.DisplayRows()
-    ShopkeeperMiniWindow:SetHidden(false)
-
-    if Shopkeeper.savedVariables.openWithMail then
-      MAIL_INBOX_SCENE:RemoveFragment(Shopkeeper.uiFragment)
-      MAIL_SEND_SCENE:RemoveFragment(Shopkeeper.uiFragment)
-      MAIL_INBOX_SCENE:AddFragment(Shopkeeper.miniUiFragment)
-      MAIL_SEND_SCENE:AddFragment(Shopkeeper.miniUiFragment)
-    end
-
-    if Shopkeeper.savedVariables.openWithStore then
-      TRADING_HOUSE_SCENE:RemoveFragment(Shopkeeper.uiFragment)
-      TRADING_HOUSE_SCENE:AddFragment(Shopkeeper.miniUiFragment)
-    end
-  else
-    Shopkeeper.savedVariables.viewSize = "full"
-    ShopkeeperMiniWindow:SetHidden(true)
-    Shopkeeper.DisplayRows()
-    ShopkeeperWindow:SetHidden(false)
-
-    if Shopkeeper.savedVariables.openWithMail then
-      MAIL_INBOX_SCENE:RemoveFragment(Shopkeeper.miniUiFragment)
-      MAIL_SEND_SCENE:RemoveFragment(Shopkeeper.miniUiFragment)
-      MAIL_INBOX_SCENE:AddFragment(Shopkeeper.uiFragment)
-      MAIL_SEND_SCENE:AddFragment(Shopkeeper.uiFragment)
-    end
-
-    if Shopkeeper.savedVariables.openWithStore then
-      TRADING_HOUSE_SCENE:RemoveFragment(Shopkeeper.miniUiFragment)
-      TRADING_HOUSE_SCENE:AddFragment(Shopkeeper.uiFragment)
-    end
-  end
-end
-
--- Set the visibility status of the main window to the opposite of its current status
-function Shopkeeper.ToggleShopkeeperWindow()
-  if Shopkeeper.savedVariables.viewSize == "full" then
-    ShopkeeperMiniWindow:SetHidden(true)
-    if ShopkeeperWindow:IsHidden() then
-      Shopkeeper.DisplayRows()
-      SetGameCameraUIMode(true)
-    end
-
-    ShopkeeperWindow:SetHidden(not ShopkeeperWindow:IsHidden())
-  else
-    ShopkeeperWindow:SetHidden(true)
-    if ShopkeeperMiniWindow:IsHidden() then
-      Shopkeeper.DisplayRows()
-      SetGameCameraUIMode(true)
-    end
-
-    ShopkeeperMiniWindow:SetHidden(not ShopkeeperMiniWindow:IsHidden())
-  end
-end
-
--- Set the visibility status of the stats window to the opposite of its current status
-function Shopkeeper.ToggleShopkeeperStatsWindow()
-  if ShopkeeperStatsWindow:IsHidden() then Shopkeeper.UpdateStatsWindow() end
-  ShopkeeperStatsWindow:SetHidden(not ShopkeeperStatsWindow:IsHidden())
-end
-
 -- Sort the scan results by 'ordering' order (asc/desc).
 -- We sort both the search result table and the master scan results table because either we do it
 -- now or we sort at separate times and try to keep track of what state each is in.  No thanks!
@@ -626,12 +134,15 @@ function Shopkeeper.TimeSort()
   end
 end

+-- Calculate some stats based on the player's sales
+-- And return them as a table.
 function Shopkeeper.SalesStats(statsDays)
   local itemsSold = 0
   local goldMade = 0
   local largestSingle = {0, nil}
   local oldestTime = 0
   local newestTime = 0
+  local kioskSales = 0
   -- 86,400 seconds in a day; this will be the epoch time statsDays ago
   local statsDaysEpoch = GetTimeStamp() - (86400 * statsDays)

@@ -641,6 +152,9 @@ function Shopkeeper.SalesStats(statsDays)
     local theItem = Shopkeeper.SelfSales[i]
     if statsDays == 0 or theItem[6] > statsDaysEpoch then
       itemsSold = itemsSold + 1
+      if #theItem > 8 and theItem[9] then
+        kioskSales = kioskSales + 1
+      end
       goldMade = goldMade + theItem[7]
       if oldestTime == 0 or theItem[6] < oldestTime then oldestTime = theItem[6] end
       if newestTime == 0 or theItem[6] > newestTime then newestTime = theItem[6] end
@@ -654,6 +168,10 @@ function Shopkeeper.SalesStats(statsDays)
   local dayWindow = 1
   if timeWindow > 86400 then dayWindow = math.floor(timeWindow / 86400) + 1 end
   local goldPerDay = math.floor(goldMade / dayWindow)
+  local kioskPercentage = 0
+  if itemsSold > 0 then
+    kioskPercentage = math.floor((kioskSales / itemsSold) * 100)
+  end

   -- If they have the option set to show prices post-cut, calculate that here
   if not Shopkeeper.savedVariables.showFullPrice then
@@ -668,11 +186,13 @@ function Shopkeeper.SalesStats(statsDays)
            numDays = dayWindow,
            totalGold = goldMade,
            avgGold = goldPerDay,
-           biggestSale = largestSingle }
+           biggestSale = largestSingle,
+           kioskPercent = kioskPercentage, }
 end

+-- Update all the fields of the stats window based on the response from SalesStats()
 function Shopkeeper.UpdateStatsWindow()
-  local sliderLevel = Shopkeeper.statsSlider:GetValue()
+  local sliderLevel = ShopkeeperStatsWindowSlider:GetValue()
   if sliderLevel == 0 then
     ShopkeeperStatsWindowSliderSettingLabel:SetText(Shopkeeper.translate('statsTimeAll'))
   else
@@ -680,7 +200,7 @@ function Shopkeeper.UpdateStatsWindow()
   end

   local newStats = Shopkeeper.SalesStats(sliderLevel)
-  ShopkeeperStatsWindowItemsSoldLabel:SetText(string.format(Shopkeeper.translate('statsItemsSold'), Shopkeeper.localizedNumber(newStats['numSold'])))
+  ShopkeeperStatsWindowItemsSoldLabel:SetText(string.format(Shopkeeper.translate('statsItemsSold'), Shopkeeper.localizedNumber(newStats['numSold']), newStats['kioskPercent']))
   ShopkeeperStatsWindowTotalGoldLabel:SetText(string.format(Shopkeeper.translate('statsTotalGold'), Shopkeeper.localizedNumber(newStats['totalGold']), Shopkeeper.localizedNumber(newStats['avgGold'])))
   ShopkeeperStatsWindowBiggestSaleLabel:SetText(string.format(Shopkeeper.translate('statsBiggest'), zo_strformat("<<t:1>>", newStats['biggestSale'][2]), Shopkeeper.localizedNumber(newStats['biggestSale'][1])))
 end
@@ -796,7 +316,7 @@ function Shopkeeper:LibAddonInit()
             Shopkeeper.savedVariables.scanFreq = value
             EVENT_MANAGER:UnregisterForUpdate(Shopkeeper.name)
             local scanInterval = value * 1000
-            EVENT_MANAGER:RegisterForUpdate(Shopkeeper.name, scanInterval, function() Shopkeeper:ScanStores(false, false) end)
+            EVENT_MANAGER:RegisterForUpdate(Shopkeeper.name, scanInterval, function() Shopkeeper:ScanStores(false) end)
           end,
         },
         [6] = {
@@ -825,34 +345,12 @@ function Shopkeeper:LibAddonInit()
   end
 end

--- Handle scrolling the main window
-function Shopkeeper.OnSliderMouseWheel(self, delta)
-  if Shopkeeper.savedVariables.viewSize == "full" then
-    local oldSliderLevel = Shopkeeper.shopSlider:GetValue()
-    local newSliderLevel = oldSliderLevel - delta
-    Shopkeeper.shopSlider:SetValue(newSliderLevel)
-  else
-    local oldSliderLevel = Shopkeeper.miniShopSlider:GetValue()
-    local newSliderLevel = oldSliderLevel - delta
-    Shopkeeper.miniShopSlider:SetValue(newSliderLevel)
-  end
-end
-
--- Update the table if the slider moved
-function Shopkeeper.OnSliderMoved(self, sliderLevel, eventReason)
-  Shopkeeper.DisplayRows()
-end
-
-function Shopkeeper.OnStatsSliderMoved(self, sliderLevel, eventReason)
-  Shopkeeper.UpdateStatsWindow()
-end
-
 -- Filters the ScanResults (or SelfSales) table into the SearchTable,
 -- using the Shopkeeper.viewMode to determine which one to use.
 function Shopkeeper.DoSearch(searchText)
   Shopkeeper.SearchTable = {}
   local searchTerm = string.lower(searchText)
-  local acctName = Shopkeeper.GetAccountName()
+  local acctName = GetDisplayName()
   local tableToUse = Shopkeeper.ScanResults
   if Shopkeeper.viewMode == "self" then tableToUse = Shopkeeper.SelfSales end

@@ -935,7 +433,6 @@ end
 -- and updates the displayed table, sending a message to chat if
 -- the scan was initiated via the 'refresh' button.
 function Shopkeeper:PostScan(doAlert)
-  Shopkeeper.isScanning = false
   if Shopkeeper.savedVariables.viewSize == "full" then
     Shopkeeper.DoSearch(ShopkeeperWindowSearchBox:GetText())
   else
@@ -946,10 +443,10 @@ function Shopkeeper:PostScan(doAlert)
   local tableToUse = Shopkeeper.ScanResults
   if Shopkeeper.viewMode == "self" then tableToUse = Shopkeeper.SelfSales end
   if #tableToUse > 15 then sliderMax = (#tableToUse - 15) end
-  Shopkeeper.shopSlider:SetMinMax(0, sliderMax)
+  ShopkeeperWindowSlider:SetMinMax(0, sliderMax)
   sliderMax = 0
   if #tableToUse > 8 then sliderMax = (#tableToUse - 8) end
-  Shopkeeper.miniShopSlider:SetMinMax(0, sliderMax)
+  ShopkeeperMiniWindowSlider:SetMinMax(0, sliderMax)

   if Shopkeeper.curSort[1] == "time" then
     Shopkeeper.SortByTime(Shopkeeper.curSort[2])
@@ -971,6 +468,7 @@ function Shopkeeper:PostScan(doAlert)
     local numSold = 0
     local totalGold = 0
     local numAlerts = #Shopkeeper.alertQueue
+    local lastEvent = {}
     for i = 1, numAlerts do
       local theEvent = table.remove(Shopkeeper.alertQueue, 1)
       numSold = numSold + 1
@@ -996,25 +494,36 @@ function Shopkeeper:PostScan(doAlert)
         -- false or nil.)
         local alertSound = (i > 1) and SOUNDS.NONE or Shopkeeper.savedVariables.alertSoundName

-        -- On-screen alert
+        -- On-screen alert - need to do something to avoid queueing identical messages in a row
         if Shopkeeper.savedVariables.showAnnounceAlerts then
+
+          local textTime = Shopkeeper.textTimeSince(theEvent.saleTime, true)
+          local alertSuffix = ""
+          if lastEvent[1] ~= nil and theEvent.itemName == lastEvent[1].itemName and textTime == lastEvent[2] then
+            lastEvent[3] = lastEvent[3] + 1
+            alertSuffix = " (" .. lastEvent[3] .. ")"
+          else
+            lastEvent[1] = theEvent
+            lastEvent[2] = textTime
+            lastEvent[3] = 1
+          end
           -- German word order differs so argument order also needs to be changed
           -- Also due to plurality differences in German, need to differentiate
           -- single item sold vs. multiple of an item sold.
           if Shopkeeper.locale == "de" then
             if theEvent.quant > 1 then
-              CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_SMALL_TEXT, alertSound,
+              CENTER_SCREEN_ANNOUNCE:AddMessage("ShopkeeperAlert", CSA_EVENT_SMALL_TEXT, alertSound,
                 string.format(Shopkeeper.translate('salesAlertColor'), theEvent.quant, zo_strformat("<<t:1>>", theEvent.itemName),
-                              stringPrice, theEvent.guild, Shopkeeper.textTimeSince(theEvent.saleTime, true)))
+                              stringPrice, theEvent.guild, textTime) .. alertSuffix)
             else
-              CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_SMALL_TEXT, alertSound,
-                string.format(Shopkeeper.translate('salesAlertColorSingle'), zo_strformat("<<t:1>>", theEvent.itemName),
-                              stringPrice, theEvent.guild, Shopkeeper.textTimeSince(theEvent.saleTime, true)))
+              CENTER_SCREEN_ANNOUNCE:AddMessage("ShopkeeperAlert", CSA_EVENT_SMALL_TEXT, alertSound,
+                string.format(Shopkeeper.translate('salesAlertColorSingle'),zo_strformat("<<t:1>>", theEvent.itemName),
+                              stringPrice, theEvent.guild, textTime) .. alertSuffix)
             end
           else
-            CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_SMALL_TEXT, alertSound,
+            CENTER_SCREEN_ANNOUNCE:AddMessage("ShopkeeperAlert", CSA_EVENT_SMALL_TEXT, alertSound,
               string.format(Shopkeeper.translate('salesAlertColor'), zo_strformat("<<t:1>>", theEvent.itemName),
-                            theEvent.quant, stringPrice, theEvent.guild, Shopkeeper.textTimeSince(theEvent.saleTime, true)))
+                            theEvent.quant, stringPrice, theEvent.guild, textTime) .. alertSuffix)
           end
         end

@@ -1042,7 +551,7 @@ function Shopkeeper:PostScan(doAlert)
       local stringPrice = Shopkeeper.localizedNumber(totalGold)

       if Shopkeeper.savedVariables.showAnnounceAlerts then
-        CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_SMALL_TEXT, Shopkeeper.savedVariables.alertSoundName,
+        CENTER_SCREEN_ANNOUNCE:AddMessage("ShopkeeperAlert", CSA_EVENT_SMALL_TEXT, Shopkeeper.savedVariables.alertSoundName,
           string.format(Shopkeeper.translate('salesGroupAlertColor'), numSold, stringPrice))
       else
         CHAT_SYSTEM:AddMessage(string.format("[Shopkeeper] " .. Shopkeeper.translate('salesGroupAlert'),
@@ -1055,75 +564,102 @@ function Shopkeeper:PostScan(doAlert)
   Shopkeeper.DisplayRows()
 end

+-- Makes sure all the necessary data is there, and adds the passed-in event theEvent
+-- to the ScanResults and SelfSales table.  If doAlert is true, also adds it to
+-- alertQueue, which means an alert may fire during PostScan.
+function Shopkeeper:InsertEvent(theEvent, doAlert)
+  local thePlayer = string.lower(GetDisplayName())
+
+  if theEvent.itemName ~= nil and theEvent.seller ~= nil and theEvent.buyer ~= nil and theEvent.salePrice ~= nil then
+    -- Grab the icon
+    local itemIcon, _, _, _ = GetItemLinkInfo(theEvent.itemName)
+
+    -- Insert the entry into the ScanResults table
+    table.insert(Shopkeeper.ScanResults, {theEvent.buyer, theEvent.guild, theEvent.itemName, itemIcon,
+                                          theEvent.quant, theEvent.saleTime, theEvent.salePrice,
+                                          theEvent.seller, theEvent.kioskSale})
+
+    -- And then, if it's the player's sale, insert into that table
+    if string.lower(theEvent.seller) == thePlayer then
+      table.insert(Shopkeeper.SelfSales, {theEvent.buyer, theEvent.guild, theEvent.itemName, itemIcon,
+                                          theEvent.quant, theEvent.saleTime, theEvent.salePrice,
+                                          theEvent.seller, theEvent.kioskSale})
+      if doAlert and (Shopkeeper.savedVariables.showChatAlerts or Shopkeeper.savedVariables.showAnnounceAlerts) then
+        table.insert(Shopkeeper.alertQueue, theEvent)
+      end
+    end
+  end
+end
+
 -- Actually carries out of the scan of a specific guild store's sales history.
--- If checkOlder is true, will request older events first if there are any.
--- Inserts all events that occurred after guildID guild's last scan into the ScanResults table.
--- Inserts all events that occurred after guildID guild's last scan and were sold by the player
--- into the SelfSales table.
+-- Grabs all the members of the guild first to determine if a sale came from the
+-- guild's kiosk (guild trader) or not.
+-- Calls InsertEvent to actually insert the event into the ScanResults and SelfSales
+-- tables.
 function Shopkeeper:DoScan(guildID, checkOlder, doAlert)
-  local timeWindow = 10
-  if Shopkeeper.missedLastScan[guildID] ~= nil and Shopkeeper.missedLastScan[guildID] == true then
-    timeWindow = 25
-  end
-  local thePlayer = string.lower(Shopkeeper.GetAccountName())
   local numEvents = GetNumGuildEvents(guildID, GUILD_HISTORY_SALES)
-  for i = 1, numEvents do
-    local theEvent = {}
-    _, theEvent.secsSince, theEvent.seller, theEvent.buyer,
-    theEvent.quant, theEvent.itemName, theEvent.salePrice, _ = GetGuildEventInfo(guildID, GUILD_HISTORY_SALES, i)
-    theEvent.guild = GetGuildName(guildID)
-    -- Only worry about items sold since our last scan
-    if theEvent.secsSince ~= nil then
-      theEvent.saleTime = Shopkeeper.requestTimestamp - theEvent.secsSince
-
-      if Shopkeeper.acctSavedVariables.lastScan[guildID] == nil or GetDiffBetweenTimeStamps(theEvent.saleTime, (Shopkeeper.acctSavedVariables.lastScan[guildID] - timeWindow)) >= 0 then
-        if theEvent.itemName ~= nil and theEvent.seller ~= nil and theEvent.buyer ~= nil and theEvent.salePrice ~= nil then
-          -- Grab the icon
-          local itemIcon, _, _, _ = GetItemLinkInfo(theEvent.itemName)
-
-          -- If the seller is the player and this isn't a deep scan (and thus the first upon login or reset),
-          -- queue up an alert
-          if not checkOlder and (Shopkeeper.savedVariables.showChatAlerts or Shopkeeper.savedVariables.showAnnounceAlerts)
-             and string.lower(theEvent.seller) == thePlayer then
-            table.insert(Shopkeeper.alertQueue, theEvent)
-          end
-
-          -- Insert the entry into the ScanResults table
-          table.insert(Shopkeeper.ScanResults, {theEvent.buyer, theEvent.guild, theEvent.itemName, itemIcon,
-                                                theEvent.quant, theEvent.saleTime, theEvent.salePrice,
-                                                theEvent.seller})
-
-          -- And then, if it's the player's sale, insert into that table
-          if string.lower(theEvent.seller) == thePlayer then
-            table.insert(Shopkeeper.SelfSales, {theEvent.buyer, theEvent.guild, theEvent.itemName, itemIcon,
-                                                theEvent.quant, theEvent.saleTime, theEvent.salePrice,
-                                                theEvent.seller})
-          end
+  local prevEvents = 0
+  if Shopkeeper.numEvents[guildID] ~= nil then prevEvents = Shopkeeper.numEvents[guildID] end
+
+  if numEvents > prevEvents then
+    local guildMemberInfo = {}
+    -- Index the table with the account names themselves as they're
+    -- (hopefully!) unique - search much faster
+    for i = 1, GetNumGuildMembers(guildID) do
+      local guildMemInfo, _, _, _, _ = GetGuildMemberInfo(guildID, i)
+      guildMemberInfo[string.lower(guildMemInfo)] = true
+    end
+
+    for i = (prevEvents + 1), numEvents do
+      local theEvent = {}
+      _, theEvent.secsSince, theEvent.seller, theEvent.buyer,
+      theEvent.quant, theEvent.itemName, theEvent.salePrice, _ = GetGuildEventInfo(guildID, GUILD_HISTORY_SALES, i)
+      theEvent.guild = GetGuildName(guildID)
+      theEvent.saleTime = GetTimeStamp() - theEvent.secsSince
+
+      -- If we didn't add an entry to guildMemberInfo earlier setting the
+      -- buyer's name index to true, then this was either bought at a kiosk
+      -- or the buyer left the guild after buying but before we scanned.
+      -- Close enough!
+      theEvent.kioskSale = (guildMemberInfo[string.lower(theEvent.buyer)] == nil)
+
+      -- If we're doing a deep scan, revert to timestamp checking
+      -- For reasons I cannot determine, I am getting items not previously
+      -- seen up to 13 seconds BEFORE the last call to RequestGuildHistoryCategoryNewest.
+      -- Bizarre, but adding this fudge factor hasn't resulted in dupes...yet.
+      if checkOlder then
+        if Shopkeeper.acctSavedVariables.lastScan[guildID] == nil or GetDiffBetweenTimeStamps(theEvent.saleTime, (Shopkeeper.acctSavedVariables.lastScan[guildID] - 13)) >= 0 then
+          Shopkeeper:InsertEvent(theEvent, false)
         end
+
+      -- Otherwise, all new events are assumed good
+      -- Inspiration for event number-based handling from sirinsidiator
+      else
+        Shopkeeper:InsertEvent(theEvent, true)
       end
     end
   end

+  -- We got through any new (to us) events, so update the timestamp and number of events
   Shopkeeper.acctSavedVariables.lastScan[guildID] = Shopkeeper.requestTimestamp
+  Shopkeeper.numEvents[guildID] = numEvents
+
+  -- If we have another guild to scan, see if we need to check older and scan it
   if guildID < GetNumGuilds() then
     local nextGuild = guildID + 1
-    local didScan = RequestGuildHistoryCategoryNewest(nextGuild, GUILD_HISTORY_SALES)
-    if Shopkeeper.missedScan[nextGuild] ~= nil then Shopkeeper.missedLastScan[nextGuild] = Shopkeeper.missedScan[nextGuild]
-    else Shopkeeper.missedLastScan[nextGuild] = false end
-
-    if not didScan then
-      Shopkeeper.missedScan[nextGuild] = true
-    else
-      Shopkeeper.missedScan[nextGuild] = false
-    end
+    local nextCheckOlder = false
+    if Shopkeeper.numEvents[nextGuild] == nil or Shopkeeper.numEvents[nextGuild] == 0 then nextCheckOlder = true end
+    RequestGuildHistoryCategoryNewest(nextGuild, GUILD_HISTORY_SALES)
     Shopkeeper.requestTimestamp = GetTimeStamp()
-    if checkOlder then
-      zo_callLater(function() Shopkeeper:ScanOlder(nextGuild, doAlert) end, 1250)
+    if nextCheckOlder then
+      zo_callLater(function() Shopkeeper:ScanOlder(nextGuild, doAlert) end, 1500)
     else
-      zo_callLater(function() Shopkeeper:DoScan(nextGuild , false, doAlert) end, 1250)
+      zo_callLater(function() Shopkeeper:DoScan(nextGuild, false, doAlert) end, 1500)
     end
+  -- Otherwise, start the postscan routines
   else
-    zo_callLater(function() Shopkeeper:PostScan(doAlert) end, 1250)
+    Shopkeeper.isScanning = false
+    Shopkeeper:PostScan(doAlert)
   end
 end

@@ -1132,63 +668,46 @@ end
 function Shopkeeper:ScanOlder(guildNum, doAlert)
   local numEvents = GetNumGuildEvents(guildNum, GUILD_HISTORY_SALES)
   local _, secsSince, _, _, _, _, _, _ = GetGuildEventInfo(guildNum, GUILD_HISTORY_SALES, numEvents)
-  local lastScan = 0
-  if Shopkeeper.acctSavedVariables.lastScan[guildNum] ~= nil then lastScan = Shopkeeper.acctSavedVariables.lastScan[guildNum] end
-  if not DoesGuildHistoryCategoryHaveMoreEvents(guildNum, GUILD_HISTORY_SALES) or
-     not RequestGuildHistoryCategoryOlder(guildNum, GUILD_HISTORY_SALES) or
-         GetDiffBetweenTimeStamps(lastScan, GetTimeStamp() - secsSince) > 0 then
-    zo_callLater(function() Shopkeeper:DoScan(guildNum, true, doAlert) end, 1250)
+  local secsSinceScan = nil
+  if Shopkeeper.acctSavedVariables.lastScan[guildNum] ~= nil then
+    secsSinceScan = GetTimeStamp() - Shopkeeper.acctSavedVariables.lastScan[guildNum]
+  end
+
+  if DoesGuildHistoryCategoryHaveMoreEvents(guildNum, GUILD_HISTORY_SALES) and
+     (secsSinceScan == nil or secsSinceScan > secsSince) then
+    RequestGuildHistoryCategoryOlder(guildNum, GUILD_HISTORY_SALES)
+    zo_callLater(function() Shopkeeper:ScanOlder(guildNum, doAlert) end, 1500)
   else
-    zo_callLater(function() Shopkeeper:ScanOlder(guildNum, doAlert) end, 1250)
+    zo_callLater(function() Shopkeeper:DoScan(guildNum, true, doAlert) end, 1500)
   end
 end

 -- Scans all stores a player has access to with delays between them.
-function Shopkeeper:ScanStores(checkOlder, doAlert)
+function Shopkeeper:ScanStores(doAlert)
   -- If it's been less than a minute since we last scanned the store,
   -- don't do it again so we don't hammer the server either accidentally
   -- or on purpose
-  local timeLimit = GetTimeStamp() - 59
+  local timeLimit = GetTimeStamp() - 60
   local guildNum = GetNumGuilds()
   -- Nothing to scan!
   if guildNum == 0 then return end
-  if not Shopkeeper.isScanning and (Shopkeeper.acctSavedVariables.lastScan[1] == nil or timeLimit > Shopkeeper.acctSavedVariables.lastScan[1]) then
+
+  if not Shopkeeper.isScanning and ((Shopkeeper.acctSavedVariables.lastScan[1] == nil) or (timeLimit > Shopkeeper.acctSavedVariables.lastScan[1])) then
     Shopkeeper.isScanning = true
+    local checkOlder = false

-    -- We'll track if we get a true back here or not, because if it's false,
-    -- someone checked this history manually recently and times may be off
-    -- by up to 10-15 seconds because of that, and the next scan may get items
-    -- that should have been caught by this one
-    local didScan = RequestGuildHistoryCategoryNewest(1, GUILD_HISTORY_SALES)
-
-    if Shopkeeper.missedScan[1] ~= nil then
-      Shopkeeper.missedLastScan[1] = Shopkeeper.missedScan[1]
-    else
-      Shopkeeper.missedLastScan[1] = false
-    end
-
-    if not didScan then
-      Shopkeeper.missedScan[1] = true
-    else
-      Shopkeeper.missedScan[1] = false
-    end
+    if Shopkeeper.numEvents[1] == nil or Shopkeeper.numEvents[1] == 0 then checkOlder = true end
+    RequestGuildHistoryCategoryNewest(1, GUILD_HISTORY_SALES)

     Shopkeeper.requestTimestamp = GetTimeStamp()
-    if checkOlder then
-      zo_callLater(function() Shopkeeper:ScanOlder(1, doAlert) end, 1250)
-    else
-      zo_callLater(function() Shopkeeper:DoScan(1, checkOlder, doAlert) end, 1250)
+    if checkOlder then
+      zo_callLater(function() Shopkeeper:ScanOlder(1, doAlert) end, 1500)
+    else
+      zo_callLater(function() Shopkeeper:DoScan(1, checkOlder, doAlert) end, 1500)
     end
   end
 end

--- It's silly, but I have to re-set the fonts each time the UI reloads because I define defaults
--- in the .xml (at least as far as I can figure.)  The PlayerActive event fires after each of these,
--- so it's a nice place to hook in for that.
-function Shopkeeper.PlayerActive()
-  Shopkeeper:UpdateFonts()
-end
-
 -- Handle the refresh button - do a scan if it's been more than a minute
 -- since the last successful one.
 function Shopkeeper.DoRefresh()
@@ -1199,12 +718,14 @@ function Shopkeeper.DoRefresh()
   -- or on purpose
   local timeLimit = timeStamp - 59
   local guildNum = GetNumGuilds()
-  if Shopkeeper.acctSavedVariables.lastScan[1] == nil or timeLimit > Shopkeeper.acctSavedVariables.lastScan[1] then
-    CHAT_SYSTEM:AddMessage("[Shopkeeper] " .. Shopkeeper.translate('refreshStart'))
-    Shopkeeper:ScanStores(false, true)
+  if guildNum > 0 then
+    if Shopkeeper.acctSavedVariables.lastScan[1] == nil or timeLimit > Shopkeeper.acctSavedVariables.lastScan[1] then
+      CHAT_SYSTEM:AddMessage("[Shopkeeper] " .. Shopkeeper.translate('refreshStart'))
+      Shopkeeper:ScanStores(true)

-  else
-    CHAT_SYSTEM:AddMessage("[Shopkeeper] " .. Shopkeeper.translate('refreshWait'))
+    else
+      CHAT_SYSTEM:AddMessage("[Shopkeeper] " .. Shopkeeper.translate('refreshWait'))
+    end
   end
 end

@@ -1218,11 +739,15 @@ function Shopkeeper.DoReset()
   Shopkeeper.acctSavedVariables.scanHistory = {}
   Shopkeeper.acctSavedVariables.lastScan = {}
   Shopkeeper.DisplayRows()
-  Shopkeeper:ScanStores(true, false)
+  Shopkeeper.isScanning = false
+  Shopkeeper.numEvents = {}
   CHAT_SYSTEM:AddMessage("[Shopkeeper] " .. Shopkeeper.translate('resetDone'))
+  CHAT_SYSTEM:AddMessage("[Shopkeeper] " .. Shopkeeper.translate('refreshStart'))
+  Shopkeeper:ScanStores(true)
 end

--- Set up the main window and the additional button added to the guild store interface
+-- Set up the labels and tooltips from translation files and do a couple other UI
+-- setup routines
 function Shopkeeper:SetupShopkeeperWindow()
   -- Shopkeeper button in guild store screen
   local reopenShopkeeper = CreateControlFromVirtual("ShopkeeperReopenButton", ZO_TradingHouseLeftPane, "ZO_DefaultButton")
@@ -1265,236 +790,97 @@ function Shopkeeper:SetupShopkeeperWindow()
   ShopkeeperStatsWindowTitle:SetText("Shopkeeper " .. Shopkeeper.translate('statsTitle'))
   ShopkeeperStatsWindowSliderLabel:SetText(Shopkeeper.translate('statsDays'))

-  -- Set up some helpful tooltips for the Buyer and Item column headers
+  -- Set up some helpful tooltips for the Buyer, Item, Time, and Price column headers
   ShopkeeperWindowBuyer:SetHandler("OnMouseEnter", function(self)
-    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('buyerTooltip'))
-  end)
-
-  ShopkeeperWindowBuyer:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
+    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('buyerTooltip')) end)

   ShopkeeperWindowItemName:SetHandler("OnMouseEnter", function(self)
-    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('itemTooltip'))
-  end)
-
-  ShopkeeperWindowItemName:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
+    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('itemTooltip')) end)

   ShopkeeperMiniWindowItemName:SetHandler("OnMouseEnter", function(self)
-    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('itemTooltip'))
-  end)
-
-  ShopkeeperMiniWindowItemName:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
+    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('itemTooltip')) end)

   ShopkeeperWindowSellTime:SetHandler("OnMouseEnter", function(self)
-    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sortTimeTip'))
-  end)
-
-  ShopkeeperWindowSellTime:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
+    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sortTimeTip')) end)

   ShopkeeperMiniWindowSellTime:SetHandler("OnMouseEnter", function(self)
-    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sortTimeTip'))
-  end)
-
-  ShopkeeperMiniWindowSellTime:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
+    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sortTimeTip')) end)

   ShopkeeperWindowPrice:SetHandler("OnMouseEnter", function(self)
-    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sortPriceTip'))
-  end)
+    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sortPriceTip')) end)

-  ShopkeeperWindowPrice:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
-
   ShopkeeperMiniWindowPrice:SetHandler("OnMouseEnter", function(self)
-    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sortPriceTip'))
-  end)
-
-  ShopkeeperMiniWindowPrice:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
-
+    ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sortPriceTip')) end)

   -- View switch button
-  local switchViews = CreateControlFromVirtual("ShopkeeperSwitchViewButton", ShopkeeperWindow, "ZO_DefaultButton")
-  switchViews:SetAnchor(BOTTOMLEFT, ShopkeeperWindow, BOTTOMLEFT, 20, -5)
-  switchViews:SetWidth(175)
-  switchViews:SetText(Shopkeeper.translate('viewModeAllName'))
-  switchViews:SetHandler("OnClicked", Shopkeeper.SwitchViewMode)
-
-  local miniSwitchViews = CreateControlFromVirtual("ShopkeeperMiniSwitchViewButton", ShopkeeperMiniWindow, "ZO_DefaultButton")
-  miniSwitchViews:SetAnchor(BOTTOMLEFT, ShopkeeperMiniWindow, BOTTOMLEFT, 2, -5)
-  miniSwitchViews:SetWidth(175)
-  miniSwitchViews:SetText(Shopkeeper.translate('viewModeAllName'))
-  miniSwitchViews:SetHandler("OnClicked", Shopkeeper.SwitchViewMode)
+  ShopkeeperSwitchViewButton:SetText(Shopkeeper.translate('viewModeAllName'))
+  ShopkeeperMiniSwitchViewButton:SetText(Shopkeeper.translate('viewModeAllName'))

   -- Total / unit price switch button
-  local unitPrice = CreateControlFromVirtual("ShopkeeperPriceSwitchButton", ShopkeeperWindow, "ZO_DefaultButton")
-  unitPrice:SetAnchor(LEFT, switchViews, RIGHT, 0, 0)
-  unitPrice:SetWidth(175)
   if Shopkeeper.savedVariables.showUnitPrice then
-    unitPrice:SetText(Shopkeeper.translate('showTotalPrice'))
+    ShopkeeperPriceSwitchButton:SetText(Shopkeeper.translate('showTotalPrice'))
   else
-    unitPrice:SetText(Shopkeeper.translate('showUnitPrice'))
+    ShopkeeperPriceSwitchButton:SetText(Shopkeeper.translate('showUnitPrice'))
   end
-  unitPrice:SetHandler("OnClicked", Shopkeeper.SwitchPriceMode)

-  local miniUnitPrice = CreateControlFromVirtual("ShopkeeperMiniPriceSwitchButton", ShopkeeperMiniWindow, "ZO_DefaultButton")
-  miniUnitPrice:SetAnchor(LEFT, miniSwitchViews, RIGHT, 0, 0)
-  miniUnitPrice:SetWidth(175)
   if Shopkeeper.savedVariables.showUnitPrice then
-    miniUnitPrice:SetText(Shopkeeper.translate('showTotalPrice'))
+    ShopkeeperMiniPriceSwitchButton:SetText(Shopkeeper.translate('showTotalPrice'))
   else
-    miniUnitPrice:SetText(Shopkeeper.translate('showUnitPrice'))
+    ShopkeeperMiniPriceSwitchButton:SetText(Shopkeeper.translate('showUnitPrice'))
   end
-  miniUnitPrice:SetHandler("OnClicked", Shopkeeper.SwitchPriceMode)

   -- Refresh button
-  local refreshButton = CreateControlFromVirtual("ShopkeeperRefreshButton", ShopkeeperWindow, "ZO_DefaultButton")
-  refreshButton:SetAnchor(BOTTOMRIGHT, ShopkeeperWindow, BOTTOMRIGHT, -20, -5)
-  refreshButton:SetWidth(150)
-  refreshButton:SetText(Shopkeeper.translate('refreshLabel'))
-  refreshButton:SetHandler("OnClicked", Shopkeeper.DoRefresh)
-  local miniRefreshButton = CreateControlFromVirtual("ShopkeeperMiniRefreshButton", ShopkeeperMiniWindow, "ZO_DefaultButton")
-  miniRefreshButton:SetAnchor(BOTTOMRIGHT, ShopkeeperMiniWindow, BOTTOMRIGHT, -2, -5)
-  miniRefreshButton:SetWidth(150)
-  miniRefreshButton:SetText(Shopkeeper.translate('refreshLabel'))
-  miniRefreshButton:SetHandler("OnClicked", Shopkeeper.DoRefresh)
+  ShopkeeperRefreshButton:SetText(Shopkeeper.translate('refreshLabel'))
+  ShopkeeperMiniRefreshButton:SetText(Shopkeeper.translate('refreshLabel'))

   -- Reset button
-  local resetButton = CreateControlFromVirtual("ShopkeeperResetButton", ShopkeeperWindow, "ZO_DefaultButton")
-  resetButton:SetAnchor(BOTTOMRIGHT, ShopkeeperWindow, BOTTOMRIGHT, -170, -5)
-  resetButton:SetWidth(150)
-  resetButton:SetText(Shopkeeper.translate('resetLabel'))
-  resetButton:SetHandler("OnClicked", Shopkeeper.DoReset)
-  local miniResetButton = CreateControlFromVirtual("ShopkeeperMiniResetButton", ShopkeeperMiniWindow, "ZO_DefaultButton")
-  miniResetButton:SetAnchor(BOTTOMRIGHT, ShopkeeperMiniWindow, BOTTOMRIGHT, -152, -5)
-  miniResetButton:SetWidth(150)
-  miniResetButton:SetText(Shopkeeper.translate('resetLabel'))
-  miniResetButton:SetHandler("OnClicked", Shopkeeper.DoReset)
+  ShopkeeperResetButton:SetText(Shopkeeper.translate('resetLabel'))
+  ShopkeeperMiniResetButton:SetText(Shopkeeper.translate('resetLabel'))

   -- Make the 15 rows that comprise the visible table
   if #Shopkeeper.DataRows == 0 then
     local dataRowOffsetX = 25
     local dataRowOffsetY = 74
-    for i = 1,15 do
+    for i = 1, 15 do
       local dRow = CreateControlFromVirtual("ShopkeeperDataRow", ShopkeeperWindow, "ShopkeeperDataRow", i)
       dRow:SetSimpleAnchorParent(dataRowOffsetX, dataRowOffsetY+((dRow:GetHeight()+2)*(i-1)))
       Shopkeeper.DataRows[i] = dRow
     end
   end

-  -- And for the mini window
+  -- And 8 for the mini window
   if #Shopkeeper.MiniDataRows == 0 then
     local dataRowOffsetX = 10
     local dataRowOffsetY = 74
-    for i = 1,8 do
+    for i = 1, 8 do
       local dRow = CreateControlFromVirtual("ShopkeeperMiniDataRow", ShopkeeperMiniWindow, "ShopkeeperMiniDataRow", i)
       dRow:SetSimpleAnchorParent(dataRowOffsetX, dataRowOffsetY+((dRow:GetHeight()+2)*(i-1)))
       Shopkeeper.MiniDataRows[i] = dRow
     end
   end
-
-  -- Close buttons
-  ShopkeeperWindowCloseButton:SetNormalTexture("/esoui/art/hud/radialicon_cancel_up.dds")
-  ShopkeeperWindowCloseButton:SetMouseOverTexture("/esoui/art/hud/radialicon_cancel_over.dds")
-  ShopkeeperWindowCloseButton:SetHandler("OnClicked", function() ShopkeeperWindow:SetHidden(true) end)
-  ShopkeeperMiniWindowCloseButton:SetNormalTexture("/esoui/art/hud/radialicon_cancel_up.dds")
-  ShopkeeperMiniWindowCloseButton:SetMouseOverTexture("/esoui/art/hud/radialicon_cancel_over.dds")
-  ShopkeeperMiniWindowCloseButton:SetHandler("OnClicked", function() ShopkeeperMiniWindow:SetHidden(true) end)
-  ShopkeeperStatsWindowCloseButton:SetNormalTexture("/esoui/art/hud/radialicon_cancel_up.dds")
-  ShopkeeperStatsWindowCloseButton:SetMouseOverTexture("/esoui/art/hud/radialicon_cancel_over.dds")
-  ShopkeeperStatsWindowCloseButton:SetHandler("OnClicked", function() ShopkeeperStatsWindow:SetHidden(true) end)

   -- Stats buttons
-  ShopkeeperWindowStatsButton:SetNormalTexture("/esoui/art/tradinghouse/tradinghouse_listings_tabicon_up.dds")
-  ShopkeeperWindowStatsButton:SetMouseOverTexture("/esoui/art/tradinghouse/tradinghouse_listings_tabicon_over.dds")
-  ShopkeeperWindowStatsButton:SetHandler("OnClicked", Shopkeeper.ToggleShopkeeperStatsWindow)
   ShopkeeperWindowStatsButton:SetHandler("OnMouseEnter", function(self)
     ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('statsTooltip'))
   end)
-  ShopkeeperWindowStatsButton:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
-  ShopkeeperMiniWindowStatsButton:SetNormalTexture("/esoui/art/tradinghouse/tradinghouse_listings_tabicon_up.dds")
-  ShopkeeperMiniWindowStatsButton:SetMouseOverTexture("/esoui/art/tradinghouse/tradinghouse_listings_tabicon_over.dds")
-  ShopkeeperMiniWindowStatsButton:SetHandler("OnClicked", Shopkeeper.ToggleShopkeeperStatsWindow)
   ShopkeeperMiniWindowStatsButton:SetHandler("OnMouseEnter", function(self)
     ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('statsTooltip'))
   end)
-  ShopkeeperMiniWindowStatsButton:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)

   -- View size change buttons
-  ShopkeeperWindowViewSizeButton:SetNormalTexture("/esoui/art/inventory/inventory_tabicon_quest_up.dds")
-  ShopkeeperWindowViewSizeButton:SetMouseOverTexture("/esoui/art/inventory/inventory_tabicon_quest_over.dds")
-  ShopkeeperWindowViewSizeButton:SetHandler("OnClicked", Shopkeeper.ToggleViewMode)
   ShopkeeperWindowViewSizeButton:SetHandler("OnMouseEnter", function(self)
     ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sizeTooltip'))
   end)
-  ShopkeeperWindowViewSizeButton:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
-  ShopkeeperMiniWindowViewSizeButton:SetNormalTexture("/esoui/art/inventory/inventory_tabicon_quest_up.dds")
-  ShopkeeperMiniWindowViewSizeButton:SetMouseOverTexture("/esoui/art/inventory/inventory_tabicon_quest_over.dds")
-  ShopkeeperMiniWindowViewSizeButton:SetHandler("OnClicked", Shopkeeper.ToggleViewMode)
   ShopkeeperMiniWindowViewSizeButton:SetHandler("OnMouseEnter", function(self)
     ZO_Tooltips_ShowTextTooltip(self, TOP, Shopkeeper.translate('sizeTooltip'))
   end)
-  ShopkeeperMiniWindowViewSizeButton:SetHandler("OnMouseExit", function(self) ZO_Tooltips_HideTextTooltip() end)
-

   -- Slider setup
   ShopkeeperWindow:SetHandler("OnMouseWheel", Shopkeeper.OnSliderMouseWheel)
-  Shopkeeper.shopSlider = CreateControl(ShopkeeperWindowSlider, ShopkeeperWindow, CT_SLIDER)
-  Shopkeeper.shopSlider.texture = "/esoui/art/miscellaneous/scrollbox_elevator.dds"
-  Shopkeeper.shopSlider.offset = 0
-  local tex = Shopkeeper.shopSlider.texture
-  Shopkeeper.shopSlider:SetDimensions(20,561)
-  Shopkeeper.shopSlider:SetMouseEnabled(true)
-  Shopkeeper.shopSlider:SetThumbTexture(tex,tex,tex,20,50,0,0,1,1)
-  Shopkeeper.shopSlider:SetMinMax(0, 100)
-  Shopkeeper.shopSlider:SetValue(0)
-  Shopkeeper.shopSlider:SetValueStep(1)
-  Shopkeeper.shopSlider:SetAnchor(LEFT, ShopkeeperWindow, RIGHT, -20, 17)
-  Shopkeeper.shopSlider:SetHandler("OnValueChanged", Shopkeeper.OnSliderMoved)
-  Shopkeeper.sliderBG = CreateControl(nil, Shopkeeper.shopSlider, CT_BACKDROP)
-  Shopkeeper.sliderBG:SetCenterColor(0, 0, 0)
-  Shopkeeper.sliderBG:SetAnchor(TOPLEFT, Shopkeeper.shopSlider, TOPLEFT, 0, -4)
-  Shopkeeper.sliderBG:SetAnchor(BOTTOMRIGHT, Shopkeeper.shopSlider, BOTTOMRIGHT, 0, 4)
-  Shopkeeper.sliderBG:SetEdgeTexture("EsoUI\\Art\\Tooltips\\UI-SliderBackdrop.dds", 32, 4)
-
+  ShopkeeperWindowSlider:SetValue(0)
   ShopkeeperMiniWindow:SetHandler("OnMouseWheel", Shopkeeper.OnSliderMouseWheel)
-  Shopkeeper.miniShopSlider = CreateControl(ShopkeeperMiniWindowSlider, ShopkeeperMiniWindow, CT_SLIDER)
-  Shopkeeper.miniShopSlider.texture = "/esoui/art/miscellaneous/scrollbox_elevator.dds"
-  Shopkeeper.miniShopSlider.offset = 0
-  Shopkeeper.miniShopSlider:SetDimensions(20,300)
-  Shopkeeper.miniShopSlider:SetMouseEnabled(true)
-  Shopkeeper.miniShopSlider:SetThumbTexture(tex,tex,tex,20,50,0,0,1,1)
-  Shopkeeper.miniShopSlider:SetMinMax(0, 100)
-  Shopkeeper.miniShopSlider:SetValue(0)
-  Shopkeeper.miniShopSlider:SetValueStep(1)
-  Shopkeeper.miniShopSlider:SetAnchor(LEFT, ShopkeeperMiniWindow, RIGHT, -20, 17)
-  Shopkeeper.miniShopSlider:SetHandler("OnValueChanged", Shopkeeper.OnSliderMoved)
-  Shopkeeper.miniSliderBG = CreateControl(nil, Shopkeeper.miniShopSlider, CT_BACKDROP)
-  Shopkeeper.miniSliderBG:SetCenterColor(0, 0, 0)
-  Shopkeeper.miniSliderBG:SetAnchor(TOPLEFT, Shopkeeper.miniShopSlider, TOPLEFT, 0, -4)
-  Shopkeeper.miniSliderBG:SetAnchor(BOTTOMRIGHT, Shopkeeper.miniShopSlider, BOTTOMRIGHT, 0, 4)
-  Shopkeeper.miniSliderBG:SetEdgeTexture("EsoUI\\Art\\Tooltips\\UI-SliderBackdrop.dds", 32, 4)
-
-  Shopkeeper.statsSlider = CreateControl(ShopkeeperStatsWindowSlider, ShopkeeperStatsWindow, CT_SLIDER)
-  Shopkeeper.statsSlider.texture = "/esoui/art/miscellaneous/scrollbox_elevator.dds"
-  Shopkeeper.statsSlider.offset = 0
-  Shopkeeper.statsSlider:SetDimensions(375,14)
-  Shopkeeper.statsSlider:SetMouseEnabled(true)
-  Shopkeeper.statsSlider:SetThumbTexture("EsoUI\\Art\\Miscellaneous\\scrollbox_elevator.dds", "EsoUI\\Art\\Miscellaneous\\scrollbox_elevator_disabled.dds", nil, 8, 16)
-  Shopkeeper.statsSlider:SetMinMax(0, 30)
-  Shopkeeper.statsSlider:SetValue(0)
-  Shopkeeper.statsSlider:SetValueStep(1)
-  Shopkeeper.statsSlider:SetOrientation(ORIENTATION_HORIZONTAL)
-  Shopkeeper.statsSlider:SetAnchor(BOTTOM, ShopkeeperStatsWindow, BOTTOM, 25, -15)
-  Shopkeeper.statsSlider:SetHandler("OnValueChanged", Shopkeeper.OnStatsSliderMoved)
-  Shopkeeper.statsSliderBG = CreateControl(nil, Shopkeeper.statsSlider, CT_BACKDROP)
-  Shopkeeper.statsSliderBG:SetCenterColor(0, 0, 0)
-  Shopkeeper.statsSliderBG:SetAnchor(TOPLEFT, Shopkeeper.statsSlider, TOPLEFT, 0, 4)
-  Shopkeeper.statsSliderBG:SetAnchor(BOTTOMRIGHT, Shopkeeper.statsSlider, BOTTOMRIGHT, 0, -4)
-  Shopkeeper.statsSliderBG:SetEdgeTexture("EsoUI\\Art\\Tooltips\\UI-SliderBackdrop.dds", 32, 4)
-
-
-  -- Sorting handlers
-  ShopkeeperWindowPrice:SetHandler("OnMouseUp", Shopkeeper.PriceSort)
-  ShopkeeperWindowSellTime:SetHandler("OnMouseUp", Shopkeeper.TimeSort)
-  ShopkeeperMiniWindowPrice:SetHandler("OnMouseUp", Shopkeeper.PriceSort)
-  ShopkeeperMiniWindowSellTime:SetHandler("OnMouseUp", Shopkeeper.TimeSort)
+  ShopkeeperMiniWindowSlider:SetValue(0)
+  ShopkeeperStatsWindowSlider:SetValue(0)

   -- Search handler
   ZO_PreHookHandler(ShopkeeperWindowSearchBox, "OnTextChanged", function(self) Shopkeeper.DoSearch(ShopkeeperWindowSearchBox:GetText()) end)
@@ -1534,8 +920,8 @@ function Shopkeeper:Initialize()
     ["scanHistory"] = {},
   }

-  self.savedVariables = ZO_SavedVars:New("ShopkeeperSavedVars", 1, Shopkeeper.GetAccountName(), Defaults)
-  self.acctSavedVariables = ZO_SavedVars:NewAccountWide("ShopkeeperSavedVars", 1, Shopkeeper.GetAccountName(), acctDefaults)
+  self.savedVariables = ZO_SavedVars:New("ShopkeeperSavedVars", 1, GetDisplayName(), Defaults)
+  self.acctSavedVariables = ZO_SavedVars:NewAccountWide("ShopkeeperSavedVars", 1, GetDisplayName(), acctDefaults)
   self.ScanResults = Shopkeeper.acctSavedVariables.scanHistory

   -- Update the lastScan value, as it was previously a number
@@ -1571,7 +957,7 @@ function Shopkeeper:Initialize()
   end

   -- Now that we've truncated, populate the SelfSales table
-  local loggedInAccount = string.lower(Shopkeeper.GetAccountName())
+  local loggedInAccount = string.lower(GetDisplayName())
   for i = 1, #self.ScanResults do
     if string.lower(self.ScanResults[i][8]) == loggedInAccount then table.insert(self.SelfSales, self.ScanResults[i]) end
   end
@@ -1644,18 +1030,15 @@ function Shopkeeper:Initialize()
     ShopkeeperMiniWindow:SetHidden(true)
   end)

-  -- Update fonts after each UI load
-  EVENT_MANAGER:RegisterForEvent(Shopkeeper.name, EVENT_PLAYER_ACTIVATED, Shopkeeper.PlayerActive)
-
   -- RegisterForUpdate lets us scan at a given interval (in ms), so we'll use that to
   -- keep the sales history updated
   local scanInterval = self.savedVariables.scanFreq * 1000
-  EVENT_MANAGER:RegisterForUpdate(Shopkeeper.name, scanInterval, function() Shopkeeper:ScanStores(false, false) end)
+  EVENT_MANAGER:RegisterForUpdate(Shopkeeper.name, scanInterval, function() Shopkeeper:ScanStores(false) end)

   -- Right, we're all set up, so give the client a few seconds to catch up on everything
   -- and then do an initial (deep) scan in case it's been a while since the player
   -- logged on.
-  zo_callLater(function() Shopkeeper:ScanStores(true, false) end, 5000)
+  zo_callLater(function() Shopkeeper:ScanStores(false) end, 5000)

 end

diff --git a/Shopkeeper.txt b/Shopkeeper.txt
index 1bb232d..c65a36c 100644
--- a/Shopkeeper.txt
+++ b/Shopkeeper.txt
@@ -2,9 +2,9 @@
 ## APIVersion: 100008
 ## Description: Notifies you when you've sold something in a guild store and presents guild sales info in a convenient table.
 ## Author: Dan Stone (@khaibit) - dankitymao@gmail.com
-## Version: 0.9a
+## Version: 0.9b
 ## License: See license - distribution without license is prohibited!
-## LastUpdated: August 8, 2014
+## LastUpdated: August 10, 2014
 ## SavedVariables: ShopkeeperSavedVars
 ## OptionalDependsOn: LibAddonMenu-2.0 LibMediaProvider-1.0 LibStub

@@ -25,6 +25,8 @@ Libs\LibAddonMenu-2.0\controls\slider.lua
 Libs\LibAddonMenu-2.0\controls\texture.lua
 Shopkeeper.xml
 Shopkeeper_Namespace_Init.lua
+Shopkeeper_Util.lua
+Shopkeeper_UI.lua
 i18n\$(language).lua
 Shopkeeper.lua
 bindings.xml
\ No newline at end of file
diff --git a/Shopkeeper.xml b/Shopkeeper.xml
index e1d8b7f..7acd0db 100644
--- a/Shopkeeper.xml
+++ b/Shopkeeper.xml
@@ -1,6 +1,6 @@
 <!--
       Shopkeeper UI Layout File
-      Last Updated August 8, 2014
+      Last Updated August 10, 2014
       Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
       Released under terms in license accompanying this file.
       Distribution without license is prohibited!
@@ -14,41 +14,62 @@
       </OnMoveStop>
       <Controls>
         <Backdrop name="$(parent)BG" inherits="ZO_DefaultBackdrop" />
-        <Label name="$(parent)Title" font="ZoFontGame" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="CENTER" text="Shopkeeper Sales Statistics">
+        <Label name="$(parent)Title" height="25" width="95" inheritAlpha="true" color="D5B526" verticalAlignment="CENTER" horizontalAlignment="CENTER" text="Shopkeeper Sales Statistics">
           <Anchor point="TOP" relativeTo="$(parent)" relativePoint="TOP" offsetX="0" offsetY="5" />
         </Label>
-        <Button name="$(parent)CloseButton" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
+        <Button name="$(parent)CloseButton" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
           <Anchor point="CENTER" relativeTo="$(parent)" relativePoint="TOPRIGHT" offsetX="-20" offsetY="20" />
           <Dimensions x="48" y="48" />
+          <Textures normal="/esoui/art/hud/radialicon_cancel_up.dds" mouseOver="/esoui/art/hud/radialicon_cancel_over.dds" />
+          <OnClicked>
+            ShopkeeperStatsWindow:SetHidden(true)
+          </OnClicked>
         </Button>
-        <Label name="$(parent)ItemsSoldLabel" font="ZoFontGame" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Items sold: 0">
-          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="15" offsetY="55" />
+        <Label name="$(parent)ItemsSoldLabel" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Items sold: 0">
+          <Anchor point="TOP" relativeTo="$(parent)" relativePoint="TOP" offsetX="0" offsetY="45" />
         </Label>
-        <Label name="$(parent)TotalGoldLabel" font="ZoFontGame" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Total gold: 0 (0 per day)">
-          <Anchor point="TOPRIGHT" relativeTo="$(parent)" relativePoint="TOPRIGHT" offsetX="-15" offsetY="55" />
+        <Label name="$(parent)TotalGoldLabel" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Total gold: 0 (0 per day)">
+          <Anchor point="TOP" relativeTo="$(parent)" relativePoint="TOP" offsetX="0" offsetY="70" />
         </Label>
-        <Label name="$(parent)BiggestSaleLabel" font="ZoFontGame" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="CENTER" text="Biggest sale: (0)">
-          <Anchor point="TOP" relativeTo="$(parent)" relativePoint="TOP" offsetX="0" offsetY="90" />
+        <Label name="$(parent)BiggestSaleLabel" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="CENTER" text="Biggest sale: (0)">
+          <Anchor point="TOP" relativeTo="$(parent)" relativePoint="TOP" offsetX="0" offsetY="95" />
         </Label>
-        <Label name="$(parent)SliderSettingLabel" font="ZoFontGame" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="CENTER" text="Using all data">
+        <Label name="$(parent)SliderSettingLabel" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="CENTER" text="Using all data">
           <Anchor point="TOP" relativeTo="$(parent)" relativePoint="TOP" offsetX="0" offsetY="125" />
         </Label>
-        <Label name="$(parent)SliderLabel" font="ZoFontGame" height="25" width="50" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Days: ">
+        <Label name="$(parent)SliderLabel" height="25" width="50" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Days: ">
           <Anchor point="BOTTOMLEFT" relativeTo="$(parent)" relativePoint="BOTTOMLEFT" offsetX="10" offsetY="-14" />
         </Label>
+        <Slider name="$(parent)Slider" mouseEnabled="true" step="1" orientation="horizontal">
+          <Anchor point="BOTTOM" relativeTo="$(parent)" relativePoint="BOTTOM" offsetX="25" offsetY="-15" />
+          <ThumbTexture textureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" disabledTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" highlightedTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" thumbWidth="8" thumbHeight="16" left="0" top="0" bottom="1" right="1" />
+          <Dimensions x="375" y="14" />
+          <Limits min="0" max="30" />
+          <OnValueChanged>
+            Shopkeeper.OnStatsSliderMoved()
+          </OnValueChanged>
+          <Controls>
+            <Backdrop name="$(parent)Backdrop" centerColor="000000">
+              <Edge file="/EsoUI/Art/Tooltips/UI-SliderBackdrop.dds" edgeFileWidth="32" edgeFileHeight="4" />
+              <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="4" />
+              <Anchor point="BOTTOMRIGHT" relativeTo="$(parent)" relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="-4" />
+            </Backdrop>
+          </Controls>
+        </Slider>
       </Controls>
     </TopLevelControl>
+
     <TopLevelControl movable="true" mouseEnabled="true" name="ShopkeeperWindow" hidden="true">
-      <Dimensions x="907" y="685" />
+      <Dimensions x="925" y="685" />
       <OnMoveStop>
         Shopkeeper.OnWindowMoveStop()
       </OnMoveStop>
       <Controls>
         <Backdrop name="$(parent)BG" inherits="ZO_DefaultBackdrop" />
-        <Label name="$(parent)SearchLabel" font="ZoFontGame" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Search: ">
+        <Label name="$(parent)SearchLabel" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Search: ">
           <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="15" offsetY="10" />
         </Label>
-        <EditBox name="$(parent)SearchBox" mouseEnabled="true" editEnabled="true" font="ZoFontGame" textType="TEXT_TYPE_ALL" multiLine="false" newLineEnabled="false">
+        <EditBox name="$(parent)SearchBox" mouseEnabled="true" editEnabled="true" textType="TEXT_TYPE_ALL" multiLine="false" newLineEnabled="false">
           <Anchor point="LEFT" relativeTo="$(parent)SearchLabel" relativePoint="RIGHT" offsetX="5" offsetY="0" />
           <Dimensions x="175" y="25" />
           <Controls>
@@ -67,54 +88,135 @@
             self:LoseFocus()
           </OnEscape>
         </EditBox>
-        <Button name="$(parent)CloseButton" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
+        <Button name="$(parent)CloseButton" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
           <Anchor point="CENTER" relativeTo="$(parent)" relativePoint="TOPRIGHT" offsetX="-20" offsetY="20" />
           <Dimensions x="48" y="48" />
+          <Textures normal="/esoui/art/hud/radialicon_cancel_up.dds" mouseOver="/esoui/art/hud/radialicon_cancel_over.dds" />
+          <OnClicked>
+            ShopkeeperWindow:SetHidden(true)
+          </OnClicked>
         </Button>
-        <Label name="$(parent)Title" font="ZoFontGame" height="25" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="CENTER" text="Shopkeeper - Your Sales">
+        <Label name="$(parent)Title" height="25" inheritAlpha="true" color="D5B526" verticalAlignment="TOP" horizontalAlignment="CENTER" text="Shopkeeper - Your Sales">
 	        <Anchor point="TOP" relativeTo="$(parent)" relativePoint="TOP" offsetX="-10" offsetY="5" />
         </Label>
-        <Button name="$(parent)StatsButton" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
+        <Button name="$(parent)StatsButton" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
           <Anchor point="LEFT" relativeTo="$(parent)Title" relativePoint="RIGHT" offsetX="15" offsetY="0" />
           <Dimensions x="48" y="48" />
+          <Textures normal="/esoui/art/tradinghouse/tradinghouse_listings_tabicon_up.dds" mouseOver="/esoui/art/tradinghouse/tradinghouse_listings_tabicon_over.dds" />
+          <OnClicked>
+            Shopkeeper.ToggleShopkeeperStatsWindow()
+          </OnClicked>
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
-        <Button name="$(parent)ViewSizeButton" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
+        <Button name="$(parent)ViewSizeButton" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
           <Anchor point="LEFT" relativeTo="$(parent)StatsButton" relativePoint="RIGHT" offsetX="0" offsetY="0" />
           <Dimensions x="48" y="48" />
+          <Textures normal="/esoui/art/inventory/inventory_tabicon_quest_up.dds" mouseOver="/esoui/art/inventory/inventory_tabicon_quest_over.dds" />
+          <OnClicked>
+            Shopkeeper.ToggleViewMode()
+          </OnClicked>
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
-        <Button name="$(parent)Buyer" font="ZoFontGame" inheritAlpha="true" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Buyer">
+        <Button name="$(parent)Buyer" inheritAlpha="true" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Buyer">
           <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="30" offsetY="44" />
-          <Dimensions x="60" y="25" />
+          <Dimensions x="78" y="25" />
           <FontColors normalColor="3689EF" mouseOverColor="3689EF" pressedColor="3689EF"/>
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
-        <Label name="$(parent)Guild" font="ZoFontGame" width="140" height="25" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Guild">
-          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="143" offsetY="44" />
+        <Label name="$(parent)Guild" width="140" height="25" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Guild">
+          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="161" offsetY="44" />
         </Label>
-        <Button name="$(parent)ItemName" font="ZoFontGame" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Item">
+        <Button name="$(parent)ItemName" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Item">
           <Dimensions x="200" y="25" />
-          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="352" offsetY="44" />
+          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="370" offsetY="44" />
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
-        <Button name="$(parent)SellTime" font="ZoFontGame" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Sale Time">
-          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="649" offsetY="44" />
+        <Button name="$(parent)SellTime" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Sale Time">
+          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="667" offsetY="44" />
           <Dimensions x="95" y="25" />
+          <OnMouseUp>
+            Shopkeeper.TimeSort()
+          </OnMouseUp>
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
         <Texture name="$(parent)SortTime" textureFile="/esoui/art/miscellaneous/list_sortheader_icon_sortdown.dds" alpha="1">
           <Dimensions x="40" y="40" />
-          <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="731" offsetY="54" />
+          <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="749" offsetY="54" />
           <TextureCoords left="0" right="1" top="0" bottom="1" />
         </Texture>
-        <Button name="$(parent)Price" font="ZoFontGame" inheritAlpha="true" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Price">
-          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="775" offsetY="44" />
+        <Button name="$(parent)Price" inheritAlpha="true" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Price">
+          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="793" offsetY="44" />
           <Dimensions x="85" y="25" />
           <FontColors normalColor="D5B526" mouseOverColor="D5B526" pressedColor="D5B526" />
+          <OnMouseUp>
+            Shopkeeper.PriceSort()
+          </OnMouseUp>
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
         <Texture name="$(parent)SortPrice" textureFile="/esoui/art/miscellaneous/list_sortheader_icon_neutral.dds" alpha="1">
           <Dimensions x="40" y="40" />
-          <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="850" offsetY="54" />
+          <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="868" offsetY="54" />
           <TextureCoords left="0" right="1" top="0" bottom="1" />
-        </Texture>
+        </Texture>
+        <Button name="ShopkeeperSwitchViewButton" inherits="ZO_DefaultButton">
+          <Anchor point="BOTTOMLEFT" relativeTo="$(parent)" relativePoint="BOTTOMLEFT" offsetX="20" offsetY="-5" />
+          <Dimensions x="160" />
+          <OnClicked>
+            Shopkeeper.SwitchViewMode()
+          </OnClicked>
+        </Button>
+        <Button name="ShopkeeperPriceSwitchButton" inherits="ZO_DefaultButton">
+          <Anchor point="LEFT" relativeTo="ShopkeeperSwitchViewButton" relativePoint="RIGHT" offsetX="0" offsetY="0" />
+          <Dimensions x="160" />
+          <OnClicked>
+            Shopkeeper.SwitchPriceMode()
+          </OnClicked>
+        </Button>
+        <Button name="ShopkeeperRefreshButton" inherits="ZO_DefaultButton">
+          <Anchor point="BOTTOMRIGHT" relativeTo="$(parent)" relativePoint="BOTTOMRIGHT" offsetX="-20" offsetY="-5" />
+          <Dimensions x="160" />
+          <OnClicked>
+            Shopkeeper.DoRefresh()
+          </OnClicked>
+        </Button>
+        <Button name="ShopkeeperResetButton" inherits="ZO_DefaultButton">
+          <Anchor point="RIGHT" relativeTo="ShopkeeperRefreshButton" relativePoint="LEFT" offsetX="0" offsetY="0" />
+          <Dimensions x="160" />
+          <OnClicked>
+            Shopkeeper.DoReset()
+          </OnClicked>
+        </Button>
+        <Slider name="$(parent)Slider" mouseEnabled="true" step="1">
+          <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="RIGHT" offsetX="-20" offsetY="17" />
+          <ThumbTexture textureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" disabledTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" highlightedTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" thumbWidth="20" thumbHeight="50" left="0" top="0" bottom="1" right="1" />
+          <Dimensions x="20" y="561" />
+          <Limits min="0" max="100" />
+          <OnValueChanged>
+            Shopkeeper.OnSliderMoved()
+          </OnValueChanged>
+          <Controls>
+            <Backdrop name="$(parent)Backdrop" centerColor="000000">
+              <Edge file="/EsoUI/Art/Tooltips/UI-SliderBackdrop.dds" edgeFileWidth="32" edgeFileHeight="4" />
+              <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="-4" />
+              <Anchor point="BOTTOMRIGHT" relativeTo="$(parent)" relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="4" />
+            </Backdrop>
+          </Controls>
+        </Slider>
       </Controls>
     </TopLevelControl>
+
     <TopLevelControl movable="true" mouseEnabled="true" name="ShopkeeperMiniWindow" hidden="true">
       <Dimensions x="650" y="416" />
       <OnMoveStop>
@@ -122,10 +224,10 @@
       </OnMoveStop>
       <Controls>
         <Backdrop name="$(parent)BG" inherits="ZO_DefaultBackdrop" />
-        <Label name="$(parent)SearchLabel" font="ZoFontGame" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Search: ">
+        <Label name="$(parent)SearchLabel" height="25" width="95" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Search: ">
           <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="15" offsetY="10" />
         </Label>
-        <EditBox name="$(parent)SearchBox" mouseEnabled="true" editEnabled="true" font="ZoFontGame" textType="TEXT_TYPE_ALL" multiLine="false" newLineEnabled="false">
+        <EditBox name="$(parent)SearchBox" mouseEnabled="true" editEnabled="true" textType="TEXT_TYPE_ALL" multiLine="false" newLineEnabled="false">
           <Anchor point="LEFT" relativeTo="$(parent)SearchLabel" relativePoint="RIGHT" offsetX="5" offsetY="0" />
           <Dimensions x="175" y="25" />
           <Controls>
@@ -144,91 +246,170 @@
             self:LoseFocus()
           </OnEscape>
         </EditBox>
-        <Button name="$(parent)CloseButton" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
+        <Button name="$(parent)CloseButton" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
           <Anchor point="CENTER" relativeTo="$(parent)" relativePoint="TOPRIGHT" offsetX="-20" offsetY="20" />
           <Dimensions x="48" y="48" />
+          <Textures normal="/esoui/art/hud/radialicon_cancel_up.dds" mouseOver="/esoui/art/hud/radialicon_cancel_over.dds" />
+          <OnClicked>
+            ShopkeeperMiniWindow:SetHidden(true)
+          </OnClicked>
         </Button>
-        <Label name="$(parent)Title" font="ZoFontGame" height="25" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="CENTER" text="Shopkeeper - Your Sales">
+        <Label name="$(parent)Title" height="25" inheritAlpha="true" color="D5B526" verticalAlignment="TOP" horizontalAlignment="CENTER" text="Shopkeeper - Your Sales">
 	        <Anchor point="LEFT" relativeTo="$(parent)SearchBox" relativePoint="RIGHT" offsetX="40" offsetY="0" />
         </Label>
-        <Button name="$(parent)StatsButton" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
+        <Button name="$(parent)StatsButton" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
           <Anchor point="LEFT" relativeTo="$(parent)Title" relativePoint="RIGHT" offsetX="15" offsetY="0" />
           <Dimensions x="48" y="48" />
+          <Textures normal="/esoui/art/tradinghouse/tradinghouse_listings_tabicon_up.dds" mouseOver="/esoui/art/tradinghouse/tradinghouse_listings_tabicon_over.dds" />
+          <OnClicked>
+            Shopkeeper.ToggleShopkeeperStatsWindow()
+          </OnClicked>
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
-        <Button name="$(parent)ViewSizeButton" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
+        <Button name="$(parent)ViewSizeButton" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="CENTER">
           <Anchor point="LEFT" relativeTo="$(parent)StatsButton" relativePoint="RIGHT" offsetX="0" offsetY="0" />
           <Dimensions x="48" y="48" />
+          <Textures normal="/esoui/art/inventory/inventory_tabicon_quest_up.dds" mouseOver="/esoui/art/inventory/inventory_tabicon_quest_over.dds" />
+          <OnClicked>
+            Shopkeeper.ToggleViewMode()
+          </OnClicked>
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
-        <Label name="$(parent)Guild" font="ZoFontGame" width="120" height="25" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Guild">
+        <Label name="$(parent)Guild" width="120" height="25" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Guild">
           <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="15" offsetY="44" />
         </Label>
-        <Button name="$(parent)ItemName" font="ZoFontGame" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Item">
+        <Button name="$(parent)ItemName" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Item">
           <Dimensions x="200" y="25" />
           <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="140" offsetY="44" />
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
-        <Button name="$(parent)SellTime" font="ZoFontGame" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Sale Time">
+        <Button name="$(parent)SellTime" inheritAlpha="true" color="FFFFFF" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Sale Time">
           <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="420" offsetY="44" />
           <Dimensions x="95" y="25" />
+          <OnMouseUp>
+            Shopkeeper.TimeSort()
+          </OnMouseUp>
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
         <Texture name="$(parent)SortTime" textureFile="/esoui/art/miscellaneous/list_sortheader_icon_sortdown.dds" alpha="1">
           <Dimensions x="40" y="40" />
           <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="495" offsetY="54" />
           <TextureCoords left="0" right="1" top="0" bottom="1" />
         </Texture>
-        <Button name="$(parent)Price" font="ZoFontGame" inheritAlpha="true" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Price">
+        <Button name="$(parent)Price" inheritAlpha="true" verticalAlignment="TOP" horizontalAlignment="LEFT" text="Price">
           <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="540" offsetY="44" />
           <Dimensions x="85" y="25" />
           <FontColors normalColor="D5B526" mouseOverColor="D5B526" pressedColor="D5B526" />
+          <OnMouseUp>
+            Shopkeeper.PriceSort()
+          </OnMouseUp>
+          <OnMouseExit>
+            ZO_Tooltips_HideTextTooltip()
+          </OnMouseExit>
         </Button>
         <Texture name="$(parent)SortPrice" textureFile="/esoui/art/miscellaneous/list_sortheader_icon_neutral.dds" alpha="1">
           <Dimensions x="40" y="40" />
           <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="600" offsetY="54" />
           <TextureCoords left="0" right="1" top="0" bottom="1" />
-        </Texture>
+        </Texture>
+        <Button name="ShopkeeperMiniSwitchViewButton" inherits="ZO_DefaultButton">
+          <Anchor point="BOTTOMLEFT" relativeTo="$(parent)" relativePoint="BOTTOMLEFT" offsetX="2" offsetY="-5" />
+          <Dimensions x="160" />
+          <OnClicked>
+            Shopkeeper.SwitchViewMode()
+          </OnClicked>
+        </Button>
+        <Button name="ShopkeeperMiniPriceSwitchButton" inherits="ZO_DefaultButton">
+          <Anchor point="LEFT" relativeTo="ShopkeeperMiniSwitchViewButton" relativePoint="RIGHT" offsetX="0" offsetY="0" />
+          <Dimensions x="160" />
+          <OnClicked>
+            Shopkeeper.SwitchPriceMode()
+          </OnClicked>
+        </Button>
+        <Button name="ShopkeeperMiniRefreshButton" inherits="ZO_DefaultButton">
+          <Anchor point="BOTTOMRIGHT" relativeTo="$(parent)" relativePoint="BOTTOMRIGHT" offsetX="-2" offsetY="-5" />
+          <Dimensions x="160" />
+          <OnClicked>
+            Shopkeeper.DoRefresh()
+          </OnClicked>
+        </Button>
+        <Button name="ShopkeeperMiniResetButton" inherits="ZO_DefaultButton">
+          <Anchor point="RIGHT" relativeTo="ShopkeeperMiniRefreshButton" relativePoint="LEFT" offsetX="0" offsetY="0" />
+          <Dimensions x="160" />
+          <OnClicked>
+            Shopkeeper.DoReset()
+          </OnClicked>
+        </Button>
+        <Slider name="$(parent)Slider" mouseEnabled="true" step="1">
+          <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="RIGHT" offsetX="-20" offsetY="17" />
+          <ThumbTexture textureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" disabledTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" highlightedTextureFile="/esoui/art/miscellaneous/scrollbox_elevator.dds" thumbWidth="20" thumbHeight="50" left="0" top="0" bottom="1" right="1" />
+          <Dimensions x="20" y="300" />
+          <Limits min="0" max="100" />
+          <OnValueChanged>
+            Shopkeeper.OnSliderMoved()
+          </OnValueChanged>
+          <Controls>
+            <Backdrop name="$(parent)Backdrop" centerColor="000000">
+              <Edge file="/EsoUI/Art/Tooltips/UI-SliderBackdrop.dds" edgeFileWidth="32" edgeFileHeight="4" />
+              <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="-4" />
+              <Anchor point="BOTTOMRIGHT" relativeTo="$(parent)" relativePoint="BOTTOMRIGHT" offsetX="0" offsetY="4" />
+            </Backdrop>
+          </Controls>
+        </Slider>
       </Controls>
     </TopLevelControl>
-    <Control name="ShopkeeperDataRow" horizontalAlignment="LEFT" verticalAlignment="CENTER" color="CFDCBD" font="ZoFontGame" virtual="true">
-      <Dimensions x="857" y="36" />
+
+    <Control name="ShopkeeperDataRow" horizontalAlignment="LEFT" verticalAlignment="CENTER" color="CFDCBD" virtual="true">
+      <Dimensions x="875" y="36" />
       <Anchor point="TOPLEFT" offsetX="25" offsetY="25" />
       <Controls>
         <Backdrop name="$(parent)BG" inherits="ZO_ThinBackdrop" />
-        <Button name="$(parent)Buyer" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Buyer">
+        <Button name="$(parent)Buyer" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Buyer">
           <Anchor point="TOPLEFT" offsetX="5" offsetY="5" />
-          <Dimensions x="113" y="26" />
+          <Dimensions x="131" y="26" />
           <FontColors normalColor="3689EF" mouseOverColor="69EFFF" pressedColor="3689EF" />
         </Button>
-        <Button name="$(parent)Guild" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Guild">
-          <Anchor point="TOPLEFT" offsetX="120" offsetY="5" />
+        <Button name="$(parent)Guild" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Guild">
+          <Anchor point="TOPLEFT" offsetX="138" offsetY="5" />
           <Dimensions x="160" y="26" />
           <FontColors normalColor="FFFFFF" mouseOverColor="FFFFFF" pressedColor="FFFFFF" />
         </Button>
         <Texture name="$(parent)ItemIcon" alpha="1">
           <Dimensions x="32" y="32" />
-          <Anchor point="TOPLEFT" offsetX="288" offsetY="3" />
+          <Anchor point="TOPLEFT" offsetX="306" offsetY="3" />
           <TextureCoords left="0" right="1" top="0" bottom="1" />
         </Texture>
-        <Button name="$(parent)ItemName" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Item Name">
-          <Anchor point="TOPLEFT" offsetX="328" offsetY="5" />
+        <Button name="$(parent)ItemName" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Item Name">
+          <Anchor point="TOPLEFT" offsetX="346" offsetY="5" />
           <Dimensions x="242" y="26" />
           <FontColors normalColor="0000FF" mouseOverColor="0000FF" pressedColor="0000FF" />
         </Button>
-        <Label name="$(parent)Quantity" font="ZoFontGame" width="30" height="26" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="x1">
-          <Anchor point="TOPLEFT" offsetX="575" offsetY="7" />
+        <Label name="$(parent)Quantity" width="30" height="26" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="x1">
+          <Anchor point="TOPLEFT" offsetX="593" offsetY="7" />
         </Label>
-        <Label name="$(parent)SellTime" font="ZoFontGame" width="115" height="26" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Time">
-          <Anchor point="TOPLEFT" offsetX="625" offsetY="7" />
+        <Label name="$(parent)SellTime" width="115" height="26" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Time">
+          <Anchor point="TOPLEFT" offsetX="643" offsetY="7" />
         </Label>
-        <Label name="$(parent)Price" font="ZoFontGame" width="115" height="26" inheritAlpha="true" color="D5B526" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Price">
-          <Anchor point="TOPLEFT" offsetX="751" offsetY="7" />
+        <Label name="$(parent)Price" width="115" height="26" inheritAlpha="true" color="D5B526" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Price">
+          <Anchor point="TOPLEFT" offsetX="769" offsetY="7" />
         </Label>
       </Controls>
     </Control>
-    <Control name="ShopkeeperMiniDataRow" horizontalAlignment="LEFT" verticalAlignment="CENTER" color="CFDCBD" font="ZoFontGame" virtual="true">
+
+    <Control name="ShopkeeperMiniDataRow" horizontalAlignment="LEFT" verticalAlignment="CENTER" color="CFDCBD" virtual="true">
       <Dimensions x="615" y="36" />
       <Anchor point="TOPLEFT" offsetX="10" offsetY="25" />
       <Controls>
         <Backdrop name="$(parent)BG" inherits="ZO_ThinBackdrop" />
-        <Button name="$(parent)Guild" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Guild">
+        <Button name="$(parent)Guild" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Guild">
           <Anchor point="TOPLEFT" offsetX="5" offsetY="5" />
           <Dimensions x="120" y="26" />
           <FontColors normalColor="FFFFFF" mouseOverColor="FFFFFF" pressedColor="FFFFFF" />
@@ -238,18 +419,18 @@
           <Anchor point="TOPLEFT" offsetX="125" offsetY="3" />
           <TextureCoords left="0" right="1" top="0" bottom="1" />
         </Texture>
-        <Button name="$(parent)ItemName" font="ZoFontGame" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Item Name">
+        <Button name="$(parent)ItemName" inheritAlpha="true" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Item Name">
           <Anchor point="TOPLEFT" offsetX="160" offsetY="5" />
           <Dimensions x="215" y="26" />
           <FontColors normalColor="0000FF" mouseOverColor="0000FF" pressedColor="0000FF" />
         </Button>
-        <Label name="$(parent)Quantity" font="ZoFontGame" width="30" height="26" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="x1">
+        <Label name="$(parent)Quantity" width="30" height="26" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="x1">
           <Anchor point="TOPLEFT" offsetX="380" offsetY="7" />
         </Label>
-        <Label name="$(parent)SellTime" font="ZoFontGame" width="115" height="26" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Time">
+        <Label name="$(parent)SellTime" width="115" height="26" inheritAlpha="true" color="FFFFFF" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Time">
           <Anchor point="TOPLEFT" offsetX="410" offsetY="7" />
         </Label>
-        <Label name="$(parent)Price" font="ZoFontGame" width="115" height="26" inheritAlpha="true" color="D5B526" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Price">
+        <Label name="$(parent)Price" width="115" height="26" inheritAlpha="true" color="D5B526" verticalAlignment="CENTER" horizontalAlignment="LEFT" text="Price">
           <Anchor point="TOPLEFT" offsetX="530" offsetY="7" />
         </Label>
       </Controls>
diff --git a/Shopkeeper_Namespace_Init.lua b/Shopkeeper_Namespace_Init.lua
index a9fe120..7540e56 100644
--- a/Shopkeeper_Namespace_Init.lua
+++ b/Shopkeeper_Namespace_Init.lua
@@ -1,12 +1,12 @@
 -- Shopkeeper Namespace Setup
--- Last Updated August 8, 2014
+-- Last Updated August 10, 2014
 -- Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
 -- Released under terms in license accompanying this file.
 -- Distribution without license is prohibited!

 Shopkeeper = {}
 Shopkeeper.name = "Shopkeeper"
-Shopkeeper.version = "0.9a"
+Shopkeeper.version = "0.9b"
 Shopkeeper.locale = "en"
 Shopkeeper.viewMode = "self"
 Shopkeeper.isScanning = false
@@ -16,13 +16,12 @@ Shopkeeper.MiniDataRows = {}
   -- ScanResults, SelfSales and SearchTable have the following fields:
   -- 1: Buyer  2: Guild  3: Item Name  4: Item Icon
   -- 5: Quantity 6: UNIX Time  7: Price 8: Seller
+  -- 9: Was a kiosk sale (true/false, maybe nil for older data)
 Shopkeeper.ScanResults = {}
 Shopkeeper.SelfSales = {}
 Shopkeeper.SearchTable = {}
-Shopkeeper.missedScan = {}
-Shopkeeper.missedLastScan = {}
+Shopkeeper.numEvents = {}
 Shopkeeper.alertQueue = {}
-Shopkeeper.shopSlider = {}
 Shopkeeper.curSort = {"time", "desc"}
 Shopkeeper.uiFragment = {}

diff --git a/Shopkeeper_UI.lua b/Shopkeeper_UI.lua
new file mode 100644
index 0000000..3afbc07
--- /dev/null
+++ b/Shopkeeper_UI.lua
@@ -0,0 +1,430 @@
+-- Shopkeeper UI Functions File
+-- Last Updated August 10, 2014
+-- Written August 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
+-- Released under terms in license accompanying this file.
+-- Distribution without license is prohibited!
+
+-- Handle the OnMoveStop event for the windows
+function Shopkeeper.OnWindowMoveStop()
+  Shopkeeper.savedVariables.winLeft = ShopkeeperWindow:GetLeft()
+  Shopkeeper.savedVariables.winTop = ShopkeeperWindow:GetTop()
+  Shopkeeper.savedVariables.miniWinLeft = ShopkeeperMiniWindow:GetLeft()
+  Shopkeeper.savedVariables.miniWinTop = ShopkeeperMiniWindow:GetTop()
+end
+
+function Shopkeeper.OnStatsWindowMoveStop()
+  Shopkeeper.savedVariables.statsWinLeft = ShopkeeperStatsWindow:GetLeft()
+  Shopkeeper.savedVariables.statsWinTop = ShopkeeperStatsWindow:GetTop()
+end
+
+-- Restore the window positions from saved vars
+function Shopkeeper:RestoreWindowPosition()
+  local left = self.savedVariables.winLeft
+  local top = self.savedVariables.winTop
+  local statsLeft = self.savedVariables.statsWinLeft
+  local statsTop = self.savedVariables.statsWinTop
+  local miniLeft = self.savedVariables.miniWinLeft
+  local miniTop = self.savedVariables.miniWinTop
+
+  ShopkeeperWindow:ClearAnchors()
+  ShopkeeperStatsWindow:ClearAnchors()
+  ShopkeeperMiniWindow:ClearAnchors()
+  ShopkeeperWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, left, top)
+  ShopkeeperStatsWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, statsLeft, statsTop)
+  ShopkeeperMiniWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, miniLeft, miniTop)
+end
+
+-- Handle the changing of window font settings
+function Shopkeeper:UpdateFonts()
+  local LMP = LibStub("LibMediaProvider-1.0")
+  if LMP then
+    local font = LMP:Fetch('font', Shopkeeper.savedVariables.windowFont)
+    local fontString = font .. "|%d"
+    ShopkeeperWindowSearchLabel:SetFont(string.format(fontString, 16))
+    ShopkeeperWindowSearchBox:SetFont(string.format(fontString, 16))
+    ShopkeeperWindowTitle:SetFont(string.format(fontString, 26))
+    ShopkeeperWindowBuyer:SetFont(string.format(fontString, 20))
+    ShopkeeperWindowGuild:SetFont(string.format(fontString, 20))
+    ShopkeeperWindowItemName:SetFont(string.format(fontString, 20))
+    ShopkeeperWindowSellTime:SetFont(string.format(fontString, 20))
+    ShopkeeperWindowPrice:SetFont(string.format(fontString, 20))
+    ShopkeeperSwitchViewButton:SetFont(string.format(fontString, 16))
+    ShopkeeperPriceSwitchButton:SetFont(string.format(fontString, 16))
+    ShopkeeperResetButton:SetFont(string.format(fontString, 16))
+    ShopkeeperRefreshButton:SetFont(string.format(fontString, 16))
+    ShopkeeperMiniWindowSearchLabel:SetFont(string.format(fontString, 12))
+    ShopkeeperMiniWindowSearchBox:SetFont(string.format(fontString, 12))
+    ShopkeeperMiniWindowTitle:SetFont(string.format(fontString, 20))
+    ShopkeeperMiniWindowGuild:SetFont(string.format(fontString, 16))
+    ShopkeeperMiniWindowItemName:SetFont(string.format(fontString, 16))
+    ShopkeeperMiniWindowSellTime:SetFont(string.format(fontString, 16))
+    ShopkeeperMiniWindowPrice:SetFont(string.format(fontString, 16))
+    ShopkeeperMiniSwitchViewButton:SetFont(string.format(fontString, 12))
+    ShopkeeperMiniPriceSwitchButton:SetFont(string.format(fontString, 12))
+    ShopkeeperMiniResetButton:SetFont(string.format(fontString, 12))
+    ShopkeeperMiniRefreshButton:SetFont(string.format(fontString, 12))
+
+    ShopkeeperStatsWindowTitle:SetFont(string.format(fontString, 26))
+    ShopkeeperStatsWindowItemsSoldLabel:SetFont(string.format(fontString, 18))
+    ShopkeeperStatsWindowTotalGoldLabel:SetFont(string.format(fontString, 18))
+    ShopkeeperStatsWindowBiggestSaleLabel:SetFont(string.format(fontString, 18))
+    ShopkeeperStatsWindowSliderSettingLabel:SetFont(string.format(fontString, 18))
+    ShopkeeperStatsWindowSliderLabel:SetFont(string.format(fontString, 16))
+
+    for i = 1, #Shopkeeper.DataRows do
+      local dataRow = Shopkeeper.DataRows[i]
+      dataRow:GetNamedChild("Buyer"):SetFont(string.format(fontString, 16))
+      dataRow:GetNamedChild("Guild"):SetFont(string.format(fontString, 16))
+      dataRow:GetNamedChild("ItemName"):SetFont(string.format(fontString, 16))
+      dataRow:GetNamedChild("Quantity"):SetFont(string.format(fontString, 16))
+      dataRow:GetNamedChild("SellTime"):SetFont(string.format(fontString, 16))
+      dataRow:GetNamedChild("Price"):SetFont(string.format(fontString, 16))
+      if i <= #Shopkeeper.MiniDataRows then
+        local miniDataRow = Shopkeeper.MiniDataRows[i]
+        miniDataRow:GetNamedChild("Guild"):SetFont(string.format(fontString, 12))
+        miniDataRow:GetNamedChild("ItemName"):SetFont(string.format(fontString, 12))
+        miniDataRow:GetNamedChild("Quantity"):SetFont(string.format(fontString, 12))
+        miniDataRow:GetNamedChild("SellTime"):SetFont(string.format(fontString, 12))
+        miniDataRow:GetNamedChild("Price"):SetFont(string.format(fontString, 12))
+      end
+    end
+  end
+end
+
+-- Item tooltips
+function Shopkeeper:ShowToolTip(itemName, itemButton)
+  InitializeTooltip(ItemTooltip, itemButton)
+  ItemTooltip:SetLink(itemName)
+end
+
+-- Clear a given row's data
+function Shopkeeper.ClearDataRow(index)
+  if index < 1 or index > 15 then
+    return
+  end
+
+  local dataRow = Shopkeeper.DataRows[index]
+  dataRow:GetNamedChild("Buyer"):SetText("")
+  dataRow:GetNamedChild("Buyer"):SetHandler("OnMouseDoubleClick", nil)
+  dataRow:GetNamedChild("Guild"):SetText("")
+  dataRow:GetNamedChild("ItemIcon"):SetTexture(nil)
+  dataRow:GetNamedChild("ItemIcon"):SetHidden(true)
+  local itemCell = dataRow:GetNamedChild("ItemName")
+  itemCell:SetText("")
+  itemCell:SetHandler("OnMouseDoubleClick", nil)
+  itemCell:SetHandler("OnMouseEnter", nil)
+  dataRow:GetNamedChild("Quantity"):SetText("")
+  dataRow:GetNamedChild("SellTime"):SetText("")
+  dataRow:GetNamedChild("Price"):SetText("")
+end
+
+function Shopkeeper.ClearMiniDataRow(index)
+  if index < 1 or index > 8 then
+    return
+  end
+
+  local dataRow = Shopkeeper.MiniDataRows[index]
+  dataRow:GetNamedChild("Guild"):SetText("")
+  dataRow:GetNamedChild("ItemIcon"):SetTexture(nil)
+  dataRow:GetNamedChild("ItemIcon"):SetHidden(true)
+  local itemCell = dataRow:GetNamedChild("ItemName")
+  itemCell:SetText("")
+  itemCell:SetHandler("OnMouseDoubleClick", nil)
+  itemCell:SetHandler("OnMouseEnter", nil)
+  dataRow:GetNamedChild("Quantity"):SetText("")
+  dataRow:GetNamedChild("SellTime"):SetText("")
+  dataRow:GetNamedChild("Price"):SetText("")
+end
+
+-- Fill out a row with the given data
+function Shopkeeper.SetDataRow(index, buyer, guild, itemName, icon, quantity, sellTime, price, seller, kioskSale)
+  if index < 1 or index > 15 then return end
+
+  local dataRow = Shopkeeper.DataRows[index]
+
+  -- Some extra stuff for the Buyer cell to handle double-click and color changes
+  -- Plus add a marker if buyer is not in-guild (kiosk sale)
+  local buyerCell = dataRow:GetNamedChild("Buyer")
+  local buyerString = buyer
+  if kioskSale ~= nil and kioskSale == true then
+    buyerString = "|t16:16:/EsoUI/Art/icons/item_generic_coinbag.dds|t" .. buyerString
+  end
+  buyerCell:SetText(buyerString)
+  -- If the seller is the player, color the buyer green.  Otherwise, blue.
+  local acctName = GetDisplayName()
+  if seller == acctName then
+    buyerCell:SetNormalFontColor(0.18, 0.77, 0.05, 1)
+    buyerCell:SetPressedFontColor(0.18, 0.77, 0.05, 1)
+    buyerCell:SetMouseOverFontColor(0.32, 0.90, 0.18, 1)
+  else
+    buyerCell:SetNormalFontColor(0.21, 0.54, 0.94, 1)
+    buyerCell:SetPressedFontColor(0.21, 0.54, 0.94, 1)
+    buyerCell:SetMouseOverFontColor(0.34, 0.67, 1, 1)
+  end
+  buyerCell:SetHandler("OnMouseDoubleClick", function()
+    if SCENE_MANAGER.currentScene.name == "mailSend" then
+      ZO_MailSendToField:SetText("")
+      ZO_MailSendToField:SetText(ZO_MailSendToField:GetText() .. buyer)
+    else
+      ZO_ChatWindowTextEntryEditBox:SetText("/w " .. buyer .. " " .. ZO_ChatWindowTextEntryEditBox:GetText())
+    end
+  end)
+  local buyerCellLabel = buyerCell:GetLabelControl()
+  buyerCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
+
+  -- Guild cell
+  dataRow:GetNamedChild("Guild"):SetText(guild)
+  local guildCellLabel = dataRow:GetNamedChild("Guild"):GetLabelControl()
+  guildCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
+
+  -- Item Icon
+  dataRow:GetNamedChild("ItemIcon"):SetHidden(false)
+  dataRow:GetNamedChild("ItemIcon"):SetTexture(icon)
+
+  -- Item name cell
+  local itemCell = dataRow:GetNamedChild("ItemName")
+  itemCell:SetText(zo_strformat("<<t:1>>", itemName))
+  -- Insert the item link into the chat box, with a quick substitution so brackets show up
+  itemCell:SetHandler("OnMouseDoubleClick", function()
+    ZO_ChatWindowTextEntryEditBox:SetText(ZO_ChatWindowTextEntryEditBox:GetText() .. string.gsub(itemName, "|H0", "|H1"))
+  end)
+  itemCell:SetHandler("OnMouseEnter", function() Shopkeeper:ShowToolTip(itemName, itemCell) end)
+  itemCell:SetHandler("OnMouseExit", function() ClearTooltip(ItemTooltip) end)
+  local itemCellLabel = itemCell:GetLabelControl()
+  itemCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
+
+  -- Quantity cell
+  dataRow:GetNamedChild("Quantity"):SetText("x" .. quantity)
+
+  -- Sale time cell
+  dataRow:GetNamedChild("SellTime"):SetText(sellTime)
+
+  -- Handle the setting of whether or not to show pre-cut sale prices
+  -- math.floor(number + 0.5) is a quick shorthand way to round for
+  -- positive values.
+  local dispPrice = price
+  if Shopkeeper.savedVariables.showFullPrice then
+    if Shopkeeper.savedVariables.showUnitPrice and quantity > 0 then
+      dispPrice = math.floor((dispPrice / quantity) + 0.5)
+    end
+  else
+    local cutPrice = price * (1 - (GetTradingHouseCutPercentage() / 100))
+    if Shopkeeper.savedVariables.showUnitPrice and quantity > 0 then
+      cutPrice = cutPrice / quantity
+    end
+    dispPrice = math.floor(cutPrice + 0.5)
+  end
+
+  -- Insert thousands separators for the price
+  local stringPrice = Shopkeeper.localizedNumber(dispPrice)
+
+  -- Finally, set the price
+  dataRow:GetNamedChild("Price"):SetText(stringPrice .. " " .. string.format("|t16:16:%s|t","EsoUI/Art/currency/currency_gold.dds"))
+end
+
+-- Fill out a row with the given data
+function Shopkeeper.SetMiniDataRow(index, guild, itemName, icon, quantity, sellTime, price, seller)
+  if index < 1 or index > 8 then return end
+
+  local dataRow = Shopkeeper.MiniDataRows[index]
+
+  -- Guild cell
+  dataRow:GetNamedChild("Guild"):SetText(guild)
+  local guildCellLabel = dataRow:GetNamedChild("Guild"):GetLabelControl()
+  guildCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
+
+  -- Item Icon
+  dataRow:GetNamedChild("ItemIcon"):SetHidden(false)
+  dataRow:GetNamedChild("ItemIcon"):SetTexture(icon)
+
+  -- Item name cell
+  local itemCell = dataRow:GetNamedChild("ItemName")
+  itemCell:SetText(zo_strformat("<<t:1>>", itemName))
+  -- Insert the item link into the chat box, with a quick substitution so brackets show up
+  itemCell:SetHandler("OnMouseDoubleClick", function()
+    ZO_ChatWindowTextEntryEditBox:SetText(ZO_ChatWindowTextEntryEditBox:GetText() .. string.gsub(itemName, "|H0", "|H1"))
+  end)
+  itemCell:SetHandler("OnMouseEnter", function() Shopkeeper:ShowToolTip(itemName, itemCell) end)
+  itemCell:SetHandler("OnMouseExit", function() ClearTooltip(ItemTooltip) end)
+  local itemCellLabel = itemCell:GetLabelControl()
+  itemCellLabel:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
+
+  -- Quantity cell
+  dataRow:GetNamedChild("Quantity"):SetText("x" .. quantity)
+
+  -- Sale time cell
+  dataRow:GetNamedChild("SellTime"):SetText(sellTime)
+
+  -- Handle the setting of whether or not to show pre-cut sale prices
+  -- math.floor(number + 0.5) is a quick shorthand way to round for
+  -- positive values.
+  local dispPrice = price
+  if Shopkeeper.savedVariables.showFullPrice then
+    if Shopkeeper.savedVariables.showUnitPrice and quantity > 0 then
+      dispPrice = math.floor((dispPrice / quantity) + 0.5)
+    end
+  else
+    local cutPrice = price * (1 - (GetTradingHouseCutPercentage() / 100))
+    if Shopkeeper.savedVariables.showUnitPrice and quantity > 0 then
+      cutPrice = cutPrice / quantity
+    end
+    dispPrice = math.floor(cutPrice + 0.5)
+  end
+
+  -- Insert thousands separators for the price
+  local stringPrice = Shopkeeper.localizedNumber(dispPrice)
+
+  -- Finally, set the price
+  dataRow:GetNamedChild("Price"):SetText(stringPrice .. " " .. string.format("|t16:16:%s|t","EsoUI/Art/currency/currency_gold.dds"))
+end
+
+-- Build the data rows based on the position of the slider
+function Shopkeeper.DisplayRows()
+  if Shopkeeper.savedVariables.viewSize == "full" then
+    local startIndex = ShopkeeperWindowSlider:GetValue()
+    if startIndex + #Shopkeeper.DataRows > #Shopkeeper.SearchTable then
+      startIndex = #Shopkeeper.SearchTable - #Shopkeeper.DataRows
+    end
+
+    if startIndex < 1 then startIndex = 0 end
+
+    -- Hide the slider if there's less than a full page of results
+    ShopkeeperWindowSlider:SetHidden(#Shopkeeper.SearchTable < 16)
+
+    for i = 1, #Shopkeeper.DataRows do
+      local rowIndex = i + startIndex
+      if rowIndex > #Shopkeeper.SearchTable then
+        Shopkeeper.ClearDataRow(i)
+        ShopkeeperWindowSlider:SetHidden(true)
+      else
+        local scanResult = Shopkeeper.SearchTable[rowIndex]
+        -- Older data won't have kiosk info
+        local kioskSale = nil
+        if #scanResult > 8 then kioskSale = scanResult[9] end
+        Shopkeeper.SetDataRow(i, scanResult[1], scanResult[2], scanResult[3], scanResult[4], scanResult[5], Shopkeeper.textTimeSince(scanResult[6], false), scanResult[7], scanResult[8], kioskSale)
+      end
+    end
+
+    -- Scale the slider's range to the number of items we have minus the number of rows
+    local sliderMax = 0
+    local tableToUse = Shopkeeper.ScanResults
+    if Shopkeeper.viewMode == "self" then tableToUse = Shopkeeper.SelfSales end
+    if #tableToUse > 15 then sliderMax = (#tableToUse - 15) end
+    ShopkeeperWindowSlider:SetMinMax(0, sliderMax)
+  else
+    local startIndex = ShopkeeperMiniWindowSlider:GetValue()
+    if startIndex + #Shopkeeper.MiniDataRows > #Shopkeeper.SearchTable then
+      startIndex = #Shopkeeper.SearchTable - #Shopkeeper.MiniDataRows
+    end
+
+    if startIndex < 1 then startIndex = 0 end
+
+    -- Hide the slider if there's less than a full page of results
+    ShopkeeperMiniWindowSlider:SetHidden(#Shopkeeper.SearchTable < 9)
+
+    for i = 1, #Shopkeeper.MiniDataRows do
+      local rowIndex = i + startIndex
+      if rowIndex > #Shopkeeper.SearchTable then
+        Shopkeeper.ClearMiniDataRow(i)
+        ShopkeeperMiniWindowSlider:SetHidden(true)
+      else
+        local scanResult = Shopkeeper.SearchTable[rowIndex]
+        Shopkeeper.SetMiniDataRow(i, scanResult[2], scanResult[3], scanResult[4], scanResult[5], Shopkeeper.textTimeSince(scanResult[6], false), scanResult[7], scanResult[8])
+      end
+    end
+
+    -- Scale the slider's range to the number of items we have minus the number of rows
+    local sliderMax = 0
+    local tableToUse = Shopkeeper.ScanResults
+    if Shopkeeper.viewMode == "self" then tableToUse = Shopkeeper.SelfSales end
+    if #tableToUse > 8 then sliderMax = (#tableToUse - 8) end
+    ShopkeeperMiniWindowSlider:SetMinMax(0, sliderMax)
+  end
+end
+
+function Shopkeeper.ToggleViewMode()
+  if Shopkeeper.savedVariables.viewSize == "full" then
+    Shopkeeper.savedVariables.viewSize = "half"
+    ShopkeeperWindow:SetHidden(true)
+    Shopkeeper.DisplayRows()
+    ShopkeeperMiniWindow:SetHidden(false)
+
+    if Shopkeeper.savedVariables.openWithMail then
+      MAIL_INBOX_SCENE:RemoveFragment(Shopkeeper.uiFragment)
+      MAIL_SEND_SCENE:RemoveFragment(Shopkeeper.uiFragment)
+      MAIL_INBOX_SCENE:AddFragment(Shopkeeper.miniUiFragment)
+      MAIL_SEND_SCENE:AddFragment(Shopkeeper.miniUiFragment)
+    end
+
+    if Shopkeeper.savedVariables.openWithStore then
+      TRADING_HOUSE_SCENE:RemoveFragment(Shopkeeper.uiFragment)
+      TRADING_HOUSE_SCENE:AddFragment(Shopkeeper.miniUiFragment)
+    end
+  else
+    Shopkeeper.savedVariables.viewSize = "full"
+    ShopkeeperMiniWindow:SetHidden(true)
+    Shopkeeper.DisplayRows()
+    ShopkeeperWindow:SetHidden(false)
+
+    if Shopkeeper.savedVariables.openWithMail then
+      MAIL_INBOX_SCENE:RemoveFragment(Shopkeeper.miniUiFragment)
+      MAIL_SEND_SCENE:RemoveFragment(Shopkeeper.miniUiFragment)
+      MAIL_INBOX_SCENE:AddFragment(Shopkeeper.uiFragment)
+      MAIL_SEND_SCENE:AddFragment(Shopkeeper.uiFragment)
+    end
+
+    if Shopkeeper.savedVariables.openWithStore then
+      TRADING_HOUSE_SCENE:RemoveFragment(Shopkeeper.miniUiFragment)
+      TRADING_HOUSE_SCENE:AddFragment(Shopkeeper.uiFragment)
+    end
+  end
+end
+
+-- Set the visibility status of the main window to the opposite of its current status
+function Shopkeeper.ToggleShopkeeperWindow()
+  if Shopkeeper.savedVariables.viewSize == "full" then
+    ShopkeeperMiniWindow:SetHidden(true)
+    if ShopkeeperWindow:IsHidden() then
+      Shopkeeper.DisplayRows()
+      SetGameCameraUIMode(true)
+    end
+
+    ShopkeeperWindow:SetHidden(not ShopkeeperWindow:IsHidden())
+  else
+    ShopkeeperWindow:SetHidden(true)
+    if ShopkeeperMiniWindow:IsHidden() then
+      Shopkeeper.DisplayRows()
+      SetGameCameraUIMode(true)
+    end
+
+    ShopkeeperMiniWindow:SetHidden(not ShopkeeperMiniWindow:IsHidden())
+  end
+end
+
+-- Set the visibility status of the stats window to the opposite of its current status
+function Shopkeeper.ToggleShopkeeperStatsWindow()
+  if ShopkeeperStatsWindow:IsHidden() then Shopkeeper.UpdateStatsWindow() end
+  ShopkeeperStatsWindow:SetHidden(not ShopkeeperStatsWindow:IsHidden())
+end
+
+-- Handle scrolling the main window
+function Shopkeeper.OnSliderMouseWheel(self, delta)
+  if Shopkeeper.savedVariables.viewSize == "full" then
+    local oldSliderLevel = ShopkeeperWindowSlider:GetValue()
+    local newSliderLevel = oldSliderLevel - delta
+    ShopkeeperWindowSlider:SetValue(newSliderLevel)
+  else
+    local oldSliderLevel = ShopkeeperMiniWindowSlider:GetValue()
+    local newSliderLevel = oldSliderLevel - delta
+    ShopkeeperMiniWindowSlider:SetValue(newSliderLevel)
+  end
+end
+
+-- Update the table if the slider moved
+function Shopkeeper.OnSliderMoved(self, sliderLevel, eventReason)
+  Shopkeeper.DisplayRows()
+end
+
+function Shopkeeper.OnStatsSliderMoved(self, sliderLevel, eventReason)
+  Shopkeeper.UpdateStatsWindow()
+end
\ No newline at end of file
diff --git a/Shopkeeper_Util.lua b/Shopkeeper_Util.lua
new file mode 100644
index 0000000..cbb2d9f
--- /dev/null
+++ b/Shopkeeper_Util.lua
@@ -0,0 +1,71 @@
+-- Shopkeeper Utility Functions File
+-- Last Updated August 10, 2014
+-- Written August 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
+-- Released under terms in license accompanying this file.
+-- Distribution without license is prohibited!
+
+-- Translate from the i18n table
+function Shopkeeper.translate(stringName)
+  local result = Shopkeeper.i18n.localized[stringName]
+  assert(result, ("The id %q was not found in the current locale"):format(stringName))
+  return result
+end
+
+function Shopkeeper.localizedNumber(numberValue)
+  local stringPrice = numberValue
+  -- Insert thousands separators for the price
+  -- local stringPrice = numberValue
+  local subString = "%1" .. Shopkeeper.translate("thousandsSep") .."%2"
+  while true do
+    stringPrice, k = string.gsub(stringPrice, "^(-?%d+)(%d%d%d)", subString)
+    if (k == 0) then break end
+  end
+
+  return stringPrice
+end
+
+-- Create a textual representation of a time interval
+-- (X and Y) or Z in LUA is the equivalent of C-style
+-- ternary syntax X ? Y : Z so long as Y is not false or nil
+function Shopkeeper.textTimeSince(theTime, useLowercase)
+  local secsSince = GetTimeStamp() - theTime
+  if secsSince < 90 then
+    return ((useLowercase and zo_strformat(Shopkeeper.translate('timeSecondsAgoLC'), secsSince)) or
+             zo_strformat(Shopkeeper.translate('timeSecondsAgo'), secsSince))
+  elseif secsSince < 5400 then
+    return ((useLowercase and zo_strformat(Shopkeeper.translate('timeMinutesAgoLC'), math.floor(secsSince / 60.0))) or
+             zo_strformat(Shopkeeper.translate('timeMinutesAgo'), math.floor(secsSince / 60.0)))
+  elseif secsSince < 86400 then
+    return ((useLowercase and zo_strformat(Shopkeeper.translate('timeHoursAgoLC'), math.floor(secsSince / 3600.0))) or
+             zo_strformat(Shopkeeper.translate('timeHoursAgo'), math.floor(secsSince / 3600.0)))
+  else
+    return ((useLowercase and zo_strformat(Shopkeeper.translate('timeDaysAgoLC'), math.floor(secsSince / 86400.0))) or
+             zo_strformat(Shopkeeper.translate('timeDaysAgo'), math.floor(secsSince / 86400.0)))
+  end
+end
+
+-- A utility function to grab all the keys of the sound table
+-- to populate the options dropdown
+function Shopkeeper.soundKeys()
+  local keyList = {}
+  for i = 1, #Shopkeeper.alertSounds do table.insert(keyList, Shopkeeper.alertSounds[i].name) end
+  return keyList
+end
+
+-- A utility function to find the key associated with a given value in
+-- the sounds table.  Best we can do is a linear search unfortunately,
+-- but it's a small table.
+function Shopkeeper.searchSounds(sound)
+  for i, theSound in ipairs(Shopkeeper.alertSounds) do
+    if theSound.sound == sound then return theSound.name end
+  end
+
+  -- If we hit this point, we didn't find what we were looking for
+  return nil
+end
+
+function Shopkeeper.searchSoundNames(name)
+  for i,theSound in ipairs(Shopkeeper.alertSounds) do
+    if theSound.name == name then return theSound.sound end
+  end
+end
\ No newline at end of file
diff --git a/i18n/DE.lua b/i18n/DE.lua
index aa273d7..563ecd9 100644
--- a/i18n/DE.lua
+++ b/i18n/DE.lua
@@ -71,7 +71,7 @@ Shopkeeper.i18n.localized = {
   statsTitle = "Sales Statistics",
   statsTimeAll = "Using all data",
   statsTimeSome = "Going back %d days",
-  statsItemsSold = "Items sold: %d",
+  statsItemsSold = "Items sold: %d (%d%% from guild trader)",
   statsTotalGold = "Total gold: %s |t16:16:EsoUI/Art/currency/currency_gold.dds|t (%s |t16:16:EsoUI/Art/currency/currency_gold.dds|t per day)",
   statsBiggest = "Gr\195\182\195\159ter Verkaufs: %s (%s |t16:16:EsoUI/Art/currency/currency_gold.dds|t)",
   statsDays = "Tagen: ",
diff --git a/i18n/EN.lua b/i18n/EN.lua
index ac29047..0ff0a68 100644
--- a/i18n/EN.lua
+++ b/i18n/EN.lua
@@ -70,7 +70,7 @@ Shopkeeper.i18n.localized = {
   statsTitle = "Sales Statistics",
   statsTimeAll = "Using all data",
   statsTimeSome = "Going back %d days",
-  statsItemsSold = "Items sold: %d",
+  statsItemsSold = "Items sold: %d (%d%% from guild trader)",
   statsTotalGold = "Total gold: %s |t16:16:EsoUI/Art/currency/currency_gold.dds|t (%s |t16:16:EsoUI/Art/currency/currency_gold.dds|t per day)",
   statsBiggest = "Biggest sale: %s (%s |t16:16:EsoUI/Art/currency/currency_gold.dds|t)",
   statsDays = "Days: ",
diff --git a/i18n/FR.lua b/i18n/FR.lua
index 8ac2301..e1daf86 100644
--- a/i18n/FR.lua
+++ b/i18n/FR.lua
@@ -70,7 +70,7 @@ Shopkeeper.i18n.localized = {
   statsTitle = "Sales Statistics",
   statsTimeAll = "Using all data",
   statsTimeSome = "Going back %d days",
-  statsItemsSold = "Items sold: %d",
+  statsItemsSold = "Items sold: %d (%d%% from guild trader)",
   statsTotalGold = "Total gold: %s |t16:16:EsoUI/Art/currency/currency_gold.dds|t (%s |t16:16:EsoUI/Art/currency/currency_gold.dds|t per day)",
   statsBiggest = "Biggest sale: %s (%s |t16:16:EsoUI/Art/currency/currency_gold.dds|t)",
   statsDays = "Days: ",
diff --git a/readme b/readme
index 6b715aa..e61da27 100644
--- a/readme
+++ b/readme
@@ -3,6 +3,13 @@ Inc. or its affiliates. The Elder Scrolls
 trademarks or trademarks of ZeniMax Media Inc. in the United States and/or
 other countries. All rights reserved.

+Changelog for 0.9b
+  Further rewrite of part of the scanning routines to be more accurate
+  Some small tweaks to the time display routines (will go up to 90 seconds before saying 1 minute, 90 minutes before 1 hour, etc.)
+  Fixes to on-screen alerts to avoid 'missing' multiple identical alerts
+  GUILD TRADER SUPPORT! Buyer names now have a gold bag icon next to them if they are not in the guild (i.e. bought at your guild's trader kiosk)
+  Stats Window now also shows you percentage of sales made at the guild trader
+  Other minor tweaks and optimizations as we push towards a fully-translated, fully-functional 1.0 release!

 Changelog for 0.9a
   Rewrite of part of the scanning routines to be more accurate