Skip to main content

Using TransformUI scaling with JComboBox

20 replies [Last post]
snoopygee
Offline
Joined: 2010-01-13

Hi,

I'm using the scaling facility of Piet Block generic TransformUI source and I'm testing it's capability with various components and I have come across an issue with the JComboBox component.

The JComboBox component changes scale correctly however the drop down box of the JComboBox is never rescaled. It always appears as it's original 1:1 size and further it's position remains constant.

How do I get the JComboBox drop down list to scale to the correct size and track to the correct position?

Further does this issue apply to any other Components?

Thanks in advance.

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
snoopygee
Offline
Joined: 2010-01-13

Hi Piet,

The work you and dpmihai have done here is very nice and works very well, I have one question however.

When the rescale size is selected (from the combo) and the actionPerformed event is triggered it passes in the new rescale size and therefore the combobox drop down popup is not only resized and repositioned correctly but also the text within popup is also tranformed and resized. However when dragging the window to rescale, the text within the combo popup is no longer rescaled along with the component. This is of course because the code within the combo's actionListeners was not fired to rescale the model and repaint the component.

I would like to perform the rescale of the text (within the popup) when resizing the window but what I am missing is a way to find the current scale of the JXLayer after the window has been resized. I have looked at the DefaultTransformModel but can't seem to locate the "currentScale", only whatever scale was previously set by selection from the scale combo. What complicated my efforts is I also cannot seem to find the latest source for the "org.pbjar.jxlayer.plaf.ext.transform" packages.

Hope all this makes sense. Any idea's how I can return the currentScale after resize of the window?

Thanks
Snoopygee

pietblok
Offline
Joined: 2003-07-17

Hi Snoopygee

> Hi Piet,
>
> The work you and dpmihai have done here is very nice
> and works very well, I have one question however.

Thanks

>
> When the rescale size is selected (from the combo)
> and the actionPerformed event is triggered it passes
> in the new rescale size and therefore the combobox
> drop down popup is not only resized and repositioned
> correctly but also the text within popup is also
> tranformed and resized. However when dragging the
> window to rescale, the text within the combo popup is
> no longer rescaled along with the component. This is
> of course because the code within the combo's
> actionListeners was not fired to rescale the model
> and repaint the component.
>
> I would like to perform the rescale of the text
> (within the popup) when resizing the window but what
> I am missing is a way to find the current scale of
> the JXLayer after the window has been resized. I have
> looked at the DefaultTransformModel but can't seem to
> locate the "currentScale", only whatever scale was
> previously set by selection from the scale combo.

One can work around this by not using the original transform model, but by creating a new transform model, based on the scale of the AffineTransform it returns from getTransform. However, this is a little dangerous because, when aspect ratio is not preserved, the scaleX and scaleY mat be different.

Furthermore, I lack the mathematical skills to explain this, the returned scaleX and scaleY may not always reflect the applied scale when other transforms like rotation and / or shearing are also set.

Anyway, see below an example of how to do this.

> What complicated my efforts is I also cannot seem to
> find the latest source for the
> "org.pbjar.jxlayer.plaf.ext.transform" packages.

What version are you using? There are two versions of my blog for the two versions of JXLayer:

JXLayer 3.0: http://www.pbjar.org/Blogs/jxlayer/version_2/index.html

JXLayer 4.0: http://www.pbjar.org/Blogs/jxlayer/jxlayer40/index.html

The combo box example is based on JXLayer 4.0

>
> Hope all this makes sense. Any idea's how I can
> return the currentScale after resize of the window?
>
> Thanks
> Snoopygee

Thanks
Piet

[code]
import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.demo.QualityHints;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
import org.pbjar.jxlayer.plaf.ext.TransformUI;

import javax.swing.*;
import javax.swing.plaf.metal.MetalComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.table.TableModel;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
* User: mihai.panaitescu Date: 17-Aug-2010 Time: 11:22:56
*/
@SuppressWarnings("unchecked")
public class TransformPanelTestPB {

private JXLayer layer;
private SteppedComboBoxUI firstComboUI;
private SteppedComboBoxUI secondComboUI;

public static void main(String[] in) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TransformPanelTestPB();
}
});
}

public TransformPanelTestPB() {

final JComboBox cbZoom = new JComboBox(new String[] { "1", "2", "3" });
cbZoom.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {

double f = Double.parseDouble(cbZoom.getSelectedItem()
.toString());

DefaultTransformModel model = (DefaultTransformModel) ((TransformUI) layer
.getUI()).getModel();
model.setScale(f);
layer.repaint();
}
});

JFrame frame = new JFrame();
JPanel testPanel = createPanel();
testPanel.setPreferredSize(new Dimension(400, 400));

layer = TransformUtils.createTransformJXLayer(testPanel, 1.0,
new QualityHints());

// Just a test to see what happens when a more complex transform is
// applied than just scaling.
// PB
// ((DefaultTransformModel) ((TransformUI) layer.getUI()).getModel())
// .setRotation(Math.PI / 32.0);

JScrollPane scr = new JScrollPane();
scr.setViewportView(layer);

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(cbZoom, BorderLayout.NORTH);
frame.add(scr, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}

private JPanel createPanel() {
JPanel zoomPanel = new JPanel();
zoomPanel.setLayout(new GridBagLayout());

final JLabel marker = new javax.swing.JLabel(
"Testing the mouse position on zoom");
marker.setHorizontalAlignment(javax.swing.JLabel.CENTER);
zoomPanel.add(marker, new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
new Insets(5, 5, 5, 5), 0, 0));

JComboBox firstComboBox = new JComboBox(new String[] { "First Item",
"Second Item", "Third Item" });
firstComboUI = new SteppedComboBoxUI();
firstComboBox.setUI(firstComboUI);
zoomPanel.add(firstComboBox, new GridBagConstraints(0, 1, 1, 1, 0.0,
0.0, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(5, 5, 5, 5), 0, 0));

JComboBox secondComboBox = new JComboBox(new String[] { "Text one",
"Text two", "Text three" });
secondComboUI = new SteppedComboBoxUI();
secondComboBox.setUI(secondComboUI);
zoomPanel.add(secondComboBox, new GridBagConstraints(1, 1, 1, 1, 0.0,
0.0, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(5, 5, 5, 5), 0, 0));

int rows = 2;
int cols = 3;
TableModel model = new DefaultTableModel(rows, cols);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
model.setValueAt(i + j, i, j);
}
}
JTable table = new JTable(model);
zoomPanel.add(table, new GridBagConstraints(0, 2, 2, 1, 1.0, 1.0,
GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5,
5, 5, 5), 0, 0));

JScrollPane sp = new JScrollPane();
sp.setPreferredSize(new Dimension(100, 100));
JTextArea textArea = new JTextArea();
sp.setViewportView(textArea);
zoomPanel.add(sp, new GridBagConstraints(0, 3, 2, 1, 1.0, 0.0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
new Insets(5, 5, 5, 5), 0, 0));

return zoomPanel;
}

class SteppedComboBoxUI extends MetalComboBoxUI {

protected ComboPopup createPopup() {
BasicComboPopup popup = new BasicComboPopup(comboBox) {

private static final long serialVersionUID = 1L;

public void show() {

// PB got rid of the zoom variable and rewrote part of this
// method's code
// Also removed the setZoom() method
// Removed "JXLayer layer" as a variable and have it inlined

Container comboContainer = comboBox.getParent();
while (!(comboContainer instanceof JXLayer)) {
comboContainer = comboContainer.getParent();
}
JXLayer parentLayer = (JXLayer) comboContainer;
DefaultTransformModel parentModel = (DefaultTransformModel) ((TransformUI) parentLayer
.getUI()).getModel();
AffineTransform parentTransform = parentModel
.getTransform(parentLayer);

// compute width of text
int widest = getWidestItemWidth();

// Get the box's size
Dimension popupSize = comboBox.getSize();

// Set the size of the popup
popupSize.setSize(widest,
getPopupHeightForRowCount(comboBox
.getMaximumRowCount()));

// Compute the complete bounds
Rectangle popupBounds = computePopupBounds(0, comboBox
.getBounds().height, popupSize.width,
popupSize.height);

// Set the size of the scroll pane
Dimension dim = parentTransform.createTransformedShape(
popupBounds).getBounds().getSize();

scroller.setMaximumSize(dim);
scroller.setPreferredSize(dim);
scroller.setMinimumSize(dim);

// Cause it to re-layout
list.invalidate();

// Handle selection of proper item
int selectedIndex = comboBox.getSelectedIndex();
if (selectedIndex == -1) {
list.clearSelection();
} else {
list.setSelectedIndex(selectedIndex);
}

// Make sure the selected item is visible
list.ensureIndexIsVisible(list.getSelectedIndex());

// Use lightweight if asked for
setLightWeightPopupEnabled(comboBox
.isLightWeightPopupEnabled());

// PB
// Commented out the use of the original transform model
// JXLayer layer =
// TransformUtils.createTransformJXLayer(list,
// parentModel, new QualityHints());

// PB
// This version creates a new transform model, based
// on the average scale X and Y of the actual transform
// NOTE: this is dangerous because scaleX and scaleY of
// the actual transform may not reflect the scaling
// when other transformations like shearing and / or
// rotation are used.
// When aspect ratio is not preserved in the original transform model
// a very funny scaling may be the result.
DefaultTransformModel newModel = new DefaultTransformModel();
newModel
.setScale((parentTransform.getScaleX() + parentTransform
.getScaleY()) / 2.0);
JXLayer layer = TransformUtils.createTransformJXLayer(list,
newModel, new QualityHints());

scroller.setViewportView(layer);

Point point = new Point(0, comboBox.getHeight());
point = SwingUtilities.convertPoint(comboBox, point,
parentLayer);
parentTransform.transform(point, point);

// Show the popup relative to JXLayer
show(parentLayer, point.x, point.y);
}
};
popup.getAccessibleContext().setAccessibleParent(comboBox);
return popup;
}

public int getWidestItemWidth() {
int numItems = comboBox.getItemCount();
Font font = comboBox.getFont();
FontMetrics metrics = comboBox.getFontMetrics(font);

// The widest width
int widest = 0;
for (int i = 0; i < numItems; i++) {
Object item = comboBox.getItemAt(i);
int lineWidth = metrics.stringWidth(item.toString());
widest = Math.max(widest, lineWidth);
}

return widest;
}

}

}
[/code]

dpmihai
Offline
Joined: 2006-08-04

I am also interested about this. I tried to adjust the popup for my comboboxes inside an UI class :

public class SteppedComboBoxUI extends MetalComboBoxUI implements ZoomEventListener {

   private int padding = 10;
   private double zoom = 1.0;
   private JXLayer layer;

   protected ComboPopup createPopup() {
      BasicComboPopup popup = new BasicComboPopup(comboBox) {

         public void show() {

            //compute width of text
            int widest = getWidestItemWidth();

            //Get the box's size
            Dimension popupSize = comboBox.getSize();

            //Set the size of the popup
            popupSize.setSize(widest + (2 * padding),             getPopupHeightForRowCount(comboBox.getMaximumRowCount()));

            //Compute the complete bounds
            Rectangle popupBounds = computePopupBounds(0, comboBox.getBounds().height, popupSize.width, popupSize.height);

            //Set the size of the scroll pane
            Dimension dim = new Dimension((int)((popupBounds.getSize().getWidth() - 2*padding) * zoom), (int)(popupBounds.getSize().getHeight()*zoom));
            scroller.setMaximumSize(dim);
            scroller.setPreferredSize(dim);
            scroller.setMinimumSize(dim);

            //Cause it to re-layout
            list.invalidate();

            //Handle selection of proper item
            int selectedIndex = comboBox.getSelectedIndex();
            if (selectedIndex == -1) {
               list.clearSelection();
            } else {
               list.setSelectedIndex(selectedIndex);
            }

            //Make sure the selected item is visible
            list.ensureIndexIsVisible(list.getSelectedIndex());

            //Use lightweight if asked for
            setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled());

            layer = TransformUtils.createTransformJXLayer(list, zoom, new QualityHints());
            scroller.setViewportView(layer);
            DefaultTransformModel model = (DefaultTransformModel) ((TransformUI) layer.getUI()).getModel();
            model.setScale(zoom);

            Point point = new Point(popupBounds.x, popupBounds.y);
            System.out.println("point="+point);
            // HERE MUST COMPUTE THE CORRECT LOCATION (point) !!

            //Show the popup
            show(comboBox, point.x, point.y);

         }
      };
      popup.getAccessibleContext().setAccessibleParent(comboBox);
      return popup;
   }

   public int getWidestItemWidth() {
      int numItems = comboBox.getItemCount();
      Font font = comboBox.getFont();
      FontMetrics metrics = comboBox.getFontMetrics(font);

      //The widest width
      int widest = 0;
      for (int i = 0; i < numItems; i++) {
         Object item = comboBox.getItemAt(i);
         int lineWidth = metrics.stringWidth(item.toString());
         widest = Math.max(widest, lineWidth);
      }

      return widest;
   }

   public void notifyZoom(ZoomEvent event) {
      zoom = event.getZoom();
      if (zoom < 1.0) {
         zoom = 1.0;
      }

      popup = createPopup();
   }

}

Popup size is ok, only the position is something which I do not know how to compute.

Message was edited by: dpmihai

pietblok
Offline
Joined: 2003-07-17

Hi dpmihai,

I'd like to test your code, but I don't know what a ZoomEventListener or ZoomEvent are. Also, there is no main method in your code that runs a demo.

About the popup location. When you can intercept the popup location that is calculated by JComboBox (or its UI), you can thransform that point with the transform obtained from getTransform(JXLayer) on the transformModel, You can calculate an adjusted popup location from the original popup location by invoking transform(Point2D, Point2D) on that transform.

Please let us know if that works for you and, please, provide a simple runnable demo so we can learn from your efforts.

Thanks,
Piet

dpmihai
Offline
Joined: 2006-08-04

ZoomEventListener and ZoomEvent were just classes I used to notify the UI about a zoom change. I create a demo without those classes. There is a comboBox to change the zoom and a panel which will be zoomed.



My problem is I compute popup position relative to the comboBox and I use JPopupMenu method show(comboBox, point.x, point.y).



I think I have to compute the position relative to its parent container, then apply the transform as you suggested and then call show(parent, point.x, point.y). I tried this but with no luck. I will investigate further.



import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.demo.QualityHints;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
import org.pbjar.jxlayer.plaf.ext.TransformUI;

import javax.swing.*;
import javax.swing.plaf.metal.MetalComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.table.TableModel;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
* User: mihai.panaitescu
* Date: 17-Aug-2010
* Time: 11:22:56
*/
public class TransformPanelTest {

    private JXLayer layer;
   
private SteppedComboBoxUI firstComboUI;
   
private SteppedComboBoxUI secondComboUI;

    public static void main(String[] in) {
             
javax.swing.SwingUtilities.invokeLater(new Runnable() {
                 
public void run() {
                     
new TransformPanelTest();
                 
}
              })
;
         
}

    public TransformPanelTest() {

        final JComboBox cbZoom=new JComboBox(new String[] {"1","2","3"});
        cbZoom.addActionListener
(new ActionListener() {
           
public void actionPerformed(ActionEvent e) {

                double f = Double.parseDouble(cbZoom.getSelectedItem().toString());

                DefaultTransformModel model = (DefaultTransformModel) ((TransformUI) layer.getUI()).getModel();
                model.setScale
(f);
                layer.repaint
();

                firstComboUI.zoom(f);
                secondComboUI.zoom
(f);
           
}
        })
;

        JFrame frame = new JFrame();
        JPanel testPanel = createPanel
();
        testPanel.setPreferredSize
(new Dimension(400, 400));

        layer = TransformUtils.createTransformJXLayer(testPanel, 1.0, new QualityHints());

        JScrollPane scr = new JScrollPane();
        scr.setViewportView
(layer);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout
(new BorderLayout());
        frame.add
(cbZoom, BorderLayout.NORTH);
        frame.add
(scr, BorderLayout.CENTER);
        frame.pack
();
        frame.setLocationRelativeTo
(null);
        frame.setVisible
(true);
   
}

