dustinkredmond / FXTrayIcon

Tray Icon implementation for JavaFX applications. Say goodbye to using AWT's SystemTray icon, instead use a JavaFX Tray Icon.
MIT License
324 stars 25 forks source link

Feature Request: Make getTrayIcon() public #72

Closed mrgreywater closed 1 year ago

mrgreywater commented 1 year ago

Currently there is no simple way to get the underlying java.awt.TrayIcon. As such you cannot do essential things such as checking if a user right clicked the tray icon.

As a workaround, you can inherit from FXTrayIcon and expose getTrayIcon with a different function name.

dustinkredmond commented 1 year ago

I think I did this by design so as not to confuse folks that may see the getTrayIcon method and not understand the AWT bits. Let me check my notes on this. Sub-classing FXTrayIcon might be preferable if you're dabbling around with the internals.

EasyG0ing1 commented 1 year ago

@mrgreywater

Here's an example of subclassing FXTrayIcon then from that subclass, setting up a listener for the right mouse button event:

import com.dustinredmond.fxtrayicon.FXTrayIcon;
import javafx.stage.Stage;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;

public class TrayIcon extends FXTrayIcon {
    public TrayIcon(Stage parentStage, File iconFile, int iconWidth, int iconHeight) {
        super(parentStage, iconFile, iconWidth, iconHeight);
    }

    public void setMouseListener() {
        getTrayIcon().addMouseListener(new MouseListener() {
            @Override public void mouseClicked(MouseEvent e) {
                if(e.getButton() == 3) {
                    System.out.println("User right clicked on tray icon");
                }
            }

            @Override public void mousePressed(MouseEvent e) {
            }

            @Override public void mouseReleased(MouseEvent e) {
            }

            @Override public void mouseEntered(MouseEvent e) {
            }

            @Override public void mouseExited(MouseEvent e) {
            }
        });
    }
}

And here is how you would use it:

File file = new File(pathToMyIconFile);
TrayIcon tray = new TrayIcon(stage,file,24,24);
tray.addExitItem("Exit");
tray.setMouseListener();
tray.show();

And here's what happens when the icon is right clicked:

User right clicked on tray icon

mrgreywater commented 1 year ago

Thank you. Yes I'm doing something similar right now as I indicated. I still think just exposing the underlying Tray Icon would make it more straight forward.

As is I had to investigate the source code to find a way to attach that callback. Autocomplete doesn't show the getTrayIcon function as it is only available in the inherited context. Making it public would make it more "self-documenting" in my opinion.

EasyG0ing1 commented 1 year ago

@mrgreywater When you're extending a class, then all of the protected methods of the class you're extending will be available in the extended (sub)class... with IntelliJ Idea those methods show up in autocomplete as you can see here

https://user-images.githubusercontent.com/10106834/217616012-a7c4cc22-b355-4121-8bf0-7ba3193ed970.mov

Also, you could create in the subclass, a public method that returns the AWT TrayIcon object ... you can even expose the builder class and pick and chose which constructors you want to include in that exposed class...

import com.dustinredmond.fxtrayicon.FXTrayIcon;
import javafx.stage.Stage;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.net.URL;

public class TrayIcon extends FXTrayIcon {
    public TrayIcon(Stage parentStage, File iconFile, int iconWidth, int iconHeight) {
        super(parentStage, iconFile, iconWidth, iconHeight);
    }

    public void setMouseListener() {
        getTrayIcon().addMouseListener(new MouseListener() {
            @Override public void mouseClicked(MouseEvent e) {
                if(e.getButton() == 3) {
                    System.out.println("User right clicked on tray icon");
                }
            }

            @Override public void mousePressed(MouseEvent e) {
            }

            @Override public void mouseReleased(MouseEvent e) {
            }

            @Override public void mouseEntered(MouseEvent e) {
            }

            @Override public void mouseExited(MouseEvent e) {
            }
        });
    }

    public java.awt.TrayIcon trayIcon() {
        return super.trayIcon;
    }

    public static class Builder extends FXTrayIcon.Builder {
        public Builder(Stage parentStage, URL iconImagePath, int iconWidth, int iconHeight) {
            super(parentStage, iconImagePath, iconWidth, iconHeight);
        }
    }
}

From there, you would just use your extended subclass in your application instead of using the library's FXTrayIcon class directly.

EasyG0ing1 commented 1 year ago

@mrgreywater It looks like I was mistaken about exposing the TrayIcon awt object. It's not accessible outside of the subclassed context... but that shouldn't be a big deal since there really shouldn't be a need to access that object outside of that class in the first place. But when I tried exposing it, the IDE barked at me reminding me that no matter how I try to make it available outside of that class, I can go pound sand cause its not gonna happen.

I even tried making it an object within the subclass, then setting it equal to the protected get method then with a different method exposing it from there, but its protected status follows it where it's used so then the only way to accomplish that would be to possibly instantiate a new awt.TrayIcon object and then copy that one to the new one if that can even be done.

But like I said once you have it in the subclass... it really shouldn't be necessary to access it outside of the subclass.

EasyG0ing1 commented 1 year ago

@mrgreywater I submitted a PR that I think will meet the original design intent of protecting the object while also providing a way of accessing it without needing to extend the main class. Ultimately it's up to Dustin if he wants to accept the PR, but I think he might like the solution that I proposed.

dustinkredmond commented 1 year ago

I much prefer what #74 does. If one is comfortable enough to use the AWT TrayIcon, I didn't envision them as using the FXTrayIcon library. I was mistaken on this, and could definitely see how folks may want to hack around with the AWT object. Planning on merging this PR. Thanks @EasyG0ing1!

EasyG0ing1 commented 1 year ago

@dustinkredmond @mrgreywater I already have a custom right-click option on an app I wrote that gives me access to my One Time Password keys right from the system tray. Now I can just right-click on it instead of navigating the menu to have the keys just pop up with that single click. I had wanted to do this from the beginning but just assumed I couldn't and never dug into it until this issue was opened. I'm loving the evolution of this library ... though I'm still waiting for the day when we will get native JavaFX access to the system tray, though I'm not holding my breath on that one.