add minimize to system tray functionality

This commit is contained in:
Craig Raw 2021-04-07 09:11:59 +02:00
parent cb884d97cb
commit 8fc971c07c
7 changed files with 123 additions and 1 deletions

View file

@ -92,6 +92,9 @@ public class AppController implements Initializable {
@FXML @FXML
private Menu fileMenu; private Menu fileMenu;
@FXML
private Menu viewMenu;
@FXML @FXML
private Menu toolsMenu; private Menu toolsMenu;
@ -116,6 +119,9 @@ public class AppController implements Initializable {
@FXML @FXML
private CheckMenuItem showTxHex; private CheckMenuItem showTxHex;
@FXML
private MenuItem minimizeToTray;
@FXML @FXML
private MenuItem refreshWallet; private MenuItem refreshWallet;
@ -285,6 +291,10 @@ public class AppController implements Initializable {
} else if(platform == org.controlsfx.tools.Platform.WINDOWS) { } else if(platform == org.controlsfx.tools.Platform.WINDOWS) {
toolsMenu.getItems().removeIf(item -> item.getStyleClass().contains("windowsHide")); toolsMenu.getItems().removeIf(item -> item.getStyleClass().contains("windowsHide"));
} }
if(platform == org.controlsfx.tools.Platform.UNIX || !TrayManager.isSupported()) {
viewMenu.getItems().remove(minimizeToTray);
}
} }
public void showIntroduction(ActionEvent event) { public void showIntroduction(ActionEvent event) {
@ -953,6 +963,10 @@ public class AppController implements Initializable {
messageSignDialog.showAndWait(); messageSignDialog.showAndWait();
} }
public void minimizeToTray(ActionEvent event) {
AppServices.get().minimizeStage((Stage)tabs.getScene().getWindow());
}
public void refreshWallet(ActionEvent event) { public void refreshWallet(ActionEvent event) {
Tab selectedTab = tabs.getSelectionModel().getSelectedItem(); Tab selectedTab = tabs.getSelectionModel().getSelectedItem();
TabData tabData = (TabData)selectedTab.getUserData(); TabData tabData = (TabData)selectedTab.getUserData();

View file

@ -9,6 +9,7 @@ import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.uri.BitcoinURI; import com.sparrowwallet.drongo.uri.BitcoinURI;
import com.sparrowwallet.drongo.wallet.KeystoreSource; import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.control.TrayManager;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Device; import com.sparrowwallet.sparrow.io.Device;
@ -64,6 +65,8 @@ public class AppServices {
private final Map<Window, List<WalletTabData>> walletWindows = new LinkedHashMap<>(); private final Map<Window, List<WalletTabData>> walletWindows = new LinkedHashMap<>();
private TrayManager trayManager;
private static final BooleanProperty onlineProperty = new SimpleBooleanProperty(false); private static final BooleanProperty onlineProperty = new SimpleBooleanProperty(false);
private ExchangeSource.RatesService ratesService; private ExchangeSource.RatesService ratesService;
@ -401,6 +404,15 @@ public class AppServices {
return application; return application;
} }
public void minimizeStage(Stage stage) {
if(trayManager == null) {
trayManager = new TrayManager();
}
trayManager.addStage(stage);
stage.hide();
}
public Map<Wallet, Storage> getOpenWallets() { public Map<Wallet, Storage> getOpenWallets() {
Map<Wallet, Storage> openWallets = new LinkedHashMap<>(); Map<Wallet, Storage> openWallets = new LinkedHashMap<>();
for(List<WalletTabData> walletTabDataList : walletWindows.values()) { for(List<WalletTabData> walletTabDataList : walletWindows.values()) {

View file

@ -0,0 +1,95 @@
package com.sparrowwallet.sparrow.control;
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.image.BaseMultiResolutionImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TrayManager {
private static final Logger log = LoggerFactory.getLogger(TrayManager.class);
private final SystemTray tray;
private final TrayIcon trayIcon;
private final PopupMenu popupMenu = new PopupMenu();
public TrayManager() {
if(!SystemTray.isSupported()) {
throw new UnsupportedOperationException("SystemTray icons are not supported by the current desktop environment.");
}
tray = SystemTray.getSystemTray();
try {
List<Image> imgList = new ArrayList<>();
imgList.add(ImageIO.read(getClass().getResource("/image/sparrow-white-small.png")));
imgList.add(ImageIO.read(getClass().getResource("/image/sparrow-white-small@2x.png")));
imgList.add(ImageIO.read(getClass().getResource("/image/sparrow-white-small@3x.png")));
BaseMultiResolutionImage mrImage = new BaseMultiResolutionImage(imgList.toArray(new Image[0]));
this.trayIcon = new TrayIcon(mrImage, "Sparrow", popupMenu);
MenuItem miExit = new MenuItem("Quit Sparrow");
miExit.addActionListener(e -> {
SwingUtilities.invokeLater(() -> { tray.remove(this.trayIcon); });
Platform.exit();
});
this.popupMenu.add(miExit);
} catch(IOException e) {
log.error("Could not load system tray image", e);
throw new IllegalStateException(e);
}
}
public void addStage(Stage stage) {
EventQueue.invokeLater(() -> {
MenuItem miStage = new MenuItem(stage.getTitle());
miStage.setFont(Font.decode(null).deriveFont(Font.BOLD));
miStage.addActionListener(e -> Platform.runLater(() -> {
stage.show();
EventQueue.invokeLater(() -> {
popupMenu.remove(miStage);
if(popupMenu.getItemCount() == 1) {
Platform.setImplicitExit(true);
SwingUtilities.invokeLater(() -> tray.remove(trayIcon));
}
});
}));
//Make sure it's always at the top
this.popupMenu.insert(miStage,popupMenu.getItemCount() - 1);
if(!isShowing()) {
// Keeps the JVM running even if there are no
// visible JavaFX Stages, otherwise JVM would
// exit and we lose the TrayIcon
Platform.setImplicitExit(false);
SwingUtilities.invokeLater(() -> {
try {
tray.add(this.trayIcon);
} catch(AWTException e) {
log.error("Unable to add system tray icon", e);
}
});
}
});
}
public boolean isShowing() {
return Arrays.stream(tray.getTrayIcons()).collect(Collectors.toList()).contains(trayIcon);
}
public static boolean isSupported() {
return Desktop.isDesktopSupported() && SystemTray.isSupported();
}
}

View file

@ -47,7 +47,7 @@
<fx:define> <fx:define>
<ToggleGroup fx:id="theme"/> <ToggleGroup fx:id="theme"/>
</fx:define> </fx:define>
<Menu mnemonicParsing="false" text="View"> <Menu fx:id="viewMenu" mnemonicParsing="false" text="View">
<items> <items>
<Menu mnemonicParsing="false" text="Bitcoin Unit"> <Menu mnemonicParsing="false" text="Bitcoin Unit">
<items> <items>
@ -87,6 +87,7 @@
<CheckMenuItem fx:id="hideEmptyUsedAddresses" mnemonicParsing="false" text="Hide Empty Used Addresses" onAction="#hideEmptyUsedAddresses"/> <CheckMenuItem fx:id="hideEmptyUsedAddresses" mnemonicParsing="false" text="Hide Empty Used Addresses" onAction="#hideEmptyUsedAddresses"/>
<CheckMenuItem fx:id="showTxHex" mnemonicParsing="false" text="Show Transaction Hex" onAction="#showTxHex"/> <CheckMenuItem fx:id="showTxHex" mnemonicParsing="false" text="Show Transaction Hex" onAction="#showTxHex"/>
<SeparatorMenuItem /> <SeparatorMenuItem />
<MenuItem fx:id="minimizeToTray" mnemonicParsing="false" text="Minimize to System Tray" accelerator="Shortcut+Y" onAction="#minimizeToTray"/>
<MenuItem fx:id="refreshWallet" mnemonicParsing="false" text="Refresh Wallet" accelerator="Shortcut+R" onAction="#refreshWallet"/> <MenuItem fx:id="refreshWallet" mnemonicParsing="false" text="Refresh Wallet" accelerator="Shortcut+R" onAction="#refreshWallet"/>
</items> </items>
</Menu> </Menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB