Real Vertical Tabs on JTabbedPane! solution
I made up this small class from HOURS of testing. Don't tell me this answer was somehwere else and my work was in vain either :D
This you select the verticle tabs, then you add this as your UI
jTabbedPane1.setUI(new TPUI() );
and the TPUI is below. I think it could use some more work and I hope you post any improvements that makes it more dynamic. Right now it only works on right side tabs.
Oh and htmlViews was private so I didn't fool with that.
Here goes
publicclass TPUIextends BasicTabbedPaneUI{
protectedvoid paintText(Graphics g,int tabPlacement,
Font font, FontMetrics metrics,int tabIndex,
String title, Rectangle textRect,
boolean isSelected){
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
float ratiowh = (float)d.width / d.height;
float ratiohw = (float)d.height / d.width;
Graphics2D g2d = (Graphics2D)g;
java.awt.geom.AffineTransform tt = g2d.getTransform();
java.awt.geom.AffineTransform at =new java.awt.geom.AffineTransform();
at.setToRotation(-Math.PI/2.0);
g2d.setFont(font);
g2d.setTransform(at);
View v;
/*if (htmlViews!=null &&
(v = (View)htmlViews.elementAt(tabIndex))!=null) {
v.paint(g, textRect);
} else {*/
if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)){
g2d.setColor(tabPane.getForegroundAt(tabIndex));
//g.drawString(title, textRect.x,textRect.y + metrics.getAscent());
g.drawString(title, -(textRect.y + SwingUtilities.computeStringWidth(metrics, title))+textRect.height ,textRect.x+metrics.getAscent());
}else{// tab disabled
g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
//g.drawString(title,
//textRect.x, textRect.y + metrics.getAscent());
g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
g.drawString(title,
textRect.x - 1, textRect.y + metrics.getAscent() - 1);
}
g2d.setTransform( tt ) ;
//}
}
protectedint calculateTabHeight(int tabPlacement,int tabIndex,int fontHeight){
Icon icon = getIconForTab(tabIndex);
FontMetrics metrics = getFontMetrics();
Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
int width = 0;
View v;
if (icon !=null){
width += icon.getIconHeight() + textIconGap;
}
//if ((htmlViews!=null) &&
//((v = (View)htmlViews.elementAt(tabIndex)) != null)) {
//width += (int)v.getPreferredSpan(View.X_AXIS);
//} else {
String title = tabPane.getTitleAt(tabIndex);
width += SwingUtilities.computeStringWidth(metrics, title);
//}
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
float ratio = (float)d.width / d.height;
int height = (int)(ratio * width);
height += tabInsets.top + tabInsets.bottom + 2;
return height;
}
protectedint calculateTabWidth(int tabPlacement,int tabIndex, FontMetrics metrics){
int height = 0;
View v;
//if ( (htmlViews!=null) &&
//((v = (View)htmlViews.elementAt(tabIndex)) != null)) {
//height += (int)v.getPreferredSpan(View.Y_AXIS);
//} else {
height += metrics.getHeight();
//}
Icon icon = getIconForTab(tabIndex);
Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
if (icon !=null){
height = Math.max(height, icon.getIconWidth());
}
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
float ratio = (float)d.height / d.width;
int width = (int)(ratio * height);
width += tabInsets.left + tabInsets.right + 3;
return width;
}
}
[6129 byte] By [
dnoyeB] at [2007-9-27 4:45:48]

