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);