    private JPanel createPanel() {
       
JPanel zoomPanel = new JPanel();
        zoomPanel.setLayout
(new GridBagLayout());

        final JLabel marker = new javax.swing.JLabel("Testing the mouse position on zoom");
        marker.setHorizontalAlignment
(javax.swing.JLabel.CENTER);
        zoomPanel.add
(marker, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.WEST,
                GridBagConstraints.NONE,
new Insets(5,5,5,5), 0, 0));

        JComboBox firstComboBox = new JComboBox(new String[] {"First Item", "Second Item", "Third Item"});
        firstComboUI =
new SteppedComboBoxUI();
        firstComboBox.setUI
(firstComboUI);
        zoomPanel.add
(firstComboBox, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
                GridBagConstraints.NONE,
new Insets(5,5,5,5), 0, 0));

        JComboBox secondComboBox = new JComboBox(new String[] {"Text one", "Text two", "Text three"});
        secondComboUI =
new SteppedComboBoxUI();
        secondComboBox.setUI
(secondComboUI);
        zoomPanel.add
(secondComboBox, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
                GridBagConstraints.NONE,
new Insets(5,5,5,5), 0, 0));

        int rows = 2;
       
int cols = 3;
        TableModel model =
new DefaultTableModel(rows, cols);
       
for (int i=0; i<rows; i++) {
           
for (int j=0; j<cols; j++) {
               
model.setValueAt(i+j,i, j);
           
}
        }
       
JTable table = new JTable(model);       
        zoomPanel.add
(table, new GridBagConstraints(0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.WEST,
                GridBagConstraints.BOTH,
new Insets(5,5,5,5), 0, 0));
       
        JScrollPane sp =
new JScrollPane();
        sp.setPreferredSize
(new Dimension(100,100));
        JTextArea textArea =
new JTextArea();
        sp.setViewportView
(textArea);
        zoomPanel.add
(sp, new GridBagConstraints(0, 3, 2, 1, 1.0, 0.0, GridBagConstraints.WEST,
                GridBagConstraints.HORIZONTAL,
new Insets(5,5,5,5), 0, 0));       

        return zoomPanel;
   
}

    class SteppedComboBoxUI extends MetalComboBoxUI {

        private double zoom = 1.0;
       
private JXLayer layer;

        protected ComboPopup createPopup() {
           
BasicComboPopup popup = new BasicComboPopup(comboBox) {

                public void show() {

                    //compute width of text
                   
int widest = getWidestItemWidth();

                    //Get the box's size
                   
Dimension popupSize = comboBox.getSize();

                    //Set the size of the popup
                   
popupSize.setSize(widest , getPopupHeightForRowCount(comboBox.getMaximumRowCount()));

                    //Compute the complete bounds
                   
Rectangle popupBounds = computePopupBounds(0, comboBox.getBounds().height, popupSize.width, popupSize.height);

                    //Set the size of the scroll pane
                   
Dimension dim = new Dimension((int)(popupBounds.getSize().getWidth() * zoom),
                           
(int)(popupBounds.getSize().getHeight()*zoom));
                    scroller.setMaximumSize
(dim);
                    scroller.setPreferredSize
(dim);
                    scroller.setMinimumSize
(dim);

                    //Cause it to re-layout
                   
list.invalidate();

                    //Handle selection of proper item
                   
int selectedIndex = comboBox.getSelectedIndex();
                   
if (selectedIndex == -1) {
                       
list.clearSelection();
                   
} else {
                       
list.setSelectedIndex(selectedIndex);
                   
}

                    //Make sure the selected item is visible
                   
list.ensureIndexIsVisible(list.getSelectedIndex());

                    //Use lightweight if asked for
                   
setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled());

                    layer = TransformUtils.createTransformJXLayer(list, zoom, new QualityHints());
                    scroller.setViewportView
(layer);
                    DefaultTransformModel model =
(DefaultTransformModel) ((TransformUI) layer.getUI()).getModel();
                    model.setScale
(zoom);

                    Point point = new Point(popupBounds.x, popupBounds.y);

                    //Show the popup
                   
show(comboBox, point.x, point.y);

                }
            }
;
            popup.getAccessibleContext
().setAccessibleParent(comboBox);
           
return popup;
       
}

        public int getWidestItemWidth() {
           
int numItems = comboBox.getItemCount();
            Font font = comboBox.getFont
();
            FontMetrics metrics = comboBox.getFontMetrics
(font);

            //The widest width
           
int widest = 0;
           
for (int i = 0; i < numItems; i++) {
               
Object item = comboBox.getItemAt(i);
               
int lineWidth = metrics.stringWidth(item.toString());
                widest = Math.max
(widest, lineWidth);
           
}

            return widest;
       
}

        public void zoom(double f) {
           
zoom = f;
           
if (zoom < 1.0) {
               
// popup has a minimum size;
               
zoom = 1.0;
           
}
           
popup = createPopup();
       
}

    }

}

dpmihai
Offline
Joined: 2006-08-04


I manage also to correctly position the popup.

The only problem I have for now is that my popup does not disappear when I have a zoom and I click on a menu item.



import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.demo.QualityHints;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
import org.pbjar.jxlayer.plaf.ext.TransformUI;

import javax.swing.*;
import javax.swing.plaf.metal.MetalComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.table.TableModel;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
 * User: mihai.panaitescu
 * Date: 17-Aug-2010
 * Time: 11:22:56
 */
public class TransformPanelTest {

    private JXLayer layer;
    private SteppedComboBoxUI firstComboUI;
    private SteppedComboBoxUI secondComboUI;

    public static void main(String[] in) {
              javax.swing.SwingUtilities.invokeLater(new Runnable() {
                  public void run() {
                      new TransformPanelTest();
                  }
              });
          }

    public TransformPanelTest() {

        final JComboBox cbZoom=new JComboBox(new String[] {"1","2","3"});
        cbZoom.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                double f = Double.parseDouble(cbZoom.getSelectedItem().toString());

                DefaultTransformModel model = (DefaultTransformModel) ((TransformUIlayer.getUI()).getModel();
                model.setScale(f);
                layer.repaint();

                firstComboUI.zoom(f);
                secondComboUI.zoom(f);
            }
        });

        JFrame frame = new JFrame();
        JPanel testPanel = createPanel();
        testPanel.setPreferredSize(new Dimension(400400));

        layer = TransformUtils.createTransformJXLayer(testPanel, 1.0new QualityHints());

        JScrollPane scr = new JScrollPane();
        scr.setViewportView(layer);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.add(cbZoom, BorderLayout.NORTH);
        frame.add(scr, BorderLayout.CENTER);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }



    private JPanel createPanel() {
        JPanel zoomPanel = new JPanel();
        zoomPanel.setLayout(new GridBagLayout());

        final JLabel marker = new javax.swing.JLabel("Testing the mouse position on zoom");
        marker.setHorizontalAlignment(javax.swing.JLabel.CENTER);
        zoomPanel.add(marker, new GridBagConstraints(00211.00.0, GridBagConstraints.WEST,
                GridBagConstraints.HORIZONTAL, new Insets(5,5,5,5)00));

        JComboBox firstComboBox = new JComboBox(new String[] {"First Item""Second Item""Third Item"});
        firstComboUI = new SteppedComboBoxUI();
        firstComboBox.setUI(firstComboUI);
        zoomPanel.add(firstComboBox, new GridBagConstraints(01110.00.0, GridBagConstraints.WEST,
                GridBagConstraints.NONE, new Insets(5,5,5,5)00));

        JComboBox secondComboBox = new JComboBox(new String[] {"Text one""Text two""Text three"});
        secondComboUI = new SteppedComboBoxUI();
        secondComboBox.setUI(secondComboUI);
        zoomPanel.add(secondComboBox, new GridBagConstraints(11110.00.0, GridBagConstraints.WEST,
                GridBagConstraints.NONE, new Insets(5,5,5,5)00));

        int rows = 2;
        int cols = 3;
        TableModel model = new DefaultTableModel(rows, cols);
        for (int i=0; i<rows; i++) {
            for (int j=0; j<cols; j++) {
                model.setValueAt(i+j,i, j);
            }
        }
        JTable table = new JTable(model);        
        zoomPanel.add(table, new GridBagConstraints(02211.01.0, GridBagConstraints.WEST,
                GridBagConstraints.BOTH, new Insets(5,5,5,5)00));
        
        JScrollPane sp = new JScrollPane();
        sp.setPreferredSize(new Dimension(100,100));
        JTextArea textArea = new JTextArea();
        sp.setViewportView(textArea);
        zoomPanel.add(sp, new GridBagConstraints(03211.00.0, GridBagConstraints.WEST,
                GridBagConstraints.HORIZONTAL, new Insets(5,5,5,5)00));        

        return zoomPanel;
    }

    class SteppedComboBoxUI extends MetalComboBoxUI {

        private double zoom = 1.0;
        private JXLayer layer;

        protected ComboPopup createPopup() {
            BasicComboPopup popup = new BasicComboPopup(comboBox) {

                public void show() {

                    //compute width of text
                    int widest = getWidestItemWidth();

                    //Get the box's size
                    Dimension popupSize = comboBox.getSize();

                    //Set the size of the popup
                    popupSize.setSize(widest , getPopupHeightForRowCount(comboBox.getMaximumRowCount()));

                    //Compute the complete bounds
                    Rectangle popupBounds = computePopupBounds(0, comboBox.getBounds().height, popupSize.width, popupSize.height);

                    //Set the size of the scroll pane
                    Dimension dim = new Dimension((int)(popupBounds.getSize().getWidth() * zoom),
                            (int)(popupBounds.getSize().getHeight()*zoom));
                    scroller.setMaximumSize(dim);
                    scroller.setPreferredSize(dim);
                    scroller.setMinimumSize(dim);

                    //Cause it to re-layout
                    list.invalidate();

                    //Handle selection of proper item
                    int selectedIndex = comboBox.getSelectedIndex();
                    if (selectedIndex == -1) {
                        list.clearSelection();
                    else {
                        list.setSelectedIndex(selectedIndex);
                    }

                    //Make sure the selected item is visible
                    list.ensureIndexIsVisible(list.getSelectedIndex());

                    //Use lightweight if asked for
                    setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled());

                    layer = TransformUtils.createTransformJXLayer(list, zoom, new QualityHints());
                    scroller.setViewportView(layer);
                    DefaultTransformModel model = (DefaultTransformModel) ((TransformUIlayer.getUI()).getModel();
                    model.setScale(zoom);

                    Point point = comboBox.getLocation();
                    point = new Point((int)(point.x * zoom)(int)((point.y + comboBox.getHeight())*zoom));

                    //Show the popup relative to JXLayer
                    Container parent = comboBox.getParent();
                    while (!(parent instanceof JXLayer)) {
                        parent = parent.getParent();
                    }
                    show(parent, point.x, point.y);
                }
            };
            popup.getAccessibleContext().setAccessibleParent(comboBox);
            return popup;
        }



        public int getWidestItemWidth() {
            int numItems = comboBox.getItemCount();
            Font font = comboBox.getFont();
            FontMetrics metrics = comboBox.getFontMetrics(font);

            //The widest width
            int widest = 0;
            for (int i = 0; i < numItems; i++) {
                Object item = comboBox.getItemAt(i);
                int lineWidth = metrics.stringWidth(item.toString());
                widest = Math.max(widest, lineWidth);
            }

            return widest;
        }


        public void zoom(double f) {
            zoom = f;
            if (zoom < 1.0) {
                // popup has a minimum size;
                zoom = 1.0;
            }
            popup = createPopup();
        }

    }


}

