Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to customize the Window (icon and title) #613

Open
RiverOmega opened this issue Oct 2, 2024 · 15 comments
Open

How to customize the Window (icon and title) #613

RiverOmega opened this issue Oct 2, 2024 · 15 comments

Comments

@RiverOmega
Copy link

Hello, I'm building a small terminal app using lanterna, I'm pretty much done with my use-case but I noticed this little detail:

image

Is there any way to customize the Window title and icon? I would like to use my own icon, and I'd rather not it said "SwingTerminalFrame".

@avl42
Copy link
Contributor

avl42 commented Oct 3, 2024 via email

@RiverOmega
Copy link
Author

Just to understand you a bit better when you say "Use SwingTerminal class". Here is a code snippet:

            final Terminal terminal = terminalFactory.createTerminal();

            screen = new TerminalScreen(terminal);
            screen.startScreen();

            //Setting up basic GUI
            multiWindowTextGUI = new MultiWindowTextGUI(new SeparateTextGUIThread.Factory(), screen);
            ((AsynchronousTextGUIThread) multiWindowTextGUI.getGUIThread()).start();

            //Creating window
            BasicWindow window = new BasicWindow("Utility Terminal");
            window.setHints(Arrays.asList(Window.Hint.CENTERED, Window.Hint.FIT_TERMINAL_WINDOW, Window.Hint.FULL_SCREEN));

