diff --git a/src/main/java/org/zephyrsoft/sdb2/gui/MainWindow.java b/src/main/java/org/zephyrsoft/sdb2/gui/MainWindow.java index eae5b5b6..e2b3a1df 100644 --- a/src/main/java/org/zephyrsoft/sdb2/gui/MainWindow.java +++ b/src/main/java/org/zephyrsoft/sdb2/gui/MainWindow.java @@ -15,24 +15,7 @@ */ package org.zephyrsoft.sdb2.gui; -import static org.zephyrsoft.sdb2.model.VirtualScreen.SCREEN_A; -import static org.zephyrsoft.sdb2.model.VirtualScreen.SCREEN_B; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.Desktop; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.GraphicsEnvironment; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Image; -import java.awt.Insets; -import java.awt.KeyboardFocusManager; -import java.awt.Rectangle; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; @@ -53,6 +36,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; @@ -81,7 +65,6 @@ import org.zephyrsoft.sdb2.model.AddressablePart; import org.zephyrsoft.sdb2.model.ExportFormat; import org.zephyrsoft.sdb2.model.FilterTypeEnum; -import org.zephyrsoft.sdb2.model.ImageSong; import org.zephyrsoft.sdb2.model.ScreenContentsEnum; import org.zephyrsoft.sdb2.model.SelectableDisplay; import org.zephyrsoft.sdb2.model.Song; @@ -120,6 +103,9 @@ import say.swing.JFontChooser; +import static org.zephyrsoft.sdb2.model.VirtualScreen.SCREEN_A; +import static org.zephyrsoft.sdb2.model.VirtualScreen.SCREEN_B; + /** * Main window of the application. */ @@ -1343,7 +1329,10 @@ protected void handleAddImage() { try { File[] selectedFiles = chooser.getSelectedFiles(); for (File selectedFile : selectedFiles) { - presentModel.addSong(new ImageSong(selectedFile)); + Song imageSong = new Song(UUID.randomUUID().toString()); + imageSong.setTitle(selectedFile.getName()); + imageSong.setImage(selectedFile.getAbsoluteFile().toURI().toString()); + presentModel.addSong(imageSong); } presentList.setSelectedIndex(presentModel.getSize() - 1); } catch (Throwable ex) { @@ -2139,27 +2128,28 @@ public void mouseClicked(MouseEvent e) { } else if (e.getButton() == MouseEvent.BUTTON3) { // select item at mouse cursor position int index = presentList.locationToIndex(e.getPoint()); - if (presentModel.get(index) instanceof ImageSong imageSong) { + if (!StringTools.isEmpty(presentModel.get(index).getImage())) { + Song imageSong = presentModel.get(index); presentList.setSelectedIndex(index); JPopupMenu rotateMenu = new JPopupMenu(); JMenuItem rotate90 = new JMenuItem("rotate right by 90°"); rotate90.addActionListener(ae -> { - imageSong.setRotateRight(90); + imageSong.setImageRotationAsInt(90); presentList.updateUI(); }); JMenuItem rotate270 = new JMenuItem("rotate left by 90°"); rotate270.addActionListener(ae -> { - imageSong.setRotateRight(270); + imageSong.setImageRotationAsInt(270); presentList.updateUI(); }); JMenuItem rotate180 = new JMenuItem("rotate by 180°"); rotate180.addActionListener(ae -> { - imageSong.setRotateRight(180); + imageSong.setImageRotationAsInt(180); presentList.updateUI(); }); JMenuItem rotate0 = new JMenuItem("reset rotation"); rotate0.addActionListener(ae -> { - imageSong.setRotateRight(0); + imageSong.setImageRotationAsInt(0); presentList.updateUI(); }); rotateMenu.add(rotate90); diff --git a/src/main/java/org/zephyrsoft/sdb2/gui/SongCell.java b/src/main/java/org/zephyrsoft/sdb2/gui/SongCell.java index c3fc54c6..ddd5cc5e 100644 --- a/src/main/java/org/zephyrsoft/sdb2/gui/SongCell.java +++ b/src/main/java/org/zephyrsoft/sdb2/gui/SongCell.java @@ -21,12 +21,16 @@ import java.awt.GridBagLayout; import java.awt.Image; import java.awt.Insets; +import java.net.MalformedURLException; +import java.net.URI; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.border.EmptyBorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.zephyrsoft.sdb2.model.Song; import org.zephyrsoft.sdb2.util.StringTools; import org.zephyrsoft.sdb2.util.gui.ImageTools; @@ -35,6 +39,7 @@ * List entry for a {@link Song}. */ public class SongCell extends JPanel { + private static final Logger LOG = LoggerFactory.getLogger(SongCell.class); private static final long serialVersionUID = 6861947343987825552L; public static final int TITLE_BOTTOM_SPACE = 5; @@ -118,20 +123,25 @@ public void setFirstLine(String text) { } } - public void setImage(final String imageFile, int degreesToRotateRight) { - if (imageFile == null) { + public void setImage(final String imageUrl, int degreesToRotateRight) { + if (imageUrl == null) { this.image.setVisible(false); } else { - ImageIcon imageIcon = new ImageIcon(imageFile); - Image image = imageIcon.getImage(); - image = ImageTools.rotate(image, degreesToRotateRight); - double factor = (songTitle.getPreferredSize().getHeight() + firstLine.getPreferredSize().getHeight() - + TITLE_BOTTOM_SPACE + FIRSTLINE_BOTTOM_SPACE) * 2 / image.getHeight(null); - image = ImageTools.scale(image, factor); - this.image.setIcon(new ImageIcon(image)); - this.image.setText(""); - this.image.setVisible(true); - } + try { + ImageIcon imageIcon = new ImageIcon(URI.create(imageUrl).toURL()); + Image image = imageIcon.getImage(); + image = ImageTools.rotate(image, degreesToRotateRight); + double factor = (songTitle.getPreferredSize().getHeight() + firstLine.getPreferredSize().getHeight() + + TITLE_BOTTOM_SPACE + FIRSTLINE_BOTTOM_SPACE) * 2 / image.getHeight(null); + image = ImageTools.scale(image, factor); + this.image.setIcon(new ImageIcon(image)); + this.image.setText(""); + this.image.setVisible(true); + } catch (MalformedURLException e) { + this.image.setVisible(false); + LOG.warn("could not locate image {}", imageUrl, e); + } + } } @Override diff --git a/src/main/java/org/zephyrsoft/sdb2/gui/renderer/SongCellRenderer.java b/src/main/java/org/zephyrsoft/sdb2/gui/renderer/SongCellRenderer.java index 662e5a2a..6f0ae826 100644 --- a/src/main/java/org/zephyrsoft/sdb2/gui/renderer/SongCellRenderer.java +++ b/src/main/java/org/zephyrsoft/sdb2/gui/renderer/SongCellRenderer.java @@ -15,11 +15,9 @@ */ package org.zephyrsoft.sdb2.gui.renderer; -import java.awt.Color; -import java.awt.Component; +import java.awt.*; -import javax.swing.JList; -import javax.swing.ListCellRenderer; +import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.JTextComponent; @@ -29,10 +27,10 @@ import org.slf4j.LoggerFactory; import org.zephyrsoft.sdb2.Feature; import org.zephyrsoft.sdb2.gui.SongCell; -import org.zephyrsoft.sdb2.model.ImageSong; import org.zephyrsoft.sdb2.model.Song; import org.zephyrsoft.sdb2.model.SongParser; import org.zephyrsoft.sdb2.service.IndexerService; +import org.zephyrsoft.sdb2.util.StringTools; import org.zephyrsoft.sdb2.util.gui.SongCellHighlighter; /** @@ -131,8 +129,8 @@ public Component getListCellRendererComponent(JList list, Song v if (value != null) { ret.setSongTitle(value.getTitle() != null ? value.getTitle() : ""); ret.setFirstLine(SongParser.getFirstLyricsLine(value)); - if (value instanceof ImageSong imageSong) { - ret.setImage(imageSong.getFile().getAbsolutePath(), imageSong.getRotateRight()); + if (!StringTools.isEmpty(value.getImage())) { + ret.setImage(value.getImage(), value.getImageRotationAsInt()); } } diff --git a/src/main/java/org/zephyrsoft/sdb2/model/ImageSong.java b/src/main/java/org/zephyrsoft/sdb2/model/ImageSong.java deleted file mode 100644 index 2a19bb5b..00000000 --- a/src/main/java/org/zephyrsoft/sdb2/model/ImageSong.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.zephyrsoft.sdb2.model; - -import java.io.File; -import java.util.UUID; - -public class ImageSong extends Song { - private final File file; - private int rotateRight = 0; - - public ImageSong(final File file) { - super(UUID.randomUUID().toString()); - this.file = file; - } - - public File getFile() { - return file; - } - - public int getRotateRight() { - return rotateRight; - } - - public void setRotateRight(final int rotateRight) { - this.rotateRight = rotateRight; - } - @Override - public String getTitle() { - return file != null ? file.getName() : "empty"; - } - - @Override - public String getLyrics() { - return ""; - } - -} diff --git a/src/main/java/org/zephyrsoft/sdb2/model/Song.java b/src/main/java/org/zephyrsoft/sdb2/model/Song.java index 1428e018..d15def0e 100644 --- a/src/main/java/org/zephyrsoft/sdb2/model/Song.java +++ b/src/main/java/org/zephyrsoft/sdb2/model/Song.java @@ -66,7 +66,14 @@ public class Song implements Serializable, Comparable, Persistable { private String tempo; @XmlElement(name = "lyrics") private String lyrics; - + @XmlElement(name = "image") + private String image; + /** + * in degrees to rotate right + */ + @XmlElement(name = "imageRotation") + private String imageRotation; + /** * Create a song instance. CAUTION: every song has to have a UUID! This constructor is only necessary for * unmarshalling from XML. @@ -97,6 +104,8 @@ public Song(Song song) { drumNotes = song.getDrumNotes(); tempo = song.getTempo(); lyrics = song.getLyrics(); + image = song.getImage(); + imageRotation = song.getImageRotation(); } /** @@ -163,6 +172,18 @@ public String getDrumNotes() { return drumNotes; } + public String getImage() { + return image; + } + + public String getImageRotation() { + return imageRotation; + } + + public int getImageRotationAsInt() { + return imageRotation == null ? 0 : Integer.parseInt(imageRotation); + } + public String getCleanChordSequence() { return chordSequence == null ? null @@ -213,6 +234,18 @@ public void setChordSequence(String chordSequence) { this.chordSequence = chordSequence; } + public void setImage(String image) { + this.image = image; + } + + public void setImageRotation(String imageRotation) { + this.imageRotation = imageRotation; + } + + public void setImageRotationAsInt(int imageRotation) { + this.imageRotation = String.valueOf(imageRotation); + } + public String getUUID() { return uuid; } @@ -283,7 +316,9 @@ public boolean equals(Object obj) { equalsAllowNull(chordSequence, other.chordSequence) && equalsAllowNull(drumNotes, other.drumNotes) && equalsAllowNull(tempo, other.tempo) && - equalsAllowNull(lyrics, other.lyrics); + equalsAllowNull(lyrics, other.lyrics) && + equalsAllowNull(image, other.image) && + equalsAllowNull(imageRotation, other.imageRotation); } @Override @@ -319,7 +354,9 @@ && isEmpty(getLyrics()) && isEmpty(getTonality()) && isEmpty(getTempo()) && isEmpty(getDrumNotes()) - && isEmpty(getChordSequence()); + && isEmpty(getChordSequence()) + && isEmpty(getImage()) + && isEmpty(getImageRotation()); } public Map toMap() { @@ -340,6 +377,8 @@ public Map toMap() { put("tempo", getTempo()); put("drumNotes", getDrumNotes()); put("chordSequence", getChordSequence()); + put("image", getImage()); + put("imageRotation", getImageRotation()); } }; } @@ -390,6 +429,12 @@ public void fromMap(Map map) { case "chordSequence": setChordSequence(value); break; + case "image": + setImage(value); + break; + case "imageRotation": + setImageRotation(value); + break; } } } diff --git a/src/main/java/org/zephyrsoft/sdb2/presenter/PresenterWindow.java b/src/main/java/org/zephyrsoft/sdb2/presenter/PresenterWindow.java index 5a2469a3..530e5a3b 100644 --- a/src/main/java/org/zephyrsoft/sdb2/presenter/PresenterWindow.java +++ b/src/main/java/org/zephyrsoft/sdb2/presenter/PresenterWindow.java @@ -15,32 +15,17 @@ */ package org.zephyrsoft.sdb2.presenter; -import static org.zephyrsoft.sdb2.model.VirtualScreen.SCREEN_A; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Cursor; -import java.awt.Dimension; -import java.awt.GraphicsConfiguration; -import java.awt.Image; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.Toolkit; +import java.awt.*; import java.awt.geom.Rectangle2D; import java.awt.image.MemoryImageSource; import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URI; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; -import javax.swing.BorderFactory; -import javax.swing.ImageIcon; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRootPane; -import javax.swing.SwingUtilities; -import javax.swing.WindowConstants; +import javax.swing.*; import javax.swing.text.DefaultStyledDocument; import org.jdesktop.core.animation.timing.Animator; @@ -51,14 +36,16 @@ import org.slf4j.LoggerFactory; import org.zephyrsoft.sdb2.MainController; import org.zephyrsoft.sdb2.model.AddressablePart; -import org.zephyrsoft.sdb2.model.ImageSong; import org.zephyrsoft.sdb2.model.SelectableDisplay; import org.zephyrsoft.sdb2.model.VirtualScreen; import org.zephyrsoft.sdb2.model.settings.SettingKey; import org.zephyrsoft.sdb2.model.settings.SettingsModel; import org.zephyrsoft.sdb2.model.settings.VirtualScreenSettingsModel; +import org.zephyrsoft.sdb2.util.StringTools; import org.zephyrsoft.sdb2.util.gui.ImageTools; +import static org.zephyrsoft.sdb2.model.VirtualScreen.SCREEN_A; + /** * The presentation display for the lyrics. */ @@ -199,7 +186,7 @@ && noSettingsWereChanged()) { tp.setBaseColor(screenSettings.getBackgroundColor()); } - if (presentable.getSong() != null && !(presentable.getSong() instanceof ImageSong)) { + if (presentable.getSong() != null && StringTools.isEmpty(presentable.getSong().getImage())) { // create a SongView to render the song songView = new SongView.Builder(presentable.getSong()) .showTitle(screenSettings.isShowTitle()) @@ -239,26 +226,30 @@ && noSettingsWereChanged()) { songViewPanel.revalidate(); songViewPanel.repaint(); - } else if (presentable.getSong() instanceof ImageSong || presentable.getImage() != null) { + } else if (presentable.getImage() != null || ( presentable.getSong() != null && !StringTools.isEmpty(presentable.getSong().getImage()))) { // display the image (fullscreen, but with margin) - String imageFile = presentable.getSong() instanceof ImageSong imageSong - ? imageSong.getFile().getAbsolutePath() - : presentable.getImage(); - int rotateRight = presentable.getSong() instanceof ImageSong imageSong - ? imageSong.getRotateRight() - : 0; - ImageIcon imageIcon = new ImageIcon(imageFile); - Image image = imageIcon.getImage(); - image = ImageTools.rotate(image, rotateRight); - double factor = Math.min((screenSize.getWidth() - screenSettings.getLeftMargin() - screenSettings.getRightMargin()) - / image.getWidth(null), - (screenSize.getHeight() - screenSettings.getTopMargin() - screenSettings.getBottomMargin()) / image.getHeight(null)); - image = ImageTools.scale(image, factor); - JLabel imageComponent = new JLabel(new ImageIcon(image)); - imageComponent.setBorder(BorderFactory.createEmptyBorder(screenSettings.getTopMargin(), screenSettings.getLeftMargin(), - screenSettings - .getBottomMargin(), screenSettings.getRightMargin())); - contentPane.add(imageComponent, BorderLayout.CENTER); + try { + ImageIcon imageIcon = presentable.getImage() == null ? new ImageIcon(URI.create(presentable.getSong().getImage()).toURL()) + : new ImageIcon(presentable.getImage()); + Image image = imageIcon.getImage(); + if (presentable.getImage() == null) { + image = ImageTools.rotate(image, presentable.getSong().getImageRotationAsInt()); + } + double factor = Math.min((screenSize.getWidth() - screenSettings.getLeftMargin() - screenSettings.getRightMargin()) + / image.getWidth(null), + (screenSize.getHeight() - screenSettings.getTopMargin() - screenSettings.getBottomMargin()) / image.getHeight(null)); + image = ImageTools.scale(image, factor); + JLabel imageComponent = new JLabel(new ImageIcon(image)); + imageComponent.setBorder(BorderFactory.createEmptyBorder(screenSettings.getTopMargin(), screenSettings.getLeftMargin(), + screenSettings + .getBottomMargin(), screenSettings.getRightMargin())); + contentPane.add(imageComponent, BorderLayout.CENTER); + } catch (MalformedURLException e) { + LOG.warn("could not display image {}", presentable.getImage() == null + ? presentable.getSong().getImage() + : presentable.getImage(), e); + // display a blank screen, remove all content: already done + } } else { // display a blank screen, remove all content: already done } diff --git a/src/main/java/org/zephyrsoft/sdb2/remote/MQTT.java b/src/main/java/org/zephyrsoft/sdb2/remote/MQTT.java index 5c5c1708..68ffa09f 100644 --- a/src/main/java/org/zephyrsoft/sdb2/remote/MQTT.java +++ b/src/main/java/org/zephyrsoft/sdb2/remote/MQTT.java @@ -15,6 +15,7 @@ */ package org.zephyrsoft.sdb2.remote; +import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; @@ -98,6 +99,7 @@ public void publish(String topic, byte[] payload, int qos, boolean retained) { LOG.debug("Publishing message: {}", topic); try { client.publish(topic, payload, qos, retained); + LOG.debug("Payload: " + new String(payload, StandardCharsets.UTF_8)); } catch (Exception e) { // only log the exception LOG.warn("could not publish message", e);