pietblok
Offline
Joined: 2003-07-17

Hi dpmihai,

Nice work!

I don't know what causes the popup not to close after selection. But you know what? After I played a little bit with your source code (I wanted to use the same transform model for the base JXLayer and the popup JXLayer and get rid of the zoom variable) suddenly the popup not closing problem was gone! No idea why.

For the changes I made in your code please search on the word "PB".

Something I noticed: the popup width seems to be slightly less wide than the combo width. Don't know why and didn't look into it.

[code]
import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.demo.QualityHints;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
import org.pbjar.jxlayer.plaf.ext.TransformUI;

import javax.swing.*;
import javax.swing.plaf.metal.MetalComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.table.TableModel;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
* User: mihai.panaitescu Date: 17-Aug-2010 Time: 11:22:56
*/
public class TransformPanelTest {

private JXLayer layer;
private SteppedComboBoxUI firstComboUI;
private SteppedComboBoxUI secondComboUI;

public static void main(String[] in) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TransformPanelTest();
}
});
}

public TransformPanelTest() {

final JComboBox cbZoom = new JComboBox(new String[] { "1", "2", "3" });
cbZoom.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {

double f = Double.parseDouble(cbZoom.getSelectedItem()
.toString());

DefaultTransformModel model = (DefaultTransformModel) ((TransformUI) layer
.getUI()).getModel();
model.setScale(f);
layer.repaint();
}
});

JFrame frame = new JFrame();
JPanel testPanel = createPanel();
testPanel.setPreferredSize(new Dimension(400, 400));

layer = TransformUtils.createTransformJXLayer(testPanel, 1.0,
new QualityHints());

// Just a test to see what happens when a more complex transform is
// applied than just scaling. // PB
// ((DefaultTransformModel) ((TransformUI) layer.getUI()).getModel())
// .setRotation(Math.PI / 32.0);

JScrollPane scr = new JScrollPane();
scr.setViewportView(layer);

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(cbZoom, BorderLayout.NORTH);
frame.add(scr, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}

private JPanel createPanel() {
JPanel zoomPanel = new JPanel();
zoomPanel.setLayout(new GridBagLayout());

final JLabel marker = new javax.swing.JLabel(
"Testing the mouse position on zoom");
marker.setHorizontalAlignment(javax.swing.JLabel.CENTER);
zoomPanel.add(marker, new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
new Insets(5, 5, 5, 5), 0, 0));

JComboBox firstComboBox = new JComboBox(new String[] { "First Item",
"Second Item", "Third Item" });
firstComboUI = new SteppedComboBoxUI();
firstComboBox.setUI(firstComboUI);
zoomPanel.add(firstComboBox, new GridBagConstraints(0, 1, 1, 1, 0.0,
0.0, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(5, 5, 5, 5), 0, 0));

JComboBox secondComboBox = new JComboBox(new String[] { "Text one",
"Text two", "Text three" });
secondComboUI = new SteppedComboBoxUI();
secondComboBox.setUI(secondComboUI);
zoomPanel.add(secondComboBox, new GridBagConstraints(1, 1, 1, 1, 0.0,
0.0, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(5, 5, 5, 5), 0, 0));

int rows = 2;
int cols = 3;
TableModel model = new DefaultTableModel(rows, cols);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
model.setValueAt(i + j, i, j);
}
}
JTable table = new JTable(model);
zoomPanel.add(table, new GridBagConstraints(0, 2, 2, 1, 1.0, 1.0,
GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5,
5, 5, 5), 0, 0));

JScrollPane sp = new JScrollPane();
sp.setPreferredSize(new Dimension(100, 100));
JTextArea textArea = new JTextArea();
sp.setViewportView(textArea);
zoomPanel.add(sp, new GridBagConstraints(0, 3, 2, 1, 1.0, 0.0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
new Insets(5, 5, 5, 5), 0, 0));

return zoomPanel;
}

class SteppedComboBoxUI extends MetalComboBoxUI {

protected ComboPopup createPopup() {
BasicComboPopup popup = new BasicComboPopup(comboBox) {

public void show() {

// PB got rid of the zoom variable and rewrote part of this
// method's code
// Also removed the setZoom() method
// Removed "JXLayer layer" as a variable and have it inlined

Container comboContainer = comboBox.getParent();
while (!(comboContainer instanceof JXLayer)) {
comboContainer = comboContainer.getParent();
}
JXLayer parentLayer = (JXLayer) comboContainer;
DefaultTransformModel parentModel = (DefaultTransformModel) ((TransformUI) parentLayer
.getUI()).getModel();
AffineTransform parentTransform = parentModel
.getTransform(parentLayer);

// compute width of text
int widest = getWidestItemWidth();

// Get the box's size
Dimension popupSize = comboBox.getSize();

// Set the size of the popup
popupSize.setSize(widest,
getPopupHeightForRowCount(comboBox
.getMaximumRowCount()));

// Compute the complete bounds
Rectangle popupBounds = computePopupBounds(0, comboBox
.getBounds().height, popupSize.width,
popupSize.height);

// Set the size of the scroll pane
Dimension dim = parentTransform.createTransformedShape(
popupBounds).getBounds().getSize();

scroller.setMaximumSize(dim);
scroller.setPreferredSize(dim);
scroller.setMinimumSize(dim);

// Cause it to re-layout
list.invalidate();

// Handle selection of proper item
int selectedIndex = comboBox.getSelectedIndex();
if (selectedIndex == -1) {
list.clearSelection();
} else {
list.setSelectedIndex(selectedIndex);
}

// Make sure the selected item is visible
list.ensureIndexIsVisible(list.getSelectedIndex());

// Use lightweight if asked for
setLightWeightPopupEnabled(comboBox
.isLightWeightPopupEnabled());

JXLayer layer = TransformUtils.createTransformJXLayer(list,
parentModel, new QualityHints());
scroller.setViewportView(layer);

Point point = new Point(0, comboBox.getHeight());
point = SwingUtilities.convertPoint(comboBox, point,
parentLayer);
parentTransform.transform(point, point);

// Show the popup relative to JXLayer
show(parentLayer, point.x, point.y);
}
};
popup.getAccessibleContext().setAccessibleParent(comboBox);
return popup;
}

public int getWidestItemWidth() {
int numItems = comboBox.getItemCount();
Font font = comboBox.getFont();
FontMetrics metrics = comboBox.getFontMetrics(font);

// The widest width
int widest = 0;
for (int i = 0; i < numItems; i++) {
Object item = comboBox.getItemAt(i);
int lineWidth = metrics.stringWidth(item.toString());
widest = Math.max(widest, lineWidth);
}

return widest;
}

}

}
[/code]

Thanks,
Piet

dpmihai
Offline
Joined: 2006-08-04

Hi Piet.

You did the trick! I did not think to use the same layer as the parent container.
Because there is no need to apply a zoom manually (which in my version recreated the popup) , there is no bug with the popup closing.

Regarding the popup width, this is a functionality I need : to have the width less than the combobox width (I have some large comboboxes). That's what getWidestItemWidth() method does. But this is out of scope for this example.

To have the width as large as combobox width use :

  popupSize.setSize(comboBox.getWidth(),     getPopupHeightForRowCount(comboBox .getMaximumRowCount()));

And you also have to set the list preferred size (to have the selection color on entire item row) :

  list.setPreferredSize(new Dimension((int)dim.getWidth(), (int)list.getPreferredSize().getHeight()));

Thanks a lot for this solution.

Mihai

pietblok
Offline
Joined: 2003-07-17

Hi Mihai,

Glad it works for you.

> You did the trick! I did not think to use the same
> layer as the parent container.

I only used the same TransformModel. The popup still gets its own JXLayer, just like your solution.
So I'm not sure why in your solution the popup didn't close and in my solution it does. But apparently I changed something vital of which I'm not aware of. ;-)

Never mind, it works, and that's what counts. Best of luck,
Piet

pietblok
Offline
Joined: 2003-07-17

Hi Mihai,

I found the real cause why your popup didn't close. And it has nothing to do with transformations or JXLayer. The problem is in the setZoom() method.
[code]
public void zoom(double f) {
zoom = f;
if (zoom < 1.0) {
// popup has a minimum size;
zoom = 1.0;
}
// PB Do not create a popup here and remove that line
// That's all
// popup = createPopup();
}
[/code]

Now I can sleep better ;-)
Piet

dpmihai
Offline
Joined: 2006-08-04

Hi Piet.

Yes that was the problem. Anyway, I am very pleased about the solution without a zoom method inside UI.

Mihai

kirillcool
Offline
Joined: 2004-11-17

> It's not perfect at the larger font sizes, but
> I think it's probably the best available option with
> Swing today.

This is correct. The main attention is given to consistent scaling of all control visuals for the most common desktop configurations - for font sizes in 11-13 point range. Going beyond that - into 20+ or even 70+ point range - is work in progress. Some controls look better under very large font sizes, and some still need to be worked on.

Thanks
Kirill (Substance developer)

snoopygee
Offline
Joined: 2010-01-13

Hi Piet,

Its been a while but I'm starting to look at the issue with popups again. I wondered if there had been any progress made by yourself or elsewhere in order to solve this issue?

...In addition, I am currently trying to manually reposition the popup to the new location/size of the combobox, after the containing jxlayer panel has been resized. However after the resize occurs the combobox no longer seems to be able to report its position or size correctly. How can this be achieved? I'd assumed there might be some sort of translation method available within the containing jxlayer panel but there does not appear to be one available.

Thanks

Message was edited by: snoopygee

pietblok
Offline
Joined: 2003-07-17

Hi snoopygee,

No, I didn't make any progress on the subject. The only solution I can think of is to not wrap the JComboBox in a transforming JXLayer, but, instead, its renderer. This won't help very much if your JComboBox is part of a larger structure wrapped in a transforming JXLayer. But nevertheless, here an example:

[code]
import java.awt.BorderLayout;
import java.awt.Component;

import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;

import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;

public class ComboDemo {

public static class TransformingRenderer implements ListCellRenderer {

private static final long serialVersionUID = 1L;
private final JXLayer layer;
private final DefaultTransformModel model;
private final JPanel panel;
private final ListCellRenderer wrappedRenderer;

public TransformingRenderer(JComboBox combo) {
this.wrappedRenderer = combo.getRenderer();
model = new DefaultTransformModel();
layer = TransformUtils.createTransformJXLayer(null, model);
panel = new JPanel(new BorderLayout());
panel.add(layer);
}

@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
layer.setView((JComponent) wrappedRenderer
.getListCellRendererComponent(list, value, index,
isSelected, cellHasFocus));
return panel;
}

public DefaultTransformModel getModel() {
return model;
}

}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {

@Override
public void run() {
new ComboDemo().createGUI();
}
});
}

private void createGUI() {
String[] selections = new String[12];
for (int index = 0; index < selections.length; index++) {
selections[index] = "Selection " + (index + 1);
}
JComboBox combo = new JComboBox(selections);

TransformingRenderer renderer = new TransformingRenderer(combo);
combo.setRenderer(renderer);

renderer.getModel().setScale(3);
renderer.getModel().setScaleToPreferredSize(true);
renderer.getModel().setPreserveAspectRatio(true);

JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(combo);
frame.pack();
frame.setVisible(true);
}

}
[/code]

Thanks,
Piet

pietblok
Offline
Joined: 2003-07-17

Hi,

I think that what's happening is the following:

1) The dropdown box of the JComboBox is not part of the hierarchy of the JComboBox. Instead, it is contained in some popup. You may have a look at the Popup, PopupFactory and JPopupMenu classes in the swing package.

2) The popup location (and probably size) will be determined with the official layout position (the getBounds()) of the JComboBox, not the rendered position on screen.

The only way I can think of to have those popups transformed as well is to use JXLayer with a TransformUI in that popup. The location and size of the popup should also be adjusted. I'm afraid that's quite some task!

If you ever delve into this and find the way to go, I'm very much interested in how you did it.

Thanks,

Piet

snoopygee
Offline
Joined: 2010-01-13

Hi Piet,

Thanks again for your quick response.

As you say, this seems like quite a bit of work and unfortunetly I currently do not have the time to commit to this. However if I do find time in the near future I will follow your guidelines and will post back any solutions that I find.

The reason I have been looking at the JXLayer and specifically the zooming capability, is that I had hoped to "wrap" some of my current applications so that I could enlarge everything within the application when larger screen resolutions were available. A little like the internet explorer 8 functionality you get where you can hold CTRL and use the mouse wheel to rescale the whole contents of the browser. I realise I could do this manually by resizing each control depending on screen resolution size, however this would also be a very large refactoring exercise that I would prefer not to undertake if I can help it. With your experience in these matters, would you be able to suggest an alterative technique/technology that would be a quick win to implement this kind of funcitonality for an entire application?

Thanks Again.

pietblok
Offline
Joined: 2003-07-17

Hi,

No, currently I've no idea how one would implement this reliably for a whole application.

Furthermore, there are other limitations of the TransformUI in JXLayer that I 'm aware of:

1) Drag and drop won't work on transformed components.
2) Scrollable delegation is not flawless (should not be a problem when the scrollpane is somewhere inside JXLayer)

And maybe there are some more.

Piet

snoopygee
Offline
Joined: 2010-01-13

OK thanks for the advice.

F.Y.I As the JXLayer doesn't seem to provide the solution I need I've started looking into the new Nimbus Look and Feel which comes with JDK6_10 and above. There seems to be a facility there to really simply resize all application components. I'm just starting to look into it so not sure how far it goes but the information I found is here: http://java.sun.com/docs/books/tutorial/uiswing/lookandfeel/size.html

Thanks

Message was edited by: snoopygee

skyewire
Offline
Joined: 2003-11-17

Hi,

You might want to take a look at Substance L&F, as the author has put a lot of work into getting the L&F to work correctly on high DPI displays. Launch the test app from here:

https://substance.dev.java.net/see.html

At the bottom right, you'll see a slider to set the font size (default on my PC is Tahoma 11pt) and changing this will change the size of all components, including combo-boxes and other tricky things like that. It's not perfect at the larger font sizes, but I think it's probably the best available option with Swing today.

You'll also want to make use of a layout manager that works with logical units (aka dialog units) instead of pixel sizes, so that your layouts and spacing scale appropriately with the DPI. A couple of good ones are JGoodies FormLayout and MigLayout.

Regards,
Daniel

snoopygee
Offline
Joined: 2010-01-13

Hi Daniel,

Thanks for your response, at first glance this L&F seems very good and provides the scaling capability I'm looking for. I'll have to take a look under the hood to see if I can easily implement it's scaling technique to my current applications customised L&F but it's a good place to start.

Thanks again for your posting.