Also on initial display the tab titles are not proper for some reason. once you touch one of the bottom tabs they look ok again. Unsure what that is about.
I forgot to do the disabled tab too because I dont have any disabled tabs but its probably almost a cut and paste from the enabled tab.
Turns out that when the L&F is changed it reverts back to the old UI. Any Ideas about this?
This is a better paint routine that follows the rules better and renders nicer.
The tabs format properly on initial display and I added the thing which displays the disabled tabs though I didnt test it. this should be enough for anyone to modify to their liking.
protected void paintText(Graphics g, int tabPlacement,
Font font, FontMetrics metrics, int tabIndex,
String title, Rectangle textRect,
boolean isSelected) {
Graphics2D g2d = (Graphics2D)g;
java.awt.geom.AffineTransform tt = g2d.getTransform();
g2d.setFont(font);
g2d.rotate(-Math.PI/2.0);
/*View v;
if (htmlViews!=null &&
(v = (View)htmlViews.elementAt(tabIndex))!=null) {
v.paint(g, textRect);
} else {*/
if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
g2d.setColor(tabPane.getForegroundAt(tabIndex));
//g.drawString(title, textRect.x,textRect.y + metrics.getAscent());
g.drawString(title, -(textRect.y + SwingUtilities.computeStringWidth(metrics, title))+textRect.height ,textRect.x+metrics.getAscent());
} else { // tab disabled
g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
//g.drawString(title,
//textRect.x, textRect.y + metrics.getAscent());
g.drawString(title, -(textRect.y + SwingUtilities.computeStringWidth(metrics, title))+textRect.height ,textRect.x+metrics.getAscent());
g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
//g.drawString(title, textRect.x - 1, textRect.y + metrics.getAscent() - 1);
g.drawString(title, -(textRect.y + SwingUtilities.computeStringWidth(metrics, title)-1)+textRect.height ,textRect.x+metrics.getAscent()-1);
}
g2d.setTransform( tt ) ;
}
An alternative solution is to use rotated text on icons:
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.metal.*;
public class VerticalTabbedPane extends JTabbedPane
{
boolean m_bVertical;
public VerticalTabbedPane()
{
this(TOP);
}
public VerticalTabbedPane(int tabPlacement)
{
super(tabPlacement);
m_bVertical = (tabPlacement == LEFT || tabPlacement == RIGHT);
setUI(new VerticalTabbedPaneUI(m_bVertical));
}
public void addTab(String s, Component c)
{
insertTab(
m_bVertical?null:s, m_bVertical?
new VerticalTextIcon(s, getTabPlacement() == RIGHT):null,
c,
null,
getTabCount());
}
private class VerticalTextIcon implements Icon
{
private String m_sText;
private boolean m_bClockwise;
public VerticalTextIcon(String sText, boolean bClockwise)
{
m_sText = sText;
m_bClockwise = bClockwise;
}
public void paintIcon(Component c, Graphics g, int x, int y)
{
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.rotate (Math.toRadians(m_bClockwise?90:-90), x, y);
g2.drawString(
m_sText,
x - (m_bClockwise?0:getIconHeight()) + 8,
y + (m_bClockwise?0:getIconWidth()));
g2.rotate (Math.toRadians(m_bClockwise?-90:90), x, y);
}
public int getIconWidth()
{
return 10;
}
public int getIconHeight()
{
return getFontMetrics(getFont()).stringWidth(m_sText) + 20;
}
}
public static void main(String args[])
{
JFrame frame = new JFrame("Vertical Text");
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout());
VerticalTabbedPane tpane1 = new VerticalTabbedPane(JTabbedPane.RIGHT);
tpane1.addTab("News", new JPanel());
tpane1.addTab("Sports", new JPanel());
tpane1.addTab("Weather", new JPanel());
VerticalTabbedPane tpane2 = new VerticalTabbedPane(JTabbedPane.LEFT);
tpane2.addTab("News", new JPanel());
tpane2.addTab("Sports", new JPanel());
tpane2.addTab("Weather", new JPanel());
VerticalTabbedPane tpane3 = new VerticalTabbedPane(JTabbedPane.BOTTOM);
tpane3.addTab("News", new JPanel());
tpane3.addTab("Sports", new JPanel());
tpane3.addTab("Weather", new JPanel());
VerticalTabbedPane tpane4 = new VerticalTabbedPane(JTabbedPane.TOP);
tpane4.addTab("News", new JPanel());
tpane4.addTab("Sports", new JPanel());
tpane4.addTab("Weather", new JPanel());
VerticalTabbedPane mainPane = new VerticalTabbedPane();
mainPane.addTab("Left", tpane2);
mainPane.addTab("Right", tpane1);
mainPane.addTab("Top", tpane4);
mainPane.addTab("Bottom", tpane3);
frame.getContentPane().add(mainPane);
frame.setVisible(true);
}
}
class VerticalTabbedPaneUI extends MetalTabbedPaneUI
{
private boolean m_bVertical;
public VerticalTabbedPaneUI(boolean bVertical)
{
m_bVertical = bVertical;
}
protected void installDefaults()
{
super.installDefaults();
tabAreaInsets = new Insets(0, 0, 0, 0);
if (m_bVertical)
{
tabInsets = new Insets(0, 1, 0, 1);
selectedTabPadInsets = new Insets(0, 1, 0, 1);
}
}
}
mrai3 at 2007-7-5 14:39:37 >

I tried that solution first but couldnt get it to do something I don't remember. It looks like a better solution. Does it have any problems you are aware of?
The solution I proposed has 1 big problem in the end I have run into. The tab Rectangle is calculated seperately from the text Rectangle. the text Rectangle uses some calculation from SwingUtilities which is unaware and presently incapable of comprehending rotated text. so I have to use the tab rectangle as opposed to the true text rect. In the end it should function just as well as the other solution though it is a little more tricky and invasive. Im going to convert to the icon method. I could never find a complete icon method in the forums. I hope this stays in the forums and can be searched so anyone else looking will see.
After cleaning up the code significantly since what you posted does not compile.. I got it to work. The text is appearantly anti-aliased? It does not appear like the rest of the text on the other panels. its quite ugly but I assume its from anti-aliasing. Also the sizing is not correct. and it does not look dynamic by this entry
public int getIconHeight(){
return getFontMetrics(getFont()).stringWidth(m_sText) + 20;
}
The arbitrary value of 20 added makes me suspecious that whoever made this was aware of the sizing problem.
So both techniques have their drawbacks. The the one I came up with currently the main drawback is that you can not let the tabbedPane use swingUtilities to determine the textBox size like it wants too and I have yet to find a way to override this. Im sure I will eventually..
Thanks for posting the code. I tried the icon method before but didnt get it to really work. Does your text also look different with that method?
I should have added that I had the same sizing problem and my solution was to take the ration of the screen width to height and use that to increasing the size based on the rotated string width value. Know whut I mean :D
Values are quite ad hoc in my example, it looks good in our application though. I haven't paid any attention to the sizing problem. There are lots of room of improvement of the code, that's partly why I posted the code, to get some feedback.
One issue is wrapping if the total size of the vertical tabs exceeds the panel size. It ought to wrap somehow. Concerning anti-aliasing, in my application it looks worse without. I agree the icon height etc. should be calculated more dynamically.
mrai3 at 2007-7-5 14:39:37 >

