nearly done, still needs a bit of work
This commit is contained in:
parent
a659e1217c
commit
be2d918620
@ -165,6 +165,7 @@ public class UserRepository {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static <T extends User> List<T> findAll(
|
||||
Path dir, Class<T> type, String lockPrefix) throws Exception {
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.Dragboard;
|
||||
@ -261,27 +260,42 @@ public class LoginController {
|
||||
private void navigateToUserUI(User user) {
|
||||
try {
|
||||
|
||||
if (user instanceof Organizer organizer) {
|
||||
|
||||
if (user instanceof Organizer) {
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource("/fxml/OrganizerView.fxml"));
|
||||
Parent root = loader.load();
|
||||
Scene oldScene = stage.getScene();
|
||||
Scene scene = new Scene(loader.load(),
|
||||
oldScene.getWidth(),
|
||||
oldScene.getHeight()
|
||||
);
|
||||
OrganizerController controller = loader.getController();
|
||||
controller.setOrganizer(organizer);
|
||||
controller.setOrganizer((Organizer) user);
|
||||
controller.setStage(stage);
|
||||
stage.setScene(new Scene(root));
|
||||
|
||||
} else if (user instanceof Voter voter) {
|
||||
stage.setScene(scene);
|
||||
stage.setTitle(((Organizer) user).getName());
|
||||
|
||||
|
||||
} else if (user instanceof Voter) {
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource("/fxml/VoterView.fxml"));
|
||||
Parent root = loader.load();
|
||||
Scene oldScene = stage.getScene();
|
||||
Scene scene = new Scene(loader.load(),
|
||||
oldScene.getWidth(),
|
||||
oldScene.getHeight()
|
||||
);
|
||||
VoterController controller = loader.getController();
|
||||
controller.setVoter(voter);
|
||||
controller.setVoter((Voter) user);
|
||||
controller.setStage(stage);
|
||||
stage.setScene(new Scene(root));
|
||||
|
||||
stage.setScene(scene);
|
||||
stage.setTitle(((Voter) user).getName());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
showError("Failed to load UI: " + e.getMessage());
|
||||
System.out.println(e);
|
||||
loginButton.setDisable(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,432 @@
|
||||
package dev.ksan.ui;
|
||||
|
||||
import dev.ksan.CASystem.KeystoreService;
|
||||
import dev.ksan.Users.Organizer;
|
||||
import dev.ksan.voteSystem.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class OrganizerController {
|
||||
private Organizer organizer;
|
||||
|
||||
|
||||
@FXML
|
||||
private Label usernameLabel;
|
||||
|
||||
@FXML private Label errorLabel;
|
||||
|
||||
@FXML private VBox panesBox;
|
||||
|
||||
@FXML
|
||||
private TitledPane activeList;
|
||||
|
||||
|
||||
|
||||
@FXML
|
||||
private TitledPane inactiveList;
|
||||
@FXML
|
||||
private ListView<Poll> activePollsListView;
|
||||
@FXML
|
||||
private ListView<Poll> inactivePollsListView;
|
||||
@FXML
|
||||
private Button newButton;
|
||||
|
||||
@FXML
|
||||
private StackPane centerPane;
|
||||
|
||||
@FXML
|
||||
private TextField descriptionField;
|
||||
@FXML
|
||||
private TextField titleField;
|
||||
|
||||
@FXML
|
||||
private DatePicker startDatePicker;
|
||||
@FXML
|
||||
private DatePicker endDatePicker;
|
||||
@FXML
|
||||
private Button confirmButton;
|
||||
@FXML
|
||||
private Button newOptionButton;
|
||||
|
||||
@FXML
|
||||
private TextField startTimeField;
|
||||
@FXML
|
||||
private TextField endTimeField;
|
||||
@FXML
|
||||
private VBox optionsContainer;
|
||||
|
||||
private Stage stage;
|
||||
|
||||
private final List<TextField> optionFields = new ArrayList<>();
|
||||
|
||||
public void setOrganizer(Organizer organizer) {
|
||||
this.organizer = organizer;
|
||||
usernameLabel.setText(organizer.getName());
|
||||
|
||||
loadPolls();
|
||||
}
|
||||
|
||||
public void setStage(Stage stage) {
|
||||
this.stage = stage;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void initialize() {
|
||||
errorLabel.setVisible(false);
|
||||
confirmButton.setOnAction(e -> createNewPoll());
|
||||
newOptionButton.setOnAction(e -> addOptionField());
|
||||
newButton.setOnAction(e -> showCreatePollForm());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void addOptionField() {
|
||||
if (optionFields.size() >= 5) return; // max 5 options
|
||||
|
||||
HBox optionBox = new HBox(10);
|
||||
Text label = new Text("option " + (optionFields.size() + 1) + ":");
|
||||
TextField textField = new TextField();
|
||||
|
||||
optionBox.getChildren().addAll(label, textField);
|
||||
optionsContainer.getChildren().add(optionBox);
|
||||
|
||||
optionFields.add(textField);
|
||||
}
|
||||
|
||||
private void createNewPoll() {
|
||||
clearError();
|
||||
|
||||
if (titleField.getText() == null || titleField.getText().isBlank()) {
|
||||
showError("Title is required."); return;
|
||||
}
|
||||
if (descriptionField.getText() == null || descriptionField.getText().isBlank()) {
|
||||
showError("Description is required."); return;
|
||||
}
|
||||
if (startDatePicker.getValue() == null) {
|
||||
showError("Start date is required."); return;
|
||||
}
|
||||
if (endDatePicker.getValue() == null) {
|
||||
showError("End date is required."); return;
|
||||
}
|
||||
if (startTimeField.getText() == null || startTimeField.getText().isBlank()) {
|
||||
showError("Start time is required."); return;
|
||||
}
|
||||
if (endTimeField.getText() == null || endTimeField.getText().isBlank()) {
|
||||
showError("End time is required."); return;
|
||||
}
|
||||
|
||||
LocalTime startTime = parseTime(startTimeField.getText());
|
||||
LocalTime endTime = parseTime(endTimeField.getText());
|
||||
|
||||
if (startTime == null) {
|
||||
showError("Invalid start time. Use HH:MM format."); return;
|
||||
}
|
||||
if (endTime == null) {
|
||||
showError("Invalid end time. Use HH:MM format."); return;
|
||||
}
|
||||
|
||||
LocalDateTime startDateTime = LocalDateTime.of(startDatePicker.getValue(), startTime);
|
||||
LocalDateTime endDateTime = LocalDateTime.of(endDatePicker.getValue(), endTime);
|
||||
|
||||
if (startDateTime.isBefore(LocalDateTime.now())) {
|
||||
showError("Start time cannot be in the past."); return;
|
||||
}
|
||||
if (!endDateTime.isAfter(startDateTime)) {
|
||||
showError("End time must be after start time."); return;
|
||||
}
|
||||
|
||||
List<String> optionTexts = optionFields.stream()
|
||||
.map(tf -> tf.getText().trim())
|
||||
.filter(t -> !t.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (optionTexts.size() < 2) {
|
||||
showError("At least 2 options are required."); return;
|
||||
}
|
||||
|
||||
long distinctCount = optionTexts.stream().distinct().count();
|
||||
if (distinctCount != optionTexts.size()) {
|
||||
showError("Options must be unique."); return;
|
||||
}
|
||||
|
||||
confirmButton.setDisable(true);
|
||||
|
||||
Task<Poll> task = new Task<>() {
|
||||
@Override
|
||||
protected Poll call() throws Exception {
|
||||
Poll poll = new Poll(
|
||||
organizer.getId(),
|
||||
titleField.getText().trim(),
|
||||
descriptionField.getText().trim(),
|
||||
startDateTime,
|
||||
endDateTime
|
||||
);
|
||||
|
||||
for (String text : optionTexts) {
|
||||
poll.addOption(new PollOption(text));
|
||||
}
|
||||
|
||||
PollRepository.save(poll);
|
||||
return poll;
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
Poll created = task.getValue();
|
||||
System.out.println("Poll created: " + created.getTitle());
|
||||
confirmButton.setDisable(false);
|
||||
clearForm();
|
||||
loadPolls(); // refresh the poll lists
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> {
|
||||
showError("Failed to create poll: " + task.getException().getMessage());
|
||||
confirmButton.setDisable(false);
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
private void clearForm() {
|
||||
titleField.clear();
|
||||
descriptionField.clear();
|
||||
startDatePicker.setValue(null);
|
||||
endDatePicker.setValue(null);
|
||||
startTimeField.clear();
|
||||
endTimeField.clear();
|
||||
optionsContainer.getChildren().clear();
|
||||
optionFields.clear();
|
||||
clearError();
|
||||
}
|
||||
|
||||
|
||||
private void loadPolls() {
|
||||
Task<List<Poll>> task = new Task<>() {
|
||||
@Override
|
||||
protected List<Poll> call() throws Exception {
|
||||
return PollRepository.findByOrganizer(organizer.getId());
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
|
||||
List<Poll> polls = task.getValue();
|
||||
|
||||
List<Poll> active = polls.stream()
|
||||
.filter(p -> p.getStatus() == PollStatus.ACTIVE)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Poll> inactive = polls.stream()
|
||||
.filter(p -> p.getStatus() != PollStatus.ACTIVE)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
activePollsListView.setItems(FXCollections.observableArrayList(active));
|
||||
inactivePollsListView.setItems(FXCollections.observableArrayList(inactive));
|
||||
activePollsListView.setCellFactory(lv -> new PollListCell());
|
||||
inactivePollsListView.setCellFactory(lv -> new PollListCell());
|
||||
});
|
||||
|
||||
task.setOnFailed(e ->
|
||||
showError("Failed to load polls: " + task.getException().getMessage()));
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
private class PollListCell extends ListCell<Poll> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Poll poll, boolean empty) {
|
||||
super.updateItem(poll, empty);
|
||||
|
||||
if (empty || poll == null) {
|
||||
setGraphic(null);
|
||||
return;
|
||||
}
|
||||
|
||||
VBox card = new VBox(4);
|
||||
card.setPadding(new Insets(8));
|
||||
|
||||
Label title = new Label(poll.getTitle());
|
||||
title.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
|
||||
|
||||
Label period = new Label(
|
||||
poll.getStartTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
|
||||
+ " → " +
|
||||
poll.getEndTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
|
||||
);
|
||||
period.setStyle("-fx-text-fill: #666; -fx-font-size: 11;");
|
||||
|
||||
Label status = new Label(formatStatus(poll));
|
||||
status.setStyle("-fx-font-size: 11; -fx-text-fill: " + statusColor(poll) + ";");
|
||||
|
||||
card.getChildren().addAll(title, period, status);
|
||||
|
||||
HBox buttons = new HBox(8);
|
||||
|
||||
if (poll.isReadyForTally()) {
|
||||
Button tallyBtn = new Button("Count Votes");
|
||||
tallyBtn.setStyle("-fx-background-color: #4A90D9; -fx-text-fill: white;");
|
||||
tallyBtn.setOnAction(e -> handleTally(poll));
|
||||
buttons.getChildren().add(tallyBtn);
|
||||
}
|
||||
|
||||
if (poll.isTallied()) {
|
||||
Button resultsBtn = new Button("View Results");
|
||||
resultsBtn.setOnAction(e -> handleViewResults(poll));
|
||||
buttons.getChildren().add(resultsBtn);
|
||||
}
|
||||
|
||||
if (!buttons.getChildren().isEmpty()) {
|
||||
card.getChildren().add(buttons);
|
||||
}
|
||||
|
||||
setGraphic(card);
|
||||
}
|
||||
|
||||
private void handleViewResults(Poll poll) {
|
||||
Task<TallyResult> task = new Task<>() {
|
||||
@Override
|
||||
protected TallyResult call() throws Exception {
|
||||
return TallyResultRepository.findByPoll(poll.getPollId());
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
TallyResult result = task.getValue();
|
||||
if (result == null) { showError("Results not found."); return; }
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle("Results — " + poll.getTitle());
|
||||
alert.setHeaderText(null);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
result.getResultsByOption().forEach((option, count) ->
|
||||
sb.append(option).append(": ").append(count).append(" votes\n"));
|
||||
sb.append("\nValid: ").append(result.getValidVotes());
|
||||
sb.append("\nInvalid: ").append(result.getInvalidVotes());
|
||||
|
||||
alert.setContentText(sb.toString());
|
||||
alert.showAndWait();
|
||||
});
|
||||
|
||||
task.setOnFailed(e ->
|
||||
showError("Could not load results: " + task.getException().getMessage()));
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
|
||||
private String formatStatus(Poll poll) {
|
||||
return switch (poll.getStatus()) {
|
||||
case PENDING -> "⏳ Pending";
|
||||
case ACTIVE -> "✅ Active";
|
||||
case FINISHED -> poll.isTallied() ? "📊 Tallied" : "🔒 Finished";
|
||||
};
|
||||
}
|
||||
|
||||
private String statusColor(Poll poll) {
|
||||
return switch (poll.getStatus()) {
|
||||
case PENDING -> "#F5A623";
|
||||
case ACTIVE -> "#7ED321";
|
||||
case FINISHED -> "#9B9B9B";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTally(Poll poll) {
|
||||
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setTitle("Tally Votes");
|
||||
dialog.setHeaderText("Enter your password to decrypt and count votes.");
|
||||
dialog.setContentText("Password:");
|
||||
|
||||
PasswordField pf = new PasswordField();
|
||||
dialog.getDialogPane().setContent(pf);
|
||||
|
||||
dialog.showAndWait().ifPresent(ignored -> {
|
||||
String password = pf.getText();
|
||||
if (password.isBlank()) return;
|
||||
|
||||
Task<TallyResult> task = new Task<>() {
|
||||
@Override
|
||||
protected TallyResult call() throws Exception {
|
||||
char[] ksPassword = KeystoreService.deriveKeystorePassword(
|
||||
password, organizer.getId());
|
||||
PrivateKey privateKey = KeystoreService.getPrivateKey(
|
||||
organizer.getId(), ksPassword);
|
||||
return TallyService.tallyPoll(poll, organizer, privateKey);
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
TallyResult result = task.getValue();
|
||||
result.printReport();
|
||||
loadPolls();
|
||||
});
|
||||
|
||||
task.setOnFailed(e ->
|
||||
showError("Tally failed: " + task.getException().getMessage()));
|
||||
|
||||
new Thread(task).start();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static LocalTime parseTime(String input) {
|
||||
if (input == null || input.trim().isEmpty()) return null;
|
||||
|
||||
input = input.trim().replaceAll("\\s+", ":"); // "7 30" to "7:30"
|
||||
|
||||
String[] parts = input.split(":");
|
||||
|
||||
try {
|
||||
int hours = Integer.parseInt(parts[0]);
|
||||
int minutes = (parts.length > 1) ? Integer.parseInt(parts[1]) : 0;
|
||||
|
||||
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LocalTime.of(hours, minutes);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void showError(String msg) {
|
||||
Platform.runLater(() -> {
|
||||
errorLabel.setText(msg);
|
||||
errorLabel.setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
private void clearError() {
|
||||
errorLabel.setText("");
|
||||
errorLabel.setVisible(false);
|
||||
}
|
||||
private void showCreatePollForm() {
|
||||
centerPane.setVisible(true);
|
||||
clearForm();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
5
src/main/java/dev/ksan/ui/PollListCell.java
Normal file
5
src/main/java/dev/ksan/ui/PollListCell.java
Normal file
@ -0,0 +1,5 @@
|
||||
package dev.ksan.ui;
|
||||
|
||||
|
||||
public class PollListCell {
|
||||
}
|
||||
@ -1,17 +1,440 @@
|
||||
package dev.ksan.ui;
|
||||
|
||||
import dev.ksan.CASystem.KeystoreService;
|
||||
import dev.ksan.Users.Organizer;
|
||||
import dev.ksan.Users.UserRepository;
|
||||
import dev.ksan.Users.Voter;
|
||||
import dev.ksan.voteSystem.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class VoterController {
|
||||
private Voter voter;
|
||||
private Stage stage;
|
||||
private Poll selectedPoll;
|
||||
@FXML
|
||||
private Label usernameLabel;
|
||||
|
||||
@FXML
|
||||
private Label alreadyVotedLabel;
|
||||
@FXML
|
||||
private TitledPane activeList;
|
||||
|
||||
@FXML
|
||||
private TitledPane inactiveList;
|
||||
|
||||
@FXML
|
||||
private StackPane centerPane;
|
||||
|
||||
@FXML
|
||||
private Text titleText;
|
||||
|
||||
@FXML
|
||||
private Button refreshButton;
|
||||
@FXML
|
||||
private Text descriptionText;
|
||||
|
||||
@FXML
|
||||
private Text startDateText;
|
||||
@FXML
|
||||
private Text endDateText;
|
||||
|
||||
@FXML
|
||||
private Button confirmButton;
|
||||
|
||||
@FXML
|
||||
private Label errorLabel;
|
||||
|
||||
@FXML
|
||||
private VBox optionsContainer;
|
||||
|
||||
private final ToggleGroup optionToggleGroup = new ToggleGroup();
|
||||
|
||||
@FXML private VBox panesBox;
|
||||
|
||||
|
||||
@FXML
|
||||
private ListView<Poll> activePollsListView;
|
||||
@FXML
|
||||
private ListView<Poll> inactivePollsListView;
|
||||
|
||||
private final List<TextField> optionFields = new ArrayList<>();
|
||||
|
||||
|
||||
public void setVoter(Voter voter) {
|
||||
this.voter = voter;
|
||||
loadPolls();
|
||||
}
|
||||
|
||||
public void refreshPolls() {
|
||||
loadPolls();
|
||||
}
|
||||
|
||||
public void setStage(Stage stage) {
|
||||
this.stage = stage;
|
||||
|
||||
usernameLabel.setText(voter.getName());
|
||||
|
||||
confirmButton.setOnAction(e -> castVote());
|
||||
activePollsListView.getSelectionModel().selectedItemProperty()
|
||||
.addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal != null) showPollDetail(newVal);
|
||||
});
|
||||
|
||||
// Inactive polls are view-only — just show detail with no voting
|
||||
inactivePollsListView.getSelectionModel().selectedItemProperty()
|
||||
.addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal != null) showPollDetail(newVal);
|
||||
});
|
||||
|
||||
refreshButton.setOnAction(e -> refreshPolls());
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
errorLabel.setVisible(false);
|
||||
centerPane.setVisible(false);
|
||||
alreadyVotedLabel.setVisible(false);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void castVote() {
|
||||
clearError();
|
||||
|
||||
if (selectedPoll == null) return;
|
||||
|
||||
Toggle selected = optionToggleGroup.getSelectedToggle();
|
||||
if (selected == null) {
|
||||
showError("Please select an option before confirming.");
|
||||
return;
|
||||
}
|
||||
|
||||
String selectedOptionId = (String) selected.getUserData();
|
||||
|
||||
Dialog<String> passwordDialog = buildPasswordDialog();
|
||||
passwordDialog.showAndWait().ifPresent(password -> {
|
||||
if (password.isBlank()) {
|
||||
showError("Password is required to sign your vote.");
|
||||
return;
|
||||
}
|
||||
|
||||
confirmButton.setDisable(true);
|
||||
|
||||
Task<Vote> task = new Task<>() {
|
||||
@Override
|
||||
protected Vote call() throws Exception {
|
||||
|
||||
char[] ksPassword = KeystoreService.deriveKeystorePassword(
|
||||
password, voter.getUsername());
|
||||
PrivateKey voterPrivateKey = KeystoreService.getPrivateKey(
|
||||
voter.getUsername(), ksPassword);
|
||||
|
||||
Organizer organizer = (Organizer) UserRepository
|
||||
.findOrganizer(selectedPoll.getOrganizerId());
|
||||
if (organizer == null)
|
||||
throw new IllegalStateException("Organizer not found.");
|
||||
|
||||
PublicKey organizerPublicKey = organizer.getCertificate().getPublicKey();
|
||||
|
||||
return VotingService.castVote(
|
||||
selectedOptionId,
|
||||
selectedPoll,
|
||||
voter,
|
||||
voterPrivateKey,
|
||||
organizerPublicKey
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
showSuccess("");
|
||||
loadPolls(); // refresh list
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> {
|
||||
task.getException().printStackTrace();
|
||||
showError("Vote failed: " + task.getException().getMessage());
|
||||
confirmButton.setDisable(false);
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void loadPolls() {
|
||||
Task<List<Poll>> task = new Task<>() {
|
||||
@Override
|
||||
protected List<Poll> call() throws Exception {
|
||||
return PollRepository.findAll();
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
List<Poll> polls = task.getValue();
|
||||
|
||||
List<Poll> active = polls.stream()
|
||||
.filter(p -> p.getStatus() == PollStatus.ACTIVE)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Poll> inactive = polls.stream()
|
||||
.filter(p -> p.getStatus() != PollStatus.ACTIVE)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
activePollsListView.setItems(FXCollections.observableArrayList(active));
|
||||
inactivePollsListView.setItems(FXCollections.observableArrayList(inactive));
|
||||
|
||||
activePollsListView.setCellFactory(lv -> new PollListCell());
|
||||
inactivePollsListView.setCellFactory(lv -> new PollListCell());
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> {
|
||||
task.getException().printStackTrace();
|
||||
showError("Failed to load polls: " + task.getException().getMessage());
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
private Dialog<String> buildPasswordDialog() {
|
||||
Dialog<String> dialog = new Dialog<>();
|
||||
dialog.setTitle("Confirm Vote");
|
||||
dialog.setHeaderText("Enter your password to sign and submit your vote.");
|
||||
|
||||
PasswordField pf = new PasswordField();
|
||||
pf.setPromptText("Password");
|
||||
|
||||
VBox content = new VBox(8);
|
||||
content.getChildren().addAll(new Label("Password:"), pf);
|
||||
dialog.getDialogPane().setContent(content);
|
||||
|
||||
ButtonType confirmType = new ButtonType("Submit Vote", ButtonBar.ButtonData.OK_DONE);
|
||||
dialog.getDialogPane().getButtonTypes().addAll(confirmType, ButtonType.CANCEL);
|
||||
|
||||
dialog.setResultConverter(btn ->
|
||||
btn == confirmType ? pf.getText() : null);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public void showPoll(Poll poll) {
|
||||
centerPane.getChildren().clear();
|
||||
|
||||
VBox mainBox = new VBox(10);
|
||||
mainBox.setPadding(new Insets(20));
|
||||
mainBox.setAlignment(Pos.TOP_LEFT);
|
||||
|
||||
Label titleLabel = new Label("Title: " + poll.getTitle());
|
||||
titleLabel.setStyle("-fx-font-size: 18px; -fx-font-weight: bold;");
|
||||
|
||||
Label descLabel = new Label("Description: " + poll.getDescription());
|
||||
descLabel.setWrapText(true);
|
||||
|
||||
Label timeLabel = new Label(
|
||||
"Start: " + poll.getStartTime() + "\nEnd: " + poll.getEndTime()
|
||||
);
|
||||
|
||||
ToggleGroup toggleGroup = new ToggleGroup();
|
||||
VBox optionsBox = new VBox(5);
|
||||
for (PollOption option : poll.getOptions()) {
|
||||
RadioButton rb = new RadioButton(option.getText());
|
||||
rb.setToggleGroup(toggleGroup);
|
||||
optionsBox.getChildren().add(rb);
|
||||
}
|
||||
|
||||
Button confirmBtn = new Button("Confirm");
|
||||
confirmBtn.setOnAction(e -> {
|
||||
RadioButton selected = (RadioButton) toggleGroup.getSelectedToggle();
|
||||
if (selected != null) {
|
||||
System.out.println("Voted for: " + selected.getText());
|
||||
// TODO vote logic
|
||||
} else {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING, "Please select an option.");
|
||||
alert.showAndWait();
|
||||
}
|
||||
});
|
||||
|
||||
HBox confirmBox = new HBox(confirmBtn);
|
||||
confirmBox.setAlignment(Pos.BOTTOM_RIGHT);
|
||||
|
||||
mainBox.getChildren().addAll(titleLabel, descLabel, timeLabel, optionsBox, confirmBox);
|
||||
|
||||
centerPane.getChildren().add(mainBox);
|
||||
}
|
||||
|
||||
private void showPollDetail(Poll poll) {
|
||||
selectedPoll = poll;
|
||||
clearError();
|
||||
|
||||
titleText.setText(poll.getTitle());
|
||||
|
||||
descriptionText.setText(poll.getDescription());
|
||||
|
||||
startDateText.setText(poll.getStartTime().toString());
|
||||
|
||||
endDateText.setText(poll.getEndTime().toString());
|
||||
|
||||
|
||||
optionsContainer.getChildren().clear();
|
||||
optionToggleGroup.getToggles().clear();
|
||||
|
||||
for (PollOption option : poll.getOptions()) {
|
||||
RadioButton rb = new RadioButton(option.getText());
|
||||
rb.setToggleGroup(optionToggleGroup);
|
||||
rb.setUserData(option.getOptionId());
|
||||
optionsContainer.getChildren().add(rb);
|
||||
}
|
||||
|
||||
boolean hasVoted = VotingService.hasVoted(voter.getUsername(), poll.getPollId());
|
||||
|
||||
confirmButton.setVisible(poll.getStatus() == PollStatus.ACTIVE && !hasVoted);
|
||||
confirmButton.setDisable(hasVoted);
|
||||
|
||||
alreadyVotedLabel.setVisible(hasVoted);
|
||||
|
||||
// If already voted show verify button instead
|
||||
if (hasVoted) {
|
||||
showVerifyButton(poll);
|
||||
}
|
||||
|
||||
centerPane.setVisible(true);
|
||||
}
|
||||
|
||||
private void showVerifyButton(Poll poll) {
|
||||
// Remove old verify button if present
|
||||
optionsContainer.getChildren().removeIf(
|
||||
node -> node instanceof Button &&
|
||||
((Button) node).getText().equals("Verify My Vote"));
|
||||
|
||||
Button verifyBtn = new Button("Verify My Vote");
|
||||
verifyBtn.setStyle("-fx-background-color: #7ED321; -fx-text-fill: white;");
|
||||
verifyBtn.setOnAction(e -> verifyVote(poll));
|
||||
optionsContainer.getChildren().add(verifyBtn);
|
||||
}
|
||||
|
||||
private void verifyVote(Poll poll) {
|
||||
Task<Boolean> task = new Task<>() {
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
return VotingService.verifyVote(voter.getUsername(), poll.getPollId());
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(e -> {
|
||||
boolean verified = task.getValue();
|
||||
Alert alert = new Alert(
|
||||
verified ? Alert.AlertType.INFORMATION : Alert.AlertType.WARNING);
|
||||
alert.setTitle("Vote Verification");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(verified
|
||||
? "✅ Your vote is recorded and its integrity is verified."
|
||||
: "⚠️ Could not verify your vote. Please contact the organizer.");
|
||||
alert.showAndWait();
|
||||
});
|
||||
|
||||
task.setOnFailed(e -> showError("Verification failed: "
|
||||
+ task.getException().getMessage()));
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private class PollListCell extends ListCell<Poll> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Poll poll, boolean empty) {
|
||||
super.updateItem(poll, empty);
|
||||
|
||||
if (empty || poll == null) {
|
||||
setGraphic(null);
|
||||
return;
|
||||
}
|
||||
|
||||
VBox card = new VBox(4);
|
||||
card.setPadding(new Insets(8));
|
||||
|
||||
Label title = new Label(poll.getTitle());
|
||||
title.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
|
||||
|
||||
Label period = new Label(
|
||||
poll.getStartTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
|
||||
+ " → " +
|
||||
poll.getEndTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
|
||||
);
|
||||
period.setStyle("-fx-text-fill: #666; -fx-font-size: 11;");
|
||||
|
||||
Label status = new Label(formatStatus(poll));
|
||||
status.setStyle("-fx-text-fill: " + statusColor(poll) + "; -fx-font-size: 11;");
|
||||
|
||||
boolean hasVoted = VotingService.hasVoted(voter.getUsername(), poll.getPollId());
|
||||
if (hasVoted) {
|
||||
Label votedBadge = new Label("✅ Voted");
|
||||
votedBadge.setStyle("-fx-text-fill: #7ED321; -fx-font-size: 10;");
|
||||
card.getChildren().addAll(title, period, status, votedBadge);
|
||||
} else {
|
||||
card.getChildren().addAll(title, period, status);
|
||||
}
|
||||
|
||||
setGraphic(card);
|
||||
}
|
||||
|
||||
private String formatStatus(Poll poll) {
|
||||
return switch (poll.getStatus()) {
|
||||
case PENDING -> "⏳ Pending";
|
||||
case ACTIVE -> "✅ Active";
|
||||
case FINISHED -> "🔒 Finished";
|
||||
};
|
||||
}
|
||||
|
||||
private String statusColor(Poll poll) {
|
||||
return switch (poll.getStatus()) {
|
||||
case PENDING -> "#F5A623";
|
||||
case ACTIVE -> "#7ED321";
|
||||
case FINISHED -> "#9B9B9B";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void showError(String msg) {
|
||||
Platform.runLater(() -> {
|
||||
errorLabel.setText("⚠️ " + msg);
|
||||
errorLabel.setStyle("-fx-text-fill: red;");
|
||||
errorLabel.setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
private void showSuccess(String msg) {
|
||||
Platform.runLater(() -> {
|
||||
errorLabel.setText(msg);
|
||||
errorLabel.setStyle("-fx-text-fill: green;");
|
||||
errorLabel.setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
private void clearError() {
|
||||
errorLabel.setText("");
|
||||
errorLabel.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
@ -32,6 +33,8 @@ public class PollRepository {
|
||||
writeLock.lock();
|
||||
try {
|
||||
writeToFile(poll, resolvePath(poll.getPollId()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
@ -63,6 +66,8 @@ public class PollRepository {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@ -101,23 +106,28 @@ public class PollRepository {
|
||||
|
||||
private static void writeToFile(Object obj, Path targetPath) throws Exception {
|
||||
Path tmpPath = targetPath.resolveSibling(targetPath.getFileName() + ".tmp");
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(tmpPath.toFile());
|
||||
FileLock fileLock = fos.getChannel().lock();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
|
||||
FileChannel channel = fos.getChannel()) {
|
||||
|
||||
try (FileLock fileLock = channel.lock()) {
|
||||
ObjectOutputStream oos = new ObjectOutputStream(fos);
|
||||
oos.writeObject(obj);
|
||||
oos.flush();
|
||||
channel.force(true);
|
||||
}
|
||||
}
|
||||
|
||||
Files.move(tmpPath, targetPath,
|
||||
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
StandardCopyOption.ATOMIC_MOVE,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
private static Object deserialize(Path path) throws Exception {
|
||||
if (!Files.exists(path)) return null;
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(path.toFile());
|
||||
FileLock fileLock = fis.getChannel().tryLock(0, Long.MAX_VALUE, true);
|
||||
ObjectInputStream ois = new ObjectInputStream(fis)) {
|
||||
if (fileLock == null)
|
||||
throw new IllegalStateException("Could not acquire read lock on: " + path);
|
||||
return ois.readObject();
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,23 +93,29 @@ public class VoteRepository {
|
||||
|
||||
private static void writeToFile(Object obj, Path targetPath) throws Exception {
|
||||
Path tmpPath = targetPath.resolveSibling(targetPath.getFileName() + ".tmp");
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(tmpPath.toFile());
|
||||
FileLock fileLock = fos.getChannel().lock();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
|
||||
|
||||
FileLock lock = fos.getChannel().lock();
|
||||
try {
|
||||
oos.writeObject(obj);
|
||||
oos.flush();
|
||||
} finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
Files.move(tmpPath, targetPath,
|
||||
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
StandardCopyOption.ATOMIC_MOVE,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
private static Object deserialize(Path path) throws Exception {
|
||||
if (!Files.exists(path)) return null;
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(path.toFile());
|
||||
FileLock fileLock = fis.getChannel().tryLock(0, Long.MAX_VALUE, true);
|
||||
ObjectInputStream ois = new ObjectInputStream(fis)) {
|
||||
if (fileLock == null)
|
||||
throw new IllegalStateException("Could not acquire read lock on: " + path);
|
||||
return ois.readObject();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.DatePicker?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.control.TitledPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
|
||||
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dev.ksan.ui.OrganizerController">
|
||||
<left>
|
||||
<VBox prefHeight="200.0" prefWidth="250.0" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Button fx:id="newButton" mnemonicParsing="false" text="new" textAlignment="CENTER">
|
||||
<VBox.margin>
|
||||
<Insets bottom="5.0" left="30.0" top="5.0" />
|
||||
</VBox.margin>
|
||||
</Button>
|
||||
<ScrollPane prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
|
||||
<content>
|
||||
<VBox fx:id="panesBox">
|
||||
<children>
|
||||
<TitledPane fx:id="activeList" animated="false" text="untitled" VBox.vgrow="ALWAYS">
|
||||
<content>
|
||||
<ListView fx:id="activePollsListView" prefHeight="200.0" prefWidth="200.0" />
|
||||
</content>
|
||||
<VBox.margin>
|
||||
<Insets bottom="5.0" />
|
||||
</VBox.margin>
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="inactiveList" animated="false" text="untitled" VBox.vgrow="ALWAYS">
|
||||
<content>
|
||||
<ListView fx:id="inactivePollsListView" />
|
||||
</content>
|
||||
</TitledPane>
|
||||
</children>
|
||||
</VBox>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
</children>
|
||||
<BorderPane.margin>
|
||||
<Insets />
|
||||
</BorderPane.margin>
|
||||
</VBox>
|
||||
</left>
|
||||
<top>
|
||||
<HBox prefHeight="27.0" prefWidth="600.0" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Label fx:id="usernameLabel" text="teemp" textAlignment="CENTER">
|
||||
<HBox.margin>
|
||||
<Insets left="20.0" top="5.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
</children>
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<StackPane fx:id="centerPane" prefHeight="150.0" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0">
|
||||
<children>
|
||||
<HBox prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Title:" />
|
||||
<TextField fx:id="titleField" />
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Description:" />
|
||||
<TextField fx:id="descriptionField" />
|
||||
</children>
|
||||
</HBox>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0">
|
||||
<children>
|
||||
<HBox prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<DatePicker fx:id="startDatePicker" />
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="<--->">
|
||||
<HBox.margin>
|
||||
<Insets top="3.0" />
|
||||
</HBox.margin>
|
||||
</Text>
|
||||
<DatePicker fx:id="endDatePicker" />
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<TextField fx:id="startTimeField" />
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="<--->" />
|
||||
<TextField fx:id="endTimeField" />
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
<ScrollPane VBox.vgrow="ALWAYS">
|
||||
<content>
|
||||
<HBox>
|
||||
<children>
|
||||
<Button fx:id="newOptionButton" mnemonicParsing="false" text="new" />
|
||||
<VBox fx:id="optionsContainer" alignment="CENTER" />
|
||||
</children>
|
||||
</HBox>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
<Button fx:id="confirmButton" alignment="BOTTOM_RIGHT" mnemonicParsing="false" text="create">
|
||||
<graphic>
|
||||
<Label fx:id="errorLabel" text="Label" />
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</StackPane>
|
||||
</center>
|
||||
</BorderPane>
|
||||
@ -1,43 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.gluonhq.charm.glisten.control.Avatar?>
|
||||
<?import com.gluonhq.charm.glisten.control.DropdownButton?>
|
||||
<?import com.gluonhq.charm.glisten.control.ExpansionPanel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.control.TitledPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
|
||||
|
||||
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dev.ksan.ui.VoterController">
|
||||
<left>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
|
||||
<VBox prefHeight="200.0" prefWidth="250.0" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
|
||||
<ScrollPane prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
|
||||
<content>
|
||||
<VBox fx:id="panesBox">
|
||||
<children>
|
||||
<Avatar>
|
||||
<HBox.margin>
|
||||
<Insets left="5.0" right="5.0" />
|
||||
</HBox.margin>
|
||||
</Avatar>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Text" />
|
||||
</children>
|
||||
</HBox>
|
||||
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0">
|
||||
<children>
|
||||
<DropdownButton>
|
||||
<items>
|
||||
<MenuItem text="Choice 1" />
|
||||
<MenuItem text="Choice 2" />
|
||||
<MenuItem text="Choice 3" />
|
||||
</items>
|
||||
</DropdownButton>
|
||||
<ExpansionPanel />
|
||||
<Button fx:id="refreshButton" mnemonicParsing="false" text="Button" />
|
||||
<TitledPane fx:id="activeList" animated="false" text="untitled" VBox.vgrow="ALWAYS">
|
||||
<content>
|
||||
<ListView fx:id="activePollsListView" prefHeight="200.0" prefWidth="200.0" />
|
||||
</content>
|
||||
<VBox.margin>
|
||||
<Insets bottom="5.0" />
|
||||
</VBox.margin>
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="inactiveList" animated="false" text="untitled" VBox.vgrow="ALWAYS">
|
||||
<content>
|
||||
<ListView fx:id="inactivePollsListView" />
|
||||
</content>
|
||||
</TitledPane>
|
||||
</children>
|
||||
</VBox>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
</children>
|
||||
<BorderPane.margin>
|
||||
<Insets />
|
||||
</BorderPane.margin>
|
||||
</VBox>
|
||||
</left>
|
||||
<top>
|
||||
<HBox prefHeight="27.0" prefWidth="600.0" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Label fx:id="usernameLabel" text="teemp" textAlignment="CENTER">
|
||||
<HBox.margin>
|
||||
<Insets left="20.0" top="5.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
</children>
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<StackPane fx:id="centerPane" prefHeight="150.0" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0">
|
||||
<children>
|
||||
<HBox prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Title:" />
|
||||
<Text fx:id="titleText" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" />
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Description:" />
|
||||
<Text fx:id="descriptionText" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" />
|
||||
</children>
|
||||
</HBox>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0">
|
||||
<children>
|
||||
<HBox prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<Text fx:id="startDateText" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" />
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="<--->">
|
||||
<HBox.margin>
|
||||
<Insets top="3.0" />
|
||||
</HBox.margin>
|
||||
</Text>
|
||||
<Text fx:id="endDateText" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" />
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
<ScrollPane VBox.vgrow="ALWAYS">
|
||||
<content>
|
||||
<HBox>
|
||||
<children>
|
||||
<VBox fx:id="optionsContainer" alignment="CENTER" />
|
||||
</children>
|
||||
</HBox>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
<Label fx:id="alreadyVotedLabel" text="Label" />
|
||||
<Label fx:id="errorLabel" text="Label" />
|
||||
<Button fx:id="confirmButton" alignment="BOTTOM_RIGHT" mnemonicParsing="false" text="create" />
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</StackPane>
|
||||
</center>
|
||||
</BorderPane>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user