diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/BisqPopupMenu.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/BisqPopupMenu.java
deleted file mode 100644
index bfd14bdd4a..0000000000
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/BisqPopupMenu.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * This file is part of Bisq.
- *
- * Bisq is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
- *
- * Bisq is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Bisq. If not, see .
- */
-
-package bisq.desktop.components.controls;
-
-import javafx.geometry.Insets;
-import javafx.scene.control.Hyperlink;
-import javafx.scene.layout.VBox;
-
-import java.util.List;
-
-public class BisqPopupMenu extends BisqPopup {
- public BisqPopupMenu(List items, Runnable onClose) {
- super();
-
- getStyleClass().add("bisq-popup-menu");
-
- VBox box = new VBox();
- box.setSpacing(5);
- box.setPadding(new Insets(10));
-
- for (BisqPopupMenuItem item : items) {
- Hyperlink hyperlink = new Hyperlink(item.getTitle());
- hyperlink.setOnAction(e -> {
- item.getAction().run();
- hide();
- });
-
- box.getChildren().add(hyperlink);
- }
-
- setContentNode(box);
-
- showingProperty().addListener((observable, wasShowing, isShowing) -> {
- if (wasShowing) {
- onClose.run();
- }
- });
- }
-}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenu.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenu.java
index 9e65e48634..2771a689bd 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenu.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenu.java
@@ -18,6 +18,8 @@
package bisq.desktop.components.controls;
import bisq.desktop.common.utils.ImageUtil;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.WeakChangeListener;
import javafx.collections.ObservableList;
@@ -33,19 +35,25 @@
import javafx.stage.PopupWindow;
import javafx.stage.WindowEvent;
import lombok.Getter;
+import lombok.Setter;
import java.util.Collection;
public class DropdownMenu extends HBox {
public static final Double INITIAL_WIDTH = 24.0;
- @Getter
- private Label label = new Label();
+
private final ImageView defaultIcon, activeIcon;
+ @Getter
+ private final BooleanProperty isMenuShowing = new SimpleBooleanProperty(false);
private final ContextMenu contextMenu = new ContextMenu();
+ @Getter
+ private Label label = new Label();
private ImageView buttonIcon;
// We need to pin it as used in a WeakChangeListener
private ChangeListener widthPropertyChangeListener;
private boolean isFirstRun = false;
+ @Setter
+ private boolean openUpwards = false;
public DropdownMenu(String defaultIconId, String activeIconId, boolean useIconOnly) {
defaultIcon = ImageUtil.getImageViewById(defaultIconId);
@@ -84,10 +92,12 @@ public void setLabel(Label label) {
private void toggleContextMenu() {
if (!contextMenu.isShowing()) {
- contextMenu.setAnchorLocation(PopupWindow.AnchorLocation.WINDOW_TOP_RIGHT);
- Bounds bounds = this.localToScreen(this.getBoundsInLocal());
+ contextMenu.setAnchorLocation(openUpwards
+ ? PopupWindow.AnchorLocation.WINDOW_BOTTOM_RIGHT
+ : PopupWindow.AnchorLocation.WINDOW_TOP_RIGHT);
+ Bounds bounds = localToScreen(getBoundsInLocal());
double x = bounds.getMaxX();
- double y = bounds.getMaxY() + 3;
+ double y = openUpwards ? bounds.getMinY() - 3 : bounds.getMaxY() + 3;
contextMenu.show(this, x, y);
} else {
contextMenu.hide();
@@ -123,7 +133,7 @@ public void setTooltip(Tooltip tooltip) {
}
private void attachListeners() {
- setOnMouseClicked(event -> toggleContextMenu());
+ setOnMouseClicked(e -> toggleContextMenu());
setOnMouseExited(e -> updateIcon(contextMenu.isShowing() ? activeIcon : defaultIcon));
setOnMouseEntered(e -> updateIcon(activeIcon));
@@ -140,11 +150,12 @@ private void attachListeners() {
contextMenu.setOnShowing(e -> {
getStyleClass().add("dropdown-menu-active");
updateIcon(activeIcon);
-
+ isMenuShowing.setValue(true);
});
contextMenu.setOnHidden(e -> {
getStyleClass().remove("dropdown-menu-active");
updateIcon(defaultIcon);
+ isMenuShowing.setValue(false);
});
widthPropertyChangeListener = (observable, oldValue, newValue) -> {
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessagesListController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessagesListController.java
index 83666c278f..fe79af392d 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessagesListController.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessagesListController.java
@@ -26,14 +26,10 @@
import bisq.common.observable.Pin;
import bisq.common.observable.collection.CollectionObserver;
import bisq.desktop.ServiceProvider;
-import bisq.desktop.common.observable.FxBindings;
import bisq.desktop.common.threading.UIScheduler;
import bisq.desktop.common.threading.UIThread;
import bisq.desktop.common.utils.ClipboardUtil;
import bisq.desktop.common.view.Navigation;
-import bisq.desktop.components.controls.BisqPopup;
-import bisq.desktop.components.controls.BisqPopupMenu;
-import bisq.desktop.components.controls.BisqPopupMenuItem;
import bisq.desktop.components.overlay.Popup;
import bisq.desktop.main.content.bisq_easy.BisqEasyServiceUtil;
import bisq.desktop.main.content.bisq_easy.take_offer.TakeOfferController;
@@ -54,7 +50,6 @@
import bisq.user.profile.UserProfileService;
import bisq.user.reputation.ReputationScore;
import bisq.user.reputation.ReputationService;
-import javafx.scene.Node;
import javafx.scene.Scene;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@@ -63,7 +58,6 @@
import javax.annotation.Nullable;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
@@ -388,29 +382,6 @@ public void onSaveEditedMessage(ChatMessage chatMessage, String editedText) {
}
}
- void onOpenMoreOptions(Node owner, ChatMessage chatMessage, Runnable onClose) {
- if (chatMessage.equals(model.getSelectedChatMessageForMoreOptionsPopup().get())) {
- return;
- }
- model.getSelectedChatMessageForMoreOptionsPopup().set(chatMessage);
-
- List items = new ArrayList<>();
- items.add(new BisqPopupMenuItem(Res.get("chat.message.contextMenu.copyMessage"),
- () -> onCopyMessage(chatMessage)));
- if (!model.isMyMessage(chatMessage)) {
- if (chatMessage instanceof PublicChatMessage) {
- items.add(new BisqPopupMenuItem(Res.get("chat.message.contextMenu.ignoreUser"),
- () -> onIgnoreUser(chatMessage)));
- }
- items.add(new BisqPopupMenuItem(Res.get("chat.message.contextMenu.reportUser"),
- () -> onReportUser(chatMessage)));
- }
-
- BisqPopupMenu menu = new BisqPopupMenu(items, onClose);
- menu.setAlignment(BisqPopup.Alignment.LEFT);
- menu.show(owner);
- }
-
public void onReportUser(ChatMessage chatMessage) {
ChatChannelDomain chatChannelDomain = model.getSelectedChannel().get().getChatChannelDomain();
if (chatMessage instanceof PrivateChatMessage) {
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessagesListModel.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessagesListModel.java
index 5509e0fc86..1e5e6648ea 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessagesListModel.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessagesListModel.java
@@ -34,7 +34,6 @@ public class ChatMessagesListModel implements bisq.desktop.common.view.Model {
private final BooleanProperty layoutChildrenDone = new SimpleBooleanProperty();
private final BooleanProperty isPublicChannel = new SimpleBooleanProperty();
- private final ObjectProperty selectedChatMessageForMoreOptionsPopup = new SimpleObjectProperty<>(null);
private final ChatChannelDomain chatChannelDomain;
@Setter
private Predicate super ChatMessageListItem extends ChatMessage, ? extends ChatChannel extends ChatMessage>>> searchPredicate = e -> true;
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/BubbleMessageBox.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/BubbleMessageBox.java
index cae9206343..7239e12719 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/BubbleMessageBox.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/BubbleMessageBox.java
@@ -25,6 +25,7 @@
import bisq.desktop.common.utils.ClipboardUtil;
import bisq.desktop.common.utils.ImageUtil;
import bisq.desktop.components.controls.BisqTooltip;
+import bisq.desktop.components.controls.DropdownMenu;
import bisq.desktop.main.content.chat.message_container.list.ChatMessageListItem;
import bisq.desktop.main.content.chat.message_container.list.ChatMessagesListController;
import bisq.desktop.main.content.chat.message_container.list.ChatMessagesListModel;
@@ -61,6 +62,7 @@ public abstract class BubbleMessageBox extends MessageBox {
protected Label supportedLanguages, userName, dateTime, message;
protected HBox userNameAndDateHBox, messageBgHBox, messageHBox;
protected VBox userProfileIconVbox;
+ protected DropdownMenu moreOptionsMenu;
public BubbleMessageBox(ChatMessageListItem extends ChatMessage, ? extends ChatChannel extends ChatMessage>> item,
ListView>> list,
@@ -133,7 +135,7 @@ protected void addReactionsHandlers() {
private void addOnMouseEventHandlers() {
setOnMouseEntered(e -> {
- if (model.getSelectedChatMessageForMoreOptionsPopup().get() != null) {
+ if (moreOptionsMenu != null && moreOptionsMenu.getIsMenuShowing().get()) {
return;
}
dateTime.setVisible(true);
@@ -141,7 +143,7 @@ private void addOnMouseEventHandlers() {
});
setOnMouseExited(e -> {
- if (model.getSelectedChatMessageForMoreOptionsPopup().get() == null) {
+ if (moreOptionsMenu == null || !moreOptionsMenu.getIsMenuShowing().get()) {
dateTime.setVisible(false);
reactionsHBox.setVisible(false);
}
@@ -152,6 +154,7 @@ private void addOnMouseEventHandlers() {
public void cleanup() {
setOnMouseEntered(null);
setOnMouseExited(null);
+
showHighlightedPin.unsubscribe();
}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/PeerOfferMessageBox.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/PeerOfferMessageBox.java
index 0482fc4d87..f2eef7c853 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/PeerOfferMessageBox.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/PeerOfferMessageBox.java
@@ -49,8 +49,9 @@ public PeerOfferMessageBox(ChatMessageListItem extends ChatMessage, ? extends
ChatMessagesListController controller, ChatMessagesListModel model) {
super(item, list, controller, model);
+ HBox.setMargin(copyIcon, new Insets(4, 0, -4, 0));
HBox.setMargin(supportedLanguages, new Insets(5, 0, -5, 0));
- reactionsHBox.getChildren().setAll(replyIcon, pmIcon, supportedLanguages, moreOptionsIcon, Spacer.fillHBox());
+ reactionsHBox.getChildren().setAll(replyIcon, pmIcon, copyIcon, supportedLanguages, moreOptionsMenu, Spacer.fillHBox());
VBox.setMargin(userNameAndDateHBox, new Insets(-5, 0, 5, 10));
contentVBox.getChildren().setAll(userNameAndDateHBox, messageBgHBox, reactionsHBox);
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/PeerTextMessageBox.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/PeerTextMessageBox.java
index 34ad680ba4..35d89bb1db 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/PeerTextMessageBox.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/PeerTextMessageBox.java
@@ -21,9 +21,8 @@
import bisq.chat.ChatMessage;
import bisq.chat.pub.PublicChatMessage;
import bisq.desktop.components.containers.Spacer;
-import bisq.desktop.components.controls.BisqPopup;
-import bisq.desktop.components.controls.BisqPopupMenu;
-import bisq.desktop.components.controls.BisqPopupMenuItem;
+import bisq.desktop.components.controls.DropdownMenu;
+import bisq.desktop.components.controls.DropdownMenuItem;
import bisq.desktop.main.content.chat.message_container.list.ChatMessageListItem;
import bisq.desktop.main.content.chat.message_container.list.ChatMessagesListController;
import bisq.desktop.main.content.chat.message_container.list.ChatMessagesListModel;
@@ -31,17 +30,17 @@
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
-import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
-
-import java.util.ArrayList;
-import java.util.List;
+import org.fxmisc.easybind.EasyBind;
+import org.fxmisc.easybind.Subscription;
public class PeerTextMessageBox extends BubbleMessageBox {
- protected Label replyIcon, pmIcon, moreOptionsIcon;
+ private Subscription isMenuShowingPin;
+ protected Label replyIcon, pmIcon, copyIcon;
+ protected DropdownMenuItem ignoreUserMenuItem, reportUserMenuItem;
public PeerTextMessageBox(ChatMessageListItem extends ChatMessage, ? extends ChatChannel extends ChatMessage>> item,
ListView>> list,
@@ -51,7 +50,7 @@ public PeerTextMessageBox(ChatMessageListItem extends ChatMessage, ? extends C
setUpPeerMessage();
setMargin(userNameAndDateHBox, new Insets(-5, 0, -5, 10));
messageHBox.getChildren().setAll(messageBgHBox, Spacer.fillHBox());
- reactionsHBox.getChildren().setAll(replyIcon, pmIcon, moreOptionsIcon, Spacer.fillHBox());
+ reactionsHBox.getChildren().setAll(replyIcon, pmIcon, copyIcon, moreOptionsMenu, Spacer.fillHBox());
contentVBox.getChildren().setAll(userNameAndDateHBox, messageHBox, reactionsHBox);
}
@@ -68,28 +67,45 @@ protected void setUpUserNameAndDateTime() {
protected void setUpReactions() {
replyIcon = getIconWithToolTip(AwesomeIcon.REPLY, Res.get("chat.message.reply"));
pmIcon = getIconWithToolTip(AwesomeIcon.COMMENT_ALT, Res.get("chat.message.privateMessage"));
- moreOptionsIcon = getIconWithToolTip(AwesomeIcon.ELLIPSIS_HORIZONTAL, Res.get("chat.message.moreOptions"));
+ copyIcon = getIconWithToolTip(AwesomeIcon.COPY, Res.get("action.copyToClipboard"));
+
+ // More options dropdown menu
+ ignoreUserMenuItem = new DropdownMenuItem(Res.get("chat.message.contextMenu.ignoreUser"));
+ reportUserMenuItem = new DropdownMenuItem(Res.get("chat.message.contextMenu.reportUser"));
+ moreOptionsMenu = new DropdownMenu("ellipsis-h-grey", "ellipsis-h-white", true);
+ moreOptionsMenu.setTooltip(Res.get("chat.message.moreOptions"));
+ moreOptionsMenu.addMenuItems(ignoreUserMenuItem, reportUserMenuItem);
+ moreOptionsMenu.setOpenUpwards(true);
+
HBox.setMargin(replyIcon, new Insets(4, 0, -4, 10));
HBox.setMargin(pmIcon, new Insets(3, 0, -3, 0));
- HBox.setMargin(moreOptionsIcon, new Insets(5, 0, -5, 0));
+ HBox.setMargin(copyIcon, new Insets(4, 0, -4, 0));
+ HBox.setMargin(moreOptionsMenu, new Insets(2, 0, -2, 0));
reactionsHBox.setVisible(false);
}
@Override
protected void addReactionsHandlers() {
ChatMessage chatMessage = item.getChatMessage();
- moreOptionsIcon.setOnMouseClicked(e -> onOpenMoreOptions(pmIcon, chatMessage, () -> {
- reactionsHBox.setVisible(false);
- model.getSelectedChatMessageForMoreOptionsPopup().set(null);
- }));
+
replyIcon.setOnMouseClicked(e -> controller.onReply(chatMessage));
pmIcon.setOnMouseClicked(e -> controller.onOpenPrivateChannel(chatMessage));
+ copyIcon.setOnMouseClicked(e -> onCopyMessage(chatMessage));
+ ignoreUserMenuItem.setOnAction(e -> controller.onIgnoreUser(chatMessage));
+ reportUserMenuItem.setOnAction(e -> controller.onReportUser(chatMessage));
replyIcon.setVisible(true);
replyIcon.setManaged(true);
pmIcon.setVisible(chatMessage instanceof PublicChatMessage);
pmIcon.setManaged(chatMessage instanceof PublicChatMessage);
+
+ isMenuShowingPin = EasyBind.subscribe(moreOptionsMenu.getIsMenuShowing(), isShowing -> {
+ if (!isShowing && !isHover()) {
+ dateTime.setVisible(false);
+ reactionsHBox.setVisible(false);
+ }
+ });
}
protected void setUpPeerMessage() {
@@ -110,28 +126,6 @@ protected void setUpPeerMessage() {
messageBgHBox.getChildren().setAll(userProfileIconVbox, messageVBox);
}
- private void onOpenMoreOptions(Node owner, ChatMessage chatMessage, Runnable onClose) {
- if (chatMessage.equals(model.getSelectedChatMessageForMoreOptionsPopup().get())) {
- return;
- }
- model.getSelectedChatMessageForMoreOptionsPopup().set(chatMessage);
-
- List items = new ArrayList<>();
- items.add(new BisqPopupMenuItem(Res.get("chat.message.contextMenu.copyMessage"),
- () -> onCopyMessage(chatMessage)));
-
- if (chatMessage instanceof PublicChatMessage) {
- items.add(new BisqPopupMenuItem(Res.get("chat.message.contextMenu.ignoreUser"),
- () -> controller.onIgnoreUser(chatMessage)));
- }
- items.add(new BisqPopupMenuItem(Res.get("chat.message.contextMenu.reportUser"),
- () -> controller.onReportUser(chatMessage)));
-
- BisqPopupMenu menu = new BisqPopupMenu(items, onClose);
- menu.setAlignment(BisqPopup.Alignment.LEFT);
- menu.show(owner);
- }
-
@Override
public void cleanup() {
super.cleanup();
@@ -142,8 +136,12 @@ public void cleanup() {
userProfileIcon.setOnMouseClicked(null);
replyIcon.setOnMouseClicked(null);
pmIcon.setOnMouseClicked(null);
- moreOptionsIcon.setOnMouseClicked(null);
+ copyIcon.setOnMouseClicked(null);
+ ignoreUserMenuItem.setOnAction(null);
+ reportUserMenuItem.setOnAction(null);
userProfileIcon.releaseResources();
+
+ isMenuShowingPin.unsubscribe();
}
}