182 lines
6.7 KiB
Java
182 lines
6.7 KiB
Java
package ctbrec.ui;
|
|
|
|
import ctbrec.Config;
|
|
import ctbrec.event.EventBusHolder;
|
|
import ctbrec.recorder.Recorder;
|
|
import ctbrec.ui.controls.Dialogs;
|
|
import javafx.application.Platform;
|
|
import javafx.stage.Stage;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.font.LineMetrics;
|
|
import java.awt.geom.Rectangle2D;
|
|
import java.awt.image.BufferedImage;
|
|
import java.io.IOException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.Map;
|
|
|
|
import static java.awt.Font.BOLD;
|
|
import static java.awt.RenderingHints.KEY_ANTIALIASING;
|
|
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
|
|
|
|
public class TrayIcon {
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(TrayIcon.class);
|
|
|
|
private final Stage stage;
|
|
private final Recorder recorder;
|
|
private SystemTray tray;
|
|
private java.awt.TrayIcon awtTrayIcon;
|
|
private BufferedImage background;
|
|
|
|
public TrayIcon(Stage stage, Recorder recorder) {
|
|
this.stage = stage;
|
|
this.recorder = recorder;
|
|
}
|
|
|
|
boolean createTrayIcon() {
|
|
if (SystemTray.isSupported()) {
|
|
if (tray == null) {
|
|
String title = CamrecApplication.title;
|
|
tray = SystemTray.getSystemTray();
|
|
BufferedImage image = null;
|
|
try {
|
|
image = createImage(recorder.getCurrentlyRecording().size());
|
|
} catch (Exception e) {
|
|
// fail silently
|
|
}
|
|
|
|
PopupMenu menu = createTrayContextMenu(stage);
|
|
awtTrayIcon = new java.awt.TrayIcon(image, title, menu);
|
|
awtTrayIcon.setImageAutoSize(true);
|
|
awtTrayIcon.setToolTip(title);
|
|
try {
|
|
tray.add(awtTrayIcon);
|
|
} catch (AWTException e) {
|
|
LOG.error("Couldn't add tray icon", e);
|
|
}
|
|
awtTrayIcon.addMouseListener(new MouseAdapter() {
|
|
@Override
|
|
public void mousePressed(MouseEvent e) {
|
|
if (SwingUtilities.isLeftMouseButton(e)) {
|
|
toggleVisibility(stage);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return true;
|
|
} else {
|
|
LOG.error("SystemTray notifications not supported by this OS");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private PopupMenu createTrayContextMenu(Stage stage) {
|
|
var menu = new PopupMenu();
|
|
var show = new MenuItem("Show");
|
|
show.addActionListener(evt -> restoreStage(stage));
|
|
menu.add(show);
|
|
menu.addSeparator();
|
|
var pauseRecording = new MenuItem("Pause recording");
|
|
pauseRecording.addActionListener(evt -> {
|
|
try {
|
|
recorder.pause();
|
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
|
|
Dialogs.showError(stage.getScene(), "Pausing recording", "Pausing of the recorder failed", e);
|
|
}
|
|
});
|
|
menu.add(pauseRecording);
|
|
var resumeRecording = new MenuItem("Resume recording");
|
|
resumeRecording.addActionListener(evt -> {
|
|
try {
|
|
recorder.resume();
|
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
|
|
Dialogs.showError(stage.getScene(), "Resuming recording", "Resuming of the recorder failed", e);
|
|
}
|
|
});
|
|
menu.add(resumeRecording);
|
|
menu.addSeparator();
|
|
var exit = new MenuItem("Exit");
|
|
exit.addActionListener(evt -> exit());
|
|
menu.add(exit);
|
|
return menu;
|
|
}
|
|
|
|
private void restoreStage(Stage stage) {
|
|
Platform.runLater(() -> {
|
|
stage.setX(Config.getInstance().getSettings().windowX);
|
|
stage.setY(Config.getInstance().getSettings().windowY);
|
|
LOG.debug("Restoring window location {},{}", stage.getX(), stage.getY());
|
|
stage.setIconified(false);
|
|
stage.show();
|
|
stage.toFront();
|
|
EventBusHolder.BUS.post(Map.of("event", "stage_restored"));
|
|
});
|
|
}
|
|
|
|
private void toggleVisibility(Stage stage) {
|
|
if (stage.isShowing()) {
|
|
Platform.runLater(stage::hide);
|
|
} else {
|
|
restoreStage(stage);
|
|
}
|
|
}
|
|
|
|
private void exit() {
|
|
EventBusHolder.BUS.post(Map.of("event", "shutdown"));
|
|
}
|
|
|
|
public void displayMessage(String header, String msg, java.awt.TrayIcon.MessageType info) {
|
|
createTrayIcon();
|
|
awtTrayIcon.displayMessage(header, msg, info);
|
|
}
|
|
|
|
public void updateActiveRecordings(int activeRecordings) {
|
|
try {
|
|
createTrayIcon();
|
|
if (awtTrayIcon != null) {
|
|
awtTrayIcon.setImage(createImage(activeRecordings));
|
|
}
|
|
} catch (IOException e) {
|
|
LOG.error("Couldn't update tray icon image", e);
|
|
}
|
|
}
|
|
|
|
private BufferedImage createImage(int number) throws IOException {
|
|
if (this.background == null) {
|
|
this.background = ImageIO.read(TrayIcon.class.getResource("/icon64.png"));
|
|
}
|
|
|
|
BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
|
|
Graphics2D g2 = (Graphics2D) image.getGraphics();
|
|
g2.drawImage(background, 0, 0, (img, infoflags, x, y, width, height) -> false);
|
|
if (number > 0 && Config.getInstance().getSettings().showActiveRecordingsInTray) {
|
|
g2.setColor(Color.decode("#dc4444"));
|
|
g2.fillOval(0, 0, 64, 64);
|
|
|
|
String text = String.valueOf(number);
|
|
String fontFamily = Config.getInstance().getSettings().showActiveRecordingsInTrayFont;
|
|
int fontSize = Config.getInstance().getSettings().showActiveRecordingsInTrayFontSize;
|
|
g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
|
|
Font font = new Font(fontFamily, BOLD, fontSize);
|
|
g2.setFont(font);
|
|
FontMetrics fontMetrics = g2.getFontMetrics(font);
|
|
LineMetrics lineMetrics = fontMetrics.getLineMetrics(text, g2);
|
|
Rectangle2D stringBounds = fontMetrics.getStringBounds(text, g2);
|
|
g2.setColor(Color.decode(Config.getInstance().getSettings().showActiveRecordingsInTrayColor));
|
|
int x = (int) (image.getWidth() - stringBounds.getWidth()) / 2;
|
|
int y = (int) (((image.getHeight() - lineMetrics.getAscent()) / 2) - 8 - stringBounds.getY());
|
|
g2.drawString(text, x, y);
|
|
g2.dispose();
|
|
}
|
|
return image;
|
|
}
|
|
}
|