package com.faforever.client.chat; import com.faforever.client.chat.avatar.AvatarBean; import com.faforever.client.chat.avatar.AvatarService; import com.faforever.client.fx.StringListCell; import com.faforever.client.fx.WindowController; import com.faforever.client.game.GameInfoBean; import com.faforever.client.game.GameService; import com.faforever.client.game.GameStatus; import com.faforever.client.game.JoinGameHelper; import com.faforever.client.i18n.I18n; import com.faforever.client.notification.ImmediateNotification; import com.faforever.client.notification.NotificationService; import com.faforever.client.notification.Severity; import com.faforever.client.player.PlayerService; import com.faforever.client.preferences.ChatPrefs; import com.faforever.client.preferences.PreferencesService; import com.faforever.client.replay.ReplayService; import com.faforever.client.user.UserService; import com.google.common.eventbus.EventBus; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.ColorPicker; import javafx.scene.control.ComboBox; import javafx.scene.control.ContextMenu; import javafx.scene.control.CustomMenuItem; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; import javafx.stage.Modality; import javafx.stage.Stage; import javafx.stage.StageStyle; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import javax.annotation.Resource; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.net.URL; import java.util.Objects; import static com.faforever.client.chat.ChatColorMode.CUSTOM; import static com.faforever.client.chat.SocialStatus.FOE; import static com.faforever.client.chat.SocialStatus.FRIEND; import static com.faforever.client.chat.SocialStatus.SELF; import static com.faforever.client.fx.WindowController.WindowButtonType.CLOSE; import static java.util.Locale.US; public class ChatUserContextMenuController { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @FXML ComboBox<AvatarBean> avatarComboBox; @FXML CustomMenuItem avatarPickerMenuItem; @FXML MenuItem sendPrivateMessageItem; @FXML SeparatorMenuItem socialSeparator; @FXML MenuItem removeCustomColorItem; @FXML CustomMenuItem colorPickerMenuItem; @FXML ColorPicker colorPicker; @FXML MenuItem joinGameItem; @FXML MenuItem addFriendItem; @FXML MenuItem removeFriendItem; @FXML MenuItem addFoeItem; @FXML MenuItem removeFoeItem; @FXML MenuItem watchGameItem; @FXML MenuItem viewReplaysItem; @FXML MenuItem inviteItem; @FXML SeparatorMenuItem moderatorActionSeparator; @FXML MenuItem kickItem; @FXML MenuItem banItem; @FXML ContextMenu contextMenu; @Resource UserService userService; @Resource ChatService chatService; @Resource PreferencesService preferencesService; @Resource ApplicationContext applicationContext; @Resource PlayerService playerService; @Resource GameService gameService; @Resource ReplayService replayService; @Resource NotificationService notificationService; @Resource I18n i18n; @Resource EventBus eventBus; @Resource JoinGameHelper joinGameHelper; @Resource AvatarService avatarService; private PlayerInfoBean playerInfoBean; @FXML void initialize() { avatarComboBox.setCellFactory(param -> avatarCell()); avatarComboBox.setButtonCell(avatarCell()); } @NotNull private StringListCell<AvatarBean> avatarCell() { return new StringListCell<>( AvatarBean::getDescription, avatarBean -> { URL url = avatarBean.getUrl(); if (url == null) { return null; } return avatarService.loadAvatar(url.toString()); }); } public ContextMenu getContextMenu() { return contextMenu; } public void setPlayerInfoBean(PlayerInfoBean playerInfoBean) { this.playerInfoBean = playerInfoBean; ChatPrefs chatPrefs = preferencesService.getPreferences().getChat(); String lowerCaseUsername = playerInfoBean.getUsername().toLowerCase(US); if (chatPrefs.getUserToColor().containsKey(lowerCaseUsername)) { colorPicker.setValue(chatPrefs.getUserToColor().get(lowerCaseUsername)); } else { colorPicker.setValue(null); } colorPicker.valueProperty().addListener((observable, oldValue, newValue) -> { String lowerUsername = playerInfoBean.getUsername().toLowerCase(US); if (newValue == null) { chatPrefs.getUserToColor().remove(lowerUsername); } else { chatPrefs.getUserToColor().put(lowerUsername, newValue); } ChatUser chatUser = chatService.getOrCreateChatUser(lowerUsername); chatUser.setColor(newValue); contextMenu.hide(); }); removeCustomColorItem.visibleProperty().bind(chatPrefs.chatColorModeProperty().isEqualTo(CUSTOM) .and(colorPicker.valueProperty().isNotNull()) .and(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF))); colorPickerMenuItem.visibleProperty().bind(chatPrefs.chatColorModeProperty() .isEqualTo(CUSTOM) .and(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF))); if (playerInfoBean.getSocialStatus() != SocialStatus.SELF) { avatarPickerMenuItem.setVisible(false); } else { loadAvailableAvatars(); } kickItem.visibleProperty().bind(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF)); banItem.visibleProperty().bind(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF)); moderatorActionSeparator.visibleProperty().bind(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF)); sendPrivateMessageItem.visibleProperty().bind(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF)); addFriendItem.visibleProperty().bind( playerInfoBean.socialStatusProperty().isNotEqualTo(FRIEND).and(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF)) ); removeFriendItem.visibleProperty().bind(playerInfoBean.socialStatusProperty().isEqualTo(FRIEND)); addFoeItem.visibleProperty().bind(playerInfoBean.socialStatusProperty().isNotEqualTo(FOE).and(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF))); removeFoeItem.visibleProperty().bind(playerInfoBean.socialStatusProperty().isEqualTo(FOE)); joinGameItem.visibleProperty().bind(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF) .and(playerInfoBean.gameStatusProperty().isEqualTo(GameStatus.LOBBY) .or(playerInfoBean.gameStatusProperty().isEqualTo(GameStatus.HOST)))); watchGameItem.visibleProperty().bind(playerInfoBean.gameStatusProperty().isEqualTo(GameStatus.PLAYING)); inviteItem.visibleProperty().bind(playerInfoBean.socialStatusProperty().isNotEqualTo(SELF) .and(playerInfoBean.gameStatusProperty().isNotEqualTo(GameStatus.PLAYING))); socialSeparator.visibleProperty().bind(addFriendItem.visibleProperty().or( removeFriendItem.visibleProperty().or( addFoeItem.visibleProperty().or( removeFoeItem.visibleProperty())))); } private void loadAvailableAvatars() { avatarService.getAvailableAvatars().thenAccept(avatars -> { ObservableList<AvatarBean> items = FXCollections.observableArrayList(avatars); items.add(0, new AvatarBean(null, i18n.get("chat.userContext.noAvatar"))); String currentAvatarUrl = playerInfoBean.getAvatarUrl(); Platform.runLater(() -> { avatarComboBox.setItems(items); avatarComboBox.getSelectionModel().select(items.stream() .filter(avatarBean -> Objects.equals(Objects.toString(avatarBean.getUrl(), null), currentAvatarUrl)) .findFirst() .orElse(null)); // Only after the box has been populated and we selected the current value, we add the listener. // Otherwise the code above already triggers a changeAvatar() avatarComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { playerInfoBean.setAvatarTooltip(newValue == null ? null : newValue.getDescription()); playerInfoBean.setAvatarUrl(newValue == null ? null : Objects.toString(newValue.getUrl(), null)); avatarService.changeAvatar(newValue); }); }); }); } @FXML void onUserInfo() { UserInfoWindowController userInfoWindowController = applicationContext.getBean(UserInfoWindowController.class); userInfoWindowController.setPlayerInfoBean(playerInfoBean); Stage userInfoWindow = new Stage(StageStyle.TRANSPARENT); userInfoWindow.initModality(Modality.NONE); userInfoWindow.initOwner(contextMenu.getOwnerWindow()); WindowController windowController = applicationContext.getBean(WindowController.class); windowController.configure(userInfoWindow, userInfoWindowController.getRoot(), true, CLOSE); userInfoWindow.show(); } @FXML void onSendPrivateMessage() { eventBus.post(new InitiatePrivateChatEvent(playerInfoBean.getUsername())); } @FXML void onAddFriend() { if (playerInfoBean.getSocialStatus() == FOE) { playerService.removeFoe(playerInfoBean); } playerService.addFriend(playerInfoBean); } @FXML void onRemoveFriend() { playerService.removeFriend(playerInfoBean); } @FXML void onAddFoe() { if (playerInfoBean.getSocialStatus() == FRIEND) { playerService.removeFriend(playerInfoBean); } playerService.addFoe(playerInfoBean); } @FXML void onRemoveFoe() { playerService.removeFoe(playerInfoBean); } @FXML void onWatchGame() { try { replayService.runLiveReplay(playerInfoBean.getGameUid(), playerInfoBean.getId()); } catch (IOException e) { logger.error("Cannot load live replay {}", e.getCause()); String title = i18n.get("replays.live.loadFailure.title"); String message = i18n.get("replays.live.loadFailure.message"); notificationService.addNotification(new ImmediateNotification(title, message, Severity.ERROR)); } } @FXML void onViewReplays() { // FIXME implement } @FXML void onInviteToGame() { //FIXME implement } @FXML void onKick() { // FIXME implement } @FXML void onBan() { // FIXME implement } @FXML void onJoinGame() { GameInfoBean gameInfoBean = gameService.getByUid(playerInfoBean.getGameUid()); joinGameHelper.join(gameInfoBean); } @FXML void onRemoveCustomColor() { colorPicker.setValue(null); } }