| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.ui.tabs.impl; |
| |
| import com.intellij.ide.DataManager; |
| import com.intellij.ide.ui.UISettings; |
| import com.intellij.openapi.actionSystem.ActionGroup; |
| import com.intellij.openapi.actionSystem.ActionPlaces; |
| import com.intellij.openapi.actionSystem.DefaultActionGroup; |
| import com.intellij.openapi.util.Pass; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.ui.*; |
| import com.intellij.ui.components.panels.Wrapper; |
| import com.intellij.ui.tabs.JBTabsPosition; |
| import com.intellij.ui.tabs.TabInfo; |
| import com.intellij.ui.tabs.TabsUtil; |
| import com.intellij.ui.tabs.UiDecorator; |
| import com.intellij.ui.tabs.impl.table.TableLayout; |
| import com.intellij.util.PairConsumer; |
| import com.intellij.util.ui.Centerizer; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.EmptyBorder; |
| import java.awt.*; |
| import java.awt.event.MouseAdapter; |
| import java.awt.event.MouseEvent; |
| import java.awt.image.BufferedImage; |
| |
| public class TabLabel extends JPanel { |
| protected final SimpleColoredComponent myLabel; |
| |
| private final LayeredIcon myIcon; |
| private Icon myOverlayedIcon; |
| |
| private final TabInfo myInfo; |
| protected ActionPanel myActionPanel; |
| private boolean myCentered; |
| |
| private final Wrapper myLabelPlaceholder = new Wrapper(false); |
| private final JBTabsImpl myTabs; |
| |
| private BufferedImage myInactiveStateImage; |
| private Rectangle myLastPaintedInactiveImageBounds; |
| |
| public TabLabel(JBTabsImpl tabs, final TabInfo info) { |
| super(false); |
| |
| myTabs = tabs; |
| myInfo = info; |
| |
| myLabel = createLabel(tabs); |
| |
| setOpaque(false); |
| setLayout(new BorderLayout()); |
| |
| myLabelPlaceholder.setOpaque(false); |
| add(myLabelPlaceholder, BorderLayout.CENTER); |
| |
| setAlignmentToCenter(true); |
| |
| myIcon = new LayeredIcon(2); |
| |
| addMouseListener(new MouseAdapter() { |
| public void mousePressed(final MouseEvent e) { |
| if (UIUtil.isCloseClick(e, MouseEvent.MOUSE_PRESSED)) return; |
| if (JBTabsImpl.isSelectionClick(e, false) && myInfo.isEnabled()) { |
| final TabInfo selectedInfo = myTabs.getSelectedInfo(); |
| if (selectedInfo != myInfo) { |
| myInfo.setPreviousSelection(selectedInfo); |
| } |
| Component c = SwingUtilities.getDeepestComponentAt(e.getComponent(), e.getX(), e.getY()); |
| if (c instanceof InplaceButton) return; |
| myTabs.select(info, true); |
| } |
| else { |
| handlePopup(e); |
| } |
| } |
| |
| public void mouseClicked(final MouseEvent e) { |
| handlePopup(e); |
| } |
| |
| public void mouseReleased(final MouseEvent e) { |
| myInfo.setPreviousSelection(null); |
| handlePopup(e); |
| } |
| }); |
| } |
| |
| private SimpleColoredComponent createLabel(JBTabsImpl tabs) { |
| SimpleColoredComponent label = new SimpleColoredComponent() { |
| @Override |
| protected boolean shouldDrawMacShadow() { |
| return SystemInfo.isMac || UIUtil.isUnderDarcula(); |
| } |
| |
| @Override |
| protected boolean shouldDrawDimmed() { |
| return myTabs.getSelectedInfo() != myInfo || myTabs.useBoldLabels(); |
| } |
| |
| @Override |
| public Font getFont() { |
| if (isFontSet() || !myTabs.useSmallLabels()) { |
| return super.getFont(); |
| } |
| return UIUtil.getLabelFont(UIUtil.FontSize.SMALL); |
| } |
| }; |
| label.setOpaque(false); |
| label.setBorder(null); |
| label.setIconTextGap(tabs.isEditorTabs() ? 2 : new JLabel().getIconTextGap()); |
| label.setIconOpaque(false); |
| label.setIpad(new Insets(0, 0, 0, 0)); |
| |
| return label; |
| } |
| |
| @Override |
| public Insets getInsets() { |
| Insets insets = super.getInsets(); |
| if (myTabs.isEditorTabs()) { |
| if (UISettings.getInstance().SHOW_CLOSE_BUTTON) { |
| return new Insets(insets.top, insets.left, insets.bottom, 3); |
| } |
| } |
| |
| return insets; |
| } |
| |
| public void setAlignmentToCenter(boolean toCenter) { |
| if (myCentered == toCenter && getLabelComponent().getParent() != null) return; |
| |
| setPlaceholderContent(toCenter, getLabelComponent()); |
| } |
| |
| protected void setPlaceholderContent(boolean toCenter, JComponent component) { |
| myLabelPlaceholder.removeAll(); |
| |
| if (toCenter) { |
| final Centerizer center = new Centerizer(component); |
| myLabelPlaceholder.setContent(center); |
| } |
| else { |
| myLabelPlaceholder.setContent(component); |
| } |
| |
| myCentered = toCenter; |
| } |
| |
| |
| |
| public void paintOffscreen(Graphics g) { |
| synchronized (getTreeLock()) { |
| validateTree(); |
| } |
| doPaint(g); |
| } |
| |
| public void paint(final Graphics g) { |
| if (myTabs.isDropTarget(myInfo)) return; |
| |
| if (myTabs.getSelectedInfo() != myInfo) { |
| doPaint(g); |
| } |
| } |
| |
| public void paintImage(Graphics g) { |
| final Rectangle b = getBounds(); |
| final Graphics lG = g.create(b.x, b.y, b.width, b.height); |
| try { |
| lG.setColor(Color.red); |
| doPaint(lG); |
| } |
| finally { |
| lG.dispose(); |
| } |
| } |
| |
| public void doTranslate(PairConsumer<Integer, Integer> consumer) { |
| final JBTabsPosition pos = myTabs.getTabsPosition(); |
| |
| int dX = 0; |
| int dXs = 0; |
| int dY = 0; |
| int dYs = 0; |
| int selected = getSelectedOffset(); |
| int plain = getNonSelectedOffset(); |
| |
| switch (pos) { |
| case bottom: |
| dY = -plain; |
| dYs = -selected; |
| break; |
| case left: |
| dX = plain; |
| dXs = selected; |
| break; |
| case right: |
| dX = -plain; |
| dXs = -selected; |
| break; |
| case top: |
| dY = plain; |
| dYs = selected; |
| break; |
| } |
| |
| if (!myTabs.isDropTarget(myInfo)) { |
| if (myTabs.getSelectedInfo() != myInfo) { |
| consumer.consume(dX, dY); |
| } |
| else { |
| consumer.consume(dXs, dYs); |
| } |
| } |
| } |
| |
| private void doPaint(final Graphics g) { |
| doTranslate(new PairConsumer<Integer, Integer>() { |
| @Override |
| public void consume(Integer x, Integer y) { |
| g.translate(x, y); |
| } |
| }); |
| |
| super.paint(g); |
| |
| doTranslate(new PairConsumer<Integer, Integer>() { |
| @Override |
| public void consume(Integer x, Integer y) { |
| g.translate(-x, -y); |
| } |
| }); |
| } |
| |
| protected int getNonSelectedOffset() { |
| if (myTabs.isEditorTabs() && (myTabs.isSingleRow() || ((TableLayout)myTabs.getEffectiveLayout()).isLastRow(getInfo()))) { |
| return -TabsUtil.ACTIVE_TAB_UNDERLINE_HEIGHT / 2 + 1; |
| } |
| return 1; |
| } |
| |
| protected int getSelectedOffset() { |
| return getNonSelectedOffset(); |
| } |
| |
| @Override |
| public Dimension getPreferredSize() { |
| final Dimension size = super.getPreferredSize(); |
| size.height = TabsUtil.getTabsHeight(); |
| if (myActionPanel != null && !myActionPanel.isVisible()) { |
| final Dimension actionPanelSize = myActionPanel.getPreferredSize(); |
| size.width += actionPanelSize.width; |
| } |
| |
| final JBTabsPosition pos = myTabs.getTabsPosition(); |
| switch (pos) { |
| case top: |
| case bottom: |
| if (myTabs.hasUnderline()) size.height += TabsUtil.ACTIVE_TAB_UNDERLINE_HEIGHT - 1; |
| break; |
| case left: |
| case right: |
| size.width += getSelectedOffset(); |
| break; |
| } |
| |
| return size; |
| } |
| |
| private void handlePopup(final MouseEvent e) { |
| if (e.getClickCount() != 1 || !e.isPopupTrigger()) return; |
| |
| if (e.getX() < 0 || e.getX() >= e.getComponent().getWidth() || e.getY() < 0 || e.getY() >= e.getComponent().getHeight()) return; |
| |
| String place = myTabs.getPopupPlace(); |
| place = place != null ? place : ActionPlaces.UNKNOWN; |
| myTabs.myPopupInfo = myInfo; |
| |
| final DefaultActionGroup toShow = new DefaultActionGroup(); |
| if (myTabs.getPopupGroup() != null) { |
| toShow.addAll(myTabs.getPopupGroup()); |
| toShow.addSeparator(); |
| } |
| |
| JBTabsImpl tabs = |
| JBTabsImpl.NAVIGATION_ACTIONS_KEY.getData(DataManager.getInstance().getDataContext(e.getComponent(), e.getX(), e.getY())); |
| if (tabs == myTabs && myTabs.myAddNavigationGroup) { |
| toShow.addAll(myTabs.myNavigationActions); |
| } |
| |
| if (toShow.getChildrenCount() == 0) return; |
| |
| myTabs.myActivePopup = myTabs.myActionManager.createActionPopupMenu(place, toShow).getComponent(); |
| myTabs.myActivePopup.addPopupMenuListener(myTabs.myPopupListener); |
| |
| myTabs.myActivePopup.addPopupMenuListener(myTabs); |
| myTabs.myActivePopup.show(e.getComponent(), e.getX(), e.getY()); |
| } |
| |
| |
| public void setText(final SimpleColoredText text) { |
| myInfo.setTitleIsShortened(false); |
| if (text != null && text.getTexts().size() == 1 && Boolean.TRUE == getClientProperty(JBEditorTabs.TABS_SHORTEN_TITLE_IF_NEED)) { |
| String title = text.getTexts().get(0); |
| if (title.length() > UISettings.getInstance().EDITOR_TAB_TITLE_LIMIT) { |
| SimpleTextAttributes attributes = text.getAttributes().get(0); |
| text.clear(); |
| text.append(StringUtil.getShortened(title, UISettings.getInstance().EDITOR_TAB_TITLE_LIMIT), attributes); |
| myInfo.setTitleIsShortened(true); |
| } |
| } |
| myLabel.change(new Runnable() { |
| public void run() { |
| myLabel.clear(); |
| myLabel.setIcon(hasIcons() ? myIcon : null); |
| |
| if (text != null) { |
| SimpleColoredText derive = myTabs.useBoldLabels() ? text.derive(SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES, true) : text; |
| derive.appendToComponent(myLabel); |
| } |
| } |
| }, false); |
| |
| invalidateIfNeeded(); |
| } |
| |
| |
| private void invalidateIfNeeded() { |
| if (getLabelComponent().getRootPane() == null) return; |
| |
| Dimension d = getLabelComponent().getSize(); |
| Dimension pref = getLabelComponent().getPreferredSize(); |
| if (d != null && d.equals(pref)) { |
| return; |
| } |
| |
| setInactiveStateImage(null); |
| |
| getLabelComponent().invalidate(); |
| |
| if (myActionPanel != null) { |
| myActionPanel.invalidate(); |
| } |
| |
| myTabs.revalidateAndRepaint(false); |
| } |
| |
| public void setIcon(final Icon icon) { |
| setIcon(icon, 0); |
| } |
| |
| private boolean hasIcons() { |
| LayeredIcon layeredIcon = getLayeredIcon(); |
| boolean hasIcons = false; |
| Icon[] layers = layeredIcon.getAllLayers(); |
| for (Icon layer1 : layers) { |
| if (layer1 != null) { |
| hasIcons = true; |
| break; |
| } |
| } |
| |
| return hasIcons; |
| } |
| |
| private void setIcon(@Nullable final Icon icon, int layer) { |
| LayeredIcon layeredIcon = getLayeredIcon(); |
| layeredIcon.setIcon(icon, layer); |
| if (hasIcons()) { |
| myLabel.setIcon(layeredIcon); |
| } |
| else { |
| myLabel.setIcon(null); |
| } |
| |
| invalidateIfNeeded(); |
| } |
| |
| private LayeredIcon getLayeredIcon() { |
| return myIcon; |
| } |
| |
| public TabInfo getInfo() { |
| return myInfo; |
| } |
| |
| public void apply(UiDecorator.UiDecoration decoration) { |
| if (decoration.getLabelFont() != null) { |
| setFont(decoration.getLabelFont()); |
| getLabelComponent().setFont(decoration.getLabelFont()); |
| } |
| |
| Insets insets = decoration.getLabelInsets(); |
| if (insets != null) { |
| Insets current = JBTabsImpl.ourDefaultDecorator.getDecoration().getLabelInsets(); |
| if (current != null) { |
| setBorder( |
| new EmptyBorder(getValue(current.top, insets.top), getValue(current.left, insets.left), getValue(current.bottom, insets.bottom), |
| getValue(current.right, insets.right))); |
| } |
| } |
| } |
| |
| private static int getValue(int currentValue, int newValue) { |
| return newValue != -1 ? newValue : currentValue; |
| } |
| |
| public void setTabActions(ActionGroup group) { |
| removeOldActionPanel(); |
| |
| if (group == null) return; |
| |
| myActionPanel = new ActionPanel(myTabs, myInfo, new Pass<MouseEvent>() { |
| public void pass(final MouseEvent event) { |
| final MouseEvent me = SwingUtilities.convertMouseEvent(event.getComponent(), event, TabLabel.this); |
| processMouseEvent(me); |
| } |
| }); |
| |
| toggleShowActions(false); |
| |
| add(myActionPanel, BorderLayout.EAST); |
| |
| myTabs.revalidateAndRepaint(false); |
| } |
| |
| |
| private void removeOldActionPanel() { |
| if (myActionPanel != null) { |
| myActionPanel.getParent().remove(myActionPanel); |
| myActionPanel = null; |
| } |
| } |
| |
| public boolean updateTabActions() { |
| return myActionPanel != null && myActionPanel.update(); |
| } |
| |
| private void setAttractionIcon(@Nullable Icon icon) { |
| if (myIcon.getIcon(0) == null) { |
| setIcon(null, 1); |
| myOverlayedIcon = icon; |
| } |
| else { |
| setIcon(icon, 1); |
| myOverlayedIcon = null; |
| } |
| } |
| |
| public boolean repaintAttraction() { |
| if (!myTabs.myAttractions.contains(myInfo)) { |
| if (getLayeredIcon().isLayerEnabled(1)) { |
| getLayeredIcon().setLayerEnabled(1, false); |
| setAttractionIcon(null); |
| invalidateIfNeeded(); |
| return true; |
| } |
| return false; |
| } |
| |
| boolean needsUpdate = false; |
| |
| if (getLayeredIcon().getIcon(1) != myInfo.getAlertIcon()) { |
| setAttractionIcon(myInfo.getAlertIcon()); |
| needsUpdate = true; |
| } |
| |
| int maxInitialBlinkCount = 5; |
| int maxRefireBlinkCount = maxInitialBlinkCount + 2; |
| if (myInfo.getBlinkCount() < maxInitialBlinkCount && myInfo.isAlertRequested()) { |
| getLayeredIcon().setLayerEnabled(1, !getLayeredIcon().isLayerEnabled(1)); |
| if (myInfo.getBlinkCount() == 0) { |
| needsUpdate = true; |
| } |
| myInfo.setBlinkCount(myInfo.getBlinkCount() + 1); |
| |
| if (myInfo.getBlinkCount() == maxInitialBlinkCount) { |
| myInfo.resetAlertRequest(); |
| } |
| |
| repaint(); |
| } |
| else { |
| if (myInfo.getBlinkCount() < maxRefireBlinkCount && myInfo.isAlertRequested()) { |
| getLayeredIcon().setLayerEnabled(1, !getLayeredIcon().isLayerEnabled(1)); |
| myInfo.setBlinkCount(myInfo.getBlinkCount() + 1); |
| |
| if (myInfo.getBlinkCount() == maxRefireBlinkCount) { |
| myInfo.setBlinkCount(maxInitialBlinkCount); |
| myInfo.resetAlertRequest(); |
| } |
| |
| repaint(); |
| } |
| else { |
| needsUpdate = !getLayeredIcon().isLayerEnabled(1); |
| getLayeredIcon().setLayerEnabled(1, true); |
| } |
| } |
| |
| invalidateIfNeeded(); |
| |
| return needsUpdate; |
| } |
| |
| protected void paintChildren(final Graphics g) { |
| super.paintChildren(g); |
| |
| if (myOverlayedIcon == null || getLabelComponent().getParent() == null) return; |
| |
| final Rectangle textBounds = SwingUtilities.convertRectangle(getLabelComponent().getParent(), getLabelComponent().getBounds(), this); |
| if (getLayeredIcon().isLayerEnabled(1)) { |
| |
| final int top = (getSize().height - myOverlayedIcon.getIconHeight()) / 2; |
| |
| myOverlayedIcon.paintIcon(this, g, textBounds.x - myOverlayedIcon.getIconWidth() / 2, top); |
| } |
| } |
| |
| public void setTabActionsAutoHide(final boolean autoHide) { |
| if (myActionPanel == null || myActionPanel.isAutoHide() == autoHide) { |
| return; |
| } |
| |
| myActionPanel.setAutoHide(autoHide); |
| } |
| |
| public void toggleShowActions(boolean show) { |
| if (myActionPanel != null) { |
| myActionPanel.toggleShowActions(show); |
| } |
| } |
| |
| public void setActionPanelVisible(boolean visible) { |
| if (myActionPanel != null) { |
| myActionPanel.setVisible(visible); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return myInfo.getText(); |
| } |
| |
| public void setTabEnabled(boolean enabled) { |
| getLabelComponent().setEnabled(enabled); |
| } |
| |
| |
| @Nullable |
| public BufferedImage getInactiveStateImage(Rectangle effectiveBounds) { |
| BufferedImage img = null; |
| if (myLastPaintedInactiveImageBounds != null && myLastPaintedInactiveImageBounds.getSize().equals(effectiveBounds.getSize())) { |
| img = myInactiveStateImage; |
| } |
| else { |
| setInactiveStateImage(null); |
| } |
| myLastPaintedInactiveImageBounds = effectiveBounds; |
| return img; |
| } |
| |
| public void setInactiveStateImage(@Nullable BufferedImage img) { |
| if (myInactiveStateImage != null && img != myInactiveStateImage) { |
| myInactiveStateImage.flush(); |
| } |
| myInactiveStateImage = img; |
| } |
| |
| public JComponent getLabelComponent() { |
| return myLabel; |
| } |
| } |