1 /* 2 * Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.synth; 27 28 import java.awt.Component; 29 import java.awt.Container; 30 import java.awt.Dimension; 31 import java.awt.FontMetrics; 32 import java.awt.Graphics; 33 import java.awt.Image; 34 import java.awt.Insets; 35 import java.awt.LayoutManager; 36 import java.awt.event.MouseAdapter; 37 import java.awt.event.MouseEvent; 38 import java.beans.PropertyChangeEvent; 39 import java.beans.PropertyChangeListener; 40 import java.beans.PropertyVetoException; 41 42 import javax.swing.Icon; 43 import javax.swing.ImageIcon; 44 import javax.swing.JButton; 45 import javax.swing.JComponent; 46 import javax.swing.JInternalFrame; 47 import javax.swing.JMenuItem; 48 import javax.swing.JPopupMenu; 49 import javax.swing.JSeparator; 50 import javax.swing.SwingConstants; 51 import javax.swing.UIManager; 52 import javax.swing.plaf.UIResource; 53 import javax.swing.plaf.basic.BasicInternalFrameTitlePane; 54 55 import sun.swing.SwingUtilities2; 56 57 /** 58 * The class that manages a synth title bar 59 * 60 * @author David Kloba 61 * @author Joshua Outwater 62 * @author Steve Wilson 63 */ 64 @SuppressWarnings("serial") // Superclass is not serializable across versions 65 class SynthInternalFrameTitlePane extends BasicInternalFrameTitlePane 66 implements SynthUI, PropertyChangeListener { 67 68 protected JPopupMenu systemPopupMenu; 69 protected JButton menuButton; 70 71 private SynthStyle style; 72 private int titleSpacing; 73 private int buttonSpacing; 74 // Alignment for the title, one of SwingConstants.(LEADING|TRAILING|CENTER) 75 private int titleAlignment; 76 SynthInternalFrameTitlePane(JInternalFrame f)77 public SynthInternalFrameTitlePane(JInternalFrame f) { 78 super(f); 79 } 80 getUIClassID()81 public String getUIClassID() { 82 return "InternalFrameTitlePaneUI"; 83 } 84 getContext(JComponent c)85 public SynthContext getContext(JComponent c) { 86 return getContext(c, getComponentState(c)); 87 } 88 getContext(JComponent c, int state)89 public SynthContext getContext(JComponent c, int state) { 90 return SynthContext.getContext(c, style, state); 91 } 92 getRegion(JComponent c)93 private Region getRegion(JComponent c) { 94 return SynthLookAndFeel.getRegion(c); 95 } 96 getComponentState(JComponent c)97 private int getComponentState(JComponent c) { 98 if (frame != null) { 99 if (frame.isSelected()) { 100 return SELECTED; 101 } 102 } 103 return SynthLookAndFeel.getComponentState(c); 104 } 105 addSubComponents()106 protected void addSubComponents() { 107 menuButton.setName("InternalFrameTitlePane.menuButton"); 108 iconButton.setName("InternalFrameTitlePane.iconifyButton"); 109 maxButton.setName("InternalFrameTitlePane.maximizeButton"); 110 closeButton.setName("InternalFrameTitlePane.closeButton"); 111 112 add(menuButton); 113 add(iconButton); 114 add(maxButton); 115 add(closeButton); 116 } 117 installListeners()118 protected void installListeners() { 119 super.installListeners(); 120 frame.addPropertyChangeListener(this); 121 addPropertyChangeListener(this); 122 } 123 uninstallListeners()124 protected void uninstallListeners() { 125 frame.removePropertyChangeListener(this); 126 removePropertyChangeListener(this); 127 super.uninstallListeners(); 128 } 129 updateStyle(JComponent c)130 private void updateStyle(JComponent c) { 131 SynthContext context = getContext(this, ENABLED); 132 SynthStyle oldStyle = style; 133 style = SynthLookAndFeel.updateStyle(context, this); 134 if (style != oldStyle) { 135 maxIcon = 136 style.getIcon(context,"InternalFrameTitlePane.maximizeIcon"); 137 minIcon = 138 style.getIcon(context,"InternalFrameTitlePane.minimizeIcon"); 139 iconIcon = 140 style.getIcon(context,"InternalFrameTitlePane.iconifyIcon"); 141 closeIcon = 142 style.getIcon(context,"InternalFrameTitlePane.closeIcon"); 143 titleSpacing = style.getInt(context, 144 "InternalFrameTitlePane.titleSpacing", 2); 145 buttonSpacing = style.getInt(context, 146 "InternalFrameTitlePane.buttonSpacing", 2); 147 String alignString = (String)style.get(context, 148 "InternalFrameTitlePane.titleAlignment"); 149 titleAlignment = SwingConstants.LEADING; 150 if (alignString != null) { 151 alignString = alignString.toUpperCase(); 152 if (alignString.equals("TRAILING")) { 153 titleAlignment = SwingConstants.TRAILING; 154 } 155 else if (alignString.equals("CENTER")) { 156 titleAlignment = SwingConstants.CENTER; 157 } 158 } 159 } 160 } 161 installDefaults()162 protected void installDefaults() { 163 super.installDefaults(); 164 updateStyle(this); 165 } 166 uninstallDefaults()167 protected void uninstallDefaults() { 168 SynthContext context = getContext(this, ENABLED); 169 style.uninstallDefaults(context); 170 style = null; 171 JInternalFrame.JDesktopIcon di = frame.getDesktopIcon(); 172 if(di != null && di.getComponentPopupMenu() == systemPopupMenu) { 173 // Release link to systemMenu from the JInternalFrame 174 di.setComponentPopupMenu(null); 175 } 176 super.uninstallDefaults(); 177 } 178 179 /** 180 * A subclass of {@code JPopupMenu} that implements {@code UIResource}. 181 */ 182 @SuppressWarnings("serial") // Superclass is not serializable across versions 183 private static class JPopupMenuUIResource extends JPopupMenu implements 184 UIResource { } 185 assembleSystemMenu()186 protected void assembleSystemMenu() { 187 systemPopupMenu = new JPopupMenuUIResource(); 188 addSystemMenuItems(systemPopupMenu); 189 enableActions(); 190 menuButton = createNoFocusButton(); 191 updateMenuIcon(); 192 menuButton.addMouseListener(new MouseAdapter() { 193 public void mousePressed(MouseEvent e) { 194 try { 195 frame.setSelected(true); 196 } catch(PropertyVetoException pve) { 197 } 198 showSystemMenu(); 199 } 200 }); 201 JPopupMenu p = frame.getComponentPopupMenu(); 202 if (p == null || p instanceof UIResource) { 203 frame.setComponentPopupMenu(systemPopupMenu); 204 } 205 if (frame.getDesktopIcon() != null) { 206 p = frame.getDesktopIcon().getComponentPopupMenu(); 207 if (p == null || p instanceof UIResource) { 208 frame.getDesktopIcon().setComponentPopupMenu(systemPopupMenu); 209 } 210 } 211 setInheritsPopupMenu(true); 212 } 213 addSystemMenuItems(JPopupMenu menu)214 protected void addSystemMenuItems(JPopupMenu menu) { 215 JMenuItem mi = menu.add(restoreAction); 216 mi.setMnemonic(getButtonMnemonic("restore")); 217 mi = menu.add(moveAction); 218 mi.setMnemonic(getButtonMnemonic("move")); 219 mi = menu.add(sizeAction); 220 mi.setMnemonic(getButtonMnemonic("size")); 221 mi = menu.add(iconifyAction); 222 mi.setMnemonic(getButtonMnemonic("minimize")); 223 mi = menu.add(maximizeAction); 224 mi.setMnemonic(getButtonMnemonic("maximize")); 225 menu.add(new JSeparator()); 226 mi = menu.add(closeAction); 227 mi.setMnemonic(getButtonMnemonic("close")); 228 } 229 getButtonMnemonic(String button)230 private static int getButtonMnemonic(String button) { 231 try { 232 return Integer.parseInt(UIManager.getString( 233 "InternalFrameTitlePane." + button + "Button.mnemonic")); 234 } catch (NumberFormatException e) { 235 return -1; 236 } 237 } 238 showSystemMenu()239 protected void showSystemMenu() { 240 Insets insets = frame.getInsets(); 241 if (!frame.isIcon()) { 242 systemPopupMenu.show(frame, menuButton.getX(), getY() + getHeight()); 243 } else { 244 systemPopupMenu.show(menuButton, 245 getX() - insets.left - insets.right, 246 getY() - systemPopupMenu.getPreferredSize().height - 247 insets.bottom - insets.top); 248 } 249 } 250 251 // SynthInternalFrameTitlePane has no UI, we'll invoke paint on it. paintComponent(Graphics g)252 public void paintComponent(Graphics g) { 253 SynthContext context = getContext(this); 254 SynthLookAndFeel.update(context, g); 255 context.getPainter().paintInternalFrameTitlePaneBackground(context, 256 g, 0, 0, getWidth(), getHeight()); 257 paint(context, g); 258 } 259 paint(SynthContext context, Graphics g)260 protected void paint(SynthContext context, Graphics g) { 261 String title = frame.getTitle(); 262 263 if (title != null) { 264 SynthStyle style = context.getStyle(); 265 266 g.setColor(style.getColor(context, ColorType.TEXT_FOREGROUND)); 267 g.setFont(style.getFont(context)); 268 269 // Center text vertically. 270 FontMetrics fm = SwingUtilities2.getFontMetrics(frame, g); 271 int baseline = (getHeight() + fm.getAscent() - fm.getLeading() - 272 fm.getDescent()) / 2; 273 JButton lastButton = null; 274 if (frame.isIconifiable()) { 275 lastButton = iconButton; 276 } 277 else if (frame.isMaximizable()) { 278 lastButton = maxButton; 279 } 280 else if (frame.isClosable()) { 281 lastButton = closeButton; 282 } 283 int maxX; 284 int minX; 285 boolean ltr = SynthLookAndFeel.isLeftToRight(frame); 286 int titleAlignment = this.titleAlignment; 287 if (ltr) { 288 if (lastButton != null) { 289 maxX = lastButton.getX() - titleSpacing; 290 } 291 else { 292 maxX = frame.getWidth() - frame.getInsets().right - 293 titleSpacing; 294 } 295 minX = menuButton.getX() + menuButton.getWidth() + 296 titleSpacing; 297 } 298 else { 299 if (lastButton != null) { 300 minX = lastButton.getX() + lastButton.getWidth() + 301 titleSpacing; 302 } 303 else { 304 minX = frame.getInsets().left + titleSpacing; 305 } 306 maxX = menuButton.getX() - titleSpacing; 307 if (titleAlignment == SwingConstants.LEADING) { 308 titleAlignment = SwingConstants.TRAILING; 309 } 310 else if (titleAlignment == SwingConstants.TRAILING) { 311 titleAlignment = SwingConstants.LEADING; 312 } 313 } 314 String clippedTitle = getTitle(title, fm, maxX - minX); 315 if (clippedTitle == title) { 316 // String fit, align as necessary. 317 if (titleAlignment == SwingConstants.TRAILING) { 318 minX = maxX - style.getGraphicsUtils(context). 319 computeStringWidth(context, g.getFont(), fm, title); 320 } 321 else if (titleAlignment == SwingConstants.CENTER) { 322 int width = style.getGraphicsUtils(context). 323 computeStringWidth(context, g.getFont(), fm, title); 324 minX = Math.max(minX, (getWidth() - width) / 2); 325 minX = Math.min(maxX - width, minX); 326 } 327 } 328 style.getGraphicsUtils(context).paintText( 329 context, g, clippedTitle, minX, baseline - fm.getAscent(), -1); 330 } 331 } 332 paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h)333 public void paintBorder(SynthContext context, Graphics g, int x, 334 int y, int w, int h) { 335 context.getPainter().paintInternalFrameTitlePaneBorder(context, 336 g, x, y, w, h); 337 } 338 createLayout()339 protected LayoutManager createLayout() { 340 SynthContext context = getContext(this); 341 LayoutManager lm = 342 (LayoutManager)style.get(context, "InternalFrameTitlePane.titlePaneLayout"); 343 return (lm != null) ? lm : new SynthTitlePaneLayout(); 344 } 345 propertyChange(PropertyChangeEvent evt)346 public void propertyChange(PropertyChangeEvent evt) { 347 if (evt.getSource() == this) { 348 if (SynthLookAndFeel.shouldUpdateStyle(evt)) { 349 updateStyle(this); 350 } 351 } 352 else { 353 // Changes for the internal frame 354 if (evt.getPropertyName() == JInternalFrame.FRAME_ICON_PROPERTY) { 355 updateMenuIcon(); 356 } 357 } 358 } 359 360 /** 361 * Resets the menuButton icon to match that of the frame. 362 */ updateMenuIcon()363 private void updateMenuIcon() { 364 Icon frameIcon = frame.getFrameIcon(); 365 SynthContext context = getContext(this); 366 if (frameIcon != null) { 367 Dimension maxSize = (Dimension)context.getStyle().get(context, 368 "InternalFrameTitlePane.maxFrameIconSize"); 369 int maxWidth = 16; 370 int maxHeight = 16; 371 if (maxSize != null) { 372 maxWidth = maxSize.width; 373 maxHeight = maxSize.height; 374 } 375 if ((frameIcon.getIconWidth() > maxWidth || 376 frameIcon.getIconHeight() > maxHeight) && 377 (frameIcon instanceof ImageIcon)) { 378 frameIcon = new ImageIcon(((ImageIcon)frameIcon). 379 getImage().getScaledInstance(maxWidth, maxHeight, 380 Image.SCALE_SMOOTH)); 381 } 382 } 383 menuButton.setIcon(frameIcon); 384 } 385 386 387 class SynthTitlePaneLayout implements LayoutManager { addLayoutComponent(String name, Component c)388 public void addLayoutComponent(String name, Component c) {} removeLayoutComponent(Component c)389 public void removeLayoutComponent(Component c) {} preferredLayoutSize(Container c)390 public Dimension preferredLayoutSize(Container c) { 391 return minimumLayoutSize(c); 392 } 393 minimumLayoutSize(Container c)394 public Dimension minimumLayoutSize(Container c) { 395 SynthContext context = getContext( 396 SynthInternalFrameTitlePane.this); 397 int width = 0; 398 int height = 0; 399 400 int buttonCount = 0; 401 Dimension pref; 402 403 if (frame.isClosable()) { 404 pref = closeButton.getPreferredSize(); 405 width += pref.width; 406 height = Math.max(pref.height, height); 407 buttonCount++; 408 } 409 if (frame.isMaximizable()) { 410 pref = maxButton.getPreferredSize(); 411 width += pref.width; 412 height = Math.max(pref.height, height); 413 buttonCount++; 414 } 415 if (frame.isIconifiable()) { 416 pref = iconButton.getPreferredSize(); 417 width += pref.width; 418 height = Math.max(pref.height, height); 419 buttonCount++; 420 } 421 pref = menuButton.getPreferredSize(); 422 width += pref.width; 423 height = Math.max(pref.height, height); 424 425 width += Math.max(0, (buttonCount - 1) * buttonSpacing); 426 427 FontMetrics fm = SynthInternalFrameTitlePane.this.getFontMetrics( 428 getFont()); 429 SynthGraphicsUtils graphicsUtils = context.getStyle(). 430 getGraphicsUtils(context); 431 String frameTitle = frame.getTitle(); 432 int title_w = frameTitle != null ? graphicsUtils. 433 computeStringWidth(context, fm.getFont(), 434 fm, frameTitle) : 0; 435 int title_length = frameTitle != null ? frameTitle.length() : 0; 436 437 // Leave room for three characters in the title. 438 if (title_length > 3) { 439 int subtitle_w = graphicsUtils.computeStringWidth(context, 440 fm.getFont(), fm, frameTitle.substring(0, 3) + "..."); 441 width += (title_w < subtitle_w) ? title_w : subtitle_w; 442 } else { 443 width += title_w; 444 } 445 446 height = Math.max(fm.getHeight() + 2, height); 447 448 width += titleSpacing + titleSpacing; 449 450 Insets insets = getInsets(); 451 height += insets.top + insets.bottom; 452 width += insets.left + insets.right; 453 return new Dimension(width, height); 454 } 455 center(Component c, Insets insets, int x, boolean trailing)456 private int center(Component c, Insets insets, int x, 457 boolean trailing) { 458 Dimension pref = c.getPreferredSize(); 459 if (trailing) { 460 x -= pref.width; 461 } 462 c.setBounds(x, insets.top + 463 (getHeight() - insets.top - insets.bottom - 464 pref.height) / 2, pref.width, pref.height); 465 if (pref.width > 0) { 466 if (trailing) { 467 return x - buttonSpacing; 468 } 469 return x + pref.width + buttonSpacing; 470 } 471 return x; 472 } 473 layoutContainer(Container c)474 public void layoutContainer(Container c) { 475 Insets insets = c.getInsets(); 476 Dimension pref; 477 478 if (SynthLookAndFeel.isLeftToRight(frame)) { 479 center(menuButton, insets, insets.left, false); 480 int x = getWidth() - insets.right; 481 if (frame.isClosable()) { 482 x = center(closeButton, insets, x, true); 483 } 484 if (frame.isMaximizable()) { 485 x = center(maxButton, insets, x, true); 486 } 487 if (frame.isIconifiable()) { 488 x = center(iconButton, insets, x, true); 489 } 490 } 491 else { 492 center(menuButton, insets, getWidth() - insets.right, 493 true); 494 int x = insets.left; 495 if (frame.isClosable()) { 496 x = center(closeButton, insets, x, false); 497 } 498 if (frame.isMaximizable()) { 499 x = center(maxButton, insets, x, false); 500 } 501 if (frame.isIconifiable()) { 502 x = center(iconButton, insets, x, false); 503 } 504 } 505 } 506 } 507 createNoFocusButton()508 private JButton createNoFocusButton() { 509 JButton button = new JButton(); 510 button.setFocusable(false); 511 button.setMargin(new Insets(0,0,0,0)); 512 return button; 513 } 514 } 515