2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ui;
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.util.Comparing;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.openapi.util.registry.Registry;
22 import com.intellij.ui.border.CustomLineBorder;
23 import com.intellij.ui.popup.AbstractPopup;
24 import com.intellij.ui.popup.MovablePopup;
25 import com.intellij.util.Alarm;
26 import com.intellij.util.ui.UIUtil;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
32 import java.awt.event.*;
33 import java.awt.image.BufferedImage;
34 import java.util.Collection;
35 import java.util.Collections;
37 public abstract class AbstractExpandableItemsHandler<KeyType, ComponentType extends JComponent> implements ExpandableItemsHandler<KeyType> {
38 protected final ComponentType myComponent;
40 private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
41 private final CellRendererPane myRendererPane = new CellRendererPane();
42 private final JComponent myTipComponent = new JComponent() {
44 protected void paintComponent(Graphics g) {
45 Insets insets = getInsets();
46 UIUtil.drawImage(g, myImage, insets.left, insets.top, null);
50 private boolean myEnabled = Registry.is("ide.expansion.hints.enabled");
51 private final MovablePopup myPopup;
52 private KeyType myKey;
53 private Rectangle myKeyItemBounds;
54 private BufferedImage myImage;
56 protected AbstractExpandableItemsHandler(@NotNull final ComponentType component) {
57 myComponent = component;
58 myComponent.add(myRendererPane);
59 myComponent.validate();
60 myPopup = new MovablePopup(myComponent, myTipComponent);
62 MouseAdapter tipMouseAdapter = new MouseAdapter() {
64 public void mouseExited(MouseEvent e) {
65 // don't hide the hint if mouse exited to myComponent
66 if (myComponent.getMousePosition() == null) {
72 public void mouseWheelMoved(MouseWheelEvent e) {
73 Point p = e.getLocationOnScreen();
74 SwingUtilities.convertPointFromScreen(p, myComponent);
75 myComponent.dispatchEvent(new MouseWheelEvent(myComponent,
84 e.getWheelRotation()));
88 public void mouseClicked(MouseEvent e) {
89 Point p = e.getLocationOnScreen();
90 SwingUtilities.convertPointFromScreen(p, myComponent);
91 myComponent.dispatchEvent(new MouseEvent(myComponent,
102 public void mousePressed(MouseEvent e) {
107 public void mouseReleased(MouseEvent e) {
112 public void mouseMoved(MouseEvent e) {
117 public void mouseDragged(MouseEvent e) {
121 myTipComponent.addMouseListener(tipMouseAdapter);
122 myTipComponent.addMouseWheelListener(tipMouseAdapter);
123 myTipComponent.addMouseMotionListener(tipMouseAdapter);
125 myComponent.addMouseListener(
126 new MouseListener() {
128 public void mouseEntered(MouseEvent e) {
133 public void mouseExited(MouseEvent e) {
134 // don't hide the hint if mouse exited to it
135 if (myTipComponent.getMousePosition() == null) {
141 public void mouseClicked(MouseEvent e) {
145 public void mousePressed(MouseEvent e) {
150 public void mouseReleased(MouseEvent e) {
156 myComponent.addMouseMotionListener(
157 new MouseMotionListener() {
159 public void mouseDragged(MouseEvent e) {
164 public void mouseMoved(MouseEvent e) {
165 handleMouseEvent(e, false);
170 myComponent.addFocusListener(
173 public void focusLost(FocusEvent e) {
178 public void focusGained(FocusEvent e) {
179 updateCurrentSelection();
184 myComponent.addComponentListener(
185 new ComponentAdapter() {
187 public void componentHidden(ComponentEvent e) {
192 public void componentMoved(ComponentEvent e) {
193 updateCurrentSelection();
197 public void componentResized(ComponentEvent e) {
198 updateCurrentSelection();
203 myComponent.addHierarchyBoundsListener(new HierarchyBoundsAdapter() {
205 public void ancestorMoved(HierarchyEvent e) {
206 updateCurrentSelection();
210 public void ancestorResized(HierarchyEvent e) {
211 updateCurrentSelection();
215 myComponent.addHierarchyListener(
216 new HierarchyListener() {
218 public void hierarchyChanged(HierarchyEvent e) {
225 protected void onFocusLost() {
230 public void setEnabled(boolean enabled) {
232 if (!myEnabled) hideHint();
236 public boolean isEnabled() {
242 public Collection<KeyType> getExpandedItems() {
243 return myKey == null ? Collections.<KeyType>emptyList() : Collections.singleton(myKey);
246 protected void updateCurrentSelection() {
247 handleSelectionChange(myKey, true);
250 private void handleMouseEvent(MouseEvent e) {
251 handleMouseEvent(e, true);
254 protected void handleMouseEvent(MouseEvent e, boolean forceUpdate) {
255 KeyType selected = getCellKeyForPoint(e.getPoint());
256 if (forceUpdate || !Comparing.equal(myKey, selected)) {
257 handleSelectionChange(selected, true);
261 protected void handleSelectionChange(KeyType selected) {
262 handleSelectionChange(selected, false);
265 protected void handleSelectionChange(final KeyType selected, final boolean processIfUnfocused) {
266 if (!ApplicationManager.getApplication().isDispatchThread()) {
269 myUpdateAlarm.cancelAllRequests();
270 if (selected == null) {
274 if (!selected.equals(myKey)) {
277 myUpdateAlarm.addRequest(new Runnable() {
280 doHandleSelectionChange(selected, processIfUnfocused);
285 private void doHandleSelectionChange(@NotNull KeyType selected, boolean processIfUnfocused) {
287 || !myComponent.isEnabled()
288 || !myComponent.isShowing()
289 || !myComponent.getVisibleRect().intersects(getVisibleRect(selected))
290 || !myComponent.isFocusOwner() && !processIfUnfocused
298 Point location = createToolTipImage(myKey);
300 if (location == null) {
304 Dimension size = myTipComponent.getPreferredSize();
305 myPopup.setBounds(location.x, location.y, size.width, size.height);
306 myPopup.setHeavyWeight(hasOwnedWindows());
307 if (!myPopup.isVisible()) {
308 myPopup.setVisible(true);
314 protected boolean isPopup() {
315 Window window = SwingUtilities.getWindowAncestor(myComponent);
316 return window != null
317 && !(window instanceof Dialog || window instanceof Frame)
318 && !isHintsAllowed(window);
321 private static boolean isHintsAllowed(Window window) {
322 if (window instanceof RootPaneContainer) {
323 final JRootPane pane = ((RootPaneContainer)window).getRootPane();
325 return Boolean.TRUE.equals(pane.getClientProperty(AbstractPopup.SHOW_HINTS));
331 private boolean hasOwnedWindows() {
332 Window owner = SwingUtilities.getWindowAncestor(myComponent);
333 Window popup = SwingUtilities.getWindowAncestor(myTipComponent);
334 for (Window other : owner.getOwnedWindows()) {
335 if (popup != other && other.isVisible()) {
342 private void hideHint() {
343 myUpdateAlarm.cancelAllRequests();
344 if (myPopup.isVisible()) {
345 myPopup.setVisible(false);
351 public boolean isShowing() {
352 return myPopup.isVisible();
355 private void repaintKeyItem() {
356 if (myKeyItemBounds != null) {
357 myComponent.repaint(myKeyItemBounds);
362 private Point createToolTipImage(@NotNull KeyType key) {
363 Pair<Component, Rectangle> rendererAndBounds = getCellRendererAndBounds(key);
364 if (rendererAndBounds == null) return null;
366 Component renderer = rendererAndBounds.first;
367 if (!(renderer instanceof JComponent)) return null;
369 myKeyItemBounds = rendererAndBounds.second;
371 Rectangle cellBounds = myKeyItemBounds;
372 Rectangle visibleRect = getVisibleRect(key);
374 if (cellBounds.y < visibleRect.y) return null;
376 int cellMaxY = cellBounds.y + cellBounds.height;
377 int visMaxY = visibleRect.y + visibleRect.height;
378 if (cellMaxY > visMaxY) return null;
380 int cellMaxX = cellBounds.x + cellBounds.width;
381 int visMaxX = visibleRect.x + visibleRect.width;
383 Point location = new Point(visMaxX, cellBounds.y);
384 SwingUtilities.convertPointToScreen(location, myComponent);
386 Rectangle screen = !Registry.is("ide.expansion.hints.on.all.screens")
387 ? ScreenUtil.getScreenRectangle(location)
388 : ScreenUtil.getAllScreensRectangle();
390 int borderWidth = isPaintBorder() ? 1 : 0;
391 int width = Math.min(screen.width + screen.x - location.x - borderWidth, cellMaxX - visMaxX);
392 int height = cellBounds.height;
394 if (width <= 0 || height <= 0) return null;
396 Dimension size = getImageSize(width, height);
397 myImage = UIUtil.createImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
399 Graphics2D g = myImage.createGraphics();
401 doFillBackground(height, width, g);
402 g.translate(cellBounds.x - visMaxX, 0);
403 doPaintTooltipImage(renderer, cellBounds, g, key);
405 CustomLineBorder border = null;
406 if (borderWidth > 0) {
407 border = new CustomLineBorder(getBorderColor(), borderWidth, 0, borderWidth, borderWidth);
408 location.y -= borderWidth;
409 size.width += borderWidth;
410 size.height += borderWidth + borderWidth;
414 myRendererPane.remove(renderer);
416 myTipComponent.setBorder(border);
417 myTipComponent.setPreferredSize(size);
421 protected boolean isPaintBorder() {
425 protected Color getBorderColor() {
426 return JBColor.border();
429 protected Dimension getImageSize(final int width, final int height) {
430 return new Dimension(width, height);
433 protected void doFillBackground(int height, int width, Graphics2D g) {
434 g.setColor(myComponent.getBackground());
435 g.fillRect(0, 0, width, height);
438 protected void doPaintTooltipImage(Component rComponent, Rectangle cellBounds, Graphics2D g, KeyType key) {
439 myRendererPane.paintComponent(g, rComponent, myComponent, 0, 0, cellBounds.width, cellBounds.height, true);
442 protected Rectangle getVisibleRect(KeyType key) {
443 return myComponent.getVisibleRect();
447 protected abstract Pair<Component, Rectangle> getCellRendererAndBounds(KeyType key);
449 protected abstract KeyType getCellKeyForPoint(Point point);