This commit is contained in:
Johannes Wolf 2026-01-26 14:03:57 -06:00 committed by GitHub
commit 63afa1ddf0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 299 additions and 84 deletions

View file

@ -0,0 +1,177 @@
package mage.client.components;
import mage.cards.decks.DeckFileFilter;
import mage.client.deck.generator.DeckGenerator;
import mage.client.util.RecentDecklistUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class DecklistChooser extends JPanel
{
private final String CLEAR_ITEM = "Clear recent decks";
private final JButton chooseButton;
private final JButton generateButton;
private final JComboBox<String> decklistCombobox;
private boolean inUpdate = false;
public DecklistChooser() {
chooseButton = new JButton("...");
chooseButton.setToolTipText("Select deck file...");
generateButton = new JButton("Generate");
generateButton.setToolTipText("Generate a new deck...");
decklistCombobox = new JComboBox<>();
setupControls();
update();
}
private void setupControls() {
chooseButton.addActionListener(e -> chooseFile());
generateButton.addActionListener(e -> generateDeck());
decklistCombobox.setEditable(true);
// We set some arbitrary width to prevent the combobox from growing
// very wide for long paths.
decklistCombobox.setPrototypeDisplayValue("xxxxxxxxxxxxxx");
decklistCombobox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent event) {
if (!inUpdate) {
if (event.getStateChange() == ItemEvent.SELECTED) {
Object selection = event.getItem();
if (selection == CLEAR_ITEM) {
clear();
} else if (selection != null) {
RecentDecklistUtil.putRecentDecklistFiles(selection.toString());
update();
}
}
}
}
});
// Set up tooltips for combobox items so very long paths don't get truncated.
decklistCombobox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
JLabel c = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value != null) {
c.setToolTipText(value.toString());
}
return c;
}
});
setLayout(new BorderLayout());
add(decklistCombobox, BorderLayout.CENTER);
JPanel rightPanel = new JPanel(new BorderLayout());
rightPanel.add(chooseButton, BorderLayout.WEST);
rightPanel.add(generateButton, BorderLayout.EAST);
add(rightPanel, BorderLayout.EAST);
}
/**
* Update the control.
*/
public void update() {
try {
inUpdate = true;
decklistCombobox.removeAllItems();
List<String> files = RecentDecklistUtil.getRecentDecklistFiles();
for (String filename : files)
decklistCombobox.addItem(filename);
if (!files.isEmpty())
decklistCombobox.addItem(CLEAR_ITEM);
if (decklistCombobox.getItemCount() > 0)
decklistCombobox.setSelectedIndex(0);
} finally {
inUpdate = false;
}
}
/**
* Clear all recent files.
*/
public void clear() {
RecentDecklistUtil.clearRecentDecklistFiles();
update();
}
/**
* Sets the current file to the filename given.
* Also adds that file to the recent list.
*
* @param filename The filename to add
*/
public void setFile(String filename) {
RecentDecklistUtil.putRecentDecklistFiles(filename);
update();
}
/**
* @return The currently entered/selected filename
*/
public String getFile() {
Object selection = decklistCombobox.getEditor().getItem();
if (selection != null)
return selection.toString();
return "";
}
/**
* Let the user select a file on their system by presenting
* a file chooser.
*/
public void chooseFile() {
JFileChooser chooser = new JFileChooser();
chooser.setAcceptAllFileFilterUsed(false);
chooser.addChoosableFileFilter(new DeckFileFilter("dck", "XMage's deck files (*.dck)"));
Object item = decklistCombobox.getEditor().getItem();
if (item != null) {
File currentFile = new File(item.toString());
chooser.setCurrentDirectory(currentFile);
}
int ret = chooser.showDialog(decklistCombobox, "Select Deck");
if (ret == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
try {
RecentDecklistUtil.putRecentDecklistFiles(file.getCanonicalPath());
update();
} catch (IOException ignore) {
}
}
}
/**
* Generate a new deck using the Generate Deck dialog.
*/
private void generateDeck() {
String filename = DeckGenerator.generateDeck();
if (filename != null) {
RecentDecklistUtil.putRecentDecklistFiles(filename);
update();
}
}
}

View file

@ -8,6 +8,7 @@ import mage.remote.Session;
import org.apache.log4j.Logger;
import javax.swing.*;
import java.awt.*;
import java.util.UUID;
/**
@ -39,12 +40,15 @@ public class JoinTableDialog extends MageDialog {
this.isTournament = isTournament;
this.newPlayerPanel.setPlayerName(SessionHandler.getUserName());
this.newPlayerPanel.showDeckElements(!isLimited);
this.newPlayerPanel.loadRecentDeckFiles();
this.setModal(true);
this.setLocation(100, 100);
this.setVisible(true);
this.setResizable(true);
this.setMinimumSize(this.getPreferredSize());
this.setMaximumSize(new Dimension(Short.MAX_VALUE, this.getPreferredSize().height));
}
/**
* This method is called from within the constructor to
* initialize the form.

View file

@ -835,7 +835,8 @@ public class NewTableDialog extends MageDialog {
this.roomId = roomId;
if (!lastSessionId.equals(SessionHandler.getSessionId())) {
lastSessionId = SessionHandler.getSessionId();
this.player1Panel.setPlayerName(SessionHandler.getUserName());
player1Panel.setPlayerName(SessionHandler.getUserName());
player1Panel.loadRecentDeckFiles();
cbGameType.setModel(new DefaultComboBoxModel(SessionHandler.getGameTypes().toArray()));
cbDeckType.setModel(new DefaultComboBoxModel(SessionHandler.getDeckTypes()));
selectLimitedByDefault();

View file

@ -18,6 +18,7 @@ import mage.client.MageFrame;
import mage.client.SessionHandler;
import mage.client.table.TournamentPlayerPanel;
import mage.client.util.IgnoreList;
import mage.client.util.RecentDecklistUtil;
import mage.client.util.gui.FastSearchUtil;
import mage.constants.*;
import mage.game.GameException;
@ -97,8 +98,9 @@ public class NewTournamentDialog extends MageDialog {
this.roomId = roomId;
if (!lastSessionId.equals(SessionHandler.getSessionId())) {
lastSessionId = SessionHandler.getSessionId();
this.player1Panel.setPlayerName(SessionHandler.getUserName());
this.player1Panel.showLevel(false); // no computer
player1Panel.setPlayerName(SessionHandler.getUserName());
player1Panel.showLevel(false); // no computer
player1Panel.loadRecentDeckFiles();
cbTournamentType.setModel(new DefaultComboBoxModel(SessionHandler.getTournamentTypes().toArray()));
cbGameType.setModel(new DefaultComboBoxModel(SessionHandler.getTournamentGameTypes().toArray()));
@ -809,7 +811,7 @@ public class NewTournamentDialog extends MageDialog {
fcSelectDeck.setAcceptAllFileFilterUsed(false);
fcSelectDeck.addChoosableFileFilter(new DeckFileFilter("dck", "XMage's deck files (*.dck)"));
}
String lastFolder = MageFrame.getPreferences().get("lastDeckFolder", "");
String lastFolder = RecentDecklistUtil.getRecentDecklistDir();
if (!lastFolder.isEmpty()) {
fcSelectDeck.setCurrentDirectory(new File(lastFolder));
}
@ -829,7 +831,7 @@ public class NewTournamentDialog extends MageDialog {
fcJumpstartSelectDeck.setAcceptAllFileFilterUsed(false);
fcJumpstartSelectDeck.addChoosableFileFilter(new DeckFileFilter("txt", "Jumpstart Packs (*.txt)"));
}
String lastFolder = MageFrame.getPreferences().get("lastDeckFolder", "");
String lastFolder = RecentDecklistUtil.getRecentDecklistDir();
if (!lastFolder.isEmpty()) {
fcJumpstartSelectDeck.setCurrentDirectory(new File(lastFolder));
}

View file

@ -330,6 +330,10 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_CONNECT_AUTO_CONNECT = "autoConnect";
public static final String KEY_CONNECT_FLAG = "connectFlag";
// Settings for the New/Join Table Dialog
public static final String KEY_RECENT_DECKLIST_FILES = "recentDecklistFiles"; // Semicolon separated list of filenames
public static final String KEY_MAX_RECENT_DECKLIST_FLIES = "numRecentDecklistFiles"; // Maximum number of recent decks to remember
// auto-update settings on first run
public static final String KEY_SETTINGS_VERSION = "settingsVersion";

View file

@ -4,10 +4,11 @@ import com.google.common.collect.Sets;
import mage.client.MageFrame;
import mage.client.util.ClientDefaultSettings;
import java.util.Date;
import java.util.Set;
import java.io.File;
import java.util.*;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
// TODO: Move all preference related logic from MageFrame and PreferencesDialog to this class.
public final class MagePreferences {

View file

@ -1,11 +1,9 @@
package mage.client.table;
import java.io.File;
import java.io.IOException;
import java.awt.*;
import javax.swing.*;
import mage.cards.decks.DeckFileFilter;
import mage.client.MageFrame;
import mage.client.components.DecklistChooser;
import mage.client.deck.generator.DeckGenerator;
import mage.client.util.ClientDefaultSettings;
@ -15,78 +13,56 @@ import mage.client.util.ClientDefaultSettings;
*/
public class NewPlayerPanel extends javax.swing.JPanel {
private final JFileChooser fcSelectDeck;
public NewPlayerPanel() {
initComponents();
fcSelectDeck = new JFileChooser();
fcSelectDeck.setAcceptAllFileFilterUsed(false);
fcSelectDeck.addChoosableFileFilter(new DeckFileFilter("dck", "XMage's deck files (*.dck)"));
this.txtPlayerDeck.setText("");
this.txtPlayerName.setText(ClientDefaultSettings.computerName);
txtPlayerName.setText(ClientDefaultSettings.computerName);
}
/**
* Fill the combobox with the recent n filenames.
*/
public void loadRecentDeckFiles() {
decklist.update();
}
public void setPlayerName(String playerName) {
this.txtPlayerName.setText(playerName);
this.txtPlayerName.setEditable(false);
this.txtPlayerName.setEnabled(false);
}
protected void playerLoadDeck() {
String lastFolder = MageFrame.getPreferences().get("lastDeckFolder", "");
if (!lastFolder.isEmpty()) {
fcSelectDeck.setCurrentDirectory(new File(lastFolder));
}
int ret = fcSelectDeck.showDialog(this, "Select Deck");
if (ret == JFileChooser.APPROVE_OPTION) {
File file = fcSelectDeck.getSelectedFile();
this.txtPlayerDeck.setText(file.getPath());
try {
MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath());
} catch (IOException ex) {
}
}
fcSelectDeck.setSelectedFile(null);
txtPlayerName.setText(playerName);
txtPlayerName.setEditable(false);
txtPlayerName.setEnabled(false);
}
protected void generateDeck() {
String path = DeckGenerator.generateDeck();
if (path == null) {
return;
}
this.txtPlayerDeck.setText(path);
decklist.setFile(DeckGenerator.generateDeck());
}
public String getPlayerName() {
return this.txtPlayerName.getText();
return txtPlayerName.getText();
}
public String getDeckFile() {
return this.txtPlayerDeck.getText();
return decklist.getFile();
}
public void setDeckFile(String deckFile) {
this.txtPlayerDeck.setText(deckFile);
public void setDeckFile(String filename) {
decklist.setFile(filename);
}
public void setSkillLevel(int level) {
this.spnLevel.setValue(level);
spnLevel.setValue(level);
}
public int getSkillLevel() {
return (Integer) spnLevel.getValue();
return (Integer)spnLevel.getValue();
}
public void showLevel(boolean show) {
this.spnLevel.setVisible(show);
this.lblLevel.setVisible(show);
spnLevel.setVisible(show);
lblLevel.setVisible(show);
}
public void showDeckElements(boolean show) {
this.lblPlayerDeck.setVisible(show);
this.txtPlayerDeck.setVisible(show);
this.btnGenerate.setVisible(show);
this.btnPlayerDeck.setVisible(show);
lblPlayerDeck.setVisible(show);
decklist.setVisible(show);
}
/**
@ -101,9 +77,7 @@ public class NewPlayerPanel extends javax.swing.JPanel {
lblPlayerName = new javax.swing.JLabel();
txtPlayerName = new javax.swing.JTextField();
lblPlayerDeck = new javax.swing.JLabel();
txtPlayerDeck = new javax.swing.JTextField();
btnPlayerDeck = new javax.swing.JButton();
btnGenerate = new javax.swing.JButton();
decklist = new DecklistChooser();
lblLevel = new javax.swing.JLabel();
spnLevel = new javax.swing.JSpinner();
@ -111,12 +85,6 @@ public class NewPlayerPanel extends javax.swing.JPanel {
lblPlayerDeck.setText("Deck:");
btnPlayerDeck.setText("...");
btnPlayerDeck.addActionListener(evt -> btnPlayerDeckActionPerformed(evt));
btnGenerate.setText("Generate");
btnGenerate.addActionListener(evt -> btnGenerateActionPerformed(evt));
lblLevel.setText("Skill:");
spnLevel.setModel(new javax.swing.SpinnerNumberModel(2, 1, 10, 1));
@ -134,13 +102,9 @@ public class NewPlayerPanel extends javax.swing.JPanel {
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(txtPlayerName, javax.swing.GroupLayout.DEFAULT_SIZE, 310, Short.MAX_VALUE)
.addComponent(txtPlayerDeck, javax.swing.GroupLayout.DEFAULT_SIZE, 310, Short.MAX_VALUE))
.addComponent(decklist, javax.swing.GroupLayout.DEFAULT_SIZE, 310, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addComponent(btnPlayerDeck, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(btnGenerate, javax.swing.GroupLayout.PREFERRED_SIZE, 87, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(lblLevel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
@ -158,28 +122,16 @@ public class NewPlayerPanel extends javax.swing.JPanel {
.addGap(3, 3, 3)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblPlayerDeck)
.addComponent(txtPlayerDeck, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(btnPlayerDeck, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(btnGenerate, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addComponent(decklist, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
);
}// </editor-fold>//GEN-END:initComponents
private void btnPlayerDeckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPlayerDeckActionPerformed
playerLoadDeck();
}//GEN-LAST:event_btnPlayerDeckActionPerformed
private void btnGenerateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnGenerateActionPerformed
generateDeck();
}//GEN-LAST:event_btnGenerateActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton btnGenerate;
private javax.swing.JButton btnPlayerDeck;
private DecklistChooser decklist;
private javax.swing.JLabel lblLevel;
private javax.swing.JLabel lblPlayerDeck;
private javax.swing.JLabel lblPlayerName;
private javax.swing.JSpinner spnLevel;
private javax.swing.JTextField txtPlayerDeck;
private javax.swing.JTextField txtPlayerName;
// End of variables declaration//GEN-END:variables

View file

@ -129,6 +129,7 @@ public class TablePlayerPanel extends javax.swing.JPanel {
if (getPlayerType() != PlayerType.HUMAN) {
this.newPlayerPanel.setVisible(true);
this.newPlayerPanel.setPlayerName(extractAiPlayerNumberFromLabel(this.lblPlayerNum.getText()));
this.newPlayerPanel.loadRecentDeckFiles();
} else {
this.newPlayerPanel.setVisible(false);
}

View file

@ -0,0 +1,73 @@
package mage.client.util;
import mage.client.MageFrame;
import mage.client.dialog.PreferencesDialog;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
public class RecentDecklistUtil {
/**
* @return The most recent decklist directory
*/
public static String getRecentDecklistDir() {
List<String> list = getRecentDecklistFiles();
if (!list.isEmpty()) {
File file = new File(list.get(0));
return file.getParent();
}
return "";
}
/**
* Get the list of recently used deck filenames.
* Only filenames that point to readable files are returned.
*
* @return A list of filenames
*/
public static List<String> getRecentDecklistFiles() {
final int limit = 10;
Preferences prefs = MageFrame.getPreferences();
return Arrays.stream(prefs.get(PreferencesDialog.KEY_RECENT_DECKLIST_FILES, "").split(";"))
.limit(limit)
.filter(filename -> {
File file = new File(filename);
return file.canRead() && file.isFile();
})
.collect(Collectors.toList());
}
/**
* Clears the list of recent deck files and removes all items from the combobox
*/
public static void clearRecentDecklistFiles() {
Preferences prefs = MageFrame.getPreferences();
prefs.put(PreferencesDialog.KEY_RECENT_DECKLIST_FILES, "");
}
/**
* Put a filename to the list of recent decks.
*
* @param filename Filename of the deck to add
*/
public static void putRecentDecklistFiles(String filename) {
if (filename == null || filename.isEmpty() || filename.contains(";"))
return;
File file = new File(filename);
if (!file.canRead())
return;
List<String> current = new ArrayList<>(getRecentDecklistFiles());
current.removeIf(filename::equals);
current.add(0, filename);
Preferences prefs = MageFrame.getPreferences();
prefs.put(PreferencesDialog.KEY_RECENT_DECKLIST_FILES, String.join(";", current));
}
}