#860: CalendarFilter.kt

projectforge-business/src/main/kotlin/org/projectforge/business/calendar/CalendarFilter.kt Type: Kotlin class (data model)
Purpose: Persistable user calendar filter with visibility, timesheet, and vacation settings
Source path: projectforge-business/src/main/kotlin/org/projectforge/business/calendar/CalendarFilter.kt
Extends: AbstractFavorite (which provides name: String? and id: Long?) 252 lines · 135 code · 92 comments · 25 blank

CalendarFilter is the core user-facing configuration object for the ProjectForge calendar. It extends the favorite system, allowing users to create named filter profiles, switch between them, and persist their preferences. Each filter controls which calendars are displayed, which are hidden, what default calendar new entries are assigned to, timesheet visibility (for a specific user), vacation entries (by group or user), break highlighting, and display parameters such as grid size and the first visible hour of the day. The class also includes deep-copy, serialization clean-up, and legacy migration support (from the old Wicket-based calendar of ProjectForge v6).

Constructor Parameters

ParameterTypeDefaultDescription
name String? null Human-readable name of the filter (inherited from AbstractFavorite).
id Long? null Database ID of the filter (inherited from AbstractFavorite).
defaultCalendarId Long? null New calendar entries will be assumed to belong to this calendar. If null, the timesheet creation page is instantiated instead.
gridSize Int 15 Grid step size in minutes. Must evenly divide 60.
firstHour Int 8 First hour of the day shown in week and day time grids (e.g., 8 for 08:00).
otherTimesheetUsersEnabled Boolean false Whether timesheets of other users can be shown.
timesheetUserId Long? null If set, display time sheets only for this user. If null, no timesheets are shown.
vacationGroupIds Set<Long?>? null Vacations of employees in these groups will be displayed.
vacationUserIds Set<Long?>? null Vacations of these specific employees (by user ID) will be displayed.
showBreaks Boolean? null If true, gaps between time sheets within a day are highlighted, and clicking a gap allows creating a new time sheet in that window.
showPlanning Boolean? null Not yet supported. Reserved for future planning features.

Key Properties

calendarIds: MutableSet<Long?>

All calendar IDs managed by this filter (both visible and hidden). This is the master set of calendars the filter knows about.

invisibleCalendars: MutableSet<Long?>

Subset of calendarIds that are currently hidden. Calendars in calendarIds but not in this set are visible.

Public Methods

copyFrom(src: CalendarFilter): CalendarFilter

Performs a deep copy of all fields from src into this, including copying all collection contents. Returns this for fluent chaining. The calendarIds and invisibleCalendars sets are re-allocated and populated with the source values.

addCalendarId(calendarId: Long)

Adds a calendar to the filter and ensures it is visible (removes it from invisibleCalendars if present).

removeCalendarId(calendarId: Long)

Removes a calendar from both calendarIds and invisibleCalendars.

setVisibility(calendarId: Long?, visible: Boolean)

Toggles visibility for a given calendar. If calendarId is null (possible during deserialization), the call is silently ignored. Calls tidyUp() after mutation.

isInvisible(calendarId: Long?): Boolean

Returns true if the calendar is either null or present in invisibleCalendars.

tidyUp()

Removes entries from invisibleCalendars that are not present in the main calendarIds set. Keeps the two sets in sync.

afterDeserialization()

Post-deserialization clean-up. Handles the case where calendarIds or invisibleCalendars might be null after deserialization, and strips any spurious null values from the sets. Calls tidyUp() at the end.

isModified(other: CalendarFilter): Boolean

Compares all fields (name, id, defaultCalendarId, timesheetUserId, gridSize, firstHour, showBreaks, showPlanning, plus all collection contents) against another filter to determine if any value differs. Uses a helper isModified(col1, col2) for collection comparison (checking mutual containment of non-null values).

Private Helpers

copySet(srcSet: Set<Long?>?): Set<Long>?

Copies a nullable set, filtering out null entries. Returns null if the source is null.

isModified(col1, col2)

Compares two nullable collections by checking that each non-null element of col1 exists in col2 and vice versa.

Companion Object — Legacy Support

copyFrom(templateEntry: TemplateEntry?): CalendarFilter

Factory method that converts a legacy TemplateEntry (from the old Wicket calendar system, ProjectForge ≤v6) into a new CalendarFilter. Maps properties including calendar IDs, visibility settings, timesheet settings, and break/planning flags. If the old template had showTimesheets enabled, the timesheet user is set to the currently logged-in user.

Git History

Design Rationale

CalendarFilter is a data-rich model class rather than a traditional DTO. It carries business logic for maintaining consistency between its two related sets (calendarIds and invisibleCalendars) through methods like tidyUp() and afterDeserialization(). The @Suppress("SENSELESS_COMPARISON") annotations acknowledge that nullable Long? values in the sets are technically possible after XML/JSON deserialization, and the class defensively cleans them up. By extending AbstractFavorite, it inherits the named-favorites infrastructure (name + id), allowing users to create, name, and switch between multiple filter configurations — a key UX feature of the calendar. The deep-copy via copyFrom() (rather than a clone() override or data class) gives callers explicit control over when copies occur, avoiding unintended shared mutable state. The legacy copyFrom(TemplateEntry) in the companion object encapsulates all migration logic from the old Wicket calendar, keeping the main class clean while preserving backward compatibility for existing user preferences.