package fetcher.controller; import fetcher.model.EntriesComparators; import fetcher.model.Pad; import fetcher.model.Utils; import fetcher.model.PageEntry; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.control.Button; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; import javafx.scene.input.MouseEvent; import javafx.scene.layout.VBox; import javafx.stage.Modality; import javafx.stage.Stage; import javafx.util.Callback; import javafx.util.Duration; import java.net.URL; import java.util.Collections; import java.util.ResourceBundle; /** * * File name : MainController.java * * This class is the controller for the Main.fxml file. * Its main purpose is to handle correctly the ListView containing the urls. * It also handles the menu bar and the upper buttons. * */ public class MainController implements Initializable { Clipboard clipboard; public Pad pad; Timeline repeatTask; @FXML private ListView<PageEntry> listURL; @FXML private Button saveBtn; @FXML private Button deleteBtn; @FXML private ListView<String> tagsListView; public MainController(){ clipboard = Clipboard.getSystemClipboard(); } public void setPadName(String padName){ pad = new Pad(padName); listURL.setItems(pad.listItems); tagsListView.setItems(pad.allTags); //Loading the pad if there's a saved one. if(Utils.FileExists(pad.getpadName()))pad.loadPad(this); } @Override public void initialize(URL location, ResourceBundle resources) { //Setting up a periodic clipboard checker. HandleClipboardChange listener = new HandleClipboardChange(); repeatTask = new Timeline(new KeyFrame(Duration.millis(200), listener)); repeatTask.setCycleCount(Timeline.INDEFINITE); repeatTask.play(); //Setting up the ListView listURL.setCellFactory(new Callback<ListView<PageEntry>, ListCell<PageEntry>>() { @Override public ListCell<PageEntry> call(ListView<PageEntry> param) { return new EntryCell(MainController.this); } }); setupButtons(); tagsListView.getSelectionModel().selectedIndexProperty().addListener(new ChangeListenerTagsFilter()); } /** * Updates the listView with the Entry which was processed by a worker thread from PageEntry class. * @param entry the entry to be added to the listview. */ public void notifyControllerNewEntry(final PageEntry entry){ Platform.runLater(new Runnable() { @Override public void run() { pad.listItems.add(entry); //TODO: Doesn't need to be here, probably can be put in the add method itself. if(pad.allTags.size() == 0) pad.allTags.add("all"); }}); } /** * Refreshes the list-view setting the listURL to null and then back to the previous status. */ public void refreshListView(){ Platform.runLater(new Runnable() { @Override public void run() { ObservableList<PageEntry> entries = listURL.getItems(); listURL.setItems(null); listURL.setItems(entries); } }); } /** * Takes care of setting up the buttons (in the upper bar) UI and listeners. */ public void setupButtons(){ //TODO: I think I can make this whole section a little cleaner by using CSS. //Setting up the buttons Image imageDelete = new Image(getClass().getResourceAsStream("/images/delete.png")); deleteBtn.setGraphic(new ImageView(imageDelete)); deleteBtn.setOnMouseClicked(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { handleDeleteMenuButton(); } }); deleteBtn.setText("Delete"); deleteBtn.setContentDisplay(ContentDisplay.TOP); //Button save Image imageSave = new Image(getClass().getResourceAsStream("/images/save.png")); saveBtn.setGraphic(new ImageView(imageSave)); saveBtn.setText("Save"); saveBtn.setContentDisplay(ContentDisplay.TOP); saveBtn.setOnMouseClicked(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { handleSaveMenuButton(); } }); } /** * Exits the program. */ @FXML private void handleExitMenuButton(){ Platform.exit(); } /** * Saves the pad. */ @FXML private void handleSaveMenuButton(){ pad.savePad(); } /** * Exports the pad as a ZIP file. */ @FXML private void handleExportMenuButton() { pad.exportPad(); } /** * Clear the tags from the selected entry. */ @FXML private void handleClearTagsMenuButton(){ if (listURL.getSelectionModel().getSelectedItem() == null) return; listURL.getSelectionModel().getSelectedItem().clearTags(); refreshListView(); } /** * Remove a specific tag. */ @FXML private void handleRemoveTagMenuButton(){ if (listURL.getSelectionModel().getSelectedItem() == null) return; //Building a pop-up final Stage dialogStage = new Stage(); dialogStage.initModality(Modality.WINDOW_MODAL); VBox vBox = new VBox(); vBox.setAlignment(Pos.CENTER); Button ok = new Button("OK"); final ComboBox<String> comboBox = new ComboBox<String>(); comboBox.setItems(Utils.convertToObservableList(listURL.getSelectionModel().getSelectedItem().getTags())); vBox.getChildren().addAll(new Label("Tag:"),comboBox,ok); //Handler of the OK pressing button. Gets the tag and adds it to the existing ones if it doesn't already exists. ok.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { //Clearing the tag: listURL.getSelectionModel().getSelectedItem().deleteTag(comboBox.getSelectionModel().getSelectedItem()); dialogStage.close(); refreshListView(); } }); dialogStage.setScene(new Scene(vBox)); dialogStage.show(); } /** * This method deletes an entry from the listView. */ @FXML private void handleDeleteMenuButton(){ if (listURL.getSelectionModel().getSelectedItem() == null) return; int index = listURL.getSelectionModel().getSelectedIndex(); pad.deleteEntry(index,listURL.getItems()); } /** * Handles the menu sorting. Checks which MenuItem was clicked and sorts accordingly using the EntrisComparators class. * @param actionEvent */ @FXML public void handleSortMenuButton(ActionEvent actionEvent) { Object eventTriggerer = actionEvent.getSource(); if(eventTriggerer instanceof MenuItem){ MenuItem buttonPressed = (MenuItem) eventTriggerer; if(buttonPressed.getId().equalsIgnoreCase("dateASC")) Collections.sort(pad.listItems, EntriesComparators.getDateComparator()); else if(buttonPressed.getId().equalsIgnoreCase("dateDESC")){ Collections.sort(pad.listItems, EntriesComparators.getDateComparator().reversed()); } else if(buttonPressed.getId().equalsIgnoreCase("alphASC")) Collections.sort(pad.listItems, EntriesComparators.getAlphabeticalComparator()); else if(buttonPressed.getId().equalsIgnoreCase("alphDESC")){ Collections.sort(pad.listItems, EntriesComparators.getAlphabeticalComparator().reversed()); } } } /** * Enables/Disables the clipboard listener. */ @FXML public void handleMagneticMenuButton() { if(repeatTask.getStatus() == Animation.Status.RUNNING){ repeatTask.pause(); } else{ repeatTask.play(); } } /** *Creates a window where the user can input a URL and inserts it into the list. * @param actionEvent the event that launched the method. */ @FXML public void handleAddEntryMenuButton(ActionEvent actionEvent) { final Stage dialogStage = new Stage(); dialogStage.initModality(Modality.WINDOW_MODAL); VBox vBox = new VBox(); vBox.setAlignment(Pos.CENTER); Button ok = new Button("OK"); final TextField textfield = new TextField(); textfield.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { processEntry(textfield.getText()); } }); vBox.getChildren().addAll(new Label("URL:"),textfield,ok); ok.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { processEntry(textfield.getText()); } }); dialogStage.setScene(new Scene(vBox)); dialogStage.show(); } /** * Adds a new entry to the pad. * @param url the url of the entry to be added. * @return true if the entry is successfully created. False otherwise. */ public boolean addNewEntry(String url){ if(Utils.isValidURL(url) && !pad.EntryAlreadyExists(url)){ new PageEntry(url,MainController.this); return true; } return false; } /** * Attempts to call the method addNewEntry and shows a message indicating if the entry was successfully added or not. * @param url the entry's url to be added. */ public void processEntry(String url){ if(addNewEntry(url)){ Utils.createAlertMessage("Entry was added"); } else{ Utils.createAlertMessage("Something went wrong (maybe the entry already exists?). Warning: If you have the magnetic clipboard activated probably the url was just added automatically!"); } } /** * This class takes care of handling the 'Magnetic clipboard' function. When a url is copied, this class handles the necessary requests to add it to the pad. */ class HandleClipboardChange implements EventHandler<ActionEvent> { String currentString; /** * Default constructor. Sets currentString to the current clipboard copied text (if any). */ public HandleClipboardChange() { if (clipboard.hasString()) currentString = clipboard.getString(); else currentString = ""; } /** * Checks if there's any change in the clipboard. Makes sure the change is a URL, creates a PageEntry based on that URL. * @param event the mouse click event. */ @Override public void handle(ActionEvent event) { String clipBoardStatus = clipboard.getString(); if (clipboard.hasString() && !clipBoardStatus.equals(currentString) && Utils.isValidURL(clipBoardStatus)) { this.currentString = clipBoardStatus; if (!pad.EntryAlreadyExists(clipBoardStatus)) { new PageEntry(clipBoardStatus, MainController.this); } } } } /** * Takes care of handling the filtering of objects when a different cell in the listview of tags is selected. */ class ChangeListenerTagsFilter implements ChangeListener<Number>{ @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number tagNewIndex) { listURL.setItems(pad.getFilterTag(tagNewIndex.intValue())); } } }