diff --git a/build.gradle b/build.gradle
index 15e7fc02c..86e8eed14 100644
--- a/build.gradle
+++ b/build.gradle
@@ -45,7 +45,7 @@ ext.libs = [
tomlj : "com.electronwill.night-config:toml:3.6.6",
- editor : "com.fifesoft:rsyntaxtextarea:3.3.3",
+ editor : "com.fifesoft:rsyntaxtextarea:3.3.4",
editorDialogs : "com.fifesoft:rstaui:3.3.1",
junit : "junit:junit:4.13.2",
diff --git a/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/PathString.java b/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/PathString.java
deleted file mode 100644
index b057de3fb..000000000
--- a/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/PathString.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * This file is part of emuStudio.
- *
- * Copyright (C) 2006-2023 Peter Jakubčo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package net.emustudio.plugins.device.audiotape_player.gui;
-
-import java.awt.*;
-import java.nio.file.Path;
-import java.util.Objects;
-
-public class PathString {
- private final static int CHARS_SHOWN = 13; // chars shown from left and right
- private final static int MAX_FILE_NAME_SIZE = 2 * CHARS_SHOWN + 10;
-
- private final Path path;
- private final boolean showWholePath;
- private int maxStringLength = MAX_FILE_NAME_SIZE;
- private int charsShown = CHARS_SHOWN;
-
- public PathString(Path path, boolean showWholePath, Component component, int width) {
- this.path = Objects.requireNonNull(path);
- this.showWholePath = showWholePath;
- if (component != null) {
- deriveMaxStringLength(component, width);
- }
- }
-
- public PathString(Path path, boolean showWholePath) {
- this(path, showWholePath, null, 0);
- }
-
- public PathString(Path path) {
- this(path, false, null, 0);
- }
-
-
- public Path getPath() {
- return path;
- }
-
- public int getMaxStringLength() {
- return maxStringLength;
- }
-
- public void deriveMaxStringLength(Component component, int componentWidth) {
- FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
- String baseName = getBaseName();
- int baseNameWidth = fontMetrics.stringWidth(baseName);
-
- maxStringLength = baseName.length() * Math.min(componentWidth, baseNameWidth) / baseNameWidth;
- charsShown = Math.max(maxStringLength - 5, 0) / 2;
- }
-
- public String getPathShortened() {
- String baseName = getBaseName();
- int len = baseName.length();
- // shorten file name with 3 dots if it is too long
- if (len > maxStringLength) {
- return baseName.substring(0, charsShown) + "..." + baseName.substring(len - charsShown);
- }
- return baseName;
- }
-
- private String getBaseName() {
- return (showWholePath ? path : path.getFileName()).toString();
- }
-
- @Override
- public String toString() {
- return getPathShortened();
- }
-}
diff --git a/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/TapePlayerGui.java b/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/TapePlayerGui.java
index 87317c3c5..176597872 100644
--- a/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/TapePlayerGui.java
+++ b/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/TapePlayerGui.java
@@ -1,10 +1,7 @@
package net.emustudio.plugins.device.audiotape_player.gui;
import java.awt.*;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseMotionAdapter;
+import java.awt.event.*;
import java.nio.file.Path;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
@@ -14,6 +11,7 @@
import net.emustudio.emulib.runtime.interaction.BrowseButton;
import net.emustudio.emulib.runtime.interaction.CachedComboBoxModel;
import net.emustudio.emulib.runtime.interaction.Dialogs;
+import net.emustudio.emulib.runtime.interaction.ShortenedString;
import net.emustudio.plugins.device.audiotape_player.TapePlaybackController;
import net.miginfocom.swing.*;
@@ -29,19 +27,19 @@ public class TapePlayerGui extends JDialog {
private final JButton btnBrowse;
private final JButton btnRefresh = new JButton("Refresh");
private final JButton btnLoad = new JButton("Load");
- private final CachedComboBoxModel cmbDirsModel = new CachedComboBoxModel<>();
- private final JComboBox cmbDirs = new JComboBox<>(cmbDirsModel);
+ private final CachedComboBoxModel> cmbDirsModel = new CachedComboBoxModel<>();
+ private final JComboBox> cmbDirs = new JComboBox<>(cmbDirsModel);
private final TapesListModel lstTapesModel = new TapesListModel();
private final JList lstTapes = new JList<>(lstTapesModel);
private final JScrollPane scrollTapes = new JScrollPane(lstTapes);
- private final AtomicReference loadedFileName = new AtomicReference<>();
+ private final AtomicReference> loadedFileName = new AtomicReference<>();
private final JButton btnPlay = new JButton("Play");
private final JButton btnStop = new JButton("Stop");
private final JButton btnEject = new JButton("Eject");
- private final JLabel lblFileName = new JLabel("N/A");
+ private final JTextArea txtFileName = new JTextArea("N/A");
private final JLabel lblStatus = new JLabel("Stopped");
private final JTextArea txtEvents = new JTextArea();
@@ -54,7 +52,7 @@ public TapePlayerGui(JFrame parent, Dialogs dialogs, TapePlaybackController cont
this.controller = Objects.requireNonNull(controller);
btnBrowse = new BrowseButton(dialogs, "Select Directory", "Select", p -> {
- PathString ps = new PathString(p, true);
+ ShortenedString ps = new ShortenedString<>(p, Path::toString);
ps.deriveMaxStringLength(cmbDirs, cmbDirs.getWidth());
cmbDirsModel.add(ps);
cmbDirs.setSelectedIndex(0);
@@ -62,6 +60,7 @@ public TapePlayerGui(JFrame parent, Dialogs dialogs, TapePlaybackController cont
btnBrowse.setIcon(new ImageIcon(getClass().getResource(FOLDER_OPEN_ICON)));
btnBrowse.setText("");
btnBrowse.setToolTipText("Select directory");
+ btnBrowse.setFocusPainted(false);
initComponents();
setupListeners();
@@ -69,13 +68,11 @@ public TapePlayerGui(JFrame parent, Dialogs dialogs, TapePlaybackController cont
}
public void addProgramDetail(String program, String detail) {
- txtEvents.setText(txtEvents.getText() + "\n" + program + ": " + detail);
+ txtEvents.append("\n" + program + ": " + detail);
}
public void addPulseInfo(String pulse) {
- JLabel pulseLabel = new JLabel(pulse);
- pulseLabel.setFont(pulseLabel.getFont().deriveFont(Font.ITALIC));
- txtEvents.setText(txtEvents.getText() + "\n" + pulseLabel);
+ txtEvents.append("\n" + pulse);
}
public void setCassetteState(TapePlaybackController.CassetteState state) {
@@ -111,27 +108,30 @@ public void setCassetteState(TapePlaybackController.CassetteState state) {
btnLoad.setEnabled(true);
btnEject.setEnabled(false);
loadedFileName.set(null);
- lblFileName.setToolTipText("");
- lblFileName.setText("N/A");
+ txtFileName.setToolTipText("");
+ txtFileName.setText("N/A");
break;
}
}
+ @SuppressWarnings("unchecked")
private void setupListeners() {
cmbDirs.addActionListener(e -> {
- PathString path = (PathString) cmbDirs.getSelectedItem();
+ ShortenedString path = (ShortenedString) cmbDirs.getSelectedItem();
if (path != null) {
- lstTapesModel.reset(path.getPath());
+ lstTapesModel.reset(path.getValue());
}
});
cmbDirs.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
- PathString path = (PathString) cmbDirs.getSelectedItem();
+ ShortenedString path = (ShortenedString) cmbDirs.getSelectedItem();
if (path != null) {
- path.deriveMaxStringLength(cmbDirs, cmbDirs.getWidth());
- String dirName = path.getPath().toString();
+ // ComboBox has 2 components: text area and drop-down button. We need to eliminate button width.
+ // From observation, the drop-down button width is 36 pixels.
+ path.deriveMaxStringLength(cmbDirs, cmbDirs.getWidth() - 36);
+ String dirName = path.getValue().toString();
if (dirName.length() > path.getMaxStringLength()) {
cmbDirs.setToolTipText(dirName);
} else {
@@ -145,14 +145,14 @@ public void componentResized(ComponentEvent e) {
panelTapeInfo.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
- PathString ps = loadedFileName.get();
+ ShortenedString ps = loadedFileName.get();
if (ps != null) {
- ps.deriveMaxStringLength(panelTapeInfo, panelTapeInfo.getWidth());
- String shortened = ps.getPathShortened();
- if (shortened.length() < ps.getPath().toString().length()) {
- lblFileName.setToolTipText(ps.getPath().toString());
+ ps.deriveMaxStringLength(panelTapeInfo);
+ String shortened = ps.getShortenedString();
+ if (shortened.length() < ps.getValue().toString().length()) {
+ txtFileName.setToolTipText(ps.getValue().toString());
}
- lblFileName.setText(shortened);
+ txtFileName.setText(shortened);
}
}
});
@@ -185,10 +185,10 @@ public void componentResized(ComponentEvent e) {
Path path = lstTapesModel.getFilePath(index);
controller.load(path);
- PathString ps = new PathString(path);
+ ShortenedString ps = new ShortenedString<>(path, p -> p.getFileName().toString());
loadedFileName.set(ps);
- ps.deriveMaxStringLength(panelTapeInfo, panelTapeInfo.getWidth());
- lblFileName.setText(ps.getPathShortened());
+ ps.deriveMaxStringLength(panelTapeInfo);
+ txtFileName.setText(ps.getShortenedString());
}
});
btnPlay.addActionListener(e -> controller.play());
@@ -198,7 +198,7 @@ public void componentResized(ComponentEvent e) {
private void initComponents() {
JPanel panelAvailableTapes = new JPanel();
- JToolBar toolbarDirs = new JToolBar();
+ JPanel panelDirs = new JPanel();
JToolBar toolbarAvailableTapes = new JToolBar();
JPanel panelTape = new JPanel();
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, panelAvailableTapes, panelTape);
@@ -220,9 +220,12 @@ private void initComponents() {
panelAvailableTapes.setBorder(new TitledBorder("Available tapes"));
panelAvailableTapes.setLayout(new MigLayout("fill,insets 2", "[fill, grow]"));
- toolbarDirs.add(cmbDirs);
- toolbarDirs.add(btnBrowse);
- toolbarDirs.setFloatable(false);
+ panelDirs.setLayout(new MigLayout("fillx, hidemode 3", "[grow, fill][]", "[]"));
+ panelDirs.add(cmbDirs);
+ JToolBar btnBrowseToolBar = new JToolBar();
+ btnBrowseToolBar.add(btnBrowse);
+ btnBrowseToolBar.setFloatable(false);
+ panelDirs.add(btnBrowseToolBar);
btnRefresh.setIcon(new ImageIcon(getClass().getResource(REFRESH_ICON)));
btnLoad.setIcon(new ImageIcon(getClass().getResource(LOAD_ICON)));
@@ -234,13 +237,13 @@ private void initComponents() {
lstTapes.setCellRenderer(new TapesListRenderer());
- panelAvailableTapes.add(toolbarDirs, "cell 0 0, growx, pushx");
+ panelAvailableTapes.add(panelDirs, "cell 0 0, growx, pushx");
panelAvailableTapes.add(scrollTapes, "cell 0 1, growy, pushy");
panelAvailableTapes.add(toolbarAvailableTapes, "cell 0 2");
splitPane.setLeftComponent(panelAvailableTapes);
panelTape.setBorder(new TitledBorder("Audio Tape"));
- panelTape.setLayout(new MigLayout("fill,insets 2,hidemode 3", "[fill]", "[][][]"));
+ panelTape.setLayout(new MigLayout("fill,insets 2,debug", "[fill]", "[][][]"));
btnPlay.setIcon(new ImageIcon(getClass().getResource(PLAY_ICON)));
btnStop.setIcon(new ImageIcon(getClass().getResource(STOP_ICON)));
@@ -255,21 +258,26 @@ private void initComponents() {
lblStatus.setFont(lblStatus.getFont().deriveFont(lblStatus.getFont().getStyle() | Font.BOLD));
- panelTapeInfo.setLayout(new MigLayout("fillx,hidemode 3","[fill][fill]", "[][]"));
+ txtFileName.setEditable(false);
+ txtFileName.setLineWrap(true);
+ txtFileName.setBackground(UIManager.getColor("Panel.background"));
+
+ panelTapeInfo.setLayout(new MigLayout("fillx, debug","[][fill]", "[][]"));
panelTapeInfo.add(lblFileNameLabel, "cell 0 0,alignx right,growx 0");
- panelTapeInfo.add(lblFileName, "cell 1 0,push, grow");
+ panelTapeInfo.add(txtFileName, "cell 1 0, growx");
panelTapeInfo.add(lblStatusLabel, "cell 0 1,alignx right,growx 0");
- panelTapeInfo.add(lblStatus, "cell 1 1,pushx,growx");
+ panelTapeInfo.add(lblStatus, "cell 1 1,growx");
panelTape.add(panelTapeInfo, "cell 0 0");
panelTape.add(scrollEvents, "cell 0 1, growy, pushy");
panelTape.add(toolbarTape, "cell 0 2");
splitPane.setRightComponent(panelTape);
- contentPane.add(splitPane, "cell 0 0");
+ contentPane.add(splitPane, "cell 0 0, push, grow");
pack();
setLocationRelativeTo(getOwner());
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ getRootPane().registerKeyboardAction(e -> dispose(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
}
}
diff --git a/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/TapesListModel.java b/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/TapesListModel.java
index fee44b09c..74dccc748 100644
--- a/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/TapesListModel.java
+++ b/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/gui/TapesListModel.java
@@ -18,6 +18,7 @@
*/
package net.emustudio.plugins.device.audiotape_player.gui;
+import net.emustudio.emulib.runtime.interaction.ShortenedString;
import net.emustudio.plugins.device.audiotape_player.loaders.Loader;
import net.jcip.annotations.NotThreadSafe;
import org.slf4j.Logger;
@@ -38,7 +39,7 @@
public class TapesListModel extends DefaultListModel {
private final static Logger LOGGER = LoggerFactory.getLogger(TapesListModel.class);
- private List files = Collections.emptyList();
+ private List> files = Collections.emptyList();
private Path directory;
private final AtomicReference componentRef = new AtomicReference<>();
private int componentWidth;
@@ -52,19 +53,19 @@ public void reset(Path directory) {
clear();
this.files = listPaths(directory);
Component c = componentRef.get();
- for (PathString file : files) {
+ for (ShortenedString file : files) {
if (c != null) {
file.deriveMaxStringLength(c, componentWidth);
}
- addElement(file.getPathShortened());
+ addElement(file.getShortenedString());
}
}
public Path getFilePath(int index) {
- return files.get(index).getPath();
+ return files.get(index).getValue();
}
- private static List listPaths(Path directory) {
+ private static List> listPaths(Path directory) {
if (directory == null) {
return Collections.emptyList();
}
@@ -72,7 +73,7 @@ private static List listPaths(Path directory) {
return stream
.filter(Files::isReadable)
.filter(Loader::hasLoader)
- .map(PathString::new)
+ .map(p -> new ShortenedString<>(p, pp -> pp.getFileName().toString()))
.collect(Collectors.toList());
} catch (IOException e) {
LOGGER.error("Could not load tape files from directory: " + directory, e);