Could you post your latest working version? Thanks!
mrai3 at 2007-7-5 14:39:37 >

public class TPUI extends BasicTabbedPaneUI {
public static javax.swing.plaf.ComponentUI createUI(JComponent c) {
return new TPUI();
}
protected void paintText(Graphics g, int tabPlacement,
Font font, FontMetrics metrics, int tabIndex,
String title, Rectangle textRect,
boolean isSelected) {
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
float ratio = (float)d.width / d.height;
Graphics2D g2d = (Graphics2D)g;
java.awt.geom.AffineTransform tt = g2d.getTransform();
g2d.setFont(font);
g2d.rotate(-Math.PI/2.0);
/*View v;
if (htmlViews!=null &&
(v = (View)htmlViews.elementAt(tabIndex))!=null) {
v.paint(g, textRect);
} else {*/
if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
g2d.setColor(tabPane.getForegroundAt(tabIndex));
//g.drawString(title, textRect.x,textRect.y + metrics.getAscent());
//g.drawString(title, -(int)((textRect.x + textRect.height )),textRect.x+metrics.getAscent());
g.drawString(title, -(int)(textRect.y + textRect.height)+10,textRect.x+metrics.getAscent());
} else { // tab disabled
g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
//g.drawString(title, textRect.x, textRect.y + metrics.getAscent());
g.drawString(title, -(textRect.y + SwingUtilities.computeStringWidth(metrics, title))+textRect.height ,textRect.x+metrics.getAscent());
g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
//g.drawString(title, textRect.x - 1, textRect.y + metrics.getAscent() - 1);
g.drawString(title, -(textRect.y + SwingUtilities.computeStringWidth(metrics, title)-1)+textRect.height ,textRect.x+metrics.getAscent()-1);
}
g2d.setTransform( tt ) ;
}
protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
Icon icon = getIconForTab(tabIndex);
FontMetrics metrics = getFontMetrics();
Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
int width = 0;
View v;
if (icon != null) {
width += icon.getIconHeight() + textIconGap;
}
//if ((htmlViews!=null) &&
//((v = (View)htmlViews.elementAt(tabIndex)) != null)) {
//width += (int)v.getPreferredSpan(View.X_AXIS);
//} else {
String title = tabPane.getTitleAt(tabIndex);
width += SwingUtilities.computeStringWidth(metrics, title);
//}
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
float ratio = (float)d.width / d.height;
int height = (int)(ratio * width);
height += tabInsets.top + tabInsets.bottom + 2;
return height;
}
protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
int height = 0;
//View v;
//if ( (htmlViews!=null) &&
//((v = (View)htmlViews.elementAt(tabIndex)) != null)) {
//height += (int)v.getPreferredSpan(View.Y_AXIS);
//} else {
height += metrics.getHeight();
//}
Icon icon = getIconForTab(tabIndex);
Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
if (icon != null) {
height = Math.max(height, icon.getIconWidth());
}
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
float ratio = (float)d.height / d.width;
int width = (int)(ratio * height);
width += tabInsets.left + tabInsets.right + 3;
return width;
}
protected void layoutLabel(int tabPlacement,
FontMetrics metrics, int tabIndex,
String title, Icon icon,
Rectangle tabRect, Rectangle iconRect,
Rectangle textRect, boolean isSelected ) {
textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
SwingUtilities.layoutCompoundLabel((JComponent) tabPane,
metrics, title, icon,
SwingUtilities.CENTER,
SwingUtilities.CENTER,
SwingUtilities.CENTER,
SwingUtilities.TRAILING,
tabRect,
iconRect,
textRect,
textIconGap);
int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
iconRect.x += xNudge;
iconRect.y += yNudge;
textRect.setBounds(tabRect); //should add some insets here to make text rect sit inside tab rect
textRect.x += xNudge;
textRect.y += yNudge;
}
}
You simply set this as the UI and it works. Do you want the code I worked up of the icon style too?
there is also some good code on:
http://www.macdevcenter.com/pub/a/mac/2002/03/22/vertical_text.html
to make it look a lot nicer, just add:
Graphics2D g2 = (Graphics2D) gr;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);