Instead of creating a new Terminal() and new TerminalScreen(terminal, I could create an explicit SwingTerminal-instance? Is that what you mean?

@avl42
Copy link
Contributor

avl42 commented Oct 3, 2024 via email

@avl42
Copy link
Contributor

avl42 commented Oct 3, 2024 via email

@RiverOmega
Copy link
Author

Ah, I'm with you. I'll take a look at it asap. Thanks for the pointers!

@RiverOmega
Copy link
Author

RiverOmega commented Oct 3, 2024

Alright, I got things working, but I'm not fully pleased with my solution. First off, how I did it:

    public static void runGui() {
       DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory().setMouseCaptureMode(
               MouseCaptureMode.CLICK);
       try {
           final SwingTerminalFrame terminal = (SwingTerminalFrame) terminalFactory.createTerminal();
           terminal.setTitle("DFObs");
           terminal.setIconImage(AssetUtil.getApplicationIconImage());

           screen = new TerminalScreen(terminal);
           screen.startScreen();

The problem here is that I assume terminalFactory.createTerminal() will always return a SwingTerminalFrame. I am testing this on my own Linux machine and I don't set any config in regards to "forceAwt" or "forceText", but it's still a big "if".

The solution I would want is this:

 public static void runGui() {
       DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory().setMouseCaptureMode(
               MouseCaptureMode.CLICK);
       try {
           final SwingTerminalFrame swingTerminal = terminalFactory.createSwingTerminal();
           swingTerminal.setTitle("DFObs");
           swingTerminal.setIconImage(AssetUtil.getApplicationIconImage());

           screen = new TerminalScreen(swingTerminal);
           screen.startScreen();

... where I explicitly create a SwingTerminal. However, when trying to create my screen using new Terminal(swingTerminal) it throws the following error:

Exception in thread "main" java.lang.IllegalArgumentException: Width (0) and height (0) cannot be <= 0
   at java.desktop/java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1016)
   at java.desktop/java.awt.image.BufferedImage.<init>(BufferedImage.java:324)
   at com.googlecode.lanterna.terminal.swing.GraphicalTerminalImplementation.ensureGraphicBufferHasRightSize(GraphicalTerminalImplementation.java:494)
   at com.googlecode.lanterna.terminal.swing.GraphicalTerminalImplementation.updateBackBuffer(GraphicalTerminalImplementation.java:332)
   at com.googlecode.lanterna.terminal.swing.GraphicalTerminalImplementation.flush(GraphicalTerminalImplementation.java:799)
   at com.googlecode.lanterna.terminal.swing.GraphicalTerminalImplementation.exitPrivateMode(GraphicalTerminalImplementation.java:667)
   at com.googlecode.lanterna.terminal.swing.SwingTerminal.exitPrivateMode(SwingTerminal.java:264)
   at com.googlecode.lanterna.terminal.swing.SwingTerminalFrame.exitPrivateMode(SwingTerminalFrame.java:242)
   at com.googlecode.lanterna.screen.TerminalScreen.stopScreen(TerminalScreen.java:123)
   at com.googlecode.lanterna.screen.TerminalScreen.stopScreen(TerminalScreen.java:106)
   at gui.DfObsGui.runGui(DfObsGui.java:106)
   at DfObsMain.main(DfObsMain.java:5)

Is my usage of the factory wrong somehow, or is this a bug?

EDIT: An effort to flesh out the question a bit more:

Why is DefaultTerminalFactory.createTerminal() capable of creating a functioning SwingTerminalFrame-instance, while DefaultTerminalFactory.createSwingTerminal() isn't?

@avl42
Copy link
Contributor

avl42 commented Oct 4, 2024 via email

@RiverOmega
Copy link
Author

I do comprehend that the purpose of this library is to provide a nice API to populate a TUI within terminals (or emulated terminals). As such I respect that when you have more specific requirements for the runtime itself, as I had with title and icons, you might have to think outside the "lanterna box". This I agree with 100%.

When testing different workarounds by manipulating the SwingTerminalFrame however, I tried to utilize the "createSwingTerminal()"-method, and discovered that this specific method causes a crash when creating the screen-instance.

In short, when using the library to create the SwingTerminal explicitly, my code will not reach the populating of the GUI:
image

It feels a bit too specific not to be faulty. Because if I change to code to this, the exception is not thrown:
image

I'm a little bit confused by this, because when debugging I can see that both variants pasted above will result in a call to the same method:
image

For now I can work around this problem by setting up the factory and cast the returned Terminal to SwingTerminalFrame, because I know it's of that type, but it feels like factory.createSwingTerminal() should return an instance very similar to the SwingTerminalFrame-instance that factory.createTerminalEmulator() provides, which doesn't result in a crash.

@avl42
Copy link
Contributor

avl42 commented Oct 4, 2024 via email

@RiverOmega
Copy link
Author

I think I traced it down. When starting the screen constructed from a SwingTerminalFrame created by the createSwingTerminal()-method one ends up here:

image

Somehow the width and height are both 0 when using the createSwingTerminal()-method, which seems to be the cause of the exception.

I did try to manipulate terminal sizes on the factory, as well as the terminal instance before creating and starting the screen but it had no effect:

image

@avl42
Copy link
Contributor

avl42 commented Oct 5, 2024 via email

@avl42
Copy link
Contributor

avl42 commented Oct 5, 2024 via email

@RiverOmega
Copy link
Author

Extracting the stacktrace from the exception thrown when startScreen() is called:

java.desktop/java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1016)
java.desktop/java.awt.image.BufferedImage.<init>(BufferedImage.java:324)
com.googlecode.lanterna.terminal.swing.GraphicalTerminalImplementation.ensureGraphicBufferHasRightSize(GraphicalTerminalImplementation.java:494)
com.googlecode.lanterna.terminal.swing.GraphicalTerminalImplementation.updateBackBuffer(GraphicalTerminalImplementation.java:332)
com.googlecode.lanterna.terminal.swing.GraphicalTerminalImplementation.flush(GraphicalTerminalImplementation.java:799)
com.googlecode.lanterna.terminal.swing.GraphicalTerminalImplementation.enterPrivateMode(GraphicalTerminalImplementation.java:660)
com.googlecode.lanterna.terminal.swing.SwingTerminal.enterPrivateMode(SwingTerminal.java:259)
com.googlecode.lanterna.terminal.swing.SwingTerminalFrame.enterPrivateMode(SwingTerminalFrame.java:237)
com.googlecode.lanterna.screen.TerminalScreen.startScreen(TerminalScreen.java:91)
gui.DfObsGui.runGui(DfObsGui.java:48)
DfObsMain.main(DfObsMain.java:5)

When debugging, the first exception seems to exit the execution into the finally-block, it doesn't seem to get caught in the first try-catch. The finally-block will call screen.stopScreen() which results in the same exception being thrown (again). However this one will get caught by the catch-block within the finally-block:

image

It is the final catch that prints the stacktrace mentioned a few comments prior to this one.

@mabe02
Copy link
Owner

mabe02 commented Oct 6, 2024

Do you mind sharing a small sample to reproduce this? So we're all looking/testing at the same code? If the original problem was to set a title and icon for the frame, I thought given SwingTerminalFrame extends from JFrame you could just modify it after it was created.

@RiverOmega
Copy link
Author

RiverOmega commented Oct 6, 2024

Of course! I can recreate the error with minimal code. Also, my apologies for deviating a bit from the original topic! I want to make it clear that I have a fully functional workaround for setting icon and title of the application, thanks to the help of @avl42!

By now, the discussion has turned into a debugging session about why DefaultTerminalFactory::createSwingTerminal creates a SwingTerminalFrame without size, causing the exception when trying to invoke TerminalScreen::start and TerminalScreen::stop.

Here is the pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sample</groupId>
    <artifactId>lanterna-swingterminal-error</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.googlecode.lanterna</groupId>
            <artifactId>lanterna</artifactId>
            <version>3.1.2</version>
        </dependency>
    </dependencies>
</project>

Here is Main.java:

import com.googlecode.lanterna.bundle.LanternaThemes;
import com.googlecode.lanterna.gui2.AsynchronousTextGUIThread;
import com.googlecode.lanterna.gui2.BasicWindow;
import com.googlecode.lanterna.gui2.MultiWindowTextGUI;
import com.googlecode.lanterna.gui2.SeparateTextGUIThread;
import com.googlecode.lanterna.gui2.Window;
import com.googlecode.lanterna.screen.TerminalScreen;
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
import com.googlecode.lanterna.terminal.swing.SwingTerminalFrame;

import java.io.IOException;
import java.util.Arrays;

public class Main {
    private static final DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory();
    private static TerminalScreen screen;

    public static void main(String[] args) {

        try {

            //Workaround. This is what I'm using currently. I'll leave this here for comparison.
            //terminalFactory.setForceTextTerminal(false);
            //terminalFactory.setForceAWTOverSwing(false);
            //final SwingTerminalFrame swingTerminal =
            //        (SwingTerminalFrame) terminalFactory.createTerminalEmulator();

            //Creating the swing terminal via factory => Exception
            final SwingTerminalFrame swingTerminal = terminalFactory.createSwingTerminal();

            screen = new TerminalScreen(swingTerminal);
            screen.startScreen();

            //Setting up basic GUI
            MultiWindowTextGUI multiWindowTextGUI = new MultiWindowTextGUI(
                    new SeparateTextGUIThread.Factory(), screen);
            ((AsynchronousTextGUIThread) multiWindowTextGUI.getGUIThread()).start();

            //Creating window
            BasicWindow window = new BasicWindow("Simple placeholder window");
            window.setHints(Arrays.asList(Window.Hint.CENTERED, Window.Hint.FIT_TERMINAL_WINDOW,
                                          Window.Hint.FULL_SCREEN));

            multiWindowTextGUI.setTheme(LanternaThemes.getRegisteredTheme("default"));

            multiWindowTextGUI.addWindowAndWait(window);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (screen != null) {
                try {
                    screen.stopScreen();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Place these files in the following file-structure (typical maven):
image

... then place a breakpoint in DirectColorModel.java - L1016 and debug the code. You'll end up at that breakpoint twice. The first time when the trying to start the screen, the second time when the screen is stopped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants