SPARK-2118: Move 'multiple resources' to 'view client version'

This reverts the previous solution to show all resources for a user (which was displayed in the contactlist tooltip for each user - introducing confusing behavior in a very visible place).

Instead, the resources are now displayed in the 'view client version' menu item, which adds a descriptive disclaimer. This is a less visible place (which will lead to less confusion) and as a bonus, the functionality now no longer returns data from one client when multiple clients are online.
This commit is contained in:
Guus der Kinderen
2026-07-31 17:44:44 +02:00
committed by Sergey Ponomarev
parent a0de2863e9
commit 61189f66f0
4 changed files with 76 additions and 39 deletions

View File

@ -80,7 +80,6 @@ public class ContactInfoWindow extends JPanel {
private final JLabel iconLabel = new JLabel();
private final JLabel titleLabel = new JLabel();
private final JLabel phoneLabel = new JLabel();
private final JTextArea clientResourcesLabel = new JTextArea();
private ContactItem contactItem;
@ -109,7 +108,6 @@ public class ContactInfoWindow extends JPanel {
add(titleLabel, new GridBagConstraints(2, 4, 1, 1, 1, 0, NORTHWEST, HORIZONTAL, new Insets(0, 0, 2, 2), 0, 0));
add(phoneLabel, new GridBagConstraints(2, 5, 1, 1, 1, 0, NORTHWEST, HORIZONTAL, new Insets(0, 0, 2, 2), 0, 0));
add(fullJIDLabel, new GridBagConstraints(0, 6, 4, 1, 1, 1, SOUTHWEST, HORIZONTAL, new Insets(0, 2, 2, 2), 0, 0));
add(clientResourcesLabel, new GridBagConstraints(0, 7, 4, 1, 1, 1, SOUTHWEST, HORIZONTAL, new Insets(0, 2, 2, 2), 0, 0));
Font dialogFont = new Font("Dialog", Font.PLAIN, 12);
nicknameLabel.setFont(new Font("Dialog", Font.BOLD, 14));
@ -128,9 +126,6 @@ public class ContactInfoWindow extends JPanel {
fullJIDLabel.setFont(dialogFont);
fullJIDLabel.setForeground(COLOR_TEXT);
fullJIDLabel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, COLOR_TEXT));
clientResourcesLabel.setLineWrap(true);
clientResourcesLabel.setWrapStyleWord(true);
clientResourcesLabel.setEditable(false);
phoneLabel.setBorder(null);
setBorder(BorderFactory.createLineBorder(COLOR_TEXT, 1));
@ -213,7 +208,6 @@ public class ContactInfoWindow extends JPanel {
idleLabel.setText("");
titleLabel.setText("");
phoneLabel.setText("");
showClientResources(isOnLeave, contactItem.getJid());
// Reserve avatar space immediately so the layout does not change later.
final ImageIcon placeholder = GraphicUtils.scaleImageIcon(SparkRes.getImageIcon(SparkRes.Icon.DEFAULT_AVATAR_64x64_IMAGE), Sizes.Avatar.PROFILE, Sizes.Avatar.PROFILE);
@ -301,21 +295,6 @@ public class ContactInfoWindow extends JPanel {
}
}
private void showClientResources(boolean isOnLeave, BareJid contactJid) {
if (isOnLeave) {
clientResourcesLabel.setText("");
return;
}
String clientResources = "";
List<Presence> allPresences = SparkManager.getRoster().getAvailablePresences(contactJid);
for (Presence p : allPresences) {
clientResources += p.getFrom().getResourceOrEmpty() + " " +
(p.getMode() != available ? p.getMode() : "") + " " +
(p.getStatus() != null ? p.getStatus() : "") + "\n";
}
clientResourcesLabel.setText(clientResources);
}
private String retrieveIdleTimeText(ContactItem contactItem, boolean isOnLeave) {
try {
//If user is away (not offline), last activity request is sent to client

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
/**
* Copyright (C) 2004-2011 Jive Software, 2026 Ignite Realtime Foundation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,15 +31,22 @@ import org.jivesoftware.spark.util.SwingWorker;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.settings.JiveInfo;
import org.jivesoftware.resource.Res;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.FullJid;
import javax.swing.*;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Jabber Version.
@ -122,7 +129,7 @@ public class JabberVersion implements Plugin {
private void viewClient() {
final JTextField field = new JTextField();
final ContactList contactList = SparkManager.getWorkspace().getContactList();
java.util.List<ContactItem> selectedUsers = contactList.getSelectedUsers();
List<ContactItem> selectedUsers = contactList.getSelectedUsers();
if (selectedUsers.size() == 1) {
ContactItem item = selectedUsers.get(0);
final Presence presence = item.getPresence();
@ -133,7 +140,14 @@ public class JabberVersion implements Plugin {
JOptionPane.INFORMATION_MESSAGE);
return;
}
final Jid jid = presence.getFrom();
// Collect all available full JIDs for the selected user.
List<Presence> presences = SparkManager.getRoster().getAvailablePresences(item.getJid());
List<FullJid> allFullJids = presences.stream()
.map(p -> p.getFrom().asFullJidIfPossible())
.filter(Objects::nonNull)
.collect(Collectors.toList());
SwingWorker worker = new SwingWorker() {
@Override
public Object construct() {
@ -143,12 +157,12 @@ public class JabberVersion implements Plugin {
catch (InterruptedException e1) {
// Nothing to do
}
return jid;
return allFullJids;
}
@Override
public void finished() {
VersionViewer.viewVersion(jid);
VersionViewer.viewVersion(allFullJids);
}
};
worker.start();

View File

@ -1,5 +1,5 @@
/**
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
* Copyright (C) 2004-2011 Jive Software, 2026 Ignite Realtime Foundation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,21 +26,63 @@ import org.jivesoftware.spark.UserManager;
import org.jivesoftware.spark.component.MessageDialog;
import org.jivesoftware.spark.util.ResourceUtils;
import org.jivesoftware.spark.util.log.Log;
import org.jxmpp.jid.FullJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.parts.Resourcepart;
import javax.swing.*;
import java.awt.*;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Collection;
public class VersionViewer {
private VersionViewer() {
}
public static void viewVersion(Jid jid) {
public static void viewVersion(Collection<FullJid> fullJids) {
final XMPPConnection connection = SparkManager.getConnection();
final JTabbedPane tabbedPane = new JTabbedPane();
for (FullJid fullJid : fullJids) {
final JPanel card = createResourceCard(connection, fullJid);
final Resourcepart resource = fullJid.getResourceOrNull();
final String title = (resource != null)
? resource.toString()
: fullJid.toString();
tabbedPane.addTab(title, card);
}
// Explanatory text above the tabs.
final JTextArea explanation = new JTextArea(Res.getString("message.client.information.multiple.resources"));
explanation.setEditable(false);
explanation.setLineWrap(true);
explanation.setWrapStyleWord(true);
explanation.setOpaque(false);
explanation.setBorder(BorderFactory.createEmptyBorder(5, 5, 8, 5));
explanation.setFont(UIManager.getFont("Label.font"));
final JPanel content = new JPanel(new BorderLayout());
content.add(explanation, BorderLayout.NORTH);
content.add(tabbedPane, BorderLayout.CENTER);
// Use the bare JID of the first entry for the dialog's header text.
final Jid first = fullJids.iterator().next();
MessageDialog.showComponent(
Res.getString("title.version.and.time"),
Res.getString("message.client.information", UserManager.unescapeJID(first.asBareJid())),
SparkRes.getImageIcon(SparkRes.Icon.PROFILE_IMAGE_24x24),
content,
SparkManager.getMainWindow(),
450, 420, false);
}
/**
* Builds a loading/data card pair (CardLayout) for a single resource and fires the Version + Time requests to
* populate it asynchronously.
*/
private static JPanel createResourceCard(final XMPPConnection connection, final Jid jid) {
final JPanel loadingCard = new JPanel();
final ImageIcon icon = new ImageIcon( VersionViewer.class.getClassLoader().getResource( "images/ajax-loader.gif"));
loadingCard.add(new JLabel("loading... ", icon, JLabel.CENTER));
@ -90,8 +132,7 @@ public class VersionViewer {
cards.add(loadingCard);
cards.add(dataCard);
final XMPPConnection connection = SparkManager.getConnection();
// Load Version
// Load version
final Version versionRequest = Version.builder(connection)
.ofType(IQ.Type.get)
.to(jid)
@ -132,7 +173,9 @@ public class VersionViewer {
cardLayout.last(cards);
});
MessageDialog.showComponent(Res.getString("title.version.and.time"), Res.getString("message.client.information", UserManager.unescapeJID(jid)), SparkRes.getImageIcon(SparkRes.Icon.PROFILE_IMAGE_24x24), cards, SparkManager.getMainWindow(), 400, 300, false);
// Wrap so the card pair sits nicely inside a tab.
final JPanel wrapper = new JPanel(new BorderLayout());
wrapper.add(cards, BorderLayout.CENTER);
return wrapper;
}
}

View File

@ -479,6 +479,7 @@ message.cert.verification.failed=Unable to verify certificate
message.chat.session.ended=Chat session has ended on {0}
message.click.to.open=Click to open
message.client.information=Client information for {0}
message.client.information.multiple.resources=A contact can be connected with more than one client at the same time. A tab is shown for each client that the contact is currently using. Each tab is named after the client's "resource-part": a unique identifier that distinguishes concurrent clients from one another.
message.close.other.chats=Close all other chats
message.close.stale.chats=Close stale chats
message.close.this.chat=Close this chat