I'm tasked to open a SWT shell so it spans multiple monitors.
So I'm relying on shell.setLocation(x, y) to open the shell in the specified location and shell.setSize(width, height) to set the dimension.
If I'm using a width larger than a single monitor (e.g. 3840 for two monitors) somehow the shell is adjusted to fit exactly one monitor (that's 1920).
Happens on Windows and Linux.
Minimal example:
public class ShellSample {
public static void main(final String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display, SWT.NO_TRIM);
final Window win = new ApplicationWindow(shell) {
#Override
protected void configureShell(final Shell shell) {
shell.setLocation(0, 0);
shell.setSize(3840, 100);
}
};
win.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
shell.dispose();
display.dispose();
}
}
Stupid me.
Actually, I was not resizing a SWT shell, but an JFace window.
So the solution was to override the constrainShellSize method...
Related
I'm trying to set the icon of a Java AWT application so it renders in native resolution on the Windows 10 taskbar (including when desktop scaling is set above 100%). It seems that by default, if an executable embeds an icon containing multiple sizes, Windows seems to pick a size larger than the actual size of taskbar icons and downsize it (at 100% scale it resizes the 32 pixel icon to 24, even if a 24 pixel icon is supplied, and similarly for other scales.)
I've solved this problem for C++ MFC applications by loading just the correctly sized icon as a resource and sending a WM_SETICON message to the window, which results in a nice sharp icon on the taskbar and alt-tab dialog.
smallIcon = (HICON)LoadImage( myInstance, MAKEINTRESOURCE(smallIconRes), IMAGE_ICON, smallIconSize, smallIconSize, LR_DEFAULTCOLOR );
SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)smallIcon);
bigIcon = (HICON)LoadImage( myInstance, MAKEINTRESOURCE(bigIconRes), IMAGE_ICON, bigIconSize, bigIconSize, LR_DEFAULTCOLOR );
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)bigIcon);
That approach doesn't seem to work for Java applications - a WM_SETICON message with wParam set to ICON_SMALL works fine, but the equivalent with ICON_BIG is ignored.
If I try to use Java's API to set the icon, by doing this
List<Image> icons = new ArrayList<Image>();
icons.add(windowIcons.getIcon(20)); // small icons are 20x20 pixels
icons.add(windowIcons.getIcon(30)); // large are 30x30 at 125% scale
setIconImages(icons);
the correct icon is used but it appears blurry, as if something has resized it to the "expected" size and then resized it back. Left here is how it appears, right is the contents of the icon file.
So, my question is: what can I do in this Java application to make Windows render the icon I give it on the taskbar without scaling it and blurring the details?
There is indeed a scaling function called getScaledIconImage() in sun.awt.SunToolkit which is is always used when setting the icons. You must bypass this function in order to get an unaliased icon. So what you need is a replacement for java.awt.Window.setIconImages() method.
Provided several icon images Icon16x16.png, Icon24x24.png, etc. This is an example of a customSetIconImages() which puts a crisp 24x24 pixels icon in the taskbar of Windows 10.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.ImageIcon;
import java.awt.peer.WindowPeer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
#SuppressWarnings("serial")
public class MyFrame extends Frame implements WindowListener {
final Image i16, i24, i32, i48;
MyFrame() throws Exception {
i16 = Toolkit.getDefaultToolkit().getImage("Icon16x16.png");
i24 = Toolkit.getDefaultToolkit().getImage("Icon24x24.png");
i32 = Toolkit.getDefaultToolkit().getImage("Icon32x32.png");
i48 = Toolkit.getDefaultToolkit().getImage("Icon48x48.png");
addWindowListener(this);
setSize(500,300);
setTitle("Unaliased icon example");
setLayout(new FlowLayout());
setVisible(true);
}
public synchronized void customSetIconImages(java.util.List<Image> icons) throws Exception {
Field windowIcons = Class.forName("java.awt.Window").getDeclaredField("icons");
windowIcons.setAccessible(true);
windowIcons.set(this, new ArrayList<Image>(icons));
if (getPeer() != null)
updateIconImages(i24, 24, 24, i24, 24, 24);
firePropertyChange("iconImage", null, null);
}
public void updateIconImages(Image big, int bw, int bh, Image small, int sw, int sh) throws Exception {
DataBufferInt iconData = getUnscaledIconData(big, bw, bh);
DataBufferInt iconSmData = getUnscaledIconData(small, sw, sh);
WindowPeer peer = (WindowPeer) getPeer();
Method setIconImagesData = Class.forName("sun.awt.windows.WWindowPeer").getDeclaredMethod("setIconImagesData", int[].class, int.class, int.class, int[].class, int.class, int.class);
setIconImagesData.setAccessible(true);
setIconImagesData.invoke(peer, iconData.getData(), bw, bh, iconSmData.getData(), sw, sh);
}
public static DataBufferInt getUnscaledIconData(Image image, int w, int h) {
Image temporary = new ImageIcon(image).getImage();
BufferedImage buffImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = buffImage.createGraphics();
g2d.drawImage(temporary, 0, 0, null);
g2d.dispose();
Raster raster = buffImage.getRaster();
DataBuffer buffer = raster.getDataBuffer();
return (DataBufferInt) buffer;
}
#Override
public void windowOpened(WindowEvent arg0) {
try {
customSetIconImages(Arrays.asList(i24));
} catch (Exception e) {
System.err.println(e.getClass().getName()+" "+e.getMessage());
}
}
#Override
public void windowActivated(WindowEvent arg0) {
}
#Override
public void windowClosed(WindowEvent arg0) {
}
#Override
public void windowClosing(WindowEvent arg0) {
dispose();
}
#Override
public void windowDeactivated(WindowEvent arg0) {
}
#Override
public void windowDeiconified(WindowEvent arg0) {
}
#Override
public void windowIconified(WindowEvent arg0) {
}
public static void main(String args[]) throws Exception {
MyFrame fr = new MyFrame();
}
}
As #df778899 said, inside sun.awt.windows.WWindowPeer there are four private native methods which you can call t determine system icons size. You can combine the information returned by these methods with your own version getScaledIconImage() that performs unaliasing or not as yoou wish.
Last, note that this is a very dirty hack just for getting an unaliased icon. I've only tested in in Java 8 and Windows 10. And there are high chances that it doesn't work in newer versions of Java.
This won't be the answer you're hoping for, but this looks like a problem at the JDK level.
The window icons are handled by the sun.awt.windows.WWindowPeer class, which in turn makes a few native method calls, but there is enough to see in the source for this to point to the problem. Please read the important bit here.
Essentially, regardless of how many icon image sizes are provided, it will only pick out two sizes - for the WWindowPeer.getSysIconWidth() and getSysSmIconWidth() - to pass into the native setIconImagesData() method.
The getSysIconWidth() and getSysSmIconWidth() methods are also native, but it is possible to directly check their return values:
JFrame frame = new JFrame();
runOnPeer(frame, "getSysIconWidth");
runOnPeer(frame, "getSysIconHeight");
runOnPeer(frame, "getSysSmIconWidth");
runOnPeer(frame, "getSysSmIconHeight");
private void runOnPeer(JFrame frame, String methodName) {
//JDK8 style
//ComponentPeer peer = frame.getPeer();
//JDK11 style
Field peerField = Component.class.getDeclaredField("peer");
peerField.setAccessible(true);
Object peer = peerField.get(frame);
Method method = Class.forName("sun.awt.windows.WWindowPeer")
.getDeclaredMethod(methodName);
method.setAccessible(true);
System.out.println(methodName + "()=" + method.invoke(peer));
}
... which returns this on Windows 10 ...
getSysIconWidth()=32
getSysIconHeight()=32
getSysSmIconWidth()=16
getSysSmIconHeight()=16
As you say, clearly one of these image sizes is then being scaled for the taskbar.
Is there a way in Java to give a label a highlight effect?
For example, say I have a label with a picture set to it, and I want to highlight it whenever the mouse cursor enters it.
I tried to look around in google and the only tip I could find is to use label.setBackground(display.getSystemColor(SWT.COLOR_YELLOW));
but this obviously will not highlight the label, let alone a label with a picture on it.
because first of all, it is too yellow, and second, it will appear as if the color is behind the picture (well, because it is setBackground() of course).
I'm looking for a much brighter yellow color that will also be transparent, so the image will be seen.
Hope my intentions are clear enough.
You could add a Listener to SWT.MouseEnter and SWT.MouseExit and keep track of whether the mouse is hovering over the Label and then repaint it with a custom Listener for SWT.Paint:
private static boolean hovering = false;
public static void main(String[] args)
{
final Display display = new Display();
Shell shell = new Shell(display);
shell.setText("StackOverflow");
shell.setLayout(new FillLayout());
final Label label = new Label(shell, SWT.NONE);
final Image image = display.getSystemImage(SWT.ICON_ERROR);
Listener mouseHover = new Listener()
{
#Override
public void handleEvent(Event e)
{
hovering = e.type == SWT.MouseEnter;
label.redraw();
}
};
label.addListener(SWT.MouseEnter, mouseHover);
label.addListener(SWT.MouseExit, mouseHover);
label.addListener(SWT.Paint, new Listener()
{
#Override
public void handleEvent(Event e)
{
int x = label.getBounds().width / 2 - image.getBounds().width / 2;
int y = label.getBounds().height / 2 - image.getBounds().height / 2;
e.gc.drawImage(image, x, y);
if(hovering)
{
e.gc.setAlpha(50);
e.gc.setBackground(display.getSystemColor(SWT.COLOR_YELLOW));
e.gc.fillRectangle(label.getBounds());
}
}
});
shell.pack();
shell.setSize(100, 100);
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
Looks like this:
After a lot of search and research, I couldn't find any answer.
I will very appreciate the person that could help me.
In SWT java,
I want my window to be not resizable, so I define it like this:
shell = new Shell(SWT.CLOSE | SWT.TITLE | SWT.MIN);
But then I want to change it to be resizable (for example after I click a button).
How can I change the style of the shell on the fly?
You can't change the style of a Shell once it has been created so you can't do this directly.
What you can do is create a new Shell (and all its contents) with the desired style and set its bounds to be the same as the old Shell and then close the old shell. Eclipse does this when it wants to convert a tool tip window in to a normal window.
If you develop exclusively for windows, you can achieve this with non pure SWT code (non portable). There are no visible glitches, no problems with re-parenting controls (see this bug, for example). Only the resizable property is changed. Here is the snippet:
public static void main(String[] args) {
final Display display = new Display();
Shell shell = new Shell(display);
final int style = OS.GetWindowLong(shell.handle, OS.GWL_STYLE);
OS.SetWindowLong(shell.handle, OS.GWL_STYLE, style & ~0x00040000);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
WS_SIZEBOX = 0x00040000L
Edit:
simple functions to use:
private static final int WS_SIZEBOX = 0x00040000;
public static void setResizable(Shell shell, boolean resizable) {
final int style = OS.GetWindowLong(shell.handle, OS.GWL_STYLE);
final int newStyle = resizable ? style | WS_SIZEBOX : style & ~WS_SIZEBOX;
OS.SetWindowLong(shell.handle, OS.GWL_STYLE, newStyle);
}
public static boolean isResizable(Shell shell) {
final int style = OS.GetWindowLong(shell.handle, OS.GWL_STYLE);
return (style & WS_SIZEBOX) != 0;
}
I am developing an app for which I need the screen DPI.. I checked a few forums and got a code snippet which goes as follows:
Dimension screen = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
System.out.println("screen width: "+screen.getWidth());
System.out.println("screen height: "+screen.getHeight());
int pixelPerInch=java.awt.Toolkit.getDefaultToolkit().getScreenResolution();
System.out.println("pixelPerInch: "+pixelPerInch);
double height=screen.getHeight()/pixelPerInch;
double width=screen.getWidth()/pixelPerInch;
double x=Math.pow(height,2);
double y=Math.pow(width,2);
But whatever be the value of my screen resolution, the pixelPerInch value remains the same at 96. What is the problem with the code??
I got another swt code for the same thing which goes as follows:
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class MainClass {
public void run() {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Display Device");
createContents(shell);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private void createContents(Shell shell) {
Device device = shell.getDisplay();
System.out.println("getBounds(): "+ device.getBounds());
System.out.println("getClientArea(): "+device.getClientArea());
System.out.println("getDepth(): "+device.getDepth());
System.out.println("getDPI(): "+device.getDPI());
device.setWarnings(true);
System.out.println("Warnings supported: "+device.getWarnings());
}
public static void main(String[] args) {
new MainClass().run();
}
But again here also whatever be my screen resolution, the getDPI() returns the same value of 96.. What is going wrong?? Is my code wrong or am I interpreting it in a wrong way??
The problem is no one, not even the OS, knows the exact physical dimensions of the screen. You'd need to know that in order to calculate the PPI.
There's a display setting in the control panel where the user can manually specify the PPI and by default it's set to 96.
this code works for me on win10
import javafx.stage.Screen
double getScaleFactor() {
double trueHorizontalLines = Toolkit.getDefaultToolkit().getScreenSize().getHeight();
double scaledHorizontalLines = Screen.getPrimary().getBounds().getHeight();
double dpiScaleFactor = trueHorizontalLines / scaledHorizontalLines;
return dpiScaleFactor;
}
it uses some awt apis though
In a Java SWT shell window, how do I set its inner size than its whole window frame size?
For instance, if I use shell.setSize(300, 250) this would make the whole window appearing as exactly 300x250. This 300x250 includes the size of the window frame.
How can I set the inner size, that is the content display region of the shell window to 300x250 instead? That's this 300x250 excludes the width of the window frame.
I tried to minus some offset values but the thing is different Operating Systems have different window frame sizes. So having a constant offset would not be accurate.
Thanks.
From your question what I understood is that you want to set the dimension of the Client Area. And in SWT lingo it is defined as a rectangle which describes the area of the receiver which is capable of displaying data (that is, not covered by the "trimmings").
You cannot directly set the dimension of Client Area because there is no API for it. Although you can achieve this by a little hack. In the below sample code I want my client area to be 300 by 250. To achieve this I have used the shell.addShellListener() event listener. When the shell is completely active (see the public void shellActivated(ShellEvent e)) then I calculate the different margins and again set the size of my shell. The calculation and resetting of the shell size gives me the desired shell size.
>>Code:
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.ShellListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
public class MenuTest {
public static void main (String [] args)
{
Display display = new Display ();
final Shell shell = new Shell (display);
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
layout.numColumns = 1;
shell.setLayout(layout);
shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,true));
final Menu bar = new Menu (shell, SWT.BAR);
shell.setMenuBar (bar);
shell.addShellListener(new ShellListener() {
public void shellIconified(ShellEvent e) {
}
public void shellDeiconified(ShellEvent e) {
}
public void shellDeactivated(ShellEvent e) {
}
public void shellClosed(ShellEvent e) {
System.out.println("Client Area: " + shell.getClientArea());
}
public void shellActivated(ShellEvent e) {
int frameX = shell.getSize().x - shell.getClientArea().width;
int frameY = shell.getSize().y - shell.getClientArea().height;
shell.setSize(300 + frameX, 250 + frameY);
}
});
shell.open ();
while (!shell.isDisposed()) {
if (!display.readAndDispatch ()) display.sleep ();
}
display.dispose ();
}
}
If I get you right you should set the size of the inner component to the needed size and use the method pack() (of the frame).
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
public class SWTClientAreaTest
{
Display display;
Shell shell;
final int DESIRED_CLIENT_AREA_WIDTH = 300;
final int DESIRED_CLIENT_AREA_HEIGHT = 200;
void render()
{
display = Display.getDefault();
shell = new Shell(display, SWT.SHELL_TRIM | SWT.CENTER);
Point shell_size = shell.getSize();
Rectangle client_area = shell.getClientArea();
shell.setSize
(
DESIRED_CLIENT_AREA_WIDTH + shell_size.x - client_area.width,
DESIRED_CLIENT_AREA_HEIGHT + shell_size.y - client_area.height
);
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
{
display.sleep();
}
}
display.dispose();
}
public static void main(String[] args)
{
SWTClientAreaTest appl = new SWTClientAreaTest();
appl.render();
}
}
Use computeTrim to calculate the bounds that are necessary to display a given client area. The method returns a rectangle that describes the bounds that are needed to provide room for the client area specified in the arguments.
In this example the size of the shell is set so that it is capable to display a client area of 100 x 200 (width x height):
Rectangle bounds = shell.computeTrim(0, 0, 100, 200);
shell.setSize(bounds.width, bounds.height);
This article describes the terms used by SWT for widget dimensions:
https://www.eclipse.org/articles/Article-Understanding-Layouts/Understanding-Layouts.htm