package de.saring.sportstracker.gui.views.calendarview;
import java.time.LocalDate;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.StackPane;
import javax.inject.Inject;
import javax.inject.Singleton;
import de.saring.sportstracker.data.Exercise;
import de.saring.sportstracker.data.Note;
import de.saring.sportstracker.data.Weight;
import de.saring.sportstracker.gui.STContext;
import de.saring.sportstracker.gui.STDocument;
import de.saring.sportstracker.gui.views.AbstractEntryViewController;
import de.saring.sportstracker.gui.views.ViewPrinter;
import de.saring.util.AppResources;
import de.saring.util.data.IdDateObject;
import de.saring.util.data.IdObject;
import de.saring.util.gui.javafx.control.calendar.CalendarActionListener;
import de.saring.util.gui.javafx.control.calendar.CalendarControl;
import de.saring.util.gui.javafx.control.calendar.CalendarEntry;
/**
* Controller class of the Calendar View, which displays all (or a filtered list) exercises, notes and
* weights of the selected month. It also contains all the navigation controls for selecting the month.
*
* @author Stefan Saring
*/
@Singleton
public class CalendarViewController extends AbstractEntryViewController {
@FXML
private Label laDisplayedMonth;
@FXML
private Label laDisplayedYear;
@FXML
private StackPane spCalendar;
private CalendarControl calendarControl;
/**
* The current displayed month.
*/
private IntegerProperty displayedMonth = new SimpleIntegerProperty();
/**
* The current displayed year.
*/
private IntegerProperty displayedYear = new SimpleIntegerProperty();
/**
* Standard c'tor for dependency injection.
*
* @param context the SportsTracker UI context
* @param document the SportsTracker document / model
* @param viewPrinter the printer of the SportsTracker views
*/
@Inject
public CalendarViewController(final STContext context, final STDocument document, final ViewPrinter viewPrinter) {
super(context, document, viewPrinter);
}
@Override
public ViewType getViewType() {
return ViewType.CALENDAR;
}
@Override
public void updateView() {
calendarControl.updateCalendar(displayedYear.get(), displayedMonth.get(), //
getDocument().getOptions().isWeekStartSunday());
}
@Override
public int getSelectedExerciseCount() {
return getSelectedExerciseIDs().length;
}
@Override
public int[] getSelectedExerciseIDs() {
return getSelectedEntryIdsOfClass(Exercise.class);
}
@Override
public int getSelectedNoteCount() {
return getSelectedNoteIDs().length;
}
@Override
public int[] getSelectedNoteIDs() {
return getSelectedEntryIdsOfClass(Note.class);
}
@Override
public int getSelectedWeightCount() {
return getSelectedWeightIDs().length;
}
@Override
public int[] getSelectedWeightIDs() {
return getSelectedEntryIdsOfClass(Weight.class);
}
@Override
public void selectEntry(final IdObject entry) {
if (entry instanceof IdDateObject) {
IdDateObject dateEntry = (IdDateObject) entry;
// set calendar to month/year of the entry
displayedYear.set(dateEntry.getDateTime().getYear());
displayedMonth.set(dateEntry.getDateTime().getMonthValue());
updateView();
calendarControl.selectEntry(dateEntry);
}
}
@Override
public void removeSelection() {
calendarControl.removeSelection();
}
@Override
protected String getFxmlFilename() {
return "/fxml/views/CalendarView.fxml";
}
@Override
protected void setupView() {
setupCalendarControl();
setCalendarActionListener();
setupCalendarContextMenu();
// bind month and year labels to current values
displayedMonth.addListener((observable, oldValue, newValue) -> laDisplayedMonth.setText( //
getContext().getResources().getString("st.calview.months." + newValue.intValue())));
laDisplayedYear.textProperty().bind(displayedYear.asString());
// display the current day at startup
onToday(null);
}
private void setupCalendarControl() {
calendarControl = new CalendarControl();
calendarControl.setCalendarDataProvider(new CalendarDataProviderImpl(getContext(), getDocument()));
spCalendar.getChildren().addAll(calendarControl);
// set localized column header names
final AppResources resources = getContext().getResources();
calendarControl.setColumnNames(new String[] { resources.getString("st.valview.weekdays.monday"), //
resources.getString("st.valview.weekdays.tuesday"), //
resources.getString("st.valview.weekdays.wednesday"), //
resources.getString("st.valview.weekdays.thursday"), //
resources.getString("st.valview.weekdays.friday"), //
resources.getString("st.valview.weekdays.saturday"), //
resources.getString("st.valview.weekdays.sunday"), //
resources.getString("st.valview.week_sum") });
// update controller-actions and the status bar on selection changes
calendarControl.selectedEntryProperty().addListener((observable, oldValue, newValue) -> //
getEventHandler().updateActionsAndStatusBar());
// scroll the displayed month when the user uses the mouse wheel on the calendar
calendarControl.setOnScroll(event -> {
if (event.getDeltaY() > 0) {
onPreviousMonth(null);
} else if (event.getDeltaY() < 0) {
onNextMonth(null);
}
});
}
private void setCalendarActionListener() {
calendarControl.calendarActionListenerProperty().set(new CalendarActionListener() {
@Override
public void onCalendarDayAction(final LocalDate date) {
// execute action 'Add Exercise' for this date when the user double clicks a calendar day cell
addExerciseForDate(date);
}
@Override
public void onCalendarEntryAction(final CalendarEntry calendarEntry) {
// execute action 'Edit Entry' when the user double clicks a calendar entry
getEventHandler().onEditEntry(null);
}
@Override
public void onDraggedFileDroppedOnCalendarDay(final String filePath) {
getEventHandler().onAddExerciseForDroppedHrmFile(filePath);
}
@Override
public void onDraggedFileDroppedOnCalendarEntry(final IdDateObject entry, final String filePath) {
if (entry instanceof Exercise) {
getEventHandler().onAssignDroppedHrmFileToExercise(filePath, (Exercise) entry);
} else {
// the target entry was not an exercise -> add a new exercise for the dropped entry
getEventHandler().onAddExerciseForDroppedHrmFile(filePath);
}
}
});
}
/**
* Sets up the context menu for the calendar control. Unfortunately it can't be defined in
* FXML, the Pane classes does not support context menus directly.
*/
private void setupCalendarContextMenu() {
final BooleanBinding bindingNoEntrySelected = Bindings.isNull(calendarControl.selectedEntryProperty());
final MenuItem miCtxAddExercise = createContextMenuItem( //
"miCtxAddExercise", "st.view.exercise_add.Action.text", //
event -> addExerciseForDate(calendarControl.dateOfContextMenuProperty().get()));
final MenuItem miCtxAddNote = createContextMenuItem( //
"miCtxAddNote", "st.view.note_add.Action.text", //
event -> addNoteForDate(calendarControl.dateOfContextMenuProperty().get()));
final MenuItem miCtxAddWeight = createContextMenuItem( //
"miCtxAddWeight", "st.view.weight_add.Action.text", //
event -> addWeightForDate(calendarControl.dateOfContextMenuProperty().get()));
final MenuItem miCtxEditEntry = createContextMenuItem( //
"miCtxEditEntry", "st.view.entry_edit.Action.text", //
event -> getEventHandler().onEditEntry(event));
miCtxEditEntry.disableProperty().bind(bindingNoEntrySelected);
final MenuItem miCtxCopyEntry = createContextMenuItem( //
"miCtxCopyEntry", "st.view.entry_copy.Action.text", //
event -> getEventHandler().onCopyEntry(event));
miCtxCopyEntry.disableProperty().bind(bindingNoEntrySelected);
final MenuItem miCtxDeleteEntry = createContextMenuItem( //
"miCtxDeleteEntry", "st.view.entry_delete.Action.text", //
event -> getEventHandler().onDeleteEntry(event));
miCtxDeleteEntry.disableProperty().bind(bindingNoEntrySelected);
calendarControl.setContextMenu(new ContextMenu( //
miCtxAddExercise, miCtxAddNote, miCtxAddWeight, miCtxEditEntry, miCtxCopyEntry, miCtxDeleteEntry));
}
private MenuItem createContextMenuItem(final String id, final String resourceKey, //
final EventHandler<ActionEvent> handler) {
final MenuItem menuItem = new MenuItem(getContext().getResources().getString(resourceKey));
menuItem.setId(id);
menuItem.setOnAction(handler);
menuItem.getStyleClass().add("contextMenuItem");
return menuItem;
}
/**
* Action handler for showing the previous month in the calendar.
*/
@FXML
private void onPreviousMonth(final ActionEvent event) {
if (displayedMonth.get() > 1) {
displayedMonth.set(displayedMonth.get() - 1);
} else {
displayedMonth.set(12);
displayedYear.set(displayedYear.get() - 1);
}
updateView();
}
/**
* Action handler for showing the next month in the calendar.
*/
@FXML
private void onNextMonth(final ActionEvent event) {
if (displayedMonth.get() < 12) {
displayedMonth.set(displayedMonth.get() + 1);
} else {
displayedMonth.set(1);
displayedYear.set(displayedYear.get() + 1);
}
updateView();
}
/**
* Action handler for showing the previous year in the calendar.
*/
@FXML
private void onPreviousYear(final ActionEvent event) {
displayedYear.set(displayedYear.get() - 1);
updateView();
}
/**
* Action handler for showing the next year in the calendar.
*/
@FXML
private void onNextYear(final ActionEvent event) {
displayedYear.set(displayedYear.get() + 1);
updateView();
}
/**
* Action handler for showing the current day in the calendar.
*/
@FXML
private void onToday(final ActionEvent event) {
final LocalDate today = LocalDate.now();
displayedMonth.set(today.getMonthValue());
displayedYear.set(today.getYear());
updateView();
}
/**
* Returns an array with the ID's of the currently selected calendar entries of the
* specified type (maximum count in the calendar view is 1).
*
* @return array of the selected CalendarEntry ID's (can be empty but not null)
*/
private int[] getSelectedEntryIdsOfClass(final Class<? extends IdDateObject> clazz) {
final IdObject selectedEntry = calendarControl.selectedEntryProperty().get();
if ((selectedEntry == null) || (selectedEntry.getClass() != clazz)) {
return new int[0];
} else {
return new int[] { selectedEntry.getId() };
}
}
private void addExerciseForDate(final LocalDate date) {
getEventHandler().setDateForNewEntries(date);
getEventHandler().onAddExercise(null);
getEventHandler().setDateForNewEntries(null);
}
private void addNoteForDate(final LocalDate date) {
getEventHandler().setDateForNewEntries(date);
getEventHandler().onAddNote(null);
getEventHandler().setDateForNewEntries(null);
}
private void addWeightForDate(final LocalDate date) {
getEventHandler().setDateForNewEntries(date);
getEventHandler().onAddWeight(null);
getEventHandler().setDateForNewEntries(null);
}
}