Repository: dlemmermann/CalendarFX Branch: master Commit: 4ce59a8e4ee1 Files: 347 Total size: 2.4 MB Directory structure: gitextract_uppttoi3/ ├── .github/ │ └── workflows/ │ ├── build.yml │ ├── codeql.yml │ └── release.yml ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CHANGELOG.md ├── CHANGES.txt ├── CODE_OF_CONDUCT.md ├── CalendarFXApp/ │ ├── .gitignore │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ ├── com/ │ │ └── calendarfx/ │ │ └── app/ │ │ ├── CalendarApp.java │ │ ├── CalendarAppAtlantaFX.java │ │ ├── CalendarAppLauncher.java │ │ └── MonthViewApp.java │ └── module-info.java ├── CalendarFXGoogle/ │ ├── .gitignore │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── com/ │ │ │ │ └── calendarfx/ │ │ │ │ └── google/ │ │ │ │ ├── GoogleCalendarApp.java │ │ │ │ ├── converter/ │ │ │ │ │ ├── BeanConverter.java │ │ │ │ │ ├── BidirectionalBeanConverter.java │ │ │ │ │ ├── CalendarListEntryToGoogleCalendarConverter.java │ │ │ │ │ ├── EventToGoogleEntryConverter.java │ │ │ │ │ ├── GoogleCalendarToCalendarConverter.java │ │ │ │ │ ├── GoogleCalendarToCalendarListEntryConverter.java │ │ │ │ │ └── GoogleEntryToEventConverter.java │ │ │ │ ├── model/ │ │ │ │ │ ├── GoogleAccount.java │ │ │ │ │ ├── GoogleCalendar.java │ │ │ │ │ ├── GoogleCalendarEvent.java │ │ │ │ │ ├── GoogleEntry.java │ │ │ │ │ ├── GoogleEntryReminder.java │ │ │ │ │ └── IGoogleCalendarSearchTextProvider.java │ │ │ │ ├── service/ │ │ │ │ │ ├── BeanConverterService.java │ │ │ │ │ ├── GoogleCalendarService.java │ │ │ │ │ ├── GoogleConnector.java │ │ │ │ │ ├── GoogleGeocoderService.java │ │ │ │ │ └── SecurityService.java │ │ │ │ └── view/ │ │ │ │ ├── GoogleCalendarAppView.java │ │ │ │ ├── data/ │ │ │ │ │ ├── GoogleCalendarData.java │ │ │ │ │ ├── IGoogleCalendarDataProvider.java │ │ │ │ │ └── Slice.java │ │ │ │ ├── log/ │ │ │ │ │ ├── ActionType.java │ │ │ │ │ ├── LogItem.java │ │ │ │ │ ├── LogPane.java │ │ │ │ │ └── StatusType.java │ │ │ │ ├── popover/ │ │ │ │ │ ├── GoogleEntryAttendeesView.java │ │ │ │ │ ├── GoogleEntryDetailsView.java │ │ │ │ │ ├── GoogleEntryGMapsFXView.java │ │ │ │ │ └── GoogleEntryPopOverContentPane.java │ │ │ │ ├── task/ │ │ │ │ │ ├── DeleteEntryTask.java │ │ │ │ │ ├── GoogleTask.java │ │ │ │ │ ├── InsertCalendarTask.java │ │ │ │ │ ├── InsertEntryTask.java │ │ │ │ │ ├── LoadAllCalendarsTask.java │ │ │ │ │ ├── LoadEntriesBySliceTask.java │ │ │ │ │ ├── LoadEntriesByTextTask.java │ │ │ │ │ ├── MoveEntryTask.java │ │ │ │ │ ├── RefreshCalendarsTask.java │ │ │ │ │ └── UpdateEntryTask.java │ │ │ │ └── thread/ │ │ │ │ ├── CalendarViewTimeUpdateThread.java │ │ │ │ ├── GoogleAutoRefreshThread.java │ │ │ │ ├── GoogleNotificationPopupThread.java │ │ │ │ └── GoogleTaskExecutor.java │ │ │ ├── impl/ │ │ │ │ └── com/ │ │ │ │ └── calendarfx/ │ │ │ │ └── google/ │ │ │ │ └── view/ │ │ │ │ ├── GoogleCalendarAppViewSkin.java │ │ │ │ ├── GoogleCalendarCreateView.java │ │ │ │ ├── GoogleCalendarDataManager.java │ │ │ │ ├── GoogleCalendarSearchTextManager.java │ │ │ │ ├── GoogleSyncManager.java │ │ │ │ └── log/ │ │ │ │ └── LogPaneSkin.java │ │ │ └── module-info.java │ │ └── resources/ │ │ └── com/ │ │ └── calendarfx/ │ │ └── google/ │ │ ├── service/ │ │ │ ├── StoredCredential │ │ │ └── client-secrets.json │ │ └── view/ │ │ └── popover/ │ │ └── google-popover.css │ └── test/ │ └── java/ │ └── com/ │ └── calendarfx/ │ └── google/ │ └── view/ │ └── popover/ │ └── HelloGoogleEntryPopOverContentPane.java ├── CalendarFXResourceApp/ │ ├── .gitignore │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ ├── com/ │ │ └── calendarfx/ │ │ └── resource/ │ │ └── app/ │ │ ├── ResourceCalendarApp.java │ │ └── ResourceCalendarAppLauncher.java │ └── module-info.java ├── CalendarFXSampler/ │ ├── .gitignore │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ ├── com/ │ │ │ └── calendarfx/ │ │ │ └── demo/ │ │ │ ├── CalendarFXDateControlSample.java │ │ │ ├── CalendarFXSample.java │ │ │ ├── CalendarFXSampler.java │ │ │ ├── CalendarFXSamplerProject.java │ │ │ ├── CalendarFXSamplerWelcome.java │ │ │ ├── entries/ │ │ │ │ ├── HelloAllDayEntryView.java │ │ │ │ ├── HelloDayEntryView.java │ │ │ │ ├── HelloEntryViewBase.java │ │ │ │ └── HelloMonthEntryView.java │ │ │ ├── pages/ │ │ │ │ ├── HelloDayPage.java │ │ │ │ ├── HelloMonthPage.java │ │ │ │ ├── HelloWeekPage.java │ │ │ │ └── HelloYearPage.java │ │ │ ├── performance/ │ │ │ │ └── HelloPerformance.java │ │ │ ├── popover/ │ │ │ │ ├── HelloEntryDetailsView.java │ │ │ │ ├── HelloEntryHeaderView.java │ │ │ │ └── HelloPopOverContentPane.java │ │ │ ├── print/ │ │ │ │ ├── HelloOptionsView.java │ │ │ │ ├── HelloPaperView.java │ │ │ │ ├── HelloPreviewPane.java │ │ │ │ ├── HelloPrintView.java │ │ │ │ ├── HelloSettingsView.java │ │ │ │ ├── HelloTimeRangeField.java │ │ │ │ └── HelloTimeRangeView.java │ │ │ └── views/ │ │ │ ├── HelloAgendaView.java │ │ │ ├── HelloAllDayView.java │ │ │ ├── HelloAvailabilityCalendar.java │ │ │ ├── HelloCalendar.java │ │ │ ├── HelloCalendarHeaderView.java │ │ │ ├── HelloCalendarSelector.java │ │ │ ├── HelloCalendarView.java │ │ │ ├── HelloDayView.java │ │ │ ├── HelloDetailedDayView.java │ │ │ ├── HelloDetailedWeekView.java │ │ │ ├── HelloMonthSheetView.java │ │ │ ├── HelloMonthView.java │ │ │ ├── HelloRecurrenceView.java │ │ │ ├── HelloResourcesCalendarView.java │ │ │ ├── HelloScrollingDayView.java │ │ │ ├── HelloScrollingTimeScaleView.java │ │ │ ├── HelloSourceGridView.java │ │ │ ├── HelloSourceView.java │ │ │ ├── HelloTimeField.java │ │ │ ├── HelloTimeScaleView.java │ │ │ ├── HelloTimezones.java │ │ │ ├── HelloTopLayer.java │ │ │ ├── HelloVisualBounds.java │ │ │ ├── HelloWeekDayHeaderView.java │ │ │ ├── HelloWeekDayView.java │ │ │ ├── HelloWeekFieldsView.java │ │ │ ├── HelloWeekTimeScaleView.java │ │ │ ├── HelloWeekView.java │ │ │ ├── HelloYearMonthView.java │ │ │ ├── HelloYearView.java │ │ │ └── resources/ │ │ │ └── HelloResourcesView.java │ │ └── module-info.java │ └── resources/ │ └── META-INF/ │ └── services/ │ └── fxsampler.FXSamplerProject ├── CalendarFXSchedulerApp/ │ ├── .gitignore │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ ├── com/ │ │ └── calendarfx/ │ │ └── scheduler/ │ │ ├── SchedulerApp.java │ │ └── SchedulerAppLauncher.java │ └── module-info.java ├── CalendarFXView/ │ ├── .gitignore │ ├── logging.properties │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── asciidoc/ │ │ │ └── manual.adoc │ │ ├── java/ │ │ │ ├── com/ │ │ │ │ └── calendarfx/ │ │ │ │ ├── model/ │ │ │ │ │ ├── Calendar.java │ │ │ │ │ ├── CalendarEvent.java │ │ │ │ │ ├── CalendarSource.java │ │ │ │ │ ├── Entry.java │ │ │ │ │ ├── Interval.java │ │ │ │ │ ├── IntervalTree.java │ │ │ │ │ ├── LoadEvent.java │ │ │ │ │ ├── Marker.java │ │ │ │ │ ├── Resource.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── util/ │ │ │ │ │ ├── CalendarFX.java │ │ │ │ │ ├── LoggingDomain.java │ │ │ │ │ ├── LoggingFormatter.java │ │ │ │ │ ├── ViewHelper.java │ │ │ │ │ ├── WeakList.java │ │ │ │ │ └── package-info.java │ │ │ │ └── view/ │ │ │ │ ├── AgendaView.java │ │ │ │ ├── AllDayEntryView.java │ │ │ │ ├── AllDayView.java │ │ │ │ ├── ButtonBar.java │ │ │ │ ├── CalendarFXControl.java │ │ │ │ ├── CalendarHeaderView.java │ │ │ │ ├── CalendarSelector.java │ │ │ │ ├── CalendarView.java │ │ │ │ ├── ContextMenuProvider.java │ │ │ │ ├── CreateAndDeleteHandler.java │ │ │ │ ├── DateControl.java │ │ │ │ ├── DateSelectionModel.java │ │ │ │ ├── DayEntryView.java │ │ │ │ ├── DayView.java │ │ │ │ ├── DayViewBase.java │ │ │ │ ├── DeleteHandler.java │ │ │ │ ├── DetailedDayView.java │ │ │ │ ├── DetailedWeekView.java │ │ │ │ ├── DeveloperConsole.java │ │ │ │ ├── DraggedEntry.java │ │ │ │ ├── EntryViewBase.java │ │ │ │ ├── Messages.java │ │ │ │ ├── MonthEntryView.java │ │ │ │ ├── MonthSheetView.java │ │ │ │ ├── MonthView.java │ │ │ │ ├── MonthViewBase.java │ │ │ │ ├── RecurrenceView.java │ │ │ │ ├── RequestEvent.java │ │ │ │ ├── ResourceCalendarView.java │ │ │ │ ├── ResourcesView.java │ │ │ │ ├── SearchResultView.java │ │ │ │ ├── SourceGridView.java │ │ │ │ ├── SourceView.java │ │ │ │ ├── TimeField.java │ │ │ │ ├── TimeScaleView.java │ │ │ │ ├── VirtualGrid.java │ │ │ │ ├── WeekDayHeaderView.java │ │ │ │ ├── WeekDayView.java │ │ │ │ ├── WeekFieldsView.java │ │ │ │ ├── WeekTimeScaleView.java │ │ │ │ ├── WeekView.java │ │ │ │ ├── YearMonthView.java │ │ │ │ ├── YearView.java │ │ │ │ ├── ZonedDateTimeProvider.java │ │ │ │ ├── package-info.java │ │ │ │ ├── page/ │ │ │ │ │ ├── DayPage.java │ │ │ │ │ ├── MonthPage.java │ │ │ │ │ ├── PageBase.java │ │ │ │ │ ├── WeekPage.java │ │ │ │ │ ├── YearPage.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── popover/ │ │ │ │ │ ├── DatePopOver.java │ │ │ │ │ ├── EntriesPane.java │ │ │ │ │ ├── EntryDetailsView.java │ │ │ │ │ ├── EntryHeaderView.java │ │ │ │ │ ├── EntryMapView.java │ │ │ │ │ ├── EntryPopOverContentPane.java │ │ │ │ │ ├── EntryPopOverPane.java │ │ │ │ │ ├── EntryPropertiesView.java │ │ │ │ │ ├── PopOverContentPane.java │ │ │ │ │ ├── PopOverTitledPane.java │ │ │ │ │ ├── RecurrencePopup.java │ │ │ │ │ ├── ZoneIdComparator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── print/ │ │ │ │ ├── OptionsView.java │ │ │ │ ├── PaperView.java │ │ │ │ ├── PreviewPane.java │ │ │ │ ├── PrintView.java │ │ │ │ ├── PrintablePage.java │ │ │ │ ├── SettingsView.java │ │ │ │ ├── TimeRangeField.java │ │ │ │ ├── TimeRangeView.java │ │ │ │ ├── ViewType.java │ │ │ │ ├── ViewTypeControl.java │ │ │ │ ├── ZoomPane.java │ │ │ │ └── package-info.java │ │ │ ├── impl/ │ │ │ │ └── com/ │ │ │ │ └── calendarfx/ │ │ │ │ └── view/ │ │ │ │ ├── AgendaViewSkin.java │ │ │ │ ├── AllDayEntryViewSkin.java │ │ │ │ ├── AllDayViewSkin.java │ │ │ │ ├── AutoScrollPane.java │ │ │ │ ├── ButtonBarSkin.java │ │ │ │ ├── CalendarHeaderViewSkin.java │ │ │ │ ├── CalendarPropertySheet.java │ │ │ │ ├── CalendarSelectorSkin.java │ │ │ │ ├── CalendarViewSkin.java │ │ │ │ ├── DataLoader.java │ │ │ │ ├── DateControlSkin.java │ │ │ │ ├── DayEntryViewSkin.java │ │ │ │ ├── DayViewBaseSkin.java │ │ │ │ ├── DayViewEditController.java │ │ │ │ ├── DayViewScrollPane.java │ │ │ │ ├── DayViewSkin.java │ │ │ │ ├── DetailedDayViewSkin.java │ │ │ │ ├── DetailedWeekViewSkin.java │ │ │ │ ├── DeveloperConsoleSkin.java │ │ │ │ ├── LoadDataSettingsProvider.java │ │ │ │ ├── MonthEntryViewSkin.java │ │ │ │ ├── MonthSheetViewSkin.java │ │ │ │ ├── MonthViewSkin.java │ │ │ │ ├── NavigateDateView.java │ │ │ │ ├── NumericTextField.java │ │ │ │ ├── RecurrenceViewSkin.java │ │ │ │ ├── ResourceCalendarViewSkin.java │ │ │ │ ├── ResourcesViewContainer.java │ │ │ │ ├── ResourcesViewContainerSkin.java │ │ │ │ ├── ResourcesViewSkin.java │ │ │ │ ├── SearchResultViewSkin.java │ │ │ │ ├── SourceGridViewSkin.java │ │ │ │ ├── SourceViewSkin.java │ │ │ │ ├── TimeFieldSkin.java │ │ │ │ ├── TimeScaleViewSkin.java │ │ │ │ ├── WeekDayHeaderViewSkin.java │ │ │ │ ├── WeekDayViewSkin.java │ │ │ │ ├── WeekFieldsViewSkin.java │ │ │ │ ├── WeekTimeScaleViewSkin.java │ │ │ │ ├── WeekViewSkin.java │ │ │ │ ├── YearMonthViewSkin.java │ │ │ │ ├── YearViewSkin.java │ │ │ │ ├── ZoneIdStringConverter.java │ │ │ │ ├── page/ │ │ │ │ │ ├── DayPageSkin.java │ │ │ │ │ ├── MonthPageSkin.java │ │ │ │ │ ├── PageBaseSkin.java │ │ │ │ │ ├── WeekPageSkin.java │ │ │ │ │ └── YearPageSkin.java │ │ │ │ ├── popover/ │ │ │ │ │ └── RecurrencePopupSkin.java │ │ │ │ ├── print/ │ │ │ │ │ ├── OptionsViewSkin.java │ │ │ │ │ ├── PaperViewSkin.java │ │ │ │ │ ├── PreviewPaneSkin.java │ │ │ │ │ ├── PrintViewSkin.java │ │ │ │ │ ├── PrintablePageSkin.java │ │ │ │ │ ├── SettingsViewSkin.java │ │ │ │ │ ├── TimeRangeFieldSkin.java │ │ │ │ │ ├── TimeRangeViewSkin.java │ │ │ │ │ └── ZoomPaneSkin.java │ │ │ │ └── util/ │ │ │ │ ├── Placement.java │ │ │ │ ├── TimeBoundsCluster.java │ │ │ │ ├── TimeBoundsColumn.java │ │ │ │ ├── TimeBoundsResolver.java │ │ │ │ ├── Util.java │ │ │ │ ├── VisualBoundsCluster.java │ │ │ │ ├── VisualBoundsColumn.java │ │ │ │ └── VisualBoundsResolver.java │ │ │ └── module-info.java │ │ └── resources/ │ │ └── com/ │ │ └── calendarfx/ │ │ ├── util/ │ │ │ ├── public_key.properties │ │ │ └── version.properties │ │ └── view/ │ │ ├── atlantafx.css │ │ ├── calendar.css │ │ ├── messages.properties │ │ ├── messages_de.properties │ │ ├── messages_es.properties │ │ ├── messages_fr.properties │ │ ├── messages_it.properties │ │ ├── messages_pt_BR.properties │ │ └── messages_sk.properties │ └── test/ │ └── java/ │ └── com/ │ └── calendarfx/ │ ├── model/ │ │ ├── CalendarTest.java │ │ ├── EntryTest.java │ │ └── IntervalTest.java │ └── view/ │ └── DateSelectionModelTests.java ├── CalendarFXWeather/ │ ├── .gitignore │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ ├── com/ │ │ └── calendarfx/ │ │ └── weather/ │ │ ├── WeatherApp.java │ │ └── WeatherAppLauncher.java │ └── module-info.java ├── CalendarFXiCal/ │ ├── .gitignore │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ ├── com/ │ │ │ └── calendarfx/ │ │ │ └── ical/ │ │ │ ├── ICalCalendarApp.java │ │ │ ├── ICalCalendarAppLauncher.java │ │ │ ├── ICalRepository.java │ │ │ ├── model/ │ │ │ │ ├── ICalCalendar.java │ │ │ │ └── ICalCalendarEntry.java │ │ │ └── view/ │ │ │ ├── ICalWebSourceFactory.java │ │ │ └── ICalWebSourcePane.java │ │ └── module-info.java │ └── resources/ │ └── com/ │ └── calendarfx/ │ └── ical/ │ └── dialog.css ├── LICENSE ├── README.md ├── docs/ │ └── index.html ├── formatter-settings.xml ├── jreleaser.yml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── scenicView.properties └── settings.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: branches: - master-11 pull_request: types: [opened, synchronize, reopened] jobs: build: name: Build runs-on: ubuntu-latest if: startsWith(github.event.head_commit.message, '🏁 Releasing version') != true steps: - uses: actions/checkout@v2 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Set up JDK 21 uses: actions/setup-java@v4 with: java-version: 21 distribution: 'zulu' - name: Cache Maven packages uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: ./mvnw -B verify ================================================ FILE: .github/workflows/codeql.yml ================================================ name: "CodeQL" on: push: branches: [ "master-11" ] pull_request: branches: [ "master-11" ] schedule: - cron: "46 7 * * 1" jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ java ] steps: - name: Checkout uses: actions/checkout@v3 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: category: "/language:${{ matrix.language }}" ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: workflow_dispatch: inputs: version: description: "Release version" required: true jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: 'Set up Java' uses: actions/setup-java@v4 with: java-version: 21 distribution: 'zulu' server-id: central server-username: MAVEN_USERNAME server-password: MAVEN_CENTRAL_TOKEN gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} gpg-passphrase: MAVEN_GPG_PASSPHRASE - name: 'Cache Maven packages' uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - name: Update version id: version run: | VERSION=${{ github.event.inputs.version }} echo "Updating POMs to version $VERSION" ./mvnw -B versions:set versions:commit -DnewVersion=$VERSION git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --global user.name "GitHub Action" git commit -a -m "🏁 Releasing version $VERSION" git push origin HEAD:master-11 - name: Release to Maven Central env: MAVEN_USERNAME: ${{ secrets.PUBLISHER_PORTAL_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.PUBLISHER_PORTAL_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} run: | export GPG_TTY=$(tty) ./mvnw --no-transfer-progress -B --file pom.xml \ -Drepository.url=https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git \ -Dmaven.site.skip=true -Drelease=true deploy -s ./settings.xml - name: Release to GitHub env: JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: ./mvnw -B --file pom.xml -Prelease -pl :calendar jreleaser:full-release - name: JReleaser output if: always() uses: actions/upload-artifact@v4 with: name: jreleaser-logs path: | target/jreleaser/trace.log target/jreleaser/output.properties ================================================ FILE: .gitignore ================================================ /target/ .idea *.iml .project *.prefs CalendarFXApp/.classpath *.classpath *.log ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.10/apache-maven-3.9.10-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar ================================================ FILE: CHANGELOG.md ================================================ # Change Log ## [11.8.3](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.8.3) (2019-10-23) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/11.8.2...11.8.3) **Closed issues:** - Missing icons [\#57](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/57) - Adapt iCal4j recurrence [\#50](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/50) **Merged pull requests:** - Fix StringIndexOutOfBoundsException [\#63](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/63) ([saarmbruster](https://github.com/saarmbruster)) - Fix page without transition [\#61](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/61) ([saarmbruster](https://github.com/saarmbruster)) - Improve start-up performance [\#60](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/60) ([saarmbruster](https://github.com/saarmbruster)) ## [11.8.2](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.8.2) (2019-10-08) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/11.8.1...11.8.2) **Closed issues:** - Merge updates from master into master-11 [\#59](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/59) ## [11.8.1](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.8.1) (2019-10-01) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/11.8.0...11.8.1) ## [11.8.0](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.8.0) (2019-09-27) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/11.7.0...11.8.0) **Merged pull requests:** - Add missing German translation [\#58](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/58) ([saarmbruster](https://github.com/saarmbruster)) - Fix typo in private method name [\#56](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/56) ([saarmbruster](https://github.com/saarmbruster)) - Add listener to dayPageLayout [\#55](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/55) ([saarmbruster](https://github.com/saarmbruster)) - iCal4j recurrence [\#51](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/51) ([mwkroening](https://github.com/mwkroening)) ## [11.7.0](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.7.0) (2019-09-25) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/11.6.4...11.7.0) ## [11.6.4](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.6.4) (2019-09-01) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/11.6.3...11.6.4) ## [11.6.3](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.6.3) (2019-08-28) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/8.6.1...11.6.3) ## [8.6.1](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/8.6.1) (2019-08-28) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/8.6.0...8.6.1) ## [8.6.0](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/8.6.0) (2019-08-28) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/11.6.2...8.6.0) ## [11.6.2](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.6.2) (2019-08-28) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/8.5.1...11.6.2) ## [8.5.1](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/8.5.1) (2019-08-28) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/11.6.1...8.5.1) ## [11.6.1](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.6.1) (2019-08-19) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/11.6.0...11.6.1) ## [11.6.0](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/11.6.0) (2019-08-19) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/v11.5.0...11.6.0) **Implemented enhancements:** - Add Continuous Delivery to Maven Central [\#54](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/54) ([martinfrancois](https://github.com/martinfrancois)) **Closed issues:** - Using CalendarFx 11.5.0 with openJFx 12 facing problem of Module not found [\#47](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/47) - 11.5.0 Unable to change event behavior [\#46](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/46) - Save Entries into database [\#45](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/45) - Issues figuring out scene builder [\#43](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/43) - \[11.5.0\] Remove snapshot dependency on controlsfx 9.0.1-SNAPSHOT once released [\#41](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/41) - Data Save [\#40](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/40) - JavaDocs Errors [\#31](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/31) - Memory-Leak in DateControl/PopOver [\#29](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/29) - Drag and drop functionality [\#28](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/28) **Merged pull requests:** - Replace Joda-Time with java.time in CalendarFXView [\#49](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/49) ([mwkroening](https://github.com/mwkroening)) - Upgrade dependencies [\#48](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/48) ([mwkroening](https://github.com/mwkroening)) ## [v11.5.0](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/v11.5.0) (2018-09-15) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/v8.5.0...v11.5.0) **Closed issues:** - Day view multiple selection not working [\#39](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/39) - Could not find artifact com.calendarfx:google:jar:8.5.0 [\#36](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/36) - Data Save and Google [\#35](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/35) - Miscount of Seconds [\#32](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/32) - Calendar is not visible [\#22](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/22) - Create a Java 10 branch and release. [\#14](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/14) **Merged pull requests:** - Fixed issues regarding the Drag and Drop of the entry at the start and end of it [\#37](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/37) ([arias9306](https://github.com/arias9306)) - pt\_BR messages [\#34](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/34) ([GabrielZulian](https://github.com/GabrielZulian)) ## [v8.5.0](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/v8.5.0) (2018-08-08) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/v8.4.2-maven-central...v8.5.0) **Closed issues:** - Disable drag and drop Entry [\#30](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/30) - calendarfx.com [\#27](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/27) - Use CalendarFX in Scene Builder [\#25](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/25) - Tests fails when building 8.4.1 [\#17](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/17) - Publish artefacts to Maven central. [\#12](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/12) **Merged pull requests:** - Enhancement on GUI, user/development experience and bugfixing [\#33](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/33) ([arias9306](https://github.com/arias9306)) ## [v8.4.2-maven-central](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/v8.4.2-maven-central) (2018-01-24) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/v8.4.2...v8.4.2-maven-central) **Closed issues:** - Issue with maven 8.4.1 and 8.4.2 [\#21](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/21) - Maven repository failing [\#20](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/20) - Update to latest ControlsFX and remove CustomMasterDetailPane [\#13](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/13) ## [v8.4.2](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/v8.4.2) (2018-01-10) [Full Changelog](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/compare/v8.4.1...v8.4.2) **Closed issues:** - Can't create CalendarView in own Project [\#16](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/16) **Merged pull requests:** - fixes a bug and make DayEntryViewSkin more reusable [\#19](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/19) ([teymourlouie](https://github.com/teymourlouie)) - DetailedWeekView adjustToFirstDayOfWeek and Entry equation [\#18](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/18) ([imario42](https://github.com/imario42)) - Update ControlsFx version [\#15](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/15) ([sanaehirotaka](https://github.com/sanaehirotaka)) ## [v8.4.1](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/tree/v8.4.1) (2017-10-23) **Closed issues:** - Reusability of DayEntryViewSkin [\#9](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/9) **Merged pull requests:** - optimized imports to single line imports [\#11](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/11) ([imario42](https://github.com/imario42)) - reformat and copyright header. no code changes. [\#10](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/10) ([imario42](https://github.com/imario42)) - additional drag/drop enhancements [\#8](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/8) ([imario42](https://github.com/imario42)) - drag/drop issues [\#7](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/7) ([imario42](https://github.com/imario42)) - immediately update UI if just the calendar of an entry changes [\#6](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/6) ([imario42](https://github.com/imario42)) - renamed CalendarFXView to CalendarFXExperimental [\#4](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/4) ([imario42](https://github.com/imario42)) - removed invalid relative parent path [\#3](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/3) ([imario42](https://github.com/imario42)) - Create LICENSE [\#2](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/2) ([dlemmermann](https://github.com/dlemmermann)) - Create CODE\_OF\_CONDUCT.md [\#1](https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/1) ([dlemmermann](https://github.com/dlemmermann)) \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* ================================================ FILE: CHANGES.txt ================================================ ------------------------------------------------------------------------------- RELEASE NOTES, VERSION 11.12.x (October 2022) ------------------------------------------------------------------------------- *** NEW FEATURES DayView / WeekDayView / WeekView -------------------------------- It is now possible to create new entries via press and drag. It is also possible to configure whether new entries shall be created after a single or a double click. We also changed the behaviour to automatically bring up the popover for newly created entries as this is what most people would expect. This can be configured by calling DateControl.setShowDetailsUponEntryCreation. ResourcesView ------------- A new view called "ResourcesView" was added. It can be used to add allocations to one or more resources / people. A typical use case would be a personal that is working at a barber / hairdresser. The calendar entries would represent customer appointments. This view can either display "dates over resources" or "resources over dates". As part of the development of we added the "Resource" model class. This class stores a regular calendar and a special one called the "availability calendar". The availability of a resource can be edited when the view's "editAvailability" property is set to true. BackgroundCanvas (mainly private API) --------------------------------- To visualize the availability of a resource we decided to use a canvas instead of scene graph nodes. The performance is just much better and availability is usually expressed by greying out the background of a resource. For controlling the availability granularity and color please see DateControl.availabilityGrid and DateControl#availabilityColor. The background canvas also renders new light-weight grid lines. This is needed as the availability grid could lead to the creation of many more grid lines compared to the previous full- and half-hour grid lines. However, these light- weight grid lines can not be styled via CSS. See the DateControl.gridLineColor property. DayEntryView ------------ It is now possible to set a "click count" number for showing the details of an entry view. In the past the user always had to double-click on the entry. Now a single or a tripple-click can also be configured. Google Map View --------------- A new view class has been added that is capable of looking up a static map image from Google based on a given address / location string. The class is called EntryMapView and is part of the EntryDetailsView. The map view will only be visible if an address and a Google Maps API key exist. *** ENHANCEMENTS DayViewEditController --------------------- With the addition of the availability feature we had to revisit the logic for editing day entry views. The code became too messy and had to be refactored. When comparing the before and after of this class one will notice that the new code is much more structured. Calendar -------- A new "user object" property was added to the calendar so that a link can be created between the calendar and its data source or its business object. Interval -------- A new "with" method has been added to derive a new interval from an existing interval by passing in a new duration. Interval.withDuration(...). *** FIXES Recurrence ---------- Recurrence entries were not updated correctly in certain circumstances. This has been fixed. Memory Leaks ------------ Memory leaks were recently introduced after we started using the ListProperty class, e.g. for "available time zones" and for "resources". There seems to be a known issue when applications try to "unbind" from a ListProperty. This seems to fail and objects in the collection are not being collected. ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at dlemmermann@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CalendarFXApp/.gitignore ================================================ /target *.iml ================================================ FILE: CalendarFXApp/pom.xml ================================================ 4.0.0 application CalendarFXApp com.calendarfx calendar 12.0.1 ../pom.xml true io.github.mkpaz atlantafx-base 2.0.1 com.calendarfx view fr.brouillard.oss cssfx org.openjfx javafx-controls org.openjfx javafx-web org.openjfx javafx-fxml org.openjfx javafx-swing net.raumzeitfalle.fx scenic-view 11.0.2 org.openjfx * org.openjfx javafx-maven-plugin ${javafx.maven.plugin.version} com.calendarfx.app.CalendarApp ================================================ FILE: CalendarFXApp/src/main/java/com/calendarfx/app/CalendarApp.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.app; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.CalendarView; import fr.brouillard.oss.cssfx.CSSFX; import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import org.scenicview.ScenicView; import java.time.LocalDate; import java.time.LocalTime; import java.util.Objects; public class CalendarApp extends Application { @Override public void start(Stage primaryStage) { CalendarView calendarView = new CalendarView(); calendarView.setEnableTimeZoneSupport(true); Calendar katja = new Calendar("Katja"); Calendar dirk = new Calendar("Dirk"); Calendar philip = new Calendar("Philip"); Calendar jule = new Calendar("Jule"); Calendar armin = new Calendar("Armin"); Calendar birthdays = new Calendar("Birthdays"); Calendar holidays = new Calendar("Holidays"); katja.setShortName("K"); dirk.setShortName("D"); philip.setShortName("P"); jule.setShortName("J"); armin.setShortName("A"); birthdays.setShortName("B"); holidays.setShortName("H"); katja.setStyle(Style.STYLE1); dirk.setStyle(Style.STYLE2); philip.setStyle(Style.STYLE3); jule.setStyle(Style.STYLE4); armin.setStyle(Style.STYLE5); birthdays.setStyle(Style.STYLE6); holidays.setStyle(Style.STYLE7); CalendarSource familyCalendarSource = new CalendarSource("Family"); familyCalendarSource.getCalendars().addAll(birthdays, holidays, katja, dirk, philip, jule, armin); calendarView.getCalendarSources().setAll(familyCalendarSource); calendarView.setRequestedTime(LocalTime.now()); StackPane stackPane = new StackPane(); stackPane.getChildren().addAll(calendarView); // introPane); Thread updateTimeThread = new Thread("Calendar: Update Time Thread") { @Override public void run() { while (true) { Platform.runLater(() -> { calendarView.setToday(LocalDate.now()); calendarView.setTime(LocalTime.now()); }); try { // update every 10 seconds sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; updateTimeThread.setPriority(Thread.MIN_PRIORITY); updateTimeThread.setDaemon(true); updateTimeThread.start(); Scene scene = new Scene(stackPane); if (Boolean.getBoolean("atlantafx")) { scene.getStylesheets().add(Objects.requireNonNull(CalendarView.class.getResource("atlantafx.css")).toExternalForm()); } scene.focusOwnerProperty().addListener(it -> System.out.println("focus owner: " + scene.getFocusOwner())); CSSFX.start(scene); primaryStage.setTitle("Calendar"); primaryStage.setScene(scene); primaryStage.setWidth(1300); primaryStage.setHeight(1000); primaryStage.centerOnScreen(); primaryStage.show(); // ScenicView.show(scene); } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXApp/src/main/java/com/calendarfx/app/CalendarAppAtlantaFX.java ================================================ package com.calendarfx.app; import atlantafx.base.theme.NordDark; import atlantafx.base.theme.NordLight; public class CalendarAppAtlantaFX extends CalendarApp { public static void main(String[] args) { System.setProperty("atlantafx", "true"); setUserAgentStylesheet(new NordDark().getUserAgentStylesheet()); launch(args); } } ================================================ FILE: CalendarFXApp/src/main/java/com/calendarfx/app/CalendarAppLauncher.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.app; public class CalendarAppLauncher { public static void main(String[] args) { System.setProperty("calendarfx.developer", "true"); CalendarApp.main(args); } } ================================================ FILE: CalendarFXApp/src/main/java/com/calendarfx/app/MonthViewApp.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.app; import com.calendarfx.view.MonthView; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class MonthViewApp extends Application { @Override public void start(Stage primaryStage) { MonthView monthView = new MonthView(); StackPane stackPane = new StackPane(); stackPane.getChildren().addAll(monthView); // introPane); Scene scene = new Scene(stackPane); primaryStage.setTitle("Month View"); primaryStage.setScene(scene); primaryStage.sizeToScene(); primaryStage.centerOnScreen(); primaryStage.show(); } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXApp/src/main/java/module-info.java ================================================ module com.calendarfx.app { requires transitive javafx.graphics; requires fr.brouillard.oss.cssfx; requires javafx.controls; requires com.calendarfx.view; requires atlantafx.base; requires org.scenicview.scenicview; exports com.calendarfx.app; } ================================================ FILE: CalendarFXGoogle/.gitignore ================================================ /target *.iml /bin/ ================================================ FILE: CalendarFXGoogle/pom.xml ================================================ 4.0.0 CalendarFXGoogle google com.calendarfx calendar 12.0.1 ../pom.xml true org.openjfx javafx-controls org.openjfx javafx-web com.dlsc GMapsFX org.slf4j slf4j-simple com.calendarfx view com.google.api-client google-api-client com.google.api-client google-api-client-java6 com.google.api-client google-api-client-jackson2 com.google.apis google-api-services-calendar com.google.apis google-api-services-oauth2 com.google.code.geocoder-java geocoder-java org.openjfx javafx-maven-plugin ${javafx.maven.plugin.version} com.calendarfx.google.GoogleCalendarApp ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/GoogleCalendarApp.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google; import com.calendarfx.google.view.GoogleCalendarAppView; import com.calendarfx.view.CalendarView; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; import java.time.LocalDate; import java.time.LocalTime; public class GoogleCalendarApp extends Application { @Override public void start(Stage primaryStage) { CalendarView calendarView = new CalendarView(); calendarView.setToday(LocalDate.now()); calendarView.setTime(LocalTime.now()); calendarView.setShowDeveloperConsole(Boolean.getBoolean("calendarfx.developer")); GoogleCalendarAppView appView = new GoogleCalendarAppView(calendarView); appView.getStylesheets().add(CalendarView.class.getResource("calendar.css").toExternalForm()); primaryStage.setTitle("Google Calendar"); primaryStage.setScene(new Scene(appView)); primaryStage.setWidth(1400); primaryStage.setHeight(950); primaryStage.centerOnScreen(); primaryStage.show(); } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/converter/BeanConverter.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.converter; /** * Interface representing a converter from/to a single bean. * * Created by gdiaz on 20/02/2017. */ public interface BeanConverter { /** * Converts the given source into an object of type defined by the target. * * @param source The object source to be converted. * @return The target result of the convertion. */ T convert(S source); } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/converter/BidirectionalBeanConverter.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.converter; /** * Converter in both directions. * * Created by gdiaz on 28/04/2017. */ public interface BidirectionalBeanConverter extends BeanConverter { R leftToRight(L left); L rightToLeft(R right); @Override default R convert(L source) { return leftToRight(source); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/converter/CalendarListEntryToGoogleCalendarConverter.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.converter; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntryReminder; import com.google.api.services.calendar.model.CalendarListEntry; import com.google.api.services.calendar.model.EventReminder; import java.util.ArrayList; import java.util.List; /** * Converts from google api to calendarfx api. * * Created by gdiaz on 20/02/2017. */ public final class CalendarListEntryToGoogleCalendarConverter implements BeanConverter { @Override public GoogleCalendar convert(CalendarListEntry source) { GoogleCalendar calendar = new GoogleCalendar(); calendar.setId(source.getId()); calendar.setName(source.getSummary()); calendar.setShortName(source.getSummary()); calendar.setPrimary(source.isPrimary()); calendar.setReadOnly(GoogleCalendar.isReadOnlyAccessRole(source.getAccessRole())); List reminders = new ArrayList<>(); if (source.getDefaultReminders() != null) { for (EventReminder r : source.getDefaultReminders()) { reminders.add(new GoogleEntryReminder(r)); } } calendar.getDefaultReminders().setAll(reminders); return calendar; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/converter/EventToGoogleEntryConverter.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.converter; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.model.GoogleEntryReminder; import com.calendarfx.model.Interval; import com.google.api.client.util.DateTime; import com.google.api.services.calendar.model.Event; import com.google.api.services.calendar.model.EventReminder; import java.time.Instant; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.TimeZone; /** * BeanConverter between event (google api) and entry (calendarfx api). * * Created by gdiaz on 20/02/2017. */ public final class EventToGoogleEntryConverter implements BeanConverter { @Override public GoogleEntry convert(Event source) { Objects.requireNonNull(source.getStart()); Objects.requireNonNull(source.getEnd()); GoogleEntry entry = new GoogleEntry(); /* * TimeZone : Although Google allows different Start/End TimeZone, we * always assume that start/end are in the TimeZone of the startDate. */ ZoneId zoneId; String stTimeZone = source.getStart().getTimeZone(); if (stTimeZone == null) { zoneId = ZoneId.systemDefault(); } else { zoneId = TimeZone.getTimeZone(stTimeZone).toZoneId(); } // Start Time DateTime stdt = source.getStart().getDate(); if (stdt == null) { stdt = source.getStart().getDateTime(); } ZoneOffset stOffset = ZoneOffset.ofTotalSeconds(stdt.getTimeZoneShift() * 60); OffsetDateTime offsetSt = Instant.ofEpochMilli(stdt.getValue()).atOffset(stOffset); // End Time DateTime etdt = source.getEnd().getDate(); if (etdt == null) { etdt = source.getEnd().getDateTime(); } ZoneOffset etoffset = ZoneOffset.ofTotalSeconds(etdt.getTimeZoneShift() * 60); OffsetDateTime offsetEt = Instant.ofEpochMilli(etdt.getValue()).atOffset(etoffset); LocalDateTime startDateTime = offsetSt.toLocalDateTime(); if (stdt.isDateOnly() || offsetEt.toLocalTime().equals(LocalTime.MIDNIGHT)) { offsetEt = offsetEt.minusDays(1); entry.setInterval(new Interval(startDateTime.toLocalDate(), startDateTime.toLocalTime(), offsetEt.toLocalDate(), LocalTime.MAX)); } else { entry.setInterval(new Interval(startDateTime, offsetEt.toLocalDateTime())); } if (source.getRecurrence() != null && !source.getRecurrence().isEmpty()) { entry.setRecurrenceRule(source.getRecurrence().get(0)); } if (source.getAttendees() != null) { entry.getAttendees().setAll(source.getAttendees()); } if (source.getReminders() != null) { Event.Reminders reminders = source.getReminders(); if (Boolean.TRUE.equals(reminders.getUseDefault())) { entry.setUseDefaultReminder(true); } else if (reminders.getOverrides() != null) { List entryReminders = new ArrayList<>(); for (EventReminder reminder : reminders.getOverrides()) { entryReminders.add(new GoogleEntryReminder(reminder)); } entry.getReminders().setAll(entryReminders); } } entry.setId(source.getId()); entry.setTitle(source.getSummary()); entry.setLocation(source.getLocation()); entry.setZoneId(zoneId); entry.setFullDay(stdt.isDateOnly()); entry.setAttendeesCanModify(source.isGuestsCanModify()); entry.setAttendeesCanInviteOthers(source.isGuestsCanInviteOthers()); entry.setAttendeesCanSeeOthers(source.isGuestsCanSeeOtherGuests()); entry.setStatus(GoogleEntry.Status.fromName(source.getStatus())); entry.setUserObject(source); return entry; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/converter/GoogleCalendarToCalendarConverter.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.converter; import com.calendarfx.google.model.GoogleCalendar; import com.google.api.services.calendar.model.Calendar; import java.time.ZoneId; /** * BeanConverter between calendarfx and google api. * * Created by gdiaz on 26/03/2017. */ public class GoogleCalendarToCalendarConverter implements BeanConverter { @Override public Calendar convert(GoogleCalendar source) { Calendar target = new Calendar(); target.setSummary(source.getName()); target.setTimeZone(ZoneId.systemDefault().getId()); return target; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/converter/GoogleCalendarToCalendarListEntryConverter.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.converter; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntryReminder; import com.google.api.services.calendar.model.CalendarListEntry; import com.google.api.services.calendar.model.EventReminder; import java.util.ArrayList; import java.util.List; /** * Converts from calendar (calendarfx api) to calendar list entry (google api). * * Created by gdiaz on 20/02/2017. */ public final class GoogleCalendarToCalendarListEntryConverter implements BeanConverter { @Override public CalendarListEntry convert(GoogleCalendar source) { CalendarListEntry calendarListEntry = new CalendarListEntry(); calendarListEntry.setId(source.getId()); calendarListEntry.setSummary(source.getName()); calendarListEntry.setPrimary(source.isPrimary()); List reminders = new ArrayList<>(); for (GoogleEntryReminder reminder : source.getDefaultReminders()) { EventReminder er = new EventReminder(); er.setMethod(reminder.getMethod().getId()); er.setMinutes(reminder.getMinutes()); reminders.add(er); } calendarListEntry.setDefaultReminders(reminders); return calendarListEntry; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/converter/GoogleEntryToEventConverter.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.converter; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.model.GoogleEntryReminder; import com.google.api.client.util.DateTime; import com.google.api.services.calendar.model.Event; import com.google.api.services.calendar.model.EventDateTime; import com.google.api.services.calendar.model.EventReminder; import java.time.LocalTime; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.TimeZone; /** * BeanConverter between entry (calendarfx api) and event (google api). * * Created by gdiaz on 20/02/2017. */ public final class GoogleEntryToEventConverter implements BeanConverter { @Override public Event convert(GoogleEntry source) { EventDateTime startTime = new EventDateTime(); EventDateTime endTime = new EventDateTime(); ZonedDateTime st = source.getStartAsZonedDateTime(); ZonedDateTime et = source.getEndAsZonedDateTime(); TimeZone timeZone = TimeZone.getTimeZone(source.getZoneId()); if (source.getEndTime().equals(LocalTime.MAX)) { et = et.plusDays(1).truncatedTo(ChronoUnit.DAYS); } if (source.isFullDay()) { startTime.setDate(new DateTime(true, st.toInstant().toEpochMilli(), 0)); startTime.setTimeZone(timeZone.getID()); endTime.setDate(new DateTime(true, et.toInstant().toEpochMilli(), 0)); endTime.setTimeZone(timeZone.getID()); } else { startTime.setDateTime(new DateTime(Date.from(st.toInstant()))); startTime.setTimeZone(timeZone.getID()); endTime.setDateTime(new DateTime(Date.from(et.toInstant()))); endTime.setTimeZone(timeZone.getID()); } Event event = new Event(); if (source.getRecurrenceRule() != null) { List recurrence = new ArrayList<>(); recurrence.add(source.getRecurrenceRule()); event.setRecurrence(recurrence); } Event.Reminders reminders = new Event.Reminders(); reminders.setUseDefault(source.isUseDefaultReminder()); if (source.getReminders() != null) { List overrides = new ArrayList<>(); for (GoogleEntryReminder reminder : source.getReminders()) { EventReminder override = new EventReminder(); override.setMethod(reminder.getMethod().getId()); override.setMinutes(reminder.getMinutes()); overrides.add(override); } reminders.setOverrides(overrides); } event.setId(source.existsInGoogle() ? source.getId() : null); event.setSummary(source.getTitle()); event.setStart(startTime); event.setEnd(endTime); event.setAttendees(source.getAttendees()); event.setGuestsCanModify(source.isAttendeesCanModify()); event.setGuestsCanInviteOthers(source.isAttendeesCanInviteOthers()); event.setGuestsCanSeeOtherGuests(source.isAttendeesCanSeeOthers()); event.setLocation(source.getLocation()); event.setReminders(reminders); return event; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/model/GoogleAccount.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.model; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import javafx.collections.ListChangeListener; import java.util.ArrayList; import java.util.List; /** * Class representing a Google account of a determinate user, which is owner of * the Calendars information. * * @author Gabriel Diaz, 03.03.2015. */ public class GoogleAccount extends CalendarSource { private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } /** * Creates one single calendar with the given name and style. * * @param name The name of the calendar. * @param style The style of the calendar. * @return The new google calendar. */ public final GoogleCalendar createCalendar(String name, Calendar.Style style) { GoogleCalendar calendar = new GoogleCalendar(); calendar.setName(name); calendar.setStyle(style); return calendar; } /** * Gets the calendar marked as primary calendar for the google account. * * @return The primary calendar, {@code null} if not loaded. */ public GoogleCalendar getPrimaryCalendar() { return (GoogleCalendar) getCalendars().stream() .filter(calendar -> ((GoogleCalendar) calendar).isPrimary()) .findFirst() .orElse(null); } /** * Gets all the google calendars hold by this source. * * @return The list of google calendars, always a new list. */ public List getGoogleCalendars() { List googleCalendars = new ArrayList<>(); for (Calendar calendar : getCalendars()) { if (!(calendar instanceof GoogleCalendar)) { continue; } googleCalendars.add((GoogleCalendar) calendar); } return googleCalendars; } /** * Adds the given calendar listener in order to get notified when any calendar/entry changes. * * @param listeners The listener. */ @SafeVarargs public final void addCalendarListeners(ListChangeListener... listeners) { if (listeners != null) { for (ListChangeListener listener : listeners) { getCalendars().addListener(listener); } } } /** * Removes the listener from the ones being notified. * * @param listeners The listener */ @SafeVarargs public final void removeCalendarListeners(ListChangeListener... listeners) { if (listeners != null) { for (ListChangeListener listener : listeners) { getCalendars().removeListener(listener); } } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/model/GoogleCalendar.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.model; import com.calendarfx.model.Calendar; import com.calendarfx.model.Entry; import com.calendarfx.model.Interval; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import java.time.ZonedDateTime; import java.util.List; import java.util.Objects; /** * Calendar class that encapsulates the logic of a Google Calendar Entry. * * @author Gabriel Diaz, 10.02.2015. */ public class GoogleCalendar extends Calendar { private static final String ACCESS_ROLE_READER = "reader"; private static final String ACCESS_ROLE_FREE_BUSY_READER = "freeBusyReader"; /** * Static field used to enumerate the entries created in this calendar. */ private static int entryConsecutive = 1; /** * Constructs a calendar using the given calendar service. */ public GoogleCalendar() { super(); } /** * Generates a new consecutive number each time is called. This always * starts at 1. * * @return The consecutive generated. */ private static int generateEntryConsecutive() { return entryConsecutive++; } /** * Represents the ID of the backend calendar entry, this field is generated * only by Google. */ private String id; /** * Gets the ID of the backend calendar entry, can be null if this calendar * has not been saved in Google. * * @return The ID of the backend calendar entry. */ public final String getId() { return id; } /** * Sets the ID for client side use; this is supposed to be called when this * calendar is associated with its backend calendar entry. * * @param id * The new ID. */ public final void setId(String id) { this.id = id; } /** * Indicating this calendar is the primary of the google account. */ private boolean primary; /** * Gets the flag that indicates whether this calendar is primary or not. A * primary calendar can be seen as the default calendar of the account. * * @return {@code true} when this calendar is primary, otherwise * {@code false}. */ public final boolean isPrimary() { return primary; } /** * Sets the primary flag, supposed to be called when this calendar is * associated with a backend calendar entry. * * @param primary * The value for the flag. */ public final void setPrimary(boolean primary) { this.primary = primary; } /** * List storing the default reminders of the calendar, represents the * default configuration for all the entries of this calendar and is * supposed to be inherited. */ private final ObservableList defaultReminders = FXCollections.observableArrayList(); /** * Gets the list of default reminders configured for this calendar. * * @return The default remind configuration. */ public final ObservableList getDefaultReminders() { return defaultReminders; } /** * Creates a new google entry by using the given parameters, this assigns a * default name by using a consecutive number. The entry is of course * associated to this calendar, but it is not sent to google for storing. * * @param start * The start date/time of the new entry. * @param fullDay * A flag indicating if the new entry is going to be full day. * @return The new google entry created, this is not send to server side. * @see #generateEntryConsecutive() */ public final GoogleEntry createEntry(ZonedDateTime start, boolean fullDay) { GoogleEntry entry = new GoogleEntry(); entry.setTitle("New Entry " + generateEntryConsecutive()); entry.setInterval(new Interval(start.toLocalDate(), start.toLocalTime(), start.toLocalDate(), start.toLocalTime().plusHours(1))); entry.setFullDay(fullDay); entry.setAttendeesCanInviteOthers(true); entry.setAttendeesCanSeeOthers(true); return entry; } /** * Indicates whether the calendar exist in google calendar or not. * * @return a flag saying whether this calendar was already persisted or not. */ public final boolean existsInGoogle() { return id != null; } /** * Checks whether the given access role means the calendar is read only. * * @param accessRole The access role to be analyzed. * @return {@code true} if the access role matches {@link #ACCESS_ROLE_READER} * or {@link #ACCESS_ROLE_FREE_BUSY_READER}. */ public static boolean isReadOnlyAccessRole(String accessRole) { return ACCESS_ROLE_READER.equals(accessRole) || ACCESS_ROLE_FREE_BUSY_READER.equals(accessRole); } /** * Consumer to delegate the loading of entries to an external provider. */ private IGoogleCalendarSearchTextProvider searchTextProvider; public void setSearchTextProvider(IGoogleCalendarSearchTextProvider searchTextProvider) { this.searchTextProvider = searchTextProvider; } @Override public List> findEntries(String text) { if (searchTextProvider != null) { searchTextProvider.search(this, text); } return super.findEntries(text); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GoogleCalendar that = (GoogleCalendar) o; return Objects.equals(id, that.id); } @Override public int hashCode() { return id != null ? id.hashCode() : 0; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/model/GoogleCalendarEvent.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.model; import com.calendarfx.model.CalendarEvent; import javafx.event.EventType; /** * Extension of the {@link CalendarEvent} which allows to know externally about * changes in {@link GoogleCalendar calendars}. * * @author Gabriel Diaz, 07.03.2015. */ public class GoogleCalendarEvent extends CalendarEvent { private static final long serialVersionUID = 8064360122175452019L; /** * Event type saying that attendees of the google entry have been changed. */ public static final EventType ENTRY_ATTENDEES_CHANGED = new EventType<>( ENTRY_CHANGED, "ENTRY_ATTENDEES_CHANGED"); /** * Event type saying whether attendees can see others or not. */ public static final EventType ENTRY_ATTENDEES_CAN_SEE_OTHERS_CHANGED = new EventType<>( ENTRY_CHANGED, "ENTRY_ATTENDEES_CAN_SEE_OTHERS_CHANGED"); /** * Event type saying whether attendees can invite others or not. */ public static final EventType ENTRY_ATTENDEES_CAN_INVITE_CHANGED = new EventType<>( ENTRY_CHANGED, "ENTRY_ATTENDEES_CAN_INVITE_CHANGED"); /** * Event type saying whether attendees can edit the google entry others or * not. */ public static final EventType ENTRY_ATTENDEES_CAN_MODIFY_CHANGED = new EventType<>( ENTRY_CHANGED, "ENTRY_ATTENDEES_CAN_MODIFY_CHANGED"); /** * Event type saying that reminders of the google entry have been changed. */ public static final EventType ENTRY_REMINDERS_CHANGED = new EventType<>( ENTRY_CHANGED, "ENTRY_REMINDERS_CHANGED"); /** * Creates a google calendar event to notify some changes in the given * google entry. * * @param eventType * The type of event. * @param calendar * The calendar which the entry belongs to. * @param entry * The entry affected. */ GoogleCalendarEvent(EventType eventType, GoogleCalendar calendar, GoogleEntry entry) { super(eventType, calendar, entry); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/model/GoogleEntry.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.model; import com.calendarfx.model.Entry; import com.google.api.services.calendar.model.Event; import com.google.api.services.calendar.model.EventAttendee; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import java.util.Objects; /** * Custom entry representing a google event. This contains all the required * information for a single event of google calendar. * * @author Gabriel Diaz, 07.03.2015. */ public class GoogleEntry extends Entry { public GoogleEntry() { super(); reminders.addListener((ListChangeListener) c -> { while (c.next()) { if (c.wasAdded()) { for (GoogleEntryReminder r : c.getAddedSubList()) { r.addListener(remindersListener); } } if (c.wasRemoved()) { for (GoogleEntryReminder r : c.getRemoved()) { r.removeListener(remindersListener); } } } }); reminders.addListener(remindersListener); attendees.addListener((Observable obs) -> { if (getCalendar() instanceof GoogleCalendar) { getCalendar().fireEvent(new GoogleCalendarEvent( GoogleCalendarEvent.ENTRY_ATTENDEES_CHANGED, (GoogleCalendar) getCalendar(), GoogleEntry.this)); } }); } private final ObjectProperty status = new SimpleObjectProperty<>(this, "status"); public final ObjectProperty statusProperty() { return status; } public final Status getStatus() { return statusProperty().get(); } public final void setStatus(Status status) { statusProperty().set(status); } private final BooleanProperty attendeesCanModify = new SimpleBooleanProperty(this, "attendeesCanModify") { @Override public void set(boolean newValue) { boolean oldValue = get(); if (!Objects.equals(oldValue, newValue)) { super.set(newValue); if (getCalendar() instanceof GoogleCalendar) { getCalendar().fireEvent(new GoogleCalendarEvent( GoogleCalendarEvent.ENTRY_ATTENDEES_CAN_MODIFY_CHANGED, (GoogleCalendar) getCalendar(), GoogleEntry.this)); } if (newValue) { setAttendeesCanInviteOthers(true); setAttendeesCanSeeOthers(true); } } } }; public final BooleanProperty attendeesCanModifyProperty() { return attendeesCanModify; } public final boolean isAttendeesCanModify() { return attendeesCanModifyProperty().get(); } public final void setAttendeesCanModify(boolean attendeesCanModify) { attendeesCanModifyProperty().set(attendeesCanModify); } private final BooleanProperty attendeesCanInviteOthers = new SimpleBooleanProperty( this, "attendeesCanInviteOthers", true) { @Override public void set(boolean newValue) { boolean oldValue = get(); if (!Objects.equals(oldValue, newValue)) { super.set(newValue); if (getCalendar() instanceof GoogleCalendar) { getCalendar().fireEvent(new GoogleCalendarEvent( GoogleCalendarEvent.ENTRY_ATTENDEES_CAN_INVITE_CHANGED, (GoogleCalendar) getCalendar(), GoogleEntry.this)); } } } }; public final BooleanProperty attendeesCanInviteOthersProperty() { return attendeesCanInviteOthers; } public final boolean isAttendeesCanInviteOthers() { return attendeesCanInviteOthersProperty().get(); } public final void setAttendeesCanInviteOthers( boolean attendeesCanInviteOthers) { attendeesCanInviteOthersProperty().set(attendeesCanInviteOthers); } private final BooleanProperty attendeesCanSeeOthers = new SimpleBooleanProperty( this, "attendeesCanSeeOthers", true) { @Override public void set(boolean newValue) { boolean oldValue = get(); if (!Objects.equals(oldValue, newValue)) { super.set(newValue); if (getCalendar() instanceof GoogleCalendar) { getCalendar().fireEvent(new GoogleCalendarEvent( GoogleCalendarEvent.ENTRY_ATTENDEES_CAN_SEE_OTHERS_CHANGED, (GoogleCalendar) getCalendar(), GoogleEntry.this)); } } } }; public final BooleanProperty attendeesCanSeeOthersProperty() { return attendeesCanSeeOthers; } public final boolean isAttendeesCanSeeOthers() { return attendeesCanSeeOthersProperty().get(); } public final void setAttendeesCanSeeOthers(boolean attendeesCanSeeOthers) { attendeesCanSeeOthersProperty().set(attendeesCanSeeOthers); } private final BooleanProperty useDefaultReminder = new SimpleBooleanProperty(this, "useDefaultReminder"); public final BooleanProperty useDefaultReminderProperty() { return useDefaultReminder; } public final boolean isUseDefaultReminder() { return useDefaultReminderProperty().get(); } public final void setUseDefaultReminder(boolean useDefaultReminder) { useDefaultReminderProperty().set(useDefaultReminder); } private final ObservableList attendees = FXCollections.observableArrayList(); public ObservableList getAttendees() { return attendees; } private final InvalidationListener remindersListener = obs -> { if (getCalendar() instanceof GoogleCalendar) { GoogleCalendar calendar = (GoogleCalendar) getCalendar(); calendar.fireEvent(new GoogleCalendarEvent(GoogleCalendarEvent.ENTRY_REMINDERS_CHANGED, calendar, GoogleEntry.this)); } }; private final ObservableList reminders = FXCollections.observableArrayList(); public ObservableList getReminders() { return reminders; } @Override public GoogleEntry createRecurrence() { GoogleEntry entry = new GoogleEntry(); entry.setStatus(getStatus()); entry.setAttendeesCanModify(isAttendeesCanModify()); entry.setAttendeesCanInviteOthers(isAttendeesCanInviteOthers()); entry.setAttendeesCanSeeOthers(isAttendeesCanSeeOthers()); entry.getAttendees().setAll(getAttendees()); entry.getReminders().setAll(getReminders()); return entry; } /** * Indicates whether the entry exist in google calendar or not. * * @return a flag saying whether this entry was already persisted or not. */ public final boolean existsInGoogle() { return getUserObject() != null; } @Override public String toString() { return "Google Entry: " + getTitle(); } /** * Enumeration representing the status of the google entry. * * @author Gabriel Diaz, 22.03.2015. */ public enum Status { CONFIRMED("confirmed"), TENTATIVE("tentative"), CANCELLED("cancelled"); private final String name; Status(String name) { this.name = name; } public String getName() { return name; } public static Status fromName(String name) { for (Status status : values()) { if (status.getName().equals(name)) { return status; } } return null; } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/model/GoogleEntryReminder.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.model; import com.google.api.services.calendar.model.EventReminder; import javafx.beans.InvalidationListener; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; /** * Remind method. * * Created by gdiaz on 28/04/2017. */ public class GoogleEntryReminder { public GoogleEntryReminder() { super(); } public GoogleEntryReminder(EventReminder reminder) { super(); setMethod(RemindMethod.fromId(reminder.getMethod())); setMinutes(reminder.getMinutes()); } public void addListener(InvalidationListener listener) { methodProperty().addListener(listener); minutesProperty().addListener(listener); } public void removeListener(InvalidationListener listener) { methodProperty().removeListener(listener); minutesProperty().addListener(listener); } private final ObjectProperty method = new SimpleObjectProperty<>(this, "method"); public final ObjectProperty methodProperty() { return method; } public final RemindMethod getMethod() { return methodProperty().get(); } public final void setMethod(RemindMethod method) { methodProperty().set(method); } private final ObjectProperty minutes = new SimpleObjectProperty<>(this, "minutes"); public final ObjectProperty minutesProperty() { return minutes; } public final Integer getMinutes() { return minutesProperty().get(); } public final void setMinutes(Integer minutes) { minutesProperty().set(minutes); } /** * Enumeration representing the available notification types. * * @author Gabriel Diaz, 07.03.2015. */ public enum RemindMethod { POPUP("popup", "Popup"), EMAIL("email", "Email"); private final String id; private final String name; RemindMethod(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } public String getName() { return name; } @Override public String toString() { return getName(); } /** * Gets a remind method. * * @param id The id of the remind method. * @return The reminder method constant that matches the id. */ public static RemindMethod fromId(String id) { if (id != null) { for (RemindMethod method : values()) { if (method.id.equals(id)) { return method; } } } return POPUP; } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/model/IGoogleCalendarSearchTextProvider.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.model; /** * Objects that provides external searching of entries by a given text on a single calendar. * * Created by gdiaz on 5/05/2017. */ public interface IGoogleCalendarSearchTextProvider { void search(GoogleCalendar calendar, String searchText); } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/service/BeanConverterService.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.service; import com.calendarfx.google.converter.BeanConverter; import com.calendarfx.google.converter.CalendarListEntryToGoogleCalendarConverter; import com.calendarfx.google.converter.EventToGoogleEntryConverter; import com.calendarfx.google.converter.GoogleCalendarToCalendarConverter; import com.calendarfx.google.converter.GoogleCalendarToCalendarListEntryConverter; import com.calendarfx.google.converter.GoogleEntryToEventConverter; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.google.api.services.calendar.model.Calendar; import com.google.api.services.calendar.model.CalendarListEntry; import com.google.api.services.calendar.model.Event; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * Service that provides convertion from one object to another based on the class. * * Created by gdiaz on 22/02/2017. */ public class BeanConverterService { private static BeanConverterService instance; public static BeanConverterService getInstance() { if (instance == null) { instance = new BeanConverterService(); } return instance; } private final Map, Class>, BeanConverter> converters = new HashMap<>(); private BeanConverterService() { converters.put(Pair.of(GoogleEntry.class, Event.class), new GoogleEntryToEventConverter()); converters.put(Pair.of(Event.class, GoogleEntry.class), new EventToGoogleEntryConverter()); converters.put(Pair.of(GoogleCalendar.class, CalendarListEntry.class), new GoogleCalendarToCalendarListEntryConverter()); converters.put(Pair.of(CalendarListEntry.class, GoogleCalendar.class), new CalendarListEntryToGoogleCalendarConverter()); converters.put(Pair.of(GoogleCalendar.class, Calendar.class), new GoogleCalendarToCalendarConverter()); } T convert(S source, Class targetClass) { if (canConvert(source.getClass(), targetClass)) { BeanConverter converter = getConverter((Class) source.getClass(), targetClass); return converter.convert(source); } throw new UnsupportedOperationException("The object " + source + " cannot be converted to " + targetClass); } private boolean canConvert(Class sourceClass, Class targetClass) { return getConverter(sourceClass, targetClass) != null; } private BeanConverter getConverter(Class sourceClass, Class targetClass) { return (BeanConverter) converters.get(Pair.of(sourceClass, targetClass)); } private static class Pair { private S source; private T target; Pair(S source, T target) { this.source = source; this.target = target; } public S getSource() { return source; } public void setSource(S source) { this.source = source; } public T getTarget() { return target; } public void setTarget(T target) { this.target = target; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Pair pair = (Pair) o; if (!Objects.equals(source, pair.source)) return false; return Objects.equals(target, pair.target); } @Override public int hashCode() { int result = source != null ? source.hashCode() : 0; result = 31 * result + (target != null ? target.hashCode() : 0); return result; } public static Pair of(S source, T target) { return new Pair<>(source, target); } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/service/GoogleCalendarService.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.service; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.google.api.client.util.DateTime; import com.google.api.services.calendar.Calendar; import com.google.api.services.calendar.model.CalendarListEntry; import com.google.api.services.calendar.model.Event; import java.io.IOException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; /** * Encapsulation of the google calendar service that provides some methods for * CRUD operations on calendars and events. * * @author Gabriel Diaz, 14.03.2015. */ public class GoogleCalendarService { private final Calendar dao; private final BeanConverterService converter; GoogleCalendarService(Calendar dao) { this.dao = Objects.requireNonNull(dao); this.converter = BeanConverterService.getInstance(); } /** * Inserts a calendar into the google calendar. * * @param calendar The calendar to be inserted. * @throws IOException For unexpected errors. */ public void insertCalendar(GoogleCalendar calendar) throws IOException { com.google.api.services.calendar.model.Calendar cal; cal = converter.convert(calendar, com.google.api.services.calendar.model.Calendar.class); cal = dao.calendars().insert(cal).execute(); calendar.setId(cal.getId()); } /** * Saves the updates done on the calendar into google calendar api. * * @param calendar The calendar to be updated. * @throws IOException For unexpected errors. */ public void updateCalendar(GoogleCalendar calendar) throws IOException { CalendarListEntry calendarListEntry = converter.convert(calendar, CalendarListEntry.class); dao.calendarList().update(calendarListEntry.getId(), calendarListEntry).execute(); } /** * Performs an immediate delete request on the google calendar api. * * @param calendar The calendar to be removed. * @throws IOException For unexpected errors */ public void deleteCalendar(GoogleCalendar calendar) throws IOException { dao.calendars().delete(calendar.getId()).execute(); } /** * Performs an immediate insert operation on google server by sending the * information provided by the given google entry. The entry is associated * to this calendar. * * @param entry The entry to be inserted in a backend google calendar. * @param calendar The calendar in which the entry will be inserted. * @return The same instance received. * @throws IOException For unexpected errors */ public GoogleEntry insertEntry(GoogleEntry entry, GoogleCalendar calendar) throws IOException { Event event = converter.convert(entry, Event.class); event = dao.events().insert(calendar.getId(), event).execute(); entry.setId(event.getId()); entry.setUserObject(event); return entry; } /** * Performs an immediate update operation on google server by sending the * information stored by the given google entry. * * @param entry The entry to be updated in a backend google calendar. * @return The same instance received. * @throws IOException For unexpected errors */ public GoogleEntry updateEntry(GoogleEntry entry) throws IOException { GoogleCalendar calendar = (GoogleCalendar) entry.getCalendar(); Event event = converter.convert(entry, Event.class); dao.events().update(calendar.getId(), event.getId(), event).execute(); return entry; } /** * Sends a delete request to the google server for the given entry. * * @param entry The entry to be deleted from the backend google calendar. * @param calendar The calendar from the entry was deleted. * @throws IOException For unexpected errors. */ public void deleteEntry(GoogleEntry entry, GoogleCalendar calendar) throws IOException { dao.events().delete(calendar.getId(), entry.getId()).execute(); } /** * Moves an entry from one calendar to another. * * @param entry The entry to be moved. * @param from The current calendar. * @param to The future calendar. * @return The entry updated. * @throws IOException For unexpected errors. */ public GoogleEntry moveEntry(GoogleEntry entry, GoogleCalendar from, GoogleCalendar to) throws IOException { dao.events().move(from.getId(), entry.getId(), to.getId()).execute(); return entry; } /** * Gets the list of all calendars available in the account. * * @return A non-null list of all calendars. * @throws IOException For unexpected errors. */ public List getCalendars() throws IOException { List calendarListEntries = dao.calendarList().list().execute().getItems(); List calendars = new ArrayList<>(); if (calendarListEntries != null && !calendarListEntries.isEmpty()) { for (int i = 0; i < calendarListEntries.size(); i++) { CalendarListEntry calendarListEntry = calendarListEntries.get(i); GoogleCalendar calendar = converter.convert(calendarListEntry, GoogleCalendar.class); calendar.setStyle(com.calendarfx.model.Calendar.Style.getStyle(i)); calendars.add(calendar); } } return calendars; } /** * Gets a list of entries belonging to the given calendar defined between the given range of time. Recurring events * are not expanded, always recurrence is handled manually within the framework. * * @param calendar The calendar owner of the entries. * @param startDate The start date, not nullable. * @param endDate The end date, not nullable * @param zoneId The timezone in which the dates are represented. * @return A non-null list of entries. * @throws IOException For unexpected errors */ public List getEntries(GoogleCalendar calendar, LocalDate startDate, LocalDate endDate, ZoneId zoneId) throws IOException { if (!calendar.existsInGoogle()) { return new ArrayList<>(0); } ZonedDateTime st = ZonedDateTime.of(startDate, LocalTime.MIN, zoneId); ZonedDateTime et = ZonedDateTime.of(endDate, LocalTime.MAX, zoneId); String calendarId = URLDecoder.decode(calendar.getId(), StandardCharsets.UTF_8); List events = dao.events() .list(calendarId) .setTimeMin(new DateTime(Date.from(st.toInstant()))) .setTimeMax(new DateTime(Date.from(et.toInstant()))) .setSingleEvents(false) .setShowDeleted(false) .execute() .getItems(); return toGoogleEntries(events); } /** * Gets a list of entries that matches the given text. Recurring events * are not expanded, always recurrence is handled manually within the framework. * * @param calendar The calendar owner of the entries. * @param searchText The search text * @return A non-null list of entries. * @throws IOException For unexpected errors */ public List getEntries(GoogleCalendar calendar, String searchText) throws IOException { if (!calendar.existsInGoogle()) { return new ArrayList<>(0); } String calendarId = URLDecoder.decode(calendar.getId(), StandardCharsets.UTF_8); List events = dao.events() .list(calendarId) .setQ(searchText) .setSingleEvents(false) .setShowDeleted(false) .execute() .getItems(); return toGoogleEntries(events); } private List toGoogleEntries(List events) { List entries = new ArrayList<>(); if (events != null && !events.isEmpty()) { for (Event event : events) { entries.add(converter.convert(event, GoogleEntry.class)); } } return entries; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/service/GoogleConnector.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.service; import com.calendarfx.google.model.GoogleAccount; import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.auth.oauth2.StoredCredential; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; import com.google.api.client.googleapis.auth.oauth2.GoogleOAuthConstants; import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.store.DataStore; import com.google.api.client.util.store.DataStoreFactory; import com.google.api.client.util.store.FileDataStoreFactory; import com.google.api.services.calendar.Calendar; import com.google.api.services.calendar.CalendarScopes; import com.google.api.services.oauth2.Oauth2; import com.google.api.services.oauth2.Oauth2Scopes; import com.google.api.services.oauth2.model.Userinfoplus; import com.google.code.geocoder.Geocoder; import com.google.common.collect.Lists; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.security.GeneralSecurityException; import java.util.List; /** * Utility class used to connect to google services. * * @author Gabriel Diaz, 16.12.2014 */ public final class GoogleConnector { /** Application name for identification purposes. */ private static final String APPLICATION_NAME = "flexcalendarfxapp"; /** Directory used to store the user credentials. */ private static final File CREDENTIALS_DIRECTORY = new File(System.getProperty("user.home"), ".store/flexcalendarfx"); /** Default scopes used by the Google calendar application. */ private static final List SCOPES = Lists.newArrayList(CalendarScopes.CALENDAR, Oauth2Scopes.USERINFO_PROFILE); /** The instance of this singleton class. */ private static GoogleConnector instance; /** JSON implementation. */ private final JsonFactory jsonFactory; /** HTTP transport used for transferring data. */ private final HttpTransport httpTransport; /** Secrets of the FX calendar application. */ private final GoogleClientSecrets secrets; /** Factory to create the data store object. */ private final DataStoreFactory dataStoreFactory; /** Flow to get the access token. */ private AuthorizationCodeFlow flow; /** Service that allows to manipulate Google Calendars. */ private GoogleCalendarService calendarService; /** Service that allows to use Google Geolocalization. */ private GoogleGeocoderService geoService; /** * Instances the google connector configuring all required resources. */ private GoogleConnector() throws IOException, GeneralSecurityException { super(); // 1. JSON library jsonFactory = JacksonFactory.getDefaultInstance(); // 2. Configure HTTP transport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); // 3. Load the credentials secrets = GoogleClientSecrets.load(jsonFactory, new InputStreamReader(GoogleConnector.class.getResourceAsStream("client-secrets.json"))); // 4. Configure the authentication flow dataStoreFactory = new FileDataStoreFactory(CREDENTIALS_DIRECTORY); // 5. Create flow imp_buildAuthorizationFlow(); } /** * On demand instance creator method used to get the single instance of this * google authenticator class. * * @return The single instance of this class, if the instance does not exist, * this is immediately created. */ public static GoogleConnector getInstance() { if (instance == null) { try { instance = new GoogleConnector(); } catch (Exception e) { throw new RuntimeException("The GoogleConnector could not be instanced!", e); } } return instance; } /** * Starts the authorization process but using the provided authorization * code, so this will not attempt to get it from the user. * * @param accountId The account to be authorized. * @param authorizationCode The code used as key of authorization. * @throws IOException If storing the code fails. */ synchronized void authorize(String accountId, String authorizationCode) throws IOException { impl_storeCredential(accountId, authorizationCode); } /** * Deletes the stored credentials for the given account id. This means the * next time the user must authorize the app to access his calendars. * * @param accountId * The identifier of the account. */ synchronized void removeCredential(String accountId) throws IOException { DataStore sc = StoredCredential.getDefaultDataStore(dataStoreFactory); sc.delete(accountId); calendarService = null; geoService = null; } /** * Checks if the given account id has already been authorized and the * user granted access to his calendars info. * * @param accountId * The identifier of the account used internally by the application. * @return {@code true} if the account has already been set up, otherwise * {@code false}. */ boolean isAuthorized(String accountId) { try { DataStore sc = StoredCredential.getDefaultDataStore(dataStoreFactory); return sc.containsKey(accountId); } catch (IOException e) { return false; } } /** * Generates a valid URL to let the user authorize access his Google * calendar information. * * @return The valid web URL. */ public String getAuthorizationURL() { return flow.newAuthorizationUrl().setRedirectUri(GoogleOAuthConstants.OOB_REDIRECT_URI).build(); } /** * Gets the service to access geo localization methods; using this service does not require authentication. * * @return The service created, this is on demand created. */ public synchronized GoogleGeocoderService getGeocoderService() { if (geoService == null) { geoService = new GoogleGeocoderService(new Geocoder()); } return geoService; } /** * Instances a new calendar service for the given google account user name. * This requires previous authorization to get the service, so if the user * has not granted access to his data, this method will start the * authorization process automatically; this attempts to open the login google page in the * default browser. * * @param accountId * The google account. * @return The calendar service, this can be null if the account cannot be * authenticated. * @throws IOException If the account has not been authenticated. */ public synchronized GoogleCalendarService getCalendarService(String accountId) throws IOException { if (calendarService == null) { Credential credential = impl_getStoredCredential(accountId); if (credential == null) { throw new UnsupportedOperationException("The account has not been authorized yet!"); } calendarService = new GoogleCalendarService(impl_createService(credential)); } return calendarService; } /** * Requests the user info for the given account. This requires previous * authorization from the user, so this might start the process. * * @param accountId * The id of the account to get the user info. * @return The user info bean. * @throws IOException If the account cannot be accessed. */ GoogleAccount getAccountInfo(String accountId) throws IOException { Credential credential = impl_getStoredCredential(accountId); if (credential == null) { throw new UnsupportedOperationException("The account has not been authorized yet!"); } Userinfoplus info = impl_requestUserInfo(credential); GoogleAccount account = new GoogleAccount(); account.setId(accountId); account.setName(info.getName()); return account; } // :::::::::::::::::: PRIVATE STUFF ::::::::::::::::::::: private Calendar impl_createService(Credential credentials) { return new Calendar.Builder(httpTransport, jsonFactory, credentials).setApplicationName(APPLICATION_NAME).build(); } private Credential impl_getStoredCredential(String accountId) throws IOException { return flow.loadCredential(accountId); } private void impl_storeCredential(String accountId, String authorizationCode) throws IOException { TokenResponse response = flow.newTokenRequest(authorizationCode).setRedirectUri(GoogleOAuthConstants.OOB_REDIRECT_URI).execute(); flow.createAndStoreCredential(response, accountId); } private Userinfoplus impl_requestUserInfo(Credential credentials) throws IOException { Oauth2 userInfoService = new Oauth2.Builder(httpTransport, jsonFactory, credentials).setApplicationName(APPLICATION_NAME).build(); return userInfoService.userinfo().get().execute(); } private void imp_buildAuthorizationFlow() throws IOException { flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, secrets, SCOPES).setDataStoreFactory(dataStoreFactory).build(); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/service/GoogleGeocoderService.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.service; import com.google.code.geocoder.Geocoder; import com.google.code.geocoder.model.GeocodeResponse; import com.google.code.geocoder.model.GeocoderGeometry; import com.google.code.geocoder.model.GeocoderRequest; import com.google.code.geocoder.model.GeocoderResult; import com.google.code.geocoder.model.GeocoderStatus; import java.io.IOException; import java.util.List; /** * BeanConverter class that allows to transform from a location (String) to a * coordinate and vice versa. Uses the Google GeoService, which is responsible * of the transformation. * * @author Gabriel Diaz, 17.02.2015. */ public final class GoogleGeocoderService { private final Geocoder geocoder; GoogleGeocoderService(Geocoder geocoder) { this.geocoder = geocoder; } public GeocoderGeometry locationToCoordinate(String location) throws IOException { GeocoderGeometry coordinate = null; if (location != null && !location.isEmpty()) { GeocoderRequest request = new GeocoderRequest(); request.setAddress(location); GeocodeResponse response = geocoder.geocode(request); if (response.getStatus() == GeocoderStatus.OK) { List results = response.getResults(); for (GeocoderResult result : results) { GeocoderGeometry geometry = result.getGeometry(); coordinate = geometry; break; } } } return coordinate; } public String coordinateToLocation(GeocoderGeometry coordinate) throws IOException { String location = null; if (coordinate != null) { GeocoderRequest request = new GeocoderRequest(); request.setLocation(coordinate.getLocation()); request.setBounds(coordinate.getBounds()); GeocodeResponse response = geocoder.geocode(request); if (response.getStatus() == GeocoderStatus.OK) { List results = response.getResults(); for (GeocoderResult result : results) { location = result.getFormattedAddress(); break; } } } return location; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/service/SecurityService.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.service; import com.calendarfx.google.model.GoogleAccount; import javafx.scene.control.Alert; import java.io.IOException; /** * Class that holds the info about the user logged in. * * Created by gdiaz on 5/05/2017. */ public class SecurityService { /** * Default user account id, used internally by the application for authentication purposes. */ private static final String DEFAULT_ACCOUNT_ID = "Google-Calendar"; private static SecurityService instance; public static SecurityService getInstance() { if (instance == null) { instance = new SecurityService(); } return instance; } private GoogleAccount account; public GoogleAccount getLoggedAccount() { return account; } public boolean isLoggedIn() { return account != null; } public boolean isAuthorized() { return GoogleConnector.getInstance().isAuthorized(SecurityService.DEFAULT_ACCOUNT_ID); } public boolean authorize(String authorizationCode) { try { GoogleConnector.getInstance().authorize(SecurityService.DEFAULT_ACCOUNT_ID, authorizationCode); return true; } catch (IOException e) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setHeaderText("Unexpected error while authenticating into Google."); alert.setContentText(e.getLocalizedMessage()); alert.show(); return false; } } public GoogleAccount login() { if (isLoggedIn()) { logout(); } try { account = GoogleConnector.getInstance().getAccountInfo(SecurityService.DEFAULT_ACCOUNT_ID); return account; } catch (IOException e) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setHeaderText("Unexpected error while login into Google."); alert.setContentText(e.getLocalizedMessage()); alert.show(); return null; } } public void logout() { account = null; try { GoogleConnector.getInstance().removeCredential(SecurityService.DEFAULT_ACCOUNT_ID); } catch (IOException e) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setHeaderText("Unexpected error removing credentials"); alert.setContentText(e.getLocalizedMessage()); alert.show(); } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/GoogleCalendarAppView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view; import com.calendarfx.google.view.log.LogPane; import com.calendarfx.view.CalendarFXControl; import com.calendarfx.view.CalendarView; import com.calendarfx.view.DateControl; import impl.com.calendarfx.google.view.GoogleCalendarAppViewSkin; import javafx.scene.control.Skin; import java.util.Objects; /** * Control which allows to log in by using a Google account in order to get * access to the user calendar data. Displays the Google Login web page and then * lets the user authorize us to read/write his calendar information. After * authorization this displays a {@link DateControl} configured externally. * * @author Gabriel Diaz, 14.02.2015. */ public class GoogleCalendarAppView extends CalendarFXControl { private final CalendarView calendarView; private final LogPane logPane; public GoogleCalendarAppView(CalendarView calendarView) { super(); this.calendarView = Objects.requireNonNull(calendarView); this.logPane = new LogPane(); } @Override protected Skin createDefaultSkin() { return new GoogleCalendarAppViewSkin(this); } public CalendarView getCalendarView() { return calendarView; } public LogPane getLogPane() { return logPane; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/data/GoogleCalendarData.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.data; import com.calendarfx.google.model.GoogleEntry; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * Class storing data about the entries loaded from google for one single calendar. * * Created by gdiaz on 27/02/2017. */ public final class GoogleCalendarData { /** * Set of Ids already loaded. */ private final Set loadedEntryIds = new HashSet<>(); /** * Set of searchText already loaded. */ private final Set loadedSearchText = new HashSet<>(); /** * Set of slices already loaded for this calendar. */ private final Set loadedSlices = new TreeSet<>(); /** * Set of slices being loaded in background. */ private final Set inProgressSlices = new TreeSet<>(); /** * Takes the list of slices and removes those already loaded. * * @param slices the slices to be processed. * @return A new list containing the unloaded slices. */ public List getUnloadedSlices(List slices) { List unloadedSlices = new ArrayList<>(slices); unloadedSlices.removeAll(loadedSlices); unloadedSlices.removeAll(inProgressSlices); return unloadedSlices; } /** * Adds the given list of slices to the ones being loaded. This produces the slices are not classified as not loaded but in progress loading. * * @param slices The list of slices to be added. */ public void addInProgressSlices(List slices) { inProgressSlices.addAll(slices); } /** * Adds the given slice to the list of loaded ones. This also removes it from the list of being loaded ones, in case it was added. * * @param slice The slice to be added. */ public void addLoadedSlice(Slice slice) { loadedSlices.add(slice); inProgressSlices.remove(slice); } /** * Checks whether an entry was already added or not. * * @param entry The entry to be loaded. * @return True or false. */ public boolean isLoadedEntry(GoogleEntry entry) { return loadedEntryIds.contains(entry.getId()); } /** * Puts the entry in the loaded ones. * * @param entry The entry loaded. */ public void addLoadedEntry(GoogleEntry entry) { loadedEntryIds.add(entry.getId()); } /** * Checks whether the search text was already loaded or not. * * @param searchText The search text. * @return True or false. */ public boolean isLoadedSearchText(String searchText) { return loadedSearchText.contains(searchText); } /** * Puts the text in the loaded ones. * * @param searchText The text to be added. */ public void addLoadedSearchText(String searchText) { loadedSearchText.add(searchText); } /** * Clears the data stored about the calendar. */ public void clear() { loadedSlices.clear(); loadedSearchText.clear(); loadedEntryIds.clear(); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/data/IGoogleCalendarDataProvider.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.data; import com.calendarfx.google.model.GoogleCalendar; /** * Provider of the google calendar data. * * Created by gdiaz on 5/05/2017. */ public interface IGoogleCalendarDataProvider { default GoogleCalendarData getCalendarData(GoogleCalendar calendar) { return getCalendarData(calendar, false); } GoogleCalendarData getCalendarData(GoogleCalendar calendar, boolean create); void removeCalendarData(GoogleCalendar calendar); void clearData(); } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/data/Slice.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.data; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import java.time.LocalDate; import java.time.YearMonth; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Objects; /** * Class representing a period for loading events from google. * * @author Gabriel Diaz, 20.03.2015. */ public final class Slice implements Comparable { private final LocalDate start; private final LocalDate end; private Slice(LocalDate start, LocalDate end) { this.start = Objects.requireNonNull(start); this.end = Objects.requireNonNull(end); } public LocalDate getStart() { return start; } public LocalDate getEnd() { return end; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((start == null) ? 0 : start.hashCode()); result = prime * result + ((end == null) ? 0 : end.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Slice other = (Slice) obj; if (start == null) { if (other.start != null) { return false; } } else if (!start.equals(other.start)) { return false; } if (end == null) { return other.end == null; } else return end.equals(other.end); } @Override public String toString() { return "Slice [" + getStart() + " | " + getEnd() + "]"; } @Override public int compareTo(Object o) { if (o instanceof Slice) { Slice other = (Slice) o; if (start == null) { return other.start == null ? 0 : -1; } return start.compareTo(other.start); } return 1; } /** * Splits the given period into multiple slices of one month long. * * @param start the start of the period. * @param end the end of the period. * @return The list of slices result of the splitting. */ public static List split(LocalDate start, LocalDate end) { Objects.requireNonNull(start); Objects.requireNonNull(end); Preconditions.checkArgument(!start.isAfter(end)); List slices = Lists.newArrayList(); LocalDate startOfMonth = start.withDayOfMonth(1); LocalDate endOfMonth = YearMonth.from(end).atEndOfMonth(); do { slices.add(new Slice(startOfMonth, YearMonth.from(startOfMonth).atEndOfMonth())); startOfMonth = startOfMonth.plus(1, ChronoUnit.MONTHS); } while (startOfMonth.isBefore(endOfMonth) || startOfMonth.isEqual(endOfMonth)); return slices; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/log/ActionType.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.log; /** * Action performed by the user. * * Created by gdiaz on 28/02/2017. */ public enum ActionType { LOAD { @Override public String getDisplayName() { return "Load"; } }, INSERT { @Override public String getDisplayName() { return "Insert"; } }, UPDATE { @Override public String getDisplayName() { return "Update"; } }, DELETE { @Override public String getDisplayName() { return "Delete"; } }, MOVE { @Override public String getDisplayName() { return "Move"; } }, REFRESH { @Override public String getDisplayName() { return "Refresh"; } }; public abstract String getDisplayName(); } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/log/LogItem.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.log; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import java.time.LocalDateTime; /** * Item of the log that provides information about one performed task. * * Created by gdiaz on 22/02/2017. */ public class LogItem { private final ObjectProperty time = new SimpleObjectProperty<>(this, "time"); public final ObjectProperty timeProperty() { return time; } public final LocalDateTime getTime() { return timeProperty().get(); } public final void setTime(LocalDateTime time) { timeProperty().set(time); } private final ObjectProperty action = new SimpleObjectProperty<>(this, "action"); public final ObjectProperty actionProperty() { return action; } public ActionType getAction() { return actionProperty().get(); } public void setAction(ActionType action) { actionProperty().set(action); } private final StringProperty calendar = new SimpleStringProperty(this, "calendar"); public final StringProperty calendarProperty() { return calendar; } public final String getCalendar() { return calendarProperty().get(); } public final void setCalendar(String calendar) { calendarProperty().set(calendar); } private final ObjectProperty exception = new SimpleObjectProperty<>(this, "exception"); public final ObjectProperty exceptionProperty() { return exception; } public final Throwable getException() { return exceptionProperty().get(); } public final void setException(Throwable exception) { exceptionProperty().set(exception); } private final StringProperty description = new SimpleStringProperty(this, "description"); public final StringProperty descriptionProperty() { return description; } public final String getDescription() { return descriptionProperty().get(); } public final void setDescription(String description) { descriptionProperty().set(description); } private final ObjectProperty status = new SimpleObjectProperty<>(this, "status"); public final ObjectProperty statusProperty() { return status; } public final StatusType getStatus() { return statusProperty().get(); } public final void setStatus(StatusType status) { statusProperty().set(status); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/log/LogPane.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.log; import impl.com.calendarfx.google.view.log.LogPaneSkin; import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.geometry.Pos; import javafx.scene.control.ContentDisplay; import javafx.scene.control.Control; import javafx.scene.control.SelectionMode; import javafx.scene.control.Skin; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.Tooltip; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.paint.Color; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.Collection; import java.util.function.Predicate; /** * Pane that displays a table with all log items registered by the app. * * Created by gdiaz on 22/02/2017. */ public class LogPane extends Control { private final ObservableList items = FXCollections.observableArrayList(); private final FilteredList filteredData; private final TableView table; private LogTableFilter filter; public LogPane() { super(); table = new TableView<>(); TableColumn statusColumn = new TableColumn<>("Status"); statusColumn.setCellValueFactory(new PropertyValueFactory<>("status")); statusColumn.prefWidthProperty().bind(Bindings.multiply(0.1, table.widthProperty())); statusColumn.setCellFactory(col -> new StatusTypeCell()); TableColumn actionColumn = new TableColumn<>("Action"); actionColumn.setCellValueFactory(new PropertyValueFactory<>("action")); actionColumn.prefWidthProperty().bind(Bindings.multiply(0.1, table.widthProperty())); actionColumn.setCellFactory(col -> new ActionTypeCell()); TableColumn timeColumn = new TableColumn<>("Time"); timeColumn.setCellValueFactory(new PropertyValueFactory<>("time")); timeColumn.prefWidthProperty().bind(Bindings.multiply(0.2, table.widthProperty())); timeColumn.setCellFactory(col -> new TimeCell()); TableColumn calendarColumn = new TableColumn<>("Calendar"); calendarColumn.setCellValueFactory(new PropertyValueFactory<>("calendar")); calendarColumn.prefWidthProperty().bind(Bindings.multiply(0.2, table.widthProperty())); TableColumn descriptionColumn = new TableColumn<>("Description"); descriptionColumn.setCellValueFactory(new PropertyValueFactory<>("description")); descriptionColumn.prefWidthProperty().bind(Bindings.multiply(0.4, table.widthProperty())); filteredData = new FilteredList<>(items); SortedList sortedData = new SortedList<>(filteredData); sortedData.comparatorProperty().bind(table.comparatorProperty()); table.getColumns().add(statusColumn); table.getColumns().add(actionColumn); table.getColumns().add(timeColumn); table.getColumns().add(calendarColumn); table.getColumns().add(descriptionColumn); table.setTableMenuButtonVisible(true); table.setItems(sortedData); table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); } @Override protected Skin createDefaultSkin() { return new LogPaneSkin(this, table); } public final ObservableList getItems() { return items; } public final void clearItems() { items.clear(); table.getSelectionModel().clearSelection(); } public final void removeItems(Collection items) { this.items.removeAll(items); table.getSelectionModel().clearSelection(); } public final ObservableList getSelectedItems() { return table.getSelectionModel().getSelectedItems(); } public final void filter(Collection statuses) { filter = new LogTableFilter(filter); filter.setStatusTypes(statuses); filteredData.setPredicate(filter); } public final void filter(String text) { filter = new LogTableFilter(filter); filter.setText(text); filteredData.setPredicate(filter); } public final void filter(ActionType actionType) { filter = new LogTableFilter(filter); filter.setActionType(actionType); filteredData.setPredicate(filter); } /** * Filter used for the table. */ private static class LogTableFilter implements Predicate { private Collection statuses; private String text; private ActionType actionType; LogTableFilter(LogTableFilter oldFilter) { if (oldFilter != null) { this.statuses = oldFilter.statuses; this.text = oldFilter.text; this.actionType = oldFilter.actionType; } } void setStatusTypes(Collection statuses) { this.statuses = statuses; } void setText(String text) { this.text = text; } void setActionType(ActionType actionType) { this.actionType = actionType; } @Override public boolean test(LogItem logItem) { if (statuses != null && !statuses.contains(logItem.getStatus())) { return false; } if (actionType != null && !actionType.equals(logItem.getAction())) { return false; } if (text != null && !text.isEmpty()) { String textLower = text.toLowerCase(); if (logItem.getDescription() != null) { String descriptionLower = logItem.getDescription().toLowerCase(); if (descriptionLower.contains(textLower)) { return true; } } if (logItem.getCalendar() != null) { String calendarLower = logItem.getCalendar().toLowerCase(); return calendarLower.contains(textLower); } return false; } return true; } } /** * Cell for the status column. */ private static class StatusTypeCell extends TableCell { @Override protected void updateItem(StatusType item, boolean empty) { super.updateItem(item, empty); setText(null); setTooltip(null); setAlignment(Pos.CENTER); setGraphic(null); setContentDisplay(ContentDisplay.GRAPHIC_ONLY); if (item != null && !empty) { setGraphic(item.createView()); setTooltip(new Tooltip(item.getDisplayName())); } } } /** * Cell for the action type column. */ private static class ActionTypeCell extends TableCell { @Override protected void updateItem(ActionType item, boolean empty) { super.updateItem(item, empty); setText(null); setTextFill(Color.BLACK); if (item != null && !empty) { setText(item.getDisplayName()); } } } /** * Cell for the time column. */ private static class TimeCell extends TableCell { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT); @Override protected void updateItem(LocalDateTime item, boolean empty) { super.updateItem(item, empty); setText(null); if (item != null && !empty) { setText(FORMATTER.format(item)); } } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/log/StatusType.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.log; import javafx.scene.paint.Color; import org.kordamp.ikonli.Ikon; import org.kordamp.ikonli.fontawesome.FontAwesome; import org.kordamp.ikonli.javafx.FontIcon; /** * Status of the google task. * * Created by gdiaz on 28/02/2017. */ public enum StatusType { SUCCEEDED { @Override public String getDisplayName() { return "Succeeded"; } @Override public Ikon getIcon() { return FontAwesome.CHECK; } @Override public Color getColor() { return Color.GREEN; } }, PENDING { @Override public String getDisplayName() { return "Pending"; } @Override public Ikon getIcon() { return FontAwesome.EXCLAMATION_TRIANGLE; } @Override public Color getColor() { return Color.ORANGE; } }, FAILED { @Override public String getDisplayName() { return "Failed"; } @Override public Ikon getIcon() { return FontAwesome.CLOSE; } @Override public Color getColor() { return Color.RED; } }, CANCELLED { @Override public String getDisplayName() { return "Cancelled"; } @Override public Ikon getIcon() { return FontAwesome.EXCLAMATION_CIRCLE; } @Override public Color getColor() { return Color.BLUE; } }; public abstract String getDisplayName(); public abstract Ikon getIcon(); public abstract Color getColor(); public FontIcon createView() { FontIcon view = new FontIcon(getIcon()); view.setFill(getColor()); return view; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/popover/GoogleEntryAttendeesView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.popover; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.view.popover.EntryPopOverPane; import com.google.api.services.calendar.model.EventAttendee; import com.google.common.collect.Lists; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.css.PseudoClass; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import org.controlsfx.glyphfont.FontAwesome; import org.controlsfx.glyphfont.Glyph; import java.util.List; import java.util.Objects; import static java.util.Objects.requireNonNull; import static javafx.scene.input.ContextMenuEvent.CONTEXT_MENU_REQUESTED; /** * Pane used to edit the attendees of a Google Entry. * * @author Gabriel Diaz, 21.02.2015. */ public class GoogleEntryAttendeesView extends EntryPopOverPane { private static final PseudoClass INVALID = PseudoClass.getPseudoClass("invalid"); private final GoogleEntry entry; private final TextField txtEmail; private final Button btAdd; public GoogleEntryAttendeesView(GoogleEntry entry) { super(); this.entry = requireNonNull(entry); txtEmail = new TextField(); txtEmail.getStyleClass().add("email-field"); txtEmail.textProperty().addListener(obs -> enableButton()); txtEmail.setOnAction(evt -> createAttendee()); btAdd = new Button("Add"); btAdd.setOnAction(evt -> createAttendee()); enableButton(); Label lblTitle = new Label("Attendees can:"); lblTitle.getStyleClass().add("title"); CheckBox chkEdit = new CheckBox("modify event"); chkEdit.selectedProperty().bindBidirectional(entry.attendeesCanModifyProperty()); chkEdit.disableProperty().bind(entry.getCalendar().readOnlyProperty()); CheckBox chkInvite = new CheckBox("invite others"); chkInvite.selectedProperty().bindBidirectional(entry.attendeesCanInviteOthersProperty()); chkInvite.disableProperty().bind(Bindings.or(entry.getCalendar().readOnlyProperty(), entry.attendeesCanModifyProperty())); CheckBox chkSeeOthers = new CheckBox("see attendees list"); chkSeeOthers.selectedProperty().bindBidirectional(entry.attendeesCanSeeOthersProperty()); chkSeeOthers.disableProperty().bind(Bindings.or(entry.getCalendar().readOnlyProperty(), entry.attendeesCanModifyProperty())); HBox checksParent = new HBox(chkEdit, chkInvite, chkSeeOthers); checksParent.getStyleClass().add("checks-parent"); HBox.setHgrow(txtEmail, Priority.ALWAYS); HBox.setHgrow(btAdd, Priority.NEVER); HBox.setHgrow(chkEdit, Priority.ALWAYS); HBox.setHgrow(chkInvite, Priority.ALWAYS); HBox.setHgrow(chkSeeOthers, Priority.ALWAYS); HBox top = new HBox(txtEmail, btAdd); top.getStyleClass().add("top"); VBox center = new VBox(); center.getStyleClass().add("center"); VBox bottom = new VBox(lblTitle, checksParent); bottom.getStyleClass().add("bottom"); BorderPane container = new BorderPane(); container.setTop(top); container.setCenter(center); container.setBottom(bottom); getChildren().add(container); getStyleClass().add("attendees-view"); entry.getAttendees().addListener((Observable obs) -> buildItems(center, entry.getAttendees())); buildItems(center, entry.getAttendees()); } private void createAttendee() { createAttendee(txtEmail.getText()); txtEmail.setText(""); enableButton(); getScene().getWindow().sizeToScene(); } private void enableButton() { txtEmail.pseudoClassStateChanged(INVALID, false); if (entry.getCalendar().isReadOnly()) { btAdd.setDisable(true); return; } if (txtEmail.getText() == null || txtEmail.getText().trim().isEmpty()) { btAdd.setDisable(true); return; } if (!isValidEmail(txtEmail.getText())) { btAdd.setDisable(true); txtEmail.pseudoClassStateChanged(INVALID, true); return; } for (EventAttendee attendee : entry.getAttendees()) { if (attendee.getEmail().equals(txtEmail.getText())) { btAdd.setDisable(true); return; } } btAdd.setDisable(false); } private boolean isValidEmail(String email) { String ePattern = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$"; java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(ePattern); java.util.regex.Matcher matcher = pattern.matcher(email); return matcher.matches(); } private void buildItems(VBox parent, List attendees) { List attendeesNode = Lists.newArrayList(); for (EventAttendee attendee : attendees) { attendeesNode.add(new GoogleEntryAttendeeItem(attendee)); } parent.getChildren().setAll(attendeesNode); } private void createAttendee(String email) { EventAttendee attendee = new EventAttendee(); attendee.setEmail(email); attendee.setDisplayName(email); entry.getAttendees().add(attendee); } private void removeAttendee(EventAttendee attendee) { entry.getAttendees().remove(attendee); } /** * Custom control to display an attendee of the Google Entry. * * @author Gabriel Diaz, 21.02.2015. */ private class GoogleEntryAttendeeItem extends HBox { private final Label optionalIcon; private final Label statusIcon; private final Label name; private final Label removeButton; private final EventAttendee attendee; public GoogleEntryAttendeeItem(EventAttendee attendee) { this.attendee = Objects.requireNonNull(attendee); optionalIcon = new Label(); optionalIcon.setOnMouseClicked(evt -> setOptional(!isOptional())); optionalIcon.getStyleClass().add("button-icon"); optionalIcon.setTooltip(new Tooltip()); statusIcon = new Label(); name = new Label(); name.setMaxWidth(Double.MAX_VALUE); setOptional(Boolean.TRUE.equals(attendee.getOptional())); optionalProperty().addListener(obs -> updateIcon()); updateIcon(); removeButton = new Label(); removeButton.setGraphic(new FontAwesome().create(FontAwesome.Glyph.TRASH_ALT)); removeButton.getStyleClass().add("button-icon"); removeButton.setOnMouseClicked(evt -> removeAttendee(attendee)); HBox.setHgrow(optionalIcon, Priority.NEVER); HBox.setHgrow(name, Priority.ALWAYS); HBox.setHgrow(removeButton, Priority.NEVER); getStyleClass().add("attendee-item"); getChildren().addAll(optionalIcon, statusIcon, name, removeButton); ContextMenu menu = new ContextMenu(); MenuItem optionalItem = new MenuItem("Mark as optional"); optionalItem.setOnAction(evt -> setOptional(true)); MenuItem requiredItem = new MenuItem("Mark as required"); requiredItem.setOnAction(evt -> setOptional(false)); MenuItem removeItem = new MenuItem("Remove attendee"); removeItem.setOnAction(evt -> removeAttendee(attendee)); menu.getItems().addAll(optionalItem, requiredItem, new SeparatorMenuItem(), removeItem); addEventHandler(CONTEXT_MENU_REQUESTED, evt -> menu.show(this, evt.getScreenX(), evt.getScreenY())); } private void updateIcon() { FontAwesome fontAwesome = new FontAwesome(); Glyph img = fontAwesome.create(FontAwesome.Glyph.MALE); img.setOpacity(isOptional() ? 0.4 : 1.0); optionalIcon.setGraphic(img); optionalIcon.getTooltip().setText(isOptional() ? "optional" : "required"); name.setText(attendee.getEmail() + (isOptional() ? " (optional)" : "")); } private final BooleanProperty optional = new SimpleBooleanProperty(this, "optional"); public final BooleanProperty optionalProperty() { return optional; } public final boolean isOptional() { return optionalProperty().get(); } public final void setOptional(boolean optional) { optionalProperty().set(optional); } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/popover/GoogleEntryDetailsView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.popover; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.model.GoogleEntryReminder; import com.calendarfx.google.model.GoogleEntryReminder.RemindMethod; import com.calendarfx.view.DateControl; import com.calendarfx.view.popover.EntryDetailsView; import com.google.common.collect.Lists; import javafx.beans.Observable; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Pos; import javafx.geometry.VPos; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.util.StringConverter; import org.controlsfx.glyphfont.FontAwesome; import java.util.List; import java.util.concurrent.TimeUnit; import static java.util.Objects.requireNonNull; /** * Custom details view which adds some additional controls to edit google entry * specific info. * * @author Gabriel Diaz, 07.03.2015. */ public class GoogleEntryDetailsView extends EntryDetailsView { private final GoogleEntry entry; public GoogleEntryDetailsView(GoogleEntry entry, DateControl dateControl) { super(requireNonNull(entry), dateControl); this.entry = entry; Label notificationLabel = new Label("Notification:"); Label addButton = new Label("Add a notification"); addButton.getStyleClass().add("link"); addButton.setOnMouseClicked(evt -> createReminder()); addButton.disableProperty().bind(entry.getCalendar().readOnlyProperty()); VBox center = new VBox(); BorderPane notificationPane = new BorderPane(); notificationPane.setCenter(center); notificationPane.setBottom(addButton); GridPane box = (GridPane) getChildren().get(0); box.add(notificationLabel, 0, 5); box.add(notificationPane, 1, 5); GridPane.setValignment(notificationLabel, VPos.TOP); getStyleClass().add("details-view"); if (entry.isUseDefaultReminder()) { GoogleCalendar calendar = (GoogleCalendar) entry.getCalendar(); reminders.addAll(calendar.getDefaultReminders()); } reminders.addAll(entry.getReminders()); reminders.addListener((Observable obs) -> buildItems(center)); buildItems(center); } private final ObservableList reminders = FXCollections.observableArrayList(); private void buildItems(VBox parent) { List attendeesNode = Lists.newArrayList(); for (GoogleEntryReminder reminder : reminders) { attendeesNode.add(new GoogleEntryReminderItem(reminder)); } parent.getChildren().setAll(attendeesNode); } private void createReminder() { GoogleEntryReminder reminder = new GoogleEntryReminder(); reminder.setMethod(RemindMethod.POPUP); reminder.setMinutes(10); reminders.add(reminder); entry.getReminders().add(reminder); } private void removeReminder(GoogleEntryReminder reminder) { reminders.remove(reminder); entry.getReminders().remove(reminder); } /** * Control that represents an event reminder item. * * @author Gabriel Diaz, 07.03.2015. */ private class GoogleEntryReminderItem extends HBox { private final GoogleEntryReminder reminder; private final ComboBox methodCombo; private final ComboBox unitCombo; private final TextField valueTxt; private final Label removeIcon; private GoogleEntryReminderItem(GoogleEntryReminder reminder) { this.reminder = requireNonNull(reminder); methodCombo = new ComboBox<>(); methodCombo.getItems().setAll(RemindMethod.values()); methodCombo.disableProperty().bind(entry.getCalendar().readOnlyProperty()); methodCombo.valueProperty().bindBidirectional(reminder.methodProperty()); methodCombo.setConverter(new StringConverter() { @Override public String toString(RemindMethod object) { return object.getName(); } @Override public RemindMethod fromString(String string) { for (RemindMethod method : RemindMethod.values()) { if (method.getName().equals(string)) { return method; } } return null; } }); Integer minutes = reminder.getMinutes(); TimeUnit unit = TimeUnit.MINUTES; if (minutes != null) { if (minutes % 1440 == 0) { unit = TimeUnit.DAYS; minutes = minutes / 1400; } else if (minutes % 60 == 0) { unit = TimeUnit.HOURS; minutes = minutes / 60; } } valueTxt = new TextField(); valueTxt.disableProperty().bind(entry.getCalendar().readOnlyProperty()); valueTxt.setPrefColumnCount(5); valueTxt.setText(minutes == null ? "" : minutes.toString()); valueTxt.textProperty().addListener(obs -> updateMinutes()); unitCombo = new ComboBox<>(); unitCombo.getItems().setAll(TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS); unitCombo.disableProperty().bind(entry.getCalendar().readOnlyProperty()); unitCombo.setValue(unit); unitCombo.valueProperty().addListener(obs -> updateMinutes()); removeIcon = new Label(); removeIcon.getStyleClass().add("button-icon"); removeIcon.setGraphic(new FontAwesome().create(FontAwesome.Glyph.TRASH_ALT)); removeIcon.setOnMouseClicked(evt -> removeReminder(reminder)); removeIcon.disableProperty().bind(entry.getCalendar().readOnlyProperty()); HBox.setHgrow(removeIcon, Priority.NEVER); setAlignment(Pos.CENTER_LEFT); getChildren().addAll(methodCombo, valueTxt, unitCombo, removeIcon); getStyleClass().add("notification-item"); } private void updateMinutes() { Integer minutes = null; try { Integer value = Integer.valueOf(valueTxt.getText()); if (unitCombo.getValue() == TimeUnit.DAYS) { minutes = Math.toIntExact(TimeUnit.DAYS.toMinutes(value)); } else if (unitCombo.getValue() == TimeUnit.HOURS) { minutes = Math.toIntExact(TimeUnit.HOURS.toMinutes(value)); } else { minutes = value; } } catch (NumberFormatException e) { // DO nothing } reminder.setMinutes(minutes); } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/popover/GoogleEntryGMapsFXView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.popover; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.view.popover.EntryPopOverPane; import com.google.code.geocoder.model.GeocoderGeometry; import com.dlsc.gmapsfx.GoogleMapView; import com.dlsc.gmapsfx.MapComponentInitializedListener; import com.dlsc.gmapsfx.javascript.object.GoogleMap; import com.dlsc.gmapsfx.javascript.object.LatLong; import com.dlsc.gmapsfx.javascript.object.MapOptions; import com.dlsc.gmapsfx.javascript.object.MapTypeIdEnum; import com.dlsc.gmapsfx.javascript.object.Marker; import com.dlsc.gmapsfx.javascript.object.MarkerOptions; import javafx.application.Platform; import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.scene.layout.StackPane; /** * Pane that shows in a map the location of the entry. * * Created by gdiaz on 13/01/2017. */ public class GoogleEntryGMapsFXView extends EntryPopOverPane implements MapComponentInitializedListener { private final GoogleEntry entry; private final GoogleMapView mapView = new GoogleMapView(); private final StackPane mapViewWrapper = new StackPane(); private final LocationFXService service = new LocationFXService(); GoogleEntryGMapsFXView(GoogleEntry entry) { this.entry = entry; this.entry.locationProperty().addListener(obs -> updateLocation()); this.mapView.addMapInitializedListener(this); this.mapView.getStyleClass().add("map"); this.mapView.setMouseTransparent(true); this.mapViewWrapper.getChildren().add(mapView); this.mapViewWrapper.setVisible(false); this.mapViewWrapper.managedProperty().bind(this.mapView.visibleProperty()); this.mapViewWrapper.setPrefSize(300, 300); StackPane stackPane = new StackPane(); stackPane.getStyleClass().add("map-view-wrapper"); stackPane.getChildren().add(mapViewWrapper); getChildren().add(stackPane); } @Override public void mapInitialized() { Platform.runLater(this::updateLocation); } private GoogleMap createMap() { MapOptions options = new MapOptions(); options.zoom(15) .overviewMapControl(false) .mapTypeControl(false) .panControl(false) .rotateControl(false) .scaleControl(false) .streetViewControl(false) .zoomControl(false) .mapType(MapTypeIdEnum.ROADMAP); return mapView.createMap(options, false); } private void updateLocation() { service.restart(); } private class LocationFXService extends Service { @Override protected Task createTask() { return new Task() { @Override protected GeocoderGeometry call() throws Exception { Thread.sleep(1000); return GoogleConnector.getInstance().getGeocoderService().locationToCoordinate(entry.getLocation()); } }; } @Override public void start() { super.start(); mapViewWrapper.setVisible(false); } @Override protected void failed() { super.failed(); mapViewWrapper.setVisible(false); } @Override protected void succeeded() { super.succeeded(); GeocoderGeometry geometry = getValue(); if (geometry != null) { LatLong position = new LatLong(geometry.getLocation().getLat().doubleValue(), geometry.getLocation().getLng().doubleValue()); MarkerOptions markerOptions = new MarkerOptions(); markerOptions.position(position); markerOptions.title(entry.getTitle()); Marker marker = new Marker(markerOptions); GoogleMap map = createMap(); map.addMarker(marker); map.setCenter(position); map.panTo(position); mapViewWrapper.setVisible(true); Platform.runLater(() -> Platform.runLater(() -> map.setCenter(position))); } } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/popover/GoogleEntryPopOverContentPane.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.popover; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.model.Calendar; import com.calendarfx.view.DateControl; import com.calendarfx.view.popover.EntryHeaderView; import com.calendarfx.view.popover.EntryPropertiesView; import com.calendarfx.view.popover.PopOverContentPane; import com.calendarfx.view.popover.PopOverTitledPane; import javafx.collections.ObservableList; import static java.util.Objects.requireNonNull; /** * Custom according Popover used to edit the information available in a google * entry. This also allows to move the entry from a calendar to another. * * @author Gabriel Diaz, 07.03.2015. */ public class GoogleEntryPopOverContentPane extends PopOverContentPane { public GoogleEntryPopOverContentPane(GoogleEntry entry, ObservableList allCalendars, DateControl dateControl) { requireNonNull(entry); getStylesheets().add(GoogleEntryPopOverContentPane.class.getResource("google-popover.css").toExternalForm()); EntryHeaderView header = new EntryHeaderView(entry, allCalendars); GoogleEntryDetailsView details = new GoogleEntryDetailsView(entry, dateControl); GoogleEntryAttendeesView attendees = new GoogleEntryAttendeesView(entry); GoogleEntryGMapsFXView mapView = new GoogleEntryGMapsFXView(entry); PopOverTitledPane detailsPane = new PopOverTitledPane("Details", details); PopOverTitledPane attendeesPane = new PopOverTitledPane("Attendees", attendees); if (Boolean.getBoolean("calendarfx.developer")) { EntryPropertiesView properties = new EntryPropertiesView(entry); PopOverTitledPane propertiesPane = new PopOverTitledPane("Properties", properties); propertiesPane.getStyleClass().add("no-padding"); getPanes().addAll(detailsPane, attendeesPane, propertiesPane); } else { getPanes().addAll(detailsPane, attendeesPane); } setHeader(header); setExpandedPane(detailsPane); setFooter(mapView); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/DeleteEntryTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.view.log.ActionType; /** * Task that deletes one entry from google. * * Created by gdiaz on 12/03/2017. */ public final class DeleteEntryTask extends GoogleTask { private final GoogleEntry entry; private final GoogleCalendar calendar; private final GoogleAccount account; public DeleteEntryTask(GoogleEntry entry, GoogleCalendar calendar, GoogleAccount account) { this.entry = entry; this.calendar = calendar; this.account = account; this.logItem.setCalendar(calendar.getName()); this.logItem.setDescription(getDescription()); } @Override public ActionType getAction() { return ActionType.DELETE; } @Override public String getDescription() { return "Delete " + entry; } @Override protected Boolean call() throws Exception { GoogleConnector.getInstance().getCalendarService(account.getId()).deleteEntry(entry, calendar); return true; } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/GoogleTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.view.log.ActionType; import com.calendarfx.google.view.log.LogItem; import com.calendarfx.google.view.log.StatusType; import javafx.concurrent.Task; import java.time.LocalDateTime; /** * Base class for tasks executed for google calendar interaction. * * Created by gdiaz on 28/02/2017. */ public abstract class GoogleTask extends Task { final LogItem logItem; protected GoogleTask() { super(); logItem = new LogItem(); logItem.setTime(LocalDateTime.now()); logItem.setStatus(StatusType.PENDING); logItem.setDescription(getDescription()); logItem.setAction(getAction()); } public LogItem getLogItem() { return logItem; } public abstract ActionType getAction(); public abstract String getDescription(); @Override protected void failed() { logItem.setStatus(StatusType.FAILED); logItem.setException(getException()); } @Override protected void cancelled() { logItem.setStatus(StatusType.CANCELLED); } @Override protected void succeeded() { logItem.setStatus(StatusType.SUCCEEDED); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/InsertCalendarTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.view.log.ActionType; /** * Task that performs an insert operation into google. * * Created by gdiaz on 06.03.2017. */ public final class InsertCalendarTask extends GoogleTask { private final GoogleCalendar calendar; private final GoogleAccount account; public InsertCalendarTask(GoogleCalendar calendar, GoogleAccount account) { this.calendar = calendar; this.account = account; this.logItem.setDescription(getDescription()); } @Override public ActionType getAction() { return ActionType.INSERT; } @Override public String getDescription() { return "Insert " + calendar; } @Override protected GoogleCalendar call() throws Exception { GoogleConnector.getInstance().getCalendarService(account.getId()).insertCalendar(calendar); return calendar; } @Override protected void succeeded() { super.succeeded(); account.getCalendars().add(calendar); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/InsertEntryTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.view.log.ActionType; /** * Task that inserts an entry into google. * * Created by gdiaz on 12/03/2017. */ public final class InsertEntryTask extends GoogleTask { private final GoogleEntry entry; private final GoogleCalendar calendar; private final GoogleAccount account; public InsertEntryTask(GoogleEntry entry, GoogleCalendar calendar, GoogleAccount account) { this.entry = entry; this.calendar = calendar; this.account = account; this.logItem.setCalendar(calendar.getName()); this.logItem.setDescription(getDescription()); } @Override public ActionType getAction() { return ActionType.INSERT; } @Override public String getDescription() { return "Insert " + entry; } @Override protected GoogleEntry call() throws Exception { return GoogleConnector.getInstance().getCalendarService(account.getId()).insertEntry(entry, calendar); } @Override protected void succeeded() { super.succeeded(); calendar.addEntries(entry); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/LoadAllCalendarsTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.view.log.ActionType; import java.util.List; /** * Task that queries all calendars from google and updates the google calendar source. * * Created by gdiaz on 28/02/2017. */ public final class LoadAllCalendarsTask extends GoogleTask> { private final GoogleAccount account; public LoadAllCalendarsTask(GoogleAccount account) { super(); this.account = account; } @Override public ActionType getAction() { return ActionType.LOAD; } @Override public String getDescription() { return "Loading all calendars"; } @Override protected List call() throws Exception { return GoogleConnector.getInstance().getCalendarService(account.getId()).getCalendars(); } @Override protected void succeeded() { super.succeeded(); account.getCalendars().setAll(getValue()); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/LoadEntriesBySliceTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.view.data.GoogleCalendarData; import com.calendarfx.google.view.data.Slice; import com.calendarfx.google.view.log.ActionType; import java.time.ZoneId; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; /** * Task that loads some entries from google for one single calendar using the period provided by some slices. * * Created by gdiaz on 6/03/2017. */ public final class LoadEntriesBySliceTask extends GoogleTask> { private final GoogleAccount account; private final GoogleCalendar calendar; private final GoogleCalendarData entryData; private final Slice slice; private final ZoneId zoneId; public LoadEntriesBySliceTask(GoogleAccount account, GoogleCalendar calendar, GoogleCalendarData entryData, Slice slice, ZoneId zoneId) { super(); this.account = checkNotNull(account); this.calendar = checkNotNull(calendar); this.entryData = checkNotNull(entryData); this.slice = checkNotNull(slice); this.zoneId = checkNotNull(zoneId); this.logItem.setCalendar(calendar.getName()); this.logItem.setDescription(getDescription()); } @Override public ActionType getAction() { return ActionType.LOAD; } @Override public String getDescription() { return "Loading " + slice; } @Override protected List call() throws Exception { return GoogleConnector.getInstance().getCalendarService(account.getId()).getEntries(calendar, slice.getStart(), slice.getEnd(), zoneId); } @Override protected void succeeded() { super.succeeded(); for (GoogleEntry entry : getValue()) { if (!entryData.isLoadedEntry(entry)) { calendar.addEntry(entry); entryData.addLoadedEntry(entry); } } entryData.addLoadedSlice(slice); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/LoadEntriesByTextTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.view.data.GoogleCalendarData; import com.calendarfx.google.view.log.ActionType; import java.util.List; /** * Search entries by text task. * * Created by gdiaz on 21/03/2017. */ public final class LoadEntriesByTextTask extends GoogleTask> { private final String searchText; private final GoogleCalendar calendar; private final GoogleCalendarData data; private final GoogleAccount account; public LoadEntriesByTextTask(String searchText, GoogleCalendar calendar, GoogleCalendarData data, GoogleAccount account) { this.searchText = searchText; this.calendar = calendar; this.data = data; this.account = account; this.logItem.setCalendar(calendar.getName()); this.logItem.setDescription(getDescription()); } @Override public ActionType getAction() { return ActionType.LOAD; } @Override public String getDescription() { return "Loading \"" + searchText + "\""; } @Override protected List call() throws Exception { return GoogleConnector.getInstance().getCalendarService(account.getId()).getEntries(calendar, searchText); } @Override protected void succeeded() { super.succeeded(); for (GoogleEntry entry : getValue()) { if (!data.isLoadedEntry(entry)) { calendar.addEntry(entry); data.addLoadedEntry(entry); } } data.addLoadedSearchText(searchText); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/MoveEntryTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.view.log.ActionType; /** * Moves an entry from one calendar to another. * * Created by gdiaz on 19/03/2017. */ public final class MoveEntryTask extends GoogleTask { private final GoogleEntry entry; private final GoogleCalendar from; private final GoogleCalendar to; private final GoogleAccount account; public MoveEntryTask(GoogleEntry entry, GoogleCalendar from, GoogleCalendar to, GoogleAccount account) { this.entry = entry; this.from = from; this.to = to; this.account = account; this.logItem.setCalendar(from.getName()); this.logItem.setDescription(getDescription()); } @Override public ActionType getAction() { return ActionType.MOVE; } @Override public String getDescription() { return "Moving " + entry + " to " + to; } @Override protected GoogleEntry call() throws Exception { return GoogleConnector.getInstance().getCalendarService(account.getId()).moveEntry(entry, from, to); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/RefreshCalendarsTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.view.data.GoogleCalendarData; import com.calendarfx.google.view.data.IGoogleCalendarDataProvider; import com.calendarfx.google.view.log.ActionType; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Task that allows to refresh the data of a single calendar. * * Created by gdiaz on 10/04/2017. */ public final class RefreshCalendarsTask extends GoogleTask> { private final GoogleAccount account; private final IGoogleCalendarDataProvider provider; public RefreshCalendarsTask(GoogleAccount account, IGoogleCalendarDataProvider provider) { this.account = account; this.provider = provider; } @Override public ActionType getAction() { return ActionType.REFRESH; } @Override public String getDescription() { return "Refreshing all calendars"; } @Override protected List call() throws Exception { return GoogleConnector.getInstance().getCalendarService(account.getId()).getCalendars(); } @Override protected void succeeded() { super.succeeded(); List oldCalendars = account.getGoogleCalendars(); List newCalendars = getValue(); List updCalendars = new ArrayList<>(); for (Iterator newIterator = newCalendars.iterator(); newIterator.hasNext(); ) { GoogleCalendar newCalendar = newIterator.next(); for (Iterator oldIterator = oldCalendars.iterator(); oldIterator.hasNext(); ) { GoogleCalendar oldCalendar = oldIterator.next(); if (newCalendar.equals(oldCalendar)) { oldIterator.remove(); newIterator.remove(); updCalendars.add(oldCalendar); GoogleCalendarData data = provider.getCalendarData(oldCalendar); if (data != null) { data.clear(); } break; } } } updCalendars.forEach(GoogleCalendar::clear); account.getCalendars().removeAll(oldCalendars); account.getCalendars().addAll(newCalendars); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/task/UpdateEntryTask.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.task; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.view.log.ActionType; import java.util.Map; /** * Task that updates one entry in google. * * Created by gdiaz on 12/03/2017. */ public final class UpdateEntryTask extends GoogleTask { private static final long TWO_SECONDS = 2 * 1000; private GoogleEntry entry; private final GoogleAccount account; private final Map updateTasks; public UpdateEntryTask(GoogleEntry entry, GoogleAccount account, Map updateTasks) { this.entry = entry; this.account = account; this.updateTasks = updateTasks; this.logItem.setCalendar(entry.getCalendar().getName()); this.logItem.setDescription(getDescription()); } public void append(GoogleEntry newVersion) { assert (entry.equals(newVersion)); this.entry = newVersion; this.logItem.setDescription(getDescription()); } @Override public ActionType getAction() { return ActionType.UPDATE; } @Override public String getDescription() { return "Update " + entry; } @Override protected GoogleEntry call() throws Exception { Thread.sleep(TWO_SECONDS); return GoogleConnector.getInstance().getCalendarService(account.getId()).updateEntry(entry); } @Override protected void succeeded() { super.succeeded(); updateTasks.remove(entry); } @Override protected void cancelled() { super.cancelled(); updateTasks.remove(entry); } @Override protected void failed() { super.failed(); updateTasks.remove(entry); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/thread/CalendarViewTimeUpdateThread.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.thread; import com.calendarfx.view.CalendarView; import javafx.application.Platform; import java.time.LocalDate; import java.time.LocalTime; /** * Thread that updates the current tine on the calendar view * * Created by gdiaz on 4/05/2017. */ public class CalendarViewTimeUpdateThread extends Thread { private static final int TEN_SECONDS = 10000; private final CalendarView calendarView; public CalendarViewTimeUpdateThread(CalendarView calendarView) { super("Google-Calendar-Update Current Time"); this.calendarView = calendarView; setPriority(MIN_PRIORITY); setDaemon(true); } @Override @SuppressWarnings("InfiniteLoopStatement") public void run() { while (true) { Platform.runLater(() -> { calendarView.setToday(LocalDate.now()); calendarView.setTime(LocalTime.now()); }); try { sleep(TEN_SECONDS); } catch (InterruptedException e) { // Do nothing } } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/thread/GoogleAutoRefreshThread.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.thread; import com.calendarfx.google.service.SecurityService; import com.calendarfx.google.view.data.IGoogleCalendarDataProvider; import com.calendarfx.google.view.task.RefreshCalendarsTask; import com.google.common.base.Preconditions; /** * Thread that performs the automatic refreshing. * * Created by gdiaz on 5/05/2017. */ public class GoogleAutoRefreshThread extends Thread { private final Object LOCK = new Object(); private final IGoogleCalendarDataProvider provider; private RefreshCalendarsTask task; private long delay; public GoogleAutoRefreshThread(IGoogleCalendarDataProvider provider) { this.provider = provider; this.delay = RefreshInterval.EVERY_5_MINUTES.getTime(); setName("Google-Calendar-Auto refresh Thread"); setPriority(NORM_PRIORITY); setDaemon(true); } public long getDelay() { return delay; } public void setDelay(long delay) { Preconditions.checkArgument(delay >= 0); synchronized (LOCK) { this.delay = delay; } } public void restart() { synchronized (LOCK) { if (task != null) { task.cancel(); task = null; } } interrupt(); } @Override @SuppressWarnings("InfiniteLoopStatement") public void run() { while (true) { try { long wait; synchronized (LOCK) { wait = delay; } if (wait > 0) { sleep(wait); } } catch (InterruptedException e) { // Do nothing } synchronized (LOCK) { if (delay > 0 && SecurityService.getInstance().isLoggedIn()) { task = new RefreshCalendarsTask(SecurityService.getInstance().getLoggedAccount(), provider); GoogleTaskExecutor.getInstance().execute(task); } } } } /** * Enum used to determine the internals of refreshing. */ public enum RefreshInterval { NEVER(0, "Never"), EVERY_MINUTE(1000 * 60, "Every Minute"), EVERY_2_MINUTES(1000 * 60 * 2, "Every 2 Minutes"), EVERY_5_MINUTES(1000 * 60 * 5, "Every 5 Minutes"), EVERY_10_MINUTES(1000 * 60 * 10, "Every 10 Minutes"), EVERY_30_MINUTES(1000 * 60 * 30, "Every 30 Minutes"); private final long time; private final String name; RefreshInterval(long time, String name) { this.time = time; this.name = name; } public long getTime() { return time; } public String getName() { return name; } } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/thread/GoogleNotificationPopupThread.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.thread; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.model.GoogleEntryReminder; import com.calendarfx.google.service.SecurityService; import com.calendarfx.model.Entry; import com.calendarfx.view.CalendarView; import com.google.common.collect.Lists; import javafx.application.Platform; import org.controlsfx.control.Notifications; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; /** * Thread that shows notifications of entries that are about to happen. * * Created by gdiaz on 6/05/2017. */ public class GoogleNotificationPopupThread extends Thread { private static final long ONE_MINUTE = 1000 * 30; private final CalendarView calendarView; public GoogleNotificationPopupThread(CalendarView calendarView) { this.calendarView = calendarView; setDaemon(true); setName("Google-Notification-Thread"); setPriority(NORM_PRIORITY); } @SuppressWarnings("InfiniteLoopStatement") @Override public void run() { while (true) { try { sleep(ONE_MINUTE); } catch (InterruptedException e) { // Do nothing } if (SecurityService.getInstance().isLoggedIn()) { GoogleAccount account = SecurityService.getInstance().getLoggedAccount(); LocalDate today = calendarView.getToday(); LocalDateTime now = calendarView.getToday().atTime(LocalTime.now()); for (GoogleCalendar calendar : account.getGoogleCalendars()) { Map>> entries = calendar.findEntries(today, today, calendarView.getZoneId()); for (List> list : entries.values()) { for (Entry e : list) { if (e instanceof GoogleEntry) { GoogleEntry entry = (GoogleEntry) e; if (isSubjectOfNotification(entry, now)) { showNotification(entry); } } } } } } } } private boolean isSubjectOfNotification(GoogleEntry entry, LocalDateTime now) { List reminders = Lists.newArrayList(); reminders.addAll(entry.getReminders()); if (reminders.isEmpty()) { reminders.addAll(((GoogleCalendar) entry.getCalendar()).getDefaultReminders()); } for (GoogleEntryReminder reminder : reminders) { if (reminder.getMethod() != GoogleEntryReminder.RemindMethod.POPUP) { continue; } if (reminder.getMinutes() == null || reminder.getMinutes() < 0) { continue; } if (!now.isBefore(entry.getStartAsLocalDateTime())) { continue; } long distanceMinutes = now.until(entry.getStartAsLocalDateTime(), ChronoUnit.MINUTES); if (distanceMinutes == reminder.getMinutes()) { return true; } } return false; } private void showNotification(GoogleEntry entry) { Platform.runLater(() -> { GoogleCalendar calendar = (GoogleCalendar) entry.getCalendar(); Notifications.create().title(calendar.getName()).text(entry.getTitle()).showInformation(); }); } } ================================================ FILE: CalendarFXGoogle/src/main/java/com/calendarfx/google/view/thread/GoogleTaskExecutor.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.thread; import com.calendarfx.google.view.log.LogItem; import com.calendarfx.google.view.task.GoogleTask; import com.google.common.util.concurrent.ThreadFactoryBuilder; import impl.com.calendarfx.view.util.Util; import javafx.beans.binding.Bindings; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.WorkerStateEvent; import javafx.event.EventHandler; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Executor class that allows to have some background tasks executed. * * Created by gdiaz on 22/02/2017. */ public final class GoogleTaskExecutor { private static GoogleTaskExecutor instance; public static GoogleTaskExecutor getInstance() { if (instance == null) { instance = new GoogleTaskExecutor(); } return instance; } private final IntegerProperty pendingTasks = new SimpleIntegerProperty(this, "pendingTasks", 0); private final ExecutorService executor = Executors.newFixedThreadPool(5, new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("Google-Calendar-Executor-%d") .setPriority(Thread.MIN_PRIORITY) .build()); private GoogleTaskExecutor() { super(); progress.bind(Bindings.divide( Bindings.when(pendingTasks.isEqualTo(0)).then(0).otherwise(1), Bindings.when(pendingTasks.isEqualTo(0)).then(Double.MAX_VALUE).otherwise(pendingTasks)) ); } private final ObservableList log = FXCollections.observableArrayList(); public ObservableList getLog() { return log; } public void clearLog() { log.clear(); } private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", 0); public ReadOnlyDoubleProperty progressProperty() { return progress.getReadOnlyProperty(); } public void execute(GoogleTask task) { updatePendingTasks(task); executor.submit(task); } public void executeImmediate(GoogleTask task) { updatePendingTasks(task); task.run(); } private void updatePendingTasks(GoogleTask task) { Util.runInFXThread(() -> { pendingTasks.set(pendingTasks.get() + 1); EventHandler handler = evt -> pendingTasks.set(pendingTasks.get() - 1); task.setOnFailed(handler); task.setOnSucceeded(handler); log.add(0, task.getLogItem()); }); } } ================================================ FILE: CalendarFXGoogle/src/main/java/impl/com/calendarfx/google/view/GoogleCalendarAppViewSkin.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package impl.com.calendarfx.google.view; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.service.GoogleConnector; import com.calendarfx.google.service.SecurityService; import com.calendarfx.google.view.GoogleCalendarAppView; import com.calendarfx.google.view.popover.GoogleEntryPopOverContentPane; import com.calendarfx.google.view.task.InsertCalendarTask; import com.calendarfx.google.view.task.InsertEntryTask; import com.calendarfx.google.view.task.LoadAllCalendarsTask; import com.calendarfx.google.view.thread.CalendarViewTimeUpdateThread; import com.calendarfx.google.view.thread.GoogleAutoRefreshThread; import com.calendarfx.google.view.thread.GoogleNotificationPopupThread; import com.calendarfx.google.view.thread.GoogleTaskExecutor; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.model.LoadEvent; import com.calendarfx.view.AllDayView; import com.calendarfx.view.CalendarView; import com.calendarfx.view.DateControl; import com.calendarfx.view.DeveloperConsole; import com.calendarfx.view.VirtualGrid; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.WeakInvalidationListener; import javafx.beans.binding.Bindings; import javafx.scene.Node; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.control.RadioMenuItem; import javafx.scene.control.SkinBase; import javafx.scene.control.Tab; import javafx.scene.control.ToggleGroup; import javafx.scene.input.KeyCombination; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.web.WebView; import javafx.stage.Window; import javafx.util.Callback; import org.controlsfx.control.PopOver; import org.controlsfx.control.StatusBar; import java.net.CookieHandler; import java.net.CookieManager; import java.time.DayOfWeek; import java.time.Duration; import java.time.ZonedDateTime; import java.util.function.Consumer; /** * Skin class for the {@link GoogleCalendarAppView} control, which allows to display * the Google Login pane. * * @author Gabriel Diaz, 14.02.2015. */ public class GoogleCalendarAppViewSkin extends SkinBase { private final WebView loginView; private final BorderPane calendarPane; private final CalendarView calendarView; private final CookieManager cookieManager; private final GoogleCalendarDataManager dataManager; private final GoogleSyncManager syncManager; private final GoogleCalendarSearchTextManager searchProvider; public GoogleCalendarAppViewSkin(GoogleCalendarAppView control) { super(control); calendarView = control.getCalendarView(); dataManager = new GoogleCalendarDataManager(); cookieManager = new CookieManager(); syncManager = new GoogleSyncManager(); searchProvider = new GoogleCalendarSearchTextManager(dataManager); CalendarViewTimeUpdateThread timeUpdateThread = new CalendarViewTimeUpdateThread(calendarView); GoogleAutoRefreshThread autoRefreshThread = new GoogleAutoRefreshThread(dataManager); GoogleNotificationPopupThread notificationThread = new GoogleNotificationPopupThread(calendarView); loginView = new WebView(); loginView.setVisible(false); loginView.getEngine().titleProperty().addListener((obs, oldValue, newValue) -> { if (newValue != null && newValue.contains("Success code=")) { String code = newValue.split("code=")[1]; if (SecurityService.getInstance().authorize(code)) { login(); } } }); Bindings.bindContentBidirectional(control.getLogPane().getItems(), GoogleTaskExecutor.getInstance().getLog()); StatusBar statusBar = new StatusBar(); statusBar.textProperty().bind(Bindings.when(GoogleTaskExecutor.getInstance().progressProperty().isEqualTo(0)).then("").otherwise("Loading...")); statusBar.progressProperty().bind(GoogleTaskExecutor.getInstance().progressProperty()); calendarView.addEventFilter(LoadEvent.LOAD, dataManager); calendarView.setEntryFactory(new GoogleEntryCreateCallback()); calendarView.setCalendarSourceFactory(new GoogleCalendarCreateCallback(control.getScene().getWindow())); calendarView.setEntryDetailsPopOverContentCallback(new GoogleEntryPopOverContentProvider()); calendarPane = new BorderPane(); calendarPane.setTop(createMenuBar(autoRefreshThread)); calendarPane.setCenter(calendarView); calendarPane.setBottom(statusBar); DeveloperConsole developerConsole = calendarView.getDeveloperConsole(); if (developerConsole != null) { developerConsole.getTabPane().getTabs().add(new Tab("Google", control.getLogPane())); } getChildren().add(new StackPane(loginView, calendarPane)); CookieHandler.setDefault(cookieManager); timeUpdateThread.start(); autoRefreshThread.start(); notificationThread.start(); attemptAutoLogin(); } private MenuBar createMenuBar(GoogleAutoRefreshThread autoRefreshThread) { MenuItem logoutItem = new MenuItem("Logout"); logoutItem.setOnAction(evt -> logout()); MenuItem exitItem = new MenuItem("Exit"); exitItem.setAccelerator(KeyCombination.keyCombination("shortcut+q")); exitItem.setOnAction(evt -> Platform.exit()); Menu fileMenu = new Menu("File"); fileMenu.getItems().add(logoutItem); fileMenu.getItems().add(exitItem); MenuItem refreshItem = new MenuItem("Refresh"); refreshItem.setOnAction(evt -> autoRefreshThread.restart()); refreshItem.setAccelerator(KeyCombination.keyCombination("F5")); ToggleGroup intervalGroup = new ToggleGroup(); Menu autoRefreshItem = new Menu("Auto Refresh"); for (GoogleAutoRefreshThread.RefreshInterval interval : GoogleAutoRefreshThread.RefreshInterval.values()) { RadioMenuItem intervalItem = new RadioMenuItem(interval.getName()); intervalItem.setOnAction(evt -> autoRefreshThread.setDelay(interval.getTime())); intervalItem.setToggleGroup(intervalGroup); intervalItem.setSelected(interval.getTime() == autoRefreshThread.getDelay()); autoRefreshItem.getItems().add(intervalItem); } Menu viewMenu = new Menu("View"); viewMenu.getItems().addAll(refreshItem, autoRefreshItem); MenuBar menuBar = new MenuBar(); menuBar.setUseSystemMenuBar(true); menuBar.getMenus().add(fileMenu); menuBar.getMenus().add(viewMenu); return menuBar; } private void attemptAutoLogin() { if (SecurityService.getInstance().isAuthorized()) { login(); } else { showLoginView(); } } private void login() { GoogleAccount account = SecurityService.getInstance().login(); if (account != null) { calendarView.getCalendarSources().setAll(account); account.addCalendarListeners(dataManager, searchProvider, syncManager); GoogleTaskExecutor.getInstance().execute(new LoadAllCalendarsTask(account)); showCalendarPane(); } else { showLoginView(); } } private void logout() { if (SecurityService.getInstance().isLoggedIn()) { GoogleAccount account = SecurityService.getInstance().getLoggedAccount(); calendarView.getCalendarSources().clear(); dataManager.clearData(); account.removeCalendarListeners(dataManager, searchProvider, syncManager); GoogleTaskExecutor.getInstance().clearLog(); SecurityService.getInstance().logout(); } showLoginView(); } private void showLoginView() { cookieManager.getCookieStore().removeAll(); loginView.getEngine().load(GoogleConnector.getInstance().getAuthorizationURL()); loginView.setVisible(true); calendarPane.setVisible(false); } private void showCalendarPane() { cookieManager.getCookieStore().removeAll(); loginView.getEngine().load(GoogleConnector.getInstance().getAuthorizationURL()); loginView.setVisible(false); calendarPane.setVisible(true); } /** * Factory for calendars. * * Created by gdiaz on 5/05/2017. */ private static class GoogleCalendarCreateCallback implements Callback, Consumer { private final Window owner; GoogleCalendarCreateCallback(Window owner) { this.owner = owner; } @Override public CalendarSource call(DateControl.CreateCalendarSourceParameter param) { if (SecurityService.getInstance().isLoggedIn()) { GoogleCalendarCreateView view = new GoogleCalendarCreateView(this); view.show(owner); } return null; } @Override public void accept(GoogleCalendarCreateView.CalendarViewBean bean) { GoogleAccount account = SecurityService.getInstance().getLoggedAccount(); GoogleCalendar calendar = account.createCalendar(bean.getName(), bean.getStyle()); GoogleTaskExecutor.getInstance().execute(new InsertCalendarTask(calendar, account)); } } /** * Provider of the google entry pop over content. * * Created by gdiaz on 5/05/2017. */ private static class GoogleEntryPopOverContentProvider implements Callback { private PopOver popOver; private GoogleEntry entry; private final InvalidationListener detachListener = obs -> { if (getEntry().isFullDay() && !getPopOver().isDetached()) { getPopOver().setDetached(true); } }; private final WeakInvalidationListener weakListener = new WeakInvalidationListener(detachListener); @Override public Node call(DateControl.EntryDetailsPopOverContentParameter param) { popOver = param.getPopOver(); entry = (GoogleEntry) param.getEntry(); entry.fullDayProperty().addListener(weakListener); popOver.setOnHidden(evt -> entry.fullDayProperty().removeListener(weakListener)); return new GoogleEntryPopOverContentPane(entry, param.getDateControl().getCalendars(), param.getDateControl()); } public PopOver getPopOver() { return popOver; } public GoogleEntry getEntry() { return entry; } } /** * Factory for google entries. * * Created by gdiaz on 5/05/2017. */ private static class GoogleEntryCreateCallback implements Callback> { @Override public Entry call(DateControl.CreateEntryParameter param) { if (SecurityService.getInstance().isLoggedIn()) { GoogleAccount account = SecurityService.getInstance().getLoggedAccount(); GoogleCalendar primaryCalendar = account.getPrimaryCalendar(); if (primaryCalendar != null) { DateControl control = param.getDateControl(); VirtualGrid grid = control.getVirtualGrid(); ZonedDateTime start = param.getZonedDateTime(); DayOfWeek firstDayOfWeek = control.getFirstDayOfWeek(); ZonedDateTime lowerTime = grid.adjustTime(start, false, firstDayOfWeek); ZonedDateTime upperTime = grid.adjustTime(start, true, firstDayOfWeek); if (Duration.between(start, lowerTime).abs().minus(Duration.between(start, upperTime).abs()).isNegative()) { start = lowerTime; } GoogleEntry entry = primaryCalendar.createEntry(start, control instanceof AllDayView); GoogleTaskExecutor.getInstance().execute(new InsertEntryTask(entry, primaryCalendar, account)); } } return null; } } } ================================================ FILE: CalendarFXGoogle/src/main/java/impl/com/calendarfx/google/view/GoogleCalendarCreateView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package impl.com.calendarfx.google.view; import com.calendarfx.model.Calendar; import com.calendarfx.view.CalendarView; import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ButtonBar; import javafx.scene.control.ComboBox; import javafx.scene.control.ContentDisplay; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.Separator; import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; import javafx.stage.Modality; import javafx.stage.Stage; import javafx.stage.Window; import java.util.function.Consumer; /** * Pane that allows to enter a web URL of an iCal. * * Created by gdiaz on 5/01/2017. */ final class GoogleCalendarCreateView extends BorderPane { private final TextField nameField; private final ComboBox styleComboBox; private Stage dialog; GoogleCalendarCreateView(Consumer onAccept) { nameField = new TextField(); styleComboBox = new ComboBox<>(); styleComboBox.getItems().setAll(Calendar.Style.values()); styleComboBox.setButtonCell(new StyleCell()); styleComboBox.setCellFactory(listView -> new StyleCell()); Button acceptButton = new Button("Accept"); acceptButton.disableProperty().bind(Bindings.or(Bindings.isEmpty(nameField.textProperty()), Bindings.isNull(styleComboBox.valueProperty()))); acceptButton.setOnAction(evt -> { if (onAccept != null) { CalendarViewBean bean = new CalendarViewBean(); bean.setName(nameField.getText()); bean.setStyle(styleComboBox.getValue()); onAccept.accept(bean); } close(); }); Button cancelButton = new Button("Cancel"); cancelButton.setOnAction(evt -> close()); GridPane gridPane = new GridPane(); gridPane.add(new Label("Name"), 0, 0); gridPane.add(nameField, 1, 0); gridPane.add(new Label("Color"), 0, 1); gridPane.add(styleComboBox, 1, 1); gridPane.getStyleClass().add("center"); gridPane.setVgap(5); gridPane.setHgap(5); gridPane.setPadding(new Insets(10)); GridPane.setHgrow(nameField, Priority.ALWAYS); GridPane.setHgrow(styleComboBox, Priority.ALWAYS); ButtonBar buttonBar = new ButtonBar(); buttonBar.getButtons().addAll(acceptButton, cancelButton); VBox bottomPane = new VBox(); bottomPane.getChildren().addAll(new Separator(), buttonBar); bottomPane.getStyleClass().add("bottom"); bottomPane.setFillWidth(true); bottomPane.setSpacing(10); setCenter(gridPane); setBottom(bottomPane); setPadding(new Insets(15)); setPrefWidth(300); getStylesheets().add(CalendarView.class.getResource("calendar.css").toExternalForm()); } void show(Window owner) { if (dialog == null) { dialog = new Stage(); dialog.initOwner(owner); dialog.setScene(new Scene(this)); dialog.sizeToScene(); dialog.centerOnScreen(); dialog.setTitle("Add Calendar"); dialog.initModality(Modality.APPLICATION_MODAL); } dialog.showAndWait(); } private void close() { nameField.setText(null); styleComboBox.setValue(null); if (dialog != null) { dialog.hide(); } } private static class StyleCell extends ListCell { @Override protected void updateItem(Calendar.Style item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { Rectangle icon = new Rectangle(12, 12); icon.getStyleClass().add(item.name().toLowerCase() + "-icon"); setGraphic(icon); setContentDisplay(ContentDisplay.GRAPHIC_ONLY); } } } static class CalendarViewBean { private String name; private Calendar.Style style; Calendar.Style getStyle() { return style; } void setStyle(Calendar.Style style) { this.style = style; } String getName() { return name; } void setName(String name) { this.name = name; } } } ================================================ FILE: CalendarFXGoogle/src/main/java/impl/com/calendarfx/google/view/GoogleCalendarDataManager.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package impl.com.calendarfx.google.view; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.service.SecurityService; import com.calendarfx.google.view.data.GoogleCalendarData; import com.calendarfx.google.view.data.IGoogleCalendarDataProvider; import com.calendarfx.google.view.data.Slice; import com.calendarfx.google.view.task.LoadEntriesBySliceTask; import com.calendarfx.google.view.thread.GoogleTaskExecutor; import com.calendarfx.model.Calendar; import com.calendarfx.model.LoadEvent; import javafx.collections.ListChangeListener; import javafx.event.EventHandler; import java.time.ZoneId; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Loader class for calendars and entries. * * Created by gdiaz on 27/02/2017. */ final class GoogleCalendarDataManager implements IGoogleCalendarDataProvider, ListChangeListener, EventHandler { private final Map calendarsData = new HashMap<>(); GoogleCalendarDataManager() { super(); } @Override public GoogleCalendarData getCalendarData(GoogleCalendar calendar, boolean create) { GoogleCalendarData data = calendarsData.get(calendar); if (data == null && create) { data = new GoogleCalendarData(); calendarsData.put(calendar, data); } return data; } @Override public void removeCalendarData(GoogleCalendar calendar) { calendarsData.remove(calendar); } @Override public void clearData() { calendarsData.clear(); } @Override public void onChanged(Change c) { List removed = new ArrayList<>(); while (c.next()) { for (Calendar calendar : c.getRemoved()) { if (calendar instanceof GoogleCalendar) { removed.add((GoogleCalendar) calendar); } } for (Calendar calendar : c.getAddedSubList()) { if (calendar instanceof GoogleCalendar) { removed.remove(calendar); } } } for (GoogleCalendar calendar : removed) { removeCalendarData(calendar); } } @Override public void handle(LoadEvent evt) { if (SecurityService.getInstance().isLoggedIn()) { List slices = Slice.split(evt.getStartDate(), evt.getEndDate()); if (!slices.isEmpty()) { ZoneId zoneId = evt.getZoneId(); GoogleAccount account = SecurityService.getInstance().getLoggedAccount(); for (GoogleCalendar cal : account.getGoogleCalendars()) { GoogleCalendarData data = getCalendarData(cal, true); List unloaded = data.getUnloadedSlices(slices); if (!unloaded.isEmpty()) { data.addInProgressSlices(unloaded); for (Slice us : unloaded) { LoadEntriesBySliceTask task = new LoadEntriesBySliceTask(account, cal, data, us, zoneId); GoogleTaskExecutor.getInstance().execute(task); } } } } } } } ================================================ FILE: CalendarFXGoogle/src/main/java/impl/com/calendarfx/google/view/GoogleCalendarSearchTextManager.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package impl.com.calendarfx.google.view; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.IGoogleCalendarSearchTextProvider; import com.calendarfx.google.service.SecurityService; import com.calendarfx.google.view.data.GoogleCalendarData; import com.calendarfx.google.view.data.IGoogleCalendarDataProvider; import com.calendarfx.google.view.task.LoadEntriesByTextTask; import com.calendarfx.google.view.thread.GoogleTaskExecutor; import com.calendarfx.model.Calendar; import javafx.collections.ListChangeListener; /** * Default implementation of the provider that searches entries in google synchronously. * * Created by gdiaz on 5/05/2017. */ final class GoogleCalendarSearchTextManager implements IGoogleCalendarSearchTextProvider, ListChangeListener { private final IGoogleCalendarDataProvider provider; GoogleCalendarSearchTextManager(IGoogleCalendarDataProvider provider) { this.provider = provider; } @Override public void search(GoogleCalendar calendar, String text) { if (SecurityService.getInstance().isLoggedIn()) { GoogleCalendarData data = provider.getCalendarData(calendar, true); if (!data.isLoadedSearchText(text)) { GoogleAccount account = SecurityService.getInstance().getLoggedAccount(); LoadEntriesByTextTask task = new LoadEntriesByTextTask(text, calendar, data, account); GoogleTaskExecutor.getInstance().executeImmediate(task); } } } @Override public void onChanged(Change c) { while (c.next()) { for (Calendar calendar : c.getRemoved()) { if (calendar instanceof GoogleCalendar) { ((GoogleCalendar) calendar).setSearchTextProvider(null); } } for (Calendar calendar : c.getAddedSubList()) { if (calendar instanceof GoogleCalendar) { ((GoogleCalendar) calendar).setSearchTextProvider(this); } } } } } ================================================ FILE: CalendarFXGoogle/src/main/java/impl/com/calendarfx/google/view/GoogleSyncManager.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package impl.com.calendarfx.google.view; import com.calendarfx.google.model.GoogleAccount; import com.calendarfx.google.model.GoogleCalendar; import com.calendarfx.google.model.GoogleCalendarEvent; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.google.service.SecurityService; import com.calendarfx.google.view.task.DeleteEntryTask; import com.calendarfx.google.view.task.InsertEntryTask; import com.calendarfx.google.view.task.MoveEntryTask; import com.calendarfx.google.view.task.UpdateEntryTask; import com.calendarfx.google.view.thread.GoogleTaskExecutor; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarEvent; import com.calendarfx.model.Entry; import javafx.collections.ListChangeListener; import javafx.event.EventHandler; import java.util.HashMap; import java.util.Map; /** * Class in charge of receiving calendar events and synchronize data between the app and google. * * Created by gdiaz on 06/03/2017. */ final class GoogleSyncManager implements EventHandler, ListChangeListener { private final Map updateTasks = new HashMap<>(); @Override public void onChanged(Change c) { while (c.next()) { for (Calendar calendar : c.getRemoved()) { calendar.removeEventHandler(this); } for (Calendar calendar : c.getAddedSubList()) { calendar.addEventHandler(this); } } } @Override public void handle(CalendarEvent evt) { if (SecurityService.getInstance().isLoggedIn()) { GoogleAccount account = SecurityService.getInstance().getLoggedAccount(); if (requiresInsertEntry(evt)) { insertEntry(evt, account); } else if (requiresDeleteEntry(evt)) { deleteEntry(evt, account); } else if (requiresUpdateEntry(evt)) { updateEntry(evt, account); } else if (requiresMoveEntry(evt)) { moveEntry(evt, account); } } } private void insertEntry(CalendarEvent evt, GoogleAccount account) { GoogleEntry entry = (GoogleEntry) evt.getEntry(); GoogleCalendar calendar = (GoogleCalendar) evt.getCalendar(); GoogleTaskExecutor.getInstance().execute(new InsertEntryTask(entry, calendar, account)); } private void updateEntry(CalendarEvent evt, GoogleAccount account) { GoogleEntry entry = (GoogleEntry) evt.getEntry(); UpdateEntryTask updateTask = updateTasks.get(entry); if (updateTask == null) { updateTask = new UpdateEntryTask(entry, account, updateTasks); updateTasks.put(entry, updateTask); GoogleTaskExecutor.getInstance().execute(updateTask); } else { updateTask.append(entry); } } private void deleteEntry(CalendarEvent evt, GoogleAccount account) { GoogleEntry entry = (GoogleEntry) evt.getEntry(); GoogleCalendar calendar = (GoogleCalendar) evt.getOldCalendar(); GoogleTaskExecutor.getInstance().execute(new DeleteEntryTask(entry, calendar, account)); } private void moveEntry(CalendarEvent evt, GoogleAccount account) { GoogleEntry entry = (GoogleEntry) evt.getEntry(); GoogleCalendar from = (GoogleCalendar) evt.getOldCalendar(); GoogleCalendar to = (GoogleCalendar) evt.getCalendar(); GoogleTaskExecutor.getInstance().execute(new MoveEntryTask(entry, from, to, account)); } private boolean requiresInsertEntry(CalendarEvent evt) { if (evt != null && evt.getEventType().equals(GoogleCalendarEvent.ENTRY_CALENDAR_CHANGED) && evt.getCalendar() != null && evt.getOldCalendar() == null) { Entry entry = evt.getEntry(); if (entry instanceof GoogleEntry && !entry.isRecurrence()) { return !((GoogleEntry) entry).existsInGoogle(); } } return false; } private boolean requiresDeleteEntry(CalendarEvent evt) { if (evt != null && evt.getEventType().equals(GoogleCalendarEvent.ENTRY_CALENDAR_CHANGED) && evt.getCalendar() == null && evt.getOldCalendar() != null) { Entry entry = evt.getEntry(); if (entry instanceof GoogleEntry && !entry.isRecurrence()) { return ((GoogleEntry) entry).existsInGoogle(); } } return false; } private boolean requiresUpdateEntry(CalendarEvent evt) { if (evt != null && !evt.getEventType().equals(GoogleCalendarEvent.ENTRY_CALENDAR_CHANGED) && evt.getEventType().getSuperType().equals(GoogleCalendarEvent.ENTRY_CHANGED)) { Entry entry = evt.getEntry(); if (entry instanceof GoogleEntry && !entry.isRecurrence()) { return ((GoogleEntry) entry).existsInGoogle(); } } return false; } private boolean requiresMoveEntry(CalendarEvent evt) { if (evt != null && evt.getEventType().equals(GoogleCalendarEvent.ENTRY_CALENDAR_CHANGED) && evt.getCalendar() != null && evt.getOldCalendar() != null) { Entry entry = evt.getEntry(); if (entry instanceof GoogleEntry && !entry.isRecurrence()) { return ((GoogleEntry) entry).existsInGoogle(); } } return false; } } ================================================ FILE: CalendarFXGoogle/src/main/java/impl/com/calendarfx/google/view/log/LogPaneSkin.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package impl.com.calendarfx.google.view.log; import com.calendarfx.google.view.log.ActionType; import com.calendarfx.google.view.log.LogItem; import com.calendarfx.google.view.log.LogPane; import com.calendarfx.google.view.log.StatusType; import com.google.api.client.util.Lists; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.event.Event; import javafx.geometry.Side; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; import javafx.scene.control.ContentDisplay; import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.control.SkinBase; import javafx.scene.control.TableView; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToolBar; import javafx.scene.control.Tooltip; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.BorderPane; import org.controlsfx.control.MasterDetailPane; import org.kordamp.ikonli.fontawesome.FontAwesome; import org.kordamp.ikonli.javafx.FontIcon; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; /** * Skin for the log pane. * * Created by gdiaz on 22/02/2017. */ public class LogPaneSkin extends SkinBase { /** * Constructor for all SkinBase instances. * * @param control The control for which this Skin should attach to. */ public LogPaneSkin(LogPane control, TableView table) { super(control); TextArea textArea = new TextArea(); textArea.addEventFilter(KeyEvent.ANY, Event::consume); BorderPane tablePane = new BorderPane(); tablePane.setTop(createToolBar()); tablePane.setCenter(table); MasterDetailPane container = new MasterDetailPane(); container.setMasterNode(tablePane); container.setDetailNode(textArea); container.setDetailSide(Side.RIGHT); container.setDividerPosition(0.65); container.setShowDetailNode(false); control.getSelectedItems().addListener((Observable obs) -> { List item = control.getSelectedItems(); if (item == null || item.isEmpty() || item.size() > 1 || item.get(0).getException() == null) { textArea.setText(null); container.setShowDetailNode(false); } else { StringWriter stackTraceWriter = new StringWriter(); item.get(0).getException().printStackTrace(new PrintWriter(stackTraceWriter)); textArea.setText(stackTraceWriter.toString()); container.setShowDetailNode(true); } }); getChildren().add(container); } private ToolBar createToolBar() { Button clearAllBtn = new Button(); clearAllBtn.setText("Clear All"); clearAllBtn.setGraphic(new FontIcon(FontAwesome.TRASH)); clearAllBtn.setOnAction(evt -> getSkinnable().clearItems()); Button clearSelectionBtn = new Button(); clearSelectionBtn.setText("Clear Selected"); clearSelectionBtn.setGraphic(new FontIcon(FontAwesome.TRASH_O)); clearSelectionBtn.setOnAction(evt -> getSkinnable().removeItems(Lists.newArrayList(getSkinnable().getSelectedItems()))); List statusTypeButtons = new ArrayList<>(); InvalidationListener statusListener = obs -> { List statuses = Lists.newArrayList(); for (ToggleButton btn : statusTypeButtons) { if (btn.isSelected()) { statuses.add((StatusType) btn.getUserData()); } } getSkinnable().filter(statuses); }; for (StatusType type : StatusType.values()) { ToggleButton btn = new ToggleButton(); btn.setSelected(true); btn.setGraphic(type.createView()); btn.setTooltip(new Tooltip(type.getDisplayName())); btn.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); btn.setUserData(type); btn.selectedProperty().addListener(statusListener); statusTypeButtons.add(btn); } TextField textField = new TextField(); textField.setPrefColumnCount(20); textField.setPromptText("Search: Calendar or Description"); textField.addEventHandler(KeyEvent.KEY_PRESSED, evt -> { if (evt.getCode() == KeyCode.ENTER) { getSkinnable().filter(textField.getText()); } }); Button searchBtn = new Button(); searchBtn.setGraphic(new FontIcon(FontAwesome.SEARCH)); searchBtn.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); searchBtn.setOnAction(evt -> getSkinnable().filter(textField.getText())); ComboBox actionTypeComboBox = new ComboBox<>(); actionTypeComboBox.getItems().addAll(ActionTypeWrapper.values()); actionTypeComboBox.setEditable(false); actionTypeComboBox.valueProperty().addListener(obs -> getSkinnable().filter(actionTypeComboBox.getValue().getActionType())); ToolBar toolBar = new ToolBar(); toolBar.getItems().add(clearAllBtn); toolBar.getItems().add(clearSelectionBtn); toolBar.getItems().add(new Separator()); toolBar.getItems().addAll(statusTypeButtons); toolBar.getItems().add(new Separator()); toolBar.getItems().add(new Label("Action")); toolBar.getItems().add(actionTypeComboBox); toolBar.getItems().add(new Separator()); toolBar.getItems().add(textField); toolBar.getItems().add(searchBtn); return toolBar; } private static class ActionTypeWrapper { private static final ActionTypeWrapper NULL_ITEM = new ActionTypeWrapper(); private ActionType actionType; ActionTypeWrapper() { super(); } ActionTypeWrapper(ActionType actionType) { this(); this.actionType = actionType; } ActionType getActionType() { return actionType; } static List values() { List values = Lists.newArrayList(); values.add(NULL_ITEM); for (ActionType actionType : ActionType.values()) { values.add(new ActionTypeWrapper(actionType)); } return values; } @Override public String toString() { return actionType == null ? "" : actionType.getDisplayName(); } } } ================================================ FILE: CalendarFXGoogle/src/main/java/module-info.java ================================================ module com.calendarfx.google { requires transitive javafx.graphics; requires org.kordamp.ikonli.javafx; requires org.kordamp.ikonli.fontawesome; requires org.kordamp.ikonli.core; requires com.calendarfx.view; requires javafx.base; requires com.google.api.services.calendar; requires org.controlsfx.controls; requires javafx.controls; requires javafx.web; requires geocoder.java; requires com.dlsc.gmapsfx; requires com.google.common; requires com.google.api.client; requires com.google.api.client.auth; requires com.google.api.services.oauth2; requires com.google.api.client.json.jackson2; requires google.api.client; exports com.calendarfx.google; } ================================================ FILE: CalendarFXGoogle/src/main/resources/com/calendarfx/google/service/client-secrets.json ================================================ { "installed": { "auth_uri": "https://accounts.google.com/o/oauth2/auth", "client_secret": "Tnwk7IGPlvnnn7TQ-WbvpWv3", "token_uri": "https://accounts.google.com/o/oauth2/token", "client_email": "", "redirect_uris": [ "urn:ietf:wg:oauth:2.0:oob", "oob" ], "client_x509_cert_url": "", "client_id": "972996368401-vlm70ls1ivnpvlfu80odn880bhne1pnc.apps.googleusercontent.com", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs" } } ================================================ FILE: CalendarFXGoogle/src/main/resources/com/calendarfx/google/view/popover/google-popover.css ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ .title { -fx-font-weight: bold; } .button-icon { -fx-cursor: hand; } .link { -fx-cursor: hand; -fx-underline: true; } .details-view .notification-item { -fx-spacing: 5; -fx-padding: 0 0 5 0; } .attendees-view .top { -fx-spacing: 5; -fx-padding: 0 0 5 0; } .attendees-view .center { -fx-spacing: 5; -fx-padding: 0 0 10 0; -fx-fill-width: true; -fx-fit-to-width: true; } .attendees-view .scroll-pane { -fx-padding: 5; -fx-background-color: transparent; -fx-border-color: transparent; -fx-fit-to-width: true; } .attendees-view .bottom { -fx-spacing: 5; -fx-padding: 5; } .attendees-view .bottom .checks-parent { -fx-spacing: 10; } .attendees-view .email-field { -fx-text-fill: black; } .attendees-view .email-field:invalid { -fx-text-fill: red; } .attendee-item { -fx-spacing: 5; -fx-padding: 5; -fx-background-color: #EEE; } .popover-footer { -fx-padding: 0.0 !important; } .map-placeholder { -fx-padding: 10.0; } .map-view-wrapper { -fx-padding: 0 20 20 20; } .map { -fx-border-color: gray; } ================================================ FILE: CalendarFXGoogle/src/test/java/com/calendarfx/google/view/popover/HelloGoogleEntryPopOverContentPane.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.google.view.popover; import com.calendarfx.google.model.GoogleEntry; import com.calendarfx.model.Calendar; import com.calendarfx.view.CalendarView; import com.calendarfx.view.DayView; import javafx.application.Application; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.stage.Stage; /** * Sample for the google entry pop over. * * Created by gdiaz on 13/01/2017. */ public class HelloGoogleEntryPopOverContentPane extends Application { @Override public void start(Stage primaryStage) throws Exception { Calendar calendar = new Calendar(); calendar.setName("Google Calendar"); calendar.setStyle(Calendar.Style.STYLE2); GoogleEntry entry = new GoogleEntry(); entry.setTitle("Google Entry"); entry.setCalendar(calendar); entry.setLocation("Bogota"); ObservableList allCalendars = FXCollections.observableArrayList(calendar); DayView dayView = new DayView(); GoogleEntryPopOverContentPane pane = new GoogleEntryPopOverContentPane(entry, allCalendars, dayView); primaryStage.setTitle("Google Calendar"); Scene scene = new Scene(pane, 400, 600); scene.getStylesheets().add(CalendarView.class.getResource("calendar.css").toExternalForm()); primaryStage.setScene(scene); primaryStage.sizeToScene(); primaryStage.centerOnScreen(); primaryStage.show(); } } ================================================ FILE: CalendarFXResourceApp/.gitignore ================================================ /target *.iml ================================================ FILE: CalendarFXResourceApp/pom.xml ================================================ 4.0.0 resources CalendarFXResourceApp com.calendarfx calendar 12.0.1 ../pom.xml true com.calendarfx view org.openjfx javafx-maven-plugin ${javafx.maven.plugin.version} com.calendarfx.resource.app.ResourceCalendarApp ================================================ FILE: CalendarFXResourceApp/src/main/java/com/calendarfx/resource/app/ResourceCalendarApp.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.resource.app; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.model.Marker; import com.calendarfx.view.DateControl; import com.calendarfx.view.DayEntryView; import com.calendarfx.view.DayView; import com.calendarfx.view.EntryViewBase; import com.calendarfx.view.ResourceCalendarView; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.stage.Stage; import org.kordamp.ikonli.fontawesome.FontAwesome; import org.kordamp.ikonli.javafx.FontIcon; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.Random; import java.util.function.Supplier; public class ResourceCalendarApp extends Application { public static final int DATA_GENERATION_SEED = 11011; final Random random = new Random(DATA_GENERATION_SEED); @Override public void start(Stage primaryStage) { ResourceCalendarView resourceCalendarView = new ResourceCalendarView<>(); resourceCalendarView.setContextMenuCallback(param -> { ContextMenu menu = new ContextMenu(); MenuItem newMarkerItem = new MenuItem("New Marker"); newMarkerItem.setOnAction(evt -> { Marker marker = new Marker(); marker.setStyle("-fx-background-color: red;"); marker.setTitle("New Marker"); marker.setTime(param.getZonedDateTime()); resourceCalendarView.getMarkers().add(marker); }); menu.getItems().add(newMarkerItem); return menu; }); resourceCalendarView.setHeaderFactory(resource -> { Label label1 = new Label("IG-TR"); Label label2 = new Label("29.000"); Label label3 = new Label("13.200"); Label label4 = new Label("92 (CPC)"); label1.setStyle("-fx-font-family: Monospaced; -fx-font-style: bold;"); label2.setStyle("-fx-font-family: Monospaced; -fx-text-fill: blue; -fx-font-style: bold;"); label3.setStyle("-fx-font-family: Monospaced; -fx-text-fill: blue; -fx-font-style: bold;"); label4.setStyle("-fx-font-family: Monospaced; -fx-text-fill: blue; -fx-font-style: bold;"); label1.setAlignment(Pos.CENTER); label2.setAlignment(Pos.CENTER); label3.setAlignment(Pos.CENTER); label4.setAlignment(Pos.CENTER); VBox box = new VBox(5, label1, label2, label3, label4); box.setPadding(new Insets(10)); box.setFillWidth(true); return box; }); for (int i = 0; i < 5; i++) { CalendarSource source = new CalendarSource("Default"); HelloDayViewCalendar calendar1 = new HelloDayViewCalendar(random.nextLong()); calendar1.generateBaseEntries(); calendar1.setStyle(Style.STYLE1); source.getCalendars().add(calendar1); HelloDayViewCalendar calendar2 = new HelloDayViewCalendar(random.nextLong()); calendar2.generateBaseEntries(); calendar2.setStyle(Style.STYLE2); source.getCalendars().add(calendar2); HelloDayViewCalendar calendar3 = new HelloDayViewCalendar(random.nextLong()); calendar3.generateBaseEntries(); calendar3.setStyle(Style.STYLE3); source.getCalendars().add(calendar3); HelloDayViewCalendar calendar4 = new HelloDayViewCalendar(random.nextLong()); calendar4.generateTopEntries(); calendar4.setStyle(Style.STYLE4); source.getCalendars().add(calendar4); String resource = "Resource " + (i + 1); resourceCalendarView.getResources().add(resource); DayView dayView = resourceCalendarView.getDayView(resource); dayView.getCalendarSources().setAll(source); dayView.setEnableCurrentTimeMarker(true); dayView.setEnableCurrentTimeCircle(i == 0); /* PSI: * Setting a custom entry view factory will allow you to set icons on your entry * views based on state information provided by your model. */ Random iconsRandom = new Random(); dayView.setEntryViewFactory(entry -> { iconsRandom.setSeed(entry.getTitle().hashCode()); DayEntryView entryView = new DayEntryView(entry); if (entry instanceof TopEntry) { entryView.setWidthPercentage(25.0); entryView.setAlignmentStrategy(EntryViewBase.AlignmentStrategy.ALIGN_RIGHT); entryView.setLayer(DateControl.Layer.TOP); } /* PSI: * Here you can experiment with the new alignment strategy that allows * applications to have entry views show up on the left, the center, or * the right of a day view with a given width. This is needed to support * "vertical bars". */ // entryView.setPrefWidth(10); // entryView.setAlignmentStrategy(AlignmentStrategy.ALIGN_LEFT); /* PSI: * Here you can experiment with the new height layout strategy that allows * applications to have entry views show up with their preferred height instead * of a height determined by their start and end time. This is required to * implement the Event Monitoring Panel. */ // entryView.setHeightLayoutStrategy(HeightLayoutStrategy.COMPUTE_PREF_SIZE); if (iconsRandom.nextDouble() > .7) { final FontIcon node = new FontIcon(FontAwesome.ERASER); node.setIconColor(Color.RED); node.setIconSize(16); entryView.addNode(Pos.BOTTOM_RIGHT, node); } if (iconsRandom.nextDouble() > .9) { final FontIcon node = new FontIcon(FontAwesome.CODE); node.setIconColor(Color.BLUE); node.setIconSize(16); entryView.addNode(Pos.BOTTOM_RIGHT, node); } if (iconsRandom.nextDouble() > .7) { final FontIcon node = new FontIcon(FontAwesome.QRCODE); node.setIconColor(Color.MEDIUMPURPLE); node.setIconSize(16); entryView.addNode(Pos.BOTTOM_RIGHT, node); } if (iconsRandom.nextDouble() > .7) { final FontIcon node = new FontIcon(FontAwesome.SIGN_IN); node.setIconColor(Color.MEDIUMSPRINGGREEN); node.setIconSize(16); entryView.addNode(Pos.TOP_RIGHT, node); } return entryView; }); } Marker marker1 = new Marker(); marker1.setTitle("My Marker 1"); marker1.setTime(ZonedDateTime.now().minusHours(1)); marker1.setMovable(false); resourceCalendarView.getMarkers().add(marker1); Marker marker2 = new Marker(); marker2.setTitle("My Marker 2"); marker2.setTime(ZonedDateTime.now().plusHours(1)); marker2.setMovable(false); marker2.getStyleClass().add("marker2"); resourceCalendarView.getMarkers().add(marker2); StackPane stackPane = new StackPane(); stackPane.getChildren().addAll(resourceCalendarView); Thread updateTimeThread = new Thread("Calendar: Update Time Thread") { @Override public void run() { while (true) { Platform.runLater(() -> { resourceCalendarView.setToday(LocalDate.now()); resourceCalendarView.setTime(LocalTime.now()); }); try { // update every 10 seconds sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; updateTimeThread.setPriority(Thread.MIN_PRIORITY); updateTimeThread.setDaemon(true); updateTimeThread.start(); Scene scene = new Scene(stackPane); primaryStage.setTitle("Calendar"); primaryStage.setScene(scene); primaryStage.setWidth(1300); primaryStage.setHeight(1000); primaryStage.centerOnScreen(); primaryStage.show(); } class HelloDayViewCalendar extends Calendar { final Random dataRandom = new Random(); public HelloDayViewCalendar(long dataSeed) { dataRandom.setSeed(dataSeed); } public void generateBaseEntries() { createEntries(LocalDate.now().minusDays(2), Entry::new); createEntries(LocalDate.now().minusDays(1), Entry::new); createEntries(LocalDate.now(), Entry::new); createEntries(LocalDate.now().plusDays(1), Entry::new); createEntries(LocalDate.now().plusDays(2), Entry::new); } public void generateTopEntries() { createEntries(LocalDate.now(), TopEntry::new); } private > void createEntries(LocalDate startDate, Supplier entryProducer) { for (int j = 0; j < 5 + (int) (dataRandom.nextDouble() * 4); j++) { T entry = entryProducer.get(); entry.changeStartDate(startDate); entry.changeEndDate(startDate); String s = entry.getClass().getSimpleName(); entry.setTitle(s + (j + 1)); int hour = (int) (dataRandom.nextDouble() * 23); int durationInHours = Math.max(1, Math.min(24 - hour, (int) (dataRandom.nextDouble() * 4))); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); entry.setCalendar(this); } } } public static void main(String[] args) { launch(args); } public static class TopEntry extends Entry { } } ================================================ FILE: CalendarFXResourceApp/src/main/java/com/calendarfx/resource/app/ResourceCalendarAppLauncher.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.resource.app; public class ResourceCalendarAppLauncher { public static void main(String[] args) { System.setProperty("calendarfx.developer", "true"); ResourceCalendarApp.main(args); } } ================================================ FILE: CalendarFXResourceApp/src/main/java/module-info.java ================================================ module com.calendarfx.resource.app { requires transitive javafx.graphics; requires javafx.controls; requires com.calendarfx.view; exports com.calendarfx.resource.app; } ================================================ FILE: CalendarFXSampler/.gitignore ================================================ /target *.iml ================================================ FILE: CalendarFXSampler/pom.xml ================================================ 4.0.0 sampler jar CalendarFXSampler true true com.calendarfx calendar 12.0.1 ../pom.xml org.openjfx javafx-base org.openjfx javafx-fxml org.openjfx javafx-graphics org.openjfx javafx-controls org.openjfx javafx-web com.calendarfx view com.calendarfx application org.controlsfx fxsampler org.controlsfx controlsfx org.openjfx javafx-maven-plugin ${javafx.maven.plugin.version} com.calendarfx.demo.CalendarFXSampler ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/CalendarFXDateControlSample.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo; import com.calendarfx.view.DateControl; import com.calendarfx.view.DeveloperConsole; import javafx.geometry.Pos; import javafx.geometry.Side; import javafx.scene.Node; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import org.controlsfx.control.MasterDetailPane; import static java.util.Objects.requireNonNull; public abstract class CalendarFXDateControlSample extends CalendarFXSample { @Override public final Node getPanel(Stage stage) { DateControl dateControl = createControl(); control = dateControl; requireNonNull(control, "missing date control"); DeveloperConsole console = new DeveloperConsole(); console.setDateControl(dateControl); if (isSupportingDeveloperConsole()) { MasterDetailPane masterDetailPane = new MasterDetailPane(); masterDetailPane.setMasterNode(wrap(dateControl)); masterDetailPane.setDetailSide(Side.BOTTOM); masterDetailPane.setDetailNode(console); masterDetailPane.setShowDetailNode(true); return masterDetailPane; } return wrap(dateControl); } @Override protected Node wrap(Node node) { StackPane outerPane = new StackPane(); outerPane.setStyle("-fx-padding: 20px;"); StackPane stackPane = new StackPane(); stackPane.setStyle("-fx-background-color: white; -fx-border-color: gray; -fx-border-width: .25px; -fx-padding: 20px;"); outerPane.getChildren().add(stackPane); StackPane.setAlignment(node, Pos.CENTER); stackPane.getChildren().add(node); return outerPane; } protected boolean isSupportingDeveloperConsole() { return true; } @Override protected abstract DateControl createControl(); } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/CalendarFXSample.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo; import atlantafx.base.theme.NordDark; import com.calendarfx.util.CalendarFX; import com.calendarfx.view.CalendarFXControl; import fr.brouillard.oss.cssfx.CSSFX; import fxsampler.SampleBase; import impl.com.calendarfx.view.CalendarPropertySheet; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import static java.util.Objects.requireNonNull; public abstract class CalendarFXSample extends SampleBase { protected Node control; { setUserAgentStylesheet(new NordDark().getUserAgentStylesheet()); } @Override public Node getPanel(Stage stage) { control = createControl(); requireNonNull(control, "missing date control"); CSSFX.start(); return wrap(control); } protected Node wrap(Node node) { StackPane stackPane = new StackPane(); stackPane.setStyle("-fx-background-color: white; -fx-border-color: gray; -fx-border-width: .25px; -fx-padding: 20px;"); stackPane.getChildren().add(node); HBox box = new HBox(); box.setAlignment(Pos.CENTER); box.setFillHeight(false); box.getChildren().add(stackPane); return box; } @Override public String getProjectName() { return "CalendarFX"; } @Override public String getProjectVersion() { return CalendarFX.getVersion(); } @Override public double getControlPanelDividerPosition() { return .7; } @Override public Node getControlPanel() { if (control instanceof CalendarFXControl) { return new CalendarPropertySheet(((CalendarFXControl) control).getPropertySheetItems()); } return null; } @Override public String getControlStylesheetURL() { return "/calendar.css"; } @Override public final String getSampleSourceURL() { return getSampleSourceBase() + getClass().getSimpleName() + ".java"; } private final String getSampleSourceBase() { return "http://dlsc.com/wp-content/html/calendarfx/sampler/"; } // Javadoc support. protected Class getJavaDocClass() { return null; } private String getJavaDocBase() { return "http://dlsc.com/wp-content/html/calendarfx/apidocs/"; } @Override public final String getJavaDocURL() { Class cl = getJavaDocClass(); String url; if (cl == null) { url = getJavaDocBase() + "index.html?sampler=true"; } else { url = getJavaDocBase() + cl.getName().replace(".", "/") + ".html"; } return url; } protected abstract Node createControl(); } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/CalendarFXSampler.java ================================================ /** * Copyright (C) 2014 - 2021 DLSC Software & Consulting GmbH (dlsc.com) * * This file is part of FlexGanttFX. */ package com.calendarfx.demo; import fxsampler.FXSampler; public class CalendarFXSampler { public static void main(String[] args) { FXSampler.main(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/CalendarFXSamplerProject.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo; import fxsampler.FXSamplerProject; import fxsampler.model.WelcomePage; public class CalendarFXSamplerProject implements FXSamplerProject { @Override public String getProjectName() { return "CalendarFX"; } @Override public String getSampleBasePackage() { return "com.calendarfx.demo"; } @Override public WelcomePage getWelcomePage() { return new CalendarFXSamplerWelcome(); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/CalendarFXSamplerWelcome.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo; import fxsampler.model.WelcomePage; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.text.TextAlignment; public class CalendarFXSamplerWelcome extends WelcomePage { public CalendarFXSamplerWelcome() { super("CalendarFX", new Label("")); Label label = (Label) getContent(); label.setWrapText(true); label.setMaxWidth(Double.MAX_VALUE); label.setMaxHeight(Double.MAX_VALUE); label.setTextAlignment(TextAlignment.CENTER); label.setAlignment(Pos.CENTER); label.setPadding(new Insets(50)); label.setText("Welcome to the CalendarFX sampler. This application allows you to quickly browse through the " + "various controls that are available in this framework. In each sample you can play around with the " + "properties and controls shown on the right-hand side."); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/entries/HelloAllDayEntryView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.entries; import com.calendarfx.model.Entry; import com.calendarfx.view.AllDayEntryView; import com.calendarfx.view.EntryViewBase; public class HelloAllDayEntryView extends HelloEntryViewBase { @Override public String getSampleName() { return "All Day Entry View"; } @Override protected EntryViewBase createEntryView(Entry entry) { AllDayEntryView view = new AllDayEntryView(entry); view.setPrefSize(400, 20); return view; } @Override public String getSampleDescription() { return "This view is used to display a single entry in an all day view."; } @Override protected Class getJavaDocClass() { return AllDayEntryView.class; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/entries/HelloDayEntryView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.entries; import com.calendarfx.model.Entry; import com.calendarfx.view.DayEntryView; import com.calendarfx.view.EntryViewBase; import javafx.geometry.Pos; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; import javafx.scene.layout.Region; public class HelloDayEntryView extends HelloEntryViewBase { @Override public String getSampleName() { return "Day Entry View"; } @Override protected EntryViewBase createEntryView(Entry entry) { DayEntryView view = new DayEntryView(entry); ContextMenu menu = new ContextMenu(); for (Pos pos : Pos.values()) { MenuItem item = new MenuItem(pos.name()); item.setOnAction(evt -> { view.clearNodes(); for (int i = 0; i < 3; i++) { Region region = new Region(); switch (i) { case 0: region.setStyle("-fx-background-color: orange;"); break; case 1: region.setStyle("-fx-background-color: yellow;"); break; case 2: region.setStyle("-fx-background-color: blue;"); break; } region.setPrefSize(8, 8); view.addNode(pos, region); } }); menu.getItems().add(item); } view.setContextMenu(menu); view.setPrefSize(200, 300); return view; } @Override public String getSampleDescription() { return "This view is used to display a single entry in a day view."; } @Override protected Class getJavaDocClass() { return DayEntryView.class; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/entries/HelloEntryViewBase.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.entries; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Entry; import com.calendarfx.view.EntryViewBase; import impl.com.calendarfx.view.CalendarPropertySheet; import javafx.scene.Node; import org.controlsfx.control.PropertySheet; public abstract class HelloEntryViewBase extends CalendarFXSample { protected EntryViewBase entryView; protected Entry entry; public HelloEntryViewBase() { Calendar calendar = new Calendar("Test Calendar"); entry = new Entry<>("Test Entry"); entry.setCalendar(calendar); } @Override protected Node createControl() { entryView = createEntryView(entry); control = entryView; return entryView; } protected abstract EntryViewBase createEntryView(Entry entry); @Override public Node getControlPanel() { PropertySheet sheet = new CalendarPropertySheet(entryView.getPropertySheetItems()); sheet.getItems().addAll(entry.getPropertySheetItems()); return sheet; } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/entries/HelloMonthEntryView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.entries; import com.calendarfx.model.Entry; import com.calendarfx.view.EntryViewBase; import com.calendarfx.view.MonthEntryView; import java.time.LocalDate; public class HelloMonthEntryView extends HelloEntryViewBase { public HelloMonthEntryView() { super(); entry.setInterval(LocalDate.now(), LocalDate.now().plusDays(5)); } @Override protected EntryViewBase createEntryView(Entry entry) { MonthEntryView view = new MonthEntryView(entry); view.setPrefSize(400, 20); return view; } @Override public String getSampleName() { return "Month Entry View"; } @Override protected Class getJavaDocClass() { return MonthEntryView.class; } @Override public String getSampleDescription() { return "This view is used to display a single entry in a month view."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/pages/HelloDayPage.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.pages; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.DateControl; import com.calendarfx.view.page.DayPage; public class HelloDayPage extends CalendarFXDateControlSample { private DayPage dayPage; @Override protected DateControl createControl() { CalendarSource calendarSource = new CalendarSource("My Calendars"); final Calendar calendar = new Calendar("Calendar"); calendar.setShortName("C"); calendar.setStyle(Style.STYLE2); calendarSource.getCalendars().add(calendar); dayPage = new DayPage(); dayPage.getCalendarSources().add(calendarSource); return dayPage; } @Override public String getSampleName() { return "Day Page"; } @Override public String getSampleDescription() { return "The day page displays the calendar information for a single day."; } @Override protected Class getJavaDocClass() { return DayPage.class; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/pages/HelloMonthPage.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.pages; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DateControl; import com.calendarfx.view.page.MonthPage; import java.time.LocalDate; import java.time.LocalTime; import java.time.YearMonth; public class HelloMonthPage extends CalendarFXDateControlSample { private MonthPage monthPage; @Override protected DateControl createControl() { monthPage = new MonthPage(); CalendarSource calendarSource = new CalendarSource(); HelloCalendar calendar1 = new HelloCalendar(monthPage.getMonthView().getYearMonth()); HelloCalendar calendar2 = new HelloCalendar(monthPage.getMonthView().getYearMonth()); HelloCalendar calendar3 = new HelloCalendar(monthPage.getMonthView().getYearMonth()); HelloCalendar calendar4 = new HelloCalendar(monthPage.getMonthView().getYearMonth()); calendar1.setStyle(Style.STYLE1); calendar2.setStyle(Style.STYLE2); calendar3.setStyle(Style.STYLE3); calendar4.setStyle(Style.STYLE4); calendarSource.getCalendars().addAll(calendar1, calendar2, calendar3, calendar4); monthPage.getCalendarSources().add(calendarSource); return monthPage; } @Override public String getSampleName() { return "Month Page"; } @Override protected Class getJavaDocClass() { return MonthPage.class; } @Override public String getSampleDescription() { return "The month page displays the calendar information for a full month."; } class HelloCalendar extends Calendar { public HelloCalendar(YearMonth month) { createEntries(month); } private void createEntries(YearMonth month) { for (int i = 1; i < 28; i++) { LocalDate date = month.atDay(i); for (int j = 0; j < (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 21); int durationInHours = Math.min(24 - hour, (int) (Math.random() * 4)); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.setInterval(date, startTime, date.plusDays((int) (Math.random() * 4)), endTime); if (Math.random() < .3) { entry.setFullDay(true); } entry.setCalendar(this); } } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/pages/HelloWeekPage.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.pages; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.DateControl; import com.calendarfx.view.page.WeekPage; public class HelloWeekPage extends CalendarFXDateControlSample { private WeekPage weekPage; @Override protected DateControl createControl() { CalendarSource calendarSource = new CalendarSource("My Calendars"); calendarSource.getCalendars().add(new Calendar("Test")); weekPage = new WeekPage(); weekPage.getCalendarSources().add(calendarSource); return weekPage; } @Override protected Class getJavaDocClass() { return WeekPage.class; } @Override public String getSampleName() { return "Week Page"; } @Override public String getSampleDescription() { return "The week page displays a week view."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/pages/HelloYearPage.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.pages; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.DateControl; import com.calendarfx.view.page.YearPage; public class HelloYearPage extends CalendarFXDateControlSample { private YearPage yearPage; @Override protected DateControl createControl() { CalendarSource calendarSource = new CalendarSource("My Calendars"); calendarSource.getCalendars().add(new Calendar("Test")); yearPage = new YearPage(); yearPage.getCalendarSources().add(calendarSource); return yearPage; } @Override public String getSampleName() { return "Year Page"; } @Override protected Class getJavaDocClass() { return YearPage.class; } @Override public String getSampleDescription() { return "The year page displays the calendar information for a full year."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/performance/HelloPerformance.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.performance; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.model.Interval; import com.calendarfx.view.CalendarView; import com.calendarfx.view.DateControl; import impl.com.calendarfx.view.CalendarPropertySheet; import javafx.geometry.Orientation; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import java.time.LocalDate; import java.time.LocalTime; public class HelloPerformance extends CalendarFXDateControlSample { private CalendarView calendarView; private ComboBox comboBox; private Label label; private HelloCalendar calendar; private int style; @Override public String getSampleName() { return "Performance"; } @Override protected DateControl createControl() { CalendarSource calendarSource = new CalendarSource("My Calendars"); calendarSource.getCalendars().add(calendar = new HelloCalendar()); calendarView = new CalendarView(); calendarView.getCalendarSources().add(calendarSource); return calendarView; } @Override public Node getControlPanel() { VBox vBox = new VBox(); vBox.setFillWidth(true); vBox.setSpacing(10); // button Button button = new Button("Create Entries"); button.setMaxWidth(Double.MAX_VALUE); button.setOnAction(evt -> createEntries()); VBox.setVgrow(button, Priority.NEVER); vBox.getChildren().add(button); // box comboBox = new ComboBox<>(); comboBox.getItems().addAll(100, 1000, 2000, 3000, 10000, 100000, 1000000); comboBox.getSelectionModel().select(0); comboBox.setMaxWidth(Double.MAX_VALUE); vBox.getChildren().add(comboBox); // label label = new Label("Time: "); vBox.getChildren().add(label); // Separator Separator separator = new Separator(Orientation.HORIZONTAL); vBox.getChildren().add(separator); // sheet CalendarPropertySheet sheet = new CalendarPropertySheet(calendarView.getPropertySheetItems()); VBox.setVgrow(sheet, Priority.ALWAYS); vBox.getChildren().add(sheet); return vBox; } public void createEntries() { calendar.setStyle(Calendar.Style.getStyle(style++)); calendar.clear(); LocalTime dailyStartTime = LocalTime.of(8, 0); LocalTime dailyEndTime = LocalTime.of(20, 0); LocalDate entryDate = LocalDate.now(); LocalTime entryTime = dailyStartTime; long startTime = System.currentTimeMillis(); int count = comboBox.getValue(); calendar.startBatchUpdates(); for (int i = 0; i < count; i++) { Entry entry = new Entry<>("Entry " + i); entry.setInterval(new Interval(entryDate, entryTime, entryDate, entryTime.plusMinutes(30))); entryTime = entryTime.plusHours(1); if (entryTime.isAfter(dailyEndTime)) { entryDate = entryDate.plusDays(1); entryTime = dailyStartTime; } entry.setCalendar(calendar); } calendar.stopBatchUpdates(); label.setText("Time: " + (System.currentTimeMillis() - startTime)); } @Override public String getSampleDescription() { return "A test sample to confirm high performance even when dealing with a lot of entries."; } @Override protected Class getJavaDocClass() { return Calendar.class; } class HelloCalendar extends Calendar { public HelloCalendar() { } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/popover/HelloEntryDetailsView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.popover; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Entry; import com.calendarfx.view.DayView; import com.calendarfx.view.popover.EntryDetailsView; import javafx.scene.Node; public class HelloEntryDetailsView extends CalendarFXSample { @Override public String getSampleName() { return "Entry Details"; } @Override protected Node createControl() { DayView dayView = new DayView(); Entry entry = new Entry<>("Hello Entry"); entry.setCalendar(new Calendar("Dummy Calendar")); return new EntryDetailsView(entry, dayView); } @Override protected Class getJavaDocClass() { return EntryDetailsView.class; } @Override public String getSampleDescription() { return "A view used to edit various properties of a calendar entry."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/popover/HelloEntryHeaderView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.popover; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.Entry; import com.calendarfx.view.popover.EntryHeaderView; import javafx.scene.Node; import java.util.ArrayList; import java.util.List; public class HelloEntryHeaderView extends CalendarFXSample { @Override public String getSampleName() { return "Entry Header View"; } @Override protected Node createControl() { Calendar meetings = new Calendar("Meetings"); Calendar training = new Calendar("Training"); Calendar customers = new Calendar("Customers"); Calendar holidays = new Calendar("Holidays"); meetings.setStyle(Style.STYLE2); training.setStyle(Style.STYLE3); customers.setStyle(Style.STYLE4); holidays.setStyle(Style.STYLE5); List calendars = new ArrayList<>(); calendars.add(meetings); calendars.add(training); calendars.add(customers); calendars.add(holidays); Entry entry = new Entry<>("Hello Header View"); entry.setCalendar(meetings); return new EntryHeaderView(entry, calendars); } @Override protected Class getJavaDocClass() { return EntryHeaderView.class; } @Override public String getSampleDescription() { return "A view used to select a calendar from a list of calendars."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/popover/HelloPopOverContentPane.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.popover; import com.calendarfx.model.Calendar; import com.calendarfx.model.Entry; import com.calendarfx.view.CalendarView; import com.calendarfx.view.popover.EntryPropertiesView; import com.calendarfx.view.popover.PopOverContentPane; import com.calendarfx.view.popover.PopOverTitledPane; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; public class HelloPopOverContentPane extends Application { @Override public void start(Stage primaryStage) { Calendar calendar = new Calendar(); calendar.setName("Calendar"); calendar.setStyle(Calendar.Style.STYLE2); Entry entry = new Entry<>(); entry.setTitle("Google Entry"); entry.setCalendar(calendar); entry.setLocation("Bogota"); PopOverContentPane pane = new PopOverContentPane(); EntryPropertiesView entryPropertiesView = new EntryPropertiesView(entry); PopOverTitledPane titledPane = new PopOverTitledPane("Properties", entryPropertiesView); titledPane.getStyleClass().add("no-padding"); titledPane.setExpanded(true); pane.getPanes().add(titledPane); primaryStage.setTitle("Entry Properties"); Scene scene = new Scene(pane, 400, 600); scene.getStylesheets().add(CalendarView.class.getResource("calendar.css").toExternalForm()); primaryStage.setScene(scene); primaryStage.sizeToScene(); primaryStage.centerOnScreen(); primaryStage.show(); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/print/HelloOptionsView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.print; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.view.print.OptionsView; import javafx.scene.Node; public class HelloOptionsView extends CalendarFXSample { private final OptionsView view = new OptionsView(); @Override protected Class getJavaDocClass() { return OptionsView.class; } @Override protected Node createControl() { return view; } @Override public String getSampleName() { return "Options View"; } @Override public String getSampleDescription() { return "A control that allows to change a few basic settings before printing a calendar view."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/print/HelloPaperView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.print; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.view.print.PaperView; import javafx.scene.Node; public class HelloPaperView extends CalendarFXSample { @Override protected Node createControl() { return new PaperView(); } @Override protected Class getJavaDocClass() { return PaperView.class; } @Override public String getSampleName() { return "Paper View"; } @Override public String getSampleDescription() { return "This control allows to select the view that is going to be printed and configure the paper type and print margins."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/print/HelloPreviewPane.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.print; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.print.PreviewPane; import javafx.scene.Node; public class HelloPreviewPane extends CalendarFXSample { @Override public String getSampleName() { return "Preview Pane"; } @Override public String getSampleDescription() { return "Preview of the page to be printed"; } @Override protected Class getJavaDocClass() { return PreviewPane.class; } @Override protected Node createControl() { Calendar meetings = new Calendar("Meetings"); Calendar training = new Calendar("Training"); Calendar customers = new Calendar("Customers"); Calendar holidays = new Calendar("Holidays"); meetings.setStyle(Style.STYLE2); training.setStyle(Style.STYLE3); customers.setStyle(Style.STYLE4); holidays.setStyle(Style.STYLE5); CalendarSource workCalendarSource = new CalendarSource("Work"); workCalendarSource.getCalendars().addAll(meetings, training, customers, holidays); Calendar birthdays = new Calendar("Birthdays"); Calendar katja = new Calendar("Katja"); Calendar dirk = new Calendar("Dirk"); Calendar philip = new Calendar("Philip"); Calendar jule = new Calendar("Jule"); Calendar armin = new Calendar("Armin"); CalendarSource familyCalendarSource = new CalendarSource("Family"); familyCalendarSource.getCalendars().addAll(birthdays, katja, dirk, philip, jule, armin); PreviewPane printPreview = new PreviewPane(); printPreview.getPrintablePage().getCalendarSources().addAll(workCalendarSource, familyCalendarSource); return printPreview; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/print/HelloPrintView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.print; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.print.PrintView; import javafx.scene.Node; public class HelloPrintView extends CalendarFXSample { @Override public String getSampleName() { return "Print View"; } @Override public String getSampleDescription() { return "The view for printing that combines all the other print controls and allows the user to print one or more days, one or more weeks, or one or more months."; } @Override protected Class getJavaDocClass() { return PrintView.class; } @Override protected Node createControl() { Calendar meetings = new Calendar("Meetings"); Calendar training = new Calendar("Training"); Calendar customers = new Calendar("Customers"); Calendar holidays = new Calendar("Holidays"); meetings.setStyle(Style.STYLE2); training.setStyle(Style.STYLE3); customers.setStyle(Style.STYLE4); holidays.setStyle(Style.STYLE5); CalendarSource workCalendarSource = new CalendarSource("Work"); workCalendarSource.getCalendars().addAll(meetings, training, customers, holidays); Calendar birthdays = new Calendar("Birthdays"); Calendar katja = new Calendar("Katja"); Calendar dirk = new Calendar("Dirk"); Calendar philip = new Calendar("Philip"); CalendarSource familyCalendarSource = new CalendarSource("Family"); familyCalendarSource.getCalendars().addAll(birthdays, katja, dirk, philip); Entry meetings1 = new Entry<>("Meetings 1"); meetings1.setCalendar(meetings); PrintView printView = new PrintView(); printView.setPrefWidth(1200); printView.setPrefHeight(950); printView.getCalendarSources().addAll(workCalendarSource, familyCalendarSource); return printView; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/print/HelloSettingsView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.print; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.print.SettingsView; import javafx.scene.Node; public class HelloSettingsView extends CalendarFXSample { @Override protected Node createControl() { Calendar meetings = new Calendar("Meetings"); Calendar training = new Calendar("Training"); Calendar customers = new Calendar("Customers"); Calendar holidays = new Calendar("Holidays"); meetings.setStyle(Style.STYLE2); training.setStyle(Style.STYLE3); customers.setStyle(Style.STYLE4); holidays.setStyle(Style.STYLE5); CalendarSource workCalendarSource = new CalendarSource("Work"); workCalendarSource.getCalendars().addAll(meetings, training, customers, holidays); Calendar birthdays = new Calendar("Birthdays"); Calendar katja = new Calendar("Katja"); Calendar dirk = new Calendar("Dirk"); Calendar philip = new Calendar("Philip"); CalendarSource familyCalendarSource = new CalendarSource("Family"); familyCalendarSource.getCalendars().addAll(birthdays, katja, dirk, philip); SettingsView printSettingsView = new SettingsView(); printSettingsView.getSourceView().getCalendarSources().addAll(workCalendarSource, familyCalendarSource); return printSettingsView; } @Override protected Class getJavaDocClass() { return SettingsView.class; } @Override public String getSampleName() { return "Print Settings View"; } @Override public String getSampleDescription() { return "Print Settings represents the right panel on the Print dialog that allows to setup the printing."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/print/HelloTimeRangeField.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.print; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.view.print.TimeRangeField; import javafx.scene.Node; public class HelloTimeRangeField extends CalendarFXSample { @Override public String getSampleName() { return "Time Range Field"; } @Override public String getSampleDescription() { return "Allows to setup a field on the time range selector."; } @Override protected Class getJavaDocClass() { return TimeRangeField.class; } @Override protected Node createControl() { return new TimeRangeField(); } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/print/HelloTimeRangeView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.print; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.view.print.TimeRangeView; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; public class HelloTimeRangeView extends CalendarFXSample { @Override public String getSampleName() { return "Time Range View"; } @Override public String getSampleDescription() { return "Allows to configure the period to be printed via the print dialog"; } @Override protected Class getJavaDocClass() { return TimeRangeView.class; } @Override protected Node createControl() { return new TimeRangeView(); } @Override protected Node wrap(Node node) { TimeRangeView field = (TimeRangeView) node; Label label = new Label("Range: " + field.getStartDate() + " to " + field.getEndDate()); label.setMaxHeight(Double.MAX_VALUE); field.startDateProperty().addListener(it -> label.setText("Range: " + field.getStartDate() + " to " + field.getEndDate())); field.endDateProperty().addListener(it -> label.setText("Range: " + field.getStartDate() + " to " + field.getEndDate())); VBox box2 = new VBox(20, field, label); box2.setFillWidth(false); StackPane stackPane = new StackPane(); stackPane.setStyle("-fx-background-color: white; -fx-border-color: gray; -fx-border-width: .25px; -fx-padding: 20px;"); stackPane.getChildren().add(box2); HBox box = new HBox(stackPane); box.setStyle("-fx-padding: 100px;"); box.setAlignment(Pos.CENTER); box.setFillHeight(false); return box; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloAgendaView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.AgendaView; import com.calendarfx.view.DateControl; public class HelloAgendaView extends CalendarFXDateControlSample { private AgendaView agendaView; @Override public String getSampleName() { return "Agenda View"; } @Override protected DateControl createControl() { agendaView = new AgendaView(); agendaView.setPrefWidth(400); CalendarSource calendarSource = new CalendarSource(); HelloCalendar calendar1 = new HelloCalendar(); HelloCalendar calendar2 = new HelloCalendar(); HelloCalendar calendar3 = new HelloCalendar(); HelloCalendar calendar4 = new HelloCalendar(); calendarSource.getCalendars().addAll(calendar1, calendar2, calendar3, calendar4); agendaView.getCalendarSources().add(calendarSource); return agendaView; } @Override protected boolean isSupportingDeveloperConsole() { return false; } @Override protected Class getJavaDocClass() { return AgendaView.class; } @Override public String getSampleDescription() { return "The agenda view displays a (text) list of calendar entries for today and several days into the future or past."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloAllDayView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.AllDayView; import com.calendarfx.view.DateControl; import javafx.beans.binding.Bindings; import java.time.LocalDate; public class HelloAllDayView extends CalendarFXDateControlSample { private AllDayView allDayView; @Override public String getSampleName() { return "All Day View"; } @Override protected DateControl createControl() { CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().add(new HelloCalendar()); allDayView = new AllDayView(); allDayView.prefWidthProperty().bind(Bindings.multiply(150, allDayView.numberOfDaysProperty())); allDayView.setMaxWidth(Double.MAX_VALUE); allDayView.getCalendarSources().add(calendarSource); return allDayView; } @Override protected Class getJavaDocClass() { return AllDayView.class; } @Override public String getSampleDescription() { return "The all-day view displays entries that last all day / span multiple days."; } class HelloCalendar extends Calendar { public HelloCalendar() { Entry entry1 = new Entry<>("Entry 1"); Entry entry2 = new Entry<>("Entry 2"); Entry entry3 = new Entry<>("Entry 3"); Entry entry4 = new Entry<>("Entry 4"); Entry entry5 = new Entry<>("Entry 5"); entry1.setInterval(LocalDate.now(), LocalDate.now().plusDays(2)); entry2.setInterval(LocalDate.now().plusDays(3), LocalDate.now().plusDays(4)); entry3.setInterval(LocalDate.now().plusDays(1), LocalDate.now().plusDays(2)); entry4.setInterval(LocalDate.now(), LocalDate.now().plusDays(4)); entry5.setInterval(LocalDate.now().plusDays(4), LocalDate.now().plusDays(6)); entry1.setFullDay(true); entry2.setFullDay(true); entry3.setFullDay(true); entry4.setFullDay(true); entry5.setFullDay(true); entry1.setCalendar(this); entry4.setCalendar(this); entry3.setCalendar(this); entry2.setCalendar(this); entry5.setCalendar(this); } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloAvailabilityCalendar.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.view.DayView; import com.calendarfx.view.DayViewBase.HoursLayoutStrategy; import com.calendarfx.view.VirtualGrid; import javafx.scene.Node; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; import javafx.scene.layout.VBox; import javafx.util.StringConverter; import java.time.temporal.ChronoUnit; public class HelloAvailabilityCalendar extends CalendarFXSample { private final DayView dayView = new DayView(); @Override public String getSampleName() { return "Availability Calendar"; } protected Node createControl() { dayView.setAvailabilityCalendar(new HelloDayViewCalendar()); dayView.setHoursLayoutStrategy(HoursLayoutStrategy.FIXED_HOUR_HEIGHT); dayView.setHourHeight(20); dayView.setPrefWidth(200); return dayView; } @Override public Node getControlPanel() { CheckBox editMode = new CheckBox("Edit Availability"); editMode.selectedProperty().bindBidirectional(dayView.editAvailabilityProperty()); ChoiceBox gridBox = new ChoiceBox<>(); gridBox.setConverter(new StringConverter<>() { @Override public String toString(VirtualGrid virtualGrid) { return virtualGrid.getName(); } @Override public VirtualGrid fromString(String s) { return null; } }); gridBox.getItems().add(dayView.getAvailabilityGrid()); gridBox.getItems().add(new VirtualGrid("10 Minutes", "10m", ChronoUnit.MINUTES, 10)); gridBox.getItems().add(new VirtualGrid("15 Minutes", "15m", ChronoUnit.MINUTES, 15)); gridBox.getItems().add(new VirtualGrid("1 Hour", "1h", ChronoUnit.HOURS, 1)); gridBox.valueProperty().bindBidirectional(dayView.availabilityGridProperty()); VBox box = new VBox(10, editMode, gridBox); return box; } @Override public String getSampleDescription() { return "The availability calendar can be used to visualize in a read-only way when a person or a resource is available or not."; } @Override protected Class getJavaDocClass() { return DayView.class; } class HelloDayViewCalendar extends Calendar { public HelloDayViewCalendar() { } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloCalendar.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.model.Calendar; import com.calendarfx.model.Entry; import java.time.LocalDate; import java.time.LocalTime; import java.time.YearMonth; /** * Created by gdiaz on 26/11/2016. */ class HelloCalendar extends Calendar { public HelloCalendar() { for (int i = 1; i < 28; i++) { LocalDate date = YearMonth.now().atDay(i); for (int j = 0; j < (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.changeStartDate(date); entry.changeEndDate(date.plusDays((int) (Math.random() * 4))); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.min(24 - hour, (int) (Math.random() * 4)); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); if (Math.random() < .3) { entry.setFullDay(true); } entry.setCalendar(this); } } } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloCalendarHeaderView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.view.CalendarHeaderView; import javafx.scene.Node; public class HelloCalendarHeaderView extends CalendarFXSample { @Override public String getSampleName() { return "Calendar Header View"; } @Override protected Node createControl() { CalendarHeaderView calendarHeaderView = new CalendarHeaderView(); calendarHeaderView.setNumberOfDays(5); calendarHeaderView.setMaxHeight(30); Calendar dirk = new Calendar("Dirk"); Calendar katja = new Calendar("Katja"); Calendar philip = new Calendar("Philip"); Calendar jule = new Calendar("Jule"); Calendar armin = new Calendar("Armin"); dirk.setStyle(Style.STYLE1); katja.setStyle(Style.STYLE1); philip.setStyle(Style.STYLE2); jule.setStyle(Style.STYLE1); armin.setStyle(Style.STYLE3); calendarHeaderView.getCalendars().add(dirk); calendarHeaderView.getCalendars().add(katja); calendarHeaderView.getCalendars().add(philip); calendarHeaderView.getCalendars().add(jule); calendarHeaderView.getCalendars().add(armin); return calendarHeaderView; } @Override protected Class getJavaDocClass() { return CalendarHeaderView.class; } @Override public String getSampleDescription() { return "The all-day view displays entries that last all day / span multiple days."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloCalendarSelector.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.view.CalendarSelector; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.HBox; public class HelloCalendarSelector extends CalendarFXSample { @Override public String getSampleName() { return "Calendar Selector"; } @Override protected Node createControl() { Calendar meetings = new Calendar("Meetings"); Calendar training = new Calendar("Training"); Calendar customers = new Calendar("Customers"); Calendar holidays = new Calendar("Holidays"); meetings.setStyle(Style.STYLE2); training.setStyle(Style.STYLE3); customers.setStyle(Style.STYLE4); holidays.setStyle(Style.STYLE5); CalendarSelector view = new CalendarSelector(); view.getCalendars().addAll(meetings, training, customers, holidays); view.setCalendar(meetings); Label label = new Label("Selected: " + view.getCalendar().getName()); label.setMaxHeight(Double.MAX_VALUE); view.calendarProperty().addListener(it -> label.setText("Selected: " + view.getCalendar().getName())); HBox box = new HBox(20); box.setFillHeight(true); box.getChildren().addAll(view, label); return box; } @Override protected Class getJavaDocClass() { return CalendarSelector.class; } @Override public String getSampleDescription() { return "A view used to select a calendar from a list of calendars."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloCalendarView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.CalendarView; import com.calendarfx.view.CalendarView.Page; import com.calendarfx.view.DateControl; import java.time.LocalDate; import java.time.LocalTime; import java.time.Month; import java.time.YearMonth; public class HelloCalendarView extends CalendarFXDateControlSample { private CalendarView calendarView; @Override public String getSampleName() { return "Calendar View"; } @Override protected DateControl createControl() { CalendarSource calendarSource = new CalendarSource("My Calendars"); calendarSource.getCalendars().add(new HelloCalendar()); calendarView = new CalendarView(Page.values()); calendarView.getCalendarSources().add(calendarSource); return calendarView; } @Override public String getSampleDescription() { return "The calendar view displays a single day, a week, a month, and a year."; } @Override protected Class getJavaDocClass() { return CalendarView.class; } class HelloCalendar extends Calendar { public HelloCalendar() { for (Month month : Month.values()) { YearMonth yearMonth = YearMonth.of(LocalDate.now().getYear(), month); for (int i = 1; i < 28; i++) { LocalDate date = yearMonth.atDay(i); for (int j = 0; j < (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.changeStartDate(date); entry.changeEndDate(date); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.min(24 - hour, (int) (Math.random() * 4)); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime .plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); if (Math.random() < .3) { entry.setFullDay(true); } entry.setCalendar(this); } } } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloDayView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DayView; import com.calendarfx.view.DayViewBase; import impl.com.calendarfx.view.CalendarPropertySheet; import javafx.scene.Node; import java.time.LocalDate; import java.time.LocalTime; public class HelloDayView extends CalendarFXSample { private final DayView dayView = new DayView(); @Override public String getSampleName() { return "Day View"; } @Override public Node getControlPanel() { return new CalendarPropertySheet(dayView.getPropertySheetItems()); } protected Node createControl() { CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().add(new HelloDayViewCalendar()); dayView.getCalendarSources().setAll(calendarSource); dayView.setHoursLayoutStrategy(DayViewBase.HoursLayoutStrategy.FIXED_HOUR_HEIGHT); dayView.setHourHeight(20); dayView.setPrefWidth(200); return dayView; } @Override public String getSampleDescription() { return "The day view displays a single day."; } @Override protected Class getJavaDocClass() { return DayView.class; } class HelloDayViewCalendar extends Calendar { public HelloDayViewCalendar() { createEntries(LocalDate.now()); } private void createEntries(LocalDate startDate) { for (int j = 0; j < 5 + (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.changeStartDate(startDate); entry.changeEndDate(startDate); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.max(1, Math.min(24 - hour, (int) (Math.random() * 4))); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); entry.setCalendar(this); } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloDetailedDayView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DateControl; import com.calendarfx.view.DetailedDayView; import javafx.application.Platform; import java.time.LocalDate; import java.time.LocalTime; public class HelloDetailedDayView extends CalendarFXDateControlSample { private DetailedDayView dayView; @Override public String getSampleName() { return "Detailed Day View"; } @Override public String getSampleDescription() { return "The detailed day view aggregates a day view, an all day view, a calendar header (for swimlane layout), and a time scale."; } @Override protected Class getJavaDocClass() { return DetailedDayView.class; } @Override protected DateControl createControl() { dayView = new DetailedDayView(); CalendarSource calendarSource = new CalendarSource(); dayView.getCalendarSources().setAll(calendarSource); HelloCalendar calendar1 = new HelloCalendar(); HelloCalendar calendar2 = new HelloCalendar(); HelloCalendar calendar3 = new HelloCalendar(); HelloCalendar calendar4 = new HelloCalendar(); calendar1.setName("Calendar 1"); calendar1.setShortName("C1"); calendar2.setName("Calendar 2"); calendar2.setShortName("C2"); calendar3.setName("Calendar 3"); calendar3.setShortName("C3"); calendar4.setName("Calendar 4"); calendar4.setShortName("C4"); calendar1.setStyle(Calendar.Style.STYLE1); calendar2.setStyle(Calendar.Style.STYLE2); calendar3.setStyle(Calendar.Style.STYLE3); calendar4.setStyle(Calendar.Style.STYLE4); calendarSource.getCalendars().setAll(calendar1, calendar2, calendar3, calendar4); Platform.runLater(() -> { calendar1.createData(); calendar2.createData(); calendar3.createData(); calendar4.createData(); }); return dayView; } class HelloCalendar extends Calendar { public HelloCalendar() { } public void createData() { LocalDate date = LocalDate.now(); for (int i = 1; i < 3; i++) { Entry entry = new Entry<>(); entry.changeStartDate(date); entry.changeEndDate(date); entry.setTitle("Entry " + i); int hour = (int) (Math.random() * 23); int durationInHours = Math.min(24 - hour, (int) (Math.random() * 4)); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); if (Math.random() < .1) { entry.setFullDay(true); entry.setTitle("Full Day Entry"); } entry.setCalendar(this); } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloDetailedWeekView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.DateControl; import com.calendarfx.view.DetailedWeekView; public class HelloDetailedWeekView extends CalendarFXDateControlSample { private DetailedWeekView detailedWeekView; @Override public String getSampleName() { return "Detailed Week View"; } @Override protected DateControl createControl() { Calendar dirk = new Calendar("Dirk"); Calendar katja = new Calendar("Katja"); Calendar philip = new Calendar("Philip"); Calendar jule = new Calendar("Jule"); Calendar armin = new Calendar("Armin"); dirk.setStyle(Style.STYLE1); katja.setStyle(Style.STYLE2); philip.setStyle(Style.STYLE3); jule.setStyle(Style.STYLE4); armin.setStyle(Style.STYLE5); CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().setAll(dirk, katja, philip, jule, armin); detailedWeekView = new DetailedWeekView(); detailedWeekView.getCalendarSources().setAll(calendarSource); return detailedWeekView; } @Override public String getSampleDescription() { return "The detailed week view displays several days inside a week view. " + "Additionally it shows an all day view at the top and a time scale " + "on its left-hand side"; } @Override protected Class getJavaDocClass() { return DetailedWeekView.class; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloMonthSheetView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DateControl; import com.calendarfx.view.MonthSheetView; import impl.com.calendarfx.view.CalendarPropertySheet; import javafx.scene.Node; import javafx.scene.control.ComboBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import java.time.Duration; import java.time.LocalDate; import java.time.LocalTime; public class HelloMonthSheetView extends CalendarFXDateControlSample { public static void main(String[] args) { launch(args); } private MonthSheetView monthView; @Override public String getSampleName() { return "Month Sheet View"; } @Override protected Class getJavaDocClass() { return MonthSheetView.class; } private enum CellType { STANDARD, USAGE, DETAILED, BADGE } @Override public Node getControlPanel() { VBox box = new VBox(); ComboBox comboBox = new ComboBox<>(); comboBox.getItems().addAll(CellType.values()); comboBox.setValue(CellType.STANDARD); comboBox.valueProperty().addListener(it -> { switch (comboBox.getValue()) { case USAGE: monthView.setCellFactory(param -> new MonthSheetView.UsageDateCell(param.getView(), param.getDate())); break; case BADGE: monthView.setCellFactory(param -> new MonthSheetView.BadgeDateCell(param.getView(), param.getDate())); break; case DETAILED: monthView.setCellFactory(param -> new MonthSheetView.DetailedDateCell(param.getView(), param.getDate())); break; case STANDARD: monthView.setCellFactory(param -> new MonthSheetView.SimpleDateCell(param.getView(), param.getDate())); break; } }); box.getChildren().add(comboBox); final CalendarPropertySheet propertySheet = new CalendarPropertySheet(monthView.getPropertySheetItems()); VBox.setVgrow(propertySheet, Priority.ALWAYS); box.getChildren().add(propertySheet); return box; } @Override protected DateControl createControl() { CalendarSource source = new CalendarSource(); source.setName("Demo Source"); Calendar[] calendar = new Calendar[7]; for (int i = 0; i < 7; i++) { calendar[i] = new Calendar("Calendar " + i); calendar[i].setStyle(Calendar.Style.getStyle(i)); } for (int i = 0; i < 1000; i++) { Entry entry = new Entry<>("Entry " + i); LocalDate date = LocalDate.now(); if (Math.random() < .5) { date = date.minusDays((long) (Math.random() * 365)); } else { date = date.plusDays((long) (Math.random() * 365)); } LocalTime start = LocalTime.of((int) (Math.random() * 20), (int) (Math.random() * 30)); Duration duration = Duration.ofHours((int) (Math.random() * 8)); LocalTime end = start.plus(duration); if (end.isBefore(start)) { end = LocalTime.MAX; } entry.changeStartDate(date); entry.changeEndDate(date); entry.changeStartTime(start); entry.changeEndTime(end); if (Math.random() > .9) { entry.setFullDay(true); } entry.setCalendar(calendar[(int) (Math.random() * 7)]); } source.getCalendars().addAll(calendar); monthView = new MonthSheetView(); monthView.getCalendarSources().add(source); monthView.setCellFactory(param -> new MonthSheetView.DetailedDateCell(param.getView(), param.getDate())); return monthView; } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloMonthView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DateControl; import com.calendarfx.view.MonthView; import java.time.LocalDate; import java.time.LocalTime; import java.time.YearMonth; public class HelloMonthView extends CalendarFXDateControlSample { private MonthView monthView; @Override public String getSampleName() { return "Month View"; } @Override protected DateControl createControl() { monthView = new MonthView(); CalendarSource calendarSource = new CalendarSource(); HelloCalendar calendar1 = new HelloCalendar(monthView.getYearMonth()); HelloCalendar calendar2 = new HelloCalendar(monthView.getYearMonth()); HelloCalendar calendar3 = new HelloCalendar(monthView.getYearMonth()); HelloCalendar calendar4 = new HelloCalendar(monthView.getYearMonth()); calendar1.setName("Calendar 1"); calendar2.setName("Calendar 2"); calendar3.setName("Calendar 3"); calendar4.setName("Calendar 4"); calendar1.setStyle(Style.STYLE1); calendar2.setStyle(Style.STYLE2); calendar3.setStyle(Style.STYLE3); calendar4.setStyle(Style.STYLE4); calendarSource.getCalendars().setAll(calendar1, calendar2, calendar3, calendar4); monthView.getCalendarSources().setAll(calendarSource); return monthView; } @Override protected Class getJavaDocClass() { return MonthView.class; } @Override public String getSampleDescription() { return "The month view displays the month of a given date."; } class HelloCalendar extends Calendar { public HelloCalendar(YearMonth month) { createEntries(month); } private void createEntries(YearMonth month) { for (int i = 1; i < 28; i++) { LocalDate date = month.atDay(i); for (int j = 0; j < (int) (Math.random() * 2); j++) { Entry entry = new Entry<>(); entry.setTitle("Entry " + (j + 1)); LocalDate startDate = date; LocalDate endDate = startDate.plusDays((int) (Math.random() * 4)); int hour = (int) (Math.random() * 23); int durationInHours = Math.min(23 - hour, (int) (Math.random() * 4)); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.setInterval(startDate, startTime, endDate, endTime); if (Math.random() < .3) { entry.setFullDay(true); } entry.setCalendar(this); } } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloRecurrenceView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.view.RecurrenceView; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.layout.VBox; public class HelloRecurrenceView extends CalendarFXSample { @Override public String getSampleName() { return "Recurrence View"; } @Override protected Node createControl() { RecurrenceView view = new RecurrenceView(); Label label = new Label("Rule: " + view.getRecurrenceRule()); label.setMaxWidth(300); label.setWrapText(true); view.recurrenceRuleProperty().addListener(it -> label.setText(view.getRecurrenceRule())); Separator separator = new Separator(Orientation.HORIZONTAL); VBox box = new VBox(20); box.setFillWidth(true); box.getChildren().addAll(view, separator, label); box.setAlignment(Pos.CENTER); return box; } @Override protected Class getJavaDocClass() { return RecurrenceView.class; } @Override public String getSampleDescription() { return "The recurrence view allows the user to specify recurrence rules according to RFC 2445."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloResourcesCalendarView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.model.Marker; import com.calendarfx.view.DayEntryView; import com.calendarfx.view.DayView; import com.calendarfx.view.DayViewBase.OverlapResolutionStrategy; import com.calendarfx.view.ResourceCalendarView; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.layout.VBox; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZonedDateTime; public class HelloResourcesCalendarView extends CalendarFXSample { private final ResourceCalendarView view = new ResourceCalendarView<>(); @Override public String getSampleName() { return "Resources Calendar View"; } @Override public Node createControl() { view.setShowNoonMarker(false); view.setOverlapResolutionStrategy(OverlapResolutionStrategy.VISUAL_BOUNDS); view.setHeaderFactory(resource -> { Label label1 = new Label("IG-TR"); Label label2 = new Label("29.000"); Label label3 = new Label("13.200"); Label label4 = new Label("92 (CPC)"); label1.setStyle("-fx-font-family: Monospaced; -fx-font-style: bold;"); label2.setStyle("-fx-font-family: Monospaced; -fx-text-fill: blue; -fx-font-style: bold;"); label3.setStyle("-fx-font-family: Monospaced; -fx-text-fill: blue; -fx-font-style: bold;"); label4.setStyle("-fx-font-family: Monospaced; -fx-text-fill: blue; -fx-font-style: bold;"); label1.setAlignment(Pos.CENTER); label2.setAlignment(Pos.CENTER); label3.setAlignment(Pos.CENTER); label4.setAlignment(Pos.CENTER); VBox box = new VBox(5, label1, label2, label3, label4); box.setFillWidth(true); if (resource.equals("Resource 1")) { box.setPrefWidth(400); } return box; }); for (int i = 0; i < 5; i++) { CalendarSource source = new CalendarSource("Default"); Calendar calendar1 = new HelloDayViewCalendar("cal1"); calendar1.setStyle(Style.STYLE1); source.getCalendars().add(calendar1); Calendar calendar2 = new HelloDayViewCalendar("cal2"); calendar2.setStyle(Style.STYLE2); source.getCalendars().add(calendar2); Calendar calendar3 = new HelloDayViewCalendar("cal3"); calendar3.setStyle(Style.STYLE3); source.getCalendars().add(calendar3); String resource = "Resource " + (i + 1); view.getResources().add(resource); DayView dayView = view.getDayView(resource); dayView.setEnableCurrentTimeMarker(true); dayView.setEnableCurrentTimeCircle(i == 0); dayView.getCalendarSources().setAll(source); dayView.setEntryViewFactory(entry -> new DayEntryView(entry) { { setPrefHeight(25); // setHeightLayoutStrategy(HeightLayoutStrategy.COMPUTE_PREF_SIZE); } }); if (i % 2 == 1) { dayView.setStyle("-fx-background-color: #e9e9e9;"); } } Marker marker1 = new Marker(); marker1.setTitle("My Marker 1"); marker1.setTime(ZonedDateTime.now().minusHours(1)); view.getMarkers().add(marker1); Marker marker2 = new Marker(); marker2.setTitle("My Marker 2"); marker2.setTime(ZonedDateTime.now().plusHours(1)); marker2.getStyleClass().add("marker2"); view.getMarkers().add(marker2); view.setPrefHeight(800); return view; } @Override public Node getControlPanel() { CheckBox editSchedule = new CheckBox("Edit Schedule"); editSchedule.selectedProperty().bindBidirectional(view.editAvailabilityProperty()); return editSchedule; } @Override public String getSampleDescription() { return "The resource calendar view can scroll vertically up and down with infinite scrolling enabled."; } @Override protected Class getJavaDocClass() { return DayView.class; } class HelloDayViewCalendar extends Calendar { public HelloDayViewCalendar(String name) { setName(name); createEntries(LocalDate.now().minusDays(2)); createEntries(LocalDate.now().minusDays(1)); createEntries(LocalDate.now()); createEntries(LocalDate.now().plusDays(1)); createEntries(LocalDate.now().plusDays(2)); } private void createEntries(LocalDate startDate) { for (int j = 0; j < 5 + (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.changeStartDate(startDate); entry.changeEndDate(startDate); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.max(1, Math.min(24 - hour, (int) (Math.random() * 4))); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); entry.setCalendar(this); } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloScrollingDayView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DayView; import com.calendarfx.view.DayViewBase; import impl.com.calendarfx.view.CalendarPropertySheet; import javafx.scene.Node; import java.time.LocalDate; import java.time.LocalTime; public class HelloScrollingDayView extends CalendarFXSample { private final DayView dayView = new DayView(); @Override public String getSampleName() { return "Day View (Scrolling)"; } @Override public Node getControlPanel() { return new CalendarPropertySheet(dayView.getPropertySheetItems()); } protected Node createControl() { CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().add(new HelloDayViewCalendar()); dayView.getCalendarSources().setAll(calendarSource); dayView.setScrollingEnabled(true); dayView.setHoursLayoutStrategy(DayViewBase.HoursLayoutStrategy.FIXED_HOUR_HEIGHT); dayView.setHourHeight(20); dayView.setPrefWidth(200); dayView.setPrefHeight(500); return dayView; } @Override public String getSampleDescription() { return "The day view can scroll vertically up and down with infinite scrolling enabled."; } @Override protected Class getJavaDocClass() { return DayView.class; } class HelloDayViewCalendar extends Calendar { public HelloDayViewCalendar() { createEntries(LocalDate.now()); } private void createEntries(LocalDate startDate) { for (int j = 0; j < 5 + (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.changeStartDate(startDate); entry.changeEndDate(startDate); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.max(1, Math.min(24 - hour, (int) (Math.random() * 4))); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); entry.setCalendar(this); } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloScrollingTimeScaleView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.view.TimeScaleView; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.effect.Reflection; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import java.time.DayOfWeek; import java.time.LocalDateTime; public class HelloScrollingTimeScaleView extends CalendarFXSample { public static final String STYLE_LABEL_TIME_EVEN_HOURS = "-fx-text-fill: gray;"; public static final String STYLE_LABEL_TIME_ODD_HOURS = "-fx-text-fill: darkblue;"; public static final String STYLE_LABEL_DATE_SUNDAY = "-fx-background-color: red;"; @Override public String getSampleName() { return "Time Scale (Scrolling)"; } @Override protected Node createControl() { return null; } @Override public Node getPanel(Stage stage) { TimeScaleView view = new TimeScaleView(); view.setTimeStyleProvider(this::provideTimeStyle); view.setDateStyleProvider(this::provideDateStyle); view.setScrollingEnabled(true); return wrap(view); } @Override public Node wrap(Node node) { HBox box = new HBox(); box.setStyle("-fx-padding: 100px;"); box.setAlignment(Pos.CENTER); box.setFillHeight(false); StackPane stackPane = new StackPane(); stackPane.setStyle( "-fx-background-color: white; -fx-border-color: gray; -fx-border-width: .25px; -fx-padding: 0 20 0 20;"); box.getChildren().add(stackPane); stackPane.getChildren().add(node); stackPane.setEffect(new Reflection()); stackPane.setPrefHeight(2000); return box; } @Override protected Class getJavaDocClass() { return TimeScaleView.class; } @Override public String getSampleDescription() { return "The scale shows the time of day vertically."; } private String provideTimeStyle(LocalDateTime dateTime) { return dateTime.getHour() % 2 == 0 ? STYLE_LABEL_TIME_EVEN_HOURS : STYLE_LABEL_TIME_ODD_HOURS; } private String provideDateStyle(LocalDateTime dateTime) { return dateTime.getDayOfWeek() == DayOfWeek.SUNDAY ? STYLE_LABEL_DATE_SUNDAY : null; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloSourceGridView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.SourceGridView; import javafx.scene.Node; import javafx.scene.control.CheckBox; import javafx.scene.layout.VBox; public class HelloSourceGridView extends CalendarFXSample { private SourceGridView sourceView; @Override public String getSampleName() { return "Source Grid View"; } @Override protected Class getJavaDocClass() { return SourceGridView.class; } @Override protected Node createControl() { sourceView = new SourceGridView(); Calendar meetings = new Calendar("Meetings"); Calendar training = new Calendar("Training"); Calendar customers = new Calendar("Customers"); Calendar holidays = new Calendar("Holidays"); meetings.setStyle(Style.STYLE2); training.setStyle(Style.STYLE3); customers.setStyle(Style.STYLE4); holidays.setStyle(Style.STYLE5); CalendarSource workCalendarSource = new CalendarSource("Work"); workCalendarSource.getCalendars().addAll(meetings, training, customers, holidays); Calendar birthdays = new Calendar("Birthdays"); Calendar katja = new Calendar("Katja"); Calendar dirk = new Calendar("Dirk"); Calendar philip = new Calendar("Philip"); Calendar jule = new Calendar("Jule"); Calendar armin = new Calendar("Armin"); CalendarSource familyCalendarSource = new CalendarSource("Family"); familyCalendarSource.getCalendars().addAll(birthdays, katja, dirk, philip, jule, armin); sourceView.getCalendarSources().addAll(workCalendarSource, familyCalendarSource); return sourceView; } @Override public Node getControlPanel() { Node controlPanel = super.getControlPanel(); VBox vBox = new VBox(); vBox.setSpacing(5); vBox.getChildren().add(controlPanel); for (CalendarSource calendarSource : sourceView.getCalendarSources()) { for (Calendar calendar : calendarSource.getCalendars()) { CheckBox checkBox = new CheckBox(calendar.getName()); checkBox.selectedProperty().bindBidirectional(sourceView.getCalendarVisibilityProperty(calendar)); vBox.getChildren().add(checkBox); } } return vBox; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloSourceView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.SourceView; import javafx.geometry.Orientation; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.Separator; import javafx.scene.layout.VBox; public class HelloSourceView extends CalendarFXSample { private SourceView sourceView; private CalendarSource workCalendarSource; private CalendarSource familyCalendarSource; @Override public String getSampleName() { return "Source View"; } @Override protected Node createControl() { sourceView = new SourceView(); Calendar meetings = new Calendar("Meetings"); Calendar training = new Calendar("Training"); Calendar customers = new Calendar("Customers"); Calendar holidays = new Calendar("Holidays"); meetings.setStyle(Style.STYLE2); training.setStyle(Style.STYLE3); customers.setStyle(Style.STYLE4); holidays.setStyle(Style.STYLE5); workCalendarSource = new CalendarSource("Work"); workCalendarSource.getCalendars().addAll(meetings, training, customers, holidays); Calendar birthdays = new Calendar("Birthdays"); Calendar katja = new Calendar("Katja"); Calendar dirk = new Calendar("Dirk"); Calendar philip = new Calendar("Philip"); Calendar jule = new Calendar("Jule"); Calendar armin = new Calendar("Armin"); familyCalendarSource = new CalendarSource("Family"); familyCalendarSource.getCalendars().addAll(birthdays, katja, dirk, philip, jule, armin); sourceView.getCalendarSources().addAll(workCalendarSource, familyCalendarSource); return sourceView; } @Override public Node getControlPanel() { VBox box = new VBox(); box.setSpacing(5); box.setFillWidth(true); Button addWorkCalendar = new Button("Add Work Calendar"); addWorkCalendar.setOnAction(evt -> addWorkCalendar()); Button removeWorkCalendar = new Button("Remove Work Calendar"); removeWorkCalendar.setOnAction(evt -> removeWorkCalendar()); Button addWorkCalendarSource = new Button("Add Work Calendar Source"); addWorkCalendarSource.setOnAction(evt -> addWorkCalendarSource()); Button removeWorkCalendarSource = new Button( "Remove Work Calendar Source"); removeWorkCalendarSource.setOnAction(evt -> removeWorkCalendarSource()); box.getChildren().addAll(addWorkCalendar, removeWorkCalendar, addWorkCalendarSource, removeWorkCalendarSource); Button addFamilyCalendar = new Button("Add Family Calendar"); addFamilyCalendar.setOnAction(evt -> addFamilyCalendar()); Button removeFamilyCalendar = new Button("Remove Family Calendar"); removeFamilyCalendar.setOnAction(evt -> removeFamilyCalendar()); Button addFamilyCalendarSource = new Button( "Add Family Calendar Source"); addFamilyCalendarSource.setOnAction(evt -> addFamilyCalendarSource()); Button removeFamilyCalendarSource = new Button( "Remove Family Calendar Source"); removeFamilyCalendarSource .setOnAction(evt -> removeFamilyCalendarSource()); box.getChildren().addAll(new Separator(Orientation.HORIZONTAL), addFamilyCalendar, removeFamilyCalendar, addFamilyCalendarSource, removeFamilyCalendarSource); addWorkCalendar.setMaxWidth(Double.MAX_VALUE); removeWorkCalendar.setMaxWidth(Double.MAX_VALUE); addWorkCalendarSource.setMaxWidth(Double.MAX_VALUE); removeWorkCalendarSource.setMaxWidth(Double.MAX_VALUE); addFamilyCalendar.setMaxWidth(Double.MAX_VALUE); removeFamilyCalendar.setMaxWidth(Double.MAX_VALUE); addFamilyCalendarSource.setMaxWidth(Double.MAX_VALUE); removeFamilyCalendarSource.setMaxWidth(Double.MAX_VALUE); return box; } private int calendarCounter = 1; private void addWorkCalendar() { Calendar calendar = new Calendar("Work Calendar " + calendarCounter++); calendar.setStyle(Style.getStyle(calendarCounter)); workCalendarSource.getCalendars().add(calendar); } private void removeWorkCalendar() { workCalendarSource.getCalendars().remove( workCalendarSource.getCalendars().size() - 1); } private void addWorkCalendarSource() { sourceView.getCalendarSources().add(workCalendarSource); } private void removeWorkCalendarSource() { sourceView.getCalendarSources().remove(workCalendarSource); } private void addFamilyCalendar() { Calendar calendar = new Calendar("Family Calendar " + calendarCounter++); calendar.setStyle(Style.getStyle(calendarCounter)); familyCalendarSource.getCalendars().add(calendar); } private void addFamilyCalendarSource() { sourceView.getCalendarSources().add(familyCalendarSource); } private void removeFamilyCalendar() { familyCalendarSource.getCalendars().remove( familyCalendarSource.getCalendars().size() - 1); } private void removeFamilyCalendarSource() { sourceView.getCalendarSources().remove(familyCalendarSource); } @Override protected Class getJavaDocClass() { return SourceView.class; } @Override public String getSampleDescription() { return "Shows all calendar sources. Sources are used to group calendars together that all origin from the same " + "source, e.g. Google calendar. Sources can be collapsed by clicking on their name."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloTimeField.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.view.TimeField; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.VBox; public class HelloTimeField extends CalendarFXSample { @Override public String getSampleName() { return "Time Field"; } @Override protected Node createControl() { TimeField timeField = new TimeField(); Label label = new Label("Time: "); label.setText("Time: " + timeField.getValue().toString()); timeField.valueProperty().addListener(it -> label.setText("Time: " + timeField.getValue().toString())); VBox box = new VBox(); box.setSpacing(20); box.getChildren().addAll(timeField, label); return box; } @Override protected Class getJavaDocClass() { return TimeField.class; } @Override public String getSampleDescription() { return "A control used to specify a local time (hour, minute)."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloTimeScaleView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.view.TimeScaleView; import impl.com.calendarfx.view.DayViewScrollPane; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.ScrollBar; import javafx.scene.effect.Reflection; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import java.time.LocalDateTime; public class HelloTimeScaleView extends CalendarFXSample { public static final String STYLE_LABEL_TIME_EVEN_HOURS = "-fx-text-fill: gray;"; public static final String STYLE_LABEL_TIME_ODD_HOURS = "-fx-text-fill: darkblue;"; @Override public String getSampleName() { return "Time Scale"; } @Override protected Node createControl() { return null; } @Override public Node getPanel(Stage stage) { TimeScaleView view = new TimeScaleView(); view.setTimeStyleProvider(this::provideTimeStyle); final DayViewScrollPane scrollPane = new DayViewScrollPane(view, new ScrollBar()); scrollPane.setPrefHeight(2000); return wrap(scrollPane); } @Override public Node wrap(Node node) { HBox box = new HBox(); box.setStyle("-fx-padding: 100px;"); box.setAlignment(Pos.CENTER); box.setFillHeight(false); StackPane stackPane = new StackPane(); stackPane.setStyle("-fx-background-color: white; -fx-border-color: gray; -fx-border-width: .25px; -fx-padding: 0 20 0 20;"); box.getChildren().add(stackPane); stackPane.getChildren().add(node); stackPane.setEffect(new Reflection()); return box; } @Override protected Class getJavaDocClass() { return TimeScaleView.class; } @Override public String getSampleDescription() { return "The scale shows the time of day vertically."; } private String provideTimeStyle(LocalDateTime dateTime) { return dateTime.getHour() % 2 == 0 ? STYLE_LABEL_TIME_EVEN_HOURS : STYLE_LABEL_TIME_ODD_HOURS; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloTimezones.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DateControl.Layout; import com.calendarfx.view.DayView; import com.calendarfx.view.DayViewBase; import com.calendarfx.view.DayViewBase.EarlyLateHoursStrategy; import impl.com.calendarfx.view.CalendarPropertySheet; import javafx.scene.Node; import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; import java.util.List; import java.util.Map; public class HelloTimezones extends CalendarFXSample { private final DayView dayView = new DayView(); @Override public String getSampleName() { return "Time Zones"; } @Override public Node getControlPanel() { return new CalendarPropertySheet(dayView.getPropertySheetItems()); } protected Node createControl() { CalendarSource calendarSource = new CalendarSource(); ParisCalendar calendar = new ParisCalendar(); calendar.setStyle(Style.STYLE1); calendarSource.getCalendars().add(calendar); LondonCalendar timeZoneCalendar = new LondonCalendar(calendar); timeZoneCalendar.setStyle(Style.STYLE2); calendarSource.getCalendars().add(timeZoneCalendar); dayView.setZoneId(ZoneId.of("Europe/Paris")); dayView.setEarlyLateHoursStrategy(EarlyLateHoursStrategy.SHOW_COMPRESSED); dayView.setStartTime(LocalTime.of(6, 0)); dayView.setEndTime(LocalTime.of(20, 0)); dayView.setLayout(Layout.SWIMLANE); dayView.getCalendarSources().setAll(calendarSource); dayView.setHoursLayoutStrategy(DayViewBase.HoursLayoutStrategy.FIXED_HOUR_HEIGHT); dayView.setHourHeight(20); dayView.setPrefWidth(200); return dayView; } @Override public String getSampleDescription() { return "The day view displays a single day."; } @Override protected Class getJavaDocClass() { return DayView.class; } class LondonCalendar extends Calendar { public LondonCalendar(ParisCalendar input) { Instant earliestTimeUsed = input.getEarliestTimeUsed(); Instant latestTimeUsed = input.getLatestTimeUsed(); Map>> entries = input.findEntries(LocalDate.ofInstant(earliestTimeUsed, ZoneId.systemDefault()), LocalDate.ofInstant(latestTimeUsed, ZoneId.systemDefault()), ZoneId.systemDefault()); entries.values().forEach(entryList -> { entryList.forEach(e -> { Entry copiedEntry = new Entry<>(); copiedEntry.setTitle(e.getTitle().replace("Paris", "Chicago")); copiedEntry.setInterval(e.getStartDate(), e.getStartTime(), e.getEndDate(), e.getEndTime(), ZoneId.of("America/Chicago")); addEntries(copiedEntry); }); }); } } class ParisCalendar extends Calendar { public ParisCalendar() { LocalDate date = LocalDate.now(); ZoneId zoneId = ZoneId.of("Europe/Paris"); Entry entry = new Entry<>(); entry.setTitle("Paris"); int hour = 2; int durationInHours = 4; LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.setInterval(date, startTime, date, endTime, zoneId); addEntry(entry); } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloTopLayer.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DateControl; import com.calendarfx.view.DayEntryView; import com.calendarfx.view.DayView; import com.calendarfx.view.DayViewBase; import com.calendarfx.view.EntryViewBase.AlignmentStrategy; import impl.com.calendarfx.view.CalendarPropertySheet; import javafx.scene.Node; import java.time.LocalDate; import java.time.LocalTime; import java.util.List; public class HelloTopLayer extends CalendarFXSample { private final DayView dayView = new DayView(); @Override public String getSampleName() { return "Day View with Top Layer"; } @Override public Node getControlPanel() { return new CalendarPropertySheet(dayView.getPropertySheetItems()); } protected Node createControl() { CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().add(new BaseLayerCalendar()); calendarSource.getCalendars().add(new TopLayerCalendar()); dayView.getCalendarSources().setAll(calendarSource); dayView.setHoursLayoutStrategy(DayViewBase.HoursLayoutStrategy.FIXED_HOUR_HEIGHT); dayView.setHourHeight(20); dayView.setPrefWidth(200); dayView.visibleLayersProperty().addAll(List.of(DateControl.Layer.BASE, DateControl.Layer.TOP)); dayView.setEntryViewFactory(this::createEntryView); return dayView; } private DayEntryView createEntryView(Entry entry) { DayEntryView entryView = new DayEntryView(entry); if (entry.getCalendar() instanceof TopLayerCalendar) { entryView.setLayer(DateControl.Layer.TOP); entryView.setWidthPercentage(20.0); entryView.setAlignmentStrategy(AlignmentStrategy.ALIGN_RIGHT); } return entryView; } @Override public String getSampleDescription() { return "The day view supports base and top layers functionality."; } @Override protected Class getJavaDocClass() { return DayView.class; } static class BaseLayerCalendar extends Calendar { public BaseLayerCalendar() { createCalendarEntries(this, "Entry", LocalDate.now()); setStyle(Style.STYLE1); } } static class TopLayerCalendar extends Calendar { public TopLayerCalendar() { createCalendarEntries(this, "Note", LocalDate.now()); setStyle(Style.STYLE2); } } private static void createCalendarEntries(Calendar calendar, String titlePrefix, LocalDate startDate) { for (int j = 0; j < 5 + (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.changeStartDate(startDate); entry.changeEndDate(startDate); entry.setTitle(titlePrefix + " " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.max(1, Math.min(24 - hour, (int) (Math.random() * 4))); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); entry.setCalendar(calendar); } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloVisualBounds.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DayEntryView; import com.calendarfx.view.DayView; import com.calendarfx.view.DayViewBase; import com.calendarfx.view.DayViewBase.OverlapResolutionStrategy; import impl.com.calendarfx.view.CalendarPropertySheet; import javafx.scene.Node; import java.time.LocalDate; import java.time.LocalTime; public class HelloVisualBounds extends CalendarFXSample { private final DayView dayView = new DayView(); @Override public String getSampleName() { return "Day View (Visual Bounds)"; } @Override public Node getControlPanel() { return new CalendarPropertySheet(dayView.getPropertySheetItems()); } protected Node createControl() { CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().add(new HelloDayViewCalendar()); dayView.setOverlapResolutionStrategy(OverlapResolutionStrategy.VISUAL_BOUNDS); dayView.getCalendarSources().setAll(calendarSource); dayView.setHoursLayoutStrategy(DayViewBase.HoursLayoutStrategy.FIXED_HOUR_HEIGHT); dayView.setHourHeight(20); dayView.setPrefWidth(200); dayView.setEntryViewFactory(entry -> new DayEntryView(entry) { { setPrefHeight(50); setHeightLayoutStrategy(HeightLayoutStrategy.COMPUTE_PREF_SIZE); } }); return dayView; } @Override public String getSampleDescription() { return "Entries are considered 'overlapping' if their visual bounds overlap, not when their time bounds do so."; } @Override protected Class getJavaDocClass() { return DayView.class; } class HelloDayViewCalendar extends Calendar { public HelloDayViewCalendar() { createEntries(LocalDate.now()); } private void createEntries(LocalDate startDate) { for (int j = 0; j < 5 + (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.changeStartDate(startDate); entry.changeEndDate(startDate); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.max(1, Math.min(24 - hour, (int) (Math.random() * 4))); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); entry.setCalendar(this); } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloWeekDayHeaderView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.view.DateControl; import com.calendarfx.view.WeekDayHeaderView; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.effect.Reflection; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; public class HelloWeekDayHeaderView extends CalendarFXDateControlSample { @Override public String getSampleName() { return "Week Day Header View"; } @Override protected DateControl createControl() { return new WeekDayHeaderView(); } @Override protected Node wrap(Node node) { HBox box = new HBox(); box.setMaxWidth(Double.MAX_VALUE); box.setAlignment(Pos.CENTER); box.setFillHeight(false); StackPane stackPane = new StackPane(); stackPane.setStyle("-fx-background-color: white; -fx-border-color: gray; -fx-border-width: .25px; -fx-padding: 20px;"); box.getChildren().add(stackPane); stackPane.getChildren().add(node); stackPane.setEffect(new Reflection()); return box; } @Override public String getSampleDescription() { return "The week day header view displays the labels for each week day."; } @Override protected Class getJavaDocClass() { return WeekDayHeaderView.class; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloWeekDayView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DateControl; import com.calendarfx.view.WeekDayView; import java.time.LocalDate; import java.time.LocalTime; public class HelloWeekDayView extends CalendarFXDateControlSample { private WeekDayView weekDayView; @Override public String getSampleName() { return "Week Day View"; } @Override protected DateControl createControl() { CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().add(new HelloDayViewCalendar()); weekDayView = new WeekDayView(); weekDayView.getCalendarSources().add(calendarSource); return weekDayView; } @Override public String getSampleDescription() { return "The day view displays a single day."; } @Override protected Class getJavaDocClass() { return WeekDayView.class; } class HelloDayViewCalendar extends Calendar { public HelloDayViewCalendar() { createEntries(LocalDate.now()); } private void createEntries(LocalDate startDate) { for (int j = 0; j < 5 + (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.changeStartDate(startDate); entry.changeEndDate(startDate); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.min(24 - hour, (int) (Math.random() * 4)); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); entry.setCalendar(this); } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloWeekFieldsView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.view.AgendaView; import com.calendarfx.view.WeekFieldsView; import javafx.scene.control.Control; public class HelloWeekFieldsView extends CalendarFXSample { private WeekFieldsView view; @Override public String getSampleName() { return "Week Fields View"; } @Override protected Control createControl() { view = new WeekFieldsView(); return view; } @Override protected Class getJavaDocClass() { return AgendaView.class; } @Override public String getSampleDescription() { return "The week fields view lets the user specify the first day of the week " + "(e.g. MONDAY in Germany, SUNDAY in the US) and the minimum number of " + "days in the first week of the year."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloWeekTimeScaleView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.view.DateControl; import com.calendarfx.view.WeekTimeScaleView; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.effect.Reflection; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; public class HelloWeekTimeScaleView extends CalendarFXDateControlSample { @Override public String getSampleName() { return "Week Time Scale"; } @Override protected DateControl createControl() { return new WeekTimeScaleView(); } @Override protected Node wrap(Node node) { HBox box = new HBox(); box.setStyle("-fx-padding: 100px;"); box.setAlignment(Pos.CENTER); box.setFillHeight(false); StackPane stackPane = new StackPane(); stackPane.setStyle( "-fx-background-color: white; -fx-border-color: gray; -fx-border-width: .25px; -fx-padding: 0 20 0 20;"); box.getChildren().add(stackPane); stackPane.getChildren().add(node); stackPane.setEffect(new Reflection()); return box; } @Override protected Class getJavaDocClass() { return WeekTimeScaleView.class; } @Override public String getSampleDescription() { return "The scale shows the time of day vertically."; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloWeekView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.DetailedWeekView; import com.calendarfx.view.WeekView; import impl.com.calendarfx.view.CalendarPropertySheet; import impl.com.calendarfx.view.DayViewScrollPane; import javafx.scene.Node; import javafx.scene.control.ScrollBar; import javafx.stage.Stage; public class HelloWeekView extends CalendarFXSample { private final WeekView weekView = new WeekView(); @Override public String getSampleName() { return "Week View"; } @Override protected Node createControl() { return null; } @Override public Node getControlPanel() { return new CalendarPropertySheet(weekView.getPropertySheetItems()); } @Override public Node getPanel(Stage stage) { Calendar dirk = new Calendar("Dirk"); Calendar katja = new Calendar("Katja"); Calendar philip = new Calendar("Philip"); Calendar jule = new Calendar("Jule"); Calendar armin = new Calendar("Armin"); dirk.setStyle(Style.STYLE1); katja.setStyle(Style.STYLE2); philip.setStyle(Style.STYLE3); jule.setStyle(Style.STYLE4); armin.setStyle(Style.STYLE5); CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().add(dirk); calendarSource.getCalendars().add(katja); calendarSource.getCalendars().add(philip); calendarSource.getCalendars().add(jule); calendarSource.getCalendars().add(armin); weekView.getCalendarSources().setAll(calendarSource); DayViewScrollPane scroll = new DayViewScrollPane(weekView, new ScrollBar()); scroll.setStyle("-fx-background-color: white;"); return scroll; } @Override public String getSampleDescription() { return "The week view displays several days. Most commonly this view " + "will be used to show the 5 work days of a week or the 7 standard " + "days."; } @Override protected Class getJavaDocClass() { return DetailedWeekView.class; } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloYearMonthView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DateControl; import com.calendarfx.view.YearMonthView; import java.time.LocalDate; import java.time.LocalTime; import java.time.YearMonth; public class HelloYearMonthView extends CalendarFXDateControlSample { private YearMonthView yearMonthView; @Override public String getSampleName() { return "Year Month View"; } @Override protected DateControl createControl() { yearMonthView = new YearMonthView(); CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().add(new HelloCalendar(yearMonthView.getYearMonth())); yearMonthView.getCalendarSources().add(calendarSource); return yearMonthView; } @Override public String getSampleDescription() { return "The month view displays the month of a given date."; } @Override protected Class getJavaDocClass() { return YearMonthView.class; } class HelloCalendar extends Calendar { public HelloCalendar(YearMonth month) { createEntries(month); } private void createEntries(YearMonth month) { for (int i = 1; i < 28; i++) { LocalDate date = month.atDay(i); for (int j = 0; j < (int) (Math.random() * 7); j++) { Entry entry = new Entry<>(); entry.changeStartDate(date); entry.changeEndDate(date); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.min(24 - hour, (int) (Math.random() * 4)); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); if (Math.random() < .3) { entry.setFullDay(true); } entry.setCalendar(this); } } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/HelloYearView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.view.DateControl; import com.calendarfx.view.YearView; import java.time.LocalDate; import java.time.LocalTime; import java.time.Year; public class HelloYearView extends CalendarFXDateControlSample { @Override public String getSampleName() { return "Year View"; } @Override protected DateControl createControl() { YearView yearView = new YearView(); CalendarSource calendarSource = new CalendarSource(); calendarSource.getCalendars().add(new HelloCalendar(yearView.getYear())); yearView.getCalendarSources().add(calendarSource); return yearView; } @Override public String getSampleDescription() { return "The year view displays the twelve month of a given year."; } @Override protected Class getJavaDocClass() { return YearView.class; } class HelloCalendar extends Calendar { public HelloCalendar(Year year) { for (int i = 1; i < 365; i++) { LocalDate date = year.atDay(i); for (int j = 0; j < (int) (Math.random() * 4); j++) { Entry entry = new Entry<>(); entry.changeStartDate(date); entry.changeEndDate(date); entry.setTitle("Entry " + (j + 1)); int hour = (int) (Math.random() * 23); int durationInHours = Math.min(24 - hour, (int) (Math.random() * 4)); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); if (Math.random() < .3) { entry.setFullDay(true); } entry.setCalendar(this); } } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/com/calendarfx/demo/views/resources/HelloResourcesView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * Copyright (C) 2006 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.demo.views.resources; import com.calendarfx.demo.CalendarFXDateControlSample; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Entry; import com.calendarfx.model.Resource; import com.calendarfx.view.DateControl; import com.calendarfx.view.DateControl.Layout; import com.calendarfx.view.DayViewBase.AvailabilityEditingEntryBehaviour; import com.calendarfx.view.DayViewBase.EarlyLateHoursStrategy; import com.calendarfx.view.DayViewBase.GridType; import com.calendarfx.view.ResourcesView; import com.calendarfx.view.ResourcesView.Type; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; import javafx.scene.control.DatePicker; import javafx.scene.control.Label; import javafx.scene.control.Slider; import javafx.scene.control.ToggleButton; import javafx.scene.layout.VBox; import javafx.util.StringConverter; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalTime; import java.time.temporal.TemporalAdjusters; import java.util.List; import java.util.Random; import java.util.function.Supplier; public class HelloResourcesView extends CalendarFXDateControlSample { private ResourcesView resourcesView; public static final int DATA_GENERATION_SEED = 11011; final Random random = new Random(DATA_GENERATION_SEED); @Override public String getSampleName() { return "ResourcesView"; } @Override public String getSampleDescription() { return "The resources view is used to display allocations of resources, e.g. people working at a hairdresser and their customer appointments."; } @Override protected Class getJavaDocClass() { return ResourcesView.class; } @Override protected boolean isSupportingDeveloperConsole() { return false; } @Override public Node getControlPanel() { ToggleButton availabilityButton = new ToggleButton("Edit Schedule"); availabilityButton.selectedProperty().bindBidirectional(resourcesView.editAvailabilityProperty()); DatePicker datePicker = new DatePicker(); datePicker.valueProperty().bindBidirectional(resourcesView.dateProperty()); ChoiceBox daysBox = new ChoiceBox<>(); daysBox.getItems().setAll(1, 2, 3, 4, 5, 7, 10, 14); daysBox.setValue(resourcesView.getNumberOfDays()); daysBox.valueProperty().addListener(it -> resourcesView.setNumberOfDays(daysBox.getValue())); ChoiceBox numberOfResourcesBox = new ChoiceBox<>(); numberOfResourcesBox.getItems().setAll(1, 2, 3, 4, 5); numberOfResourcesBox.setValue(resourcesView.getResources().size()); numberOfResourcesBox.valueProperty().addListener(it -> resourcesView.getResources().setAll(createResources(numberOfResourcesBox.getValue()))); Button memoryTestButton = new Button("Test Heap"); memoryTestButton.setOnAction(evt -> { Thread thread = new Thread(() -> { Runtime r = Runtime.getRuntime(); int counter = 0; while (true) { counter++; final int fc = counter; Platform.runLater(() -> { List> resources = createResources(5); resourcesView.getResources().setAll(resources); resources.forEach(resource -> { CalendarSource source = new CalendarSource("Default"); HelloDayViewCalendar calendar1 = new HelloDayViewCalendar(random.nextLong()); calendar1.generateBaseEntries(); calendar1.setStyle(Style.STYLE1); source.getCalendars().add(calendar1); HelloDayViewCalendar calendar2 = new HelloDayViewCalendar(random.nextLong()); calendar2.generateBaseEntries(); calendar2.setStyle(Style.STYLE2); source.getCalendars().add(calendar2); HelloDayViewCalendar calendar3 = new HelloDayViewCalendar(random.nextLong()); calendar3.generateBaseEntries(); calendar3.setStyle(Style.STYLE3); source.getCalendars().add(calendar3); HelloDayViewCalendar calendar4 = new HelloDayViewCalendar(random.nextLong()); calendar4.generateTopEntries(); calendar4.setStyle(Style.STYLE4); source.getCalendars().add(calendar4); resource.getCalendarSources().setAll(source); }); System.out.println(fc + ": free: " + (r.freeMemory() / 1_000) + " kb"); }); try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); thread.setName("Memory Test Thread"); thread.setDaemon(true); thread.start(); }); ChoiceBox clicksBox = new ChoiceBox<>(); clicksBox.getItems().setAll(1, 2, 3); clicksBox.setValue(resourcesView.getCreateEntryClickCount()); clicksBox.valueProperty().addListener(it -> resourcesView.setCreateEntryClickCount(clicksBox.getValue())); ChoiceBox behaviourBox = new ChoiceBox<>(); behaviourBox.getItems().setAll(AvailabilityEditingEntryBehaviour.values()); behaviourBox.valueProperty().bindBidirectional(resourcesView.entryViewAvailabilityEditingBehaviourProperty()); ChoiceBox typeBox = new ChoiceBox<>(); typeBox.getItems().setAll(Type.values()); typeBox.valueProperty().bindBidirectional(resourcesView.typeProperty()); typeBox.setConverter(new StringConverter<>() { @Override public String toString(Type object) { if (object != null) { if (object.equals(Type.RESOURCES_OVER_DATES)) { return "Resources over date"; } else if (object.equals(Type.DATES_OVER_RESOURCES)) { return "Date over resources"; } else { return "unknown view type: " + object.name(); } } return ""; } @Override public Type fromString(String string) { return null; } }); ChoiceBox layoutBox = new ChoiceBox<>(); layoutBox.getItems().setAll(Layout.values()); layoutBox.valueProperty().bindBidirectional(resourcesView.layoutProperty()); layoutBox.setConverter(new StringConverter<>() { @Override public String toString(Layout object) { if (object != null) { if (object.equals(Layout.SWIMLANE)) { return "Swim Lanes"; } else if (object.equals(Layout.STANDARD)) { return "Standard"; } else { return "unknown layout type: " + object.name(); } } return ""; } @Override public Layout fromString(String string) { return null; } }); ChoiceBox gridTypeBox = new ChoiceBox<>(); gridTypeBox.getItems().setAll(GridType.values()); gridTypeBox.valueProperty().bindBidirectional(resourcesView.gridTypeProperty()); CheckBox infiniteScrolling = new CheckBox("Infinite scrolling"); infiniteScrolling.selectedProperty().bindBidirectional(resourcesView.scrollingEnabledProperty()); infiniteScrolling.setDisable(true); CheckBox adjustBox = new CheckBox("Adjust first day of week"); adjustBox.selectedProperty().bindBidirectional(resourcesView.adjustToFirstDayOfWeekProperty()); CheckBox scrollbarBox = new CheckBox("Show scrollbar"); scrollbarBox.selectedProperty().bindBidirectional(resourcesView.showScrollBarProperty()); CheckBox timescaleBox = new CheckBox("Show timescale"); timescaleBox.selectedProperty().bindBidirectional(resourcesView.showTimeScaleViewProperty()); CheckBox allDayBox = new CheckBox("Show all day events"); allDayBox.selectedProperty().bindBidirectional(resourcesView.showAllDayViewProperty()); CheckBox detailsBox = new CheckBox("Show details upon creation"); detailsBox.selectedProperty().bindBidirectional(resourcesView.showDetailsUponEntryCreationProperty()); CheckBox flipBox = new CheckBox("Enable start / end flip over"); flipBox.selectedProperty().bindBidirectional(resourcesView.enableStartAndEndTimesFlipProperty()); Slider slider = new Slider(); slider.setMin(0); slider.setMax(1); slider.valueProperty().bindBidirectional(resourcesView.entryViewAvailabilityEditingOpacityProperty()); return new VBox(10, availabilityButton, new Label("View type"), typeBox, layoutBox, datePicker, infiniteScrolling, adjustBox, memoryTestButton, new Label("Number of resources"), numberOfResourcesBox, new Label("Number of days"), daysBox, new Label("Clicks to create"), clicksBox, new Label("Availability Behaviour"), behaviourBox, new Label("Availability Opacity"), slider, new Label("Grid Type"), gridTypeBox, scrollbarBox, timescaleBox, allDayBox, detailsBox, flipBox); } @Override protected DateControl createControl() { resourcesView = new ResourcesView(); resourcesView.setScrollingEnabled(false); resourcesView.setType(Type.DATES_OVER_RESOURCES); resourcesView.setNumberOfDays(5); resourcesView.setCreateEntryClickCount(1); resourcesView.setGridType(GridType.CUSTOM); resourcesView.setEarlyLateHoursStrategy(EarlyLateHoursStrategy.HIDE); resourcesView.getResources().setAll(createResources(3)); resourcesView.setShowDetailsUponEntryCreation(false); return resourcesView; } private List> createResources(int count) { ObservableList> result = FXCollections.observableArrayList(); switch (count) { case 1: result.addAll(create("Dirk", Style.STYLE1)); break; case 2: result.addAll(create("Dirk", Style.STYLE1), create("Katja", Style.STYLE2)); break; case 3: result.addAll(create("Dirk", Style.STYLE1), create("Katja", Style.STYLE2), create("Philip", Style.STYLE3)); break; case 4: result.addAll(create("Dirk", Style.STYLE1), create("Katja", Style.STYLE2), create("Philip", Style.STYLE3), create("Jule", Style.STYLE4)); break; case 5: result.addAll(create("Dirk", Style.STYLE1), create("Katja", Style.STYLE2), create("Philip", Style.STYLE3), create("Jule", Style.STYLE4), create("Armin", Style.STYLE5)); break; } return result; } private Resource create(String name, Style style) { Resource resource = new Resource(name); resource.getAvailabilityCalendar().setName("Availability of " + name); // resource.getCalendars().get(0).setStyle(style); // resource.getCalendars().get(0).setUserObject(resource); // resource.getCalendarSources().get(0).getCalendars().add(new Calendar("Second", resource)); fillAvailabilities(resource.getAvailabilityCalendar()); return resource; } private void fillAvailabilities(Calendar calendar) { LocalDate date = LocalDate.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); for (int i = 0; i < 14; i++) { // fourteen days is enough for this demo Entry morning = new Entry("Morning"); morning.setInterval(date, LocalTime.MIN, date, LocalTime.of(8, 0)); calendar.addEntry(morning); Entry noon = new Entry("Noon"); noon.setInterval(date, LocalTime.of(12, 0), date, LocalTime.of(13, 0)); calendar.addEntry(noon); Entry evening = new Entry("Evening"); evening.setInterval(date, LocalTime.of(18, 0), date, LocalTime.MAX); calendar.addEntry(evening); date = date.plusDays(1); } } class HelloDayViewCalendar extends Calendar { final Random dataRandom = new Random(); public HelloDayViewCalendar(long dataSeed) { dataRandom.setSeed(dataSeed); } public void generateBaseEntries() { createEntries(LocalDate.now(), Entry::new); createEntries(LocalDate.now().plusDays(1), Entry::new); createEntries(LocalDate.now().plusDays(2), Entry::new); createEntries(LocalDate.now().plusDays(3), Entry::new); createEntries(LocalDate.now().plusDays(4), Entry::new); } public void generateTopEntries() { createEntries(LocalDate.now(), Entry::new); } private > void createEntries(LocalDate startDate, Supplier entryProducer) { for (int j = 0; j < 5 + (int) (dataRandom.nextDouble() * 4); j++) { T entry = entryProducer.get(); entry.changeStartDate(startDate); entry.changeEndDate(startDate); String s = entry.getClass().getSimpleName(); entry.setTitle(s + (j + 1)); int hour = (int) (dataRandom.nextDouble() * 23); int durationInHours = Math.max(1, Math.min(24 - hour, (int) (dataRandom.nextDouble() * 4))); LocalTime startTime = LocalTime.of(hour, 0); LocalTime endTime = startTime.plusHours(durationInHours); entry.changeStartTime(startTime); entry.changeEndTime(endTime); entry.setCalendar(this); } } } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSampler/src/main/java/module-info.java ================================================ open module com.calendarfx.sampler { requires transitive org.controlsfx.fxsampler; requires transitive javafx.graphics; requires javafx.web; requires fr.brouillard.oss.cssfx; requires com.calendarfx.view; requires atlantafx.base; exports com.calendarfx.demo to org.controlsfx.fxsampler; exports com.calendarfx.demo.entries to org.controlsfx.fxsampler; exports com.calendarfx.demo.pages to org.controlsfx.fxsampler; exports com.calendarfx.demo.performance to org.controlsfx.fxsampler; exports com.calendarfx.demo.popover to org.controlsfx.fxsampler; exports com.calendarfx.demo.print to org.controlsfx.fxsampler; exports com.calendarfx.demo.views to org.controlsfx.fxsampler; provides fxsampler.FXSamplerProject with com.calendarfx.demo.CalendarFXSamplerProject; } ================================================ FILE: CalendarFXSampler/src/main/resources/META-INF/services/fxsampler.FXSamplerProject ================================================ com.calendarfx.demo.CalendarFXSamplerProject ================================================ FILE: CalendarFXSchedulerApp/.gitignore ================================================ /target *.iml ================================================ FILE: CalendarFXSchedulerApp/pom.xml ================================================ 4.0.0 scheduler CalendarFXScheduler com.calendarfx calendar 12.0.1 ../pom.xml true com.calendarfx view fr.brouillard.oss cssfx org.openjfx javafx-maven-plugin ${javafx.maven.plugin.version} com.calendarfx.scheduler.SchedulerApp ================================================ FILE: CalendarFXSchedulerApp/src/main/java/com/calendarfx/scheduler/SchedulerApp.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.scheduler; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.CalendarView; import com.calendarfx.view.CalendarView.Page; import com.calendarfx.view.DayView; import com.calendarfx.view.DayViewBase.EarlyLateHoursStrategy; import com.calendarfx.view.DetailedWeekView; import com.calendarfx.view.WeekView; import fr.brouillard.oss.cssfx.CSSFX; import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.control.ToggleButton; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import java.time.LocalDate; import java.time.LocalTime; public class SchedulerApp extends Application { @Override public void start(Stage primaryStage) { CalendarView calendarView = new CalendarView(Page.DAY, Page.WEEK); calendarView.showWeekPage(); calendarView.setEnableTimeZoneSupport(false); calendarView.setCreateEntryClickCount(1); DetailedWeekView detailedWeekView = calendarView.getWeekPage().getDetailedWeekView(); WeekView weekView = detailedWeekView.getWeekView(); DayView dayView = calendarView.getDayPage().getDetailedDayView().getDayView(); detailedWeekView.setShowToday(false); detailedWeekView.setEarlyLateHoursStrategy(EarlyLateHoursStrategy.HIDE); // extra button for week page ToggleButton editScheduleButton1 = new ToggleButton("Edit Schedule"); editScheduleButton1.selectedProperty().bindBidirectional(weekView.editAvailabilityProperty()); ((Pane) calendarView.getWeekPage().getToolBarControls()).getChildren().add(editScheduleButton1); // extra button for day page ToggleButton editScheduleButton2 = new ToggleButton("Edit Schedule"); editScheduleButton2.selectedProperty().bindBidirectional(dayView.editAvailabilityProperty()); ((Pane) calendarView.getDayPage().getToolBarControls()).getChildren().add(editScheduleButton2); Calendar katja = new Calendar("Katja"); Calendar dirk = new Calendar("Dirk"); Calendar philip = new Calendar("Philip"); Calendar jule = new Calendar("Jule"); Calendar armin = new Calendar("Armin"); katja.setShortName("K"); dirk.setShortName("D"); philip.setShortName("P"); jule.setShortName("J"); armin.setShortName("A"); katja.setStyle(Style.STYLE1); dirk.setStyle(Style.STYLE2); philip.setStyle(Style.STYLE3); jule.setStyle(Style.STYLE4); armin.setStyle(Style.STYLE5); CalendarSource familyCalendarSource = new CalendarSource("Family"); familyCalendarSource.getCalendars().addAll(katja, dirk, philip, jule, armin); calendarView.getCalendarSources().setAll(familyCalendarSource); calendarView.setRequestedTime(LocalTime.now()); StackPane stackPane = new StackPane(); stackPane.getChildren().addAll(calendarView); // introPane); Thread updateTimeThread = new Thread("Calendar: Update Time Thread") { @Override public void run() { while (true) { Platform.runLater(() -> { calendarView.setToday(LocalDate.now()); calendarView.setTime(LocalTime.now()); }); try { // update every 10 seconds sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; updateTimeThread.setPriority(Thread.MIN_PRIORITY); updateTimeThread.setDaemon(true); updateTimeThread.start(); Scene scene = new Scene(stackPane); CSSFX.start(scene); primaryStage.setTitle("Calendar"); primaryStage.setScene(scene); primaryStage.setWidth(1300); primaryStage.setHeight(1000); primaryStage.centerOnScreen(); primaryStage.show(); } public static void main(String[] args) { launch(args); } } ================================================ FILE: CalendarFXSchedulerApp/src/main/java/com/calendarfx/scheduler/SchedulerAppLauncher.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.scheduler; public class SchedulerAppLauncher { public static void main(String[] args) { System.setProperty("calendarfx.developer", "true"); SchedulerApp.main(args); } } ================================================ FILE: CalendarFXSchedulerApp/src/main/java/module-info.java ================================================ module com.calendarfx.scheduler { requires transitive javafx.graphics; requires fr.brouillard.oss.cssfx; requires javafx.controls; requires com.calendarfx.view; exports com.calendarfx.scheduler; } ================================================ FILE: CalendarFXView/.gitignore ================================================ /target/ /scenicView.properties *.iml ================================================ FILE: CalendarFXView/logging.properties ================================================ # To use this property file add the following command line argument: # -Djava.util.logging.config.file=${project_loc}/logging.properties # Specify the handlers to create in the root logger # (all loggers are children of the root logger) # The following creates two handlers # handlers = java.util.logging.ConsoleHandler, java.util.logging.FileHandler handlers = java.util.logging.ConsoleHandler # Set the default logging level for the root logger .level = OFF # Set the default logging level for new ConsoleHandler instances java.util.logging.ConsoleHandler.level = FINE # Set the default logging level for new FileHandler instances # java.util.logging.FileHandler.level = ALL # Set the default formatter for new ConsoleHandler instances java.util.logging.ConsoleHandler.formatter = com.calendarfx.util.LoggingFormatter # LOG DOMAINS com.calendarfx.config.level = OFF com.calendarfx.model.level = OFF com.calendarfx.events.level = OFF com.calendarfx.view.level = OFF com.calendarfx.search.level = OFF com.calendarfx.editing.level = OFF com.calendarfx.recurrence.level = OFF com.calendarfx.printing.level = OFF com.calendarfx.performance.level = OFF ================================================ FILE: CalendarFXView/pom.xml ================================================ 4.0.0 view CalendarFXView com.calendarfx calendar 12.0.1 ../pom.xml org.kordamp.ikonli ikonli-javafx org.kordamp.ikonli ikonli-fontawesome-pack org.mnode.ical4j ical4j org.controlsfx controlsfx org.openjfx javafx-controls com.dlsc.gemsfx gemsfx commons-validator commons-validator commons-logging commons-logging org.asciidoctor asciidoctor-maven-plugin 2.2.2 output-html install process-asciidoc html index.html org.apache.maven.plugins maven-source-plugin 3.2.0 attach-sources jar org.apache.maven.plugins maven-javadoc-plugin false true CalendarFX API false https://openjfx.io/javadoc/14/ -J-Djavafx.javadoc=true -html5 true defaultValue a Default Value: apiNote a API Note: implSpec a Implementation Requirements: implNote a Implementation Note: **\/\module-info.java org.ow2.asm asm 7.3.1 make-docs package aggregate attach-javadocs jar ================================================ FILE: CalendarFXView/src/main/asciidoc/manual.adoc ================================================ = CalendarFX Developer Manual Dirk Lemmermann :toc: left :source-highlighter: coderay :imagesdir: manual-images This is the _CalendarFX_ developer manual. It aims to contain all the information required to quickly get a calendar UI into your application. If you notice any mistakes or if you are missing vital information then please let us know. image::title.png[Calendar View,align="center"] == Quick Start The following section shows you how to quickly set up a JavaFX application that will show a complete calendar user interface. It includes a day view, a week view, a month view, a year view, an agenda view, a calendar selection view, and a search UI. [source,java,linenums] .CalendarApp.java ---- package com.calendarfx.app; import java.time.LocalDate; import java.time.LocalTime; import com.calendarfx.model.Calendar; import com.calendarfx.model.Calendar.Style; import com.calendarfx.model.CalendarSource; import com.calendarfx.view.CalendarView; import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.stage.Stage; public class CalendarApp extends Application { @Override public void start(Stage primaryStage) throws Exception { CalendarView calendarView = new CalendarView(); // <1> Calendar birthdays = new Calendar("Birthdays"); // <2> Calendar holidays = new Calendar("Holidays"); birthdays.setStyle(Style.STYLE1); // <3> holidays.setStyle(Style.STYLE2); CalendarSource myCalendarSource = new CalendarSource("My Calendars"); // <4> myCalendarSource.getCalendars().addAll(birthdays, holidays); calendarView.getCalendarSources().addAll(myCalendarSource); // <5> calendarView.setRequestedTime(LocalTime.now()); Thread updateTimeThread = new Thread("Calendar: Update Time Thread") { @Override public void run() { while (true) { Platform.runLater(() -> { calendarView.setToday(LocalDate.now()); calendarView.setTime(LocalTime.now()); }); try { // update every 10 seconds sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; updateTimeThread.setPriority(Thread.MIN_PRIORITY); updateTimeThread.setDaemon(true); updateTimeThread.start(); Scene scene = new Scene(calendarView); primaryStage.setTitle("Calendar"); primaryStage.setScene(scene); primaryStage.setWidth(1300); primaryStage.setHeight(1000); primaryStage.centerOnScreen(); primaryStage.show(); } public static void main(String[] args) { launch(args); } } ---- <1> Create the calendar view <2> Create one or more calendars <3> Set a style on each calendar (entries will use different colors) <4> Create a calendar source (e.g. "Google") and add calendars to it <5> Add calendars to the view == Model The primary model classes in _CalendarFX_ are `CalendarSource`, `Calendar` and `Entry`. A calendar source often represents a calendar account, for example an account with "Google Calendar" (http://calendar.google.com). A calendar source consists of a list of calendars and each calendar manages any number of entries. An entry represents an event with a start date / time, an end date / time, and a time zone. === Entry The `Entry` class encapsulates all information that is required to display an event or an appointment in any of the calendar views included in _CalendarFX_. [.thumb] image::entry.png[Calendar Entry,align="center"] The properties of an entry are: ID:: a unique identifier Title:: The title / name of the event or appointment (e.g. "Dentist Appointment") Calendar:: The calendar to which the entry belongs. Hidden:: A flag that can be used to explicitly / manually hide an entry. Interval:: A complex data type grouping together start date / time, end date / time, and a time zone. Location:: A free text description of a location, for example "Manhatten, New York". This information can be used by Geo services to return coordinates so that the UI can display a map if needed. Full Day:: A flag used to signal that the event is relevant for the entire day and that the start and end times are not relevant, for example a birthday or a holiday. Full day entries are displayed as shown below. [.thumb] image::all-day-view.png[All Day View] Minimum Duration:: Ensures that the user can not create entries with a duration of less than, for example, 15 minutes. User Object:: An arbitrary object which might be responsible for the creation of the entry in the first place. Recurrence Rule:: A text representation of a recurrence pattern according to RFC 2445 ("RRULE:FREQ=DAILY") [IMPORTANT] ==== This last property is very interesting. It allows the entry to express that it defines a recurrence. The entry can specify that it will be repeated over and over again following a given pattern. For example: "every Monday, Tuesday and Wednesday of every week until December 31st". If an entry is indeed a recurring entry then it produces one or more "recurrences". These recurrences are created by the framework by invoking the `Entry.createRecurrence()` method. The result of this method is another Entry that will be configured with the same values as the source entry. ==== Recurrence:: A flag that expresses whether the entry represents a recurrence or not. Recurrence Source:: A reference to the original source entry. Recurrence ID:: If an entry represents a recurrence of a source entry then this property will store an additional ID, normally the date where the recurrence occurs. In addition to these properties several read-only properties are available for convenience. Multi Day:: Needed y to easily determine if an entry spans multiple days. This information is constantly needed in various places of the framework for display / layout purposes. Start Date:: The date when the event begins (e.g. 5/12/2015). Start Time:: The time of day when the event begins (e.g. 2:15pm). End Date:: The date when the event ends (e.g. 8/12/2015). End Time:: The time of day when the event ends (e.g. 6:45pm). === Calendar The "Calendar" class is used to store entries in a binary interval tree. This data structure is not exposed to the outside. Instead methods exist on Calendar to add, remove, and find entries. The following is a description of the main properties of the Calendar class: Name:: The display name of the calendar, shown in several places within the UI. Short Name:: A short version of the calendar name. By default, it is set to be equal to the regular name, but if the application is using the swimlane layout then it might make sense to also define a short name due to limited space. Read-Only:: A flag for controlling whether entries can be added interactively in the UI or not. Setting this flag to false does not prevent the application itself to add entries. Style:: Basically a name prefix for looking up different styles from the CSS file (calendar.css): _"style1-"_, _"style2-"_. The `Calendar` class defines an enumerator called `Style` that can be used to easily set the value of this property with one of the predefined styles. Look Ahead / Back Duration:: Two properties of type `java.time.Duration` that are used in combination with the current system time in order to create a time interval. The calendar class uses this time interval inside its `findEntries(String searchTerm)` method. ==== Adding and Removing Entries To add an entry simply call the `addEntry()` method on calendar. Example: [source,java,linenums] ---- Calendar calendar = ... Entry dentistAppointment = new Entry<>("Dentist"); calendar.addEntry(dentistAppointment); ---- To remove an entry call the `removeEntry()` method on calendar. [source,java,linenums] ---- Calendar calendar = ... Entry dentistAppointment = ... calendar.removeEntry(dentistAppointment); ---- Alternatively you can simply set the calendar directly on the entry. [source,java,linenums] ---- Calendar calendar = ... Entry dentistAppointment = ... dentistAppointment.setCalendar(calendar); ---- To remove the entry from its calendar simply set the calendar to _null_. [source,java,linenums] ---- Entry dentistAppointment = ... dentistAppointment.setCalendar(null); ---- ==== Finding Entries for a Time Interval The calendar class provides a `findEntries()` method which receives a start date, an end date, and a time zone. The result of invoking this method is a map where the keys are the dates for which entries were found and the values are lists of entries on that day. [NOTE] ==== The result does not only contain entries that were previously added by calling the `addEntry()` method but also recurrence entries that were generated on-the-fly for those entries that define a recurrence rule. ==== [source,java,linenums] ---- Calendar calendar = ... Map>> result = calendar.findEntries(LocalDate startDate, LocalDate endDate, ZoneId zoneId) ---- ==== Finding Entries for a Search String The second `findEntries()` method accepts a search term as a parameter and is used to find entries that were previously added to the calendar and that match the term. [source,java,linenums] ---- Calendar calendar = ... List> result = calendar.findEntries(String searchTerm) ---- To find actual matches the method invokes the `Entry.matches(String)` method on all entries that are found within the time interval defined by the current date, the look back duration, and the look ahead duration. === Calendar Source A calendar source is used for creating a group of calendars. A very typical scenario would be that a calendar source represents an online calendar account (e.g. Google calendar). Calendars can be added to a source by simply calling `mySource.getCalendars().add(myCalendar)`. === Resource An instance of `Resource` represents a business object that can be scheduled. Hence, the resource model class contains a calendar for scheduled entries / events / allocations and also a calendar that represents the resource's availability. The availability can be edited interactively in the `DayView` by setting the `editAvailability` property to true. It is important to note that it is the application's responsibility to make sense out of the created entries inside the availability calendar. While editing is in progress each newly created time interval will be directly added to the availability calendar. ==== The `Resource` class is currently only used by the `ResourcesView`. ==== == Events _CalendarFX_ utilizes the JavaFX event model to inform the application about changes made in a calendar, about user interaction that might require loading of new data, and about user interaction that might require showing different views. === Calendar Events An event type that indicates that a change was made to the data is probably the most obvious type that anyone would expect from a UI framework. In _CalendarFX_ this event type is called `CalendarEvent`. .Calendar Event Type Hierarchy * `ANY` : the super event type ** `CALENDAR_CHANGED` : "something" inside the calendar changed, usually causing rebuild of views (example: calendar batch updates finished) ** `ENTRY_CHANGED` : the super type for changes made to an entry *** `ENTRY_CALENDAR_CHANGED` : the entry was assigned to a different calendar *** `ENTRY_FULL_DAY_CHANGED` : the full day flag was changed (from true to false or vice versa) *** `ENTRY_INTERVAL_CHANGED` : the time interval of the entry was changed (start date / time, end date / time) *** `ENTRY_LOCATION_CHANGED` : the location of the entry has changed *** `ENTRY_RECURRENCE_RULE_CHANGED` : the recurrence rule was modified *** `ENTRY_TITLE_CHANGED` : the entry title has changed *** `ENTRY_USER_OBJECT_CHANGED` : a new user object was set on the entry Listeners for this event type can be added to calendars by calling: [source,java,linenums] ---- Calendar calendar = new Calendar("Demo"); EventHandler handler = evt -> foo(evt); calendar.addEventHandler(handler); ---- === Load Events Load events are used by the framework to signal to the application that the UI requires data for a specific time interval. This can be very useful for implementing a lazy loading strategy. If the user switches from one month to another then an event of this type will be fired and the time bounds on this event will be the first and the last day of that month. The `LoadEvent` type only supports a single event type called `LOAD`. Listeners for this event type can be registered on any date control: [source,java,linenums] ---- DayView view = new DayView(); view.addEventHandler(LoadEvent.LOAD, evt -> foo(evt)); ---- === Request Events A unique event class is `RequestEvent`. It is used by the controls of the framework to signal to other framework controls that the user wants to "jump" to another view. For example: the user clicks on the date shown for a day in the `MonthView` then the month view will fire a request event that informs the framework that the user wants to switch to the `DayView` to see more detail for that day. == DateControl A calendar user interface hardly ever consists of just a single control. They are composed of several views, some showing a single day or a week or a month. In _CalendarFX_ the `CalendarView` control consists of dedicated "pages" for a day, a week, a month, or a full year. Each one of these pages consists of one or more subtypes of DateControl. The following image shows a simplified view of the scene graph / the containment hierarchy. [.thumb] image::hierarchy.png[Hierarchy View,align="center"] To make all of these controls work together in harmony it is important that they share many properties. This is accomplished by JavaFX property binding. The class `DateControl` features a method called "bind" that ensures the dates and times shown by the controls are synchronized. But also that many of the customization features (e.g. node factories) are shared. The following listing shows the implementation of the `DateControl.bind()` method to give you an idea how much is bound within _CalendarFX_. [source,java,linenums] ---- public final void bind(DateControl otherControl, boolean bindDate) { // bind lists Bindings.bindContentBidirectional(otherControl.getCalendarSources(), getCalendarSources()); Bindings.bindContentBidirectional(otherControl.getSelections(), getSelections()); Bindings.bindContentBidirectional(otherControl.getWeekendDays(), getWeekendDays()); // bind properties Bindings.bindBidirectional(otherControl.entryFactoryProperty(), entryFactoryProperty()); Bindings.bindBidirectional(otherControl.defaultCalendarProviderProperty(), defaultCalendarProviderProperty()); Bindings.bindBidirectional(otherControl.virtualGridProperty(), virtualGridProperty()); Bindings.bindBidirectional(otherControl.draggedEntryProperty(), draggedEntryProperty()); Bindings.bindBidirectional(otherControl.requestedTimeProperty(), requestedTimeProperty()); Bindings.bindBidirectional(otherControl.selectionModeProperty(), selectionModeProperty()); Bindings.bindBidirectional(otherControl.selectionModeProperty(), selectionModeProperty()); Bindings.bindBidirectional(otherControl.weekFieldsProperty(), weekFieldsProperty()); Bindings.bindBidirectional(otherControl.layoutProperty(), layoutProperty()); if (bindDate) { Bindings.bindBidirectional(otherControl.dateProperty(), dateProperty()); } Bindings.bindBidirectional(otherControl.todayProperty(), todayProperty()); Bindings.bindBidirectional(otherControl.zoneIdProperty(), zoneIdProperty()); // edit callbacks Bindings.bindBidirectional( otherControl.entryDetailsCallbackProperty(), entryDetailsCallbackProperty()); Bindings.bindBidirectional( otherControl.dateDetailsCallbackProperty(), dateDetailsCallbackProperty()); Bindings.bindBidirectional( otherControl.contextMenuCallbackProperty(), contextMenuCallbackProperty()); Bindings.bindBidirectional( otherControl.entryContextMenuCallbackProperty(), entryContextMenuCallbackProperty()); Bindings.bindBidirectional( otherControl.calendarSourceFactoryProperty(), calendarSourceFactoryProperty()); Bindings.bindBidirectional( otherControl.entryDetailsPopOverContentCallbackProperty(), entryDetailsPopOverContentCallbackProperty()); } ---- === Class Hierarchy _CalendarFX_ ships with many built-in views for displaying calendar information. All of these views inherit from `DateControl`. The class hierarchy can be seen in the following image: image::datecontrol.png[Class Hierarchy,align="center"] === Current Date, Time, and Today Each `DateControl` keeps track of the "current date" and "today". The current date is the date that the control is supposed to display to the user. "Today" is the date that the control assumes to be the actual date. "Today" defaults to the current system date (provided by the operating system), but it can be any date. [IMPORTANT] .Updating today and current time ==== The "today" and "time" properties do not get updated by themselves. See the daemon thread created in the listing shown in the "Quick Start" section. ==== `DateControl` defines utility methods that allow for easy modification of the "current" date. [source,java,linenums] ---- public void goToday(); public void goForward(); public void goBack(); ---- === Adding Calendars / Sources Even though the `DateControl` class provides a `getCalendars()` method this is not the place where calendars are being added. Instead, always create calendar sources, add calendars to them, and then add the sources to the control. The "calendars" list is a read-only flat list representation of all calendars in all calendar sources. The "calendars" list gets updated by the framework. [source,java,linenums] .Adding Calendars ---- Calendar katja = new Calendar("Katja"); Calendar dirk = new Calendar("Dirk"); CalendarSource familyCalendarSource = new CalendarSource("Family"); familyCalendarSource.getCalendars().addAll(katja, dirk); CalendarView calendarView = new CalendarView(); calendarView.getCalendarSources().setAll(familyCalendarSource); ---- === Customizing or Replacing the PopOver The `DateControl` class has built-in support for displaying a `PopOver` control when the user double-clicks on a calendar entry. The content node of this `PopOver` can be replaced. It is normally used to show some basic entry details (e.g. start / end date, title, event location) but applications might have defined specialized entries with custom properties that require additional UI elements. This can be accomplished by the help of the `PopOver` content node factory. [source,java,linenums] .PopOver Content Node Factory ---- CalendarView calendarView = new CalendarView(); calendarView.setEntryDetailsPopOverContentCallback(param -> new MyCustomPopOverContentNode()); ---- If an application does not want to use the `PopOver` at all but instead display a standard dialog then there is a way of doing that, too. Simply register an entry details callback. [source,java,linenums] .Entry Details Callback ---- CalendarView calendarView = new CalendarView(); calendarView.setEntryDetailsCallback(param -> new MyCustomEntryDialog()); ---- These two callbacks normally work hand in hand. The default implementation of the entry details callback is producing a `PopOver` and sets the content node on the PopOver via the help of the content node callback. === Context Menu Support A common place for customization are context menus. The `DateControl` class produces a context menu via specialized callbacks. One callback is used to produce a menu for a given calendar entry, the second callback is used when the user triggers the context menu by clicking in the background of a `DateControl`. [source,java,linenums] .PopOver Content Node Factory ---- CalendarView calendarView = new CalendarView(); calendarView.setEntryContextMenuCallback(param -> new MyEntryContextMenu()); calendarView.setContextMenuCallback(param -> new MyContextMenu()); ---- [IMPORTANT] .Context Menus ==== The context menu callbacks are automatically shared among all date controls that are bound to each other. The same context menu code will execute for different views, the `DayView`, the `MonthView`, and so on. This means that the code that builds the context menu will need to check the parameter object that was passed to the callback to configure itself appropriately. The same is true for basically all callbacks used by the DateControl. ==== === Creating Entries The user can create new entries by double-clicking anywhere inside a `DateControl`. The actual work of creating a new entry instance is then delegated to a specialized entry factory that can be set on `DateControl`. [source,java,linenums] .Entry Factory ---- CalendarView calendarView = new CalendarView(); calendarView.setEntryFactory(param -> new MyEntryFactory()); ---- Once the entry factory has returned the new entry it will be added to the calendar that is being returned by the "default calendar" provider. This provider is also customizable via a callback. [source,java,linenums] .Default Calendar Provider ---- CalendarView calendarView = new CalendarView(); calendarView.setDefaultCalendarProvider(param -> new MyDefaultCalendarProvider()); ---- Besides the double click creation the application can also programmatically request the DateControl to create a new entry at a given point in time. Two methods are available for this: createEntryAt(ZonedDateTime) and createEntryAt(ZonedDateTime, Calendar). The second method will ensure that the entry will be added to the given calendar while the first method will invoke the default calendar provider. === Creating Calendar Sources The user might also wish to add another calendar source to the application. In this case the DateControl will invoke the calendar source factory. The default implementation of this factory does nothing more than to create a new instance of the standard CalendarSource class. Applications are free to return a specialization of CalendarSource instead (e.g. GoogleCalendarAccount). A custom factory might even prompt the user first with a dialog, e.g. to request user credentials. [source,java,linenums] .Default Calendar Provider ---- CalendarView calendarView = new CalendarView(); calendarView.setCalendarSourceFactory(param -> new MyCalendarSource()); ---- The calendar source factory gets invoked when the method `DateControl.createCalendarSource()` gets invoked. The `CalendarView` class already provides a button in its toolbar that will call this method. == Entry Views Entry views are JavaFX nodes that are representing calendar entries. There are several types, all extending `EntryViewBase`: Day Entry View:: Shown inside a `DayView` or `WeekDayView` control. These views can be customized by subclassing `DayEntryViewSkin` and overriding the `createContent()` method. All Day Entry View:: Shown inside the `AllDayView` control. Month Entry View:: Shown inside the `MonthView` control. == Standard Calendar Views The most fundamental views inside _CalendarFX_ are of course the views used to display a day (24 hours), an entire week, a month, and a year. DayView:: Shows a 24-hour time period vertically. The control has several options that can be used to influence the layout of the hours. E.g.: it is possible to define hour ranges where the time will be compressed in order to save space on the screen (early and late hours are often not relevant). The view can also specify whether it wants to always show a fixed number of hours or a fixed height for each hour. [.thumb] image::day-view.png[Day View,align="center"] DetailedDayView:: wraps the `DayView` control with several additional controls: an `AllDayView`, a `TimeScaleView`, a `CalendarHeaderView`, a `ScrollBar` and an (optional) `AgendaView`. [.thumb] image::detailed-day-view-agenda.png[Detailed Day View,align="center"] WeekView:: The name of this control is somewhat misleading, because it can show any number of `WeekDayView` instances, not just 5 or 7 but also 14 (two weeks) or 21 (three weeks). In this view entries can be easily edited to span multiple days. [.thumb] image::week-view.png[Week View,align="center"] DetailedWeekView:: same concept as the `DetailedDayView`. This view wraps the `WeekView` and adds several other controls. [.thumb] image::detailed-week-view.png[Detailed Week View,align="center"] MonthView:: Shows up to 31 days for the current month plus some days of the previous and the next month. [.thumb] image::month-view.png[Month View,align="center"] MonthSheetView:: Shows several months in a column layout. Weekdays can be aligned so that the same weekdays are always next to each other. A customizable cell factory is used to create the date cells. Several default implementations are included in _CalendarFX_: simple date cell, usage date cell, badge date cell, detail date cell. [.thumb] image::month-sheet-view.png[Month Sheet View,align="center"] [.thumb] image::month-sheet-view-aligned.png[Month Sheet View Aligned,align="center"] YearView:: Shows twelve `YearMonthView` instances. [.thumb] image::year-view.png[Year View,align="center"] YearMonthView:: Sort of a date picker control. 12 instances of this control are used to build up the `YearPage` control. This control provides many properties for easy customization. The month label, the year label, and the arrow buttons can be hidden. A cell factory can be set to customize the appearance of each day, and so on. [.thumb] image::date-picker.png[Year Month View,align="center"] AllDayView:: Just like the `WeekView` this control can also span multiple days. It is being used as a header for the `DayView` inside the `DayPage` and also for the `WeekView` inside the `WeekPage`. The control displays calendar entries that have their "full day" property set to true. [.thumb] image::all-day-view.png[All Day View,align="center"] CalendarHeaderView:: Displays the names of all currently visible calendars, but only when the `DateControl` has its layout set to `SWIMLANE` and not to `STANDARD`. [.thumb] image::calendar-header-view.png[Calendar Header View,align="center"] == Calendar Pages Calendar pages are complex controls that are composed of several controls, many of them `DateControl` instances. All pages provide controls to navigate to different dates or to quickly jump to "Today". Each page also shows a title with the current date shown. The `CalendarView` class manages one instance of each page type to let the user switch from a day, to a week, to a month, to a year. DayPage:: Shows an `AgendaView`, a `DetailedDayView`, and a `YearMonthView`. This page is designed to give the user a quick overview of what is going on today and in the near future (agenda). [.thumb] image::day-page.png[Day Page,align="center",border="1"] WeekPage:: Composed of a `DetailedWeekView`. [.thumb] image::week-page.png[Week Page,align="center"] MonthPage:: Shows a single `MonthView` control. [.thumb] image::month-page.png[Month Page,align="center"] YearPage:: Shows a `YearView` with twelve `YearMonthView` sub-controls. Alternatively can switch to a `MonthSheetView`. [.thumb] image::year-page.png[Year Page using YearView,align="center"] [.thumb] image::year-page-2.png[Year Page using MonthSheetView,align="center"] == Resource Scheduling Views Another category of views is used for scheduling resource allocations. These are commonly used by scheduling software, e.g. a customer appointment application for a garage, a hairdresser, and so on. The === ResourcesView The class `ResourcesView` displays one or more days for one or more resources. The view can either display one or more resources for a given day (`ResourceView.Type.RESOURCES_OVER_DATES`) or one or more days for a given resource (`ResourceView.Type.DATES_OVER_RESOURCE`). Each one of these options can be configured to show one or more dates and one or more resources. This screenshot shows the resources view when the type of the view has been set to "resources over dates". [.thumb] image::resources-view-resources-over-dates.png[ResourcesView - Resources over Dates,align="center"] The next screenshot shows the resources view when the type of the view has been set to "dates over resources". [.thumb] image::resources-view-dates-over-resources.png[esourcesView - Dates over Resources,align="center"] By default, the calendar entries will become semi-transparent when the user switches to the "edit availability" mode. This behaviour can be configured so that either the entries stay completely visible or they are completely hidden. The following screenshot shows the situation where the user is editing the resources' availability and the already existing calendar entries become semi-transparent. [.thumb] image::resources-view-availability.png[esourcesView - Availability Editing,align="center"] == Developer Console _CalendarFX_ supports a special system property called `calendarfx.developer`. If this property is set to `true` then a developer console is being added to the skin of `CalendarView`. The console can be made visible by pressing `SHORTCUT-D`. The console is a standard _CalendarFX_ control and you can also add it directly to your application for development purposes. [.thumb] image::developer-console.png[Developer Console,align="center"] == Logging _CalendarFX_ uses the standard java logging api for its logging. The logging settings and the available loggers can be found inside the file `logging.properties`. _CalendarFX_ uses domains for logging and not packages or classes. Several domains are available: view, model, editing, recurrence, etc... == Internationalization (i18n) The default resource bundle of _CalendarFX_ is English. Additional bundles include German, Spanish, French, Italian, Portuguese (Brazil), and Czech. All can be found in the distribution (misc/messages.properties, misc/messages_de.properties, etc...). Please submit a pull request to add another language to _CalendarFX_. == Known Issues * There is currently no support for defining exceptions for recurrence rules. In most calendar applications, when the user edits a recurrent entry, the user will be asked whether he wants to change just this one recurrence or the whole series. This feature is currently not supported but will be in one of the next releases. * In `SwimLane` layout it would be nice if the user could drag an entry horizontally from one column / calendar to another. This is currently not supported. We will investigate if this can be added in one of the next releases. ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.model; import com.calendarfx.view.DateControl; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.Event; import javafx.event.EventDispatchChain; import javafx.event.EventHandler; import javafx.event.EventTarget; import net.fortuna.ical4j.model.Recur; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import static com.calendarfx.model.CalendarEvent.CALENDAR_CHANGED; import static com.calendarfx.model.CalendarEvent.ENTRY_CHANGED; import static com.calendarfx.util.LoggingDomain.MODEL; import static java.util.Objects.requireNonNull; import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINER; /** * A calendar is responsible for storing calendar entries. It provides methods * for adding, removing, and querying entries. A calendar also defines a visual * style / color theme that will be used throughout the UI controls. Calendars * fire events whenever entries are added or removed. Calendars are grouped together * inside a {@link CalendarSource}. These calendar sources are then added to * {@link DateControl#getCalendarSources()}. * *

Example

*
 *     {@code
 *     // create the calendar and listen to all changes
 *     Calendar calendar = new Calendar("Home");
 *     calendar.addEventHandler(CalendarEvent.ANY, evt -> handleEvent(evt));
 *
 *     // create the calendar source and attach the calendar
 *     CalendarSource source = new CalendarSource("Online Calendars");
 *     source.getCalendars().add(calendar);
 *
 *     // attach the source to the date control / calendar view.
 *     CalendarView view = new CalendarView();
 *     view.getCalendarSources().add(source);
 *     }
 * 
* * @param the type of the (optional) user object */ public class Calendar implements EventTarget { /** * Predefined visual styles for calendars. The actual CSS settings for these * styles can be found in the framework stylesheet, prefixed with "style1-", * "style2-", etc. The picture below shows the colors used for the various * styles. * * Styles * * @see Calendar#setStyle(Style) */ public enum Style { /** * Default style "1". */ STYLE1, /** * Default style "2". */ STYLE2, /** * Default style "3". */ STYLE3, /** * Default style "4". */ STYLE4, /** * Default style "5". */ STYLE5, /** * Default style "6". */ STYLE6, /** * Default style "7". */ STYLE7; /** * Returns a style for the given ordinal. This method is implemented * with a roll over strategy: the final ordinal value is the given * ordinal value modulo the number of elements in this enum. * * @param ordinal the ordinal value for which to return a style * @return a style, guaranteed to be non-null */ public static Style getStyle(int ordinal) { return Style.values()[ordinal % Style.values().length]; } } private final IntervalTree> intervalTree = new IntervalTree<>(); /** * Constructs a new calendar. */ public Calendar() { addEventHandler(evt -> { Entry entry = evt.getEntry(); if (evt.getEventType().getSuperType().equals(ENTRY_CHANGED) && entry.isRecurrence()) { updateRecurrenceSourceEntry(evt, entry.getRecurrenceSourceEntry()); } }); } /** * Constructs a new calendar with the given name. * * @param name the name of the calendar * @param userObject an optional user object */ public Calendar(String name, T userObject) { this(); setName(name); if (name != null) { setShortName(!name.isEmpty() ? name.substring(0, 1) : ""); } setUserObject(userObject); } /** * Constructs a new calendar with the given name. * * @param name the name of the calendar */ public Calendar(String name) { this(name, null); } @SuppressWarnings({"rawtypes", "unchecked"}) private void updateRecurrenceSourceEntry(CalendarEvent evt, Entry source) { Entry recurrence = evt.getEntry(); if (evt.getEventType().equals(CalendarEvent.ENTRY_INTERVAL_CHANGED)) { Interval oldInterval = evt.getOldInterval(); Interval newInterval = calculateSourceBoundsFromRecurrenceBounds(source, recurrence, oldInterval); source.setInterval(newInterval); } else if (evt.getEventType().equals(CalendarEvent.ENTRY_LOCATION_CHANGED)) { source.setLocation(recurrence.getLocation()); } else if (evt.getEventType().equals(CalendarEvent.ENTRY_RECURRENCE_RULE_CHANGED)) { source.setRecurrenceRule(recurrence.getRecurrenceRule()); } else if (evt.getEventType().equals(CalendarEvent.ENTRY_TITLE_CHANGED)) { source.setTitle(recurrence.getTitle()); } else if (evt.getEventType().equals(CalendarEvent.ENTRY_USER_OBJECT_CHANGED)) { source.setUserObject(recurrence.getUserObject()); } else if (evt.getEventType().equals(CalendarEvent.ENTRY_CALENDAR_CHANGED)) { source.setCalendar(recurrence.getCalendar()); } else if (evt.getEventType().equals(CalendarEvent.ENTRY_FULL_DAY_CHANGED)) { source.setFullDay(recurrence.isFullDay()); } } private Interval calculateSourceBoundsFromRecurrenceBounds(Entry source, Entry recurrence, Interval oldInterval) { ZonedDateTime recurrenceStart = recurrence.getStartAsZonedDateTime(); ZonedDateTime recurrenceEnd = recurrence.getEndAsZonedDateTime(); Duration startDelta = Duration.between(oldInterval.getStartZonedDateTime(), recurrenceStart); Duration endDelta = Duration.between(oldInterval.getEndZonedDateTime(), recurrenceEnd); ZonedDateTime sourceStart = source.getStartAsZonedDateTime(); ZonedDateTime sourceEnd = source.getEndAsZonedDateTime(); sourceStart = sourceStart.plus(startDelta); sourceEnd = sourceEnd.plus(endDelta); return new Interval(sourceStart.toLocalDate(), sourceStart.toLocalTime(), sourceEnd.toLocalDate(), sourceEnd.toLocalTime(), recurrence.getZoneId()); } /** * Gets the earliest time used by this calendar, that means the start of the * first entry stored. * * @return An instant representing the earliest time, can be null if no * entries are contained. */ public final Instant getEarliestTimeUsed() { return intervalTree.getEarliestTimeUsed(); } /** * Gets the latest time used by this calendar, that means the end of the * last entry stored. * * @return An instant representing the latest time, can be null if no * entries are contained. */ public final Instant getLatestTimeUsed() { return intervalTree.getLatestTimeUsed(); } private boolean batchUpdates; private boolean dirty; /** * Tells the calendar that the application will perform a large number of changes. * While batch updates in progress the calendar will stop to fire events. To finish * this mode the application has to call {@link #stopBatchUpdates()}. */ public final void startBatchUpdates() { batchUpdates = true; dirty = false; } /** * Tells the calendar that the application is done making big changes. Invoking * this method will trigger a calendar event of type {@link CalendarEvent#CALENDAR_CHANGED} which * will then force an update of the views. */ public final void stopBatchUpdates() { batchUpdates = false; if (dirty) { dirty = false; fireEvent(new CalendarEvent(CalendarEvent.CALENDAR_CHANGED, this)); } } /** * Queries the calendar for all entries within the time interval defined by * the start date and end date. * * @param startDate the start of the time interval * @param endDate the end of the time interval * @param zoneId the time zone for which to find entries * @return a map filled with list of entries for given days */ public final Map>> findEntries(LocalDate startDate, LocalDate endDate, ZoneId zoneId) { fireEvents = false; Map>> result; try { result = doGetEntries(startDate, endDate, zoneId); } finally { fireEvents = true; } return result; } @SuppressWarnings({"rawtypes", "unchecked"}) private Map>> doGetEntries(LocalDate startDate, LocalDate endDate, ZoneId zoneId) { if (MODEL.isLoggable(FINE)) { MODEL.fine(getName() + ": getting entries from " + startDate + " until " + endDate + ", zone = " + zoneId); } ZonedDateTime st = ZonedDateTime.of(startDate, LocalTime.MIN, zoneId); ZonedDateTime et = ZonedDateTime.of(endDate, LocalTime.MAX, zoneId); Collection> intersectingEntries = intervalTree.getIntersectingObjects(st.toInstant(), et.toInstant()); if (intersectingEntries.isEmpty()) { if (MODEL.isLoggable(FINE)) { MODEL.fine(getName() + ": found no entries"); } return Collections.emptyMap(); } if (MODEL.isLoggable(FINE)) { MODEL.fine(getName() + ": found " + intersectingEntries.size() + " entries"); } Map>> result = new HashMap<>(); for (Entry entry : intersectingEntries) { if (entry.isRecurring()) { String recurrenceRule = entry.getRecurrenceRule().replaceFirst("^RRULE:", ""); LocalDate utilStartDate = entry.getStartDate(); try { LocalDate utilEndDate = et.toLocalDate(); List dateList = new Recur(recurrenceRule).getDates(utilStartDate, utilEndDate); for (LocalDate repeatingDate : dateList) { ZonedDateTime zonedDateTime = ZonedDateTime.of(repeatingDate, LocalTime.MIN, zoneId); Entry recurrence = entry.createRecurrence(); recurrence.setId(entry.getId()); recurrence.getProperties().put("com.calendarfx.recurrence.source", entry); recurrence.getProperties().put("com.calendarfx.recurrence.id", zonedDateTime.toString()); recurrence.setRecurrenceRule(entry.getRecurrenceRule()); // update the recurrence interval LocalDate recurrenceStartDate = zonedDateTime.toLocalDate(); LocalDate recurrenceEndDate = recurrenceStartDate.plus(entry.getStartDate().until(entry.getEndDate())); recurrence.setInterval(entry.getInterval().withDates(recurrenceStartDate, recurrenceEndDate)); recurrence.setUserObject(entry.getUserObject()); recurrence.setTitle(entry.getTitle()); recurrence.setMinimumDuration(entry.getMinimumDuration()); recurrence.setFullDay(entry.isFullDay()); recurrence.setLocation(entry.getLocation()); recurrence.setCalendar(this); addEntryToResult(result, recurrence, startDate, endDate); } } catch (IllegalArgumentException | DateTimeParseException e) { e.printStackTrace(); } } else { addEntryToResult(result, entry, startDate, endDate); } } if (MODEL.isLoggable(FINE)) { MODEL.fine(getName() + ": found entries for " + result.size() + " different days"); } result.values().forEach(Collections::sort); return result; } /* * Assign the given entry to each date that it intersects with in the given search interval. */ private void addEntryToResult(Map>> result, Entry entry, LocalDate startDate, LocalDate endDate) { LocalDate entryStartDate = entry.getStartDate(); LocalDate entryEndDate = entry.getEndDate(); // entry does not intersect with time interval if (entryEndDate.isBefore(startDate) || entryStartDate.isAfter(endDate)) { return; } if (entryStartDate.isAfter(startDate)) { startDate = entryStartDate; } if (entryEndDate.isBefore(endDate)) { endDate = entryEndDate; } LocalDate date = startDate; do { result.computeIfAbsent(date, it -> new ArrayList<>()).add(entry); date = date.plusDays(1); } while (!date.isAfter(endDate)); } private final ObjectProperty lookAheadDuration = new SimpleObjectProperty<>(this, "lookAheadDuration", Duration.ofDays(730)); /** * Stores a time duration used for the entry search functionality of this * calendar. The look ahead and the look back durations limit the search to * the time interval [now - lookBackDuration, now + lookAheadDuration]. The * default value of this property is 730 days (2 years). * * @return the look ahead duration * @see #findEntries(String) */ public final ObjectProperty lookAheadDurationProperty() { return lookAheadDuration; } /** * Sets the value of {@link #lookAheadDurationProperty()}. * * @param duration the look ahead duration */ public final void setLookAheadDuration(Duration duration) { requireNonNull(duration); lookAheadDurationProperty().set(duration); } /** * Returns the value of {@link #lookAheadDurationProperty()}. * * @return the look ahead duration */ public final Duration getLookAheadDuration() { return lookAheadDurationProperty().get(); } private final ObjectProperty lookBackDuration = new SimpleObjectProperty<>(this, "lookBackDuration", Duration.ofDays(730)); /** * Stores a time duration used for the entry search functionality of this * calendar. The look ahead and the look back durations limit the search to * the time interval [now - lookBackDuration, now + lookAheadDuration]. The * default value of this property is 730 days (2 years). * * @return the look back duration * @see #findEntries(String) */ public final ObjectProperty lookBackDurationProperty() { return lookBackDuration; } /** * Sets the value of {@link #lookBackDurationProperty()}. * * @param duration the look back duration */ public final void setLookBackDuration(Duration duration) { requireNonNull(duration); lookBackDurationProperty().set(duration); } /** * Returns the value of {@link #lookBackDurationProperty()}. * * @return the look back duration */ public final Duration getLookBackDuration() { return lookBackDurationProperty().get(); } /** * Queries the calendar for entries that match the given search text. The method * can be overridden to implement custom find / search strategies. * * @param searchText the search text * @return a list of entries that match the search * @see Entry#matches(String) */ public List> findEntries(String searchText) { if (MODEL.isLoggable(FINE)) { MODEL.fine(getName() + ": getting entries for search term: " + searchText); } Instant horizonStart = Instant.now().minus(getLookBackDuration()); Instant horizonEnd = Instant.now().plus(getLookAheadDuration()); ZoneId zoneId = ZoneId.systemDefault(); ZonedDateTime st = ZonedDateTime.ofInstant(horizonStart, zoneId); ZonedDateTime et = ZonedDateTime.ofInstant(horizonEnd, zoneId); List> result = new ArrayList<>(); Map>> map = findEntries(st.toLocalDate(), et.toLocalDate(), zoneId); for (List> list : map.values()) { for (Entry entry : list) { if (entry.matches(searchText)) { result.add(entry); } } } if (MODEL.isLoggable(FINE)) { MODEL.fine(getName() + ": found " + result.size() + " entries"); } return result; } /** * Removes all entries from the calendar. Fires an * {@link CalendarEvent#CALENDAR_CHANGED} event. */ public final void clear() { intervalTree.clear(); fireEvent(new CalendarEvent(CALENDAR_CHANGED, this)); } // support for adding entries /** * Adds the given entry to the calendar. This is basically just a convenience * method as the actual work of adding an entry to a calendar is done inside * {@link Entry#setCalendar(Calendar)}. * * @param entry the entry to add */ public final void addEntry(Entry entry) { addEntries(entry); } /** * Adds the given entries to the calendar. This is basically just a convenience * method as the actual work of adding an entry to a calendar is done inside * {@link Entry#setCalendar(Calendar)}. * * @param entries the entries to add */ public final void addEntries(Entry... entries) { if (entries != null) { for (Entry entry : entries) { entry.setCalendar(this); } } } /** * Adds the given entries to the calendar. This is basically just a convenience * method as the actual work of adding an entry to a calendar is done inside * {@link Entry#setCalendar(Calendar)}. * * @param entries the collection of entries to add */ public final void addEntries(Collection> entries) { if (entries != null) { entries.forEach(this::addEntry); } } /** * Adds the entries returned by the iterator to the calendar. This is basically just a convenience * method as the actual work of adding an entry to a calendar is done inside {@link Entry#setCalendar(Calendar)}. * * @param entries the entries to add */ public final void addEntries(Iterator> entries) { if (entries != null) { while (entries.hasNext()) { addEntry(entries.next()); } } } /** * Adds the entries returned by the iterable to the calendar. This is basically just a convenience * method as the actual work of adding an entry to a calendar is done inside {@link Entry#setCalendar(Calendar)}. * * @param entries the entries to add */ public final void addEntries(Iterable> entries) { if (entries != null) { addEntries(entries.iterator()); } } // support for removing entries /** * Removes the given entry from the calendar. This is basically just a convenience * method as the actual work of removing an entry from a calendar is done inside * {@link Entry#setCalendar(Calendar)}. * * @param entry the entry to remove */ public final void removeEntry(Entry entry) { removeEntries(entry); } /** * Removes the given entries from the calendar. This is basically just a convenience * method as the actual work of removing an entry from a calendar is done inside * {@link Entry#setCalendar(Calendar)}. * * @param entries the entries to remove */ public final void removeEntries(Entry... entries) { if (entries != null) { for (Entry entry : entries) { entry.setCalendar(null); } } } /** * Removes the given entries from the calendar. This is basically just a convenience * method as the actual work of removing an entry from a calendar is done inside * {@link Entry#setCalendar(Calendar)}. * * @param entries the collection of entries to remove */ public final void removeEntries(Collection> entries) { if (entries != null) { entries.forEach(this::removeEntry); } } /** * Removes the entries returned by the iterator from the calendar. This is basically just a convenience * method as the actual work of removing an entry from a calendar is done inside {@link Entry#setCalendar(Calendar)}. * * @param entries the entries to remove */ public final void removeEntries(Iterator> entries) { if (entries != null) { while (entries.hasNext()) { removeEntry(entries.next()); } } } /** * Adds the entries returned by the iterable to the calendar. This is basically just a convenience * method as the actual work of adding an entry to a calendar is done inside {@link Entry#setCalendar(Calendar)}. * * @param entries the entries to add */ public final void removeEntries(Iterable> entries) { if (entries != null) { removeEntries(entries.iterator()); } } final void impl_addEntry(Entry entry) { if (entry.isRecurrence()) { throw new IllegalArgumentException("a recurrence entry can not be added to a calendar"); } dirty = true; intervalTree.add(entry); } final void impl_removeEntry(Entry entry) { if (entry.isRecurrence()) { throw new IllegalArgumentException("a recurrence entry can not be added to a calendar"); } dirty = true; intervalTree.remove(entry); } // Name support. private final StringProperty name = new SimpleStringProperty(this, "name", "Untitled"); /** * A property used to store the name of the calendar. * * @return the property used for storing the calendar name */ public final StringProperty nameProperty() { return name; } /** * Sets the value of {@link #nameProperty()}. * * @param name the new name for the calendar */ public final void setName(String name) { nameProperty().set(name); } /** * Returns the value of {@link #nameProperty()}. * * @return the name of the calendar */ public final String getName() { return nameProperty().get(); } // Short name support. private final StringProperty shortName = new SimpleStringProperty(this, "shortName", "Unt."); /** * A property used to store the short name of the calendar. * * @return the property used for storing the calendar short name */ public final StringProperty shortNameProperty() { return shortName; } /** * Sets the value of {@link #shortNameProperty()}. * * @param name the new short name for the calendar */ public final void setShortName(String name) { shortNameProperty().set(name); } /** * Returns the value of {@link #shortNameProperty()}. * * @return the short name of the calendar */ public final String getShortName() { return shortNameProperty().get(); } // Style prefix support. private final StringProperty style = new SimpleStringProperty(this, "style", Style.STYLE1.name().toLowerCase()); /** * A property used to store the visual style that will be used for the * calendar in the UI. A style can be any arbitrary name. The style will be * used as a prefix to find the styles in the stylesheet. For examples * please search the standard framework stylesheet for the predefined styles * "style1-", "style2-", etc. * * @return the visual calendar style */ public final StringProperty styleProperty() { return style; } /** * Sets the value of {@link #styleProperty()} based on one of the predefined * styles (see also the enum {@link Style}). The image below shows how the * styles appear in the UI. * * Styles * * @param style the calendar style */ public final void setStyle(Style style) { MODEL.finer(getName() + ": setting style to: " + style); setStyle(style.name().toLowerCase()); } /** * Sets the value of {@link #styleProperty()}. * * @param stylePrefix the calendar style */ public final void setStyle(String stylePrefix) { requireNonNull(stylePrefix); MODEL.finer(getName() + ": setting style to: " + style); styleProperty().set(stylePrefix); } /** * Returns the value of {@link #styleProperty()}. * * @return the current calendar style */ public final String getStyle() { return styleProperty().get(); } // Read only support. private final BooleanProperty readOnly = new SimpleBooleanProperty(this, "readOnly", false); /** * A property used to control if the calendar is read-only or not. * * @return true if the calendar is read-only (default is false) */ public final BooleanProperty readOnlyProperty() { return readOnly; } /** * Returns the value of {@link #readOnlyProperty()}. * * @return true if the calendar can not be edited by the user */ public final boolean isReadOnly() { return readOnlyProperty().get(); } /** * Sets the value of {@link #readOnlyProperty()}. * * @param readOnly the calendar can not be edited by the user if true */ public final void setReadOnly(boolean readOnly) { MODEL.finer(getName() + ": setting read only to: " + readOnly); readOnlyProperty().set(readOnly); } private final ObservableList> eventHandlers = FXCollections.observableArrayList(); /** * Adds an event handler for calendar events. Handlers will be called when * an entry gets added, removed, changes, etc. * * @param l the event handler to add */ public final void addEventHandler(EventHandler l) { if (l != null) { if (MODEL.isLoggable(FINER)) { MODEL.finer(getName() + ": adding event handler: " + l); } eventHandlers.add(l); } } /** * Removes an event handler from the calendar. * * @param l the event handler to remove */ public final void removeEventHandler(EventHandler l) { if (l != null) { if (MODEL.isLoggable(FINER)) { MODEL.finer(getName() + ": removing event handler: " + l); } eventHandlers.remove(l); } } private boolean fireEvents = true; /** * Fires the given calendar event to all event handlers currently registered * with this calendar. * * @param evt the event to fire */ public final void fireEvent(CalendarEvent evt) { if (fireEvents && !batchUpdates) { if (MODEL.isLoggable(FINER)) { MODEL.finer(getName() + ": firing event: " + evt); } requireNonNull(evt); Event.fireEvent(this, evt); } } @Override public final EventDispatchChain buildEventDispatchChain(EventDispatchChain givenTail) { return givenTail.append((event, tail) -> { if (event instanceof CalendarEvent) { for (EventHandler handler : eventHandlers) { handler.handle((CalendarEvent) event); } } return event; }); } private final ObjectProperty userObject = new SimpleObjectProperty<>(this, "userObject"); public final T getUserObject() { return userObject.get(); } /** * An (optional) user object that can be used to link this calendar to the source * of its data or the business object that it represents. * * @return a user object */ public final ObjectProperty userObjectProperty() { return userObject; } public final void setUserObject(T userObject) { this.userObject.set(userObject); } @Override public String toString() { return "Calendar [name=" + getName() + ", style=" + getStyle() + ", readOnly=" + isReadOnly() + ", " + (getUserObject() != null ? getUserObject().toString() : "null") + "]"; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/CalendarEvent.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.model; import javafx.event.Event; import javafx.event.EventType; import java.time.ZonedDateTime; import static java.util.Objects.requireNonNull; /** * An event class used to signal changes done within a calendar or changes done * to a calendar entry. Events of this type can be received by adding an event * handler to a calendar. * * *

Example

* *
 * {@code
 * Calendar calendar = new Calendar("Home");
 * calendar.addEventHandler(CalendarEvent.ENTRY_ADDED, evt -> {...});
 * }
 * 
* * @see Calendar#addEventHandler(javafx.event.EventHandler) */ public class CalendarEvent extends Event { private static final long serialVersionUID = 4279597664476680474L; /** * The supertype of all event types in this event class. */ public static final EventType ANY = new EventType<>(Event.ANY, "CALENDAR"); /** * An event type used to inform the application that "something" inside the * calendar has changed and that the views need to update their visuals * accordingly (brute force update). */ public static final EventType CALENDAR_CHANGED = new EventType<>(CalendarEvent.ANY, "CALENDAR_CHANGED"); /** * The supertype of all events that a related to an entry itself and not the * calendar. */ public static final EventType ENTRY_CHANGED = new EventType<>(CalendarEvent.ANY, "ENTRY_CHANGED"); /** * An event type used to inform the application that an entry has been moved * from one calendar to another. */ public static final EventType ENTRY_CALENDAR_CHANGED = new EventType<>(CalendarEvent.ENTRY_CHANGED, "ENTRY_CALENDAR_CHANGED"); /** * An event type used to inform the application that an entry has become a * "full day" entry, meaning its start and end time are no longer relevant. * The entry should be visualized in a way that signals that the entry will * take all day (e.g. a birthday). */ public static final EventType ENTRY_FULL_DAY_CHANGED = new EventType<>(CalendarEvent.ENTRY_CHANGED, "ENTRY_FULL_DAY_CHANGED"); /** * An event type used to inform the application that an entry has been * assigned a new user object. */ public static final EventType ENTRY_RECURRENCE_RULE_CHANGED = new EventType<>(CalendarEvent.ENTRY_CHANGED, "ENTRY_RECURRENCE_RULE_CHANGED"); /** * An event type used to inform the application that an entry has been * assigned a new title. */ public static final EventType ENTRY_TITLE_CHANGED = new EventType<>(CalendarEvent.ENTRY_CHANGED, "ENTRY_TITLE_CHANGED"); /** * An event type used to inform the application that an entry has been * assigned a new user object. */ public static final EventType ENTRY_USER_OBJECT_CHANGED = new EventType<>(CalendarEvent.ENTRY_CHANGED, "ENTRY_USER_OBJECT_CHANGED"); /** * An event type used to inform the application that an entry has been * assigned a new user object. */ public static final EventType ENTRY_LOCATION_CHANGED = new EventType<>(CalendarEvent.ENTRY_CHANGED, "ENTRY_LOCATION_CHANGED"); /** * An event type used to inform the application that the time bounds of an * entry have been changed. One or several of start / end date, start / end * time. */ public static final EventType ENTRY_INTERVAL_CHANGED = new EventType<>(CalendarEvent.ENTRY_CHANGED, "ENTRY_INTERVAL_CHANGED"); private Entry entry; private final Calendar calendar; private boolean oldFullDay; private String oldText; private Calendar oldCalendar; private Interval oldInterval; private Object oldUserObject; /** * Constructs a new event for subclass. * * @param eventType the event type * @param calendar the calendar where the event occurred. */ protected CalendarEvent(EventType eventType, Calendar calendar) { super(calendar, calendar, eventType); this.calendar = requireNonNull(calendar); } /** * Constructs a new event. * * @param eventType the event type * @param calendar the calendar where the event occured * @param entry the affected entry */ public CalendarEvent(EventType eventType, Calendar calendar, Entry entry) { super(calendar, calendar, eventType); this.calendar = calendar; this.entry = requireNonNull(entry); } /** * Constructs a new event used for signalling that an entry was assigned to * a new calendar. The entry already carries a reference to new calendar and * the event object will know the old calendar. * * @param eventType the event type * @param calendar the calendar where the event occured * @param entry the affected entry * @param oldCalendar the calendar to which the event belonged before */ public CalendarEvent(EventType eventType, Calendar calendar, Entry entry, Calendar oldCalendar) { this(eventType, calendar, entry); this.oldCalendar = oldCalendar; } /** * Constructs a new event used for signalling that an entry has been * assigned a new user object. The entry already carries a reference to the * new user object and the event object will know the old user object. * * @param eventType the event type * @param calendar the calendar where the event occured * @param entry the affected entry * @param oldUserObject the calendar to which the event belonged before */ public CalendarEvent(EventType eventType, Calendar calendar, Entry entry, Object oldUserObject) { this(eventType, calendar, entry); this.oldUserObject = oldUserObject; } /** * Constructs a new event used for signalling that an entry was assigned a * new start end date / time. The entry already carries the new values, * while the old values can be retrieved from the event object. * * @param eventType the event type * @param calendar the calendar where the event occured * @param entry the affected entry * @param oldInterval the previous time interval */ public CalendarEvent(EventType eventType, Calendar calendar, Entry entry, Interval oldInterval) { this(eventType, calendar, entry); this.oldInterval = requireNonNull(oldInterval); } /** * Constructs a new event used for signalling that an entry was assigned a * new text (normally the title). The entry already carries a reference to * new text and the event object will know the old one. * * @param eventType the event type * @param calendar the calendar where the event occured * @param entry the affected entry * @param oldText the previous value of the text */ public CalendarEvent(EventType eventType, Calendar calendar, Entry entry, String oldText) { this(eventType, calendar, entry); this.oldText = oldText; } /** * Constructs a new event used for signaling that an entry was set to full * day. The entry already carries a reference to new full day value and the * event object will know the old one. * * @param eventType the event type * @param calendar the calendar where the event occured * @param entry the affected entry * @param oldFullDay the previous value of the full day */ public CalendarEvent(EventType eventType, Calendar calendar, Entry entry, boolean oldFullDay) { this(eventType, calendar, entry); this.oldFullDay = oldFullDay; } /** * Returns the entry for which the event was fired. * * @return the affected entry */ public Entry getEntry() { return entry; } /** * Returns the calendar for which the event was fired. * * @return the affected calendar */ public final Calendar getCalendar() { return calendar; } /** * Returns the old user object of the modified entry. * * @return the old user object */ public Object getOldUserObject() { return oldUserObject; } /** * Returns the old time interval of the modified entry. * * @return the old time interval */ public Interval getOldInterval() { return oldInterval; } /** * Returns the old text. * * @return the old text */ public final String getOldText() { return oldText; } /** * Returns the old value of the "full day" flag. * * @return the old value of "full day" */ public final boolean getOldFullDay() { return oldFullDay; } /** * Returns the old calendar. * * @return the old calendar */ public final Calendar getOldCalendar() { return oldCalendar; } /** * A utility method to determine if the event describes the creation of a new * entry. This is the case when the event type is {@link #ENTRY_CALENDAR_CHANGED} and * the old calendar was null. * * @return true if the event describes the creation of a new entry */ public final boolean isEntryAdded() { if (eventType == ENTRY_CALENDAR_CHANGED) { return (getOldCalendar() == null && entry.getCalendar() != null); } return false; } /** * A utility method to determine if the event describes the removal of an * entry. This is the case when the event type is {@link #ENTRY_CALENDAR_CHANGED} and * the old calendar was not null but the new calendar is null. * * @return true if the event describes the removal of a new entry */ public final boolean isEntryRemoved() { if (eventType == ENTRY_CALENDAR_CHANGED) { return (getOldCalendar() != null && entry.getCalendar() == null); } return false; } /** * Determines whether the event will have an impact on different days. * The method will return false if the user simply changed the start and / or * end time of an entry within the current day. However, when the user drags * the entry from one day to another then this method will return true. * * @return true if the new time interval of the entry touches other days than the old time interval of the entry */ public final boolean isDayChange() { if (!getEventType().equals(ENTRY_INTERVAL_CHANGED)) { return false; } Interval newInterval = entry.getInterval(); Interval oldInterval = getOldInterval(); ZonedDateTime newStart = newInterval.getStartZonedDateTime(); ZonedDateTime oldStart = oldInterval.getStartZonedDateTime(); if (!newStart.toLocalDate().equals(oldStart.toLocalDate())) { return true; } ZonedDateTime newEnd = newInterval.getEndZonedDateTime(); ZonedDateTime oldEnd = oldInterval.getEndZonedDateTime(); return !newEnd.toLocalDate().equals(oldEnd.toLocalDate()); } @Override public String toString() { return "CalendarEvent [" + (entry == null ? "" : ("entry=" + entry + ", ")) + "calendar=" + calendar + ", oldInterval=" + oldInterval + ", oldFullDay=" + oldFullDay + ", oldText=" + oldText + ", oldCalendar=" + oldCalendar + ", eventType=" + eventType + ", target=" + target + ", consumed=" + consumed + ", source=" + source + "]"; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/CalendarSource.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.model; import com.calendarfx.util.LoggingDomain; import com.calendarfx.view.SourceView; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; import static com.calendarfx.util.LoggingDomain.MODEL; import static java.util.logging.Level.FINE; /** * A calendar source is a collection of calendars. It often represents a user * account with some calendar service, e.g. Google Calendar or Apple me.com. The * image below shows an example: a calendar source called "Work" with calendars * "Meetings, Training, Customers, Holidays". * * * Calendar Source View *

*

* Calendar sources can be shown to the user via the {@link SourceView} control. */ public class CalendarSource { /** * Constructs a new untitled calendar source. */ public CalendarSource() { if (MODEL.isLoggable(FINE)) { getCalendars().addListener((Change change) -> { while (change.next()) { if (change.wasAdded()) { for (Calendar calendar : change.getAddedSubList()) { LoggingDomain.MODEL.fine("added calendar " + calendar.getName() + " to source " + getName()); } } else if (change.wasRemoved()) { for (Calendar calendar : change.getRemoved()) { MODEL.fine("removed calendar " + calendar.getName() + " from source " + getName()); } } } }); } } /** * Constructs a new calendar source with the given name. * * @param name the name of the calendar source, e.g. "Google", "Apple" */ public CalendarSource(String name) { this(); setName(name); } private final StringProperty name = new SimpleStringProperty(this, "name", "Untitled"); /** * The property used to store the name of the calendar source. * * @return the name property */ public final StringProperty nameProperty() { return name; } /** * Sets the value of {@link #nameProperty()}. * * @param name the new name for the calendar source */ public final void setName(String name) { MODEL.fine("changing name to " + name); nameProperty().set(name); } /** * Returns the value fo {@link #nameProperty()}. * * @return the calendar source name */ public final String getName() { return nameProperty().get(); } private final ObservableList calendars = FXCollections.observableArrayList(); /** * Returns the list of calendars that belong to this calendar source. * Example: the calendar source is "Google" and calendars might be "Work", * "Home", "Sport", "Children". * * @return the calendars owned by this calendar source */ public final ObservableList getCalendars() { return calendars; } @Override public String toString() { return "CalendarSource [name=" + getName() + ", calendars=" + calendars + "]"; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/Entry.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.model; import impl.com.calendarfx.view.util.Util; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import net.fortuna.ical4j.model.Recur; import org.controlsfx.control.PropertySheet.Item; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; import static com.calendarfx.util.LoggingDomain.MODEL; import static java.util.Objects.requireNonNull; import static java.util.logging.Level.FINE; /** * An entry inside a calendar, for example "Dentist Appointment, Feb 2nd, 9am". * Entries are added to and managed by calendars. The main attributes of an * entry are: * *

    *
  • Title - the title shown to the user in the UI
  • *
  • Interval - the time interval and time zone occupied by the entry
  • *
  • Full Day - a flag signalling whether the entry should be treated as a "full day" event, e.g. a birthday
  • *
  • Calendar - the calendar to which the entry belongs
  • *
* The default minimum duration of an entry is 15 minutes. * *

Visual Appearance

* The image below shows an entry called "dentist appointment" as it would be visualized * via an {@link com.calendarfx.view.DayEntryView} inside a {@link com.calendarfx.view.DayView}. * * Entry * * *

Recurrence

*

* This class supports the industry standard for defining recurring events (RFC * 2445). For recurring events the method {@link #setRecurrenceRule(String)} * must be fed with a valid RRULE string, for example "RRULE:FREQ=DAILY" for an * event that occures every day. The calendar to which the entry belongs is then * responsible for creating the recurring entries when its * {@link Calendar#findEntries(LocalDate, LocalDate, ZoneId)} method gets * invoked. Recurring entries will return "true" when their * {@link #isRecurrence()} method is called and they will also be able to return * the "source" entry ({@link #getRecurrenceSourceEntry()}). * *

Example

* *
 * Entry entry = new Entry("Dentist Appointment");
 * Interval interval = new Interval(...);
 * entry.setInterval(interval);
 * entry.setRecurrenceRule("RRULE:FREQ=DAILY;INTERVAL=2;");
 *
 * Calendar calendar = new Calendar("Health");
 * calendar.addEntry(entry);
 * 
* * @param the type of the user object */ public class Entry implements Comparable> { private static final Duration DEFAULT_MINIMUM_DURATION = Duration.ofMinutes(15); private String id; /** * Constructs a new entry with a default time interval. The ID will be generated * via {@link UUID#randomUUID()}. */ public Entry() { this(UUID.randomUUID().toString()); } /** * Constructs a new entry with the given title and a default time interval. * The ID will be generated via {@link UUID#randomUUID()}. * * @param title the title shown to the user */ public Entry(String title) { this(title, new Interval(), UUID.randomUUID().toString()); } /** * Constructs a new entry with the given title, a default time interval, and * the given ID. * * @param title the title shown to the user * @param id the unique id of the entry */ public Entry(String title, String id) { this(title, new Interval(), id); } /** * Constructs a new entry with the given title. The ID will be generated * via {@link UUID#randomUUID()}. * * @param title the title shown to the user * @param interval the time interval where the entry is located */ public Entry(String title, Interval interval) { this(title, interval, UUID.randomUUID().toString()); } /** * Constructs a new entry with the given title. * * @param title the title shown to the user * @param interval the time interval where the entry is located * @param id a unique ID, e.g. UUID.randomUUID(); */ public Entry(String title, Interval interval, String id) { requireNonNull(title); requireNonNull(interval); requireNonNull(id); setTitle(title); setInterval(interval); this.id = id; } // A map containing a set of properties for this entry private ObservableMap properties; /** * Returns an observable map of properties on this entry for use primarily * by application developers. * * @return an observable map of properties on this entry for use primarily * by application developers */ public final ObservableMap getProperties() { if (properties == null) { properties = FXCollections.observableMap(new HashMap<>()); MapChangeListener changeListener = change -> { if (change.getKey().equals("com.calendarfx.recurrence.source")) { if (change.getValueAdded() != null) { @SuppressWarnings("unchecked") Entry source = (Entry) change.getValueAdded(); // lookup of property first to instantiate recurrenceSourceProperty(); recurrenceSource.set(source); } } else if (change.getKey().equals("com.calendarfx.recurrence.id")) { if (change.getValueAdded() != null) { setRecurrenceId((String) change.getValueAdded()); } } }; properties.addListener(changeListener); } return properties; } /** * Tests if the entry has properties. * * @return true if the entry has properties. */ public final boolean hasProperties() { return properties != null && !properties.isEmpty(); } // Lazily instantiated to save memory. private ObservableList styleClass; /** * Checks whether the entry has defined any custom styles at all. Calling * this method is better than calling {@link #getStyleClass()} directly as * it does not instantiate the lazily created style class list if it doesn't * exist, yet. * * @return true if the entry defines any styles on its own */ public final boolean hasStyleClass() { return styleClass != null && !styleClass.isEmpty(); } /** * Returns a list of style classes. Adding styles to this list allows the * application to style individual calendar entries (e.g. based on some kind * of status). * * @return a list of style classes */ public final ObservableList getStyleClass() { if (styleClass == null) { styleClass = FXCollections.observableArrayList(); } return styleClass; } private final ObjectProperty interval = new SimpleObjectProperty<>(this, "interval") { @Override public void set(Interval newInterval) { if (newInterval == null) { return; } Interval oldInterval = getValue(); if (!Objects.equals(newInterval, oldInterval)) { Calendar calendar = getCalendar(); if (!isRecurrence() && calendar != null) { calendar.impl_removeEntry(Entry.this); } super.set(newInterval); /* * Update the read-only properties if needed. */ if (startDate != null) { startDate.set(newInterval.getStartDate()); } if (startTime != null) { startTime.set(newInterval.getStartTime()); } if (endDate != null) { endDate.set(newInterval.getEndDate()); } if (endTime != null) { endTime.set(newInterval.getEndTime()); } if (zoneId != null) { zoneId.set(newInterval.getZoneId()); } updateMultiDay(); if (calendar != null) { if (!isRecurrence()) { calendar.impl_addEntry(Entry.this); } calendar.fireEvent(new CalendarEvent(CalendarEvent.ENTRY_INTERVAL_CHANGED, calendar, Entry.this, oldInterval)); } } } }; /** * A property used to store the time interval occupied by this entry. The * interval object stores the start and end dates, the start and end times, * and the time zone. Changes to this property will automatically update the * read-only properties {@link #startDateProperty()}, * {@link #endDateProperty()}, {@link #startTimeProperty()}, * {@link #endTimeProperty()}, {@link #zoneIdProperty()}, and * {@link #multiDayProperty()}. * * @return the time interval used by the entry */ public final ObjectProperty intervalProperty() { return interval; } /** * Returns the value of {@link #intervalProperty()}. * * @return the time interval used by the entry */ public final Interval getInterval() { return interval.get(); } /** * Sets the value of {@link #intervalProperty()}. * * @param interval the new time interval used by the entry */ public final void setInterval(Interval interval) { requireNonNull(interval); intervalProperty().set(interval); } // Set Interval: LocalDate support public final void setInterval(LocalDate date) { setInterval(date, getZoneId()); } public final void setInterval(LocalDate date, ZoneId zoneId) { setInterval(date, date, zoneId); } public final void setInterval(LocalDate startDate, LocalDate endDate) { setInterval(startDate, endDate, getZoneId()); } public final void setInterval(LocalDate startDate, LocalDate endDate, ZoneId zoneId) { setInterval(startDate, LocalTime.MIN, endDate, LocalTime.MAX, zoneId); } public final void setInterval(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { setInterval(startDate, startTime, endDate, endTime, getZoneId()); } public final void setInterval(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime, ZoneId zoneId) { setInterval(new Interval(startDate, startTime, endDate, endTime, zoneId)); } // Set Interval: LocalTime support public final void setInterval(LocalTime startTime, LocalTime endTime) { setInterval(startTime, endTime, getZoneId()); } public final void setInterval(LocalTime startTime, LocalTime endTime, ZoneId zoneId) { setInterval(getStartDate(), startTime, getEndDate(), endTime, zoneId); } // Set Interval: LocalDateTime support public final void setInterval(LocalDateTime dateTime) { setInterval(dateTime, dateTime); } public final void setInterval(LocalDateTime dateTime, ZoneId zoneId) { setInterval(dateTime, dateTime, zoneId); } public final void setInterval(LocalDateTime startDateTime, LocalDateTime endDateTime) { setInterval(startDateTime, endDateTime, getZoneId()); } public final void setInterval(LocalDateTime startDateTime, LocalDateTime endDateTime, ZoneId zoneId) { setInterval(new Interval(startDateTime, endDateTime, zoneId)); } // Set Interval: ZonedDateTime support public final void setInterval(ZonedDateTime date) { setInterval(date, date); } public final void setInterval(ZonedDateTime startDate, ZonedDateTime endDate) { setInterval(new Interval(startDate, endDate)); } /** * Changes the start date of the entry interval and ensures that the entry's interval * stays valid, which means that the start time will be before the end time and that the * duration of the entry will be at least the duration defined by the {@link #minimumDurationProperty()}. * * @param date the new start date */ public final void changeStartDate(LocalDate date) { changeStartDate(date, false); } /** * Changes the start date of the entry interval. * * @param date the new start date * @param keepDuration if true then this method will also change the end date and time in such a way that the total duration * of the entry will not change. If false then this method will ensure that the entry's interval * stays valid, which means that the start time will be before the end time and that the * duration of the entry will be at least the duration defined by the {@link #minimumDurationProperty()}. */ public final void changeStartDate(LocalDate date, boolean keepDuration) { requireNonNull(date); Interval interval = getInterval(); LocalDateTime newStartDateTime = getStartAsLocalDateTime().with(date); LocalDateTime endDateTime = getEndAsLocalDateTime(); if (keepDuration) { endDateTime = newStartDateTime.plus(getDuration()); setInterval(newStartDateTime, endDateTime, getZoneId()); } else { /* * We might have a problem if the new start time is AFTER the current end time. */ if (newStartDateTime.isAfter(endDateTime)) { interval = interval.withEndDateTime(newStartDateTime.plus(interval.getDuration())); } setInterval(interval.withStartDate(date)); } } /** * Changes the start time of the entry interval and ensures that the entry's interval * stays valid, which means that the start time will be before the end time and that the * duration of the entry will be at least the duration defined by the {@link #minimumDurationProperty()}. * * @param time the new start time */ public final void changeStartTime(LocalTime time) { changeStartTime(time, false); } /** * Changes the start time of the entry interval. * * @param time the new start time * @param keepDuration if true then this method will also change the end time in such a way that the total duration * of the entry will not change. If false then this method will ensure that the entry's interval * stays valid, which means that the start time will be before the end time and that the * duration of the entry will be at least the duration defined by the {@link #minimumDurationProperty()}. */ public final void changeStartTime(LocalTime time, boolean keepDuration) { requireNonNull(time); Interval interval = getInterval(); LocalDateTime newStartDateTime = getStartAsLocalDateTime().with(time); LocalDateTime endDateTime = getEndAsLocalDateTime(); if (keepDuration) { endDateTime = newStartDateTime.plus(getDuration()); setInterval(newStartDateTime, endDateTime); } else { /* * We might have a problem if the new start time is AFTER the current end time. */ if (newStartDateTime.isAfter(endDateTime.minus(getMinimumDuration()))) { interval = interval.withEndDateTime(newStartDateTime.plus(getMinimumDuration())); } setInterval(interval.withStartTime(time)); } } /** * Changes the end date of the entry interval and ensures that the entry's interval * stays valid, which means that the start time will be before the end time and that the * duration of the entry will be at least the duration defined by the {@link #minimumDurationProperty()}. * * @param date the new end date */ public final void changeEndDate(LocalDate date) { changeEndDate(date, false); } /** * Changes the end date of the entry interval. * * @param date the new end date * @param keepDuration if true then this method will also change the start date and time in such a way that the total duration * of the entry will not change. If false then this method will ensure that the entry's interval * stays valid, which means that the start time will be before the end time and that the * duration of the entry will be at least the duration defined by the {@link #minimumDurationProperty()}. */ public final void changeEndDate(LocalDate date, boolean keepDuration) { requireNonNull(date); Interval interval = getInterval(); LocalDateTime newEndDateTime = getEndAsLocalDateTime().with(date); LocalDateTime startDateTime = getStartAsLocalDateTime(); if (keepDuration) { startDateTime = newEndDateTime.minus(getDuration()); setInterval(startDateTime, newEndDateTime, getZoneId()); } else { /* * We might have a problem if the new end time is BEFORE the current start time. */ if (newEndDateTime.isBefore(startDateTime)) { interval = interval.withStartDateTime(newEndDateTime.minus(interval.getDuration())); } setInterval(interval.withEndDate(date)); } } /** * Changes the end time of the entry interval and ensures that the entry's interval * stays valid, which means that the start time will be before the end time and that the * duration of the entry will be at least the duration defined by the {@link #minimumDurationProperty()}. * * @param time the new end time */ public final void changeEndTime(LocalTime time) { changeEndTime(time, false); } /** * Changes the end time of the entry interval. * * @param time the new end time * @param keepDuration if true then this method will also change the start time in such a way that the total duration * of the entry will not change. If false then this method will ensure that the entry's interval * stays valid, which means that the start time will be before the end time and that the * duration of the entry will be at least the duration defined by the {@link #minimumDurationProperty()}. */ public final void changeEndTime(LocalTime time, boolean keepDuration) { requireNonNull(time); Interval interval = getInterval(); LocalDateTime newEndDateTime = getEndAsLocalDateTime().with(time); LocalDateTime startDateTime = getStartAsLocalDateTime(); if (keepDuration) { startDateTime = newEndDateTime.minus(getDuration()); setInterval(startDateTime, newEndDateTime, getZoneId()); } else { /* * We might have a problem if the new end time is BEFORE the current start time. */ if (newEndDateTime.isBefore(startDateTime.plus(getMinimumDuration()))) { interval = interval.withStartDateTime(newEndDateTime.minus(getMinimumDuration())); } setInterval(interval.withEndTime(time)); } } /** * Changes the zone ID of the entry interval. * * @param zoneId the new zone */ public final void changeZoneId(ZoneId zoneId) { requireNonNull(zoneId); setInterval(getInterval().withZoneId(zoneId)); } private ReadOnlyObjectWrapper> recurrenceSource; /** * If the entry is a recurrence (see {@link #recurrenceProperty()}) then * this property will store a reference to the entry for which the * recurrence was created. * * @return the entry that was the source of the recurrence */ public final ReadOnlyObjectProperty> recurrenceSourceProperty() { if (recurrenceSource == null) { recurrenceSource = new ReadOnlyObjectWrapper>(this, "recurrenceSource") { @Override public void set(Entry newEntry) { super.set(newEntry); setRecurrence(newEntry != null); } }; } return recurrenceSource.getReadOnlyProperty(); } /** * Returns the value of {@link #recurrenceSourceProperty()}. * * @return the recurrence source */ public final Entry getRecurrenceSourceEntry() { return recurrenceSource == null ? null : recurrenceSource.get(); } /** * If the entry defines a recurrence rule (see * {@link #recurrenceRuleProperty()}) then the calendar will use this method * to create one or more "copies" of the entry. The default implementation * of this method will simply create a new instance of type {@link Entry}. * The initialization of the standard fields (e.g. "Interval" or "Title") of * the recurrence copy will be done by the calendar. Subclasses should * override this method to also initialize additional fields. * * @return a recurrence "copy" of the entry. */ public Entry createRecurrence() { return new Entry<>(); } private boolean _recurrence; private ReadOnlyBooleanWrapper recurrence; /** * A read-only property used to indicate whether the entry is a recurrence * copy of a recurrence source. This property will be set to true if the * property {@link #recurrenceSourceProperty()} gets initialized with a * value other than null. * * @return true if the entry is a recurrence copy * @see #recurrenceRuleProperty() * @see #recurrenceSourceProperty() */ public final ReadOnlyBooleanProperty recurrenceProperty() { if (recurrence == null) { recurrence = new ReadOnlyBooleanWrapper(this, "recurrence", _recurrence); } return recurrence.getReadOnlyProperty(); } /** * Returns the value of {@link #recurrenceProperty()}. * * @return true if the entry is a recurrence copy */ public final boolean isRecurrence() { return recurrence == null ? _recurrence : recurrence.get(); } private void setRecurrence(boolean b) { if (recurrence == null) { _recurrence = b; } else { recurrence.set(b); } } /** * Determines if the entry describes a recurring event. * * @return true if the entry is recurring * @see #recurrenceRuleProperty() */ public final boolean isRecurring() { return recurrenceRule != null && !(recurrenceRule.get() == null) && !recurrenceRule.get().isBlank(); } /* * Recurrence support. */ private StringProperty recurrenceRule; /** * A property used to store a recurrence rule according to RFC-2445. *

Example

Repeat entry / event every other day until September * 1st, 2015. * *
     * String rrule = "RRULE:FREQ=DAILY;INTERVAL=2;UNTIL=20150901";
     * setRecurrenceRule(rrule);
     * 
* * @return the recurrenceRule property * @see #recurrenceEndProperty() */ public final StringProperty recurrenceRuleProperty() { if (recurrenceRule == null) { recurrenceRule = new SimpleStringProperty(null, "recurrenceRule") { @Override public void set(String newRecurrence) { String oldRecurrence = get(); if (!Objects.equals(oldRecurrence, newRecurrence)) { Calendar calendar = getCalendar(); if (calendar != null && !isRecurrence()) { calendar.impl_removeEntry(Entry.this); } super.set(newRecurrence); updateRecurrenceEndProperty(newRecurrence); if (calendar != null) { if (!isRecurrence()) { calendar.impl_addEntry(Entry.this); } calendar.fireEvent(new CalendarEvent(CalendarEvent.ENTRY_RECURRENCE_RULE_CHANGED, calendar, Entry.this, oldRecurrence)); } } } private void updateRecurrenceEndProperty(String newRecurrence) { if (newRecurrence != null && !newRecurrence.trim().equals("")) { try { Recur recur = new Recur<>(newRecurrence.replaceFirst("^RRULE:", "")); setRecurrenceEnd(Objects.requireNonNullElse(recur.getUntil(), LocalDate.MAX)); } catch (IllegalArgumentException | DateTimeParseException e) { e.printStackTrace(); } } else { setRecurrenceEnd(LocalDate.MAX); } } }; } return recurrenceRule; } /** * Sets the value of {@link #recurrenceRuleProperty()}. * * @param rec the new recurrence rule */ public final void setRecurrenceRule(String rec) { if (recurrenceRule == null && rec == null) { // no unnecessary property creation if everything is null return; } recurrenceRuleProperty().set(rec); } /** * Returns the value of {@link #recurrenceRuleProperty()}. * * @return the recurrence rule */ public final String getRecurrenceRule() { return recurrenceRule == null ? null : recurrenceRule.get(); } private String _recurrenceId; private ReadOnlyStringWrapper recurrenceId; /** * Stores the recurrence ID which is being generated on-the-fly by the * {@link Calendar} to which the recurrence source entry belongs. * * @return the recurrence ID property */ public final ReadOnlyStringProperty recurrenceIdProperty() { if (recurrenceId == null) { recurrenceId = new ReadOnlyStringWrapper(this, "recurrenceId", _recurrenceId); } return recurrenceId.getReadOnlyProperty(); } /** * Returns the value of {@link #recurrenceIdProperty()}. * * @return the recurrence ID */ public final String getRecurrenceId() { return recurrenceId == null ? _recurrenceId : recurrenceId.get(); } private void setRecurrenceId(String id) { if (recurrenceId == null) { _recurrenceId = id; } else { recurrenceId.set(id); } } private LocalDate _recurrenceEnd; private ReadOnlyObjectWrapper recurrenceEnd; /** * The property used to store the end time of the recurrence rule. * * @return the recurrence rule end time * @see #recurrenceRuleProperty() */ public final ReadOnlyObjectProperty recurrenceEndProperty() { if (recurrenceEnd == null) { recurrenceEnd = new ReadOnlyObjectWrapper<>(this, "recurrenceEnd", _recurrenceEnd); } return recurrenceEnd.getReadOnlyProperty(); } /** * Returns the value of {@link #recurrenceRuleProperty()}. * * @return the recurrence rule end time */ public final LocalDate getRecurrenceEnd() { return recurrenceEnd == null ? _recurrenceEnd : recurrenceEnd.get(); } private void setRecurrenceEnd(LocalDate date) { if (recurrenceEnd == null) { _recurrenceEnd = date; } else { recurrenceEnd.set(date); } } /** * Assigns a new ID to the entry. IDs do not have to be unique. If several * entries share the same ID it means that they are representing the same * "real world" entry. An entry spanning multiple days will be shown via * several entries in the month view. Clicking on one of them will select * all of them as they all represent the same thing. * * @param id the new ID of the entry */ public final void setId(String id) { requireNonNull(id); if (MODEL.isLoggable(FINE)) { MODEL.fine("setting id to " + id); } this.id = id; } /** * Returns the ID of the entry. * * @return the id object */ public final String getId() { return id; } /* * Calendar support. */ private final SimpleObjectProperty calendar = new SimpleObjectProperty(this, "calendar") { @Override public void set(Calendar newCalendar) { Calendar oldCalendar = get(); if (!Objects.equals(oldCalendar, newCalendar)) { if (oldCalendar != null) { if (!isRecurrence()) { oldCalendar.impl_removeEntry(Entry.this); } } super.set(newCalendar); if (newCalendar != null) { if (!isRecurrence()) { newCalendar.impl_addEntry(Entry.this); } } if (newCalendar != null) { newCalendar.fireEvent(new CalendarEvent(CalendarEvent.ENTRY_CALENDAR_CHANGED, newCalendar, Entry.this, oldCalendar)); } else if (oldCalendar != null) { oldCalendar.fireEvent(new CalendarEvent(CalendarEvent.ENTRY_CALENDAR_CHANGED, newCalendar, Entry.this, oldCalendar)); } } } }; /** * A property used to store a reference to the calendar that owns the entry. * * @return the calendar property */ public final ObjectProperty calendarProperty() { return calendar; } /** * Sets the value of {@link #calendarProperty()}. * * @param cal the new owning calendar of this entry */ public final void setCalendar(Calendar cal) { calendar.set(cal); } /** * Returns the value of {@link #calendarProperty()}. * * @return the owning calendar of this entry */ public final Calendar getCalendar() { return calendar.get(); } /** * A convenience method to easily remove the entry from its calendar. Delegates * to {@link #setCalendar(Calendar)} and passes a null value. */ public final void removeFromCalendar() { setCalendar(null); } /* * User object support. */ private ObjectProperty userObject; /** * A property used to store a reference to an optional user object. The user * object is usually the reason why the entry was created. * * @return the user object property */ public final ObjectProperty userObjectProperty() { if (userObject == null) { userObject = new SimpleObjectProperty(this, "userObject") { @Override public void set(T newObject) { T oldUserObject = get(); // We do not use .equals() here to allow to reset the object even if is "looks" the same e.g. if it // has some .equals() method implemented which just compares an id/business key. if (oldUserObject != newObject) { super.set(newObject); Calendar calendar = getCalendar(); if (calendar != null) { calendar.fireEvent(new CalendarEvent(CalendarEvent.ENTRY_USER_OBJECT_CHANGED, calendar, Entry.this, oldUserObject)); } } } }; } return userObject; } /** * Sets the value of {@link #userObjectProperty()}. * * @param object the new user object */ public final void setUserObject(T object) { if (userObject == null && object == null) { // no unnecessary property creation if everything is null return; } userObjectProperty().set(object); } /** * Returns the value of {@link #userObjectProperty()}. * * @return the user object */ public final T getUserObject() { return userObject == null ? null : userObject.get(); } /* * Zone ID support. */ private ReadOnlyObjectWrapper zoneId; /** * A property used to store a time zone for the entry. The time zone is * needed for properly interpreting the dates and times of the entry. * * @return the time zone property */ public final ReadOnlyObjectProperty zoneIdProperty() { if (zoneId == null) { zoneId = new ReadOnlyObjectWrapper<>(this, "zoneId", getInterval().getZoneId()); } return zoneId.getReadOnlyProperty(); } /** * Sets the value of {@link #zoneIdProperty()}. * * @param zoneId the new time zone to use for this entry */ public final void setZoneId(ZoneId zoneId) { requireNonNull(zoneId); setInterval(getInterval().withZoneId(zoneId)); } /** * Returns the value of {@link #zoneIdProperty()}. * * @return the entry's time zone */ public final ZoneId getZoneId() { return getInterval().getZoneId(); } /* * Title support. */ private final StringProperty title = new SimpleStringProperty(this, "title") { @Override public void set(String newTitle) { String oldTitle = get(); if (!Objects.equals(oldTitle, newTitle)) { super.set(newTitle); Calendar calendar = getCalendar(); if (calendar != null) { calendar.fireEvent(new CalendarEvent(CalendarEvent.ENTRY_TITLE_CHANGED, calendar, Entry.this, oldTitle)); } } } }; /** * A property used to store the title of the entry. * * @return the title property */ public final StringProperty titleProperty() { return title; } /** * Sets the value of {@link #titleProperty()}. * * @param title the title shown by the entry */ public final void setTitle(String title) { titleProperty().set(title); } /** * Returns the value of {@link #titleProperty()}. * * @return the title of the entry */ public final String getTitle() { return titleProperty().get(); } /* * Location support. */ private StringProperty location; /** * A property used to store a free-text location specification for the given * entry. This could be as simple as "New York" or a full address as in * "128 Madison Avenue, New York, USA". * * @return the location of the event specified by the entry */ public final StringProperty locationProperty() { if (location == null) { location = new SimpleStringProperty(null, "location") { @Override public void set(String newLocation) { String oldLocation = get(); if (!Objects.equals(oldLocation, newLocation)) { super.set(newLocation); Calendar calendar = getCalendar(); if (calendar != null) { calendar.fireEvent(new CalendarEvent(CalendarEvent.ENTRY_LOCATION_CHANGED, calendar, Entry.this, oldLocation)); } } } }; } return location; } /** * Sets the value of {@link #locationProperty()}. * * @param loc the new location */ public final void setLocation(String loc) { if (location == null && loc == null) { // no unnecessary property creation if everything is null return; } locationProperty().set(loc); } /** * Returns the value of {@link #locationProperty()}. * * @return the location */ public final String getLocation() { return location == null ? null : location.get(); } private ReadOnlyObjectWrapper startDate; /** * A read-only property used for retrieving the start date of the entry. The * property gets updated whenever the start date inside the entry interval * changes (see {@link #intervalProperty()}). * * @return the start date of the entry */ public final ReadOnlyObjectProperty startDateProperty() { if (startDate == null) { startDate = new ReadOnlyObjectWrapper<>(this, "startDate", getInterval().getStartDate()); } return startDate.getReadOnlyProperty(); } /** * Returns the start date of the entry's interval (see * {@link #intervalProperty()}). * * @return the entry's start date */ public final LocalDate getStartDate() { return getInterval().getStartDate(); } private ReadOnlyObjectWrapper startTime; /** * A read-only property used for retrieving the start time of the entry. The * property gets updated whenever the start time inside the entry interval * changes (see {@link #intervalProperty()}). * * @return the start time of the entry */ public final ReadOnlyObjectProperty startTimeProperty() { if (startTime == null) { startTime = new ReadOnlyObjectWrapper<>(this, "startTime", getInterval().getStartTime()); } return startTime.getReadOnlyProperty(); } /** * Returns the start time of the entry's interval (see * {@link #intervalProperty()}). * * @return the entry's start time */ public final LocalTime getStartTime() { return getInterval().getStartTime(); } private ReadOnlyObjectWrapper endDate; /** * A read-only property used for retrieving the end date of the entry. The * property gets updated whenever the end date inside the entry interval * changes (see {@link #intervalProperty()}). * * @return the end date of the entry */ public final ReadOnlyObjectProperty endDateProperty() { if (endDate == null) { endDate = new ReadOnlyObjectWrapper<>(this, "endDate", getInterval().getEndDate()); } return endDate.getReadOnlyProperty(); } /** * Returns the end date of the entry's interval (see * {@link #intervalProperty()}). * * @return the entry's end date */ public final LocalDate getEndDate() { return getInterval().getEndDate(); } private ReadOnlyObjectWrapper endTime; /** * A read-only property used for retrieving the end time of the entry. The * property gets updated whenever the end time inside the entry interval * changes (see {@link #intervalProperty()}). * * @return the end time of the entry */ public final ReadOnlyObjectProperty endTimeProperty() { if (endTime == null) { endTime = new ReadOnlyObjectWrapper<>(this, "endTime", getInterval().getEndTime()); } return endTime.getReadOnlyProperty(); } /** * Returns the end time of the entry's interval (see * {@link #intervalProperty()}). * * @return the entry's end time */ public final LocalTime getEndTime() { return getInterval().getEndTime(); } /* * Full day support. */ private final BooleanProperty fullDay = new SimpleBooleanProperty(this, "fullDay", false) { @Override public void set(boolean newFullDay) { boolean oldFullDay = get(); if (!Objects.equals(oldFullDay, newFullDay)) { super.set(newFullDay); Calendar calendar = getCalendar(); if (calendar != null) { calendar.fireEvent(new CalendarEvent(CalendarEvent.ENTRY_FULL_DAY_CHANGED, calendar, Entry.this)); } } } }; /** * A property used to signal whether an entry is considered to be a * "full day" entry, for example a birthday. The image below shows how full * day entries are shown in the UI. * * Full Day * * @return the full day property */ public final BooleanProperty fullDayProperty() { return fullDay; } /** * Returns the value of {@link #fullDayProperty()}. * * @return true if the entry is a full day entry, e.g. a birthday */ public final boolean isFullDay() { return fullDayProperty().get(); } /** * Sets the value of {@link #fullDayProperty()}. * * @param fullDay true if entry is a full day entry, e.g. a birthday */ public final void setFullDay(boolean fullDay) { fullDayProperty().set(fullDay); } // shadow field private Duration _minimumDuration = DEFAULT_MINIMUM_DURATION; private ObjectProperty minimumDuration; /** * A property used to store the minimum duration an entry can have. It is * often the case that applications do not allow calendar entries to be * shorter than a certain duration. This property can be used to specify * this. The default value is 15 minutes. Use {@link Duration#ZERO} to allow * zero duration entries. * * @return the minimum duration of the entry */ public final ObjectProperty minimumDurationProperty() { if (minimumDuration == null) { minimumDuration = new SimpleObjectProperty<>(this, "minimumDuration", _minimumDuration); } return minimumDuration; } /** * Returns the value of {@link #minimumDurationProperty()}. * * @return the minimum duration of the entry */ public final Duration getMinimumDuration() { return minimumDuration == null ? _minimumDuration : minimumDuration.get(); } /** * Sets the value of {@link #minimumDurationProperty()}. * * @param duration the minimum duration */ public final void setMinimumDuration(Duration duration) { Objects.requireNonNull(duration); if (minimumDuration != null) { minimumDuration.set(duration); } else { _minimumDuration = duration; } } /* * Utility methods. */ /** * Used by the {@link Calendar#findEntries(String)} to find entries based on * a text search. This method can be overriden. The default implementation * compares the given text with the title of the entry (lower case * comparison). * * @param searchTerm the search term * @return true if the entry matches the given search term */ public boolean matches(String searchTerm) { String title = getTitle(); if (title != null) { return title.toLowerCase().contains(searchTerm.toLowerCase()); } return false; } /** * Utility method to get the zoned start time. This method combines the * start date, start time, and the zone id to create a zoned date time * object. * * @return the zoned start time * @see #getStartDate() * @see #getStartTime() * @see #getZoneId() */ public final ZonedDateTime getStartAsZonedDateTime() { return getInterval().getStartZonedDateTime(); } /** * Returns the start time in milliseconds since 1.1.1970. * * @return the start time in milliseconds */ public final long getStartMillis() { return getInterval().getStartMillis(); } /** * Utility method to get the local start date time. This method combines the * start date and the start time to create a date time object. * * @return the start local date time * @see #getStartDate() * @see #getStartTime() */ public final LocalDateTime getStartAsLocalDateTime() { return getInterval().getStartDateTime(); } /** * Utility method to get the zoned end time. This method combines the end * date, end time, and the zone id to create a zoned date time object. * * @return the zoned end time * @see #getEndDate() * @see #getEndTime() * @see #getZoneId() */ public final ZonedDateTime getEndAsZonedDateTime() { return getInterval().getEndZonedDateTime(); } /** * Returns the end time in milliseconds since 1.1.1970. * * @return the end time in milliseconds */ public final long getEndMillis() { return getInterval().getEndMillis(); } /** * Utility method to get the local end date time. This method combines the * end date and the end time to create a date time object. * * @return the end local date time * @see #getEndDate() * @see #getEndTime() */ public final LocalDateTime getEndAsLocalDateTime() { return getInterval().getEndDateTime(); } private void updateMultiDay() { setMultiDay(getEndDate().isAfter(getStartDate())); } private boolean _multiDay; private ReadOnlyBooleanWrapper multiDay; /** * A read-only property to determine if the entry spans several days. The * image below shows such an entry. * * Multi Day * * @return true if the end date is after the start date (multiple days) * @see #getStartDate() * @see #getEndDate() */ public final ReadOnlyBooleanProperty multiDayProperty() { if (multiDay == null) { multiDay = new ReadOnlyBooleanWrapper(this, "multiDay", _multiDay); } return multiDay.getReadOnlyProperty(); } /** * Returns the value of {@link #multiDayProperty()}. * * @return true if the entry spans multiple days */ public final boolean isMultiDay() { return multiDay == null ? _multiDay : multiDay.get(); } private void setMultiDay(boolean b) { if (multiDay == null) { _multiDay = b; } else { multiDay.set(b); } } /** * Utility method to determine if this entry and the given entry intersect * each other (time bounds overlap each other). * * @param entry the other entry to check * @return true if the entries' time bounds overlap */ public final boolean intersects(Entry entry) { return intersects(entry.getStartAsZonedDateTime(), entry.getEndAsZonedDateTime()); } /** * Utility method to determine if this entry and the given time interval * intersect each other (time bounds overlap each other). * * @param startTime time interval start * @param endTime time interval end * @return true if the entry and the given time interval overlap */ public final boolean intersects(ZonedDateTime startTime, ZonedDateTime endTime) { return Util.intersect(startTime, endTime, getStartAsZonedDateTime(), getEndAsZonedDateTime()); } /** * Utility method to calculate the duration of the entry. The duration is * computed based on the zoned start and end time. * * @return the duration of the entry * @see #getStartAsZonedDateTime() * @see #getEndAsZonedDateTime() */ public final Duration getDuration() { return Duration.between(getStartAsZonedDateTime(), getEndAsZonedDateTime()); } /** * Checks whether the entry will be visible within the given start and end dates. This method * takes recurrence into consideration and will return true if any recurrence of this entry * will be displayed inside the given time interval. * * @param startDate the start date of the search interval * @param endDate the end date of the search interval * @param zoneId the time zone * @return true if the entry or any of its recurrences is showing */ public final boolean isShowing(LocalDate startDate, LocalDate endDate, ZoneId zoneId) { return isShowing(this, startDate, endDate, zoneId); } private boolean isShowing(Entry entry, LocalDate startDate, LocalDate endDate, ZoneId zoneId) { ZonedDateTime st = ZonedDateTime.of(startDate, LocalTime.MIN, zoneId); ZonedDateTime et = ZonedDateTime.of(endDate, LocalTime.MAX, zoneId); if (entry.isRecurring() || entry.isRecurrence()) { return isRecurrenceShowing(entry, st, et, zoneId); } Interval interval = entry.getInterval(); return Util.intersect(interval.getStartZonedDateTime(), interval.getEndZonedDateTime(), st, et); } private final BooleanProperty hidden = new SimpleBooleanProperty(this, "hidden", false); public final boolean isHidden() { return hidden.get(); } public final BooleanProperty hiddenProperty() { return hidden; } /** * An entry can be made explicitly hidden. * * @param hidden true if the entry should not be visible in the calendar */ public final void setHidden(boolean hidden) { this.hidden.set(hidden); } private boolean isRecurrenceShowing(Entry entry, ZonedDateTime st, ZonedDateTime et, ZoneId zoneId) { String recurrenceRule = entry.getRecurrenceRule().replaceFirst("^RRULE:", ""); LocalDate utilStartDate = entry.getStartDate(); try { LocalDate utilEndDate = et.toLocalDate(); /* * TODO: for performance reasons we should definitely * use the advanceTo() call, but unfortunately this * collides with the fact that e.g. the DetailedWeekView loads * data day by day. So a given day would not show * entries that start on the day before but intersect * with the given day. We have to find a solution for * this. */ // iterator.advanceTo(st.toLocalDate()); List dateList = new Recur(recurrenceRule).getDates(utilStartDate, utilEndDate); for (LocalDate repeatingDate : dateList) { ZonedDateTime recurrenceStart = ZonedDateTime.of(repeatingDate, LocalTime.MIN, zoneId); ZonedDateTime recurrenceEnd = recurrenceStart.plus(entry.getDuration()); if (Util.intersect(recurrenceStart, recurrenceEnd, st, et)) { return true; } } } catch (IllegalArgumentException | DateTimeParseException ex) { ex.printStackTrace(); } return false; } @Override public final int compareTo(Entry other) { if (isFullDay() && other.isFullDay()) { return getStartDate().compareTo(other.getStartDate()); } if (isFullDay()) { return -1; } if (other.isFullDay()) { return +1; } LocalDateTime a = LocalDateTime.of(getStartDate(), getStartTime()); LocalDateTime b = LocalDateTime.of(other.getStartDate(), other.getStartTime()); int result = a.compareTo(b); if (result == 0) { String titleA = getTitle() != null ? getTitle() : ""; String titleB = other.getTitle() != null ? other.getTitle() : ""; result = titleA.compareTo(titleB); } return result; } @Override public String toString() { return "Entry [title=" + getTitle() + ", id=" + getId() + ", fullDay=" + isFullDay() + ", startDate=" + getStartDate() + ", endDate=" + getEndDate() + ", startTime=" + getStartTime() + ", endTime=" + getEndTime() + ", zoneId=" + getZoneId() + ", recurring = " + isRecurring() + ", rrule = " + getRecurrenceRule() + ", recurrence = " + isRecurrence() + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((getRecurrenceId() == null) ? 0 : getRecurrenceId().hashCode()); return result; } @SuppressWarnings("rawtypes") @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Entry other = (Entry) obj; if (id == null) { if (other.id != null) { return false; } } else if (!id.equals(other.id)) { return false; } String recId = getRecurrenceId(); String otherRecId = other.getRecurrenceId(); if (recId == null) { return otherRecId == null; } return recId.equals(otherRecId); } private static final String ENTRY_CATEGORY = "Entry"; public ObservableList getPropertySheetItems() { ObservableList items = FXCollections.observableArrayList(); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(calendarProperty()); } @Override public Class getType() { return Calendar.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "Calendar"; } @Override public String getDescription() { return "Calendar"; } @Override public Object getValue() { return getCalendar(); } @Override public void setValue(Object value) { setCalendar((Calendar) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(startTimeProperty()); } @Override public Class getType() { return LocalTime.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "Start time"; } @Override public String getDescription() { return "Start time"; } @Override public Object getValue() { return getStartTime(); } @Override public void setValue(Object value) { changeStartTime((LocalTime) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(endTimeProperty()); } @Override public Class getType() { return LocalTime.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "End time"; } @Override public String getDescription() { return "End time"; } @Override public Object getValue() { return getStartTime(); } @Override public void setValue(Object value) { changeEndTime((LocalTime) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(startDateProperty()); } @Override public Class getType() { return LocalDate.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "Start date"; } @Override public String getDescription() { return "Start date"; } @Override public Object getValue() { return getStartDate(); } @Override public void setValue(Object value) { changeStartDate((LocalDate) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(endDateProperty()); } @Override public Class getType() { return LocalDate.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "End date"; } @Override public String getDescription() { return "End date"; } @Override public Object getValue() { return getEndDate(); } @Override public void setValue(Object value) { changeEndDate((LocalDate) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(zoneIdProperty()); } @Override public Class getType() { return ZoneId.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "Zone ID"; } @Override public String getDescription() { return "Zone ID"; } @Override public Object getValue() { return getZoneId(); } @Override public void setValue(Object value) { setZoneId((ZoneId) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(titleProperty()); } @Override public Class getType() { return String.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "Title"; } @Override public String getDescription() { return "Title"; } @Override public Object getValue() { return getTitle(); } @Override public void setValue(Object value) { setTitle((String) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(fullDayProperty()); } @Override public Class getType() { return Boolean.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "Full Day"; } @Override public String getDescription() { return "Full Day"; } @Override public Object getValue() { return isFullDay(); } @Override public void setValue(Object value) { setFullDay((boolean) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(locationProperty()); } @Override public Class getType() { return String.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "Location"; } @Override public String getDescription() { return "Geographic location (free text)"; } @Override public Object getValue() { return getLocation(); } @Override public void setValue(Object value) { setLocation((String) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(recurrenceRuleProperty()); } @Override public Class getType() { return String.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "Recurrence Rule"; } @Override public String getDescription() { return "RRULE"; } @Override public Object getValue() { return getRecurrenceRule(); } @Override public void setValue(Object value) { setRecurrenceRule((String) value); } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(minimumDurationProperty()); } @Override public Class getType() { return Duration.class; } @Override public String getCategory() { return ENTRY_CATEGORY; } @Override public String getName() { return "Minimum Duration"; } @Override public String getDescription() { return "Minimum Duration"; } @Override public Object getValue() { return getMinimumDuration(); } @Override public void setValue(Object value) { setMinimumDuration((Duration) value); } }); return items; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/Interval.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.model; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import static java.util.Objects.requireNonNull; /** * A class used for storing the time interval and time zone of an entry. * * @see Entry#setInterval(Interval) */ public final class Interval { private static final LocalDate defaultDate = LocalDate.now(); private static final LocalTime defaultStartTime = LocalTime.of(12, 0); private static final LocalTime defaultEndTime = LocalTime.of(13, 0); private static final ZoneId defaultZoneId = ZoneId.systemDefault(); private final LocalDate startDate; private final LocalDate endDate; private final LocalTime startTime; private final LocalTime endTime; private ZonedDateTime zonedStartDateTime; private ZonedDateTime zonedEndDateTime; private LocalDateTime startDateTime; private LocalDateTime endDateTime; private final ZoneId zoneId; private long startMillis = Long.MIN_VALUE; private long endMillis = Long.MAX_VALUE; /** * Constructs a new time interval with start and end dates equal to * {@link LocalDate#now()}. The start and end times will be set to * {@link LocalTime#now()} and {@link LocalTime#now()} plus one hour. The * time zone will be set to {@link ZoneId#systemDefault()}. */ public Interval() { this(defaultDate, defaultStartTime, defaultDate, defaultEndTime, defaultZoneId); } /** * Constructs a new time interval with the given start and end dates / * times. The time zone will be initialized with * {@link ZoneId#systemDefault()}. * * @param startDate the start date (e.g. Oct. 3rd, 2015) * @param startTime the start time (e.g. 10:45am) * @param endDate the end date * @param endTime the end time */ public Interval(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { this(startDate, startTime, endDate, endTime, defaultZoneId); } /** * Constructs a new time interval with the given start and end dates / * times. The time zone will be initialized with * {@link ZoneId#systemDefault()}. * * @param startDateTime the start date and time (e.g. Oct. 3rd, 2015, 6:15pm) * @param endDateTime the end date and time */ public Interval(LocalDateTime startDateTime, LocalDateTime endDateTime) { this(startDateTime, endDateTime, ZoneId.systemDefault()); } /** * Constructs a new time interval with the given start and end zoned dates / * times. The time zone will be initialized with the time zone of the start * date time. However, if the zone ID of the second argument is different then * an exception will be thrown. * * @throws IllegalArgumentException if two different time zones are used * * @param zonedStartDateTime the start date and time (e.g. Oct. 3rd, 2015, 6:15pm) * @param zonedEndDateTime the end date and time */ public Interval(ZonedDateTime zonedStartDateTime, ZonedDateTime zonedEndDateTime) { this(zonedStartDateTime.toLocalDateTime(), zonedEndDateTime.toLocalDateTime(), zonedStartDateTime.getZone()); if (!zonedStartDateTime.getZone().equals(zonedEndDateTime.getZone())) { throw new IllegalArgumentException("the zoned start and end times use different time zones, zone1 = " + zonedStartDateTime.getZone() + ", zone2 = " + zonedEndDateTime.getZone()); } } /** * Constructs a new time interval with the given start and end dates / * times and time zone. * * @param startDateTime the start date and time (e.g. Oct. 3rd, 2015, 6:15pm) * @param endDateTime the end date and time * @param zoneId the time zone */ public Interval(LocalDateTime startDateTime, LocalDateTime endDateTime, ZoneId zoneId) { this(startDateTime.toLocalDate(), startDateTime.toLocalTime(), endDateTime.toLocalDate(), endDateTime.toLocalTime(), zoneId); } /** * Constructs a new time interval with the given start and end times and time zone. * * @param startTime the start time * @param endTime the end time * @param zoneId the time zone */ public Interval(Instant startTime, Instant endTime, ZoneId zoneId) { this(ZonedDateTime.ofInstant(startTime, zoneId), ZonedDateTime.ofInstant(endTime, zoneId)); } /** * Constructs a new time interval with the given start and end dates / times * and time zone. * * @param startDate the start date (e.g. Oct. 3rd, 2015) * @param startTime the start time (e.g. 10:45am) * @param endDate the end date * @param endTime the end time * @param zoneId the time zone */ public Interval(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime, ZoneId zoneId) { this.startDate = requireNonNull(startDate); this.startTime = requireNonNull(startTime); this.endDate = requireNonNull(endDate); this.endTime = requireNonNull(endTime); this.zoneId = requireNonNull(zoneId); if (startDate.isAfter(endDate)) { throw new IllegalArgumentException("the start date can never be after the end date"); } /* * Now we know that the start date is either earlier than the end date or * on the same date. */ if (startDate.equals(endDate)) { /* * If the start date and the end date are on the same date then we have to make sure that the * start time is not after the end time. */ if (getStartTime().isAfter(getEndTime())) { throw new IllegalArgumentException("the start time can not be after the end time if both are on the same date"); } } } /** * Returns the time zone ID. * * @return the time zone */ public ZoneId getZoneId() { return zoneId; } /** * Returns the start date of the interval. * * @return the start date */ public LocalDate getStartDate() { return startDate; } /** * Returns the start time of the interval. * * @return the start time */ public LocalTime getStartTime() { return startTime; } /** * A convenience method to retrieve a zoned date time based on the start * date, start time, and time zone id. * * @return the zoned start time */ public ZonedDateTime getStartZonedDateTime() { if (zonedStartDateTime == null) { zonedStartDateTime = ZonedDateTime.of(startDate, startTime, zoneId); } return zonedStartDateTime; } /** * Returns the start time in milliseconds since 1.1.1970. * * @return the start time in milliseconds */ public long getStartMillis() { if (startMillis == Long.MIN_VALUE) { startMillis = getStartZonedDateTime().toInstant().toEpochMilli(); } return startMillis; } /** * Returns the end date of the interval. * * @return the end date */ public LocalDate getEndDate() { return endDate; } /** * Returns the end time of the interval. * * @return the end time */ public LocalTime getEndTime() { return endTime; } /** * A convenience method to retrieve a zoned date time based on the end date, * end time, and time zone id. * * @return the zoned end time */ public ZonedDateTime getEndZonedDateTime() { if (zonedEndDateTime == null) { zonedEndDateTime = ZonedDateTime.of(endDate, endTime, zoneId); } return zonedEndDateTime; } /** * Returns the start time in milliseconds since 1.1.1970. * * @return the start time in milliseconds */ public long getEndMillis() { if (endMillis == Long.MAX_VALUE) { endMillis = getEndZonedDateTime().toInstant().toEpochMilli(); } return endMillis; } /** * Returns a new interval based on this interval but with a different start * and end date. * * @param startDate the new start date * @param endDate the new end date * @return a new interval */ public Interval withDates(LocalDate startDate, LocalDate endDate) { requireNonNull(startDate); requireNonNull(endDate); return new Interval(startDate, this.startTime, endDate, this.endTime, this.zoneId); } /** * Returns a new interval based on this interval but with a different start * and end date. * * @param startDateTime the new start date * @param endDateTime the new end date * @return a new interval */ public Interval withDates(LocalDateTime startDateTime, LocalDateTime endDateTime) { requireNonNull(startDateTime); requireNonNull(endDateTime); return new Interval(startDateTime, endDateTime, this.zoneId); } /** * Returns a new interval based on this interval but with a different start * and end time. * * @param startTime the new start time * @param endTime the new end time * @return a new interval */ public Interval withTimes(LocalTime startTime, LocalTime endTime) { requireNonNull(startTime); requireNonNull(endTime); return new Interval(this.startDate, startTime, this.endDate, endTime, this.zoneId); } /** * Returns a new interval based on this interval but with a different start * date. * * @param date the new start date * @return a new interval */ public Interval withStartDate(LocalDate date) { requireNonNull(date); return new Interval(date, startTime, endDate, endTime, zoneId); } /** * Returns a new interval based on this interval but with a different end * date. * * @param date the new end date * @return a new interval */ public Interval withEndDate(LocalDate date) { requireNonNull(date); return new Interval(startDate, startTime, date, endTime, zoneId); } /** * Returns a new interval based on this interval but with a different start * time. * * @param time the new start time * @return a new interval */ public Interval withStartTime(LocalTime time) { requireNonNull(time); return new Interval(startDate, time, endDate, endTime, zoneId); } /** * Returns a new interval based on this interval but with a different start date * and time. * * @param dateTime the new start date and time * @return a new interval */ public Interval withStartDateTime(LocalDateTime dateTime) { requireNonNull(dateTime); return new Interval(dateTime.toLocalDate(), dateTime.toLocalTime(), endDate, endTime); } /** * Returns a new interval based on this interval but with a different end * time. * * @param time the new end time * @return a new interval */ public Interval withEndTime(LocalTime time) { requireNonNull(time); return new Interval(startDate, startTime, endDate, time, zoneId); } /** * Returns a new interval based on this interval but with a different end * date and time. * * @param dateTime the new end date and time * @return a new interval */ public Interval withEndDateTime(LocalDateTime dateTime) { requireNonNull(dateTime); return new Interval(startDate, startTime, dateTime.toLocalDate(), dateTime.toLocalTime()); } /** * Returns a new interval based on this interval but with a different time * zone id. * * @param zone the new time zone * @return a new interval */ public Interval withZoneId(ZoneId zone) { requireNonNull(zone); return new Interval(startDate, startTime, endDate, endTime, zone); } /** * Returns a new interval based on this interval but with a different duration. The duration * will change the end time and / or the end date. * * @param duration the new duration * @return a new interval */ public Interval withDuration(Duration duration) { requireNonNull(duration); ZonedDateTime zonedDateTime = ZonedDateTime.of(getStartDate(), getStartTime(), getZoneId()).plus(duration); return new Interval(startDate, startTime, zonedDateTime.toLocalDate(), zonedDateTime.toLocalTime(), getZoneId()); } /** * Utility method to get the local start date time. This method combines the * start date and the start time to create a date time object. * * @return the start local date time * @see #getStartDate() * @see #getStartTime() */ public LocalDateTime getStartDateTime() { if (startDateTime == null) { startDateTime = LocalDateTime.of(getStartDate(), getStartTime()); } return startDateTime; } /** * Returns the duration of this interval. * * @return the duration between the zoned start and end date and time */ public Duration getDuration() { return Duration.between(getStartZonedDateTime(), getEndZonedDateTime()); } /** * Utility method to get the local end date time. This method combines the * end date and the end time to create a date time object. * * @return the end local date time * @see #getEndDate() * @see #getEndTime() */ public LocalDateTime getEndDateTime() { if (endDateTime == null) { endDateTime = LocalDateTime.of(getEndDate(), getEndTime()); } return endDateTime; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((endDate == null) ? 0 : endDate.hashCode()); result = prime * result + ((endTime == null) ? 0 : endTime.hashCode()); result = prime * result + ((startDate == null) ? 0 : startDate.hashCode()); result = prime * result + ((startTime == null) ? 0 : startTime.hashCode()); result = prime * result + ((zoneId == null) ? 0 : zoneId.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Interval other = (Interval) obj; if (endDate == null) { if (other.endDate != null) return false; } else if (!endDate.equals(other.endDate)) return false; if (endTime == null) { if (other.endTime != null) return false; } else if (!endTime.equals(other.endTime)) return false; if (startDate == null) { if (other.startDate != null) return false; } else if (!startDate.equals(other.startDate)) return false; if (startTime == null) { if (other.startTime != null) return false; } else if (!startTime.equals(other.startTime)) return false; if (zoneId == null) { return other.zoneId == null; } else return zoneId.equals(other.zoneId); } @Override public String toString() { return "Interval [startDate=" + startDate + ", endDate=" + endDate + ", startTime=" + startTime + ", endTime=" + endTime + ", zoneId=" + zoneId + "]"; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/IntervalTree.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.model; import java.time.Instant; import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Objects; import java.util.Set; import static java.util.Objects.requireNonNull; /** * An interval tree implementation to store entries based on their start and end * time. * * @param * the entry type */ class IntervalTree> { // package private on purpose private TreeEntry root; private int treeSize; private final Set entryIDs = new HashSet<>(); public final Instant getEarliestTimeUsed() { if (root != null) { return Instant.ofEpochMilli(getEarliestTimeUsed(root)); } return null; } private long getEarliestTimeUsed(TreeEntry entry) { if (entry.getLeft() != null) { return getEarliestTimeUsed(entry.getLeft()); } return entry.low; } public final Instant getLatestTimeUsed() { if (root != null) { return Instant.ofEpochMilli(getLatestTimeUsed(root)); } return null; } private long getLatestTimeUsed(TreeEntry entry) { if (entry.getRight() != null) { return getLatestTimeUsed(entry.getRight()); } return entry.high; } public final boolean add(E entry) { TreeEntry e = addEntry(entry); return e != null; } /** * Method to remove period/key object from tree. Entry to delete will be * found by period and key values of given parameter p (not by given object * reference). * * @param entry * the entry to remove * @return true if the entry was a member of this tree */ public final boolean remove(E entry) { TreeEntry e = getEntry(entry); if (e == null) { return false; } else { deleteEntry(e); } return true; } /** * Method to determine if the interval tree contains the given entry. * * @param entry * the entry to check * @return true if the entry is a member of this tree */ public final boolean contains(E entry) { TreeEntry e = getEntry(entry); return e != null; } public final Collection removePeriod(Instant start, Instant end) { Collection result = getIntersectingObjects(start, end); for (E p : result) { deleteEntry(getEntry(p)); } return result; } public final Collection getIntersectingObjects(Instant start, Instant end) { Collection result = new ArrayList<>(); if (root == null) { return result; } searchIntersecting(root, new TimeInterval(start, end), result); return result; } private void searchIntersecting(TreeEntry entry, TimeInterval timeInterval, Collection result) { // Don't search nodes that don't exist if (entry == null) { return; } long pLow = getLow(timeInterval); long pHigh = getHigh(timeInterval); // If p is to the right of the rightmost point of any interval // in this node and all children, there won't be any matches. if (entry.maxHigh < pLow) { return; } // Search left children if (entry.left != null) { searchIntersecting(entry.left, timeInterval, result); } // Check this node if (checkPLow(entry, pLow) || checkPHigh(entry, pHigh) || (pLow <= entry.low && entry.high <= pHigh)) { result.add(entry.value); } // If p is to the left of the start of this interval, // then it can't be in any child to the right. if (pHigh < entry.low) { return; } // Otherwise, search right children if (entry.right != null) { searchIntersecting(entry.right, timeInterval, result); } } private boolean checkPLow(TreeEntry n, long pLow) { return n.low <= pLow && n.high > pLow; } private boolean checkPHigh(TreeEntry n, long pHigh) { return n.low < pHigh && n.high >= pHigh; } public final long size() { return treeSize; } public final void clear() { treeSize = 0; root = null; } private long getLow(TimeInterval obj) { try { return obj.getStartTime() == null ? Long.MIN_VALUE : obj.getStartTime().toEpochMilli(); } catch (Exception e) { return Long.MAX_VALUE; } } private long getHigh(TimeInterval interval) { try { return interval.getEndTime() == null ? Long.MAX_VALUE : interval.getEndTime().toEpochMilli(); } catch (ArithmeticException e) { return Long.MAX_VALUE; } } private long getLow(Entry entry) { try { return entry.getStartMillis(); } catch (ArithmeticException e) { return Long.MAX_VALUE; } } private long getHigh(Entry entry) { try { return entry.isRecurring() ? ZonedDateTime.of(entry.getRecurrenceEnd(), LocalTime.MAX, entry.getZoneId()).toInstant().toEpochMilli() : entry.getEndMillis(); } catch (ArithmeticException e) { return Long.MAX_VALUE; } } private void fixUpMaxHigh(TreeEntry entry) { while (entry != null) { entry.maxHigh = Math.max(entry.high, Math.max(entry.left != null ? entry.left.maxHigh : Long.MIN_VALUE, entry.right != null ? entry.right.maxHigh : Long.MIN_VALUE)); entry = entry.parent; } } /** * Method to find entry by period. Period start, period end and object key * are used to identify each entry. * * @param entry the calendar entry * @return appropriate entry, or null if not found */ private TreeEntry getEntry(Entry entry) { TreeEntry t = root; while (t != null) { int cmp = compareLongs(getLow(entry), t.low); if (cmp == 0) cmp = compareLongs(getHigh(entry), t.high); if (cmp == 0) cmp = entry.hashCode() - t.value.hashCode(); if (cmp < 0) { t = t.left; } else if (cmp > 0) { t = t.right; } else { return t; } } return null; } private TreeEntry addEntry(E entry) { Objects.requireNonNull(entry, "null entry is not supported"); String id = entry.getId(); if (entryIDs.contains(id)) { // TODO: reactivate this check, currently does not work when the start and end time // of an entry get changed inside the EntryDetailView (two lambda expressions being evaluated // in parallel). // throw new IllegalArgumentException("an entry with ID = " + entry.getId() + " was already added to the calendar"); } entryIDs.add(id); TreeEntry t = root; if (t == null) { root = new TreeEntry<>(getLow(entry), getHigh(entry), entry, null); treeSize = 1; return root; } long cmp; TreeEntry parent; do { parent = t; cmp = compareLongs(getLow(entry), t.low); if (cmp == 0) { cmp = compareLongs(getHigh(entry), t.high); if (cmp == 0) cmp = entry.hashCode() - t.value.hashCode(); } if (cmp < 0) { t = t.left; } else if (cmp > 0) { t = t.right; } else { return null; } } while (t != null); TreeEntry e = new TreeEntry<>(getLow(entry), getHigh(entry), entry, parent); if (cmp < 0) { parent.left = e; } else { parent.right = e; } fixAfterInsertion(e); treeSize++; return e; } private static int compareLongs(long val1, long val2) { return val1 < val2 ? -1 : (val1 == val2 ? 0 : 1); } // This part of code was copied from java.util.TreeMap // Red-black mechanics private static final boolean RED = false; private static final boolean BLACK = true; /** * Internal Entry class. * * @author koop * * @param */ private static final class TreeEntry { private long low; private long high; private V value; private long maxHigh; private TreeEntry left; private TreeEntry right; private TreeEntry parent; private boolean color = BLACK; /** * Make a new cell with given key, value, and parent, and with * null child links, and BLACK color. */ TreeEntry(long low, long high, V value, TreeEntry parent) { this.low = low; this.high = high; this.value = value; this.parent = parent; this.maxHigh = high; } @Override public String toString() { return "[" + Instant.ofEpochMilli(low) + " - " + Instant.ofEpochMilli(high) + "]=" + value; } public TreeEntry getLeft() { return left; } public TreeEntry getRight() { return right; } } /** * Returns the successor of the specified Entry, or null if no such. * * @param the value type */ private static TreeEntry successor(TreeEntry t) { if (t == null) { return null; } else if (t.right != null) { TreeEntry p = t.right; while (p.left != null) { p = p.left; } return p; } else { TreeEntry p = t.parent; TreeEntry ch = t; while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } } /** * Balancing operations. * * Implementations of rebalancings during insertion and deletion are * slightly different than the CLR version. Rather than using dummy * nilnodes, we use a set of accessors that deal properly with null. They * are used to avoid messiness surrounding nullness checks in the main * algorithms. */ private static boolean colorOf(TreeEntry p) { return p == null ? BLACK : p.color; } private static TreeEntry parentOf(TreeEntry p) { return p == null ? null : p.parent; } private static void setColor(TreeEntry p, boolean c) { if (p != null) { p.color = c; } } private static TreeEntry leftOf(TreeEntry p) { return (p == null) ? null : p.left; } private static TreeEntry rightOf(TreeEntry p) { return (p == null) ? null : p.right; } /* From CLR */ private void rotateLeft(TreeEntry p) { if (p != null) { TreeEntry r = p.right; p.right = r.left; if (r.left != null) { r.left.parent = p; } r.parent = p.parent; if (p.parent == null) { root = r; } else if (p.parent.left == p) { p.parent.left = r; } else { p.parent.right = r; } r.left = p; p.parent = r; // Original C code: // x->maxHigh=ITMax(x->left->maxHigh,ITMax(x->right->maxHigh,x->high)) // Original C Code: // y->maxHigh=ITMax(x->maxHigh,ITMax(y->right->maxHigh,y->high)) p.maxHigh = Math.max( p.left != null ? p.left.maxHigh : Long.MIN_VALUE, Math.max(p.right != null ? p.right.maxHigh : Long.MIN_VALUE, p.high)); r.maxHigh = Math.max(p.maxHigh, Math.max(r.right != null ? r.right.maxHigh : Long.MIN_VALUE, r.high)); } } /* From CLR */ private void rotateRight(TreeEntry p) { if (p != null) { TreeEntry l = p.left; p.left = l.right; if (l.right != null) { l.right.parent = p; } l.parent = p.parent; if (p.parent == null) { root = l; } else if (p.parent.right == p) { p.parent.right = l; } else { p.parent.left = l; } l.right = p; p.parent = l; // Original C code: // y->maxHigh=ITMax(y->left->maxHigh,ITMax(y->right->maxHigh,y->high)) // Original C code: // x->maxHigh=ITMax(x->left->maxHigh,ITMax(y->maxHigh,x->high)) p.maxHigh = Math.max( p.left != null ? p.left.maxHigh : Long.MIN_VALUE, Math.max(p.right != null ? p.right.maxHigh : Long.MIN_VALUE, p.high)); l.maxHigh = Math.max(p.maxHigh, Math.max( l.left != null ? l.left.maxHigh : Long.MIN_VALUE, l.high)); } } /* From CLR */ private void fixAfterInsertion(TreeEntry x) { fixUpMaxHigh(x.parent); // augmented interval tree x.color = RED; while (x != null && x != root && x.parent.color == RED) { if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { TreeEntry y = rightOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { TreeEntry y = leftOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); } } } root.color = BLACK; } /** * Delete node p, and then rebalance the tree. */ private void deleteEntry(TreeEntry p) { entryIDs.remove(p.value.getId()); treeSize--; // If strictly internal, copy successor's element to p and then make p // point to successor. if (p.left != null && p.right != null) { TreeEntry s = successor(p); p.low = s.low; p.high = s.high; p.value = s.value; p.maxHigh = s.maxHigh; p = s; } // p has 2 children // Start fixup at replacement node, if it exists. TreeEntry replacement = p.left != null ? p.left : p.right; if (replacement != null) { // Link replacement to parent replacement.parent = p.parent; if (p.parent == null) { root = replacement; } else if (p == p.parent.left) { p.parent.left = replacement; } else { p.parent.right = replacement; } // Null out links so they are OK to use by fixAfterDeletion. p.left = null; p.right = null; p.parent = null; fixUpMaxHigh(replacement.parent); // augmented interval tree // Fix replacement if (p.color == BLACK) { fixAfterDeletion(replacement); } } else if (p.parent == null) { // return if we are the only node. root = null; } else { // No children. Use self as phantom replacement and unlink. if (p.color == BLACK) { fixAfterDeletion(p); } if (p.parent != null) { if (p == p.parent.left) { p.parent.left = null; } else if (p == p.parent.right) { p.parent.right = null; } fixUpMaxHigh(p.parent); // augmented interval tree p.parent = null; } } } /* From CLR */ private void fixAfterDeletion(TreeEntry x) { while (x != root && colorOf(x) == BLACK) { if (x == leftOf(parentOf(x))) { TreeEntry sib = rightOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } else { // symmetric TreeEntry sib = leftOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateRight(parentOf(x)); sib = leftOf(parentOf(x)); } if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(leftOf(sib)) == BLACK) { setColor(rightOf(sib), BLACK); setColor(sib, RED); rotateLeft(sib); sib = leftOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(leftOf(sib), BLACK); rotateRight(parentOf(x)); x = root; } } } setColor(x, BLACK); } private class TimeInterval { private final Instant startTime; private final Instant endTime; public TimeInterval(Instant startTime, Instant endTime) { requireNonNull(startTime); requireNonNull(endTime); if (startTime.isAfter(endTime)) { throw new IllegalArgumentException( "start time can not be after end time, start = " + startTime + ", end = " + endTime); } this.startTime = startTime; this.endTime = endTime; } public Instant getStartTime() { return startTime; } public Instant getEndTime() { return endTime; } } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/LoadEvent.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.model; import javafx.event.Event; import javafx.event.EventType; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import static java.util.Objects.requireNonNull; /** * An event fired by the framework to inform the application that data will be * required for the time interval defined by the given start and end date. This * type of event can be used to implement a lazy loading strategy. * *

Code Example

* The following code snippet was taken from the Google calendar application included * in the distribution. *
 * CalendarView calendarView = ...
 * calendarView.addEventFilter(LoadEvent.LOAD, evt -> {
 *    for (CalendarSource source : evt.getCalendarSources()) {
 *      if (source instanceof GoogleAccount) {
 *         GoogleAccount account = (GoogleAccount) source;
 *
 *         Thread loadThread = new Thread() {
 *            public void run() {
 *               for (Calendar calendar : account.getCalendars()) {
 *                  GoogleCalendar googleCalendar = (GoogleCalendar) calendar;
 *                  googleCalendar.load(evt.getStartDate(), evt.getEndDate(), evt.getZoneId());
 *               }
 *            }
 *         };
 *
 *         loadThread.setDaemon(true);
 *         loadThread.start();
 *
 *         break;
 *      }
 *   }
 * });
 * 
*/ public final class LoadEvent extends Event { private static final long serialVersionUID = -2691268182059394731L; /** * Gets fired frequently by the framework to indicate that data for the * given date range is required to be present in the calendars. */ public static final EventType LOAD = new EventType<>(Event.ANY, "LOAD"); private final List calendarSources; private final LocalDate startDate; private final LocalDate endDate; private final ZoneId zoneId; private final String sourceName; /** * Constructs a new load event. * * @param eventType * the type of the load event * @param sourceName * the name of the source where the event originated, e.g. * "DayView" * @param calendarSources * the affected calendar sources * @param startDate * the start date of the time interval * @param endDate * the end date of the time interval * @param zoneId * the time zone */ public LoadEvent(EventType eventType, String sourceName, List calendarSources, LocalDate startDate, LocalDate endDate, ZoneId zoneId) { super(eventType); this.sourceName = requireNonNull(sourceName); this.calendarSources = requireNonNull(calendarSources); this.startDate = requireNonNull(startDate); this.endDate = requireNonNull(endDate); this.zoneId = requireNonNull(zoneId); } /** * A human readable name of the control that triggered the load event, e.g. * "Day View" or "Month View". Mainly used for debugging purposes. * * @return the name of the control requesting the data */ public String getSourceName() { return sourceName; } /** * The calendar sources that are affected by the load event. * * @return the calendar sources */ public List getCalendarSources() { return calendarSources; } /** * The start of the loaded time interval. * * @return the start date */ public LocalDate getStartDate() { return startDate; } /** * The end of the loaded time interval. * * @return the end date */ public LocalDate getEndDate() { return endDate; } /** * The time zone used for the load. * * @return the time zone */ public ZoneId getZoneId() { return zoneId; } /** * Convenience method to return a zoned date time based on the given start * date and time zone. Uses {@link LocalTime#MIN} as time. * * @return the start time defined by this event */ public ZonedDateTime getStartTime() { return ZonedDateTime.of(startDate, LocalTime.MIN, zoneId); } /** * Convenience method to return a zoned date time based on the given end * date and time zone. Uses {@link LocalTime#MAX} as time. * * @return the start time defined by this event */ public ZonedDateTime getEndTime() { return ZonedDateTime.of(endDate, LocalTime.MAX, zoneId); } @Override public String toString() { return "LoadEvent [sourceName=" + sourceName + ", startDate=" + startDate + ", endDate=" + endDate + ", zoneId=" + zoneId + ", calendarSources=" + calendarSources + "]"; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/Marker.java ================================================ package com.calendarfx.model; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import java.time.ZonedDateTime; public class Marker { public Marker() { } private final BooleanProperty movable = new SimpleBooleanProperty(this, "movable", true); public final boolean isMovable() { return movable.get(); } public final BooleanProperty movableProperty() { return movable; } public final void setMovable(boolean movable) { this.movable.set(movable); } private final ObjectProperty time = new SimpleObjectProperty<>(this, "time", ZonedDateTime.now()); public ZonedDateTime getTime() { return time.get(); } public ObjectProperty timeProperty() { return time; } public void setTime(ZonedDateTime time) { this.time.set(time); } private final StringProperty title = new SimpleStringProperty(this, "title", "Untitled"); public String getTitle() { return title.get(); } public StringProperty titleProperty() { return title; } public void setTitle(String title) { this.title.set(title); } private final StringProperty style = new SimpleStringProperty(this, "style"); public final String getStyle() { return style.get(); } public final StringProperty styleProperty() { return style; } public final void setStyle(String style) { this.style.set(style); } private final ObservableList styleClass = FXCollections.observableArrayList(); public final ObservableList getStyleClass() { return styleClass; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/Resource.java ================================================ package com.calendarfx.model; import com.calendarfx.view.DateControl; import com.calendarfx.view.Messages; import com.calendarfx.view.ResourcesView; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.WeakInvalidationListener; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyListProperty; import javafx.beans.property.ReadOnlyListWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import java.util.ArrayList; import java.util.List; /** * A resource represents a person or a machine. Resources can be edited via * the {@link ResourcesView}. A typical use case would be the allocation of * personnel in a hairdresser salon. A resource might be available or not. * This can be expressed via the {@link #availabilityCalendarProperty()}. * * @param the type of the wrapped / referenced business object (person or machine). * * @see ResourcesView#getResources() * @see DateControl#editAvailabilityProperty() * @see DateControl#availabilityGridProperty() * @see DateControl#availabilityFillProperty() */ public class Resource { private final InvalidationListener updateCalendarListListener = (Observable it) -> updateCalendarList(); private final WeakInvalidationListener weakUpdateCalendarListListener = new WeakInvalidationListener(updateCalendarListListener); public Resource() { getCalendarSources().addListener(weakUpdateCalendarListListener); /* * Every resource is initially populated with a default source and calendar. * We borrow the i18n strings from DateControl. */ Calendar defaultCalendar = new Calendar(Messages.getString("DateControl.DEFAULT_CALENDAR_NAME")); defaultCalendar.setUserObject(this); CalendarSource defaultCalendarSource = new CalendarSource(Messages.getString("DateControl.DEFAULT_CALENDAR_SOURCE_NAME")); defaultCalendarSource.getCalendars().add(defaultCalendar); getCalendarSources().add(defaultCalendarSource); } public Resource(T userObject) { this(); setUserObject(userObject); } private final ObjectProperty userObject = new SimpleObjectProperty<>(this, "userObject"); public final T getUserObject() { return userObject.get(); } /** * An (optional) user object. * * @return the user object (e.g. the person or the calendar data source). */ public final ObjectProperty userObjectProperty() { return userObject; } public final void setUserObject(T userObject) { this.userObject.set(userObject); } private final ObservableList calendarSources = FXCollections.observableArrayList(); /** * The list of all calendar sources attached to this resource. * * @return the calendar sources * @see DateControl#getCalendarSources() */ public final ObservableList getCalendarSources() { return calendarSources; } private final ReadOnlyListWrapper calendars = new ReadOnlyListWrapper<>(FXCollections.observableArrayList()); /** * A list that contains all calendars found in all calendar sources * currently attached to this resource. This is a convenience list that * "flattens" the two level structure of sources and their calendars. It is * a read-only list because calendars can not be added directly to a resource. * Instead, they are added to calendar sources and those sources are * then added to the control. * * @return the list of all calendars available for this resource * @see #getCalendarSources() */ public final ReadOnlyListProperty calendarsProperty() { return calendars.getReadOnlyProperty(); } private final ObservableList unmodifiableCalendars = FXCollections.unmodifiableObservableList(calendars.get()); /** * Returns the value of {@link #calendarsProperty()}. * * @return the list of all calendars available for this control */ public final ObservableList getCalendars() { return unmodifiableCalendars; } public final ObjectProperty availabilityCalendar = new SimpleObjectProperty<>(this, "availabilityCalendar", new Calendar()); public Calendar getAvailabilityCalendar() { return availabilityCalendar.get(); } /** * A resource might be available or not. This can be determined via the "availability" * calendar. * * @return the resource's availability calendar * * @see DateControl#editAvailabilityProperty() * @see DateControl#availabilityGridProperty() * @see DateControl#availabilityFillProperty() */ public ObjectProperty availabilityCalendarProperty() { return availabilityCalendar; } public void setAvailabilityCalendar(Calendar availabilityCalendar) { this.availabilityCalendar.set(availabilityCalendar); } private void updateCalendarList() { List removedCalendars = new ArrayList<>(calendars); List newCalendars = new ArrayList<>(); for (CalendarSource source : getCalendarSources()) { for (Calendar calendar : source.getCalendars()) { if (calendars.contains(calendar)) { removedCalendars.remove(calendar); } else { newCalendars.add(calendar); } } source.getCalendars().removeListener(weakUpdateCalendarListListener); source.getCalendars().addListener(weakUpdateCalendarListListener); } calendars.addAll(newCalendars); calendars.removeAll(removedCalendars); } @Override public String toString() { if (getUserObject() != null) { return getUserObject().toString(); } return super.toString(); } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/model/package-info.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Classes for modeling calendars and entries. */ package com.calendarfx.model; ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/util/CalendarFX.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.util; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * Common superclass for all controls in this framework. */ public final class CalendarFX { private static String version; /** * Returns the CalendarFX version number in the format major.minor.bug * (1.0.0). * * @return the CalendarFX version number */ public static String getVersion() { if (version == null) { InputStream stream = CalendarFX.class.getResourceAsStream("version.properties"); Properties props = new Properties(); try { props.load(stream); } catch (IOException ex) { LoggingDomain.CONFIG.throwing(CalendarFX.class.getName(), "getVersion()", ex); } version = props.getProperty("calendarfx.version", "1.0.0"); LoggingDomain.CONFIG.info("CalendarFX Version: " + version); } return version; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/util/LoggingDomain.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.util; import java.util.logging.Logger; /** * Globally defined loggers. Alternative approach to using class based loggers. */ public final class LoggingDomain { private static final String PREFIX = "com.calendarfx"; /** * Logger used for anything related to the configuration of the calendar. */ public static final Logger CONFIG = Logger.getLogger(PREFIX + ".config"); /** * Logger used for anything related to the creation, fireing, and handling * of events. */ public static final Logger EVENTS = Logger.getLogger(PREFIX + ".events"); /** * Logger used for anything related to the model, adding / removing entries. */ public static final Logger MODEL = Logger.getLogger(PREFIX + ".model"); /** * Logger used for anything related to the creation of the view. */ public static final Logger VIEW = Logger.getLogger(PREFIX + ".view"); /** * Logger used for the search service. */ public static final Logger SEARCH = Logger.getLogger(PREFIX + ".search"); /** * Logger used for anything related to the editing of entries. */ public static final Logger EDITING = Logger.getLogger(PREFIX + ".editing"); /** * Logger used for anything related to recurrence. */ public static final Logger RECURRENCE = Logger.getLogger(PREFIX + ".recurrence"); /** * Logger used for anything related to printing. */ public static final Logger PRINTING = Logger.getLogger(PREFIX + ".printing"); /** * Logger used for anything related to performance. */ public static final Logger PERFORMANCE = Logger.getLogger(PREFIX + ".performance"); } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/util/LoggingFormatter.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.util; import java.io.PrintWriter; import java.io.StringWriter; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.logging.Formatter; import java.util.logging.Level; import java.util.logging.LogRecord; /** * A formatter for the logging framework. Formats logging messages in a very * compact format. * * @author Dirk Lemmermann */ public class LoggingFormatter extends Formatter { private static final DateTimeFormatter formatter = DateTimeFormatter .ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT); private static int MAX_LEVEL_SIZE = 0; static { MAX_LEVEL_SIZE = Math.max(MAX_LEVEL_SIZE, Level.INFO.getLocalizedName() .length()); MAX_LEVEL_SIZE = Math.max(MAX_LEVEL_SIZE, Level.CONFIG .getLocalizedName().length()); MAX_LEVEL_SIZE = Math.max(MAX_LEVEL_SIZE, Level.FINE.getLocalizedName() .length()); MAX_LEVEL_SIZE = Math.max(MAX_LEVEL_SIZE, Level.FINER .getLocalizedName().length()); MAX_LEVEL_SIZE = Math.max(MAX_LEVEL_SIZE, Level.FINEST .getLocalizedName().length()); MAX_LEVEL_SIZE = Math.max(MAX_LEVEL_SIZE, Level.SEVERE .getLocalizedName().length()); MAX_LEVEL_SIZE = Math.max(MAX_LEVEL_SIZE, Level.WARNING .getLocalizedName().length()); } /** * Format the given LogRecord. * * @param record * the log record to be formatted. * @return a formatted log record */ @Override public synchronized String format(LogRecord record) { StringBuilder sb = new StringBuilder(); ZonedDateTime timestamp = ZonedDateTime.ofInstant( Instant.ofEpochMilli(record.getMillis()), ZoneId.systemDefault()); sb.append(formatter.format(timestamp)); sb.append(" | "); if (record.getSourceClassName() != null) { sb.append(truncate( record.getSourceClassName().substring( record.getSourceClassName().lastIndexOf('.') + 1), 30)); } else { sb.append(truncate(record.getLoggerName(), 10)); } sb.append(" | "); if (record.getSourceMethodName() != null) { sb.append(truncate(record.getSourceMethodName(), 30)); } sb.append(" | "); String message = formatMessage(record); sb.append(truncate(record.getLevel().getLocalizedName(), MAX_LEVEL_SIZE)); sb.append(" | "); sb.append(message); sb.append(System.getProperty("line.separator")); if (record.getThrown() != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw); } return sb.toString(); } private CharSequence truncate(String s, int n) { if (s.length() > n) { s = s.substring(0, n); } return String.format("%1$-" + n + "s", s); } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/util/ViewHelper.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.util; import com.calendarfx.view.DateControl; import com.calendarfx.view.DayView; import com.calendarfx.view.DayViewBase; import impl.com.calendarfx.view.DayViewScrollPane; import javafx.collections.ObservableList; import javafx.geometry.Bounds; import javafx.geometry.Point2D; import javafx.geometry.Rectangle2D; import javafx.scene.Node; import javafx.stage.Screen; import org.controlsfx.control.PopOver.ArrowLocation; import java.time.Instant; import java.time.LocalTime; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @SuppressWarnings("javadoc") public final class ViewHelper { public static double getTimeLocation(DayViewBase view, LocalTime time) { return getTimeLocation(view, ZonedDateTime.of(view.getDate(), time, view.getZoneId())); } public static double getTimeLocation(DayViewBase view, LocalTime time, boolean prefHeight) { return getTimeLocation(view, ZonedDateTime.of(view.getDate(), time, view.getZoneId()), prefHeight); } public static double getTimeLocation(DayViewBase view, ZonedDateTime time) { return getTimeLocation(view, time, false); } public static double getTimeLocation(DayViewBase view, Instant instant) { return getTimeLocation(view, ZonedDateTime.ofInstant(instant, view.getZoneId()), false); } public static double getTimeLocation(DayViewBase view, ZonedDateTime zonedTime, boolean prefHeight) { if (view.isScrollingEnabled()) { final Instant scrollInstant = view.getScrollTime().toInstant(); final double mpp = DayView.MILLIS_PER_HOUR / view.getHourHeight(); final long millis = zonedTime.toInstant().toEpochMilli() - scrollInstant.toEpochMilli(); return millis / mpp; } double availableHeight = view.getHeight(); if (prefHeight) { availableHeight = view.prefHeight(-1); } long epochMilli = zonedTime.toInstant().toEpochMilli(); switch (view.getEarlyLateHoursStrategy()) { case SHOW: ZonedDateTime startTime = view.getZonedDateTimeMin(); ZonedDateTime endTime = view.getZonedDateTimeMax(); long startMillis = startTime.toInstant().toEpochMilli(); long endMillis = endTime.toInstant().toEpochMilli(); double mpp = (endMillis - startMillis) / availableHeight; return ((int) ((epochMilli - startMillis) / mpp)) + .5; case HIDE: startTime = view.getZonedDateTimeStart(); endTime = view.getZonedDateTimeEnd(); if (zonedTime.isBefore(startTime)) { return -1; } if (zonedTime.isAfter(endTime)) { return availableHeight; } startMillis = startTime.toInstant().toEpochMilli(); endMillis = endTime.toInstant().toEpochMilli(); mpp = (endMillis - startMillis) / availableHeight; return ((int) ((epochMilli - startMillis) / mpp)) + .5; case SHOW_COMPRESSED: ZonedDateTime minTime = view.getZonedDateTimeMin(); ZonedDateTime maxTime = view.getZonedDateTimeMax(); startTime = view.getZonedDateTimeStart(); endTime = view.getZonedDateTimeEnd(); long earlyHours = ChronoUnit.HOURS.between(minTime, startTime); long lateHours = ChronoUnit.HOURS.between(endTime, maxTime) + 1; double hourHeightCompressed = view.getHourHeightCompressed(); double earlyHeight = hourHeightCompressed * earlyHours; double lateHeight = hourHeightCompressed * lateHours; if (zonedTime.isBefore(startTime)) { /* * Early compressed hours. */ startMillis = minTime.toInstant().toEpochMilli(); endMillis = startTime.toInstant().toEpochMilli(); mpp = (endMillis - startMillis) / earlyHeight; return ((int) ((epochMilli - startMillis) / mpp)) + .5; } else if (zonedTime.isAfter(endTime)) { /* * Late compressed hours. */ startMillis = endTime.toInstant().toEpochMilli(); endMillis = maxTime.toInstant().toEpochMilli(); mpp = (endMillis - startMillis) / lateHeight; return ((int) ((epochMilli - startMillis) / mpp)) + (availableHeight - lateHeight) + .5; } else { /* * Regular hours. */ startMillis = startTime.toInstant().toEpochMilli(); endMillis = endTime.toInstant().toEpochMilli(); mpp = (endMillis - startMillis) / (availableHeight - earlyHeight - lateHeight); return earlyHeight + ((int) ((epochMilli - startMillis) / mpp)) + .5; } default: return 0; } } public static Instant getInstantAt(DayViewBase view, double y) { ZonedDateTime zonedDateTime = view.getZonedDateTimeStart(); double availableHeight = view.getHeight(); switch (view.getEarlyLateHoursStrategy()) { case SHOW: long startMillis = view.getZonedDateTimeMin().toInstant().toEpochMilli(); long endMillis = view.getZonedDateTimeMax().toInstant().toEpochMilli(); double mpp = (endMillis - startMillis) / availableHeight; long millis = (long) (mpp * y) + startMillis; return Instant.ofEpochMilli(millis); case HIDE: ZonedDateTime startTime = view.getZonedDateTimeStart(); ZonedDateTime endTime = view.getZonedDateTimeEnd(); startMillis = startTime.toInstant().toEpochMilli(); endMillis = endTime.toInstant().toEpochMilli(); mpp = (endMillis - startMillis) / availableHeight; millis = (long) (mpp * y) + startMillis; return Instant.ofEpochMilli(millis); case SHOW_COMPRESSED: startTime = view.getZonedDateTimeStart(); endTime = view.getZonedDateTimeEnd(); ZonedDateTime minTime = view.getZonedDateTimeMin(); ZonedDateTime maxTime = view.getZonedDateTimeMax(); long earlyHours = ChronoUnit.HOURS.between(minTime, startTime); long lateHours = ChronoUnit.HOURS.between(endTime, maxTime) + 1; double hourHeightCompressed = view.getHourHeightCompressed(); double earlyHeight = hourHeightCompressed * earlyHours; double lateHeight = hourHeightCompressed * lateHours; if (y < earlyHeight) { /* * Early compressed hours. */ startMillis = minTime.toInstant().toEpochMilli(); endMillis = startTime.toInstant().toEpochMilli(); mpp = (endMillis - startMillis) / earlyHeight; millis = (long) (mpp * y) + startMillis; return Instant.ofEpochMilli(millis); } else if (y > availableHeight - lateHeight) { /* * Late compressed hours. */ startMillis = endTime.toInstant().toEpochMilli(); endMillis = maxTime.toInstant().toEpochMilli(); mpp = (endMillis - startMillis) / lateHeight; millis = (long) (mpp * (y - (availableHeight - lateHeight))) + startMillis; return Instant.ofEpochMilli(millis); } else { /* * Regular hours. */ startMillis = startTime.toInstant().toEpochMilli(); endMillis = endTime.toInstant().toEpochMilli(); mpp = (endMillis - startMillis) / (availableHeight - earlyHeight - lateHeight); millis = (long) (mpp * (y - earlyHeight)) + startMillis; return Instant.ofEpochMilli(millis); } default: return zonedDateTime.toInstant(); } } public static ArrowLocation findPopOverArrowLocation(Node view) { Bounds localBounds = view.getBoundsInLocal(); Bounds entryBounds = view.localToScreen(localBounds); ObservableList screens = Screen.getScreensForRectangle( entryBounds.getMinX(), entryBounds.getMinY(), entryBounds.getWidth(), entryBounds.getHeight()); if (screens.isEmpty()) { return null; } Rectangle2D screenBounds = screens.get(0).getVisualBounds(); double spaceLeft = entryBounds.getMinX(); double spaceRight = screenBounds.getWidth() - entryBounds.getMaxX(); double spaceTop = entryBounds.getMinY(); double spaceBottom = screenBounds.getHeight() - entryBounds.getMaxY(); if (spaceLeft > spaceRight) { if (spaceTop > spaceBottom) { return ArrowLocation.RIGHT_BOTTOM; } return ArrowLocation.RIGHT_TOP; } if (spaceTop > spaceBottom) { return ArrowLocation.LEFT_BOTTOM; } return ArrowLocation.LEFT_TOP; } public static Point2D findPopOverArrowPosition(Node node, double screenY, double arrowSize, ArrowLocation arrowLocation) { Bounds entryBounds = node.localToScreen(node.getBoundsInLocal()); double screenX; if (arrowLocation == ArrowLocation.LEFT_TOP || arrowLocation == ArrowLocation.LEFT_BOTTOM) { screenX = entryBounds.getMaxX(); } else { screenX = entryBounds.getMinX() - arrowSize; } return new Point2D(screenX, screenY); } public static void scrollToRequestedTime(DateControl control, DayViewScrollPane scrollPane) { LocalTime requestedTime = control.getRequestedTime(); if (requestedTime != null) { scrollPane.scrollToTime(requestedTime); } } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/util/WeakList.java ================================================ package com.calendarfx.util; import java.lang.ref.WeakReference; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; /** * A simple list wich holds only weak references to the original objects. */ public class WeakList extends AbstractList { private final ArrayList> items; /** * Creates new WeakList */ public WeakList() { items = new ArrayList<>(); } public WeakList(Collection c) { items = new ArrayList(); addAll(0, c); } public void add(int index, Object element) { items.add(index, new WeakReference(element)); } public Iterator iterator() { return new WeakListIterator(); } public int size() { removeReleased(); return items.size(); } public T get(int index) { return items.get(index).get(); } private void removeReleased() { List> temp = new ArrayList<>(items); temp.forEach(ref -> { if (ref.get() == null) { items.remove(ref); } }); } private class WeakListIterator implements Iterator { private final int n; private int i; public WeakListIterator() { n = size(); i = 0; } public boolean hasNext() { return i < n; } public T next() { return get(i++); } public void remove() { throw new UnsupportedOperationException(); } } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/util/package-info.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Core classes used for logging, license management. */ package com.calendarfx.util; ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/view/AgendaView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.view; import com.calendarfx.model.Entry; import impl.com.calendarfx.view.AgendaViewSkin; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.geometry.HPos; import javafx.scene.Node; import javafx.scene.control.ContentDisplay; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.Skin; import javafx.scene.layout.BorderPane; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.shape.Circle; import javafx.util.Callback; import org.controlsfx.control.PropertySheet.Item; import java.text.MessageFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.time.format.TextStyle; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Optional; import static java.util.Objects.requireNonNull; /** * The agenda view displays calendar entries in a list. The view can be * configured to look back a given number of days and also to look forward a * given number of days. * * Agenda View */ public class AgendaView extends DateControl { private static final String DEFAULT_STYLE_CLASS = "agenda-view"; private static final String AGENDA_CATEGORY = "Agenda View"; private final ListView listView = new ListView<>(); /** * Constructs a new agenda view. */ public AgendaView() { getStyleClass().add(DEFAULT_STYLE_CLASS); listView.setCellFactory(cbListView -> getCellFactory().call(this)); setContextMenu(buildContextMenu()); } /** * Returns the list view that will be used to display one cell for each day that * contains at least one calendar entry. * * @return the list view used by this control */ public final ListView getListView() { return listView; } @Override protected Skin createDefaultSkin() { return new AgendaViewSkin(this); } private ContextMenu buildContextMenu() { ContextMenu menu = new ContextMenu(); Menu lookBackMenu = new Menu(Messages.getString("AgendaView.MENU_ITEM_LOOK_BACK")); Menu lookAheadMenu = new Menu(Messages.getString("AgendaView.MENU_ITEM_LOOK_AHEAD")); String format = Messages.getString("AgendaView.MENU_ITEM_DAYS"); MenuItem lookBack0 = new MenuItem(MessageFormat.format(format, 0)); MenuItem lookBack10 = new MenuItem(MessageFormat.format(format, 10)); MenuItem lookBack20 = new MenuItem(MessageFormat.format(format, 20)); MenuItem lookBack30 = new MenuItem(MessageFormat.format(format, 30)); MenuItem lookAhead0 = new MenuItem(MessageFormat.format(format, 0)); MenuItem lookAhead10 = new MenuItem(MessageFormat.format(format, 10)); MenuItem lookAhead20 = new MenuItem(MessageFormat.format(format, 20)); MenuItem lookAhead30 = new MenuItem(MessageFormat.format(format, 30)); lookBackMenu.getItems().addAll(lookBack0, lookBack10, lookBack20, lookBack30); lookAheadMenu.getItems().addAll(lookAhead0, lookAhead10, lookAhead20, lookAhead30); menu.getItems().addAll(lookBackMenu, lookAheadMenu); lookBack0.setOnAction(evt -> setLookBackPeriodInDays(0)); lookBack10.setOnAction(evt -> setLookBackPeriodInDays(10)); lookBack20.setOnAction(evt -> setLookBackPeriodInDays(20)); lookBack30.setOnAction(evt -> setLookBackPeriodInDays(30)); lookAhead0.setOnAction(evt -> setLookAheadPeriodInDays(0)); lookAhead10.setOnAction(evt -> setLookAheadPeriodInDays(10)); lookAhead20.setOnAction(evt -> setLookAheadPeriodInDays(20)); lookAhead30.setOnAction(evt -> setLookAheadPeriodInDays(30)); return menu; } private final IntegerProperty lookBackPeriodInDays = new SimpleIntegerProperty(this, "lookBackPeriodInDays", 0); /** * Stores the number of days to "look back" into the past when loading data. * * @return the number of days to look back */ public final IntegerProperty lookBackPeriodInDaysProperty() { return lookBackPeriodInDays; } /** * Gets the value of {@link #lookBackPeriodInDaysProperty()}. * * @return the number of days to look back */ public final int getLookBackPeriodInDays() { return lookBackPeriodInDaysProperty().get(); } /** * Sets the value of {@link #lookBackPeriodInDaysProperty()}. * * @param days the new number of days to look back */ public final void setLookBackPeriodInDays(int days) { if (days < 0) { throw new IllegalArgumentException("days must be larger than or equal to 0"); } lookBackPeriodInDaysProperty().set(days); } private final IntegerProperty lookAheadPeriodInDays = new SimpleIntegerProperty(this, "lookAheadPeriodInDays", 30); /** * Stores the number of days to "look ahead" into the future when loading * its data. * * @return the number of days to "look ahead" */ public final IntegerProperty lookAheadPeriodInDaysProperty() { return lookAheadPeriodInDays; } /** * Returns the value of {@link #lookAheadPeriodInDaysProperty()}. * * @return the number of days to look ahead */ public final int getLookAheadPeriodInDays() { return lookAheadPeriodInDaysProperty().get(); } /** * Sets the value of {@link #lookAheadPeriodInDaysProperty()}. * * @param days the number of days to look ahead */ public final void setLookAheadPeriodInDays(int days) { if (days < 0) { throw new IllegalArgumentException("days must be larger than or equal to 0"); } lookAheadPeriodInDaysProperty().set(days); } private final BooleanProperty showStatusLabel = new SimpleBooleanProperty(this, "showStatusLabel", true); public final BooleanProperty showStatusLabelProperty() { return showStatusLabel; } public final boolean isShowStatusLabel() { return showStatusLabelProperty().get(); } public final void setShowStatusLabel(boolean showStatusLabel) { showStatusLabelProperty().set(showStatusLabel); } private final ObjectProperty> cellFactory = new SimpleObjectProperty>(this, "cellFactory", view -> new AgendaEntryCell(this)) { @Override public void set(Callback newValue) { super.set(Objects.requireNonNull(newValue)); } }; public final ObjectProperty> cellFactoryProperty() { return cellFactory; } public final Callback getCellFactory() { return cellFactoryProperty().get(); } public final void setCellFactory(Callback cellFactory) { cellFactoryProperty().set(cellFactory); } private final ObjectProperty formatter = new SimpleObjectProperty<>(this, "formatter", DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)); /** * Gets the DateTimeFormatter property, which is use to provide the format on the TimeScale Labels. By default it * has a value of {@link FormatStyle#LONG} * * @return the date formatter. */ public final ObjectProperty dateTimeFormatterProperty() { return formatter; } /** * Returns the value of {@link #dateTimeFormatterProperty()} * * @return a date time formatter */ public final DateTimeFormatter getDateTimeFormatter() { return dateTimeFormatterProperty().get(); } /** * Sets the value of {@link #dateTimeFormatterProperty()} * * @param formatter a date time formatter, not {@code null} */ public final void setDateTimeFormatter(DateTimeFormatter formatter) { requireNonNull(formatter); dateTimeFormatterProperty().set(formatter); } /** * Agenda entries are model objects that reference a collection of calendar * entries for a specific date. */ public static class AgendaEntry implements Comparable { private final LocalDate date; public AgendaEntry(LocalDate date) { this.date = requireNonNull(date); } public LocalDate getDate() { return date; } private final List> entries = new ArrayList<>(); public final List> getEntries() { return entries; } @Override public int compareTo(AgendaEntry o) { return getDate().compareTo(o.getDate()); } } /** * A specialized list cell that is capable of displaying all entries currently assigned * to a given day. Each cell features a header that shows the date information and a body * that lists all entries. Each entry is visualized with an icon, a title text, and a * time text. * * @see AgendaView#getListView() * @see ListView#setCellFactory(javafx.util.Callback) */ public static class AgendaEntryCell extends ListCell { private static final String AGENDA_VIEW_LIST_CELL = "agenda-view-list-cell"; private static final String AGENDA_VIEW_TIME_LABEL = "time-label"; private static final String AGENDA_VIEW_TITLE_LABEL = "title-label"; private static final String AGENDA_VIEW_BODY = "body"; private static final String AGENDA_VIEW_DATE_LABEL = "date-label"; private static final String AGENDA_VIEW_DATE_LABEL_TODAY = "today"; private static final String AGENDA_VIEW_WEEKDAY_LABEL = "weekday-label"; private static final String AGENDA_VIEW_WEEKDAY_LABEL_TODAY = "today"; private static final String AGENDA_VIEW_HEADER = "header"; private static final String AGENDA_VIEW_HEADER_TODAY = "today"; private static final String AGENDA_VIEW_BODY_SEPARATOR = "separator"; private DateTimeFormatter weekdayFormatter = DateTimeFormatter.ofPattern(Messages.getString("AgendaEntryCell.WEEKDAY_FORMAT")); private DateTimeFormatter mediumDateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); private DateTimeFormatter shortDateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT); private DateTimeFormatter timeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT); private Label weekdayLabel; private Label dateLabel; private GridPane gridPane; private BorderPane headerPane; private final boolean headerPaneVisible; private final AgendaView agendaView; /** * Constructs a new cell that will work with the given agenda view. * * @param view the parent list view */ public AgendaEntryCell(AgendaView view) { this(view, true); } /** * Constructs a new cell that will work with the given agenda view. * * @param view the parent list view * @param headerPaneVisible flag to control the visibility of the cell's header. */ public AgendaEntryCell(AgendaView view, boolean headerPaneVisible) { this.agendaView = Objects.requireNonNull(view); this.headerPaneVisible = headerPaneVisible; BorderPane borderPane = new BorderPane(); borderPane.getStyleClass().add("container"); borderPane.setTop(createHeader()); borderPane.setCenter(createBody()); setGraphic(borderPane); setContentDisplay(ContentDisplay.GRAPHIC_ONLY); getStyleClass().add(AGENDA_VIEW_LIST_CELL); } /** * Creates the node used for the body part of each cell. *

* In this default implementation the body consists of a grid pane with * three columns. The middle column is used for showing the title of * calendar entries. This column will get whatever space is left after * the icon and the time column have used what they need. This means * that a very long title will automatically be truncated. * * @return the body node */ protected Node createBody() { // icon column ColumnConstraints iconColumn = new ColumnConstraints(); // title column ColumnConstraints descriptionColumn = new ColumnConstraints(); descriptionColumn.setFillWidth(true); descriptionColumn.setHgrow(Priority.SOMETIMES); descriptionColumn.setMinWidth(0); descriptionColumn.setPrefWidth(0); // time column ColumnConstraints timeColumn = new ColumnConstraints(); timeColumn.setHalignment(HPos.RIGHT); gridPane = new GridPane(); gridPane.setGridLinesVisible(true); gridPane.setMinWidth(0); gridPane.setPrefWidth(0); gridPane.getStyleClass().add(AGENDA_VIEW_BODY); gridPane.getColumnConstraints().addAll(iconColumn, descriptionColumn, timeColumn); return gridPane; } /** * Creates the header part for each cell. The header consists of a border pane * with the weekday label in the "left" position and the date label in the "right" * position. * * @return the header node */ protected Node createHeader() { headerPane = new BorderPane(); headerPane.getStyleClass().add(AGENDA_VIEW_HEADER); headerPane.setVisible(headerPaneVisible); headerPane.managedProperty().bind(headerPane.visibleProperty()); weekdayLabel = new Label(); weekdayLabel.getStyleClass().add(AGENDA_VIEW_WEEKDAY_LABEL); weekdayLabel.setMinWidth(0); dateLabel = new Label(); dateLabel.setMinWidth(0); dateLabel.getStyleClass().add(AGENDA_VIEW_DATE_LABEL); headerPane.setLeft(weekdayLabel); headerPane.setRight(dateLabel); return headerPane; } @Override protected void updateItem(AgendaEntry item, boolean empty) { super.updateItem(item, empty); gridPane.getChildren().clear(); if (item != null) { LocalDate date = item.getDate(); if (date.equals(agendaView.getToday())) { if (!headerPane.getStyleClass().contains(AGENDA_VIEW_HEADER_TODAY)) { headerPane.getStyleClass().add(AGENDA_VIEW_HEADER_TODAY); dateLabel.getStyleClass().add(AGENDA_VIEW_DATE_LABEL_TODAY); weekdayLabel.getStyleClass().add(AGENDA_VIEW_WEEKDAY_LABEL_TODAY); } } else { headerPane.getStyleClass().remove(AGENDA_VIEW_HEADER_TODAY); dateLabel.getStyleClass().remove(AGENDA_VIEW_DATE_LABEL_TODAY); weekdayLabel.getStyleClass().remove(AGENDA_VIEW_WEEKDAY_LABEL_TODAY); } dateLabel.setText(mediumDateFormatter.format(date)); weekdayLabel.setText(weekdayFormatter.format(date)); int count = item.getEntries().size(); int row = 0; for (int i = 0; i < count; i++) { Entry entry = item.getEntries().get(i); Node entryGraphic = createEntryGraphic(entry); gridPane.add(entryGraphic, 0, row); Label titleLabel = createEntryTitleLabel(entry); titleLabel.setMinWidth(0); gridPane.add(titleLabel, 1, row); Label timeLabel = createEntryTimeLabel(entry); timeLabel.setMinWidth(0); gridPane.add(timeLabel, 2, row); if (count > 1 && i < count - 1) { Region separator = new Region(); separator.getStyleClass().add(AGENDA_VIEW_BODY_SEPARATOR); row++; gridPane.add(separator, 0, row); GridPane.setColumnSpan(separator, 3); GridPane.setFillWidth(separator, true); } row++; } getGraphic().setVisible(true); } else { getGraphic().setVisible(false); } } /** * Creates the label used to display the time of the entry. The default implementation of this * method creates a label and sets the text returned by {@link #getTimeText(Entry)}. * * @param entry the entry for which the time will be displayed * @return a label for displaying the time information on the entry */ protected Label createEntryTimeLabel(Entry entry) { Label timeLabel = new Label(getTimeText(entry)); timeLabel.getStyleClass().add(AGENDA_VIEW_TIME_LABEL); if (agendaView.isEnableHyperlinks()) { timeLabel.setOnMouseClicked(evt -> fireEvent(new RequestEvent(this, this, entry))); timeLabel.getStyleClass().add("date-hyperlink"); } return timeLabel; } /** * Creates the label used to display the title of the entry. The default implementation of this * method creates a label and sets the text found in {@link Entry#getTitle()}. * * @param entry the entry for which the title will be displayed * @return a label for displaying the title of the entry */ protected Label createEntryTitleLabel(Entry entry) { Label titleLabel = new Label(entry.getTitle()); titleLabel.getStyleClass().add(AGENDA_VIEW_TITLE_LABEL); if (agendaView.isEnableHyperlinks()) { titleLabel.setOnMouseClicked(evt -> fireEvent(new RequestEvent(this, this, entry))); titleLabel.getStyleClass().add("date-hyperlink"); } return titleLabel; } /** * Creates a node used to display an icon for the entry. The default implementation of this method * creates a node of type {@link Circle}. The color of the circle will match the color of * the calendar to which the entry belongs. *

         * 	  Circle circle = new Circle(4);
         * 	  circle.getStyleClass().add(entry.getCalendar().getStyle() + "-icon");
         * 
* * @param entry the entry for which the icon will be displayed * @return a node for displaying a graphic for the entry */ protected Node createEntryGraphic(Entry entry) { Circle circle = new Circle(4); circle.getStyleClass().add(entry.getCalendar().getStyle() + "-icon"); return circle; } /** * Creates a nicely formatted text that contains the start and end time of * the given entry. The text can also be something like "full day" if the entry * is a full-day entry. * * @param entry the entry for which the text will be created * @return a text showing the start and end times of the entry */ protected String getTimeText(Entry entry) { if (entry.isFullDay()) { return Messages.getString("AgendaEntryCell.ALL_DAY"); } LocalDate startDate = entry.getStartDate(); LocalDate endDate = entry.getEndDate(); String text; if (startDate.equals(endDate)) { if (Objects.equals(entry.getZoneId(), agendaView.getZoneId())) { text = MessageFormat.format(Messages.getString("AgendaEntryCell.ENTRY_TIME_RANGE"), timeFormatter.format(entry.getStartAsZonedDateTime()), timeFormatter.format(entry.getEndAsZonedDateTime())); } else { text = MessageFormat.format(Messages.getString("AgendaEntryCell.ENTRY_TIME_RANGE"), timeFormatter.format(entry.getStartAsZonedDateTime().withZoneSameInstant(agendaView.getZoneId())), timeFormatter.format(entry.getEndAsZonedDateTime().withZoneSameInstant(agendaView.getZoneId()))); text = text + " (" + MessageFormat.format(Messages.getString("AgendaEntryCell.ENTRY_TIME_RANGE"), timeFormatter.format(entry.getStartAsZonedDateTime()), timeFormatter.format(entry.getEndAsZonedDateTime())) + " " + entry.getZoneId().getDisplayName(TextStyle.SHORT, Locale.getDefault()) + ")"; } } else { if (Objects.equals(entry.getZoneId(), agendaView.getZoneId())) { text = MessageFormat.format(Messages.getString("AgendaEntryCell.ENTRY_TIME_RANGE_WITH_DATE"), shortDateFormatter.format(entry.getStartAsZonedDateTime()), timeFormatter.format(entry.getStartAsZonedDateTime()), shortDateFormatter.format(entry.getEndAsZonedDateTime()), timeFormatter.format(entry.getEndAsZonedDateTime())); } else { text = MessageFormat.format(Messages.getString("AgendaEntryCell.ENTRY_TIME_RANGE_WITH_DATE"), shortDateFormatter.format(entry.getStartAsZonedDateTime().withZoneSameInstant(agendaView.getZoneId())), timeFormatter.format(entry.getStartAsZonedDateTime().withZoneSameInstant(agendaView.getZoneId())), shortDateFormatter.format(entry.getEndAsZonedDateTime().withZoneSameInstant(agendaView.getZoneId())), timeFormatter.format(entry.getEndAsZonedDateTime().withZoneSameInstant(agendaView.getZoneId()))); text = text + "\n" + MessageFormat.format(Messages.getString("AgendaEntryCell.ENTRY_TIME_RANGE_WITH_DATE"), shortDateFormatter.format(entry.getStartAsZonedDateTime()), timeFormatter.format(entry.getStartAsZonedDateTime()), shortDateFormatter.format(entry.getEndAsZonedDateTime()), timeFormatter.format(entry.getEndAsZonedDateTime())) + " " + entry.getZoneId().getDisplayName(TextStyle.SHORT, Locale.getDefault()); } } return text; } /** * Sets the Week Formatter, the value by default is 'EEEE' Format. * * @param weekdayFormatter sets the week date time format. */ public void setWeekdayFormatter(DateTimeFormatter weekdayFormatter) { this.weekdayFormatter = weekdayFormatter; } /** * Sets the Medium Date Formatter, the value by default is {@link FormatStyle#MEDIUM}.
* Is used to set a format text on the Date Label. * * @param mediumDateFormatter sets medium date time format. */ public void setMediumDateFormatter(DateTimeFormatter mediumDateFormatter) { this.mediumDateFormatter = mediumDateFormatter; } /** * Sets the Short Date Formatter, the value by default is {@link FormatStyle#SHORT}.
* Is be used to set a Date format text in {@link #getTimeText(Entry)} * * @param shortDateFormatter sets the short date time format. */ public void setShortDateFormatter(DateTimeFormatter shortDateFormatter) { this.shortDateFormatter = shortDateFormatter; } /** * Sets the Time Formatter, the value by default is {@link FormatStyle#SHORT}.
* Is used to set a Time format text in {@link #getTimeText(Entry)} * * @param timeFormatter sets the time format. */ public void setTimeFormatter(DateTimeFormatter timeFormatter) { this.timeFormatter = timeFormatter; } } @Override public ObservableList getPropertySheetItems() { ObservableList items = super.getPropertySheetItems(); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(lookAheadPeriodInDaysProperty()); } @Override public void setValue(Object value) { setLookAheadPeriodInDays((int) value); } @Override public Object getValue() { return getLookAheadPeriodInDays(); } @Override public Class getType() { return Integer.class; } @Override public String getName() { return "Look Ahead Period"; } @Override public String getDescription() { return "Look ahead period in days"; } @Override public String getCategory() { return AGENDA_CATEGORY; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(lookBackPeriodInDaysProperty()); } @Override public void setValue(Object value) { setLookBackPeriodInDays((int) value); } @Override public Object getValue() { return getLookBackPeriodInDays(); } @Override public Class getType() { return Integer.class; } @Override public String getName() { return "Look Back Period"; } @Override public String getDescription() { return "Look back period in days"; } @Override public String getCategory() { return AGENDA_CATEGORY; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(showStatusLabelProperty()); } @Override public void setValue(Object value) { setShowStatusLabel((boolean) value); } @Override public Object getValue() { return isShowStatusLabel(); } @Override public Class getType() { return Boolean.class; } @Override public String getName() { return "Show Status Label"; } @Override public String getDescription() { return "Show Status Label"; } @Override public String getCategory() { return AGENDA_CATEGORY; } }); return items; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/view/AllDayEntryView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.view; import com.calendarfx.model.Entry; import impl.com.calendarfx.view.AllDayEntryViewSkin; import javafx.scene.control.Skin; /** * An entry view specialized for display in the {@link AllDayView} control. */ public class AllDayEntryView extends EntryViewBase { /** * Constructs a new entry. * * @param entry the entry for which the view will be created */ public AllDayEntryView(Entry entry) { super(entry); } @Override protected Skin createDefaultSkin() { return new AllDayEntryViewSkin(this); } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/view/AllDayView.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.view; import com.calendarfx.model.Entry; import impl.com.calendarfx.view.AllDayViewSkin; import impl.com.calendarfx.view.util.Util; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.css.CssMetaData; import javafx.css.StyleConverter; import javafx.css.Styleable; import javafx.css.StyleableDoubleProperty; import javafx.css.StyleableObjectProperty; import javafx.css.StyleableProperty; import javafx.geometry.Insets; import javafx.scene.control.Skin; import javafx.scene.layout.Region; import javafx.util.Callback; import org.controlsfx.control.PropertySheet.Item; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import static java.util.Objects.requireNonNull; /** * A date control used on top of a {@link DayView} or a {@link DetailedWeekView} for * showing "full day" calendar entries. This view can be configured to span a * given number of days. One day is sufficient when used with a {@link DayView} * and seven days when used with a {@link DetailedWeekView}. * * All Day View * * @see Entry#isFullDay() */ public class AllDayView extends DateControl implements ZonedDateTimeProvider { private static final String ALL_DAY_VIEW = "all-day-view"; /** * Constructs a new view for the given number of days. * * @param numberOfDays the number of days to be shown by this view */ public AllDayView(int numberOfDays) { if (numberOfDays <= 0) { throw new IllegalArgumentException("number of days must be larger than zero"); } getStyleClass().add(ALL_DAY_VIEW); setNumberOfDays(numberOfDays); new CreateAndDeleteHandler(this); } /** * Constructs a new view for seven days. */ public AllDayView() { this(7); } @Override public final ZonedDateTime getZonedDateTimeAt(double x, double y, ZoneId zoneId) { int day = (int) (x / (getWidth() / getNumberOfDays())); LocalDate date = getDate(); if (isAdjustToFirstDayOfWeek()) { date = Util.adjustToFirstDayOfWeek(date, getFirstDayOfWeek()); } date = date.plusDays(day); LocalTime time = LocalTime.NOON; return ZonedDateTime.of(date, time, zoneId); } @Override protected Skin createDefaultSkin() { return new AllDayViewSkin(this); } private StyleableObjectProperty extraPadding; /** * Extra padding to be used inside of the view above and below the full day * entries. This is required as the regular padding is already used for * other styling purposes. * * @return insets for extra padding */ public final ObjectProperty extraPaddingProperty() { if (extraPadding == null) { extraPadding = new StyleableObjectProperty<>(new Insets(2, 0, 9, 0)) { @Override public CssMetaData getCssMetaData() { return StyleableProperties.EXTRA_PADDING; } @Override public Object getBean() { return AllDayView.this; } @Override public String getName() { return "extraPadding"; } }; } return extraPadding; } /** * Returns the value of {@link #extraPaddingProperty()}. * * @return extra padding insets */ public final Insets getExtraPadding() { return extraPaddingProperty().get(); } /** * Sets the value of {@link #extraPaddingProperty()}. * * @param padding padding insets */ public final void setExtraPadding(Insets padding) { requireNonNull(padding); extraPaddingProperty().set(padding); } private StyleableDoubleProperty rowHeight; /** * The height for each row shown by the view. This value determines the * total height of the view. * * @return the row height property */ public final DoubleProperty rowHeightProperty() { if (rowHeight == null) { rowHeight = new StyleableDoubleProperty(20) { @Override public CssMetaData getCssMetaData() { return StyleableProperties.ROW_HEIGHT; } @Override public Object getBean() { return AllDayView.this; } @Override public String getName() { return "rowHeight"; } }; } return rowHeight; } /** * Returns the value of {@link #rowHeightProperty()}. * * @return the row height */ public final double getRowHeight() { return rowHeightProperty().get(); } /** * Sets the value of the {@link #rowHeightProperty()}. * * @param height the new row height */ public final void setRowHeight(double height) { rowHeightProperty().set(height); } private StyleableDoubleProperty rowSpacing; /** * Stores the spacing between rows in the view. * * @return the spacing between rows in pixels */ public final DoubleProperty rowSpacingProperty() { if (rowSpacing == null) { rowSpacing = new StyleableDoubleProperty(2) { @Override public CssMetaData getCssMetaData() { return StyleableProperties.ROW_SPACING; } @Override public Object getBean() { return AllDayView.this; } @Override public String getName() { return "rowSpacing"; } }; } return rowSpacing; } /** * Returns the value of {@link #rowSpacingProperty()}. * * @return the row spacing in pixels */ public final double getRowSpacing() { return rowSpacingProperty().get(); } /** * Sets the value of {@link #rowSpacingProperty()}. * * @param space the space between rows in pixel */ public final void setRowSpacing(double space) { if (space < 0) { throw new IllegalArgumentException("row spacing can not be smaller than zero"); } rowSpacingProperty().set(space); } private StyleableDoubleProperty columnSpacing; /** * Stores the spacing between columns in the view. * * @return the spacing between columns in pixels */ public final DoubleProperty columnSpacingProperty() { if (columnSpacing == null) { columnSpacing = new StyleableDoubleProperty(2) { @Override public CssMetaData getCssMetaData() { return StyleableProperties.COLUMN_SPACING; } @Override public Object getBean() { return AllDayView.this; } @Override public String getName() { return "columnSpacing"; } }; } return columnSpacing; } /** * Returns the value of {@link #columnSpacingProperty()}. * * @return the row spacing in pixels */ public final double getColumnSpacing() { return columnSpacingProperty().get(); } /** * Sets the value of {@link #columnSpacingProperty()}. * * @param space the space between columns in pixel */ public final void setColumnSpacing(double space) { columnSpacingProperty().set(space); } private final BooleanProperty adjustToFirstDayOfWeek = new SimpleBooleanProperty(this, "adjustToFirstDayOfWeek", true); /** * A flag used to indicate that the view should always show the first day of * the week (e.g. "Monday") at its beginning even if the * {@link #dateProperty()} is set to another day (e.g. "Thursday"). The * adjustment is normally needed if the view is used in combination with the * {@link DetailedWeekView}. It is not needed if the view is used together with the * {@link DayView}. * * @return true if the view always shows the first day of the week */ public final BooleanProperty adjustToFirstDayOfWeekProperty() { return adjustToFirstDayOfWeek; } /** * Returns the value of {@link #adjustToFirstDayOfWeekProperty()}. * * @return true if the view always shows the first day of the week */ public final boolean isAdjustToFirstDayOfWeek() { return adjustToFirstDayOfWeekProperty().get(); } /** * Sets the value of {@link #adjustToFirstDayOfWeekProperty()}. * * @param adjust if true the view will always show the first day of the week */ public final void setAdjustToFirstDayOfWeek(boolean adjust) { adjustToFirstDayOfWeekProperty().set(adjust); } private final IntegerProperty numberOfDays = new SimpleIntegerProperty(this, "numberOfDays"); /** * Stores the number of days that will be shown by this view. This value * will be 1 if the view is used in combination with the {@link DayView} and * 7 if used together with the {@link DetailedWeekView}. * * @return the number of days shown by the view */ public final IntegerProperty numberOfDaysProperty() { return numberOfDays; } /** * Returns the value of {@link #numberOfDaysProperty()}. * * @return the number of days shown by the view */ public final int getNumberOfDays() { return numberOfDaysProperty().get(); } /** * Sets the value of {@link #numberOfDaysProperty()}. * * @param number the new number of days shown by the view */ public final void setNumberOfDays(int number) { if (number < 1) { throw new IllegalArgumentException("invalid number of days, must be larger than 0 but was " + number); } numberOfDaysProperty().set(number); } private final ObjectProperty, AllDayEntryView>> entryViewFactory = new SimpleObjectProperty<>(this, "entryViewFactory", AllDayEntryView::new); /** * A callback used for producing views for entries. The views have to be of * type {@link AllDayEntryView}. * * @return the entry view factory */ public final ObjectProperty, AllDayEntryView>> entryViewFactoryProperty() { return entryViewFactory; } /** * Returns the value of {@link #entryViewFactoryProperty()}. * * @return the entry view factory callback */ public final Callback, AllDayEntryView> getEntryViewFactory() { return entryViewFactoryProperty().get(); } /** * Sets the value of {@link #entryViewFactoryProperty()}. * * @param factory the new entry view factory */ public final void setEntryViewFactory(Callback, AllDayEntryView> factory) { requireNonNull(factory); entryViewFactoryProperty().set(factory); } private final ObjectProperty> separatorFactory = new SimpleObjectProperty<>(this, "separatorFactory", it -> { Region region = new Region(); region.getStyleClass().add("weekday-separator"); return region; }); public final Callback getSeparatorFactory() { return separatorFactory.get(); } /** * A factory used for creating (optional) vertical separators between the all day view. * * @return the separator factory */ public final ObjectProperty> separatorFactoryProperty() { return separatorFactory; } public final void setSeparatorFactory(Callback separatorFactory) { this.separatorFactory.set(separatorFactory); } private static class StyleableProperties { private static final List> STYLEABLES; private static final CssMetaData ROW_HEIGHT = new CssMetaData( "-fx-row-height", StyleConverter.getSizeConverter(), 20d) { @Override public Double getInitialValue(AllDayView node) { return node.getRowHeight(); } @Override public boolean isSettable(AllDayView n) { return n.rowHeight == null || !n.rowHeight.isBound(); } @SuppressWarnings("unchecked") @Override public StyleableProperty getStyleableProperty(AllDayView n) { return (StyleableProperty) n.rowHeightProperty(); } }; private static final CssMetaData ROW_SPACING = new CssMetaData( "-fx-row-spacing", StyleConverter.getSizeConverter(), 2d) { @Override public Double getInitialValue(AllDayView node) { return node.getRowSpacing(); } @Override public boolean isSettable(AllDayView n) { return n.rowSpacing == null || !n.rowSpacing.isBound(); } @SuppressWarnings("unchecked") @Override public StyleableProperty getStyleableProperty(AllDayView n) { return (StyleableProperty) n.rowSpacingProperty(); } }; private static final CssMetaData COLUMN_SPACING = new CssMetaData( "-fx-column-spacing", StyleConverter.getSizeConverter(), 2d) { @Override public Double getInitialValue(AllDayView node) { return node.getColumnSpacing(); } @Override public boolean isSettable(AllDayView n) { return n.columnSpacing == null || !n.columnSpacing.isBound(); } @SuppressWarnings("unchecked") @Override public StyleableProperty getStyleableProperty(AllDayView n) { return (StyleableProperty) n.columnSpacingProperty(); } }; private static final CssMetaData EXTRA_PADDING = new CssMetaData( "-fx-extra-padding", StyleConverter.getInsetsConverter(), Insets.EMPTY) { @Override public Insets getInitialValue(AllDayView node) { return node.getExtraPadding(); } @Override public boolean isSettable(AllDayView n) { return n.extraPadding == null || !n.extraPadding.isBound(); } @SuppressWarnings("unchecked") @Override public StyleableProperty getStyleableProperty(AllDayView n) { return (StyleableProperty) n.extraPaddingProperty(); } }; static { final List> styleables = new ArrayList<>(Region.getClassCssMetaData()); styleables.add(ROW_HEIGHT); styleables.add(ROW_SPACING); styleables.add(COLUMN_SPACING); styleables.add(EXTRA_PADDING); STYLEABLES = Collections.unmodifiableList(styleables); } } public static List> getClassCssMetaData() { return StyleableProperties.STYLEABLES; } @Override public final List> getControlCssMetaData() { return getClassCssMetaData(); } private static final String ALL_DAY_VIEW_CATEGORY = "All Day View"; @Override public ObservableList getPropertySheetItems() { ObservableList items = super.getPropertySheetItems(); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(numberOfDaysProperty()); } @Override public void setValue(Object value) { setNumberOfDays((Integer) value); } @Override public Object getValue() { return getNumberOfDays(); } @Override public Class getType() { return Integer.class; } @Override public String getName() { return "Number Of Days"; } @Override public String getDescription() { return "Determines how many days will be covered by this control"; } @Override public String getCategory() { return ALL_DAY_VIEW_CATEGORY; } }); // column spacing items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(columnSpacingProperty()); } @Override public void setValue(Object value) { setColumnSpacing((double) value); } @Override public Object getValue() { return getColumnSpacing(); } @Override public Class getType() { return Double.class; } @Override public String getName() { return "Column Spacing"; } @Override public String getDescription() { return "The gap between the days / columns"; } @Override public String getCategory() { return ALL_DAY_VIEW_CATEGORY; } }); // row height items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(rowHeightProperty()); } @Override public void setValue(Object value) { setRowHeight((double) value); } @Override public Object getValue() { return getRowHeight(); } @Override public Class getType() { return Double.class; } @Override public String getName() { return "Row Height"; } @Override public String getDescription() { return "The height of each row in the control"; } @Override public String getCategory() { return ALL_DAY_VIEW_CATEGORY; } }); // row spacing items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(rowSpacingProperty()); } @Override public void setValue(Object value) { setRowSpacing((double) value); } @Override public Object getValue() { return getRowSpacing(); } @Override public Class getType() { return Double.class; } @Override public String getName() { return "Row Spacing"; } @Override public String getDescription() { return "The gap between the rows"; } @Override public String getCategory() { return ALL_DAY_VIEW_CATEGORY; } }); // extra padding items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(extraPaddingProperty()); } @Override public void setValue(Object value) { setExtraPadding((Insets) value); } @Override public Object getValue() { return getExtraPadding(); } @Override public Class getType() { return Insets.class; } @Override public String getName() { return "Extra Padding"; } @Override public String getDescription() { return "Additional padding inside the control"; } @Override public String getCategory() { return ALL_DAY_VIEW_CATEGORY; } }); return items; } } ================================================ FILE: CalendarFXView/src/main/java/com/calendarfx/view/ButtonBar.java ================================================ /* * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.calendarfx.view; import impl.com.calendarfx.view.ButtonBarSkin; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.Button; import javafx.scene.control.Skin; /** * A segmented button bar control based on the segmented button control * found in ControlsFX but this one working with regular buttons and not * toggle buttons. * Button Bar */ public class ButtonBar extends CalendarFXControl { private final ObservableList