package envoy.client.ui.control; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.io.*; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Map; import java.util.logging.*; import javafx.geometry.*; import javafx.scene.control.*; import javafx.scene.image.*; import javafx.scene.layout.*; import javafx.stage.FileChooser; import envoy.client.data.*; import envoy.client.ui.*; import envoy.client.util.IconUtil; import envoy.data.*; import envoy.data.Message.MessageStatus; import envoy.util.EnvoyLog; /** * This class transforms a single {@link Message} into a UI component. * * @author Leon Hofmeister * @author Maximilian Käfer * @since Envoy Client v0.1-beta */ public final class MessageControl extends Label { private final boolean ownMessage; private final LocalDB localDB = Context.getInstance().getLocalDB(); private final SceneContext sceneContext = Context.getInstance().getSceneContext(); private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss") .withZone(ZoneId.systemDefault()); private static final Map statusImages = IconUtil.loadByEnum(MessageStatus.class, 16); private static final Settings settings = Settings.getInstance(); private static final Logger logger = EnvoyLog.getLogger(MessageControl.class); /** * * @param message the message that should be formatted * @since Envoy Client v0.1-beta */ public MessageControl(Message message) { // Creating the underlying VBox and the dateLabel final var hbox = new HBox(); if (message.getSenderID() != localDB.getUser().getID() && message instanceof GroupMessage) { // Displaying the name of the sender in a group final var label = new Label(); label.getStyleClass().add("group-member-names"); label.setText(localDB.getUsers() .values() .stream() .filter(c -> c.getID() == message.getSenderID()) .findFirst() .map(User::getName) .orElse("Unknown User")); label.setPadding(new Insets(0, 5, 0, 0)); hbox.getChildren().add(label); } hbox.getChildren().add(new Label(dateFormat.format(message.getCreationDate()))); final var vbox = new VBox(hbox); // Creating the actions for the MenuItems final var contextMenu = new ContextMenu(); final var copyMenuItem = new MenuItem("Copy"); final var deleteMenuItem = new MenuItem("Delete"); final var forwardMenuItem = new MenuItem("Forward"); final var quoteMenuItem = new MenuItem("Quote"); final var infoMenuItem = new MenuItem("Info"); copyMenuItem.setOnAction(e -> copyMessage(message)); deleteMenuItem.setOnAction(e -> deleteMessage(message)); forwardMenuItem.setOnAction(e -> forwardMessage(message)); quoteMenuItem.setOnAction(e -> quoteMessage(message)); infoMenuItem.setOnAction(e -> loadMessageInfoScene(message)); contextMenu.getItems().addAll(copyMenuItem, deleteMenuItem, forwardMenuItem, quoteMenuItem, infoMenuItem); // Handling message attachment display // TODO: Add missing attachment types if (message.hasAttachment()) { switch (message.getAttachment().getType()) { case PICTURE: vbox.getChildren() .add(new ImageView(new Image(new ByteArrayInputStream(message.getAttachment().getData()), 256, 256, true, true))); break; case VIDEO: break; case VOICE: vbox.getChildren().add(new AudioControl(message.getAttachment().getData())); break; case DOCUMENT: break; } final var saveAttachment = new MenuItem("Save attachment"); saveAttachment.setOnAction(e -> saveAttachment(message)); contextMenu.getItems().add(saveAttachment); } // Creating the textLabel final var textLabel = new Label(message.getText()); textLabel.setMaxWidth(430); textLabel.setWrapText(true); final var hBoxBottom = new HBox(); hBoxBottom.getChildren().add(textLabel); // Setting the message status icon and background color if (message.getSenderID() == localDB.getUser().getID()) { final var statusIcon = new ImageView(statusImages.get(message.getStatus())); statusIcon.setPreserveRatio(true); final var space = new Region(); HBox.setHgrow(space, Priority.ALWAYS); hBoxBottom.getChildren().add(space); hBoxBottom.getChildren().add(statusIcon); hBoxBottom.setAlignment(Pos.BOTTOM_RIGHT); getStyleClass().add("own-message"); ownMessage = true; hbox.setAlignment(Pos.CENTER_RIGHT); } else { getStyleClass().add("received-message"); ownMessage = false; } vbox.getChildren().add(hBoxBottom); // Adjusting height and weight of the cell to the corresponding ListView paddingProperty().setValue(new Insets(5, 20, 5, 20)); setContextMenu(contextMenu); setGraphic(vbox); } // Context Menu actions private void copyMessage(Message message) { Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(message.getText()), null); } private void deleteMessage(Message message) { logger.log(Level.FINEST, "message deletion was requested for " + message); } private void forwardMessage(Message message) { logger.log(Level.FINEST, "message forwarding was requested for " + message); } private void quoteMessage(Message message) { logger.log(Level.FINEST, "message quotation was requested for " + message); } private void loadMessageInfoScene(Message message) { logger.log(Level.FINEST, "message info scene was requested for " + message); } private void saveAttachment(Message message) { File file; final var fileName = message.getAttachment().getName(); final var downloadLocation = settings.getDownloadLocation(); // Show save file dialog, if the user did not opt-out if (!settings.isDownloadSavedWithoutAsking()) { final var fileChooser = new FileChooser(); fileChooser.setInitialFileName(fileName); fileChooser.setInitialDirectory(downloadLocation); file = fileChooser.showSaveDialog(sceneContext.getStage()); } else file = new File(downloadLocation, fileName); // A file was selected if (file != null) try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(message.getAttachment().getData()); logger.log(Level.FINE, "Attachment of message was saved at " + file.getAbsolutePath()); } catch (final IOException e) { logger.log(Level.WARNING, "Could not save attachment of " + message + ": ", e); } } /** * @return whether the message stored by this {@code MessageControl} has been * sent by this user of Envoy * @since Envoy Client v0.1-beta */ public boolean isOwnMessage() { return ownMessage; } }