import "Turbine";
import "Turbine.Gameplay";
import "Turbine.UI";
import "Turbine.UI.Extensions";
import "Turbine.UI.Lotro";
--import "Turbine.Utils";
import "FrostyPlugins.Calendar.Locale";
import "FrostyPlugins.Calendar.Options";
import "FrostyPlugins.Calendar.CalendarEntry";
import "FrostyPlugins.Calendar.CalTools";
import "FrostyPlugins.Calendar.DateTime";
import "FrostyPlugins.Calendar.DateDropDownBox";
--calTools = require "CalTools"; -- "require" not available in LOTRO
calTools = FrostyPlugins.Calendar.CalTools;
-- Get reference to user configurable options (singleton)
--options = FrostyPlugins.Calendar.OptionsInstance();
--table.dump("CalenderWindow Options",Options);
--------------------------------------------------------------------------
-- CalendarWindow is a display of the current month.
-- You can see entries that are on the calendar and edit them.
--
-- ToDo:
-- add icons for events?
CalendarWindow = class( Turbine.UI.Lotro.Window );
local CalendarView = {
"Month",
"Week",
"Day",
"List",
}
-- Some constants
local HOURS_PER_DAY = 24;
local DAYS_PER_WEEK = 7;
local GRID_MARGIN = 2;
local HOUR_LABEL_WIDTH = 30;
local WEEK_DAY_HOUR_BUTTON_HEIGHT = 30;
-- Font size conversion
-- Font Width Chars Width/Char
-- Verdana20 156 19 8.21
-- Verdana22 156 18 8.67
-- Arial12 156
-- ? 156 22 7.10
local FontWidthPerChar = {
[Turbine.UI.Lotro.Font.Verdana20] = (156/19),
[Turbine.UI.Lotro.Font.Verdana22] = (156/18),
--[Turbine.UI.Lotro.Font.Arial12] = (156/?),
};
--------------------------------------------------------------------------
-- Constructor
--
-- Description: this is where the window is laid out, the fields
-- and buttons are created, and events are trapped.
function CalendarWindow:Constructor()
--Turbine.Shell.WriteLine( "CalendarWindow:Constructor()" );
Turbine.UI.Lotro.Window.Constructor( self );
-- --------------------------------------------
-- initialize the actual calendar
-- --------------------------------------------
self:InitializeCalendar();
-- load the last settings saved
self:LoadSettings();
-- statics
self.calendarEntryEditor = FrostyPlugins.Calendar.CalendarEntryEditor();
self.calendarEntryEditor:SetVisible( false );
-- --------------------------------------------
-- window layout
-- --------------------------------------------
self:SetSize( self.settings.width, self.settings.height ); -- width,height
self:SetPosition( self.settings.positionX, self.settings.positionY );
self:SetBackColor( Turbine.UI.Color() );
self:SetText( "Calendar" );
self:SetOpacity( 1 );
self:SetAllowDrop( false );
self:SetResizable(true);
-- Declare the interior area of the window where all controls are placed
local windowMargin = 20;
local topMargin = 35;
local interior = Turbine.UI.Control();
interior:SetParent( self );
interior:SetPosition( -- left,top
windowMargin, topMargin);
interior:SetSize( -- width,height
self:GetWidth() - windowMargin*2,
self:GetHeight() - interior:GetTop() - 50 );
interior:SetBlendMode( Turbine.UI.BlendMode.Overlay );
--interior:SetBackColor(Turbine.UI.Color.Red);
interior:SetVisible(true); -- for debug window layout
-- on resizing window, resize interior space
self.SizeChanged = function( sender, args )
--Turbine.Shell.WriteLine( "CalendarWindow.SizeChanged()" );
interior:SetSize( -- width,height
self:GetWidth() - windowMargin*2,
self:GetHeight() - interior:GetTop() - 50 );
end
-- the layout will look something like:
-- = view < today > September 2022
-- = view < today > 02/20/2022 - 02/26/2022
-- = view < today > 02/20/2022
-- - 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 settings button
self.buttonOptions = Turbine.UI.Button();
self.buttonOptions:SetBackground( "FrostyPlugins/Calendar/Resources/options.tga" );
self.buttonOptions:SetBlendMode( Turbine.UI.BlendMode.Overlay );
self.buttonOptions:SetParent( interior );
self.buttonOptions:SetSize( 30,30 ); -- width,height
self.buttonOptions:SetPosition( 0, 0 ); -- left,top
self.buttonOptions:SetVisible( true );
self.buttonOptions.MouseEnter = function( sender, args )
sender:SetBackground( "FrostyPlugins/Calendar/Resources/options_hover.tga" );
end
self.buttonOptions.MouseLeave = function( sender, args )
sender:SetBackground( "FrostyPlugins/Calendar/Resources/options.tga" );
end
self.buttonOptions.Click = function( sender, args )
-- not implemented yet
--Turbine.Shell.WriteLine( "Options window not yet implemented" );
Turbine.PluginManager.ShowOptions(Plugins["Calendar"]);
end
self.buttonView = FrostyPlugins.Calendar.DropDownBox{
itemList=CalendarView,
button=Turbine.UI.Lotro.Button()
};
self.buttonView:SetParent( interior );
--self.buttonView:SetSize( -- width,height
-- 45,
-- self.buttonOptions:GetHeight() );
self.buttonView:SetPosition( -- left,top
self.buttonOptions:GetLeft() + self.buttonOptions:GetWidth() + 5,
self.buttonOptions:GetTop() );
self.buttonView:SetText( "View" );
self.buttonView:SetVisible( true );
self.buttonView:SetSelectedValue(self.settings.view);
self.buttonView.SelectionChanged = function( sender, args )
local idx = self.buttonView:GetSelectedIndex();
local viewName = self.buttonView:GetSelectedValue();
--Turbine.Shell.WriteLine( string.format(
-- "buttonView.SelectionChanged: [%d] = [%s]",
-- idx, viewName) );
self.settings.view = viewName;
--Turbine.Shell.WriteLine( "You selected view [" .. self.settings.view .. "]" );
self:SaveSettings();
self:RebuildCalendar();
end
-- create the today button
--self.buttonToday = Turbine.UI.Lotro.GoldButton();
self.buttonToday = Turbine.UI.Lotro.Button();
self.buttonToday:SetBlendMode( Turbine.UI.BlendMode.Overlay );
self.buttonToday:SetParent( interior );
--self.buttonToday:SetSize( -- width,height
-- 60,
-- self.buttonOptions:GetHeight() );
self.buttonToday:SetPosition( -- left,top
interior:GetWidth() - self.buttonToday:GetWidth(),
self.buttonOptions:GetTop() );
self.buttonToday:SetText( "Today" );
self.buttonToday:SetVisible( true );
self.buttonToday.Click = function( sender, args )
-- Set display to now
self.current = DateTime:now();
self:RebuildCalendar();
end
-- 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( interior );
self.buttonLeft:SetSize( 20, 20 ); -- width,height
-- 20,
-- self.buttonOptions:GetHeight());
self.buttonLeft:SetPosition(
self.buttonView:GetLeft() + self.buttonView:GetWidth() + 5,
0 ); -- left,top
self.buttonLeft:SetVisible( true );
self.buttonLeft.MouseEnter = function( sender, args )
--sender:SetBackColor(Turbine.UI.Color.Gray); -- for debug window layout
sender:SetBackground( "FrostyPlugins/Calendar/Resources/LeftArrowHighlight.tga" );
end
self.buttonLeft.MouseLeave = function( sender, args )
sender:SetBackground( "FrostyPlugins/Calendar/Resources/LeftArrow.tga" );
end
self.buttonLeft.EnabledChanged = function( sender, args )
sender:SetVisible( sender:IsEnabled() );
end
self.buttonLeft.Click = function( sender, args )
self:AdjustView( -1 );
end
-- 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( interior );
self.buttonRight:SetSize( self.buttonLeft:GetSize() ); -- width,height
self.buttonRight:SetPosition( -- left,top
self.buttonToday:GetLeft() - self.buttonRight:GetWidth() - 5,
self.buttonLeft:GetTop() );
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.EnabledChanged = function( sender, args )
sender:SetVisible( sender:IsEnabled() );
end
self.buttonRight.Click = function( sender, args )
self:AdjustView( 1 );
end
-- create the month label/button
self.buttonViewTitle = Turbine.UI.Button();
self.buttonViewTitle:SetParent( interior );
self.buttonViewTitle:SetSize( -- width,height
self.buttonRight:GetLeft() - self.buttonLeft:GetLeft() - self.buttonLeft:GetWidth() - 5,
self.buttonOptions:GetHeight() );
self.buttonViewTitle:SetPosition( -- left,top
self.buttonLeft:GetLeft() + self.buttonLeft:GetWidth() + 5,
self.buttonOptions:GetTop() );
self.buttonViewTitle:SetMultiline( false );
self.buttonViewTitle:SetSelectable( false );
self.buttonViewTitle:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
self.buttonViewTitle:SetText( "September" ); -- initialize to month with longest name to test control layout
self.buttonViewTitle:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
self.buttonViewTitle:SetVisible( true );
self.buttonViewTitle:SetBackColor(Turbine.UI.Color.Red); -- for debug window layout
-- Create a container to hold the container views
self.calenderView = Turbine.UI.Control();
self.calenderView:SetParent( interior );
self.calenderView:SetSize( -- width,height
interior:GetWidth(),
interior:GetHeight() - self.buttonOptions:GetTop() - self.buttonOptions:GetHeight() - 5 );
self.calenderView:SetPosition( -- left,top
0,
self.buttonOptions:GetTop() + self.buttonOptions:GetHeight() + 5);
self.calenderView:SetVisible(true); -- for debug window layout
-- Overlay multiple view controls on top of same view control
-- - one for each kind of view
self.calenderViews = {}; -- list of controls for each view
self.calenderViews["Month"] = self:CreateViewMonth();
self.calenderViews["Week"] = self:CreateViewWeek();
self.calenderViews["Day"] = self:CreateViewDay();
self.calenderViews["List"] = self:CreateViewList();
self.calenderView.SizeChanged = function( sender, args )
--Turbine.Shell.WriteLine( "self.calenderView.SizeChanged()" );
for name,view in pairs(self.calenderViews) do
--Turbine.Shell.WriteLine( string.format("view[%s].SetSize()",name) );
view:SetSize( self.calenderView:GetSize() ); -- width,height
end
end
-- The set of buttons for each event
-- - will be re-used and arranged by each view
self.entryListButtons = {};
self.nextEntryButton = 1; -- The next button to retreive from the above list
-- the radio button for storing data on the character or the account
local dataLocationLabel = Turbine.UI.Label();
dataLocationLabel:SetParent( self );
dataLocationLabel:SetSize( -- width,height
interior:GetWidth(),
20 );
dataLocationLabel:SetPosition( -- left,top
interior:GetLeft(),
interior:GetTop() + interior:GetHeight() + GRID_MARGIN );
--dataLocationLabel:SetBackColor( Turbine.UI.Color.Purple );
dataLocationLabel:SetFont( Turbine.UI.Lotro.Font.Verdana14 );
dataLocationLabel:SetMultiline( false );
dataLocationLabel:SetMouseVisible( true );
dataLocationLabel:SetOutlineColor( Turbine.UI.Color.Yellow );
dataLocationLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
dataLocationLabel:SetVisible( true );
--if( true == self.dataLocation.StoredOnCharacter ) then
-- self.dataLocationLabel:SetText( "Save Data on Character" );
--else
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
dataLocationLabel.MouseEnter = function( sender, args )
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
dataLocationLabel.MouseLeave = function( sender, args )
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
dataLocationLabel.MouseClick = function( sender, args )
-- toggle data save location from per account vs. per character
--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 escape key is pressed, hide the window
--
--self.MouseDown = function( sender, args )
-- Turbine.Shell.WriteLine("MouseDown");
-- self.isMouseUp = false;
--end
self.MouseUp = function( sender, args )
--Turbine.Shell.WriteLine("MouseUp");
--self.isMouseUp = true;
-- save window location after mouse released
local x,y = self:GetPosition();
self.settings.positionX = x;
self.settings.positionY = y;
self.settings.width = self:GetWidth();
self.settings.height = self:GetHeight();
self:SaveSettings();
end
--
-- if the position changes, save the new window location
--
self.PositionChanged = function( sender, args )
-- Only save settings (window location) after the mouse is released
-- Unfortunately, this event fires BEFORE the MouseUp event
--if( self.isMouseUp ) then
-- local x,y = self:GetPosition();
-- self.settings.positionX = x;
-- self.settings.positionY = y;
-- self:SaveSettings();
--end
end
-- handle reize evnets
interior.SizeChanged = function( sender, args )
--Turbine.Shell.WriteLine( string.format("view[%s].SizeChanged()",view.Name) );
self.buttonToday:SetPosition( -- left,top
interior:GetWidth() - self.buttonToday:GetWidth(),
self.buttonOptions:GetTop() );
self.buttonRight:SetPosition( -- left,top
self.buttonToday:GetLeft() - self.buttonRight:GetWidth() - 5,
self.buttonLeft:GetTop() );
self.buttonViewTitle:SetSize( -- width,height
self.buttonRight:GetLeft() - self.buttonLeft:GetLeft() - self.buttonLeft:GetWidth() - 5,
self.buttonOptions:GetHeight() );
self.calenderView:SetSize( -- width,height
interior:GetWidth(),
interior:GetHeight() - self.buttonOptions:GetTop() - self.buttonOptions:GetHeight() - 5 );
dataLocationLabel:SetSize( -- width,height
interior:GetWidth(),
20 );
dataLocationLabel:SetPosition( -- left,top
interior:GetLeft(),
interior:GetTop() + interior:GetHeight() + GRID_MARGIN );
end
SubscribeToChangeNotification(self);
self:RebuildCalendar();
--Turbine.Shell.WriteLine( "CalendarWindow:Constructor() END" );
end
--------------------------------------------------------------------------
--- CreateViewMonth
--
-- Description: Creates the month view control
-- returns: the control for the month view
function CalendarWindow:CreateViewMonth()
--Turbine.Shell.WriteLine( "CalendarWindow:CreateViewMonth()" );
local view = Turbine.UI.Control();
view:SetParent( self.calenderView );
view:SetSize(self.calenderView:GetSize());
view:SetPosition(0,0);
view:SetVisible(true);
view:SetBackColor(Turbine.UI.Color.DimGray);
view.Name = 'Month';
-- Add dropdown button to select month (Only visible from month view)
local MonthLabels = { "January","February","March","April","May","June","July","August","September","October","November","December"};
local buttonSelectMonth = FrostyPlugins.Calendar.DropDownBox{itemList=MonthLabels};
view.buttonSelectMonth = buttonSelectMonth;
buttonSelectMonth:SetParent( self.buttonViewTitle );
buttonSelectMonth:SetSize( -- width,height
150,
self.buttonViewTitle:GetHeight() );
buttonSelectMonth:SetPosition( -- left,top
-- self.buttonOptions:GetLeft() + self.buttonOptions:GetWidth() + 5,
-- self.buttonOptions:GetTop() );
50, 5);
--buttonSelectMonth:SetBackColor(Turbine.UI.Color.Purple); -- for debug window layout
buttonSelectMonth:SetBackColor( self.buttonViewTitle:GetBackColor());
buttonSelectMonth:SetForeColor(Turbine.UI.Color.White);
buttonSelectMonth:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleRight );
buttonSelectMonth:SetOutlineColor( Turbine.UI.Color.Yellow );
buttonSelectMonth:SetFontStyle( Turbine.UI.FontStyle.None );
buttonSelectMonth:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
buttonSelectMonth:SetVisible( false );
buttonSelectMonth:SetSelectedIndex(self.current.Month);
buttonSelectMonth:SetText( buttonSelectMonth:GetSelectedValue() );
buttonSelectMonth.SelectionChanged = function( sender, args )
local idx = sender:GetSelectedIndex();
local value = sender:GetSelectedValue();
--Turbine.Shell.WriteLine( string.format(
-- "buttonSelectMonth.SelectionChanged: [%d] = [%s]",
-- idx, value) );
buttonSelectMonth:SetText( buttonSelectMonth:GetSelectedValue() );
-- Something else (e.g. AdjustView()) has already changed the month
-- So only adjust month if needed, to avoid an endless notification loop
if( self.current.Month ~= idx ) then
self.current:set({Month=idx});
self:RebuildCalendar();
end
end
buttonSelectMonth.MouseEnter = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.Yellow );
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
buttonSelectMonth.MouseLeave = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.White );
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
local now = DateTime:now();
local minYear = now.Year - (Options.yearLimit-1);
local maxYear = now.Year + (Options.yearLimit-1);
local yearValues = table.fill(minYear,maxYear);
local buttonSelectYear = FrostyPlugins.Calendar.DropDownBox{itemList=yearValues};
view.buttonSelectYear = buttonSelectYear;
buttonSelectYear:SetParent( self.buttonViewTitle );
buttonSelectYear:SetSize( -- width,height
100,
self.buttonViewTitle:GetHeight() );
buttonSelectYear:SetPosition( -- left,top
buttonSelectMonth:GetLeft() + buttonSelectMonth:GetWidth() + 5,
buttonSelectMonth:GetTop() );
--buttonSelectYear:SetBackColor(Turbine.UI.Color.Purple); -- for debug window layout
buttonSelectYear:SetBackColor( self.buttonViewTitle:GetBackColor());
buttonSelectYear:SetForeColor(Turbine.UI.Color.White);
buttonSelectYear:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleLeft );
buttonSelectYear:SetOutlineColor( Turbine.UI.Color.Yellow );
buttonSelectYear:SetFontStyle( Turbine.UI.FontStyle.None );
buttonSelectYear:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
buttonSelectYear:SetVisible( false );
buttonSelectYear:SetSelectedValue(self.current.Year);
buttonSelectYear:SetText( buttonSelectYear:GetSelectedValue() );
buttonSelectYear.SelectionChanged = function( sender, args )
local idx = sender:GetSelectedIndex();
local value = sender:GetSelectedValue();
--Turbine.Shell.WriteLine( string.format(
-- "buttonSelectYear.SelectionChanged: [%d] = [%s]",
-- idx, value) );
buttonSelectYear:SetText( buttonSelectYear:GetSelectedValue() );
-- Something else (e.g. AdjustView()) has already changed the month
-- So only adjust month if needed, to avoid an endless notification loop
if( self.current.Year ~= value ) then
self.current:set({Year=value});
self:RebuildCalendar();
end
end
buttonSelectYear.MouseEnter = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.Yellow );
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
buttonSelectYear.MouseLeave = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.White );
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
-- Compute size of each grid item based on size of container they are in
local index = 1;
local nbrRows = 6; -- weeks
local nbrColumns = DAYS_PER_WEEK; -- days
local gridItemWidth = view:GetWidth() / nbrColumns;
local gridItemHeight = (view:GetHeight() - GRID_MARGIN*2) / nbrRows;
--Turbine.Shell.WriteLine( string.format(
-- "viewHeight: [%d] gridItemHeight: [%d]",
-- view:GetHeight(),
-- gridItemHeight));
-- fill the month grid with placeholders for each day
view.gridElementsMonth = {};
for row = 0, nbrRows - 1 do
local y = row * gridItemHeight;
for col = 0, nbrColumns - 1 do
local gridElement = Turbine.UI.Button();
local x = col * gridItemWidth;
gridElement:SetParent( view );
-- Note: Leave some small interior margins
gridElement:SetPosition( x+GRID_MARGIN, y+GRID_MARGIN ); -- left,top
gridElement:SetSize( gridItemWidth-GRID_MARGIN*2, gridItemHeight-GRID_MARGIN*2 ); -- width,height
gridElement:SetBackColor(Turbine.UI.Color.Black);
--gridElement:SetBackColorBlendMode( Turbine.UI.BlendMode.Overlay );
gridElement:SetOutlineColor( Turbine.UI.Color.Yellow );
gridElement:SetMultiline( false );
gridElement:SetSelectable( false );
gridElement:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
gridElement:SetText( tostring(row) .. "." .. tostring(col) );
gridElement:SetTextAlignment( Turbine.UI.ContentAlignment.TopCenter );
gridElement:SetVisible( true );
gridElement.MouseEnter = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.Yellow );
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
gridElement.MouseLeave = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.White );
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
gridElement.Click = function( sender, args )
local day = sender:GetText();
--Turbine.Shell.WriteLine("click day " .. day ..
-- " with entryKey = [" .. sender.entryKey:tostring() .. "]" );
local startTime = sender.entryKey:clone();
local endTime = startTime:clone():set({Hour=23,Minute=59});
self:AddCalendarEntry(
startTime,
endTime,
true ); -- isAllDay
end
-- Assign grid elements an index
view.gridElementsMonth[ index ] = gridElement;
index = index + 1;
end
end
-- handle resize events
view.SizeChanged = function( sender, args )
--Turbine.Shell.WriteLine( string.format("view[%s].SizeChanged()",view.Name) );
local gridItemWidth = view:GetWidth() / nbrColumns;
local gridItemHeight = (view:GetHeight() - GRID_MARGIN*2) / nbrRows;
for row = 0, nbrRows - 1 do
local y = row * gridItemHeight;
for col = 0, nbrColumns - 1 do
local x = col * gridItemWidth;
local gridElement = view.gridElementsMonth[1 + row * nbrColumns + col];
gridElement:SetPosition( x+GRID_MARGIN, y+GRID_MARGIN ); -- left,top
gridElement:SetSize( gridItemWidth-GRID_MARGIN*2, gridItemHeight-GRID_MARGIN*2 ); -- width,height
end
end
self:RebuildCalendar();
end
return view;
end
--------------------------------------------------------------------------
--- CreateViewWeek
--
-- Description: Creates the week view control
-- returns: the control for the week view
function CalendarWindow:CreateViewWeek()
--Turbine.Shell.WriteLine( "CalendarWindow:CreateViewWeek()" );
local view = Turbine.UI.Control();
view:SetParent( self.calenderView );
view:SetSize(self.calenderView:GetSize());
view:SetPosition(0,0);
view:SetVisible(false);
view:SetBackColor(Turbine.UI.Color.DarkGreen);
view.Name = 'Week';
-- blank placeholder at top left of week display
local topLeftLabel = Turbine.UI.Label();
topLeftLabel:SetParent( view );
topLeftLabel:SetSize( -- width,height
HOUR_LABEL_WIDTH,
20 );
topLeftLabel:SetPosition( -- left,top
GRID_MARGIN,
GRID_MARGIN );
topLeftLabel:SetBackColor(Turbine.UI.Color.Black);
local allDayEventLabel = Turbine.UI.Label();
allDayEventLabel:SetParent( view );
allDayEventLabel:SetSize( -- width,height
topLeftLabel:GetWidth(),
100 );
allDayEventLabel:SetPosition( -- left,top
topLeftLabel:GetLeft(),
topLeftLabel:GetTop() + topLeftLabel:GetHeight() + GRID_MARGIN );
allDayEventLabel:SetBackColor(Turbine.UI.Color.Black);
allDayEventLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
allDayEventLabel:SetText( "All\nDay" );
-- Add horizontal scrollbar at bottom to scroll left/right through days of week
local hScroll = Turbine.UI.Lotro.ScrollBar();
hScroll:SetOrientation(Turbine.UI.Orientation.Horizontal);
hScroll:SetParent(view);
hScroll:SetSize( -- width,height
view:GetWidth() - topLeftLabel:GetWidth() - GRID_MARGIN*2 - 12,
12);
hScroll:SetPosition( -- left,top
topLeftLabel:GetLeft() + topLeftLabel:GetWidth() + GRID_MARGIN,
view:GetHeight() - hScroll:GetHeight() - GRID_MARGIN*2 );
hScroll:SetVisible(true);
view.hScroll = hScroll;
-- Add vertical scrollbar at right to scroll through hours of day
local vScroll = Turbine.UI.Lotro.ScrollBar();
vScroll:SetOrientation(Turbine.UI.Orientation.Vertical);
vScroll:SetParent(view);
vScroll:SetSize( -- width,height
12,
hScroll:GetTop() - allDayEventLabel:GetTop() - allDayEventLabel:GetHeight() - GRID_MARGIN*2 );
vScroll:SetPosition( -- left,top
view:GetWidth() - vScroll:GetWidth(),
allDayEventLabel:GetTop() + allDayEventLabel:GetHeight() + GRID_MARGIN);
vScroll:SetVisible(true);
view.vScroll = vScroll;
-- The height of each grid item
local gridItemHeight = 30;
local dayColumnWidth = 150;
---- Add day labels at top of each day column
local dayLabelViewPort = Turbine.UI.Control();
dayLabelViewPort:SetParent( view );
dayLabelViewPort:SetPosition( -- left,top
hScroll:GetLeft(),
topLeftLabel:GetTop() );
dayLabelViewPort:SetSize( -- width,height
hScroll:GetWidth(),
topLeftLabel:GetHeight() );
--dayLabelViewPort:SetBackColor(Turbine.UI.Color.Red);
dayLabelViewPort.entries = Turbine.UI.Control();
dayLabelViewPort.entries:SetParent( dayLabelViewPort );
dayLabelViewPort.entries:SetPosition( 0, 0 ); -- left,top
dayLabelViewPort.entries:SetSize( -- width,height
DAYS_PER_WEEK * (GRID_MARGIN + dayColumnWidth),
topLeftLabel:GetHeight() );
--dayLabelViewPort.entries:SetBackColor(Turbine.UI.Color.Red);
-- Add all-day-event buttons
local allDayEventsViewPort = Turbine.UI.Control();
allDayEventsViewPort:SetParent( view );
allDayEventsViewPort:SetPosition( -- left,top
hScroll:GetLeft(),
allDayEventLabel:GetTop() );
allDayEventsViewPort:SetSize( -- width,height
hScroll:GetWidth(),
allDayEventLabel:GetHeight() );
--allDayEventsViewPort:SetBackColor(Turbine.UI.Color.Red);
allDayEventsViewPort.entries = Turbine.UI.Control();
allDayEventsViewPort.entries:SetParent( allDayEventsViewPort );
allDayEventsViewPort.entries:SetPosition( 0, 0 ); -- left,top
allDayEventsViewPort.entries:SetSize( -- width,height
dayLabelViewPort.entries:GetWidth(),
allDayEventsViewPort:GetHeight() );
--allDayEventsViewPort.entries:SetBackColor(Turbine.UI.Color.Red);
view.allDayEventsViewPort = allDayEventsViewPort;
view.dayLabels = {};
view.allDayEvents = {};
for weekday = 1, DAYS_PER_WEEK do
local colLeft = (weekday-1) * (dayColumnWidth + GRID_MARGIN);
-- add day of week column header
local dayLabel = Turbine.UI.Label();
dayLabel:SetParent( dayLabelViewPort.entries );
dayLabel:SetPosition( colLeft, 0 ); -- left,top
dayLabel:SetSize( -- width,height
dayColumnWidth,
dayLabelViewPort:GetHeight() );
dayLabel:SetBackColor(Turbine.UI.Color.Black);
dayLabel:SetMultiline( false );
dayLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
dayLabel:SetText( CalTools:dayLabel(weekday) );
dayLabel:SetVisible( true );
view.dayLabels[weekday] = dayLabel;
-- add all-day button for day of week
local allDayEvent = Turbine.UI.Button();
allDayEvent:SetParent( allDayEventsViewPort.entries );
allDayEvent:SetPosition( colLeft, 0 ); -- left,top
allDayEvent:SetSize( -- width,height
dayColumnWidth,
allDayEventsViewPort:GetHeight() );
allDayEvent:SetBackColor(Turbine.UI.Color.Black);
--allDayEvent:SetBackColor(Turbine.UI.Color.Purple);
allDayEvent:SetOutlineColor( Turbine.UI.Color.Yellow );
allDayEvent:SetMultiline( false );
allDayEvent:SetTextAlignment( Turbine.UI.ContentAlignment.TopCenter );
allDayEvent:SetText("<click to add>" );
allDayEvent:SetVisible( true );
allDayEvent.weekday = weekday; -- add custom property to identify on click
view.allDayEvents[weekday] = allDayEvent;
allDayEvent.MouseEnter = function( sender, args )
sender:SetBackColor( Turbine.UI.Color(.1, .1, .1) );
sender:SetForeColor( Turbine.UI.Color.Yellow );
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
allDayEvent.MouseLeave = function( sender, args )
sender:SetBackColor( Turbine.UI.Color.Black );
sender:SetForeColor( Turbine.UI.Color.White );
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
allDayEvent.Click = function( sender, args )
local startTime = self.current:clone():set({Hour=0,Minute=0}):add({Day=sender.weekday-self.current:dayOfWeek()});
local endTime = startTime:clone():set({Hour=23,Minute=59});
--Turbine.Shell.WriteLine( string.format(
-- "clicked day[%s] startTime=[%s] endTime=[%s]",
-- tostring(sender.weekday),
-- startTime:tostring(),
-- endTime:tostring()));
self:AddCalendarEntry(
startTime,
endTime,
true ); -- isAllDay
end
end
-- Add column of hour labels
local weekViewHourLabelsViewPort = Turbine.UI.Control();
weekViewHourLabelsViewPort:SetParent( view );
weekViewHourLabelsViewPort:SetSize( -- width,height
allDayEventLabel:GetWidth(),
vScroll:GetHeight() );
weekViewHourLabelsViewPort:SetPosition( -- left,top
allDayEventLabel:GetLeft(),
vScroll:GetTop() );
--weekViewHourLabelsViewPort:SetBackColor(Turbine.UI.Color.Gray);
weekViewHourLabelsViewPort.entries = Turbine.UI.Control();
weekViewHourLabelsViewPort.entries:SetParent( weekViewHourLabelsViewPort );
weekViewHourLabelsViewPort.entries:SetPosition( 0, 0 ); -- left,top
weekViewHourLabelsViewPort.entries:SetSize( -- width,height
dayLabelViewPort.entries:GetWidth(),
HOURS_PER_DAY * (GRID_MARGIN + WEEK_DAY_HOUR_BUTTON_HEIGHT) );
for hourOfDay = 0, HOURS_PER_DAY - 1 do
local rowTop = hourOfDay * (gridItemHeight + GRID_MARGIN);
local hourLabel = Turbine.UI.Label();
hourLabel:SetParent( weekViewHourLabelsViewPort.entries );
hourLabel:SetPosition( 0, rowTop); -- left,top
hourLabel:SetSize( -- width,height
weekViewHourLabelsViewPort:GetWidth(),
WEEK_DAY_HOUR_BUTTON_HEIGHT );
hourLabel:SetBackColor(Turbine.UI.Color.Black);
hourLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleRight );
hourLabel:SetText( CalTools:hourLabel(hourOfDay) );
end
-- Create the hourly events using a view port instead of a ListBox
-- this allows placing the entry buttons on top anywhere we need to
-- ** MY SINCERE AND ENDURING THANKS TO GARAN FOR THE VIEWPORT IDEA **
local partialDayEventsViewPort = Turbine.UI.Control();
partialDayEventsViewPort:SetParent( view );
partialDayEventsViewPort:SetPosition( -- left,top
hScroll:GetLeft(),
vScroll:GetTop() );
partialDayEventsViewPort:SetSize( -- width,height
hScroll:GetWidth(),
vScroll:GetHeight() );
--partialDayEventsViewPort:SetBackColor(Turbine.UI.Color.Gray);
--partialDayEventsViewPort:SetBackColor(Turbine.UI.Color.Purple); -- for debug
-- Create control within the viewport to hold the actual items
partialDayEventsViewPort.entries = Turbine.UI.Control();
partialDayEventsViewPort.entries:SetParent( partialDayEventsViewPort );
partialDayEventsViewPort.entries:SetPosition( 0, 0 ); -- left,top
partialDayEventsViewPort.entries:SetSize( -- width,height
dayLabelViewPort.entries:GetWidth(),
weekViewHourLabelsViewPort.entries:GetWidth());
view.partialDayEventsViewPort = partialDayEventsViewPort;
view.partialDayEntries = {};
-- fill grid of buttons for every hour of every day of week
for weekday = 1, DAYS_PER_WEEK do
local colLeft = (weekday-1) * (dayColumnWidth + GRID_MARGIN);
view.partialDayEntries[weekday] = {};
for hourOfDay = 0, HOURS_PER_DAY - 1 do
local rowTop = hourOfDay * (gridItemHeight + GRID_MARGIN);
local hourButton = Turbine.UI.Button();
hourButton:SetParent( partialDayEventsViewPort.entries );
hourButton:SetSize( -- width,height
dayColumnWidth,
WEEK_DAY_HOUR_BUTTON_HEIGHT );
hourButton:SetPosition( colLeft, rowTop); -- left,top
hourButton:SetBackColor(Turbine.UI.Color.Black);
hourButton:SetOutlineColor( Turbine.UI.Color.Yellow );
--hourButton:SetText( string.format("-- %d.%d --",weekday, hourOfDay) );
hourButton:SetText( "--" );
hourButton.weekday = weekday; -- add custom property to identify on click
hourButton.hourOfDay = hourOfDay; -- add custom property to identify on click
view.partialDayEntries[weekday][hourOfDay] = hourButton;
hourButton.MouseEnter = function( sender, args )
sender:SetBackColor( Turbine.UI.Color(.1, .1, .1) );
sender:SetForeColor( Turbine.UI.Color.Yellow );
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
hourButton.MouseLeave = function( sender, args )
sender:SetBackColor( Turbine.UI.Color.Black );
sender:SetForeColor( Turbine.UI.Color.White );
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
hourButton.Click = function( sender, args )
--Turbine.Shell.WriteLine( string.format(
-- "clicked weekday[%s] hour[%s] firstDayOfWeek[%s]",
-- tostring(sender.weekday),
-- tostring(sender.hourOfDay),
-- tostring(view.startOfWeek:tostring())));
local startTime = view.startOfWeek:clone():add({Day=sender.weekday-1}):set({Hour=hourOfDay});
local endTime = startTime:clone():set({Minute=59});
--Turbine.Shell.WriteLine( string.format(
-- " start[%s] end[%s]",
-- startTime:tostring(),
-- endTime:tostring()));
self:AddCalendarEntry(
startTime,
endTime,
false ); -- isAllDay
end
end
end
-- Set scrollbar for the viewport
vScroll:SetMinimum(0);
vScroll:SetMaximum(weekViewHourLabelsViewPort.entries:GetHeight()-weekViewHourLabelsViewPort:GetHeight());
vScroll:SetValue(0);
-- set scrollbar ValueChanged event handler to take an action when our value changes,
-- in this case, to change the map position relative to the viewport
vScroll.ValueChanged=function()
weekViewHourLabelsViewPort.entries:SetTop(0-vScroll:GetValue());
partialDayEventsViewPort.entries:SetTop(0-vScroll:GetValue());
end
-- Set scrollbar for the viewport
hScroll:SetMinimum(0);
hScroll:SetMaximum(dayLabelViewPort.entries:GetWidth()-dayLabelViewPort:GetWidth());
hScroll:SetValue(0);
-- set scrollbar ValueChanged event handler to take an action when our value changes,
-- in this case, to change the map position relative to the viewport
hScroll.ValueChanged=function()
dayLabelViewPort.entries:SetLeft(0-hScroll:GetValue());
allDayEventsViewPort.entries:SetLeft(0-hScroll:GetValue());
partialDayEventsViewPort.entries:SetLeft(0-hScroll:GetValue());
end
-- forward mouse-wheel from children of viewport to scrollbar
function forwardMouseWheel(control)
control.MouseWheel=function(sender,args)
--Turbine.Shell.WriteLine( string.format(
-- "partialDayEventsViewPort.entries.MouseWheel value=[%d]",
-- vScroll:GetValue()));
--table.dump("sender",sender);
--table.dump("args",args);
local oldValue = vScroll:GetValue();
local newValue = oldValue - args.Direction * vScroll:GetLargeChange();
if( newValue <= vScroll:GetMinimum() ) then
newValue = vScroll:GetMinimum()
elseif( newValue >= vScroll:GetMaximum() ) then
newValue = vScroll:GetMaximum()
end
if( oldValue ~= newValue ) then
vScroll:SetValue(newValue);
end
end
end
ForEachControlList(weekViewHourLabelsViewPort.entries:GetControls(), forwardMouseWheel);
ForEachControlList(partialDayEventsViewPort.entries:GetControls(), forwardMouseWheel);
-- handle resize events
view.SizeChanged = function( sender, args )
--Turbine.Shell.WriteLine( string.format("View[%s].SizeChanged()", view.Name) );
hScroll:SetWidth( -- width,height
view:GetWidth() - topLeftLabel:GetWidth() - GRID_MARGIN*2 - 12);
hScroll:SetTop( -- left,top
view:GetHeight() - hScroll:GetHeight() - GRID_MARGIN*2 );
vScroll:SetHeight( -- width,height
hScroll:GetTop() - allDayEventLabel:GetTop() - allDayEventLabel:GetHeight() - GRID_MARGIN*2 );
vScroll:SetLeft( -- left,top
view:GetWidth() - vScroll:GetWidth());
dayLabelViewPort:SetWidth(hScroll:GetWidth());
allDayEventsViewPort:SetWidth(hScroll:GetWidth());
weekViewHourLabelsViewPort:SetHeight( vScroll:GetHeight() );
partialDayEventsViewPort:SetSize( -- width,height
hScroll:GetWidth(),
vScroll:GetHeight() );
end
return view;
end
--------------------------------------------------------------------------
--- CreateViewDay
--
-- Description: Creates the day view control
-- returns: the control for the day view
--
function CalendarWindow:CreateViewDay()
--Turbine.Shell.WriteLine( "CalendarWindow:CreateViewDay()" );
local view = Turbine.UI.Control();
view:SetParent( self.calenderView );
view:SetSize(self.calenderView:GetSize());
view:SetPosition(0,0);
view:SetVisible(false);
view:SetBackColor(Turbine.UI.Color.Blue);
view.Name = 'Day';
-- Add dropdown button to select Year,month,day (Only visible from Day view)
local now = DateTime:now();
local minYear = now.Year - (Options.yearLimit-1);
local maxYear = now.Year + (Options.yearLimit-1);
local buttonViewDayDropdown = FrostyPlugins.Calendar.DateDropDownBox{format="%y-%m-%d"};
view.buttonViewDayDropdown = buttonViewDayDropdown;
buttonViewDayDropdown:SetParent( self.buttonViewTitle );
buttonViewDayDropdown:SetSize( -- width,height
150,
self.buttonViewTitle:GetHeight() );
buttonViewDayDropdown:SetPosition( -- left,top
-- self.buttonOptions:GetLeft() + self.buttonOptions:GetWidth() + 5,
110, 0 );
--buttonViewDayDropdown:SetBackColor(Turbine.UI.Color.Purple); -- for debug window layout
----buttonSelectYear:SetBackColor( self.buttonViewTitle:GetBackColor());
buttonViewDayDropdown:SetForeColor(Turbine.UI.Color.White);
--buttonViewDayDropdown:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleRight );
buttonViewDayDropdown:SetOutlineColor( Turbine.UI.Color.Yellow );
buttonViewDayDropdown:SetFontStyle( Turbine.UI.FontStyle.None );
buttonViewDayDropdown:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
buttonViewDayDropdown:SetVisible( true );
buttonViewDayDropdown:SetYearRange( minYear, maxYear );
buttonViewDayDropdown:SetValue( self.current );
buttonViewDayDropdown.ValueChanged = function( sender, args )
--Turbine.Shell.WriteLine( string.format(
-- "buttonViewDayDropdown.ValueChanged: [%s] was [%s]",
-- args:tostring(),
-- self.current:tostring()
-- ));
-- Something else (e.g. AdjustView()) has already changed the month
-- So only adjust month if needed, to avoid an endless notification loop
if( self.current ~= args ) then
self.current = args;
self:RebuildCalendar();
end
end
buttonViewDayDropdown.MouseEnterField = function( sender, args )
--Turbine.Shell.WriteLine( "MouseEnterField " .. tostring(args.fieldName) );
args:SetFontStyle( Turbine.UI.FontStyle.Outline );
args:SetVisible( false );
args:SetVisible( true );
end
buttonViewDayDropdown.MouseLeaveField = function( sender, args )
--Turbine.Shell.WriteLine( "MouseLeaveField " .. tostring(args.fieldName) );
args:SetFontStyle( Turbine.UI.FontStyle.None );
args:SetVisible( false );
args:SetVisible( true );
end
local allDayEventLabel = Turbine.UI.Button();
allDayEventLabel:SetParent( view );
allDayEventLabel:SetSize( -- width,height
HOUR_LABEL_WIDTH,
100 );
allDayEventLabel:SetPosition( -- left,top
GRID_MARGIN,
GRID_MARGIN );
allDayEventLabel:SetBackColor(Turbine.UI.Color.Black);
allDayEventLabel:SetOutlineColor( Turbine.UI.Color.Yellow );
allDayEventLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
allDayEventLabel:SetText( "All\nDay" );
allDayEventLabel.MouseEnter = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.Yellow );
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
allDayEventLabel.MouseLeave = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.White );
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
allDayEventLabel.Click = function( sender, args )
--Turbine.Shell.WriteLine("clicked allDayEvent " .. self.current:tostring());
local startTime = self.current:clone():set({Hour=0,Minute=0});
local endTime = startTime:clone():set({Hour=23,Minute=59});
--Turbine.Shell.WriteLine( string.format(
-- " start[%s] end[%s]",
-- startTime:tostring(),
-- endTime:tostring()));
self:AddCalendarEntry(
startTime,
endTime,
true ); -- isAllDay
end
local vScroll = Turbine.UI.Lotro.ScrollBar();
vScroll:SetOrientation(Turbine.UI.Orientation.Vertical);
vScroll:SetParent(view);
vScroll:SetSize( -- width,height
11,
view:GetHeight() - allDayEventLabel:GetHeight() - GRID_MARGIN*4);
vScroll:SetPosition( -- left,top
view:GetLeft() + view:GetWidth() - vScroll:GetWidth(),
allDayEventLabel:GetTop() + allDayEventLabel:GetHeight() + GRID_MARGIN);
vScroll:SetVisible(true);
view.vScroll = vScroll;
local allDayEvents = Turbine.UI.Button();
allDayEvents:SetParent( view );
allDayEvents:SetSize( -- width,height
vScroll:GetLeft() - (allDayEventLabel:GetLeft() + allDayEventLabel:GetWidth() + GRID_MARGIN),
allDayEventLabel:GetHeight() );
allDayEvents:SetPosition( -- left,top
allDayEventLabel:GetLeft() + allDayEventLabel:GetWidth() + GRID_MARGIN,
allDayEventLabel:GetTop() );
allDayEvents:SetBackColor(Turbine.UI.Color.Black);
allDayEvents:SetOutlineColor( Turbine.UI.Color.Yellow );
allDayEvents:SetTextAlignment( Turbine.UI.ContentAlignment.TopCenter );
allDayEvents:SetText( "<click to add>" );
view.allDayEvents = allDayEvents;
allDayEvents.MouseEnter = function( sender, args )
sender:SetBackColor( Turbine.UI.Color(.1, .1, .1) );
sender:SetForeColor( Turbine.UI.Color.Yellow );
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
allDayEvents.MouseLeave = function( sender, args )
sender:SetBackColor( Turbine.UI.Color.Black );
sender:SetForeColor( Turbine.UI.Color.White );
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
allDayEvents.Click = function( sender, args )
--Turbine.Shell.WriteLine("clicked allDayEvent " .. self.current:tostring());
local startTime = self.current:clone():set({Hour=0,Minute=0});
local endTime = startTime:clone():set({Hour=23,Minute=59});
--Turbine.Shell.WriteLine( string.format(
-- " start[%s] end[%s]",
-- startTime:tostring(),
-- endTime:tostring()));
self:AddCalendarEntry(
startTime,
endTime,
true ); -- isAllDay
end
-- The height of each grid item
local gridItemHeight = 30;
-- Create the hourly events using a view port instead of a ListBox
-- this allows placing the entry buttons on top anywhere we need to
-- ** MY SINCERE AND ENDURING THANKS TO GARAN FOR THE VIEWPORT IDEA **
local hourlyViewPort = Turbine.UI.Control();
hourlyViewPort:SetParent( view );
hourlyViewPort:SetPosition( -- left,top
0,
vScroll:GetTop() );
hourlyViewPort:SetSize( -- width,height
view:GetWidth() - vScroll:GetWidth(),
vScroll:GetHeight() );
--hourlyViewPort:SetBackColor(Turbine.UI.Color.Purple); -- for debug
-- Create control within the viewport to hold the actual items
hourlyViewPort.entries = Turbine.UI.Control();
hourlyViewPort.entries:SetParent( hourlyViewPort );
hourlyViewPort.entries:SetPosition( 0, 0 ); -- left,top
hourlyViewPort.entries:SetSize( -- width,height
hourlyViewPort:GetWidth(),
HOURS_PER_DAY * (GRID_MARGIN + gridItemHeight) );
view.hourlyViewPort = hourlyViewPort;
-- Set scrollbar for the viewport
vScroll:SetMinimum(0);
vScroll:SetMaximum(hourlyViewPort.entries:GetHeight()-hourlyViewPort:GetHeight());
vScroll:SetValue(0);
-- set scrollbar ValueChanged event handler to take an action when our value changes,
-- in this case, to change the map position relative to the viewport
vScroll.ValueChanged=function()
hourlyViewPort.entries:SetTop(0-vScroll:GetValue());
end
-- fill the day grid with placeholders for each hour of the day
view.gridElementsDay = {};
for hourOfDay = 0, HOURS_PER_DAY - 1 do
local rowTop = hourOfDay * (gridItemHeight + GRID_MARGIN);
local hourRowLabel = Turbine.UI.Button();
hourRowLabel:SetParent( hourlyViewPort.entries );
hourRowLabel:SetSize( -- width,height
allDayEventLabel:GetWidth(),
gridItemHeight);
hourRowLabel:SetPosition( -- left,top
GRID_MARGIN,
rowTop);
hourRowLabel:SetBackColor(Turbine.UI.Color.Black);
hourRowLabel:SetOutlineColor( Turbine.UI.Color.Yellow );
hourRowLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleRight );
hourRowLabel:SetText( CalTools:hourLabel(hourOfDay) );
hourRowLabel.MouseEnter = function( sender, args )
sender:SetBackColor( Turbine.UI.Color(.1, .1, .1) );
sender:SetForeColor( Turbine.UI.Color.Yellow );
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
hourRowLabel.MouseLeave = function( sender, args )
sender:SetBackColor( Turbine.UI.Color.Black );
sender:SetForeColor( Turbine.UI.Color.White );
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
hourRowLabel.Click = function( sender, args )
--Turbine.Shell.WriteLine("clicked hour " .. tostring(hourOfDay));
local startTime = self.current:clone():set({Hour=hourOfDay, Minute=0});
local endTime = startTime:clone():set({Minute=59});
self:AddCalendarEntry(
startTime,
endTime,
false ); -- isAllDay
end
local hourEntryButton = Turbine.UI.Button();
hourEntryButton:SetParent( hourlyViewPort.entries );
hourEntryButton:SetSize( -- width,height
hourlyViewPort.entries:GetWidth(),
gridItemHeight );
hourEntryButton:SetPosition( -- left,top
hourRowLabel:GetLeft() + hourRowLabel:GetWidth() + GRID_MARGIN,
rowTop);
--hourEntryButton:SetBlendMode( Turbine.UI.BlendMode.Normal );
hourEntryButton:SetBackColor(Turbine.UI.Color.Black);
hourEntryButton:SetOutlineColor( Turbine.UI.Color.Yellow );
hourEntryButton:SetText( "<click to add entry>" );
hourEntryButton.hourOfDay = hourOfDay; -- add custom property to identify on click
-- Assign grid elements to hour of day
view.gridElementsDay[ hourOfDay+1 ] = hourEntryButton;
hourEntryButton.MouseEnter = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.Yellow );
sender:SetBackColor( Turbine.UI.Color(.1, .1, .1) );
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
hourEntryButton.MouseLeave = function( sender, args )
sender:SetForeColor( Turbine.UI.Color.White );
sender:SetBackColor( Turbine.UI.Color.Black );
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
hourEntryButton.Click = function( sender, args )
--Turbine.Shell.WriteLine("clicked hour " .. tostring(sender.hourOfDay));
local startTime = self.current:clone():set({Hour=sender.hourOfDay, Minute=0});
local endTime = startTime:clone():set({Minute=59});
self:AddCalendarEntry(
startTime,
endTime,
false ); -- isAllDay
end
end
-- forward mouse-wheel from children of viewport to scrollbar
function forwardMouseWheel(control)
control.MouseWheel=function(sender,args)
--Turbine.Shell.WriteLine( string.format(
-- "hourlyViewPort.entries.MouseWheel value=[%d]",
-- vScroll:GetValue()));
--table.dump("sender",sender);
--table.dump("args",args);
local oldValue = vScroll:GetValue();
local newValue = oldValue - args.Direction * vScroll:GetLargeChange();
if( newValue <= vScroll:GetMinimum() ) then
newValue = vScroll:GetMinimum()
elseif( newValue >= vScroll:GetMaximum() ) then
newValue = vScroll:GetMaximum()
end
if( oldValue ~= newValue ) then
vScroll:SetValue(newValue);
end
end
end
ForEachControlList(hourlyViewPort.entries:GetControls(), forwardMouseWheel);
-- handle resize events
view.SizeChanged = function( sender, args )
--Turbine.Shell.WriteLine( string.format("view[%s].SizeChanged()",view.Name) );
vScroll:SetHeight(
view:GetHeight() - allDayEventLabel:GetHeight() - GRID_MARGIN*4);
vScroll:SetLeft(
view:GetLeft() + view:GetWidth() - vScroll:GetWidth());
allDayEvents:SetWidth(
vScroll:GetLeft() - (allDayEventLabel:GetLeft() + allDayEventLabel:GetWidth() + GRID_MARGIN));
hourlyViewPort:SetSize( -- width,height
view:GetWidth() - vScroll:GetWidth(),
vScroll:GetHeight() );
hourlyViewPort.entries:SetWidth(hourlyViewPort:GetWidth());
for idx,hourEntryButton in ipairs(view.gridElementsDay) do
hourEntryButton:SetWidth(hourlyViewPort.entries:GetWidth());
end
end
return view;
end
--------------------------------------------------------------------------
--- CreateViewList
--
-- Description: Creates the list view control
-- returns: the control for the list view
function CalendarWindow:CreateViewList()
--Turbine.Shell.WriteLine( "CalendarWindow:CreateViewList()" );
local view = Turbine.UI.Control();
view:SetParent( self.calenderView );
view:SetSize(self.calenderView:GetSize());
view:SetPosition(0,0);
view:SetVisible(false);
view:SetBackColor(Turbine.UI.Color.Purple);
view.Name = 'List';
----------------------------------------------------------------------
-- create the list view
local nbrLabel = Turbine.UI.Label();
nbrLabel:SetParent( view );
nbrLabel:SetSize( -- width,height
30,
20 );
nbrLabel:SetPosition( -- left,top
GRID_MARGIN,
GRID_MARGIN );
--dateLabel:SetText( "Date/Time" );
nbrLabel:SetText( "#" );
nbrLabel:SetTextAlignment( Turbine.UI.ContentAlignment.TopCenter );
nbrLabel:SetBackColor(Turbine.UI.Color.Black);
local dateLabel = Turbine.UI.Label();
dateLabel:SetParent( view );
dateLabel:SetSize( -- width,height
145,
20 );
dateLabel:SetPosition( -- left,top
nbrLabel:GetLeft() + nbrLabel:GetWidth() + GRID_MARGIN,
GRID_MARGIN );
dateLabel:SetText( "Start Date/Time" );
--dateLabel:SetText( "yyyy-mm-dd hh:mm" );
dateLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
dateLabel:SetBackColor(Turbine.UI.Color.Black);
local vScroll = Turbine.UI.Lotro.ScrollBar();
vScroll:SetOrientation( Turbine.UI.Orientation.Vertical );
vScroll:SetParent( view );
vScroll:SetSize( -- width,height
11,
view:GetHeight() - dateLabel:GetHeight() - GRID_MARGIN*5 ); -- why 5?
vScroll:SetPosition( -- left,top
view:GetWidth() - vScroll:GetWidth(),
dateLabel:GetTop() + dateLabel:GetHeight() + GRID_MARGIN);
vScroll:SetVisible(true);
local descriptionLabel = Turbine.UI.Label();
descriptionLabel:SetParent( view );
descriptionLabel:SetSize( -- width,height
vScroll:GetLeft() - (dateLabel:GetLeft() + dateLabel:GetWidth() + GRID_MARGIN),
dateLabel:GetHeight() );
descriptionLabel:SetPosition( -- left,top
dateLabel:GetLeft() + dateLabel:GetWidth() + GRID_MARGIN,
dateLabel:GetTop() );
descriptionLabel:SetBackColor(Turbine.UI.Color.Black);
descriptionLabel:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
descriptionLabel:SetText( "Event Description" );
local entriesListBox = Turbine.UI.ListBox();
--local entriesListBox = Turbine.UI.Label();
entriesListBox:SetParent( view );
entriesListBox:SetPosition( -- left,top
GRID_MARGIN,
vScroll:GetTop() );
entriesListBox:SetSize( -- width,height
view:GetWidth() - vScroll:GetWidth() - GRID_MARGIN,
vScroll:GetHeight() );
--entriesListBox:SetBackColor(Turbine.UI.Color.Black);
--entriesListBox:SetVisible(true);
entriesListBox:SetVerticalScrollBar(vScroll);
view.entriesListBox = entriesListBox;
-- A custom function to add a calendar entry
local descriptionLabels = {};
entriesListBox.addCalendarEntry = function( sender, entry )
--Turbine.Shell.WriteLine( "entriesListBox.addCalendarEntry(" .. tostring(entry) .. ")");
--table.dump("entry",entry);
local entryRow = Turbine.UI.ListBox();
entryRow:SetParent( entriesListBox );
entryRow:SetSize( -- width,height
entriesListBox:GetWidth(),
30 );
entryRow:SetOrientation(Turbine.UI.Orientation.Horizontal);
entryRow:SetBlendMode( Turbine.UI.BlendMode.Normal );
--entryRow:SetWantsKeyEvents(true);
--entryRow:SetBackColor(Turbine.UI.Color.Blue);
--entryRow:SetBackColor(Turbine.UI.Color[entryColor]);
--entryRow.entryNumber = i;
entryRow.entry = entry; -- add custom property to identify on click
entriesListBox:AddItem(entryRow);
entryRow.MouseEnter = function( sender, args )
-- Increment by 2 to skip divider labels
for idx = 1, sender:GetItemCount(), 2 do
local item = sender:GetItem(idx);
item:SetFontStyle( Turbine.UI.FontStyle.Outline );
--item:SetForeColor( Turbine.UI.Color.Yellow );
item:SetBackColor( Turbine.UI.Color(.1, .1, .1) );
end
end
entryRow.MouseLeave = function( sender, args )
-- Increment by 2 to skip divider labels
for idx = 1, sender:GetItemCount(), 2 do
local item = sender:GetItem(idx);
item:SetFontStyle( Turbine.UI.FontStyle.None );
--item:SetForeColor( Turbine.UI.Color.White );
item:SetBackColor( Turbine.UI.Color.Black );
end
end
entryRow.MouseClick = function( sender, args )
--Turbine.Shell.WriteLine("entryRow.MouseClick entry " .. tostring(sender.entry));
self:UpdateCalendarEvent( sender.entry );
end
local rowDivider = Turbine.UI.Label();
rowDivider:SetParent( entryRow );
rowDivider:SetSize( -- width,height
entryRow:GetWidth(),
GRID_MARGIN );
entriesListBox:AddItem(rowDivider);
local entryColor = Turbine.UI.Color[entry.Color or "White"];
local entryNbr = Turbine.UI.Label();
entryNbr:SetParent( entryRow );
entryNbr:SetSize( -- width,height
nbrLabel:GetWidth(),
entryRow:GetHeight() );
entryNbr:SetBackColor(Turbine.UI.Color.Black);
entryNbr:SetForeColor(entryColor);
entryNbr:SetOutlineColor( Turbine.UI.Color.Yellow );
entryNbr:SetTextAlignment( nbrLabel:GetTextAlignment() );
entryNbr:SetText( tostring(entriesListBox:GetItemCount()/2) ); -- /2 because of dividers
entryRow:AddItem(entryNbr);
--entryNbr.MouseClick = function( sender, args )
-- --Turbine.Shell.WriteLine("entryNbr.MouseClick entry " .. tostring(sender));
-- entryRow:MouseClick(sender);
--end
local divider = Turbine.UI.Label();
divider:SetParent( entriesListBox );
divider:SetSize( -- width,height
GRID_MARGIN,
entryRow:GetHeight() );
entryRow:AddItem(divider);
local entryStart = Turbine.UI.Label();
entryStart:SetParent( entryRow );
entryStart:SetSize( -- width,height
dateLabel:GetWidth(),
entryRow:GetHeight() );
entryStart:SetBackColor(Turbine.UI.Color.Black);
entryStart:SetForeColor(entryColor);
entryStart:SetOutlineColor( Turbine.UI.Color.Yellow );
entryStart:SetText( entry.Start );
entryRow:AddItem(entryStart);
local divider = Turbine.UI.Label();
divider:SetParent( entriesListBox );
divider:SetSize( -- width,height
GRID_MARGIN,
entryRow:GetHeight() );
entryRow:AddItem(divider);
local entryDescription = Turbine.UI.Label();
entryDescription:SetParent( entryRow );
entryDescription:SetSize( -- width,height
descriptionLabel:GetWidth(),
entryRow:GetHeight() );
entryDescription:SetBackColor(Turbine.UI.Color.Black);
entryDescription:SetForeColor(entryColor);
entryDescription:SetOutlineColor( Turbine.UI.Color.Yellow );
entryDescription:SetText( entry.Description );
entryRow:AddItem(entryDescription);
entryRow.SizeChanged = function( sender, args )
--Turbine.Shell.WriteLine( string.format("entryRow.SizeChanged()") );
entryDescription:SetWidth(
entryRow:GetWidth() - entryStart:GetWidth() - GRID_MARGIN );
end
-- Modify settings for all controls in row
for idx=1, entryRow:GetItemCount() do
childCtl = entryRow:GetItem(idx);
--Turbine.Shell.WriteLine("set controls for entryNbr " .. entryNbr.GetText());
-- forward MouseClick event to row MouseClick
childCtl.MouseClick = function( sender, args )
--Turbine.Shell.WriteLine("entryNbr.MouseClick entry " .. tostring(sender));
entryRow:MouseClick(sender);
end
end
end
-- handle resize events
view.SizeChanged = function( sender, args )
--Turbine.Shell.WriteLine( string.format("view[%s].SizeChanged()",view.Name) );
vScroll:SetHeight(
view:GetHeight() - dateLabel:GetHeight() - GRID_MARGIN*5 ); -- why 5?
vScroll:SetLeft(
view:GetWidth() - vScroll:GetWidth());
descriptionLabel:SetWidth(
vScroll:GetLeft() - (dateLabel:GetLeft() + dateLabel:GetWidth() + GRID_MARGIN));
entriesListBox:SetSize( -- width,height
view:GetWidth() - vScroll:GetWidth() - GRID_MARGIN,
vScroll:GetHeight() );
for idx = 1, entriesListBox:GetItemCount() do
local entryRow = entriesListBox:GetItem(idx);
entryRow:SetWidth( entriesListBox:GetWidth() );
end
end
return view;
end
--------------------------------------------------------------------------
--- InitializeCalendar
--
-- Description: initialize the calendar: get the current day and
-- initialize the starting day and month
function CalendarWindow:InitializeCalendar()
--Turbine.Shell.WriteLine( "CalendarWindow:InitializeCalendar()" );
-- track "today"
--self.today = Turbine.Engine.GetDate();
self.today = DateTime:now();
-- initialize the rest of the date members that we use to
-- display the calendar
self.current = DateTime:now();
--Turbine.Shell.WriteLine("current: " .. self.current:tostring());
--for k,v in pairs(Turbine.Engine.GetDate()) do
-- Turbine.Shell.WriteLine("k: " .. k .. "=" .. tostring(v));
--end
-- Dump what routines are available in LOTRO's Engine package
--Turbine.Shell.WriteLine("Engine: ");
--for k,v in pairs(Turbine.Engine) do
-- Turbine.Shell.WriteLine("k: " .. k .. "=" .. tostring(v));
--end
-- Try to see what packages are available
--Turbine.Shell.WriteLine("package.path=" .. tostring(package.path));
--Turbine.Shell.WriteLine("package.loaded: ");
--for k,v in pairs(package.loaded) do
-- Turbine.Shell.WriteLine("k: " .. k .. "=" .. tostring(v));
--end
end
--------------------------------------------------------------------------
--- AdjustView
--
-- Description: the user has pressed the + or - buttons, so adjust
-- the display according to the view selected (month,week,day)
--
-- @param delta - integer - the amount of time to change the month by.
-- this will be 1 or -1 months
function CalendarWindow:AdjustView( delta )
--Turbine.Shell.WriteLine( string.format(
-- "CalendarWindow:AdjustView [%d] current=[%s]",
-- delta,
-- self.current:tostring()));
-- Compute new proposed date
local newDate = self.current:clone();
if( "Month" == self.settings.view) then
newDate:add({Month=delta});
elseif( "Week" == self.settings.view) then
newDate:add({Day=delta*(DAYS_PER_WEEK)});
elseif( "Day" == self.settings.view) then
newDate:add({Day=delta});
else
Turbine.Shell.WriteLine( " ignoring view adjustment in list view" );
return;
end
--Turbine.Shell.WriteLine( string.format(
-- "CalendarWindow:AdjustView new=[%s]",
-- newDate:tostring()));
-- Compute limits of adjustments
local now = DateTime:now();
local minYear = now.Year - (Options.yearLimit-1);
local maxYear = now.Year + (Options.yearLimit-1);
-- Abort if trying to go to date more than 5 years year ago
if( newDate.Year < minYear ) then
Turbine.Shell.WriteLine( string.format(
"Refused to go earlier than [%s]",
lowerLimit:tostring()));
return;
elseif( newDate.Year > maxYear ) then
Turbine.Shell.WriteLine( string.format(
"Refused to go later than [%s]",
upperLimit:tostring()));
return;
else
self.current = newDate;
end
self:RebuildCalendar();
end
--------------------------------------------------------------------------
--- RebuildCalendar
--
-- Description: update the calendar window with any changes.
-- Changes include:
-- * increasing or decreasing months (entire calendar needs to be redrawn),
-- * 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()
--Turbine.Shell.WriteLine( "CalendarWindow:RebuildCalendar()" );
-- Clear/Hide existing event buttons
-- - instead of creating/destroying buttons each time
-- just create a list of buttons for each event
-- and let each view re-arrange the buttons for each view
-- - How to handle events that wrap multiple weeks?
for idx,btn in ipairs(self.entryListButtons) do
local entryButton = self.entryListButtons[idx];
entryButton:SetParent( nil );
entryButton:SetVisible(false);
entryButton:SetEnabled(false);
--Turbine.Shell.WriteLine( string.format(" entryBtn[%d][%s] enabled[%s] visible[%s]",
-- idx,
-- tostring(btn),
-- tostring(btn:IsEnabled()),
-- tostring(btn:IsVisible())
-- ));
end
self.nextEntryButton = 1; -- reset event buttons
-- Enable/Disable left/right buttons depending on view and current date
-- Compute dates the left/right buttons would generate based on view
local now = DateTime:now();
local minYear = now.Year - (Options.yearLimit-1);
local maxYear = now.Year + (Options.yearLimit-1);
local newEarlierDate = self.current:clone();
local newLaterDate = self.current:clone();
if( "Month" == self.settings.view) then
newEarlierDate:add({Month=-1});
newLaterDate:add({Month=1});
elseif( "Week" == self.settings.view) then
newEarlierDate:add({Day=-(DAYS_PER_WEEK-1)});
newLaterDate:add({Day=(DAYS_PER_WEEK-1)});
elseif( "Day" == self.settings.view) then
newEarlierDate:add({Day=-1});
newLaterDate:add({Day=1});
else
-- set past limits to disable buttons
newEarlierDate = self.current:clone():set{Year=minYear-1};
newLaterDate = self.current:clone():set{Year=maxYear+1};
end
local isNotAtMinDate = not (newEarlierDate.Year < minYear);
local isNotAtMaxDate = not (newLaterDate.Year > maxYear);
--Turbine.Shell.WriteLine( string.format(
-- "CalendarWindow:RebuildCalendar() enableLeft=[%s] enableRight=[%s]",
-- tostring(isNotAtMinDate),
-- tostring(isNotAtMaxDate)));
self.buttonLeft:SetEnabled(isNotAtMinDate);
self.buttonRight:SetEnabled(isNotAtMaxDate);
-- enable the selected calendar display
--Turbine.Shell.WriteLine( "RebuildCalendar view [" .. self.settings.view .. "]" );
for view,control in pairs(self.calenderViews) do
if( view == self.settings.view) then
control:SetVisible(true);
else
control:SetVisible(false);
end
end
--Turbine.Shell.WriteLine( string.format(
-- "CalendarWindow:RebuildCalendar() currentDay = [%s]",
-- self.current:tostring() ));
local isVisible = self.calenderViews["Month"]:IsVisible();
self.calenderViews["Month"].buttonSelectMonth:SetVisible( isVisible );
self.calenderViews["Month"].buttonSelectYear:SetVisible( isVisible );
local isVisible = self.calenderViews["Day"]:IsVisible();
--view.buttonSelectYear:SetVisible( isVisible );
self.calenderViews["Day"].buttonViewDayDropdown:SetVisible( isVisible );
-- update the calendar display
if( self.calenderViews["Month"]:IsVisible() ) then
self:RebuildViewMonth();
elseif( self.calenderViews["Week"]:IsVisible() ) then
self:RebuildViewWeek();
elseif( self.calenderViews["Day"]:IsVisible() ) then
self:RebuildViewDay();
elseif( self.calenderViews["List"]:IsVisible() ) then
self:RebuildViewList();
end
end
--------------------------------------------------------------------------
--- getNextEntryButton
--
-- Description: gets the next calendar entry button
-- Creates a new button as needed
--
function CalendarWindow:getNextEntryButton()
--Turbine.Shell.WriteLine( string.format(
-- "CalendarWindow:getNextEntryButton() nbr[%d] next[%d]",
-- #self.entryListButtons,
-- self.nextEntryButton));
-- Get next button as needed (add button as needed)
if( self.nextEntryButton > #self.entryListButtons ) then
--Turbine.Shell.WriteLine( "CalendarWindow:getNextEntryButton() creating new button");
local btn = Turbine.UI.Button();
table.insert(self.entryListButtons, btn);
--table.dump("entryListButtons",self.entryListButtons);
--Turbine.Shell.WriteLine( string.format(
-- "CalendarWindow:getNextEntryButton() add nbr[%d] btn[%s]",
-- #self.entryListButtons,
-- tostring(btn)));
end
--table.dump("entryListButtons",self.entryListButtons);
local entryButton = self.entryListButtons[self.nextEntryButton];
assert( nil ~= entryButton, "failed to obtain entry button");
self.nextEntryButton = self.nextEntryButton+1;
--Turbine.Shell.WriteLine( string.format(
-- "CalendarWindow:getNextEntryButton() returning[%s]",
-- tostring(entryButton)));
entryButton:SetEnabled( true );
entryButton:SetVisible( true );
-- Override the SetBackColor to also contract the outline color
if( nil == entryButton.BaseSetBackColor) then
-- save the original function
entryButton.BaseSetBackColor = entryButton.SetBackColor;
end
entryButton.SetBackColor = function( self, color )
self:BaseSetBackColor(color);
-- Colors have R,G,B components with values 0 to 1
-- to invert color, take inverse of each color component
local contrastColor = Turbine.UI.Color(
1-color.R,
1-color.G,
1-color.B);
self:SetOutlineColor( contrastColor );
end
--entryButton:SetOutlineColor( Turbine.UI.Color.Yellow );
entryButton.MouseEnter = function( sender, args )
sender:SetFontStyle( Turbine.UI.FontStyle.Outline );
sender:SetVisible( false );
sender:SetVisible( true );
end
entryButton.MouseLeave = function( sender, args )
sender:SetFontStyle( Turbine.UI.FontStyle.None );
sender:SetVisible( false );
sender:SetVisible( true );
end
return entryButton;
end
--------------------------------------------------------------------------
--- AssignLanesToEntries
--
-- Description: Assign specified entries a lane for layout on calendar
-- where a lane is either a row in the monthly view or weekly all-day row
-- or a column in the daily or weekly view hourly events
-- @param entriesToDisplay = the entries to assign
-- @param granularity = the granularity for events
-- @return laneForEntry = a lookup table of the lanes for each entry
function CalendarWindow:AssignLanesToEntries(entriesToDisplay, granularity)
--Turbine.Shell.WriteLine( string.format(
-- "CalendarWindow:AssignLanesToEntries() entriesToDisplay[%s] nbr[%d] granularity[%s]",
-- tostring(entriesToDisplay),
-- #entriesToDisplay,
-- granularity));
if( ("day" ~= granularity) and ("hour" ~= granularity) ) then
error(string.format("Unsupported granularity [%s]",granularity),3);
end
--table.dump("BEFORE entriesToDisplay",entriesToDisplay);
--Turbine.Shell.WriteLine( " BEFORE entriesToDisplay:");
--for idx,entry in ipairs(entriesToDisplay) do
-- Turbine.Shell.WriteLine(string.format(" [%d][%s] start[%s] end[%s] desc[%s] ",
-- idx,
-- tostring(entry),
-- entry.Start,
-- entry.End,
-- entry.Description
-- ));
--end
-- order events by start date and longest duration
function orderByStartAndDuration(a,b)
-- if start times are identical
-- then pick the longer duration first
if( a.Start == b.Start) then
local dtStartA = DateTime:parse(a.Start);
local dtEndA = DateTime:parse(a.End);
local dtStartB = DateTime:parse(b.Start);
local dtEndB = DateTime:parse(b.End);
local aDuration = dtEndA:MinutesSince(dtStartA);
local bDuration = dtEndB:MinutesSince(dtStartB);
return aDuration > bDuration;
else
-- otherwise pick the one that starts first
return a.Start < b.Start;
end
end
table.sort(entriesToDisplay, orderByStartAndDuration);
--table.dump("SORTED entriesToDisplay",entriesToDisplay);
--Turbine.Shell.WriteLine( " SORTED entriesToDisplay:");
--for idx,entry in ipairs(entriesToDisplay) do
-- Turbine.Shell.WriteLine(string.format(" [%d] start[%s] end[%s] desc[%s]",
-- idx,
-- entry.Start,
-- entry.End,
-- entry.Description
-- ));
--end
-- layout calendar entry algorithm
-- https://stackoverflow.com/questions/50512059/algorithm-to-organise-calendar-events-using-minimum-positions
-- - initialize a list of free lanes
-- - for each event e,
-- 1. check which occupied lanes are free for e.startTime
-- 2. assign e.lane to a free lane, or add a new free lane if none empty
-- 3. mark the e.lane as occupied until e.endTime is reached
local laneExpiration = {}; -- keep track of when lane assignment expires
local laneForEntry = {}; -- Lookup of entry to lane on day
-- Assumes events are ordered by start date, longest item first
for idx,entry in ipairs(entriesToDisplay) do
--Turbine.Shell.WriteLine(string.format(
-- " entriesToDisplay[%d] start [%s] end [%s] description [%s]",
-- idx,
-- entry.Start,
-- entry.End,
-- entry.Description) );
-- Find next free lane for entry (add one if needed)
for lane = 1, #laneExpiration+1 do
--Turbine.Shell.WriteLine(string.format(
-- " ? lane [%d] expire [%s]",
-- lane,
-- tostring(laneExpiration[lane])));
-- treat each entry as all-day for month display
local entryStart;
local entryEnd;
if( "day" == granularity) then
entryStart = DateTime:parse(entry.Start):set({Hour=0,Minute=0});
entryEnd = DateTime:parse(entry.End):set({Hour=23,Minute=59});
elseif( "hour" == granularity) then
entryStart = DateTime:parse(entry.Start):set({Minute=0});
entryEnd = DateTime:parse(entry.End):set({Minute=59});
else
error("Unsupported granularity",2);
end
-- if lane not assigned, or has expired
-- then assign entry to lane, tracking expiration for lane
if( (not laneExpiration[lane]) or (laneExpiration[lane] < entryStart) ) then
laneForEntry[entry] = lane; -- assign entry to lane
laneExpiration[lane] = entryEnd; -- track expiration of lane
break; -- is now assigned, break out of lane-search loop
end
end
--Turbine.Shell.WriteLine(string.format(
-- " => lane [%d] expire [%s]",
-- laneForEntry[entry],
-- laneExpiration[laneForEntry[entry]] ) );
end
return laneForEntry;
end
--------------------------------------------------------------------------
--- RebuildViewMonth
--
-- Description: update the calendar month view
--
function CalendarWindow:RebuildViewMonth()
--Turbine.Shell.WriteLine( "CalendarWindow:RebuildViewMonth()" );
local view = self.calenderViews["Month"];
-- update the month label: month year
--local updateLabel = string.format("%s %s",
-- calTools:monthLabel(self.current.Month),
-- tostring(self.current.Year));
--self.buttonViewTitle:SetText( updateLabel );
self.buttonViewTitle:SetText( "" ); -- clear
local buttonSelectMonth = view.buttonSelectMonth;
buttonSelectMonth:SetSelectedIndex(self.current.Month);
local buttonSelectYear = view.buttonSelectYear;
local now = DateTime:now();
--Turbine.Shell.WriteLine( string.format("yearLimit[%d]",
-- Options.yearLimit));
local minYear = now.Year - (Options.yearLimit-1);
local maxYear = now.Year + (Options.yearLimit-1);
local yearValues = table.fill(minYear,maxYear);
--table.dump("yearValues",yearValues);
buttonSelectYear:SetDropDownList(yearValues);
buttonSelectYear:SetSelectedValue(self.current.Year);
-- determine the number of days in the month
local numberOfDays = self.current:daysInMonth();
--Turbine.Shell.WriteLine( string.format(
-- "days in month [%s] = [%d]",
-- self.current:ymd(),
-- numberOfDays) );
-- determine day that month starts
local startDay = calTools:dayOfWeek(
self.current.Year,
self.current.Month,
1);
--Turbine.Shell.WriteLine( string.format(
-- "Month [%s] starts on day [%d]",
-- self.current:ymd(),
-- startDay) );
-- create a key string for today (yyyy-mm-dd)
local todayString = DateTime:now():ymd();
--Turbine.Shell.WriteLine( "todayString = [" .. todayString .. "]" );
-- Enable the today button if not viewing current month
self.buttonToday:SetEnabled(
(self.current.Month ~= self.today.Month) or
(self.current.Year ~= self.today.Year)
);
-- intersection of
-- - all events that start before or on day
-- - all events that end after or on day
-- maintain indices of:
-- startIndices = all events that start on day
-- endIndices = all events that end on day
local dayIndex = 1;
for i = 1, #view.gridElementsMonth do
local gridElement = view.gridElementsMonth[ i ];
gridElement:SetVisible(true);
-- reset any indicators that the date has an event on it
gridElement:SetBackColor( Turbine.UI.Color.Black );
-- disable mouse click on inactive grid cells
gridElement:SetMouseVisible(false);
gridElement.entryKey = nil; -- clear old entry key
local newLabel = "";
if( i > startDay ) then
if( i < ( startDay + numberOfDays + 1 ) ) then
-- give slightly brighter color to active grid cells
gridElement:SetBackColor(
--Turbine.UI.Color.MidnightBlue
--Turbine.UI.Color.DarkGray -- too light
--Turbine.UI.Color.DarkBlue -- too light
Turbine.UI.Color(0.1,0.1,0.1) -- r,b,g
);
-- enable mouse clicks on active grid clls
gridElement:SetMouseVisible(true);
-- compute the day of the month label for this grid entry
local dayIndex = i - startDay;
newLabel = tostring( dayIndex );
-- if this date has an entry on it, change the back color
--local entryKey = string.format("%04d-%02d-%02d",
-- self.current.Year,
-- self.current.Month,
-- dayIndex );
local entryKey = self.current:clone():set({Day=dayIndex,Hour=0,Minute=0});
gridElement.entryKey = entryKey; -- Assign date for mouseClick
--Turbine.Shell.WriteLine( string.format(
-- "gridElement[%d] entryKey=[%s] %s",
-- i,
-- entryKey:tostring(),
-- tostring(gridElement)) );
-- highlight day for events
--local entriesForDay = self.settings.CalendarEntries[ entryKey:ymd() ];
--if( entriesForDay and (#entriesForDay > 0) ) then
-- gridElement:SetBackColor( Turbine.UI.Color( .75, .5, .5, 0 ) );
--end
-- highlight if this day is today
if( entryKey:ymd() == todayString ) then
gridElement:SetBackColor( Turbine.UI.Color.DarkSlateBlue );
end
end
end
gridElement:SetText( newLabel );
--Turbine.Shell.WriteLine(string.format(
-- " Grid [%d] text [%s]", i, newLabel) );
end
self:RebuildViewMonthEvents();
end
--------------------------------------------------------------------------
--- RebuildViewMonthEvents
--
-- Description: update the calendar month view with buttons for each event
--
function CalendarWindow:RebuildViewMonthEvents()
--Turbine.Shell.WriteLine( "CalendarWindow:RebuildViewMonthEvents()" );
-- Retreive all entries to display for this month
-- - this will also retreive entries that started before this month
-- - also need to include entries that end after this month
local somDateTime = self.current:clone():set({Day=1,Hour=0,Minute=0});
local numberOfDays = self.current:daysInMonth();
local eomDateTime = somDateTime:clone():set({Day=numberOfDays,Hour=23,Minute=59});
local entriesToDisplay = self:GetCalendarEntriesBetween(somDateTime, eomDateTime);
-- Assign entries to lanes (e.g. rows)
local laneForEntry = self:AssignLanesToEntries(entriesToDisplay, "day");
-- determine day that month starts
local monthStartDay = somDateTime:dayOfWeek();
-- Iterate over each entry, and place buttons on calendar grid
-- - Note: Entries that span weeks will have multiple buttons
for idx,entry in ipairs(entriesToDisplay) do
-- since month view, adjust start/end dates to appear to be all day
local entryStart = DateTime:parse(entry.Start):set({Hour=0,Minute=0});
local entryEnd = DateTime:parse(entry.End):set({Hour=23,Minute=59});
local duration = entryEnd:DaysSince(entryStart)+1;
--Turbine.Shell.WriteLine(string.format(
-- " entry [%d] Start [%s] End [%s] duration[%d] Lane[%d] [%s]",
-- idx,
-- tostring(entry.Start),
-- tostring(entry.End),
-- duration,
-- laneForEntry[entry],
-- entry.Description) );
--local nbrDaysLeft = duration;
if( entryStart < somDateTime ) then
-- if started in prior month, adjust to start of month
-- and adjust days left
entryStart = somDateTime;
end
if( entryEnd > eomDateTime ) then
-- if end in next month, clip to start of month
entryEnd = eomDateTime;
end
local nbrDaysLeft = entryEnd:DaysSince(entryStart)+1;
-- Iterate over days for entry to determine how many individual buttons to create
local setsOfButtons = {}; -- startIdx, endIdx
local buttonStartDay = entryStart;
local countdown = 6; -- prevent hang if bug in loop (can't have more than 6 weeks of buttons per event)
while( buttonStartDay < entryEnd ) do
local dayOfWeek = buttonStartDay:dayOfWeek();
local nbrDaysLeftInWeek = DAYS_PER_WEEK - dayOfWeek + 1;
local nbrDaysForButton = nbrDaysLeft; -- assume to end
if( nbrDaysLeft > nbrDaysLeftInWeek ) then
nbrDaysForButton = nbrDaysLeftInWeek;
end
nbrDaysLeft = nbrDaysLeft - nbrDaysForButton;
local buttonEndDay = buttonStartDay:clone():add({Day=nbrDaysForButton-1}):set({Hour=23,Minute=59});
local idxGridStart = monthStartDay + buttonStartDay.Day - 1;
local idxGridEnd = monthStartDay + buttonEndDay.Day - 1;
setsOfButtons[idxGridStart] = idxGridEnd;
buttonStartDay = buttonEndDay:clone():add({Day=1}):set({Hour=0,Minute=0}); -- move to next day
countdown = countdown - 1;
if( countdown <= 0 ) then
error("Detected error in loop!");
break; -- prevent infinite loop
end
end
-- TODO - display elipses '...' if not all events can be displayed in a cell
-- - click on '...' could bring to day/week display for
-- Iterate over buttons for event, add each button to month display
local view = self.calenderViews["Month"]
assert(view,"Missing view");
for idxGridStart,idxGridEnd in pairs(setsOfButtons) do
local gridElementStart = view.gridElementsMonth[ idxGridStart ];
local gridElementEnd = view.gridElementsMonth[ idxGridEnd ];
local lane = laneForEntry[entry];
--Turbine.Shell.WriteLine(string.format(
-- " event [%d] lane [%d] startGrid=[%d] endGrid=[%d]",
-- idx,
-- lane,
-- idxGridStart,
-- idxGridEnd) );
-- Get next entry button
local entryButton = self:getNextEntryButton();
entryButton:SetParent( view );
entryButton:SetSize( -- width,height
gridElementEnd:GetLeft() + gridElementEnd:GetWidth() - gridElementStart:GetLeft(),
20 );
local top = gridElementStart:GetTop() +
(entryButton:GetHeight() * lane );
entryButton:SetPosition( -- left,top
gridElementStart:GetLeft(),
top);
--entryButton:SetBackColor( gridElementStart:GetBackColor() );
--entryButton:SetBackColor( Turbine.UI.Color.DarkBlue );
entryButton:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleLeft );
--Turbine.Shell.WriteLine(string.format(
-- " event [%d] color [%s]",
-- idx, tostring(entry.Color)));
local entryColor = "White"; -- default
if( "string" == type(entry.Color) ) then
--Turbine.Shell.WriteLine(string.format(
-- " event [%d] setcolor [%s]",
-- idx, tostring(entry.Color)));
entryColor = entry.Color;
end
entryButton:SetForeColor( Turbine.UI.Color[entryColor] );
--entryButton:SetForeColor( Turbine.UI.Color.Orange );
entryButton:SetBackColor( gridElementStart:GetBackColor() );
entryButton:SetOutlineColor( Turbine.UI.Color.Yellow );
entryButton:SetMultiline( false );
entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
--entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana22 );
entryButton:SetText( entry.Description );
-- For items that span multiple days, append ' ---|' to show entry duration
if( idxGridStart ~= idxGridEnd ) then
--local maxTextLength = entryButton:GetWidth() / 7;
local maxTextLength = entryButton:GetWidth() /
FontWidthPerChar[entryButton:GetFont()];
local charsLeft = maxTextLength - string.len(entryButton:GetText()) -1;
--Turbine.Shell.WriteLine(string.format(
-- " event [%d] btnWidth [%d] maxTextLength [%d] charsLeft=[%d] font [%s]",
-- idx, entryButton:GetWidth(), maxTextLength, charsLeft, entryButton:GetFont()));
if( charsLeft > 3 ) then -- Leave roomt for at least " -|"
entryButton:AppendText( " " .. string.rep("-",charsLeft-1) .. "|");
end
end
-- Hide if more than 3 lanes
if( lane > 3 ) then
entryButton:SetVisible(false);
entryButton:SetEnabled(false);
else
entryButton:SetVisible(true);
entryButton:SetEnabled(true);
end
entryButton.entry = entry;
entryButton.Click = function( sender, args )
local day = sender:GetText();
--Turbine.Shell.WriteLine("click entry:\n" ..
-- " [" .. sender.entry.Description .. "]" );
self:UpdateCalendarEvent( sender.entry );
end
end
end
end
--------------------------------------------------------------------------
--- RebuildViewWeek
--
-- Description: update the calendar week view
--
function CalendarWindow:RebuildViewWeek()
--Turbine.Shell.WriteLine( "CalendarWindow:RebuildViewWeek" );
-- update the month label: y/m/d - y/m/d
-- - set the startDay to start of week, endDay to end of week.
local now = DateTime:now();
local currentDayOfWeek = self.current:dayOfWeek();
--Turbine.Shell.WriteLine( " currentDayOfWeek=[" .. currentDayOfWeek .. "]" );
local startOfWeek = self.current:clone():set({Hour=0, Minute=0}):add({Day = -(currentDayOfWeek-1)});
local endOfWeek = startOfWeek:clone():add({Day=6, Hour=23, Minute=59});
--Turbine.Shell.WriteLine( " startOfWeek=[" .. startOfWeek:tostring() .. "]" );
--Turbine.Shell.WriteLine( " endOfWeek=[" .. endOfWeek:tostring() .. "]" );
local updateLabel = string.format("%s - %s",
startOfWeek:ymd(),
endOfWeek:ymd() );
self.buttonViewTitle:SetText( updateLabel );
-- Enable the today button if not viewing current month
local isCurrentWeek = (startOfWeek < now ) and (now < endOfWeek);
--Turbine.Shell.WriteLine( " isCurrentWeek=[" .. tostring(isCurrentWeek) .. "]" );
self.buttonToday:SetEnabled( not isCurrentWeek );
-- Save first day of week so that click on hour buttons can compute day
local view = self.calenderViews["Week"];
view.startOfWeek = startOfWeek;
-- highlight day label if the day is today
local today = now:dayOfWeek();
for weekday = 1, DAYS_PER_WEEK do
local dayLabel = view.dayLabels[weekday];
local bgcolor = Turbine.UI.Color.Black;
if( isCurrentWeek and (weekday == today)) then
--Turbine.Shell.WriteLine( string.format(
-- " setting active color on weekday[%d] [%s]",
-- weekday,
-- dayLabel:GetText()));
bgcolor = Turbine.UI.Color.DarkSlateBlue;
end
dayLabel:SetBackColor( bgcolor );
end
self:RebuildViewWeekEvents();
end
--------------------------------------------------------------------------
function CalendarWindow:RebuildViewWeekEvents()
--Turbine.Shell.WriteLine( "CalendarWindow:RebuildViewWeekEvents" );
local view = self.calenderViews["Week"];
assert(view,"Missing view");
-- Retreive all entries to display for this month
-- - this will also retreive entries that started before this month
-- - also need to include entries that end after this month
local startOfWeek = view.startOfWeek;
local endOfWeek = startOfWeek:clone():add({Day=6, Hour=23, Minute=59});
--Turbine.Shell.WriteLine( " startOfWeek=[" .. startOfWeek:tostring() .. "]" );
--Turbine.Shell.WriteLine( " endOfWeek=[" .. endOfWeek:tostring() .. "]" );
local entriesToDisplay = self:GetCalendarEntriesBetween(startOfWeek, endOfWeek);
-- split out all-day from partial-day entries
local allDayEntries = {};
local partialDayEntries = {};
for idx,entry in ipairs(entriesToDisplay) do
--Turbine.Shell.WriteLine(string.format(
-- " entriesToDisplay[%d] start [%s] end [%s] isAllDay[%s] description [%s]",
-- idx,
-- entry.Start,
-- entry.End,
-- tostring(entry.isAllDay),
-- entry.Description) );
if( entry.isAllDay ) then
table.insert(allDayEntries, entry);
else
table.insert(partialDayEntries, entry);
end
end
--table.dump("allDayEntries",allDayEntries);
--table.dump("partialDayEntries",partialDayEntries);
-- Assign allDay entries to lanes (e.g. rows)
local laneForEntry = self:AssignLanesToEntries(allDayEntries, "day");
-- Get max lane, ensure always at least 1 lane in case no entries exist
local maxLane = math.max(1,unpack(table.values(laneForEntry)));
-- Add buttons for all day events
local allDayEvents = view.allDayEvents;
for idx,entry in ipairs(allDayEntries) do
local entryStart = DateTime:parse(entry.Start):set({Hour=0,Minute=0});
local entryEnd = DateTime:parse(entry.End):set({Hour=23,Minute=59});
local duration = entryEnd:DaysSince(entryStart)+1;
--Turbine.Shell.WriteLine(string.format(
-- " allday idx [%d] Start [%s] End [%s] duration[%d] Lane[%d] [%s]",
-- idx,
-- tostring(entry.Start),
-- tostring(entry.End),
-- duration,
-- laneForEntry[entry],
-- entry.Description) );
if( entryStart < startOfWeek ) then
-- if started in prior month, adjust to start of month
-- and adjust days left
entryStart = startOfWeek;
end
if( entryEnd > endOfWeek ) then
-- if end in next month, clip to start of month
entryEnd = endOfWeek;
end
local nbrDaysLeft = entryEnd:DaysSince(entryStart)+1;
local idxGridStart = entryStart:dayOfWeek();
local idxGridEnd = entryEnd:dayOfWeek();
local weekdayStart = view.allDayEvents[ idxGridStart ];
local weekdayEnd = view.allDayEvents[ idxGridEnd ];
--Turbine.Shell.WriteLine(string.format(
-- " idxStart [%s] idxEnd [%s] start[%s] end[%s]",
-- idxGridStart,
-- idxGridEnd,
-- tostring(weekdayStart),
-- tostring(weekdayEnd) ));
-- Get next button as needed (add button as needed)
local entryButton = self:getNextEntryButton();
entryButton:SetParent( view.allDayEventsViewPort.entries );
entryButton:SetSize( -- width,height
weekdayEnd:GetLeft() + weekdayEnd:GetWidth() - weekdayStart:GetLeft(),
20 );
entryButton:SetPosition( -- left,top
weekdayStart:GetLeft(),
entryButton:GetHeight() * laneForEntry[entry]);
--Turbine.Shell.WriteLine(string.format(
-- " btn left[%d] top[%d] wid[%d] height[%d]",
-- entryButton:GetLeft(),
-- entryButton:GetTop(),
-- entryButton:GetWidth(),
-- entryButton:GetHeight()));
entryButton:SetBackColor( Turbine.UI.Color.Black );
--entryButton:SetBackColor( Turbine.UI.Color.Red );
--Turbine.Shell.WriteLine(string.format(
-- " color [%s]",
-- tostring(entry.Color)));
local entryColor = entry.Color or "White";
entryButton:SetForeColor( Turbine.UI.Color[entryColor] );
--entryButton:SetForeColor( Turbine.UI.Color.Orange );
entryButton:SetOutlineColor( Turbine.UI.Color.Yellow );
entryButton:SetMultiline( false );
entryButton:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
--entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana22 );
entryButton:SetText( entry.Description );
-- For items that span multiple days, append ' ---|' to show entry duration
if( idxGridStart ~= idxGridEnd ) then
--local maxTextLength = entryButton:GetWidth() / 7;
local maxTextLength = entryButton:GetWidth() /
FontWidthPerChar[entryButton:GetFont()];
local charsLeft = maxTextLength - string.len(entryButton:GetText()) -1;
--Turbine.Shell.WriteLine(string.format(
-- " btnWidth [%d] maxTextLength [%d] charsLeft=[%d] font [%s]",
-- entryButton:GetWidth(), maxTextLength, charsLeft, entryButton:GetFont()));
if( charsLeft > 3 ) then -- Leave roomt for at least " -|"
entryButton:AppendText( " " .. string.rep("-",charsLeft-1) .. "|");
end
end
-- Hide if more than N rows
entryButton:SetVisible(idx < 4);
entryButton:SetEnabled(idx < 4);
entryButton.entry = entry;
entryButton.Click = function( sender, args )
--Turbine.Shell.WriteLine("click entry:\n" ..
-- " [" .. sender.entry.Description .. "]" );
self:UpdateCalendarEvent( sender.entry );
end
end
-- Assign allDay entries to lanes (e.g. cols)
local laneForEntry = self:AssignLanesToEntries(partialDayEntries, "hour");
-- Get max lane, ensure always at least 1 lane in case no entries exist
local maxLane = math.max(1,unpack(table.values(laneForEntry)));
-- Compute the entry width as a percentage of the hour column width
-- - subtract a little more to leave as a divider between entries
local width = (view.partialDayEntries[1][1]:GetWidth()/maxLane) -
(GRID_MARGIN * maxLane);
-- Add buttons for all day events
local partialDayEvents = view.partialDayEventsViewPort.entries;
for idx,entry in ipairs(partialDayEntries) do
local entryStart = DateTime:parse(entry.Start);
local entryEnd = DateTime:parse(entry.End);
local duration = entryEnd:MinutesSince(entryStart)+1;
--Turbine.Shell.WriteLine(string.format(
-- " pday idx [%d] Start [%s] End [%s] duration[%d] Lane[%d] [%s]",
-- idx,
-- tostring(entry.Start),
-- tostring(entry.End),
-- duration,
-- laneForEntry[entry],
-- entry.Description) );
local weekdayStart = entryStart:dayOfWeek();
local weekdayEnd = entryEnd:dayOfWeek();
--Turbine.Shell.WriteLine(string.format(
-- " start day [%d] end day [%d]",
-- weekdayStart, weekdayEnd));
if( entryStart < startOfWeek ) then
-- if started in prior month, adjust to start of month
-- and adjust days left
weekdayStart = 1;
end
if( entryEnd > endOfWeek ) then
-- if end in next month, clip to start of month
weekdayEnd = DAYS_PER_WEEK;
end
-- Iterate over all days to add buttons
--local weekdayStart = view.allDayEvents[ idxGridStart ];
--local weekdayEnd = view.allDayEvents[ idxGridEnd ];
local btn = 0;
for weekday = weekdayStart, weekdayEnd do
btn = btn + 1;
local startOfDay = view.startOfWeek:clone():add({Day=weekday-1}):set({Hour=0});
local endOfDay = startOfDay:clone():set({Hour=23});
local lane = laneForEntry[entry];
local hourStart = entryStart.Hour;
if( entryStart < startOfDay ) then
hourStart = 0;
end
local hourEnd = entryEnd.Hour;
if( entryEnd > endOfDay ) then
hourEnd = 23;
end
--Turbine.Shell.WriteLine(string.format(
-- " btn [%d] day [%s] hrStart [%d] hrEnd [%d]",
-- btn, startOfDay:tostring(), hourStart, hourEnd));
local firstHourEntry = view.partialDayEntries[weekday][hourStart];
local lastHourEntry = view.partialDayEntries[weekday][hourEnd];
local top = firstHourEntry:GetTop();
local height = lastHourEntry:GetTop() + lastHourEntry:GetHeight() - top;
local left = firstHourEntry:GetLeft() + (width * (lane-1)) + (GRID_MARGIN * (lane-1));
--Turbine.Shell.WriteLine(string.format(
-- " btn [%d] top [%d] height [%d] left [%d] width [%d]",
-- btn, top, height, left, width));
-- Get next entry button
local entryButton = self:getNextEntryButton();
entryButton:SetParent( view.partialDayEventsViewPort.entries );
entryButton:SetSize( width, height ); -- width,height
entryButton:SetPosition( left, top ); -- left,top
--entryButton:SetBackColor( gridElementStart:GetBackColor() );
--entryButton:SetBackColor( Turbine.UI.Color.DarkBlue );
entryButton:SetTextAlignment( Turbine.UI.ContentAlignment.TopLeft );
--Turbine.Shell.WriteLine(string.format(
-- " event [%d] color [%s]",
-- idx, tostring(entry.Color)));
local entryColor = entry.Color or "White";
entryButton:SetForeColor( Turbine.UI.Color.Black );
--entryButton:SetForeColor( Turbine.UI.Color.Orange );
--entryButton:SetBackColor( Turbine.UI.Black );
entryButton:SetBackColor( Turbine.UI.Color[entryColor] );
--entryButton:SetOutlineColor( Turbine.UI.Color.Yellow );
entryButton:SetMultiline( false );
entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
--entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana22 );
entryButton:SetText( entry.Description );
entryButton:SetVisible( true );
entryButton.entry = entry;
entryButton.Click = function( sender, args )
local day = sender:GetText();
--Turbine.Shell.WriteLine("click entry:\n" ..
-- " [" .. sender.entry.Description .. "]" );
self:UpdateCalendarEvent( sender.entry );
end
-- set callbacks to forward mouse-wheel from children of viewport to scrollbar
entryButton.MouseWheel=function(sender,args)
--Turbine.Shell.WriteLine( string.format(
-- "hourlyViewPort.entries.entryButton.MouseWheel value=[%d]",
-- view.vScroll:GetValue()));
--table.dump("sender",sender);
--table.dump("args",args);
local oldValue = view.vScroll:GetValue();
local newValue = oldValue - args.Direction * view.vScroll:GetLargeChange();
if( newValue <= view.vScroll:GetMinimum() ) then
newValue = view.vScroll:GetMinimum()
elseif( newValue >= view.vScroll:GetMaximum() ) then
newValue = view.vScroll:GetMaximum()
end
if( oldValue ~= newValue ) then
view.vScroll:SetValue(newValue);
end
end
end
end
end
--------------------------------------------------------------------------
--- RebuildViewDay
--
-- Description: update the calendar day view
--
function CalendarWindow:RebuildViewDay()
--Turbine.Shell.WriteLine( "CalendarWindow:RebuildViewDay" );
-- update the month label: month year
--local updateLabel = self.current:ymd();
--self.buttonViewTitle:SetText( updateLabel );
self.buttonViewTitle:SetText( "" ); -- clear
--local buttonSelectMonth = self.calenderViewMonth.buttonSelectMonth;
--buttonSelectMonth:SetSelectedIndex(self.current.Month);
local buttonViewDayDropdown = self.calenderViews["Day"].buttonViewDayDropdown;
buttonViewDayDropdown:SetValue(self.current);
-- Enable the today button if not the current day
local now = DateTime:now();
self.buttonToday:SetEnabled( updateLabel ~= now:ymd() );
self:RebuildViewDayEvents();
end
--------------------------------------------------------------------------
function CalendarWindow:RebuildViewDayEvents()
--Turbine.Shell.WriteLine( "CalendarWindow:RebuildViewDayEvents" );
-- Retreive all entries to display for this month
-- - this will also retreive entries that started before this month
-- - also need to include entries that end after this month
local sodDateTime = self.current:clone():set({Hour=0,Minute=0});
local eodDateTime = sodDateTime:clone():set({Hour=23,Minute=59});
local entriesToDisplay = self:GetCalendarEntriesBetween(sodDateTime, eodDateTime);
-- split out all-day from partial-day entries
local allDayEntries = {};
local partialDayEntries = {};
for idx,entry in ipairs(entriesToDisplay) do
--Turbine.Shell.WriteLine(string.format(
-- " entriesToDisplay[%d] start [%s] end [%s] isAllDay[%s] description [%s]",
-- idx,
-- entry.Start,
-- entry.End,
-- tostring(entry.isAllDay),
-- entry.Description) );
if( entry.isAllDay ) then
table.insert(allDayEntries, entry);
else
table.insert(partialDayEntries, entry);
end
end
--table.dump("allDayEntries",allDayEntries);
--table.dump("partialDayEntries",partialDayEntries);
-- Add buttons for all-day entries in top row
local view = self.calenderViews["Day"];
local allDayEvents = view.allDayEvents;
for idx,entry in ipairs(allDayEntries) do
-- Get next button as needed (add button as needed)
local entryButton = self:getNextEntryButton();
entryButton:SetParent( allDayEvents );
entryButton:SetSize( -- width,height
allDayEvents:GetWidth(),
20 );
local top = entryButton:GetHeight() * idx;
entryButton:SetPosition( -- left,top
0,
top);
--Turbine.Shell.WriteLine(string.format(
-- " entry [%d] btn left[%d] top[%d] wid[%d] height[%d]",
-- idx,
-- entryButton:GetLeft(),
-- entryButton:GetTop(),
-- entryButton:GetWidth(),
-- entryButton:GetHeight()));
entryButton:SetBackColor( allDayEvents:GetBackColor() );
--entryButton:SetBackColor( Turbine.UI.Color.Red );
--Turbine.Shell.WriteLine(string.format(
-- " entry [%d] color [%s]",
-- idx, tostring(entry.Color)));
local entryColor = entry.Color or "White";
entryButton:SetForeColor( Turbine.UI.Color[entryColor] );
--entryButton:SetForeColor( Turbine.UI.Color.Orange );
entryButton:SetOutlineColor( Turbine.UI.Color.Yellow );
entryButton:SetMultiline( false );
entryButton:SetTextAlignment( Turbine.UI.ContentAlignment.MiddleCenter );
entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
--entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana22 );
entryButton:SetText( entry.Description );
-- Hide if more than N rows
entryButton:SetVisible(idx < 4);
entryButton:SetEnabled(idx < 4);
entryButton.entry = entry;
entryButton.Click = function( sender, args )
--Turbine.Shell.WriteLine("click entry:\n" ..
-- " [" .. sender.entry.Description .. "]" );
self:UpdateCalendarEvent( sender.entry );
end
end
-- Assign entries to lanes (e.g. columns)
local laneForEntry = self:AssignLanesToEntries(partialDayEntries, "hour");
-- Get max lane, ensure always at least 1 lane in case no entries exist
local maxLane = math.max(1,unpack(table.values(laneForEntry)));
-- Get the width as a percentage of the 3rd column
-- - subtract a little more to leave as a divider between entries
local width = (view.allDayEvents:GetWidth()/maxLane) -
(GRID_MARGIN * maxLane);
-- Add buttons for partial-day entries to hourly rows
for idx,entry in ipairs(partialDayEntries) do
local entryStart = DateTime:parse(entry.Start):set({Minute=0});
local entryEnd = DateTime:parse(entry.End):set({Minute=59});
-- Determine which hourly rows the entry start/end
idxStart = 1;
if( entryStart:ymd() == self.current:ymd() ) then
idxStart = entryStart.Hour+1;
end
idxEnd = 24;
if( entryEnd:ymd() == self.current:ymd() ) then
idxEnd = entryEnd.Hour+1;
end
local lane = laneForEntry[entry];
--Turbine.Shell.WriteLine(string.format(
-- " entry [%d] btn idxStart[%d] idxEnd[%d] lane[%d] width[%d]",
-- idx,
-- idxStart,
-- idxEnd,
-- lane,
-- width));
-- Select the start/end row elements in the hourly column
local gridStart = view.gridElementsDay[idxStart];
local gridEnd = view.gridElementsDay[idxEnd];
-- Get next entry button
local entryButton = self:getNextEntryButton();
entryButton:SetParent( view.hourlyViewPort.entries );
entryButton:SetPosition( -- left,top
gridStart:GetLeft() + (width * (lane-1)) + (GRID_MARGIN * (lane-1)),
gridStart:GetTop());
entryButton:SetSize( -- width,height
width,
gridEnd:GetTop() + gridEnd:GetHeight() - gridStart:GetTop());
--entryButton:SetBackColor( gridElementStart:GetBackColor() );
entryButton:SetTextAlignment( Turbine.UI.ContentAlignment.TopLeft );
local entryColor = entry.Color or "White";
--entryButton:SetForeColor( Turbine.UI.Color[entryColor] );
entryButton:SetForeColor( Turbine.UI.Color.Black );
--entryButton:SetForeColor( Turbine.UI.Color.Orange );
--entryButton:SetBackColor( rowElementStart:GetBackColor() );
entryButton:SetBackColor( Turbine.UI.Color[entryColor] );
--entryButton:SetOutlineColor( Turbine.UI.Color.Yellow );
entryButton:SetMultiline( false );
entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana20 );
--entryButton:SetFont( Turbine.UI.Lotro.Font.Verdana22 );
entryButton:SetText( entry.Description );
entryButton:SetVisible( true );
entryButton.entry = entry;
entryButton.Click = function( sender, args )
local day = sender:GetText();
--Turbine.Shell.WriteLine("click entry:\n" ..
-- " [" .. sender.entry.Description .. "]" );
self:UpdateCalendarEvent( sender.entry );
end
-- set callbacks to forward mouse-wheel from children of viewport to scrollbar
entryButton.MouseWheel=function(sender,args)
--Turbine.Shell.WriteLine( string.format(
-- "hourlyViewPort.entries.entryButton.MouseWheel value=[%d]",
-- view.vScroll:GetValue()));
--table.dump("sender",sender);
--table.dump("args",args);
local oldValue = view.vScroll:GetValue();
local newValue = oldValue - args.Direction * view.vScroll:GetLargeChange();
if( newValue <= view.vScroll:GetMinimum() ) then
newValue = view.vScroll:GetMinimum()
elseif( newValue >= view.vScroll:GetMaximum() ) then
newValue = view.vScroll:GetMaximum()
end
if( oldValue ~= newValue ) then
view.vScroll:SetValue(newValue);
end
end
--Turbine.Shell.WriteLine(string.format(
-- " event [%d] btn[%s] evt[%s] enabled[%s] visible[%s]",
-- idx,
-- tostring(entryButton),
-- tostring(entryButton.entry),
-- tostring(entryButton:IsEnabled()),
-- tostring(entryButton:IsVisible())
-- ));
end
end
--------------------------------------------------------------------------
--- RebuildListView
--
-- Description: update the calendar list view
--
function CalendarWindow:RebuildViewList()
--Turbine.Shell.WriteLine( "CalendarWindow:RebuildListView()" );
self.buttonViewTitle:SetText( "List of all entries" );
-- Clear out existing events
local entriesListBox = self.calenderViews["List"].entriesListBox;
entriesListBox:ClearItems();
-- Rebuild list of all indexed events
for entryKey,entriesForDay in table.pairsBySortedKeys(self.index) do
for idx,entry in pairs(entriesForDay) do
entriesListBox:addCalendarEntry(entry);
end
end
end
--------------------------------------------------------------------------
--- IndexCalendarEntries
--
-- Description: Re-indexes all calendar entries in the index
--
function CalendarWindow:IndexCalendarEntries()
--Turbine.Shell.WriteLine( "CalendarWindow:IndexCalendarEntries()" );
-- Initialize calendar entry index
self.index = {};
self.reverseIndex = {};
--table.dump("Options.subscribedCalendars",Options.subscribedCalendars);
for k,v in pairs(Options.subscribedCalendars) do
if( v ) then
--Turbine.Shell.WriteLine(string.format("Indexing [%s] Entries ...",k));
local entriesList = self.entries[k];
for idx,entry in pairs(entriesList) do
self:AddEntryToIndex(entry);
end
end
end
--table.dump("index",self.index);
--table.dump("reverseIndex",self.reverseIndex);
--for k,v in pairs(self.reverseIndex) do
-- Turbine.Shell.WriteLine(string.format("%s k[%s] v[%s]",
-- "reverseIndex",
-- tostring(k),
-- tostring(v)));
--end
end
--------------------------------------------------------------------------
--- AddEntryToIndex
--
-- Description: Adds the specified item to the event entry index
function CalendarWindow:AddEntryToIndex(calendarEntry)
--Turbine.Shell.WriteLine( string.format(
-- "CalendarWindow:AddEntryToIndex [%s] [%s] [%s]",
-- tostring(calendarEntry),
-- tostring(calendarEntry.Start),
-- tostring(calendarEntry.Description)));
assert( calendarEntry, "AddEntryToIndex : Missing calendar entry");
-- - Index is multi-tiered map based on the start date:
-- index[yyyy-mm-dd][idx]
-- - This allows for:
-- 1) fast lookup per day and by extension month
-- 2) multiple entries that start on same day
-- Also keep a reverse index of each entry to the index it is in.
-- This provides for fast lookup of which index to remove item from.
-- - Keep in mind that after an update, the entry.Start may have changed
-- so can't rely on this to determine the index it is in
local dateStart = DateTime:parse(calendarEntry.Start);
local dayKey = dateStart:ymd();
local entriesForDay = self.index[ dayKey ];
-- If entry list for day does not exist yet, then create it
if( not entriesForDay ) then
--Turbine.Shell.WriteLine(string.format("Adding index for date [%s]", dayKey));
entriesForDay = {};
self.index[ dayKey ] = entriesForDay;
end
table.insert( entriesForDay, calendarEntry);
self.reverseIndex[calendarEntry] = dayKey;
end
--------------------------------------------------------------------------
--- RemoveEntryFromIndex
--
-- Removes the entry from the index
--
-- Params:
-- @param calendarEntry - a reference to the event to search for
--
function CalendarWindow:RemoveEntryFromIndex( calendarEntry )
--Turbine.Shell.WriteLine(string.format(
-- "CalendarWindow:RemoveEntryFromIndex calendarEntry=[%s]",
-- tostring(calendarEntry) ));
assert( calendarEntry, "RemoveEntryFromIndex : Missing calendar entry");
--table.dump("reverseIndex",self.reverseIndex);
-- Find the index from the reverseIndex[entry]
local dayKey = self.reverseIndex[calendarEntry];
self.reverseIndex[calendarEntry] = nil;
if( nil == dayKey ) then
--Turbine.Shell.WriteLine(string.format(
-- " entry [%s] not found in reverseIndex",
-- tostring(calendarEntry)));
return false;
end
local entriesForDay = self.index[ dayKey ];
--Turbine.Shell.WriteLine(string.format(
-- " Date [%s] has [%d] entries",
-- dayKey, #entriesForDay));
-- Find this entry in the existing index for that day
for idx,entryOnDay in ipairs(entriesForDay) do
--Turbine.Shell.WriteLine(string.format(" Checking [%s] = [%s] ",
-- idx, tostring(entryOnDay) ) );
if( entryOnDay == calendarEntry ) then
table.remove( entriesForDay, idx);
--Turbine.Shell.WriteLine(string.format(
-- " Removed from index [%s][%d]",
-- dayKey,
-- idx));
if( 0 == #entriesForDay ) then
self.index[ dayKey ] = nil;
--Turbine.Shell.WriteLine(string.format(
-- " Removed index list for [%s]",
-- dayKey));
end
return true; -- Found it!
end
end
return false; -- did not find it
end
--------------------------------------------------------------------------
--- GetCalendarEntriesBetween
--
-- Finds calendar entries between the specified date/times
--
-- Params:
-- @param startDateTime - the earliest time to accept
-- @param endDateTime - the latest time to accept
-- @return a list of all entries found
function CalendarWindow:GetCalendarEntriesBetween( startDateTime, endDateTime)
--Turbine.Shell.WriteLine(string.format("%s\n startDateTime=[%s]\n endDateTime=[%s]",
-- "CalendarWindow:GetCalendarEntriesBetween",
-- startDateTime:tostring(),
-- endDateTime:tostring() ) );
assert( startDateTime:validate(), "startDateTime expected to be a valid DateTime");
assert( endDateTime:validate(), "endDateTime expected to be a valid DateTime");
local endDateYMD = endDateTime:ymd();
-- Include entries that start before the endDateTime,
-- but do not finish before the startDateTime
local entriesToReturn = {};
for entryKey,entriesForDay in table.pairsBySortedKeys(self.index) do
--Turbine.Shell.WriteLine(string.format(" entryKey [%s] nbr=[%d]",
-- entryKey,
-- #entriesForDay) );
if( entryKey <= endDateYMD) then
for idx,entry in pairs(entriesForDay) do
local endDateTime = DateTime:parse(entry.End);
local rslt="skip";
if (endDateTime > startDateTime ) then
rslt="add";
table.insert(entriesToReturn, entry);
end
--Turbine.Shell.WriteLine(string.format(" idx [%d] start=[%s] end=[%s] => [%s]",
-- idx,
-- entry.Start,
-- entry.End,
-- rslt) );
end
end
end
--for idx,entry in ipairs(entriesToReturn) do
-- Turbine.Shell.WriteLine(string.format(" idx[%d] start[%s] end[%s] desc[%s]",
-- idx,
-- entry.Start,
-- entry.End,
-- entry.Description) );
--end
return entriesToReturn;
end
--------------------------------------------------------------------------
--- AddCalendarEntry
--
-- 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:
-- @param startTime - the start time in UTC
-- @param endTime - the end time in UTC
-- @param isAllDay - true if the event is all day, false otherwise
--
function CalendarWindow:AddCalendarEntry( startTime, endTime, isAllDay )
--Turbine.Shell.WriteLine( "CalendarWindow:AddCalendarEntry(" ..
-- "\n start = [" .. startTime:tostring() .. "]" ..
-- "\n end = [" .. endTime:tostring() .. "]" ..
-- "\n isAllDay = [" .. tostring(isAllDay) .. "]" );
-- Create new entry
newCalendarEntry = FrostyPlugins.Calendar.CalendarEntry();
newCalendarEntry.Start = startTime:tostring();
newCalendarEntry.End = endTime:tostring();
newCalendarEntry.isAllDay = isAllDay;
-- display the window that edits the CalendarEntry
-- the editor will take care of calling us back to save the entry
self.calendarEntryEditor:InitializeEntry( newCalendarEntry, self );
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.
--
-- @param calendarEntry - a reference to the Calendar entry item
-- to update
--
-- fixme: frosty
-- currently, the existing system only allows for a single
-- event per day.
--
function CalendarWindow:UpdateCalendarEvent( calendarEvent )
--Turbine.Shell.WriteLine( "CalendarWindow:UpdateCalendarEvent(" .. tostring(calendarEvent) .. ")");
assert( calendarEvent, "Missing entry!" );
-- display the window that edits the CalendarEntry
-- the editor notify by callback to save entry updates
self.calendarEntryEditor:InitializeEntry( calendarEvent, self );
end
--------------------------------------------------------------------------
--- NotifySaveEntry
--
-- Description: the user has modified an entry on the calendar and
-- elected to save it. 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.
--
-- @param entryEditor - CalendarEntryEditor - the editor window that contains
-- the CalendarEntry that is being saved
-- @param entryIndex - the index to the item
function CalendarWindow:NotifySaveEntry( calendarEntry )
--Turbine.Shell.WriteLine("NotifySaveEntry: " .. tostring(calendarEntry) );
assert( calendarEntry, "NotifySaveEntry : Missing calendar entry");
--table.dump("calendarEntry",calendarEntry);
-- Find item to PersonalCalendarEntries
local entryIdx;
for idx,entry in ipairs(self.settings.PersonalCalendarEntries) do
--Turbine.Shell.WriteLine(string.format(" Comparing to entry [%d][%s]",
-- idx,
-- tostring(entry)));
if( entry == calendarEntry ) then
entryIdx = idx;
break;
end
end
--Turbine.Shell.WriteLine(string.format(" Matched entry at [%s][%s]",
-- tostring(entryIdx),
-- tostring(calendarEntry)));
if( nil == entryIdx ) then
-- If entry list for day does not exist yet, then create it
--Turbine.Shell.WriteLine(string.format("Adding entry [%s]",
-- tostring(calendarEntry)));
table.insert( self.settings.PersonalCalendarEntries, calendarEntry);
else
-- Entry *DOES* exist
--Turbine.Shell.WriteLine(string.format("Found entry [%s] at [%d]",
-- tostring(calendarEntry),
-- entryIdx));
-- Remove existing entry from index + reverseIndex
self:RemoveEntryFromIndex( calendarEntry );
self.settings.PersonalCalendarEntries[entryIdx] = calendarEntry;
end
-- Re-Add entry to index (presumably at new position)
self:AddEntryToIndex( calendarEntry )
self:SaveSettings();
self:RebuildCalendar();
end
--------------------------------------------------------------------------
--- NotifyDeleteEntry
--
-- 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.
--
-- @param entryEditor - the CalendarEntryEditor window that contains
-- the CalendarEntry that is being deleted
function CalendarWindow:NotifyDeleteEntry( entryEditor )
local calendarEntry = entryEditor.calendarEntry;
Turbine.Shell.WriteLine(string.format(
"NotifyDeleteEntry: entry[%s]",
tostring(calendarEntry)) );
assert( calendarEntry, "NotifyDeleteEntry : Missing calendar entry");
--for idx,entry in ipairs(self.settings.PersonalCalendarEntries) do
-- Turbine.Shell.WriteLine(string.format(" BEFORE [%d] = [%s] ",
-- idx, tostring(entry) ) );
--end
-- Find this entry in the existing entries
self:RemoveEntryFromIndex( calendarEntry );
-- Find item to PersonalCalendarEntries
-- CAUTION: If there are gaps in the numerical indices of the list
-- then '#table' operator, table.remove(table,idx) and ipairs()
-- won't work anymore. (All will stop at the first gap).
-- Therefore, since we are already doing a linear search, just
-- iterate using pairs() instead.
local entryIdx;
for idx,entry in pairs(self.settings.PersonalCalendarEntries) do
--Turbine.Shell.WriteLine(string.format(" Checking [%d] = [%s] ",
-- idx, tostring(entry) ) );
if( entry == calendarEntry ) then
entryIdx = idx;
break;
end
end
if( nil == entryIdx ) then
Turbine.Shell.WriteLine(string.format("Entry not found [%s]",
tostring(calendarEntry)));
else
-- Entry *DOES* exist
--Turbine.Shell.WriteLine(string.format("Removing entry [%s] at [%d]",
-- tostring(calendarEntry),
-- entryIdx));
-- CAUTION: table.remove() won't work if there are gaps in the table.
-- so assign to nil instead.
--table.remove(self.settings.PersonalCalendarEntries, entryIdx);
self.settings.PersonalCalendarEntries[entryIdx] = nil;
end
self:SaveSettings();
self:RebuildCalendar();
end
--------------------------------------------------------------------------
--- NotifyOptionsChanged
--
-- Description: Notification that Options has changed
function CalendarWindow:NotifyOptionsChanged()
self:IndexCalendarEntries();
self:RebuildCalendar();
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()
--Turbine.Shell.WriteLine( "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 scope.
--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
-- Set calendar to middle of display if prior position unknown or offscreen
if( (nil == self.settings.positionX) or
(self.settings.positionX < 0 ) or
(self.settings.positionX >= Turbine.UI.Display.GetWidth()-10) ) then
self.settings.positionX = (Turbine.UI.Display.GetWidth() / 2) - (self:GetWidth() / 2 );
end
if( (nil == self.settings.positionY) or
(self.settings.positionY < 0) or
(self.settings.positionY >= Turbine.UI.Display.GetHeight()-10) ) then
self.settings.positionY = (Turbine.UI.Display.GetHeight() / 2) - self:GetHeight();
end
if( nil == self.settings.width ) then
self.settings.width = 600;
end
if( nil == self.settings.height ) then
self.settings.height = 650;
end
--table.dump("settings",self.settings);
-- If no personal entries exist, then create empty table
if( nil == self.settings.PersonalCalendarEntries ) then
self.settings.PersonalCalendarEntries = {};
else
Turbine.Shell.WriteLine(string.format(
"Loaded [%d] Personal Calendar Entries ...",
table.size(self.settings.PersonalCalendarEntries)));
--table.dump("self.settings.PersonalCalendarEntries",
-- self.settings.PersonalCalendarEntries);
--for idx,entry in ipairs(self.settings.PersonalCalendarEntries) do
-- Turbine.Shell.WriteLine( string.format(
-- " [%d][%s] = [%s] [%s] [%s] [%s] [%s]",
-- idx, tostring(entry),
-- tostring(entry.Start),
-- tostring(entry.End),
-- tostring(entry.Color),
-- tostring(entry.isAllDay),
-- tostring(entry.Description)));
--end
end
-- Convert old entries from old version of calendar
if( self.settings.CalendarEntryArray ) then
Turbine.Shell.WriteLine("Converting entries from prior version of Calendar ...");
table.dump("self.settings.CalendarEntryArray",self.settings.CalendarEntryArray);
for oldEntryKey,oldEntry in pairs(self.settings.CalendarEntryArray) do
--if( self.settings.CalendarEntryArray[ entryKey ] = calendarEntry;
-- entryKey = 'day..monthLabel..year' (e.g. "5April2022")
-- Old Fields:
--DateEntry : 'day..monthLabel..year' (e.g. "5April2022")
--TimeEntry
--Description
local day,monthLabel,year;
local idxStart,idxEnd,day,monthLabel,year = string.find(
oldEntry.DateEntry,"(%d+)(%a+)(%d%d%d%d)");
Turbine.Shell.WriteLine( string.format(
" day=[%s] monthLabel=[%s] year=[%d]",
day, monthLabel, year));
local month = CalTools:monthForLabel(monthLabel);
Turbine.Shell.WriteLine( string.format(
" month=[%d]",
tostring(month)));
local szStartDate = string.format("%4d-%02d-%02dT00:00:00",
year,month,day);
local dtStart = DateTime:parse(szStartDate);
local dtEnd = dtStart:clone():set({Hour=23,Minute=59});
Turbine.Shell.WriteLine( string.format(
" start[%s] end[%s]",
dtStart:tostring(),
dtEnd:tostring()));
local newCalendarEntry = FrostyPlugins.Calendar.CalendarEntry();
newCalendarEntry.Start = dtStart:tostring();
newCalendarEntry.End = dtEnd:tostring();
newCalendarEntry.isAllDay = true;
newCalendarEntry.Description = oldEntry.Description;
newCalendarEntry.Color = "HotPink";
table.insert(self.settings.PersonalCalendarEntries, newCalendarEntry);
-- Remove old entry
self.settings.CalendarEntryArray[oldEntryKey] = nil;
end
if( table.size( self.settings.CalendarEntryArray ) < 1 ) then
-- Remove old table
self.settings.CalendarEntryArray = nil;
Turbine.Shell.WriteLine("Removing calendar entry array from prior version of Calendar ...");
end
end
--table.dump("self.settings.PersonalCalendarEntries",self.settings.PersonalCalendarEntries);
--for idx = 1,#self.settings.PersonalCalendarEntries do
-- local entry = self.settings.PersonalCalendarEntries[idx];
-- Turbine.Shell.WriteLine( string.format(
-- "self.settings.PersonalCalendarEntries[%d] [%s]",
-- idx, tostring(entry)));
-- --table.dump(string.format("self.settings.PersonalCalendarEntries[%d]",idx),entry);
-- if( nil == entry ) then
-- table.remove(self.settings.PersonalCalendarEntries,idx);
-- end
--end
--for idx = 1,table.size(self.settings.PersonalCalendarEntries) do
-- local entry = self.settings.PersonalCalendarEntries[idx];
-- look for gaps in table, reform table to close gaps as needed
if( #self.settings.PersonalCalendarEntries ~= table.size(self.settings.PersonalCalendarEntries) ) then
Turbine.Shell.WriteLine("CAUTION: Found Gaps in PersonalCalendarEntries! Reforming ...");
for idx,entry in table.pairsBySortedKeys(self.settings.PersonalCalendarEntries) do
Turbine.Shell.WriteLine(string.format(" [%s] = [%s] [%s]",
idx,
tostring(entry),
entry and entry.Description or '(n/a)'));
end
local newTable = {};
for idx,entry in pairs(self.settings.PersonalCalendarEntries) do
table.insert(newTable, entry);
end
self.settings.PersonalCalendarEntries = newTable;
end
--error("ABORT!");
-- Look for entries with junk
--for idx,entry in pairs(self.settings.PersonalCalendarEntries) do
-- local gotJunk =
-- ( nil ~= entry.entryKey ) or
-- ( nil ~= entry.Invitees ) or
-- ( nil ~= entry.Organizer );
-- if( gotJunk ) then
-- --Turbine.Shell.WriteLine( string.format(
-- -- " [%d][%s] = [%s] [%s] [%s] [%s] [%s] [%s]",
-- -- idx, tostring(entry),
-- -- tostring(entry.entryKey),
-- -- tostring(entry.Start),
-- -- tostring(entry.End),
-- -- tostring(entry.Color),
-- -- tostring(entry.isAllDay),
-- -- tostring(entry.Description)));
-- table.dump(string.format("got junk entry[%d]",idx),entry);
-- entry.entryKey = nil;
-- entry.Invitees = nil;
-- end
--end
self.entries = {
Global = {};
Personal = self.settings.PersonalCalendarEntries;
};
--Turbine.Shell.WriteLine( string.format(
-- "\n PersonalCalendarEntries[%s]\n entries.Personal [%s]",
-- tostring(self.settings.PersonalCalendarEntries),
-- tostring(self.entries.Personal)));
-- setup global calendar
for idx,globalEntry in ipairs(globalEvents) do
local szTitle = globalEntry[1];
local szStart = globalEntry[2];
local szEnd = globalEntry[3];
local newCalendarEntry = FrostyPlugins.Calendar.CalendarEntry();
newCalendarEntry.ReadOnly = true;
newCalendarEntry.Start = szStart;
newCalendarEntry.End = szEnd;
newCalendarEntry.isAllDay = true;
newCalendarEntry.Description = szTitle;
newCalendarEntry.Color = "LightGreen";
-- append to table of global entries
table.insert(self.entries.Global, newCalendarEntry);
--Turbine.Shell.WriteLine( string.format(" [%d] [%s] [%s] [%s]",
-- idx,
-- szTitle,
-- tostring(szStart),
-- tostring(szEnd)));
end
Turbine.Shell.WriteLine(string.format(
"Loaded [%d] Global Calendar Entries ...",
#self.entries.Global));
-- Initialize calendar entry index
self:IndexCalendarEntries();
--assert(false,"ABORT!");
-- Set default view style as needed
if( nil == self.settings.view ) then
self.settings.view = CalendarView[1]; -- Month
end
--Turbine.Shell.WriteLine( "view = " .. self.settings.view );
--local epoch = os.time(); -- Not available to LOTRO clients
--local epoch = Turbine.Engine.GetLocalTime();
--Turbine.Shell.WriteLine("epoch: " .. epoch);
-- Keep track of the timezone because we'll need to translate
-- date/time to a global standard (UTC) to reasonably share calendar
-- entries between people in different timezones
-- - Unfortunately there doesn't appear to be a way to obtain
-- the timezone from Turbine.Engine.
-- - GetLocale() does NOT return timezone
-- - so we'll have to save this as a calendar setting
--local locale = Turbine.Engine.GetLocale(); -- en = ?
--Turbine.Shell.WriteLine("locale: " .. locale);
-- - This could be messy, since would have to seemlessly handle when
-- daylight savings starts/ends
--if( nil == self.settings.timeZone ) then
-- self.settings.timeZone = -5; # EDT
--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()
--Turbine.Shell.WriteLine( "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
local scope = Turbine.DataScope.Account;
--if( true == self.dataLocation.StoredOnCharacter ) then
-- scope = Turbine.DataScope.Character;
--end
Turbine.PluginData.Save(
scope,
"CalendarWindowSettings",
self.settings );
end
--------------------------------------------------------------------------
--- ListEntries
--
-- Description: Lists the calendar entries to command line
function CalendarWindow:ListEntries()
local entriesToReturn = {};
Turbine.Shell.WriteLine("Calendar Entries:" );
for entryKey,entriesForDay in table.pairsBySortedKeys(self.index) do
for idx,entry in ipairs(entriesForDay) do
Turbine.Shell.WriteLine( string.format(
" start[%s] desc[%s]",
--" [%s][%d] = start[%s] desc[%s]",
--entryKey, idx,
entry.Start,
tostring(entry.Description)
) );
end
end
end