mirror of
https://github.com/sabazadam/Se302.git
synced 2025-12-31 20:31:22 +00:00
Implemented Drag-and-Drop in calendar view for examinations, with real-time validation
This commit is contained in:
@@ -7,8 +7,13 @@ import javafx.geometry.Insets;
|
|||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.input.DataFormat;
|
||||||
|
import javafx.scene.input.Dragboard;
|
||||||
|
import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import org.example.se302.model.*;
|
import org.example.se302.model.*;
|
||||||
|
import org.example.se302.service.ConstraintValidationService;
|
||||||
import org.example.se302.service.DataManager;
|
import org.example.se302.service.DataManager;
|
||||||
import org.example.se302.service.ScheduleGeneratorService;
|
import org.example.se302.service.ScheduleGeneratorService;
|
||||||
|
|
||||||
@@ -78,11 +83,17 @@ public class ScheduleCalendarController {
|
|||||||
|
|
||||||
// Services
|
// Services
|
||||||
private final DataManager dataManager = DataManager.getInstance();
|
private final DataManager dataManager = DataManager.getInstance();
|
||||||
|
private final ConstraintValidationService validationService = new ConstraintValidationService();
|
||||||
private ScheduleGeneratorService generatorService;
|
private ScheduleGeneratorService generatorService;
|
||||||
private ScheduleState currentSchedule;
|
private ScheduleState currentSchedule;
|
||||||
private ScheduleConfiguration currentConfig;
|
private ScheduleConfiguration currentConfig;
|
||||||
private Thread generationThread;
|
private Thread generationThread;
|
||||||
|
|
||||||
|
// Drag-and-drop state
|
||||||
|
private static final DataFormat EXAM_DATA_FORMAT = new DataFormat("application/x-exam-assignment");
|
||||||
|
private ExamAssignment draggedExam = null;
|
||||||
|
private Map<String, VBox> cellMap = new HashMap<>(); // "day_slot" -> cell VBox
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
// Initialize spinners
|
// Initialize spinners
|
||||||
@@ -382,6 +393,7 @@ public class ScheduleCalendarController {
|
|||||||
scheduleGrid.getChildren().clear();
|
scheduleGrid.getChildren().clear();
|
||||||
scheduleGrid.getColumnConstraints().clear();
|
scheduleGrid.getColumnConstraints().clear();
|
||||||
scheduleGrid.getRowConstraints().clear();
|
scheduleGrid.getRowConstraints().clear();
|
||||||
|
cellMap.clear(); // Clear cell map for drag-and-drop
|
||||||
|
|
||||||
int numDays = config.getNumDays();
|
int numDays = config.getNumDays();
|
||||||
int slotsPerDay = config.getSlotsPerDay();
|
int slotsPerDay = config.getSlotsPerDay();
|
||||||
@@ -432,6 +444,9 @@ public class ScheduleCalendarController {
|
|||||||
for (int day = 0; day < numDays; day++) {
|
for (int day = 0; day < numDays; day++) {
|
||||||
VBox cellContent = createScheduleCell(schedule, day, slot);
|
VBox cellContent = createScheduleCell(schedule, day, slot);
|
||||||
scheduleGrid.add(cellContent, day + 1, slot + 1);
|
scheduleGrid.add(cellContent, day + 1, slot + 1);
|
||||||
|
|
||||||
|
// Store cell reference for drag-and-drop
|
||||||
|
cellMap.put(day + "_" + slot, cellContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -450,7 +465,11 @@ public class ScheduleCalendarController {
|
|||||||
VBox cell = new VBox(3);
|
VBox cell = new VBox(3);
|
||||||
cell.setPadding(new Insets(5));
|
cell.setPadding(new Insets(5));
|
||||||
cell.setAlignment(Pos.TOP_LEFT);
|
cell.setAlignment(Pos.TOP_LEFT);
|
||||||
cell.setStyle("-fx-background-color: #ecf0f1; -fx-border-color: #bdc3c7; -fx-border-width: 1;");
|
String defaultCellStyle = "-fx-background-color: #ecf0f1; -fx-border-color: #bdc3c7; -fx-border-width: 1;";
|
||||||
|
cell.setStyle(defaultCellStyle);
|
||||||
|
|
||||||
|
// Store cell coordinates for drop handling
|
||||||
|
cell.setUserData(new int[] { day, slot });
|
||||||
|
|
||||||
// Find all exams at this day/slot
|
// Find all exams at this day/slot
|
||||||
List<ExamAssignment> examsAtSlot = new ArrayList<>();
|
List<ExamAssignment> examsAtSlot = new ArrayList<>();
|
||||||
@@ -474,7 +493,7 @@ public class ScheduleCalendarController {
|
|||||||
HBox examBox = new HBox(5);
|
HBox examBox = new HBox(5);
|
||||||
examBox.setAlignment(Pos.CENTER_LEFT);
|
examBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
examBox.setStyle("-fx-background-color: #3498db; -fx-background-radius: 3; -fx-padding: 3 6;");
|
examBox.setStyle("-fx-background-color: #3498db; -fx-background-radius: 3; -fx-padding: 3 6;");
|
||||||
examBox.setCursor(Cursor.HAND);
|
examBox.setCursor(Cursor.MOVE);
|
||||||
|
|
||||||
Label courseLabel = new Label(exam.getCourseCode());
|
Label courseLabel = new Label(exam.getCourseCode());
|
||||||
courseLabel.setStyle("-fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 11;");
|
courseLabel.setStyle("-fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 11;");
|
||||||
@@ -485,17 +504,51 @@ public class ScheduleCalendarController {
|
|||||||
examBox.getChildren().addAll(courseLabel, roomLabel);
|
examBox.getChildren().addAll(courseLabel, roomLabel);
|
||||||
|
|
||||||
// Hover highlight
|
// Hover highlight
|
||||||
examBox.setOnMouseEntered(e -> examBox
|
examBox.setOnMouseEntered(e -> {
|
||||||
.setStyle("-fx-background-color: #2980b9; -fx-background-radius: 3; -fx-padding: 3 6;"));
|
if (draggedExam == null) {
|
||||||
examBox.setOnMouseExited(e -> examBox
|
examBox.setStyle("-fx-background-color: #2980b9; -fx-background-radius: 3; -fx-padding: 3 6;");
|
||||||
.setStyle("-fx-background-color: #3498db; -fx-background-radius: 3; -fx-padding: 3 6;"));
|
}
|
||||||
|
});
|
||||||
|
examBox.setOnMouseExited(e -> {
|
||||||
|
if (draggedExam == null) {
|
||||||
|
examBox.setStyle("-fx-background-color: #3498db; -fx-background-radius: 3; -fx-padding: 3 6;");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Click opens the edit dialog (which also shows details)
|
// Click opens the edit dialog (which also shows details)
|
||||||
examBox.setOnMouseClicked(e -> openEditDialog(exam));
|
examBox.setOnMouseClicked(e -> {
|
||||||
|
if (e.getClickCount() == 1 && draggedExam == null) {
|
||||||
|
openEditDialog(exam);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DRAG SOURCE - Start drag on exam box
|
||||||
|
examBox.setOnDragDetected(e -> {
|
||||||
|
draggedExam = exam;
|
||||||
|
Dragboard db = examBox.startDragAndDrop(TransferMode.MOVE);
|
||||||
|
ClipboardContent content = new ClipboardContent();
|
||||||
|
content.put(EXAM_DATA_FORMAT, exam.getCourseCode());
|
||||||
|
db.setContent(content);
|
||||||
|
|
||||||
|
// Visual feedback - make the dragged box semi-transparent
|
||||||
|
examBox.setOpacity(0.5);
|
||||||
|
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
examBox.setOnDragDone(e -> {
|
||||||
|
draggedExam = null;
|
||||||
|
examBox.setOpacity(1.0);
|
||||||
|
// Refresh grid to reset all cell colors
|
||||||
|
if (currentSchedule != null && currentConfig != null) {
|
||||||
|
displayScheduleGrid(currentSchedule, currentConfig);
|
||||||
|
}
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
|
||||||
// Tooltip with hint
|
// Tooltip with hint
|
||||||
Tooltip tooltip = new Tooltip(
|
Tooltip tooltip = new Tooltip(
|
||||||
exam.getCourseCode() + "\n" + exam.getClassroomId() + "\nClick to view/edit");
|
exam.getCourseCode() + "\n" + exam.getClassroomId() + "\nDrag to move, click to edit");
|
||||||
Tooltip.install(examBox, tooltip);
|
Tooltip.install(examBox, tooltip);
|
||||||
|
|
||||||
cell.getChildren().add(examBox);
|
cell.getChildren().add(examBox);
|
||||||
@@ -509,9 +562,116 @@ public class ScheduleCalendarController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DROP TARGET - Set up cell as drop target
|
||||||
|
setupDropTarget(cell, day, slot);
|
||||||
|
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a cell as a drop target for drag-and-drop operations.
|
||||||
|
*/
|
||||||
|
private void setupDropTarget(VBox cell, int targetDay, int targetSlot) {
|
||||||
|
// Accept drag over this cell
|
||||||
|
cell.setOnDragOver(e -> {
|
||||||
|
if (e.getGestureSource() != cell && draggedExam != null && e.getDragboard().hasContent(EXAM_DATA_FORMAT)) {
|
||||||
|
e.acceptTransferModes(TransferMode.MOVE);
|
||||||
|
}
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Visual feedback when dragging over
|
||||||
|
cell.setOnDragEntered(e -> {
|
||||||
|
if (e.getGestureSource() != cell && draggedExam != null && e.getDragboard().hasContent(EXAM_DATA_FORMAT)) {
|
||||||
|
// Validate if this drop would be valid
|
||||||
|
String classroomId = draggedExam.getClassroomId();
|
||||||
|
ConstraintValidationService.ValidationResult result = validationService.validateAssignment(
|
||||||
|
draggedExam.getCourseCode(),
|
||||||
|
targetDay, targetSlot, classroomId, currentSchedule);
|
||||||
|
|
||||||
|
if (result.isValid()) {
|
||||||
|
// Valid drop zone - green
|
||||||
|
cell.setStyle("-fx-background-color: #d5f5e3; -fx-border-color: #27ae60; -fx-border-width: 2;");
|
||||||
|
} else {
|
||||||
|
// Invalid drop zone - red
|
||||||
|
cell.setStyle("-fx-background-color: #fadbd8; -fx-border-color: #e74c3c; -fx-border-width: 2;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset style when drag exits
|
||||||
|
cell.setOnDragExited(e -> {
|
||||||
|
// Reset to default style
|
||||||
|
cell.setStyle("-fx-background-color: #ecf0f1; -fx-border-color: #bdc3c7; -fx-border-width: 1;");
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle the drop
|
||||||
|
cell.setOnDragDropped(e -> {
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
if (draggedExam != null && e.getDragboard().hasContent(EXAM_DATA_FORMAT)) {
|
||||||
|
String classroomId = draggedExam.getClassroomId();
|
||||||
|
|
||||||
|
// Validate the drop
|
||||||
|
ConstraintValidationService.ValidationResult result = validationService.validateAssignment(
|
||||||
|
draggedExam.getCourseCode(),
|
||||||
|
targetDay, targetSlot, classroomId, currentSchedule);
|
||||||
|
|
||||||
|
if (result.isValid()) {
|
||||||
|
// Perform the move
|
||||||
|
success = performExamMove(draggedExam.getCourseCode(), targetDay, targetSlot, classroomId, false);
|
||||||
|
} else {
|
||||||
|
// Show confirmation for invalid drop
|
||||||
|
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
|
confirm.setTitle("Constraint Violation");
|
||||||
|
confirm.setHeaderText("Move would violate constraints");
|
||||||
|
confirm.setContentText(result.getFormattedMessage() + "\n\nDo you want to move anyway?");
|
||||||
|
confirm.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
|
||||||
|
|
||||||
|
Optional<ButtonType> confirmResult = confirm.showAndWait();
|
||||||
|
if (confirmResult.isPresent() && confirmResult.get() == ButtonType.YES) {
|
||||||
|
success = performExamMove(draggedExam.getCourseCode(), targetDay, targetSlot, classroomId,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.setDropCompleted(success);
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an exam move operation and refreshes the display.
|
||||||
|
*/
|
||||||
|
private boolean performExamMove(String courseCode, int newDay, int newSlot, String classroomId, boolean forced) {
|
||||||
|
// Update the ScheduleState
|
||||||
|
boolean updated = currentSchedule.updateAssignment(courseCode, newDay, newSlot, classroomId);
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
// Also update the Course in DataManager
|
||||||
|
Course course = dataManager.getCourse(courseCode);
|
||||||
|
if (course != null) {
|
||||||
|
course.setExamSchedule(newDay, newSlot, classroomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update status
|
||||||
|
String message = forced ? "⚠️ Exam moved (with override)" : "✓ Exam moved successfully";
|
||||||
|
statusLabel.setText(message);
|
||||||
|
statusLabel.setStyle(forced ? "-fx-text-fill: #f39c12;" : "-fx-text-fill: #27ae60;");
|
||||||
|
|
||||||
|
// Log the change
|
||||||
|
System.out.println("Exam moved: " + courseCode + " -> Day " + (newDay + 1) +
|
||||||
|
", Slot " + (newSlot + 1) + ", Room " + classroomId +
|
||||||
|
(forced ? " (forced)" : ""));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void onCancelGeneration() {
|
private void onCancelGeneration() {
|
||||||
if (generatorService != null) {
|
if (generatorService != null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user