All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.laf.menu.PopupMenuPainter Maven / Gradle / Ivy

/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.laf.menu;

import com.alee.api.jdk.Objects;
import com.alee.laf.WebLookAndFeel;
import com.alee.laf.combobox.WebComboBoxUI;
import com.alee.utils.*;

import javax.swing.*;
import java.awt.*;

/**
 * Base painter for {@link JPopupMenu} component.
 * It is used as {@link WebPopupMenuUI} default styling.
 *
 * @param  component type
 * @param  component UI type
 * @author Mikle Garin
 */
public class PopupMenuPainter extends AbstractPopupPainter
        implements IPopupMenuPainter
{
    /**
     * todo 1. Incorrect menu placement when corner is off (spacing == shade)
     * todo 2. When using popupMenuWay take invoker shade into account (if its UI has one -> ShadeProvider interface)
     * todo 3. Add left/right corners display support
     */

    /**
     * Style settings.
     */
    protected int menuSpacing = 1;
    protected boolean fixLocation = true;
    protected boolean dropdownStyleForMenuBar = true;

    /**
     * Runtime variables.
     */
    protected transient PopupMenuType popupMenuType = null;

    @Override
    protected void propertyChanged ( final String property, final Object oldValue, final Object newValue )
    {
        // Perform basic actions on property changes
        super.propertyChanged ( property, oldValue, newValue );

        // Visibility property changes
        if ( Objects.equals ( property, WebLookAndFeel.VISIBLE_PROPERTY ) )
        {
            // Updating menu type
            if ( newValue == Boolean.TRUE )
            {
                final Component invoker = component.getInvoker ();
                if ( invoker != null )
                {
                    if ( invoker instanceof JMenu )
                    {
                        if ( invoker.getParent () instanceof JPopupMenu )
                        {
                            setPopupMenuType ( PopupMenuType.menuBarSubMenu );
                        }
                        else
                        {
                            setPopupMenuType ( PopupMenuType.menuBarMenu );
                        }
                    }
                    else if ( invoker instanceof JComboBox )
                    {
                        setPopupMenuType ( PopupMenuType.comboBoxMenu );
                    }
                    else
                    {
                        setPopupMenuType ( PopupMenuType.customPopupMenu );
                    }
                }
                else
                {
                    setPopupMenuType ( PopupMenuType.customPopupMenu );
                }
            }

            // Either install or uninstall popup settings
            if ( newValue == Boolean.TRUE )
            {
                // For unix systems it is not required to repeat this update
                if ( !SystemUtils.isUnix () )
                {
                    // Install custom popup window settings
                    installPopupSettings ( CoreSwingUtils.getWindowAncestor ( component ), component );
                }
            }
            else
            {
                // Uninstall custom popup window settings
                uninstallPopupSettings ( CoreSwingUtils.getWindowAncestor ( component ), component );
            }
        }
    }

    @Override
    protected void orientationChange ()
    {
        // Performing default actions
        super.orientationChange ();

        // todo Probably just update location properly?
        // Hiding menu on orientation changes
        if ( component.isShowing () )
        {
            component.setVisible ( false );
        }
    }

    /**
     * Returns spacing between popup menus.
     *
     * @return spacing between popup menus
     */
    public int getMenuSpacing ()
    {
        return menuSpacing;
    }

    /**
     * Sets spacing between popup menus.
     *
     * @param spacing spacing between popup menus
     */
    public void setMenuSpacing ( final int spacing )
    {
        this.menuSpacing = spacing;
    }

    /**
     * Returns whether should fix initial popup menu location or not.
     *
     * @return true if should fix initial popup menu location, false otherwise
     */
    public boolean isFixLocation ()
    {
        return fixLocation;
    }

    /**
     * Sets whether should fix initial popup menu location or not.
     * If set to true popup menu will try to use best possible location to show up.
     * 

* This is set to true by default to place menubar and menu popups correctly. * You might want to set this to false for some specific popup menu, but not all of them at once. * * @param fix whether should fix initial popup menu location or not */ public void setFixLocation ( final boolean fix ) { this.fixLocation = fix; } /** * Sets popup menu type. * This value is updated right before popup menu window becomes visible. * You can use it to draw different popup menu decoration for each popup menu type. * * @param type popup menu type */ public void setPopupMenuType ( final PopupMenuType type ) { this.popupMenuType = type; if ( popupMenuType == PopupMenuType.menuBarSubMenu ) { setPopupStyle ( PopupStyle.simple ); } } @Override protected Insets getBorder () { final Insets margin = super.getBorder (); margin.top += round; margin.bottom += round; return margin; } @Override protected void paintTransparentPopup ( final Graphics2D g2d, final C popupMenu ) { final Dimension menuSize = popupMenu.getSize (); // Painting shade paintShade ( g2d, popupMenu, menuSize ); // Painting background paintBackground ( g2d, popupMenu, menuSize ); // Painting dropdown corner fill // This is a specific for WebPopupMenuUI feature paintDropdownCornerFill ( g2d, popupMenu, menuSize ); // Painting border paintBorder ( g2d, popupMenu, menuSize ); } /** * Paints dropdown-styled popup menu corner fill if menu item near it is selected. * * @param g2d graphics context * @param popupMenu popup menu * @param menuSize menu size */ protected void paintDropdownCornerFill ( final Graphics2D g2d, final C popupMenu, final Dimension menuSize ) { // Checking whether corner should be filled or not if ( popupStyle == PopupStyle.dropdown && round == 0 ) { // Check that menu item is attached to menu side final boolean top = cornerSide == TOP; final boolean stick = top ? getBorder ().top == 0 : getBorder ().bottom == 0; if ( stick ) { // todo Implement corner support // // Checking that we can actually retrieve what item wants to fill corner with // final int zIndex = top ? 0 : popupMenu.getComponentCount () - 1; // final Component component = popupMenu.getComponent ( zIndex ); // if ( popupMenu instanceof BasicComboPopup ) // { // // Filling corner according to combobox preferences // if ( component instanceof JScrollPane ) // { // // Usually there will be a scrollpane with list as the first element // final JScrollPane scrollPane = ( JScrollPane ) component; // final JList list = ( JList ) scrollPane.getViewport ().getView (); // if ( top && list.getSelectedIndex () == 0 ) // { // // Filling top corner when first list element is selected // final WebComboBoxUI ui = geComboBoxUI ( popupMenu ); // if ( ui != null ) // { // g2d.setPaint ( ui.getNorthCornerFill () ); // g2d.fill ( getDropdownCornerShape ( popupMenu, menuSize, true ) ); // } // } // else if ( !top && list.getSelectedIndex () == list.getModel ().getSize () - 1 ) // { // // Filling bottom corner when last list element is selected // final WebComboBoxUI ui = geComboBoxUI ( popupMenu ); // if ( ui != null ) // { // g2d.setPaint ( ui.getSouthCornerFill () ); // g2d.fill ( getDropdownCornerShape ( popupMenu, menuSize, true ) ); // } // } // } // } // else if ( component instanceof JMenuItem ) // { // // Filling corner if selected menu item is placed nearby // final JMenuItem menuItem = ( JMenuItem ) component; // if ( menuItem.isEnabled () && ( menuItem.getModel ().isArmed () || menuItem.isSelected () ) ) // { // // Filling corner properly // if ( menuItem.getUI () instanceof WebMenuUI ) // { // // Filling corner according to WebMenu styling // final WebMenuUI ui = ( WebMenuUI ) menuItem.getUI (); // g2d.setPaint ( top ? ui.getNorthCornerFill () : ui.getSouthCornerFill () ); // g2d.fill ( getDropdownCornerShape ( popupMenu, menuSize, true ) ); // } // else if ( menuItem.getUI () instanceof WebMenuItemUI ) // { // // Filling corner according to WebMenuItem styling // final WebMenuItemUI ui = ( WebMenuItemUI ) menuItem.getUI (); // g2d.setPaint ( top ? ui.getNorthCornerFill () : ui.getSouthCornerFill () ); // g2d.fill ( getDropdownCornerShape ( popupMenu, menuSize, true ) ); // } // } // } } } } /** * Returns combobox UI for the specified combobox popup menu. * * @param popupMenu popup menu to retrieve combobox UI for * @return combobox UI for the specified combobox popup menu */ protected WebComboBoxUI geComboBoxUI ( final C popupMenu ) { final JComboBox comboBox = ReflectUtils.getFieldValueSafely ( popupMenu, "comboBox" ); return comboBox != null && comboBox.getUI () instanceof WebComboBoxUI ? ( WebComboBoxUI ) comboBox.getUI () : null; } @Override public Point preparePopupMenu ( final C popupMenu, final Component invoker, int x, int y ) { // Updating popup location according to popup menu UI settings if ( invoker != null ) { // Default corner position according to invoker's orientation relativeCorner = ltr ? 0 : Integer.MAX_VALUE; // Calculating position variables final boolean showing = invoker.isShowing (); final Point los = showing ? CoreSwingUtils.locationOnScreen ( invoker ) : new Point ( 0, 0 ); final boolean fixLocation = this.fixLocation && showing; final int sideWidth = getSideWidth (); // Calculating final position variables if ( invoker instanceof JMenu ) { if ( invoker.getParent () instanceof JPopupMenu ) { // Displaying simple-styled sub-menu // It is displayed to left or right of the menu item setPopupStyle ( PopupStyle.simple ); if ( fixLocation ) { // Invoker X-location on screen also works as orientation indicator, so we don't need to check it here x += ( los.x <= x ? -1 : 1 ) * ( shaped ? sideWidth - menuSpacing : -menuSpacing ); y += ( los.y <= y ? -1 : 1 ) * ( shaped ? sideWidth + 1 + round : round ); } } else if ( !dropdownStyleForMenuBar ) { // Displaying simple-styled top-level menu // It is displayed below or above the menu bar setPopupStyle ( PopupStyle.simple ); if ( fixLocation ) { // Invoker X-location on screen also works as orientation indicator, so we don't need to check it here x += ( los.x <= x ? -1 : 1 ) * ( shaped ? sideWidth - menuSpacing : -menuSpacing ); y -= shaped ? sideWidth + round + 1 : round; } } else { // Displaying dropdown-styled top-level menu // It is displayed below or above the menu bar setPopupStyle ( PopupStyle.dropdown ); cornerSide = los.y <= y ? TOP : BOTTOM; if ( fixLocation ) { // Invoker X-location on screen also works as orientation indicator, so we don't need to check it here x += ( los.x <= x ? -1 : 1 ) * ( shaped ? sideWidth : 0 ); y += ( los.y <= y ? -1 : 1 ) * ( shaped ? sideWidth - cornerWidth : 0 ); } relativeCorner = los.x + invoker.getWidth () / 2 - x; } } else { final boolean dropdown = popupStyle == PopupStyle.dropdown; if ( invoker instanceof JComboBox && popupMenu.getName ().equals ( "ComboPopup.popup" ) ) { cornerSide = los.y <= y ? TOP : BOTTOM; if ( fixLocation ) { x += shaped ? -sideWidth : 0; if ( cornerSide == TOP ) { y -= shaped ? sideWidth - ( dropdown ? cornerWidth : 0 ) : 0; } else { // Invoker preferred size is required instead of actual height // This is because the original position takes it into account instead of height final int ih = invoker.getPreferredSize ().height; y -= ih + ( shaped ? sideWidth - ( dropdown ? cornerWidth : 0 ) : 0 ); } } relativeCorner = los.x + invoker.getWidth () / 2 - x; } else { if ( fixLocation ) { // Applying new location according to specified popup menu way final PopupMenuWay popupMenuWay = ui.getPopupMenuWay (); if ( popupMenuWay != null ) { final Dimension ps = popupMenu.getPreferredSize (); final Dimension is = invoker.getSize (); final int cornerShear = dropdown ? sideWidth - cornerWidth : 0; switch ( popupMenuWay ) { case aboveStart: { x = ( ltr ? los.x : los.x + is.width - ps.width ) + ( shaped ? ltr ? -sideWidth : sideWidth : 0 ); y = los.y - ps.height + cornerShear; relativeCorner = ltr ? 0 : Integer.MAX_VALUE; break; } case aboveMiddle: { x = los.x + is.width / 2 - ps.width / 2; y = los.y - ps.height + cornerShear; relativeCorner = los.x + invoker.getWidth () / 2 - x; break; } case aboveEnd: { x = ( ltr ? los.x + is.width - ps.width : los.x ) + ( shaped ? ltr ? sideWidth : -sideWidth : 0 ); y = los.y - ps.height + cornerShear; relativeCorner = ltr ? Integer.MAX_VALUE : 0; break; } case belowStart: { x = ( ltr ? los.x : los.x + is.width - ps.width ) + ( shaped ? ltr ? -sideWidth : sideWidth : 0 ); y = los.y + is.height - cornerShear; relativeCorner = ltr ? 0 : Integer.MAX_VALUE; break; } case belowMiddle: { x = los.x + is.width / 2 - ps.width / 2; y = los.y + is.height - cornerShear; relativeCorner = los.x + invoker.getWidth () / 2 - x; break; } case belowEnd: { x = ( ltr ? los.x + is.width - ps.width : los.x ) + ( shaped ? ltr ? sideWidth : -sideWidth : 0 ); y = los.y + is.height - cornerShear; relativeCorner = ltr ? Integer.MAX_VALUE : 0; break; } } cornerSide = popupMenuWay.getCornerSide (); } } } } } else { // Default corner position relativeCorner = 0; } return new Point ( x, y ); } @Override public void configurePopup ( final C popupMenu, final Component invoker, final int x, final int y, final Popup popup ) { // Retrieve component directly from the popup final Component window = ReflectUtils.callMethodSafely ( popup, "getComponent" ); if ( window instanceof Window ) { // Install custom popup window settings installPopupSettings ( ( Window ) window, popupMenu ); } } /** * Configures popup menu window opacity and shape. * * @param window popup menu window * @param popupMenu popup menu */ protected void installPopupSettings ( final Window window, final C popupMenu ) { if ( window != null && shaped && SwingUtils.isHeavyWeightWindow ( window ) ) { // Workaround to remove Mac OS X shade around the window if ( window instanceof JWindow && SystemUtils.isMac () ) { ( ( JWindow ) window ).getRootPane ().putClientProperty ( "Window.shadow", Boolean.FALSE ); } // Updating window opacity state in case menu is displayed in a heavy-weight popup if ( SwingUtils.isHeavyWeightWindow ( window ) ) { // Either change window opacity or shape if it is possible if ( ProprietaryUtils.isWindowTransparencyAllowed () ) { // Change window opacity ProprietaryUtils.setWindowOpaque ( window, false ); } else if ( ProprietaryUtils.isWindowShapeAllowed () ) { // Change window shape window.pack (); final Rectangle bounds = window.getBounds (); ++bounds.width; ++bounds.height; final Shape shape = provideShape ( popupMenu, bounds ); ProprietaryUtils.setWindowShape ( window, shape ); } } } } /** * Unconfigures popup menu window opacity and shape. * * @param window popup menu window * @param popupMenu popup menu */ protected void uninstallPopupSettings ( final Window window, final C popupMenu ) { if ( window != null && shaped && SwingUtils.isHeavyWeightWindow ( window ) ) { // Restoring menu opacity state in case menu is in a separate heavy-weight window ProprietaryUtils.setWindowOpaque ( window, true ); ProprietaryUtils.setWindowShape ( window, null ); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy