|
Server : Apache/2.4.62 System : FreeBSD fbsdweb2.web.rcn.net 14.1-RELEASE FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC amd64 User : www ( 80) PHP Version : 8.3.8 Disable Function : NONE Directory : /domains/compasssysweb/calendar/CalciumDir39/Calendar/ |
Upload File : |
# Copyright 2001-2003, Fred Steinberg, Brown Bear Software
# Utils for day or week block views, with vertical time blocks
package TimeBlock;
use strict;
use CGI;
sub new {
my ($class, %args) = @_;
my $self = {cgi => CGI->new,
op => undef,
dates => [],
headers => {},
events => {},
%args};
$self->{numHeaders} = keys %{$self->{headers}};
bless $self, $class;
$self->_initialize;
$self;
}
sub hourLabels {
shift->{hourLabels};
}
sub _initialize {
my $self = shift;
my $op = $self->{op};
my $prefs = $op->prefs;
my ($startHour, $numHours) = $op->getParams (qw (DayViewStart
DayViewHours));
$numHours = $prefs->DayViewHours || 8 unless defined $numHours;
$startHour = $prefs->DayViewStart unless defined $startHour;
$startHour = 9 unless defined $startHour;
if ($startHour + $numHours > 24) {
$startHour = 24 - $numHours;
} elsif ($startHour < 0) {
$startHour = 0;
}
# Convert integer hours to hour strings
my $milTime = $prefs->MilitaryTime;
my @hours = ($startHour .. ($startHour + $numHours - 1));
@hours = map {_timeLabel ($_, $milTime)} @hours;
$self->{startHour} = $startHour;
$self->{numHours} = $numHours;
$self->{hourLabels} = \@hours;
$self->{colors}->{Hours} = [$prefs->color ('WeekHeaderBG') || '',
$prefs->color ('WeekHeaderFG') || ''];
$self->{colors}->{Days} = [$prefs->color ('DayHeaderBG') || '',
$prefs->color ('DayHeaderFG') || ''];
$self->{colors}->{DaySep} = [$prefs->color ('MainPageBG') || '',
$prefs->color ('MainPageFG') || ''];
$self->{colors}->{Cells} = [$prefs->color ('EventBG') || '',
$prefs->color ('EventFG') || ''];
$self->{fonts}->{Event} = [$prefs->font ('BlockEvent')];
$self->{fonts}->{Time} = [$prefs->font ('BlockEventTime')];
$self->{fonts}->{Include} = [$prefs->font ('BlockInclude')];
$self->{fonts}->{Category} = [$prefs->font ('BlockCategory')];
return $self;
}
sub render {
my $self = shift;
my $cgi = $self->{cgi};
my %tablesPerDay; # key is date; val is "table"
my %colsPerDay; # number of columns each day needs
my %untimedEvents;
my $untimedCount = 0;
# "Process" events for each day; 1 (or more) columns per day
foreach my $date (@{$self->{dates}}) {
my $events = $self->{events}->{"$date"};
my $untimed;
($events, $untimed) = $self->filterAndMungeEvents ($events, $date);
$untimedEvents{"$date"} = $untimed;
$untimedCount += @$untimed;
my @colsForDate; # different columns for this single date
my @table;
foreach my $event (@$events) {
my ($startRow, $numRows) = $self->getRowsForEvent ($event);
next unless defined $startRow;
my $column = $self->getColumnForEvent ($event, \@colsForDate);
$table[$startRow][$column] = [$event, $numRows];
# keep track so we don't put nbsp fillers later
for (my $i=1; $i<$numRows; $i++) {
$table[$startRow + $i][$column] = 'fnord';
}
}
$colsPerDay{$date} = @colsForDate || 1; # don't want 0, if no events
$tablesPerDay{$date} = \@table;
}
my @rows;
my ($bg, $fg) = @{$self->{colors}->{Days}};
my $fonts = $self->{fonts};
my $op = $self->{op};
# Do header row; first item is blank cell above hour labels
# my @tds = ($cgi->td ({bgcolor => $self->{colors}->{DaySep}->[0]},
my @tds = ($cgi->td ({bgcolor => $self->{colors}->{Hours}->[0]},
' '));
my @untimedTds = @tds;
my $rowSpan = $self->{numHours} + 1 + ($untimedCount ? 1 : 0);
my $colWidth = int (100 / $self->{numHeaders}) - 1 . '%';
foreach my $date (@{$self->{dates}}) {
push @tds, $cgi->td ({colspan => $colsPerDay{$date},
width => $colWidth, # IE needs this
bgcolor => $bg},
$cgi->font ({color => $fg},
$self->{headers}->{$date}));
push @tds, $cgi->td ({rowspan => $rowSpan,
bgcolor => $self->{colors}->{DaySep}->[0],
width => '0%'}, ' ')
# width => 1}, ' ')
unless ($date == $self->{dates}->[-1]);
my $untimedHTML = '';
foreach my $ev (Event->sort ($untimedEvents{$date} || [],
$op->prefs->EventSorting)) {
my ($fg, $bg) = $ev->colors ($op->calendarName, $op->prefs,
'no default');
$bg ||= $op->prefs->color ('MonthTailBG');
$fg ||= $op->prefs->color ('MonthTailFG');
# Just for $textID
my ($blah, $textID);
my $incFrom = $ev->includedFrom || '';
if ($incFrom ne $op->calendarName) {
($blah, $blah, $blah, $textID) =
$ev->getIncludedOverrides ($op->prefs->Includes);
}
my ($eventSize, $timeSize, $includeSize, $categorySize) =
($fonts->{Event}->[1], $fonts->{Time}->[1],
$fonts->{Include}->[1], $fonts->{Category}->[1]);
$eventSize-- if defined ($eventSize);
$timeSize-- if defined ($timeSize);
$includeSize-- if defined ($includeSize);
$categorySize-- if defined ($categorySize);
my $html = $ev->getHTML ({op => $op,
calName => $op->calendarName,
date => $date,
prefs => $op->prefs,
i18n => $op->I18N,
textFG => $fg,
textID => $textID,
eventFace => $fonts->{Event}->[0],
eventSize => $eventSize,
timeFace => $fonts->{Time}->[0],
timeSize => $timeSize,
includeFace => $fonts->{Include}->[0],
includeSize => $includeSize,
categoryFace => $fonts->{Category}->[0],
categorySize => $categorySize});
$untimedHTML .= $cgi->table ({-width => '100%',},
$cgi->Tr ($cgi->td ({bgcolor => $bg},
$html)));
}
$untimedHTML ||= ' ';
push @untimedTds, $cgi->td ({colspan => $colsPerDay{$date},
bgcolor => $op->prefs->color
('MonthTailBG')},
$untimedHTML);
}
push @rows, $cgi->Tr ({align => 'center'}, @tds);
push @rows, $cgi->Tr ({valign => 'top'}, @untimedTds) if $untimedCount;
my $showTimes = $op->prefs->TimePlanShowTimes || 'always';
# And build up the table, one row at a time
my @hourLabels = @{$self->hourLabels};
for (my $row=0; $row<$self->{numHours}; $row++) {
# First, the leftmost hour cell
my @tds = ($cgi->td ({-width => '5%',
-bgcolor => $self->{colors}->{Hours}->[0]},
$cgi->font ({-color => $self->{colors}->{Hours}->[1]},
shift @hourLabels)));
# Then, cells for each day. 1 (or more) columns per day.
foreach my $date (@{$self->{dates}}) {
my $nbspSpan = 0;
for (my $col=1; $col<=$colsPerDay{$date}; $col++) {
my $eventInfo = $tablesPerDay{$date}[$row][$col];
if (ref $eventInfo) {
# If needed, put spaces which come before event this row
if ($nbspSpan) {
push @tds, $cgi->td ({-colSpan => $nbspSpan},
' ');
$nbspSpan = 0;
}
my $ev = $eventInfo->[0];
my ($fg, $bg) = $ev->colors ($op->calendarName,
$op->prefs);
# Just for $textID
my ($blah, $textID);
my $incFrom = $ev->includedFrom || '';
if ($incFrom ne $op->calendarName) {
($blah, $blah, $blah, $textID) =
$ev->getIncludedOverrides ($op->prefs->Includes);
}
# Maybe don't display times
my $hideTimes;
if (lc ($showTimes) eq 'never') {
$hideTimes = 1;
} elsif (lc ($showTimes eq 'unaligned')) {
$hideTimes = 1 unless (($ev->startTime || 0) % 60
or
($ev->endTime || 0) % 60);
}
push @tds, $cgi->td ({-rowSpan => $eventInfo->[1],
-bgcolor => $bg},
$ev->getHTML ({op => $op,
calName => $op->calendarName,
date => $date,
prefs => $op->prefs,
i18n => $op->I18N,
textFG => $fg,
textID => $textID,
eventFace => $fonts->{Event}->[0],
eventSize => $fonts->{Event}->[1],
hideTimes => $hideTimes,
timeFace => $fonts->{Time}->[0],
timeSize => $fonts->{Time}->[1],
includeFace => $fonts->{Include}->[0],
includeSize => $fonts->{Include}->[1],
categoryFace => $fonts->{Category}->[0],
categorySize => $fonts->{Category}->[1],
}));
} elsif ($eventInfo and $eventInfo eq 'fnord') {
if ($nbspSpan) {
push @tds, $cgi->td ({-colSpan => $nbspSpan},
' ');
$nbspSpan = 0;
}
} else {
$nbspSpan++; # fill in for multi-row events in other cols.
}
}
if ($nbspSpan) {
my $data = ' '; # workaround for goofy browsers
push @tds, $cgi->td ({-colSpan => $nbspSpan}, $data);
$nbspSpan = 0;
}
}
push @rows, $cgi->Tr (@tds);
}
($bg, $fg) = @{$self->{colors}->{Cells}};
my $html = $cgi->table ({border => 1,
cellpadding => 2,
cellspacing => 1,
bgcolor => $bg,
width => '100%',
cols => $self->{numHeaders} * 2
},
@rows);
}
sub filterAndMungeEvents {
my ($self, $events, $date) = @_;
my (@events, @untimed);
# First, keep only events with times
foreach my $event (@$events) {
if (defined $event->startTime) {
push @events, $event;
} else {
push @untimed, $event;
}
}
my %munged;
# Keep track of which events (if any) start on previous
# day.
foreach (@events) {
if ($_->Date and $_->Date != $date) {
my $key = $_->includedFrom || '' . $_->id;
$munged{$key} ||= 1;
}
}
$self->{displayAtMidnight} = \%munged;
# Sort on time; if starts on previous day, start time is 0
@events = sort {if ($munged{$a->includedFrom || '' . $a->id}) {-1}
elsif ($munged{$b->includedFrom || '' . $b->id}) {1}
else { $a->startTime <=> $b->startTime or
($a->endTime || 0) <=> ($b->endTime || 0)}} @events;
return (\@events, \@untimed);
}
sub getRowsForEvent {
my ($self, $event) = @_;
my $startTime = $self->_displayTime ($event);
my $endTime = $event->endTime;
my $startHour = $self->{startHour};
my $numHours = $self->{numHours};
$endTime = $startTime if (defined $startTime and !defined $endTime);
$endTime = 1440 if ($endTime < $startTime); # it ends on next day
# out of bounds; just return;
return if ($startTime >= ($startHour + $numHours) * 60 or
$endTime < $startHour * 60 or
($endTime != $startTime and $endTime == $startHour * 60));
my ($startRow, $rowSpan, $blah);
$startRow = int ($startTime / 60) - $startHour;
$startRow = 0 if ($startRow < 0);
$blah = $endTime / 60;
my $endHour = int $blah;
$endHour++ unless ($endHour == $blah);
$endHour = $numHours + $startHour if $endHour > $numHours + $startHour;
$rowSpan = $endHour - $startHour - $startRow;
$rowSpan = 1 if $rowSpan < 1;
return ($startRow, $rowSpan);
}
# Find which column an event goes in for a particular day; only multiple
# columns per day if there are time conflicts in that day.
# Returns the number of the column, e.g. '3'
sub getColumnForEvent {
my ($self, $event, $columns) = @_;
# $columns is list of list of events already placed in each column
my ($isInColumn, $i);
foreach my $thisColumn (@$columns) {
$i++;
next if $self->_conflicts ($event, $thisColumn);
push @$thisColumn, $event;
$isInColumn = $i;
last;
}
unless ($isInColumn) {
push @$columns, [$event]; # put new listref, w/event in it
$isInColumn = @$columns;
}
return $isInColumn;
}
sub hourControls {
my ($self) = @_;
my $cgi = $self->{cgi};
my $op = $self->{op};
my $i18n = $op->I18N;
my $prefs = $op->prefs;
my $startHour = $self->{startHour};
my $numHours = $self->{numHours};
return '' if (($prefs->DayViewControls || '') eq 'hide');
my $milTime = $prefs->MilitaryTime;
my @startAtValues = (0..23);
my %startAtLables = map {$_ =>_timeLabel ($_, $milTime)} @startAtValues;
my ($hours18, $hour18) = ($i18n->get ('hours'), $i18n->get ('hour'));
my %displayLabels = map {$_ => "$_ $hours18"} (1..24);
$displayLabels{1} = "1 $hour18";
my ($full, $half) = ($numHours, int ($numHours / 2));
my $backFull = $op->makeURL ({DayViewStart => $startHour - $full});
my $backHalf = $op->makeURL ({DayViewStart => $startHour - $half});
my $foreHalf = $op->makeURL ({DayViewStart => $startHour + $half});
my $foreFull = $op->makeURL ({DayViewStart => $startHour + $full});
my ($bg, $fg) = ($prefs->color ('DayViewControlsBG') || '',
$prefs->color ('DayViewControlsFG') || '');
my ($face, $size) = $prefs->font ('DayViewControls');
my $fontParams = {-color => $fg,
-face => $face,
-size => $size};
my @links =
($cgi->a ({href => $backFull},
$cgi->font ($fontParams, "<$full " . $i18n->get ('hours'))),
' ',
$cgi->a ({href => $backHalf},
$cgi->font ($fontParams, "<$half " . $i18n->get ('hours'))),
' ',
$cgi->b ($cgi->font ($fontParams, $i18n->get ('Shift Hours'))),
' ',
$cgi->a ({href => $foreHalf},
$cgi->font ($fontParams, "$half " . $i18n->get('hours') .
'>')),
' ',
$cgi->a ({href => $foreFull},
$cgi->font ($fontParams, "$full " . $i18n->get('hours') .
'>')));
my $hourShifts = $cgi->td (\@links);
my $html = $cgi->startform;
if (!$prefs->PrintPrefs) {
$html .= $cgi->table
({align => 'center',
width => '100%',
cellspacing => 0,
cellpadding => 0,
border => 0,
bgcolor => 'gray'},
$cgi->Tr ({-align => 'left',
-bgcolor => $bg},
$cgi->td
(' ' .
$cgi->font
($fontParams,
$i18n->get ('Start at: ') .
$cgi->popup_menu (-name => 'DayViewStart',
-onChange =>
'document.forms[0].submit()',
-default => $startHour,
-values => \@startAtValues,
-labels => \%startAtLables))),
$cgi->td
($cgi->font
($fontParams,
$i18n->get ('Display: '),
$cgi->popup_menu (-name => 'DayViewHours',
-onChange =>
'document.forms[0].submit()',
-default => $numHours,
-values => [1..24],
-labels => \%displayLabels))),
$hourShifts));
}
$html .= $cgi->hidden (-name => 'CalendarName',
-value => $op->calendarName);
$html .= $op->hiddenDisplaySpecs;
$html .= $cgi->endform;
return $html;
}
sub _timeLabel {
my ($hour, $milTime) = @_;
my $amPm = '';
if (!$milTime) {
$amPm = $hour < 12 ? 'am ' : 'pm ';
$hour = 12 if $hour == 0;
$hour -= 12 if ($hour > 12);
}
return "$hour:00" . $amPm;
}
# ------------------------------------------------------------------
# Note that in KISS model, we must round to floor, ceiling hours...
sub _conflicts {
my ($self, $event, $col) = @_;
my ($evStart, $evEnd) = $self->_normTime ($event);
foreach my $ev (@$col) {
my ($start, $end) = $self->_normTime ($ev);
next if ($evStart >= $end);
next if ($evEnd <= $start);
return 1;
}
return undef;
}
sub _normTime {
my ($self, $event) = @_;
my $startTime = $self->_displayTime ($event);
my $start = int ($startTime / 60);
my $blah = defined $event->endTime ? $event->endTime
: ($startTime + 1);
$blah /= 60;
my $end = int $blah;
$end++ unless ($end == $blah);
$end = 24 if ($end < $start); # if it ends tomorrow
return ($start, $end);
}
sub _displayTime {
my ($self, $event) = @_;
my $key = $event->includedFrom || '' . $event->id;
return $self->{displayAtMidnight}->{$key} ? 0 : $event->startTime;
}
1;