import "Turbine";
import "Turbine.Gameplay";
import "Turbine.UI";
import "Turbine.UI.Extensions";
import "Turbine.UI.Lotro";
import "Turbine.Utils";
import "FrostyPlugins.Calendar.CalendarEntry";
-- --------------------------------------------------------------
-- CalendarWindow is a display of the current month. You can see
-- events that are on the calendar and edit them.
--
-- ToDo:
-- allow multiple events per day
-- add icons for events?
-- --------------------------------------------------------------
CalendarWindow = class( Turbine.UI.Lotro.Window);
-- --------------------------------------------------------------
-- Constructor
--
-- Description: this is where the window is laid out, the fields
-- and buttons are created, and events are trapped.
-- --------------------------------------------------------------
function CalendarWindow:Constructor()
Turbine.UI.Lotro.Window.Constructor( self );
-- load the last settings saved
self:LoadSettings();
-- statics
self.monthLabels = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }
self.monthsTable = { 0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5 };
self.monthsLeapTable = { 6, 2, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5 };
self.calendarEntryEditor = FrostyPlugins.Calendar.CalendarEntryEditor();
self.calendarEntryEditor:SetVisible( false );
-- --------------------------------------------
-- window layout
-- --------------------------------------------
-- set window properties
self:SetSize( 250, 280 );
self:SetBackColor( Turbine.UI.Color() );
self:SetPosition( self.settings.positionX, self.settings.positionY );
self:SetText( "Calendar" );
self:SetOpacity( 1 );
self:SetAllowDrop( false );
-- the layout will look something like:
-- - June +
--
-- 1 2 3 4 5 6
-- 7 8 9 10 11 12 13
-- 14 15 16 17 18 19 20
-- 21 22 23 24 25 26 27
-- 28 29 30
--
-- create the month/year - button
self.buttonLeft = Turbine.UI.Button();
self.buttonLeft:SetBackground( "FrostyPlugins/Calendar/Resources/LeftArrow.tga" );
self.buttonLeft:SetBlendMode( Turbine.UI.BlendMode.Overlay );
self.buttonLeft:SetParent( self );
self.buttonLeft:SetPosition( 20, 42 );
self.buttonLeft:SetSize( 20, 20 );
self.buttonLeft:SetVisible( true );
self.buttonLeft.MouseEnter = function( sender, args )
sender:SetBackground( "FrostyPlugins/Calendar/Resources/LeftArrowHighlight.tga" );
end
self.buttonLeft.MouseLeave = function( sender, args )
sender:SetBackground( "FrostyPlugins/Calendar/Resources/LeftArrow.tga" );
end
self.buttonLeft.Click = function( sender, args )
self:AdjustMonth( -1 );
end
-- create the month layout
self.monthLabel = Turbine.UI.Label();
self.monthLabel:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
self.monthLabel:SetParent( self );
self.monthLabel:SetPosition( 45, 40 );
self.monthLabel:SetMultiline( false );
self.monthLabel:SetSelectable( false );
self.monthLabel:SetSize( 160, 25 );
self.monthLabel:SetText( "September" );
self.monthLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
self.monthLabel:SetVisible( true );
-- create the month/year + button
self.buttonRight = Turbine.UI.Button();
self.buttonRight:SetBackground( "FrostyPlugins/Calendar/Resources/RightArrow.tga" );
self.buttonRight:SetBlendMode( Turbine.UI.BlendMode.Overlay );
self.buttonRight:SetParent( self );
self.buttonRight:SetPosition( 205, 42 );
self.buttonRight:SetSize( 20, 20 );
self.buttonRight:SetVisible( true );
self.buttonRight.MouseEnter = function( sender, args )
sender:SetBackground( "FrostyPlugins/Calendar/Resources/RightArrowHighlight.tga" );
end
self.buttonRight.MouseLeave = function( sender, args )
sender:SetBackground( "FrostyPlugins/Calendar/Resources/RightArrow.tga" );
end
self.buttonRight.Click = function( sender, args )
self:AdjustMonth( 1 );
end
-- create the actual calendar items
self.gridElements = {};
local index = 1;
local rowCount = 6;
local columnCount = 7;
for i = 1, rowCount do
for j = 1, columnCount do
local gridElement = Turbine.UI.Button();
local x = -10 + j * 30;
local y = 40 + i * 30;
gridElement:SetBackground( "FrostyPlugins/Calendar/Resources/DateBackground.jpg" );
gridElement:SetBackColorBlendMode( Turbine.UI.BlendMode.Overlay );
gridElement:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
gridElement:SetParent( self );
gridElement:SetPosition( x, y );
gridElement:SetMultiline( false );
gridElement:SetSelectable( false );
gridElement:SetSize( 30, 30 );
gridElement:SetText( "x" );
gridElement:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
gridElement:SetVisible( true );
gridElement.Click = function( sender, args )
self:UpdateCalendarEntry( sender:GetText() );
end
gridElement.MouseEnter = function( sender, args )
sender:SetForeColor( Turbine.UI.Color( 1, 1, 0 ) );
end
gridElement.MouseLeave = function( sender, args )
sender:SetForeColor( Turbine.UI.Color( 1, 1, 1 ) );
end
self.gridElements[ index ] = gridElement;
index = index + 1;
end
end
-- the special "today" overlay
self.todayOverlay = Turbine.UI.Window();
self.todayOverlay:SetBackground( "FrostyPlugins/Calendar/Resources/TodayOverlay.tga" );
self.todayOverlay:SetMouseVisible( false );
self.todayOverlay:SetParent( self );
self.todayOverlay:SetPosition( 0, 0 );
self.todayOverlay:SetSize( 30, 30 );
self.todayOverlay:SetVisible( false );
-- the radio button for storing data on the character or the account
self.dataLocationLabel = Turbine.UI.Label();
self.dataLocationLabel:SetFont( Turbine.UI.Lotro.Font.Verdana14 );
self.dataLocationLabel:SetMouseVisible( true );
self.dataLocationLabel:SetOutlineColor( Turbine.UI.Color( 1, 1, 0 ) );
self.dataLocationLabel:SetParent( self );
self.dataLocationLabel:SetPosition( 20, 250 );
self.dataLocationLabel:SetMultiline( false );
self.dataLocationLabel:SetSize( 210, 20 );
self.dataLocationLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
self.dataLocationLabel:SetVisible( true );
if( true == self.dataLocation.StoredOnCharacter ) then
self.dataLocationLabel:SetText( "Save Data on Character" );
else
self.dataLocationLabel:SetText( "Save Data on Account" );
end
-- fixme: frosty
-- there is a bug with SetFontStyle - it does not "dirty" the control.
-- to get around this, toggle the visibility of the field
self.dataLocationLabel.MouseEnter = function( sender, args )
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
self.dataLocationLabel:SetVisible( false );
self.dataLocationLabel:SetVisible( true );
end
self.dataLocationLabel.MouseLeave = function( sender, args )
sender:SetFontStyle( Turbine.UI.FontStyle.None );
self.dataLocationLabel:SetVisible( false );
self.dataLocationLabel:SetVisible( true );
end
self.dataLocationLabel.MouseClick = function( sender, args )
self.dataLocation.StoredOnCharacter = not self.dataLocation.StoredOnCharacter;
self:SaveSettings();
if( true == self.dataLocation.StoredOnCharacter ) then
self.dataLocationLabel:SetText( "Save Data on Character" );
else
self.dataLocationLabel:SetText( "Save Data on Account" );
end
end
-- this is the label for the dataLocation store
-- make sure we listen for key presses
self:SetWantsKeyEvents( true );
-- --------------------------------------------
-- event handling
-- --------------------------------------------
--
-- if the escape key is pressed, hide the window
--
self.KeyDown = function( sender, args )
-- do this if the escape key is pressed
if ( args.Action == Turbine.UI.Lotro.Action.Escape ) then
sender:SetVisible( false )
end
end
--
-- if the position changes, save the new window location
--
self.PositionChanged = function( sender, args )
local x,y = self:GetPosition();
self.settings.positionX = x;
self.settings.positionY = y;
self:SaveSettings();
end
-- --------------------------------------------
-- initialize the actual calendar
-- --------------------------------------------
self:InitializeCalendar();
self:RebuildCalendar();
end
-- --------------------------------------------------------------
-- InitializeCalendar
--
-- Description: initialize the calendar: get the current day and
-- initialize the starting day and month
-- --------------------------------------------------------------
function CalendarWindow:InitializeCalendar()
-- track "today"
self.today = Turbine.Engine.GetDate();
-- initialize the rest of the date members that we use to
-- display the calendar
self.currentMonth = self.today.Month;
self.currentYear = self.today.Year;
end
-- --------------------------------------------------------------
-- AdjustMonth
--
-- Description: the user has pressed one of the month buttons
-- (inc. or dec.) This function is used to bounds check the number
-- of months and rebuild the calendar
--
-- Params:
-- delta - integer - the amount of time to change the month by.
-- this will be 1 or -1 months
-- --------------------------------------------------------------
function CalendarWindow:AdjustMonth( delta )
-- if we're trying to go before 2000 or after 2099, forget it!
self.currentMonth = self.currentMonth + delta;
if( self.currentMonth < 1 ) then
self.currentYear = self.currentYear - 1;
self.currentMonth = 12;
if( self.currentYear < 2000 ) then
self.currentYear = 2000;
self.currentMonth = 1;
end
end
if( self.currentMonth > 12 ) then
self.currentYear = self.currentYear + 1;
self.currentMonth = 1;
if( self.currentYear > 2099 ) then
self.currentYear = 2099;
self.currentMonth = 12;
end
end
self:RebuildCalendar();
end
-- --------------------------------------------------------------
-- RebuildCalendar
--
-- Description: update the calendar window with any changes. Changes
-- include: increasing or decreasing months (the entire calendar needs
-- to be redrawn), and saving or clearing events.
--
-- fixme: frosty
-- can I pass in a hint so that I don't have to rebuild the
-- entire calendar from scratch every time? The current method
-- is overkill when saving or clearing the calendar events.
-- --------------------------------------------------------------
function CalendarWindow:RebuildCalendar()
-- update the month label: month year
local updateLabel = self.monthLabels[ self.currentMonth ] .. " " .. self.currentYear;
self.monthLabel:SetText( updateLabel );
-- determine the number of days in the month
local bLeapYear = 0;
local numberOfDays = 31;
if( ( self.currentMonth == 4 ) or ( self.currentMonth == 6 ) or ( self.currentMonth == 9 ) or ( self.currentMonth == 11 ) ) then
numberOfDays = 30;
end
if( self.currentMonth == 2 ) then
if( ( 0 == ( self.currentYear % 4 ) ) and ( ( self.currentYear % 500 ) ~= 0 ) ) then
numberOfDays = 29;
bLeapYear = 1;
else
numberOfDays = 28;
end
end
-- determine where to start updating the calendar
-- anything in the 2000s is century "6"
local centuriesTable = 6;
-- take the digits from the current year and % 4 it
local yearsTable = self.currentYear - ( ( self.currentYear % 100 ) * 100 );
local yearsMod4 = yearsTable % 4;
-- lookup the months from the month table
local monthTable = self.monthsTable[ self.currentMonth ];
if( bLeapYear ) then
monthTable = self.monthsLeapTable[ self.currentMonth ];
end
-- add all the numbers, including the day the months starts, which is 1;
local dayTotal = centuriesTable + yearsTable + yearsMod4 + monthTable + 1;
-- dayTotal % 7 will give us the day of the week
-- however, that day of the week is mod 6... we need to add '1' to work in our calendar
local startDay = ( dayTotal % 7 ) + 1;
-- create a key string for today
local todayString = tostring( self.today.Day ) .. self.monthLabels[ self.today.Month ] .. self.today.Year;
self.todayOverlay:SetVisible( false );
-- update the calendar
-- we update 42 items, since the calendar is 6 rows of 7 days
local dayIndex = 1;
for i = 1, 42 do
-- reset any indicators that the date has an event on it
self.gridElements[ i ]:SetBackColor( Turbine.UI.Color( 0, 0, 0, 0 ) );
local newLabel = "";
if( i > startDay ) then
if( i < ( startDay + numberOfDays + 1 ) ) then
local dayIndex = i - startDay;
newLabel = tostring( dayIndex );
-- if this date has an entry on it, change the back color!
local entryKey = tostring( dayIndex ) .. self.monthLabels[ self.currentMonth ] .. self.currentYear;
local calendarEntry = self.settings.CalendarEntryArray[ entryKey ];
if( calendarEntry ) then
self.gridElements[ i ]:SetBackColor( Turbine.UI.Color( .75, .5, .5, 0 ) );
end
-- if this day is today, then we move the "today" indicator and change its visibility
if( entryKey == todayString ) then
local x, y = self.gridElements[ i ]:GetPosition();
self.todayOverlay:SetPosition( x, y );
self.todayOverlay:SetVisible( true );
end
end
end
self.gridElements[ i ]:SetText( newLabel );
-- we only show the last row when the month actually requires it
-- (the 30th and/or 31st fall on a Sunday/Monday)
if( i > 35 ) then
if( startDay + numberOfDays > 35 ) then
self.gridElements[ i ]:SetVisible( true );
else
self.gridElements[ i ]:SetVisible( false );
end
end
end
end
-- --------------------------------------------------------------
-- UpdateCalendarEntry
--
-- Description: the user has clicked on a date in the calendar.
-- retrieve the existing entry (if it exists) and initialize the
-- CalendarEntryEditor with the entry.
--
-- Params:
-- dayString - string - the string-ified day, i.e. 12 becomes "12"
--
-- fixme: frosty
-- currently, the existing system only allows for a single
-- event per day.
-- --------------------------------------------------------------
function CalendarWindow:UpdateCalendarEntry( dayString )
-- create a string used to access the list of CalendarEntrys
local entryKey = dayString .. self.monthLabels[ self.currentMonth ] .. self.currentYear;
-- look this entry up in the table
local calendarEntry = self.settings.CalendarEntryArray[ entryKey ];
if( not calendarEntry ) then
calendarEntry = FrostyPlugins.Calendar.CalendarEntry();
calendarEntry.DateEntry = entryKey;
calendarEntry.TimeEntry = "12:00";
calendarEntry.Description = "Enter text here...";
end
-- display the window that edits the CalendarEntry
-- the editor will take care of calling us back to save the entry
self.calendarEntryEditor:InitializeEntry( calendarEntry, self );
end
-- --------------------------------------------------------------
-- NotifySaveEntry
--
-- Description: the user has modified an entry on the calendar and
-- elected to save it to disk. look up the entry to see if it is something
-- we know about. if there is no current entry, create one. update the
-- entry with the information on the editor and save the entry to disk.
-- finally, rebuild the calendar.
--
-- Params:
-- entryEditor - CalendarEntryEditor - the editor window that contains
-- the CalendarEntry that is being saved
-- --------------------------------------------------------------
function CalendarWindow:NotifySaveEntry( entryEditor )
-- look this input entry's date and time up in the table
local entryKey = entryEditor.calendarEntry.DateEntry;
local calendarEntry = self.settings.CalendarEntryArray[ entryKey ];
if( not calendarEntry ) then
-- if not there, create one
calendarEntry = FrostyPlugins.Calendar.CalendarEntry();
end
-- update the entry with the input data
calendarEntry.DateEntry = entryEditor.calendarEntry.DateEntry;
calendarEntry.TimeEntry = entryEditor.hoursField:GetText() .. ":" .. entryEditor.minutesField:GetText();
calendarEntry.Description = entryEditor.descriptionField:GetText();
-- and save it back to the calendar
self.settings.CalendarEntryArray[ entryKey ] = calendarEntry;
self:SaveSettings();
self:RebuildCalendar();
end
-- --------------------------------------------------------------
-- NotifyClearEntry
--
-- Description: the user has cleared an entry from the calendar.
-- look up the entry to make sure it is something we know about.
-- if so, delete it from the list and rebuild the calendar.
--
-- Params:
-- entryEditor - CalendarEntryEditor - the editor window that contains
-- the CalendarEntry that is being cleared
-- --------------------------------------------------------------
function CalendarWindow:NotifyClearEntry( entryEditor )
local entryKey = entryEditor.calendarEntry.DateEntry;
local calendarEntry = self.settings.CalendarEntryArray[ entryKey ];
if( calendarEntry ) then
self.settings.CalendarEntryArray[ entryKey ] = nil;
self:SaveSettings();
self:RebuildCalendar();
end
end
-- --------------------------------------------------------------
-- LoadSettings
--
-- Description: loads our internal settings from disk. For this class,
-- this includes the position of the calendar window and the list of
-- events that have been created. if the position does not exist, we
-- attempt to place it in the center of the window. if the entry list
-- does not exist, we create it.
-- --------------------------------------------------------------
function CalendarWindow:LoadSettings()
-- the first thing to check: where are we storing the settings for this
-- user? at the account level or the character level? to do this, we
-- read a special object that contains this information.
self.dataLocation = Turbine.PluginData.Load( Turbine.DataScope.Character, "CalendarDataSettings" );
if( type( self.dataLocation ) ~= "table" ) then
self.dataLocation = { };
end
if( nil == self.dataLocation.StoredOnCharacter ) then
self.dataLocation.StoredOnCharacter = true;
end
-- load the settings from either the character or the account scopt.
if( true == self.dataLocation.StoredOnCharacter ) then
self.settings = Turbine.PluginData.Load( Turbine.DataScope.Character, "CalendarWindowSettings" );
else
self.settings = Turbine.PluginData.Load( Turbine.DataScope.Account, "CalendarWindowSettings" );
end
-- If any of the values are not available, set a default value
if ( type( self.settings ) ~= "table" ) then
self.settings = { };
end
if ( nil == self.settings.positionX ) then
self.settings.positionX = (Turbine.UI.Display.GetWidth() / 2) - (self:GetWidth() / 2 );
end
if ( nil == self.settings.positionY ) then
self.settings.positionY = (Turbine.UI.Display.GetHeight() / 2) - self:GetHeight();
end
if( nil == self.settings.CalendarEntryArray ) then
self.settings.CalendarEntryArray = { };
end
end
-- --------------------------------------------------------------
-- SaveSettings
--
-- Description: save our internal settings to disk. For this class,
-- this includes the position of the calendar window and the list of
-- events that have been created.
-- --------------------------------------------------------------
function CalendarWindow:SaveSettings()
-- always save the dataLocation to the character scope!
Turbine.PluginData.Save( Turbine.DataScope.Character, "CalendarDataSettings", self.dataLocation );
-- save the settings depending on the dataLocation
if( true == self.dataLocation.StoredOnCharacter ) then
Turbine.PluginData.Save( Turbine.DataScope.Character, "CalendarWindowSettings", self.settings );
else
Turbine.PluginData.Save( Turbine.DataScope.Account, "CalendarWindowSettings", self.settings );
end
end