diff --git a/components/tvplanit/source/vpganttview.pas b/components/tvplanit/source/vpganttview.pas index 4828a75a8..aefe2c9a8 100644 --- a/components/tvplanit/source/vpganttview.pas +++ b/components/tvplanit/source/vpganttview.pas @@ -89,8 +89,10 @@ type FActiveDate: TDateTime; // Selected date FFirstDate: TDateTime; // Date of the first event in the resource FLastDate: TDateTime; // Date of the last event in the resource - FStartDate: TDateTime; // Date of the first event to be displayed/printed - FEndDate: TDateTime; // Date of the last event to be displayed/printed + FStartDate: TDateTime; // Date of the first event to be displayed/printed (0 = first event ever) + FEndDate: TDateTime; // Date of the last event to be displayed/printed (0 = last event ever) + FRealStartDate: TDate; // Date of the first event to be displayed/printed (0 replaced) + FRealEndDate: TDate; // Date of the last event to be displayed/printed (0 repalaced) FLeftCol: Integer; // Index of the left-most day column FTopRow: Integer; // Index of the top-most event row @@ -181,10 +183,11 @@ type function GetDateTimeAtCoord(X: Integer): TDateTime; function GetEventAtCoord(X, Y: Integer): TVpEvent; function GetEventOfRow(ARow: Integer): TVpEvent; + procedure GetRealEventDateRange(out AStartDate, AEndDate: TDate); function GetRowAtCoord(Y: Integer): Integer; function GetRowOfEvent(AEvent: TVpEvent): Integer; - procedure GetEventDateRange; - function IsEventOnDate(AEvent: TVpEvent; ADate: TDateTime): Boolean; + procedure GetEventDateRange(out AFirstDate, ALastDate: TDate); + function IsEventOnDate(AEvent: TVpEvent; ADate: TDate): Boolean; procedure Hookup; procedure Populate; procedure PopulateDayRecords; @@ -251,6 +254,7 @@ type Angle: TVpRotationAngle; Scale: Extended; RenderDate: TDateTime; StartLine, StopLine: Integer; UseGran: TVpGranularity; DisplayOnly: Boolean); override; + procedure SetDateLimits(AStartDate, AEndDate: TDateTime); {$IF VP_LCL_SCALING = 2} procedure FixDesignFontsPPI(const ADesignTimePPI: Integer); override; @@ -261,16 +265,18 @@ type {$ENDIF} {$ENDIF} - // Methods to be called by painter + // Methods/properties used by painter. Not meant to be called by user. function CalcVisibleCols(AWidth, AFixedColWidth, AColWidth: Integer): Integer; function CalcVisibleRows(AHeight, AHeaderHeight, ARowHeight: Integer): Integer; + property FirstDate: TDateTime read FFirstDate; + property LastDate: TDateTime read FLastDate; + property RealStartDate: TDateTime read FRealStartDate; + property RealEndDate: TDateTime read FRealEndDate; property ActiveCol: Integer read FActiveCol write SetActiveCol; property ActiveEvent: TVpEvent read FActiveEvent write SetActiveEvent; property ActiveDate: TDateTime read FActiveDate write SetActiveDate; property ActiveRow: Integer read FActiveRow write SetActiveRow; - property FirstDate: TDateTime read FFirstDate; - property LastDate: TDateTime read FLastDate; property StartDate: TDateTime read FStartDate write FStartDate; property EndDate: TDateTime read FEndDate write FEndDate; property ColCount: Integer read FColCount write FColCount; @@ -502,7 +508,7 @@ begin if UseAsHint then begin { Usage as hint } startDateStr := FormatDateTime(DATE_FORMAT, AEvent.StartTime); - if trunc(AEvent.StartTime) = trunc(AEvent.EndTime) then + if SameDate(AEvent.StartTime, AEvent.EndTime) then endDateStr := '' else endDateStr := FormatDateTime(DATE_FORMAT, AEvent.EndTime); @@ -612,9 +618,7 @@ var dt, startTime, endTime: TDateTime; begin inherited; -// dvClickTimer.Enabled := false; FMouseDown := false; - //FDragging := false; // If the mouse was pressed down in the client area, then select the cell. if not Focused then @@ -626,15 +630,18 @@ begin exit; end; + // Is there an event at the clicked cell? FActiveEvent := GetEventAtCoord(FMouseDownPoint.X, FMouseDownPoint.Y); if (FActiveEvent <> nil) then + // yes: edit the event SpawnEventEditDialog(False) else begin + // no: add a new event dt := GetDateTimeAtCoord(FMouseDownPoint.X); if dt <> NO_DATE then begin - startTime := trunc(dt); + startTime := DatePart(dt); endTime := startTime + 1.0; ActiveEvent := Datastore.Resource.Schedule.AddEvent( Datastore.GetNextID(EventsTableName), @@ -645,33 +652,6 @@ begin end; end; FMouseDownPoint := Point(0, 0); - - (* - - if (Msg.XPos > dvRowHeadWidth - 9) and (Msg.YPos > dvColHeadHeight) then - begin - { The mouse click landed inside the client area } - dvSetActiveRowByCoord(Point(Msg.XPos, Msg.YPos), True); - { See if we hit an active event } - if (FActiveEvent <> nil) and (not ReadOnly) then begin - { edit this event } - dvSpawnEventEditDialog(False); - end else if not ReadOnly then begin - if not CheckCreateResource then - Exit; - if (DataStore = nil) or (DataStore.Resource = nil) then - Exit; - { otherwise, we must want to create a new event } - StartTime := trunc(FDisplayDate + ActiveCol) - + dvLineMatrix[ActiveCol, ActiveRow].Time; - EndTime := StartTime + dvTimeIncSize * FRowLinesStep; - FActiveEvent := DataStore.Resource.Schedule.AddEvent( - DataStore.GetNextID(EventsTableName), StartTime, EndTime); - { edit this new event } - dvSpawnEventEditDialog(True); - end; - end; - *) end; procedure TVpGanttView.DeleteActiveEvent(Prompt: Boolean); @@ -786,7 +766,7 @@ end; function TVpGanttView.GetDateOfCol(ACol: Integer): TDateTime; begin - Result := FStartDate + ACol; + Result := FRealStartDate + ACol; end; function TVpGanttView.GetDateTimeAtCoord(X: Integer): TDateTime; @@ -795,7 +775,7 @@ var begin days := (X - FixedColWidth) / FColWidth + FLeftCol; if (days >= 0) and (days < NumDays) then - Result := FStartDate + days + Result := FRealStartDate + days else Result := NO_DATE; end; @@ -827,7 +807,7 @@ begin Result := eventRec.Event; if Result.AllDayEvent then begin - if (dt < trunc(Result.StartTime)) or (dt > trunc(Result.EndTime) + 1) then + if (dt < DatePart(Result.StartTime)) or (dt > DatePart(Result.EndTime) + 1) then Result := nil; end else if (dt < Result.StartTime) or (dt > Result.EndTime) then @@ -837,8 +817,8 @@ end; { Determines the date when the earliest event starts, and the date when the latest event ends. - Stores them in the internal variables FStartdate and FEndDate. } -procedure TVpGanttView.GetEventDateRange; + Stores them in the internal variables FFirstDate and FLastDate. } +procedure TVpGanttView.GetEventDateRange(out AFirstDate, ALastDate: TDate); var i: Integer; event: TVpEvent; @@ -851,20 +831,15 @@ begin end else begin event := Datastore.Resource.Schedule.GetEvent(0); - FFirstDate := trunc(event.StartTime); + FFirstDate := DatePart(event.StartTime); FLastDate := -99999; for i := 0 to Datastore.Resource.Schedule.EventCount-1 do begin event := Datastore.Resource.Schedule.GetEvent(i); - d := trunc(event.EndTime); + d := DatePart(event.EndTime); if d > FLastDate then FLastDate := d; end; end; - - if FStartDate = 0 then - FStartDate := FFirstDate; - if FEndDate = 0 then - FEndDate := FLastDate; end; function TVpGanttView.GetEventOfRow(ARow: Integer): TVpEvent; @@ -882,12 +857,12 @@ begin Result := FMonthRecords[AIndex]; end; -{ Determines the number days between the first and last Gantt event. This is +{ Determines the number of days between the first and last Gantt event. This is the number of day columns in the view. } function TVpGanttView.GetNumDays: Integer; begin - if (FStartDate <> NO_DATE) then - Result := trunc(FEndDate) - trunc(FStartDate) + 1 + if (FRealStartDate <> NO_DATE) then + Result := trunc(FRealEndDate) - trunc(FRealStartDate) + 1 else Result := 0; end; @@ -895,10 +870,7 @@ end; { Determines the number of events (= rows) to be displayed in the GanttView. } function TVpGanttView.GetNumEvents: Integer; begin - if (Datastore <> nil) and (Datastore.Resource <> nil) then - Result := Datastore.Resource.Schedule.EventCount - else - Result := 0; + Result := Length(FEventRecords); end; { Determines the number of months (complete or partial) between the first and @@ -908,10 +880,10 @@ var y1, m1, d1: Word; y2, m2, d2: Word; begin - if (FStartDate <> NO_DATE) then + if (FRealStartDate <> NO_DATE) then begin - DecodeDate(FStartDate, y1, m1, d1); - DecodeDate(FEndDate, y2, m2, d2); + DecodeDate(FRealStartDate, y1, m1, d1); + DecodeDate(FRealEndDate, y2, m2, d2); if (y1 = y2) then Result := m2 - m1 + 1 else @@ -920,6 +892,15 @@ begin Result := 0; end; +{ Calculates the first and last date to be displayed in the GanttView. This + is necessary because the properties StartDate and EndDate may be 0 which + means the very first/last event. } +procedure TVpGanttView.GetRealEventDateRange(out AStartDate, AEndDate: TDate); +begin + if FStartDate > 0 then AStartDate := DatePart(FStartDate) else AStartDate := FFirstDate; + if FEndDate > 0 then AEndDate := DatePart(FEndDate) else AEndDate := FLastDate; +end; + function TVpGanttView.GetRowAtCoord(Y: Integer): Integer; begin Result := (Y - FTotalColHeaderHeight) div FRowHeight + FTopRow; @@ -958,7 +939,8 @@ begin CalcRowHeight; CalcColHeaderHeight; - GetEventDateRange; + GetEventDateRange(FFirstDate, FLastDate); + GetRealEventDateRange(FRealStartDate, FRealEndDate); FColCount := GetNumDays; FRowCount := GetNumEvents; @@ -966,16 +948,18 @@ begin PopulateMonthRecords; PopulateEventRecords; end; - -function TVpGanttView.IsEventOnDate(AEvent: TVpEvent; ADate: TDateTime): Boolean; +{ Checks whether the specified date belongs to the specified event. + The function returns true if the event begins before or at the date and ends + at or after it. } +function TVpGanttView.IsEventOnDate(AEvent: TVpEvent; ADate: TDate): Boolean; var - tEv1, tEv2: TDateTime; + dEv1, dEv2: TDate; begin if AEvent <> nil then begin - tEv1 := trunc(AEvent.StartTime); - tEv2 := trunc(AEvent.EndTime); - Result := (tEv1 <= ADate) and (tEv2 >= ADate); + dEv1 := DatePart(AEvent.StartTime); + dEv2 := DatePart(AEvent.EndTime); + Result := (dEv1 <= ADate) and (ADate <= dEv2); end else Result := false; end; @@ -1188,7 +1172,7 @@ begin Rect(0, 0, Width, Height), // Paint Rectangle ra0, // Rotation angle: none 1, // Scale - FStartDate, // Date + FActiveDate, // Date -1, // Start At -1, // End At gr30Min, // Granularity @@ -1219,7 +1203,7 @@ begin begin x2 := x1 + ColWidth; FDayRecords[i].Rect := Rect(x1, y1, x2, y2); - FDayRecords[i].Date := FStartDate + i; + FDayRecords[i].Date := FRealStartDate + i; x1 := x2; end; end; @@ -1233,37 +1217,54 @@ var totalWidth: Integer; list: TFPList; begin - SetLength(FEventRecords, GetNumEvents); if (Datastore = nil) or (DataStore.Resource = nil) then exit; list := TFPList.Create; try - // Sort events by date/time + // Consider only events which are, fully or partly, inside the + // displayed date range between FRealStartDate and FRealEndDate for i := 0 to Datastore.Resource.Schedule.EventCount-1 do - list.Add(Datastore.Resource.Schedule.GetEvent(i)); + begin + event := Datastore.Resource.Schedule.GetEvent(i); + if DatePart(event.EndTime) < FRealStartDate then + continue; + if DatePart(event.StartTime) > FRealEndDate then + continue; + list.Add(event); + end; + + // Sort events by date/time - this is a general requirement for Gantt list.Sort(@CompareEvents); + // Prepare array for the event records simplifying work for the Gantt view + SetLength(FEventRecords, list.Count); + + // Iterate over all considered events, fill the event record and store it + // in the array xh1 := 0; xh2 := FixedColWidth; y1 := FTotalColHeaderHeight; totalWidth := GetNumDays * ColWidth; - for i := 0 to High(FEventRecords) do + for i := 0 to list.Count-1 do begin event := TVpEvent(list[i]); + + // The time range of events reaching out of the displayed date range + // must be clipped at the edges. + t1 := IfThen(event.StartTime >= FRealStartDate, event.StartTime, FRealStartDate); + t2 := IfThen(event.EndTime <= FRealEndDate + 1, event.EndTime, FRealEndDate + 1); if event.AllDayEvent then begin - t1 := trunc(event.StartTime); - t2 := trunc(event.EndTime) + 1; - if frac(event.EndTime) = 0 then t2 := t2 + 1; - end else - begin - t1 := event.StartTime; - t2 := event.EndTime; + t1 := DatePart(t1); + t2 := DatePart(t2) + 1; + if TimePart(t2) = 0 then t2 := t2 + 1; end; + + // Store event, caption and its rectangle coordinates in the EventRec y2 := y1 + FRowHeight; - xe1 := round((t1 - FStartDate) / numDays * totalWidth) + FixedColWidth; - xe2 := round((t2 - FStartDate) / numDays * totalWidth) + FixedColWidth; + xe1 := round((t1 - FRealStartDate) / numDays * totalWidth) + FixedColWidth; + xe2 := round((t2 - FRealStartDate) / numDays * totalWidth) + FixedColWidth; if xe1 = xe2 then xe2 := xe1 + 1; FEventRecords[i].Event := event; FEventRecords[i].Caption := event.Description; @@ -1297,7 +1298,8 @@ begin if n > 1 then begin - dm := FStartDate; + // Date interval crosses one or more month boundaries + dm := FRealStartDate; for i := 0 to n - 1 do begin if i = 0 then begin @@ -1305,11 +1307,11 @@ begin dm := StartOfTheMonth(dm); end else if i = n-1 then - nDays := DayOf(FEndDate) + nDays := DayOf(FRealEndDate) else nDays := DaysInMonth(dm); - if dm + nDays > FEndDate then - nDays := trunc(FEndDate) - trunc(dm) + 1; + if dm + nDays > FRealEndDate then + nDays := trunc(FRealEndDate) - trunc(dm) + 1; x2 := x1 + nDays * ColWidth; FMonthRecords[i].Rect := Rect(x1, y1, x2, y2); FMonthRecords[i].Date := dm; @@ -1318,10 +1320,11 @@ begin end; end else begin - nDays := DayOf(FEndDate) - DayOf(FStartDate) + 1; + // Date interval is within the same month + nDays := DayOf(FRealEndDate) - DayOf(FRealStartDate) + 1; x2 := x1 + nDays * ColWidth; FMonthRecords[0].Rect := Rect(x1, y1, x2, y2); - FMonthRecords[0].Date := FStartDate; + FMonthRecords[0].Date := FRealStartDate; end; end; @@ -1342,6 +1345,20 @@ begin end; end; +procedure TVpGanttView.SetDateLimits(AStartDate, AEndDate: TDateTime); +var + oldDate: TDateTime; +begin + oldDate := FActiveDate; + + FStartDate := AStartDate; + FEndDate := AEndDate; + Init; + + FActiveDate := 0; + SetActiveDate(oldDate); +end; + {$IF VP_LCL_SCALING = 2} procedure TVpGanttView.FixDesignFontsPPI(const ADesignTimePPI: Integer); begin @@ -1371,26 +1388,26 @@ end; procedure TVpGanttView.ScrollDateIntoView(ADate: TDateTime); begin - if (FStartDate = 0) or (FStartDate = NO_DATE) then + if (FRealStartDate = 0) or (FRealStartDate = NO_DATE) then exit; - if ADate < FStartDate then + if ADate < FRealStartDate then begin - FStartDate := trunc(ADate); + FRealStartDate := DatePart(ADate); FColCount := GetNumDays; SetLeftCol(-MaxInt); end else - if ADate > FEndDate then + if ADate > FRealEndDate then begin - FEndDate := trunc(ADate); + FRealEndDate := DatePart(ADate); FColCount := GetNumDays; SetLeftCol(FColCount - 1 - FVisibleCols); end else - if ADate < FStartDate + FLeftCol then - SetLeftCol(trunc(ADate) - trunc(FStartDate)) + if ADate < FRealStartDate + FLeftCol then + SetLeftCol(trunc(ADate) - trunc(FRealStartDate)) else - if ADate > FStartDate + VisibleCols then - SetLeftCol(trunc(ADate) - VisibleCols) + if ADate > FRealStartDate + FVisibleCols then + SetLeftCol(trunc(ADate) - FVisibleCols) else exit; Invalidate; @@ -1446,14 +1463,14 @@ end; procedure TVpGanttView.SetActiveDate(AValue: TDateTime); begin - if FActiveDate <> trunc(AValue) then begin - FActiveDate := trunc(AValue); + if FActiveDate <> DatePart(AValue) then begin + FActiveDate := DatePart(AValue); if FLoaded then Populate; ScrollDateIntoView(FActiveDate); - FActiveCol := trunc(FActiveDate) - trunc(FStartDate); + FActiveCol := trunc(FActiveDate) - trunc(FRealStartDate); Invalidate; if (not FInLinkHandler) and (ControlLink <> nil) then @@ -1871,7 +1888,7 @@ begin Exit; // Create the new event as an all-day event for the clicked day. - startTime := trunc(FActiveDate); + startTime := DatePart(FActiveDate); endTime := startTime + 1 - OneMilliSecond; FActiveEvent := DataStore.Resource.Schedule.AddEvent( DataStore.GetNextID(EventsTableName), diff --git a/components/tvplanit/source/vpganttviewpainter.pas b/components/tvplanit/source/vpganttviewpainter.pas index 7b381a451..507bccbd0 100644 --- a/components/tvplanit/source/vpganttviewpainter.pas +++ b/components/tvplanit/source/vpganttviewpainter.pas @@ -333,6 +333,10 @@ begin begin eventRec := FGanttView.EventRecords[i]; event := eventRec.Event; + if event.EndTime < FGanttView.FirstDate then + Continue; + if event.StartTime > FGanttView.LastDate then + exit; R := ScaleRect(eventRec.EventRect); OffsetRect(R, -dx, -dy); inc(R.Top, top_margin); @@ -409,18 +413,17 @@ begin eventRec := FGanttView.EventRecords[numEvents-1]; R := ScaleRect(eventRec.EventRect); y2 := R.Bottom - dy; - end else - y2 := RealBottom; - n := FGanttView.NumDays; - for i := 0 to n-1 do - begin - dayRec := FGanttView.DayRecords[i]; - R := ScaleRect(dayRec.Rect); - x1 := R.Right - dx; - if x1 >= FScaledFixedColWidth then + n := FGanttView.NumDays; + for i := 0 to n-1 do begin - TPSMoveTo(RenderCanvas, Angle, RenderIn, x1, y1); - TPSLineTo(RenderCanvas, Angle, RenderIn, x1, y2) + dayRec := FGanttView.DayRecords[i]; + R := ScaleRect(dayRec.Rect); + x1 := R.Right - dx; + if x1 >= FScaledFixedColWidth then + begin + TPSMoveTo(RenderCanvas, Angle, RenderIn, x1, y1); + TPSLineTo(RenderCanvas, Angle, RenderIn, x1, y2) + end; end; end; end; @@ -507,7 +510,7 @@ var begin with FGanttView do begin - if (StartDate = NO_DATE) or (SpecialDayMode <> sdmColumn) then + if (RealStartDate = NO_DATE) or (SpecialDayMode <> sdmColumn) then exit; nEvents := NumEvents; @@ -515,9 +518,13 @@ begin dx := LeftCol * FScaledColWidth; dy := TopRow * FScaledRowHeight; - R := ScaleRect(EventRecords[nEvents-1].HeadRect); y1 := RealTop + FScaledTotalColHeaderHeight; - y2 := R.Bottom - dy; + if nEvents > 0 then + begin + R := ScaleRect(EventRecords[nEvents-1].HeadRect); + y2 := R.Bottom - dy; + end else + y2 := y1; RenderCanvas.Brush.style := bsSolid; for i := 0 to nDays-1 do diff --git a/components/tvplanit/source/vpmisc.pas b/components/tvplanit/source/vpmisc.pas index 62e6b1e68..d16a5e525 100644 --- a/components/tvplanit/source/vpmisc.pas +++ b/components/tvplanit/source/vpmisc.pas @@ -146,6 +146,9 @@ function DaysInAMonth(Year, Month: Integer): Integer; {-return the number of days in the specified month of a given year} {$ENDIF}{$ENDIF} +function DatePart(ADateTime: TDateTime): TDateTime; inline; +function TimePart(ADateTime: TDateTime): TDateTime; inline; + function GetJulianDate(Date: TDateTime): Word; function GetWeekOfYear(ADate: TDateTime): byte; function IsWeekend(ADate: TDateTime): Boolean; @@ -710,6 +713,16 @@ begin end; {$ENDIF}{$ENDIF} +function DatePart(ADateTime: TDateTime): TDateTime; +begin + Result := trunc(ADateTime); +end; + +function TimePart(ADateTime: TDateTime): TDateTime; +begin + Result := frac(ADateTime); +end; + function GetJulianDate(Date: TDateTime): Word; var y, m, d, I: word;