Push for version 0.3 - fixed a few UI bugs, introduced a total/per-unit price toggle.

Khaibit [07-16-14 - 14:45]
Push for version 0.3 - fixed a few UI bugs, introduced a total/per-unit price toggle.
Filename
Shopkeeper.lua
Shopkeeper.txt
Shopkeeper.xml
Shopkeeper_Namespace_Init.lua
i18n/DE.lua
i18n/EN.lua
i18n/FR.lua
readme
diff --git a/Shopkeeper.lua b/Shopkeeper.lua
index b2c1734..856457e 100644
--- a/Shopkeeper.lua
+++ b/Shopkeeper.lua
@@ -1,5 +1,5 @@
 -- Shopkeeper Main Addon File
--- Last Updated July 8, 2014
+-- Last Updated July 15, 2014
 -- Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
 -- Released under terms in license accompanying this file.
 -- Distribution without license is prohibited!
@@ -49,8 +49,7 @@ function Shopkeeper.textTimeSince(theTime, useLowercase)
 end

 -- A utility function to grab all the keys of the sound table
--- to populate the options dropdown; we sort it and stick None
--- on the top for convenience
+-- to populate the options dropdown
 function Shopkeeper.soundKeys()
   local keyList = {}
   local keyIndex = 0
@@ -112,6 +111,7 @@ function Shopkeeper:UpdateFonts()
     ShopkeeperWindowSellTime:SetFont(headerLook)
     ShopkeeperWindowPrice:SetFont(headerLook)
     ShopkeeperSwitchViewButton:SetFont(look)
+    ShopkeeperPriceSwitchButton:SetFont(look)
     ShopkeeperResetButton:SetFont(look)
     ShopkeeperRefreshButton:SetFont(look)

@@ -127,11 +127,6 @@ function Shopkeeper:UpdateFonts()
   end
 end

--- Set the visibility status of the main window to the opposite of its current status
-function Shopkeeper.ToggleShopkeeperWindow()
-  ShopkeeperWindow:SetHidden(not ShopkeeperWindow:IsHidden())
-end
-
 -- Item tooltips
 function Shopkeeper:ShowToolTip(itemName, itemButton)
   self.itemToolTip = PopupTooltip
@@ -154,10 +149,15 @@ function Shopkeeper.ClearDataRow(index)

   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)
-  dataRow:GetNamedChild("ItemName"):SetText("")
+  local itemCell = dataRow:GetNamedChild("ItemName")
+  itemCell:SetText("")
+  itemCell:SetHandler("OnMouseDoubleClick", nil)
+  itemCell:SetHandler("OnMouseEnter", nil)
+  itemCell:SetHandler("OnMouseExit", nil)
   dataRow:GetNamedChild("Quantity"):SetText("")
   dataRow:GetNamedChild("SellTime"):SetText("")
   dataRow:GetNamedChild("Price"):SetText("")
@@ -223,10 +223,17 @@ function Shopkeeper.SetDataRow(index, buyer, guild, itemName, icon, quantity, se

   -- 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
+  -- positive values.
   local dispPrice = price
-  if not Shopkeeper.savedVariables.showFullPrice then
+  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

@@ -242,27 +249,97 @@ function Shopkeeper.SetDataRow(index, buyer, guild, itemName, icon, quantity, se
   dataRow:GetNamedChild("Price"):SetText(stringPrice .. " G")
 end

--- Sort the scan results by descending price.  If already sorted by price, toggle the sort order.
+-- Build the data rows based on the position of the slider
+function Shopkeeper.DisplayRows()
+  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
+  if #Shopkeeper.SearchTable > 15 then sliderMax = (#Shopkeeper.SearchTable - 15) end
+  Shopkeeper.shopSlider:SetMinMax(0, sliderMax)
+end
+
+-- Set the visibility status of the main window to the opposite of its current status
+function Shopkeeper.ToggleShopkeeperWindow()
+  if ShopkeeperWindow:IsHidden() then
+    Shopkeeper.DisplayRows()
+    SetGameCameraUIMode(true)
+  end
+
+  ShopkeeperWindow:SetHidden(not ShopkeeperWindow: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!
 function Shopkeeper.SortByPrice(ordering)
   Shopkeeper.curSort[1] = "price"
   Shopkeeper.curSort[2] = ordering
   if ordering == "asc" then
-    table.sort(Shopkeeper.SearchTable, function(sortA, sortB)
-      return sortA[7] > sortB[7]
-    end)
-    table.sort(Shopkeeper.ScanResults, function(sortA, sortB)
-      return sortA[7] > sortB[7]
-    end)
+    -- If they're viewing prices per-unit, then we need to sort on price / quantity.
+    if Shopkeeper.savedVariables.showUnitPrice then
+      table.sort(Shopkeeper.SearchTable, function(sortA, sortB)
+        -- In case quantity ends up 0 or nil somehow, let's not divide by it
+        if sortA[5] and sortA[5] > 0 and sortB[5] and sortB[5] > 0 then
+          return (sortA[7] / sortA[5]) > (sortB[7] / sortB[5])
+        else
+          return sortA[7] > sortB[7]
+        end
+      end)
+      table.sort(Shopkeeper.ScanResults, function(sortA, sortB)
+        if sortA[5] and sortA[5] > 0 and sortB[5] and sortB[5] > 0 then
+          return (sortA[7] / sortA[5]) > (sortB[7] / sortB[5])
+        else
+          return sortA[7] > sortB[7]
+        end
+      end)
+    -- Otherwise just sort on pure price.
+    else
+      table.sort(Shopkeeper.SearchTable, function(sortA, sortB)
+        return sortA[7] > sortB[7]
+      end)
+      table.sort(Shopkeeper.ScanResults, function(sortA, sortB)
+        return sortA[7] > sortB[7]
+      end)
+    end
     ShopkeeperWindowSortPrice:SetTexture("/esoui/art/miscellaneous/list_sortheader_icon_sortup.dds")
   else
-    table.sort(Shopkeeper.SearchTable, function(sortA, sortB)
-      return sortA[7] < sortB[7]
-    end)
-    table.sort(Shopkeeper.ScanResults, function(sortA, sortB)
-      return sortA[7] < sortB[7]
-    end)
+    -- And the same thing with descending sort
+    if Shopkeeper.savedVariables.showUnitPrice then
+      table.sort(Shopkeeper.SearchTable, function(sortA, sortB)
+        return (sortA[7] / sortA[5]) < (sortB[7] / sortB[5])
+      end)
+      table.sort(Shopkeeper.ScanResults, function(sortA, sortB)
+        return (sortA[7] / sortA[5]) < (sortB[7] / sortB[5])
+      end)
+    else
+      table.sort(Shopkeeper.SearchTable, function(sortA, sortB)
+        return sortA[7] < sortB[7]
+      end)
+      table.sort(Shopkeeper.ScanResults, function(sortA, sortB)
+        return sortA[7] < sortB[7]
+      end)
+    end
     ShopkeeperWindowSortPrice:SetTexture("/esoui/art/miscellaneous/list_sortheader_icon_sortdown.dds")
   end

@@ -271,7 +348,7 @@ function Shopkeeper.SortByPrice(ordering)
   Shopkeeper.DisplayRows()
 end

--- Sort the scan results by descending time (most recent first).  If already sorted by time, toggle the sort order.
+-- 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!
 function Shopkeeper.SortByTime(ordering)
@@ -320,36 +397,6 @@ function Shopkeeper.TimeSort()
   end
 end

--- Build the data rows based on the position of the slider
-function Shopkeeper.DisplayRows()
-  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
-  if #Shopkeeper.SearchTable > 15 then sliderMax = (#Shopkeeper.SearchTable - 15) end
-  Shopkeeper.shopSlider:SetMinMax(0, sliderMax)
-
-end

 -- LibAddon init code
 function Shopkeeper:LibAddonInit()
@@ -564,13 +611,29 @@ function Shopkeeper.SwitchViewMode()
   Shopkeeper.DoSearch(ShopkeeperWindowSearchBox:GetText())
 end

+function Shopkeeper.SwitchPriceMode()
+  if Shopkeeper.savedVariables.showUnitPrice then
+    Shopkeeper.savedVariables.showUnitPrice = false
+    ShopkeeperPriceSwitchButton:SetText(Shopkeeper.translate('showUnitPrice'))
+    ShopkeeperWindow:GetNamedChild('Price'):SetText(Shopkeeper.translate('priceColumnName'))
+  else
+    Shopkeeper.savedVariables.showUnitPrice = true
+    ShopkeeperPriceSwitchButton:SetText(Shopkeeper.translate('showTotalPrice'))
+    ShopkeeperWindow:GetNamedChild('Price'):SetText(Shopkeeper.translate('priceEachColumnName'))
+  end
+
+  if Shopkeeper.curSort[1] == "price" then
+    Shopkeeper.SortByPrice(Shopkeeper.curSort[2])
+  else
+    Shopkeeper.DisplayRows()
+  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 timeStamp into the ScanResults table.
 function Shopkeeper:DoScan(guildID, timeStamp, checkOlder)
-  if checkOlder and DoesGuildHistoryCategoryHaveMoreEvents(guildID, GUILD_HISTORY_SALES) then
-    RequestGuildHistoryCategoryOlder(guildID, GUILD_HISTORY_SALES)
-  end
   local numEvents = GetNumGuildEvents(guildID, GUILD_HISTORY_SALES)
   local thePlayer = Shopkeeper.GetAccountName()
   for i = 0, numEvents do
@@ -604,7 +667,12 @@ function Shopkeeper:DoScan(guildID, timeStamp, checkOlder)
   end

   if guildID < GetNumGuilds() then
-    RequestGuildHistoryCategoryNewest((guildID + 1), GUILD_HISTORY_SALES)
+    if checkOlder and DoesGuildHistoryCategoryHaveMoreEvents((guildID + 1), GUILD_HISTORY_SALES) then
+      RequestGuildHistoryCategoryNewest((guildID + 1), GUILD_HISTORY_SALES)
+      RequestGuildHistoryCategoryOlder((guildID + 1), GUILD_HISTORY_SALES)
+    else
+      RequestGuildHistoryCategoryNewest((guildID + 1), GUILD_HISTORY_SALES)
+    end
   end
 end

@@ -750,7 +818,13 @@ function Shopkeeper:ScanStores(checkOlder, doAlert)
     if guildNum == 0 then return end

     Shopkeeper.isScanning = true
-    RequestGuildHistoryCategoryNewest(1, GUILD_HISTORY_SALES)
+    if checkOlder and DoesGuildHistoryCategoryHaveMoreEvents(1, GUILD_HISTORY_SALES) then
+      RequestGuildHistoryCategoryNewest(1, GUILD_HISTORY_SALES)
+      RequestGuildHistoryCategoryOlder(1, GUILD_HISTORY_SALES)
+    else
+      RequestGuildHistoryCategoryNewest(1, GUILD_HISTORY_SALES)
+    end
+
     for j = 1, guildNum do
       local guildID = GetGuildId(j)

@@ -778,7 +852,7 @@ function Shopkeeper.DoRefresh()
   -- 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 = timeStamp - 60
+  local timeLimit = timeStamp - 59
   if timeLimit > Shopkeeper.acctSavedVariables.lastScan then
     CHAT_SYSTEM:AddMessage("[Shopkeeper] " .. Shopkeeper.translate('refreshStart'))
     Shopkeeper:ScanStores(false, true)
@@ -799,8 +873,6 @@ function Shopkeeper.DoReset()
   Shopkeeper.DisplayRows()
   Shopkeeper:ScanStores(true, false)
   CHAT_SYSTEM:AddMessage("[Shopkeeper] " .. Shopkeeper.translate('resetDone'))
-  d(Shopkeeper.GetAccountName())
-  d("ShopkeeperAccountVars" .. Shopkeeper.GetAccountName())
 end

 -- Set up the main window and the additional button added to the guild store interface
@@ -824,7 +896,11 @@ function Shopkeeper:SetupShopkeeperWindow()
   ShopkeeperWindow:GetNamedChild('Guild'):SetText(Shopkeeper.translate('guildColumnName'))
   ShopkeeperWindow:GetNamedChild('ItemName'):SetText(Shopkeeper.translate('itemColumnName'))
   ShopkeeperWindow:GetNamedChild('SellTime'):SetText(Shopkeeper.translate('timeColumnName'))
-  ShopkeeperWindow:GetNamedChild('Price'):SetText(Shopkeeper.translate('priceColumnName'))
+  if Shopkeeper.savedVariables.showUnitPrice then
+    ShopkeeperWindow:GetNamedChild('Price'):SetText(Shopkeeper.translate('priceEachColumnName'))
+  else
+    ShopkeeperWindow:GetNamedChild('Price'):SetText(Shopkeeper.translate('priceColumnName'))
+  end
   ShopkeeperWindow:GetNamedChild('SearchLabel'):SetText(Shopkeeper.translate('searchBoxName'))

   -- Set second half of window title from translation
@@ -861,6 +937,17 @@ function Shopkeeper:SetupShopkeeperWindow()
   switchViews:SetWidth(175)
   switchViews:SetText(Shopkeeper.translate('viewModeAllName'))
   switchViews:SetHandler("OnClicked", Shopkeeper.SwitchViewMode)
+
+  -- 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'))
+  else
+    unitPrice:SetText(Shopkeeper.translate('showUnitPrice'))
+  end
+  unitPrice:SetHandler("OnClicked", Shopkeeper.SwitchPriceMode)

   -- Refresh button
   local refreshButton = CreateControlFromVirtual("ShopkeeperRefreshButton", ShopkeeperWindow, "ZO_DefaultButton")
@@ -935,6 +1022,7 @@ function Shopkeeper:Initialize()
     ["scanFreq"] = 120,
     ["showAnnounceAlerts"] = true,
     ["alertSoundName"] = "Book_Acquired",
+    ["showUnitPrice"] = false,
   }

   local acctDefaults = {
@@ -997,7 +1085,14 @@ function Shopkeeper:Initialize()

   -- We also want to make sure the Shopkeeper window is hidden in the game menu
   ZO_PreHookHandler(ZO_GameMenu_InGame, "OnShow", function() ShopkeeperWindow:SetHidden(true) end)
-
+
+  -- I could do this with action layer pop/push, but it's kind've a pain
+  -- when it's just these I want to hook
+  EVENT_MANAGER:RegisterForEvent(Shopkeeper.name, EVENT_CLOSE_BANK, function() ShopkeeperWindow:SetHidden(true) end)
+  EVENT_MANAGER:RegisterForEvent(Shopkeeper.name, EVENT_CLOSE_GUILD_BANK, function() ShopkeeperWindow:SetHidden(true) end)
+  EVENT_MANAGER:RegisterForEvent(Shopkeeper.name, EVENT_CLOSE_STORE, function() ShopkeeperWindow:SetHidden(true) end)
+  EVENT_MANAGER:RegisterForEvent(Shopkeeper.name, EVENT_END_CRAFTING_STATION_INTERACT, function() ShopkeeperWindow:SetHidden(true) end)
+
   -- Update fonts after each UI load
   EVENT_MANAGER:RegisterForEvent(Shopkeeper.name, EVENT_PLAYER_ACTIVATED, Shopkeeper.PlayerActive)

@@ -1025,6 +1120,10 @@ EVENT_MANAGER:RegisterForEvent(Shopkeeper.name, EVENT_ADD_ON_LOADED, Shopkeeper.

 -- Set up /shopkeeper as a slash command toggle for the main window
 SLASH_COMMANDS["/shopkeeper"] = function()
-  if ShopkeeperWindow:IsHidden() then Shopkeeper.DisplayRows() end
+  if ShopkeeperWindow:IsHidden() then
+    Shopkeeper.DisplayRows()
+    SetGameCameraUIMode(true)
+  end
+
   ShopkeeperWindow:SetHidden(not ShopkeeperWindow:IsHidden())
 end
\ No newline at end of file
diff --git a/Shopkeeper.txt b/Shopkeeper.txt
index 753c283..f31e467 100644
--- a/Shopkeeper.txt
+++ b/Shopkeeper.txt
@@ -2,11 +2,11 @@
 ## APIVersion: 100007
 ## 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.2
+## Version: 0.3
 ## License: See license - distribution without license is prohibited!
-## LastUpdated: July 7, 2014
+## LastUpdated: July 8, 2014
 ## SavedVariables: ShopkeeperSavedVars
-## OptionalDependsOn: LibAddonMenu-2.0, LibMediaProvider-1.0, LibStub
+## OptionalDependsOn: LibAddonMenu-2.0 LibMediaProvider-1.0 LibStub

 Libs\LibStub\LibStub.lua
 Libs\LibMediaProvider-1.0\LibMediaProvider-1.0.lua
diff --git a/Shopkeeper.xml b/Shopkeeper.xml
index ac5a858..4cf0da5 100644
--- a/Shopkeeper.xml
+++ b/Shopkeeper.xml
@@ -1,6 +1,6 @@
 <!--
       Shopkeeper UI Layout File
-      Last Updated July 7, 2014
+      Last Updated July 14, 2014
       Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
       Released under terms in license accompanying this file.
       Distribution without license is prohibited!
@@ -61,12 +61,12 @@
         </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="741" offsetY="54" />
+          <Anchor point="LEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="731" 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="788" offsetY="44" />
-          <Dimensions x="75" y="25" />
+          <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="775" offsetY="44" />
+          <Dimensions x="85" y="25" />
           <FontColors normalColor="D5B526" mouseOverColor="D5B526" pressedColor="D5B526" />
         </Button>
         <Texture name="$(parent)SortPrice" textureFile="/esoui/art/miscellaneous/list_sortheader_icon_neutral.dds" alpha="1">
@@ -108,7 +108,7 @@
           <Anchor point="TOPLEFT" offsetX="625" 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="765" offsetY="7" />
+          <Anchor point="TOPLEFT" offsetX="751" offsetY="7" />
         </Label>
       </Controls>
     </Control>
diff --git a/Shopkeeper_Namespace_Init.lua b/Shopkeeper_Namespace_Init.lua
index 57480fc..ecea85b 100644
--- a/Shopkeeper_Namespace_Init.lua
+++ b/Shopkeeper_Namespace_Init.lua
@@ -1,12 +1,12 @@
 -- Shopkeeper Namespace Setup
--- Last Updated July 7, 2014
+-- Last Updated July 15, 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.2"
+Shopkeeper.version = "0.3"
 Shopkeeper.locale = "en"
 Shopkeeper.viewMode = "self"
 Shopkeeper.isScanning = false
diff --git a/i18n/DE.lua b/i18n/DE.lua
index 0a95423..82417d1 100644
--- a/i18n/DE.lua
+++ b/i18n/DE.lua
@@ -1,5 +1,5 @@
 -- Shopkeeper German Localization File
--- Last Updated July 7, 2014
+-- Last Updated July 14, 2014
 -- Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
 -- Released under terms in license.txt accompanying this file.
 -- Distribution without license is prohibited!
@@ -30,7 +30,8 @@ Shopkeeper.i18n.localized = {
   guildColumnName = "Gilde",
   itemColumnName = "Verkaufte Gegenst\195\164nde",
   timeColumnName = "Zeitpunkt",
-  priceColumnName = "Preis",
+  priceColumnName = "Preis",
+  priceEachColumnName = "St\195\188ckpreis",
   searchBoxName = "Durchsuchen:",
   itemTooltip = "Doppelklick auf einen Gegenstand f\195\188gt einen Link auf diesen im Chat ein.",
   buyerTooltip = "Doppelkick auf einen K\195\164ufer, um diesen anzufl\195\188stern bzw. wenn der Senden-Reiter in Nachrichten ge\195\182ffnet ist, f\195\188gt es den Namen in die Adresszeile ein.",
@@ -63,6 +64,8 @@ Shopkeeper.i18n.localized = {
   alertTypeName = "Ton der Meldungen",
   alertTypeTip = "Der Ton der abgespielt wird, wenn ein Geganstand verkauft wurde.",
   thousandsSep = ".",
+  showUnitPrice = "Zeigt St\195\188ckpreis",
+  showTotalPrice = "Zeigt Gesamtpreis",
 }

 ZO_CreateStringId("SI_BINDING_NAME_SHOPKEEPER_TOGGLE", "Ein-/Ausblenden des Fensters")
\ No newline at end of file
diff --git a/i18n/EN.lua b/i18n/EN.lua
index 88e544d..27a2253 100644
--- a/i18n/EN.lua
+++ b/i18n/EN.lua
@@ -1,5 +1,5 @@
 -- Shopkeeper English Localization File
--- Last Updated July 7, 2014
+-- Last Updated July 14, 2014
 -- Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
 -- Released under terms in license accompanying this file.
 -- Distribution without license is prohibited!
@@ -30,6 +30,7 @@ Shopkeeper.i18n.localized = {
   itemColumnName = "Item Sold",
   timeColumnName = "Sale Time",
   priceColumnName = "Price",
+  priceEachColumnName = "Price(ea.)",
   searchBoxName = "Search: ",
   itemTooltip = "Double-click on an item to link it in chat.",
   buyerTooltip = "Double-click on a buyer to contact them.",
@@ -62,6 +63,8 @@ Shopkeeper.i18n.localized = {
   saleAlertChatName = "Chat Alerts",
   saleAlertChatTip = "Show sales alerts in your chat box.",
   thousandsSep = ",",
+  showUnitPrice = "Show Unit Price",
+  showTotalPrice = "Show Total Price",
 }

 ZO_CreateStringId("SI_BINDING_NAME_SHOPKEEPER_TOGGLE", "Show/Hide Shopkeeper Window")
\ No newline at end of file
diff --git a/i18n/FR.lua b/i18n/FR.lua
index 1cfa912..d4f5fda 100644
--- a/i18n/FR.lua
+++ b/i18n/FR.lua
@@ -1,5 +1,5 @@
 -- Shopkeeper French Localization File
--- Last Updated July 7, 2014
+-- Last Updated July 14, 2014
 -- Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com
 -- Released under terms in license accompanying this file.
 -- Distribution without license is prohibited!
@@ -30,6 +30,7 @@ Shopkeeper.i18n.localized = {
   itemColumnName = "Item Sold",
   timeColumnName = "Sale Time",
   priceColumnName = "Price",
+  priceEachColumnName = "Price(ea.)",
   searchBoxName = "Search: ",
   itemTooltip = "Double-click on an item to link it in chat.",
   buyerTooltip = "Double-click on a buyer to contact them.",
@@ -61,7 +62,9 @@ Shopkeeper.i18n.localized = {
   alertTypeTip = "The sound to play when you sell an item, if any.",
   saleAlertChatName = "Chat Alerts",
   saleAlertChatTip = "Show sales alerts in your chat box.",
-  thousandsSep = ",",
+  thousandsSep = ".",
+  showUnitPrice = "Show Unit Price",
+  showTotalPrice = "Show Total Price",
 }

 ZO_CreateStringId("SI_BINDING_NAME_SHOPKEEPER_TOGGLE", "Show/Hide Shopkeeper Window")
\ No newline at end of file
diff --git a/readme b/readme
index ebd3ec6..7cddb51 100644
--- a/readme
+++ b/readme
@@ -4,20 +4,31 @@ trademarks or trademarks of ZeniMax Media Inc. in the United States and/or
 other countries. All rights reserved.


-Known Issues / Todo July 07,2014:
+Known Issues July 16, 2014:

- - Due to patch 1.2.3 changing the format for item links, it is not possible to
-   directly parse the name of an item from the link anymore.  As such, until I
-   or someone else can figure out a way to do so, searching item names will
-   not be possible.  See http://www.esoui.com/forums/showthread.php?t=1892 for
-   a discussion on the matter or if you'd like to help out!
+    Due to patch 1.2.3 changing the format for item links, it is not possible to directly parse the name
+ of an item from the link anymore. As such, until I or someone else can figure out a way to do so, searching
+ item names will not be possible. UPDATE: On PTS 1.3.0, a new API function has been added that does EXACTLY
+ what I need - once Update Three goes live, I will be able to have item searching fully operational! I will be
+ releasing version 0.3 sometime during the week of July 14, but expect another update the day 1.3.0 hits the live
+ servers as I've written all the code needed to implement item searching and just need to un-comment it to enable
+ it once the patch goes live.

- - I need translators!  If you speak French, and would like to help
-   with translation, send me a message either on esoui.com here or in-game
-   (@khaibit on the NA server).  Or, if you'd like to dive right in, take a
-   look at i18n\FR.lua in the addon's directory and start
-   translating each of those strings!
+    The API calls to retrieve store sales histories currently return the most recent 100 sales from each guild. The
+ function to retrieve older items behaves...inconsistently. If you are part of a busy guild and/or log on infrequently,
+ Shopkeeper may miss some of your sales. If this is the case, you'll notice your sales also don't show up in the actual
+ guild sales history window either - I can't report on what the servers won't tell me =\ I'm aware of it and trying to
+ find a workaround!
+
+Changelog for 0.3:
+  Added ability to toggle between gross/total sales price and per-unit price displays
+  Better support for multiple accounts that use the same computer
+  Further improvements to store scanning
+  UI improvements - Shopkeeper closes along with most other UI scenes now (bank, crafting station, etc.)

+Changelog for 0.2a:
+  German localization updated/fixed (Credit to Urbs of the EU Server for his hard work on this!)
+
 Changelog for 0.2:
   German localization is complete!
   Fixed missing localizations on Reset/Refresh buttons.