Browse Source

Show hidden part of tree cell on mouse over using popup.

pull/198/head
weisj 4 years ago committed by Jannis Weis
parent
commit
9854161a57
  1. 2
      core/src/main/java/com/github/weisj/darklaf/components/border/MutableLineBorder.java
  2. 13
      core/src/main/java/com/github/weisj/darklaf/graphics/PaintUtil.java
  3. 40
      core/src/main/java/com/github/weisj/darklaf/ui/cell/hint/CellContainer.java
  4. 376
      core/src/main/java/com/github/weisj/darklaf/ui/cell/hint/CellHintPopupListener.java
  5. 56
      core/src/main/java/com/github/weisj/darklaf/ui/cell/hint/IndexedCellContainer.java
  6. 16
      core/src/main/java/com/github/weisj/darklaf/ui/table/DarkTableUI.java
  7. 95
      core/src/main/java/com/github/weisj/darklaf/ui/table/TableCellContainer.java
  8. 35
      core/src/main/java/com/github/weisj/darklaf/ui/tree/DarkTreeUI.java
  9. 94
      core/src/main/java/com/github/weisj/darklaf/ui/tree/TreeCellContainer.java
  10. 30
      core/src/main/java/com/github/weisj/darklaf/util/DarkUIUtil.java
  11. 2
      core/src/main/resources/com/github/weisj/darklaf/properties/ui/cell.properties
  12. 26
      core/src/test/java/ui/tree/TreeDemo.java

2
core/src/main/java/com/github/weisj/darklaf/components/border/MutableLineBorder.java

@ -66,7 +66,7 @@ public class MutableLineBorder extends AbstractBorder {
return true;
}
protected Color getColor() {
public Color getColor() {
return color;
}

13
core/src/main/java/com/github/weisj/darklaf/graphics/PaintUtil.java

@ -30,9 +30,7 @@ import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.*;
import com.github.weisj.darklaf.util.*;
import com.github.weisj.darklaf.util.Scale;
public class PaintUtil {
@ -245,6 +243,15 @@ public class PaintUtil {
g.fillRect(x, y + height - thickness, width, thickness);
}
public static void drawRect(final Graphics g, final int x, final int y,
final int width, final int height, final Insets lineWidths) {
g.fillRect(x, y, width, lineWidths.top);
g.fillRect(x, y + lineWidths.top, lineWidths.left, height - lineWidths.top - lineWidths.bottom);
g.fillRect(x + width - lineWidths.right, y + lineWidths.top, lineWidths.right,
height - lineWidths.left - lineWidths.right);
g.fillRect(x, y + height - lineWidths.bottom, width, lineWidths.bottom);
}
public static void fillRect(final Graphics g, final Rectangle r) {
fillRect(g, r.x, r.y, r.width, r.height);
}

40
core/src/main/java/com/github/weisj/darklaf/ui/cell/hint/CellContainer.java

@ -0,0 +1,40 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.ui.cell.hint;
import java.awt.*;
import javax.swing.*;
public interface CellContainer<T extends JComponent> {
T getComponent();
boolean isEditing();
default Rectangle getAllocation() {
return getComponent().getVisibleRect();
}
}

376
core/src/main/java/com/github/weisj/darklaf/ui/cell/hint/CellHintPopupListener.java

@ -0,0 +1,376 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.ui.cell.hint;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.Objects;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import com.github.weisj.darklaf.graphics.PaintUtil;
import com.github.weisj.darklaf.ui.DarkPopupFactory;
import com.github.weisj.darklaf.util.DarkUIUtil;
public class CellHintPopupListener<T extends JComponent, I> extends MouseInputAdapter {
private final IndexedCellContainer<T, I> cellContainer;
private final PopupComponent popupComponent;
private I lastIndex;
private Popup popup;
private boolean respectCellHeight = false;
private boolean respectCellWidth = true;
public CellHintPopupListener(final IndexedCellContainer<T, I> cellContainer) {
this.cellContainer = cellContainer;
this.popupComponent = new PopupComponent(this);
}
public void setRespectCellHeight(final boolean respectCellHeight) {
this.respectCellHeight = respectCellHeight;
}
public void setRespectCellWidth(final boolean respectCellWidth) {
this.respectCellWidth = respectCellWidth;
}
public boolean isRespectCellHeight() {
return respectCellHeight;
}
public boolean isRespectCellWidth() {
return respectCellWidth;
}
public void install() {
JComponent comp = cellContainer.getComponent();
comp.addMouseListener(this);
comp.addMouseMotionListener(this);
}
public void uninstall() {
JComponent comp = cellContainer.getComponent();
comp.removeMouseListener(this);
comp.removeMouseMotionListener(this);
}
@Override
public void mouseMoved(final MouseEvent e) {
final Point p = e.getPoint();
final I index = cellContainer.getCellPosition(p);
updatePopup(index, p);
}
private void updatePopup(final I index, final Point p) {
if (cellContainer.getComponent() == null || index == null) return;
final boolean isEditing = cellContainer.isEditingCell(index);
final Rectangle allocation = cellContainer.getAllocation();
final Rectangle cellBounds = cellContainer.getCellBoundsAt(index, isEditing);
final Rectangle visibleBounds = allocation.intersection(cellBounds);
if (visibleBounds.contains(p)) {
final Component comp = cellContainer.getEffectiveCellRendererComponent(index, isEditing);
final Dimension prefSize = isEditing
? comp.getBounds().getSize()
: comp.getPreferredSize();
if (!(visibleBounds.width >= prefSize.width && visibleBounds.height >= prefSize.height)) {
Point popupLocation = cellContainer.getComponent().getLocationOnScreen();
Rectangle rect = calculatePopupBounds(cellBounds, visibleBounds, !isEditing);
if (!visibleBounds.contains(rect)) {
cellBounds.x = rect.x - cellBounds.x;
cellBounds.y = rect.y - cellBounds.y;
rect.x += popupLocation.x;
rect.y += popupLocation.y;
enter(index, rect, cellBounds);
return;
} else {
lastIndex = index;
}
}
} else {
lastIndex = null;
}
leave();
}
private Rectangle calculatePopupBounds(final Rectangle cellBounds, final Rectangle visibleBounds,
final boolean addBorder) {
Rectangle rect = new Rectangle(visibleBounds);
boolean ltr = cellContainer.getComponent().getComponentOrientation().isLeftToRight();
popupComponent.setBorderInsets(1, 1, 1, 1);
if (isRespectCellWidth()) {
if (cellBounds.x >= visibleBounds.x
&& cellBounds.x + cellBounds.width <= visibleBounds.x + visibleBounds.width) {
rect.x = cellBounds.x;
rect.width = cellBounds.width;
} else {
int upperDiff = Math.max(cellBounds.x + cellBounds.width - visibleBounds.x - visibleBounds.width,
0);
int lowerDiff = Math.max(visibleBounds.x - cellBounds.x, 0);
if (ltr && upperDiff > 0) lowerDiff = 0;
if (!ltr && lowerDiff > 0) upperDiff = 0;
if (upperDiff >= lowerDiff) {
rect.x = visibleBounds.x + visibleBounds.width;
rect.width = upperDiff;
popupComponent.setLeft(0);
} else {
rect.x = cellBounds.x;
rect.width = lowerDiff;
popupComponent.setRight(0);
}
}
}
if (isRespectCellHeight()) {
if ((cellBounds.y >= visibleBounds.y
&& cellBounds.y + cellBounds.height <= visibleBounds.y + visibleBounds.height)) {
rect.y = cellBounds.y;
rect.height = cellBounds.height;
} else {
int upperDiff = Math.max(cellBounds.y + cellBounds.height - visibleBounds.y - visibleBounds.height,
0);
int lowerDiff = Math.max(visibleBounds.y - cellBounds.y, 0);
if (upperDiff > 0) lowerDiff = 0;
if (upperDiff >= lowerDiff) {
rect.y = visibleBounds.y + visibleBounds.height;
rect.height = upperDiff;
popupComponent.setBottom(0);
} else {
rect.y = cellBounds.y;
rect.height = lowerDiff;
popupComponent.setTop(0);
}
}
}
if (!addBorder) {
popupComponent.setBorderInsets(0, 0, 0, 0);
} else {
rect.x -= popupComponent.getLeft();
rect.y -= popupComponent.getTop();
rect.width += popupComponent.getLeft() + popupComponent.getRight();
rect.height += popupComponent.getTop() + popupComponent.getBottom();
}
return rect;
}
@Override
public void mouseExited(final MouseEvent e) {
if (isOverEditor(e.getPoint())) {
if (popup == null) {
/*
* If mouse is over editor and no popup is currently visible
* check if we need to show the popup.
*/
mouseMoved(e);
}
return;
}
leave();
}
private boolean isOverEditor(final Point p) {
return cellContainer.isEditing()
&& cellContainer.getCellEditorComponent(lastIndex).getBounds().contains(p);
}
public void repaint() {
if (!cellContainer.getComponent().isShowing()) return;
if (popup != null) popupComponent.repaint();
if (lastIndex != null) {
Point p = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(p, cellContainer.getComponent());
updatePopup(lastIndex, p);
}
}
private void enter(final I index, final Rectangle bounds, final Rectangle rendererBounds) {
if (index != null) {
lastIndex = index;
popupComponent.setPreferredSize(bounds.getSize());
popupComponent.setRendererBounds(rendererBounds);
if (popup != null) {
Point p = popupComponent.isShowing() ? popupComponent.getLocationOnScreen() : null;
if (p == null
|| p.x != bounds.x || p.y != bounds.y
|| popupComponent.getWidth() != bounds.width
|| popupComponent.getHeight() != bounds.height) {
movePopup(bounds);
}
}
if (popup == null) {
popup = PopupFactory.getSharedInstance()
.getPopup(cellContainer.getComponent(), popupComponent, bounds.x, bounds.y);
popup.show();
if (DarkPopupFactory.getPopupType(popup) == DarkPopupFactory.PopupType.HEAVY_WEIGHT) {
// Ensure heavy weight popup is at desired location.
movePopup(bounds);
}
}
}
}
private void movePopup(final Rectangle bounds) {
DarkPopupFactory.PopupType popupType = DarkPopupFactory.getPopupType(popup);
Window w = DarkUIUtil.getWindow(popupComponent);
if (popupType == DarkPopupFactory.PopupType.HEAVY_WEIGHT) {
moveHeavyWeightPopup(bounds, w);
} else if (w instanceof RootPaneContainer) {
moveMediumLightWeightPopup(bounds, w);
}
popupComponent.repaint();
}
private void moveHeavyWeightPopup(final Rectangle bounds, final Window popupWindow) {
GraphicsConfiguration gc = popupWindow.getGraphicsConfiguration();
GraphicsConfiguration componentGc = cellContainer.getComponent().getGraphicsConfiguration();
if (!Objects.equals(componentGc, gc)) {
popup.hide();
popup = null;
} else {
popupWindow.setBounds(bounds);
}
}
private void moveMediumLightWeightPopup(final Rectangle bounds, final Window parentWindow) {
JLayeredPane layeredPane = ((RootPaneContainer) parentWindow).getLayeredPane();
Component comp = DarkUIUtil.getParentBeforeMatching(popupComponent.getParent(),
c -> c == layeredPane);
Rectangle windowBounds = parentWindow.getBounds();
if (windowBounds.contains(bounds.x, bounds.y)
&& windowBounds.contains(bounds.x + bounds.width, bounds.y + bounds.height)) {
Point windowPos = parentWindow.getLocationOnScreen();
bounds.x -= windowPos.x;
bounds.y -= windowPos.y;
comp.setBounds(bounds);
Component c = popupComponent;
while (c != null && comp != c) {
c.setBounds(0, 0, bounds.width, bounds.height);
c = c.getParent();
}
} else {
/*
* Popup was moved outside the window. Request heavy weight popup.
*/
popup.hide();
popup = null;
}
}
private void leave() {
if (popup != null) {
popup.hide();
popup = null;
}
}
private Component getRenderer() {
return cellContainer.getEffectiveCellRendererComponent(lastIndex, cellContainer.isEditingCell(lastIndex));
}
private Color getBackground(final Component renderer) {
return cellContainer.getBackgroundAt(lastIndex, renderer);
}
private static class PopupComponent extends JComponent {
private final CellHintPopupListener<?, ?> cellHintPopupListener;
private final Insets borderInsets = new Insets(0, 0, 0, 0);
private final Color borderColor;
private Rectangle rendererBounds;
private PopupComponent(final CellHintPopupListener<?, ?> cellHintPopupListener) {
this.cellHintPopupListener = cellHintPopupListener;
this.borderColor = UIManager.getColor("CellHintPopup.borderColor");
putClientProperty(DarkPopupFactory.KEY_NO_DECORATION, true);
putClientProperty(DarkPopupFactory.KEY_OPAQUE, true);
}
public void setTop(final int top) {
borderInsets.top = top;
}
public void setBottom(final int bottom) {
borderInsets.bottom = bottom;
}
public void setLeft(final int left) {
borderInsets.left = left;
}
public void setRight(final int right) {
borderInsets.right = right;
}
public int getTop() {
return borderInsets.top;
}
public int getBottom() {
return borderInsets.bottom;
}
public int getLeft() {
return borderInsets.left;
}
public int getRight() {
return borderInsets.right;
}
public void setBorderInsets(final int top, final int left, final int bottom, final int right) {
borderInsets.set(top, left, bottom, right);
}
public void setRendererBounds(final Rectangle rendererBounds) {
this.rendererBounds = rendererBounds;
}
@Override
public void paint(final Graphics g) {
Component renderer = cellHintPopupListener.getRenderer();
if (rendererBounds != null && renderer != null) {
Color bg = cellHintPopupListener.getBackground(renderer);
g.setColor(bg);
g.fillRect(0, 0, getWidth(), getHeight());
g.translate(-rendererBounds.x, -rendererBounds.y);
// If the renderer is an editor we need to restore the bounds.
Rectangle bounds = renderer.getBounds();
renderer.setBounds(rendererBounds);
renderer.paint(g);
renderer.setBounds(bounds);
g.translate(rendererBounds.x, rendererBounds.y);
}
g.setColor(getBorderColor());
PaintUtil.drawRect(g, 0, 0, getWidth(), getHeight(), borderInsets);
}
public Color getBorderColor() {
return borderColor;
}
}
}

56
core/src/main/java/com/github/weisj/darklaf/ui/cell/hint/IndexedCellContainer.java

@ -0,0 +1,56 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.ui.cell.hint;
import java.awt.*;
import javax.swing.*;
public interface IndexedCellContainer<T extends JComponent, I> extends CellContainer<T> {
default Rectangle getCellBoundsAt(final I position) {
return getCellBoundsAt(position, false);
}
Rectangle getCellBoundsAt(final I position, final boolean isEditing);
I getCellPosition(final Point p);
Color getBackgroundAt(final I position, final Component renderer);
default Component getEffectiveCellRendererComponent(final I position, final boolean isEditing) {
if (isEditing) {
return getCellEditorComponent(position);
} else {
return getCellRendererComponent(position);
}
}
boolean isEditingCell(final I position);
Component getCellRendererComponent(final I position);
Component getCellEditorComponent(final I position);
}

16
core/src/main/java/com/github/weisj/darklaf/ui/table/DarkTableUI.java

@ -91,12 +91,6 @@ public class DarkTableUI extends DarkTableUIBridge implements TableConstants {
LookAndFeel.uninstallBorder((JComponent) oldUnwrapped);
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
handler = null;
}
@Override
protected void installDefaults() {
super.installDefaults();
@ -116,6 +110,12 @@ public class DarkTableUI extends DarkTableUIBridge implements TableConstants {
table.setSurrendersFocusOnKeystroke(true);
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
handler = null;
}
protected CellRendererPane createCellRendererPane() {
return new DarkCellRendererPane();
}
@ -417,7 +417,7 @@ public class DarkTableUI extends DarkTableUIBridge implements TableConstants {
}
}
if (isEditorCell) {
Component component = getCellEditor();
Component component = getCellEditorComponent();
component.setBounds(r);
component.validate();
} else {
@ -437,7 +437,7 @@ public class DarkTableUI extends DarkTableUIBridge implements TableConstants {
return rendererDelegate;
}
protected Component getCellEditor() {
public Component getCellEditorComponent() {
Component c = table.getEditorComponent();
if (!(table.getCellEditor() instanceof DarkTableCellEditorDelegate)) {
int row = table.getEditingRow();

95
core/src/main/java/com/github/weisj/darklaf/ui/table/TableCellContainer.java

@ -0,0 +1,95 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.ui.table;
import java.awt.*;
import javax.swing.*;
import com.github.weisj.darklaf.ui.cell.hint.IndexedCellContainer;
import com.github.weisj.darklaf.util.Pair;
public class TableCellContainer implements IndexedCellContainer<JTable, Pair<Integer, Integer>> {
private final JTable table;
private final DarkTableUI ui;
public TableCellContainer(final JTable table, final DarkTableUI ui) {
this.table = table;
this.ui = ui;
}
@Override
public Rectangle getCellBoundsAt(final Pair<Integer, Integer> position, final boolean isEditing) {
return isEditing
? ui.getCellEditorComponent().getBounds()
: table.getCellRect(position.getFirst(), position.getSecond(), false);
}
@Override
public Pair<Integer, Integer> getCellPosition(final Point p) {
return new Pair<>(table.rowAtPoint(p), table.columnAtPoint(p));
}
@Override
public JTable getComponent() {
return table;
}
@Override
public boolean isEditing() {
return table.isEditing();
}
@Override
public Color getBackgroundAt(final Pair<Integer, Integer> position, final Component renderer) {
return renderer.getBackground();
}
@Override
public boolean isEditingCell(final Pair<Integer, Integer> position) {
return isEditing()
&& position != null
&& table.getEditingColumn() == position.getSecond()
&& table.getEditingRow() == position.getFirst();
}
@Override
public Component getCellRendererComponent(final Pair<Integer, Integer> position) {
if (position == null) return null;
int row = position.getFirst();
int column = position.getSecond();
boolean isSelected = table.isCellSelected(row, column);
Object value = table.getValueAt(row, column);
boolean focus = table.hasFocus();
return ui.getCellRenderer(row, column)
.getTableCellRendererComponent(table, value, isSelected, focus, row, column);
}
@Override
public Component getCellEditorComponent(final Pair<Integer, Integer> position) {
return ui.getCellEditorComponent();
}
}

35
core/src/main/java/com/github/weisj/darklaf/ui/tree/DarkTreeUI.java

@ -39,6 +39,7 @@ import javax.swing.tree.*;
import com.github.weisj.darklaf.ui.cell.CellConstants;
import com.github.weisj.darklaf.ui.cell.CellUtil;
import com.github.weisj.darklaf.ui.cell.DarkCellRendererPane;
import com.github.weisj.darklaf.ui.cell.hint.CellHintPopupListener;
import com.github.weisj.darklaf.util.DarkUIUtil;
import com.github.weisj.darklaf.util.PropertyUtil;
import com.github.weisj.darklaf.util.SystemInfo;
@ -117,6 +118,8 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
protected Icon collapsed;
private boolean oldRepaintAllRowValue;
protected CellHintPopupListener<JTree, ?> popupListener;
protected DarkTreeCellRendererDelegate rendererDelegate;
public static ComponentUI createUI(final JComponent c) {
@ -176,10 +179,16 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
@Override
protected void installListeners() {
super.installListeners();
popupListener = createPopupMouseListener();
popupListener.install();
tree.addPropertyChangeListener(this);
tree.addMouseListener(selectionListener);
}
protected CellHintPopupListener<JTree, ?> createPopupMouseListener() {
return new CellHintPopupListener<>(new TreeCellContainer(tree, this));
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
@ -368,6 +377,8 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
@Override
protected void uninstallListeners() {
super.uninstallListeners();
popupListener.uninstall();
popupListener = null;
tree.removeMouseListener(selectionListener);
tree.removePropertyChangeListener(this);
}
@ -425,20 +436,12 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
while (backgroundEnumerator != null && backgroundEnumerator.hasMoreElements()) {
path = (TreePath) backgroundEnumerator.nextElement();
if (path != null) {
isLeaf = treeModel.isLeaf(path.getLastPathComponent());
if (isLeaf) {
isExpanded = hasBeenExpanded = false;
} else {
isExpanded = treeState.getExpandedState(path);
hasBeenExpanded = tree.hasBeenExpanded(path);
}
bounds = getPathBounds(path, insets, boundsBuffer);
if (bounds == null) return;
bounds.x = xOffset;
bounds.width = containerWidth;
if (paintBounds.intersects(bounds)) {
paintRowBackground(g, paintBounds, bounds, path,
tree.getRowForPath(path));
paintRowBackground(g, paintBounds, bounds, path, tree.getRowForPath(path));
}
}
}
@ -508,6 +511,14 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
return super.getCellRenderer();
}
public Component getEditingComponent() {
return editingComponent;
}
public int getEditingRow() {
return editingRow;
}
protected Rectangle getPathBounds(final TreePath path, final Insets insets, Rectangle bounds) {
bounds = treeState.getBounds(path, bounds);
if (bounds != null) {
@ -537,6 +548,12 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
}
}
@Override
public void update(final Graphics g, final JComponent c) {
popupListener.repaint();
super.update(g, c);
}
@Override
protected void updateRenderer() {
super.updateRenderer();

94
core/src/main/java/com/github/weisj/darklaf/ui/tree/TreeCellContainer.java

@ -0,0 +1,94 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.ui.tree;
import java.awt.*;
import javax.swing.*;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import com.github.weisj.darklaf.ui.cell.CellUtil;
import com.github.weisj.darklaf.ui.cell.hint.IndexedCellContainer;
public class TreeCellContainer implements IndexedCellContainer<JTree, Integer> {
private final JTree tree;
private final DarkTreeUI ui;
public TreeCellContainer(final JTree tree, final DarkTreeUI ui) {
this.tree = tree;
this.ui = ui;
}
@Override
public Rectangle getCellBoundsAt(final Integer position, final boolean isEditing) {
return isEditing ? ui.getEditingComponent().getBounds() : tree.getRowBounds(position);
}
@Override
public Integer getCellPosition(final Point p) {
return tree.getClosestRowForLocation(p.x, p.y);
}
@Override
public JTree getComponent() {
return tree;
}
@Override
public Color getBackgroundAt(final Integer position, final Component renderer) {
if (position == null) return null;
return CellUtil.getTreeBackground(tree, tree.isRowSelected(position), position);
}
@Override
public boolean isEditingCell(final Integer row) {
return isEditing() && row != null && ui.getEditingRow() == row;
}
@Override
public Component getCellRendererComponent(final Integer row) {
if (row == null) return null;
TreeCellRenderer renderer = ui.getCellRenderer();
TreePath path = tree.getPathForRow(row);
boolean isExpanded = tree.isExpanded(row);
boolean isLeaf = tree.getModel().isLeaf(path.getLastPathComponent());
int leadIndex = tree.getLeadSelectionRow();
return renderer.getTreeCellRendererComponent(tree, path.getLastPathComponent(),
tree.isRowSelected(row), isExpanded, isLeaf, row,
(leadIndex == row));
}
@Override
public Component getCellEditorComponent(final Integer position) {
return ui.getEditingComponent();
}
@Override
public boolean isEditing() {
return tree.isEditing();
}
}

30
core/src/main/java/com/github/weisj/darklaf/util/DarkUIUtil.java

@ -189,14 +189,20 @@ public final class DarkUIUtil {
return SwingUtilities.getWindowAncestor(owner) == w;
}
public static Container getUnwrappedParent(final Container comp) {
public static Container getUnwrappedParent(final Component comp) {
if (comp == null) return null;
return SwingUtilities.getUnwrappedParent(comp);
}
public static Container getUnwrappedParent(final Component comp) {
if (comp == null) return null;
return SwingUtilities.getUnwrappedParent(comp);
public static Component unwrapComponent(final Component component) {
if (component == null) return null;
if (!(component.getParent() instanceof JLayer)
&& !(component.getParent() instanceof JViewport)) return component;
Container parent = component.getParent();
while (parent instanceof JLayer || parent instanceof JViewport) {
parent = parent.getParent();
}
return parent;
}
public static int getFocusAcceleratorKeyMask() {
@ -480,11 +486,23 @@ public final class DarkUIUtil {
}
public static Container getParentMatching(final Container parent, final Predicate<Container> test) {
Container p;
for (p = parent; p != null && !test.test(p); p = p.getParent()) {}
Container p = parent;
while (p != null && !test.test(p)) {
p = p.getParent();
}
return p;
}
public static Container getParentBeforeMatching(final Container parent, final Predicate<Container> test) {
Container p = parent;
Container prev = null;
while (p != null && !test.test(p)) {
prev = p;
p = prev.getParent();
}
return prev;
}
public static Container getOpaqueParent(final Container parent) {
return getParentMatching(parent, Container::isOpaque);
}

2
core/src/main/resources/com/github/weisj/darklaf/properties/ui/cell.properties

@ -47,3 +47,5 @@ Cell.inactiveBackgroundSelected = %highlightFill
Cell.inactiveBackgroundNoFocus = %backgroundContainer
Cell.inactiveBackgroundNoFocusAlternative = %backgroundAlternative
Cell.inactiveBackgroundSelectedNoFocus = %highlightFill
CellHintPopup.borderColor = %borderSecondary

26
core/src/test/java/ui/tree/TreeDemo.java

@ -49,11 +49,11 @@ public class TreeDemo implements ComponentDemo {
@Override
public JComponent createComponent() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
DefaultMutableTreeNode parent1 = new DefaultMutableTreeNode("Node A");
DefaultMutableTreeNode child = new DefaultMutableTreeNode("Leaf A");
DefaultMutableTreeNode parent1 = new DefaultMutableTreeNode("Very very very very very long node A");
DefaultMutableTreeNode child = new DefaultMutableTreeNode("A loooooooooooooooooooooooooooooooooooooong leaf A");
DefaultMutableTreeNode child1 = new SelectableTreeNode("Leaf B (boolean)", true);
DefaultMutableTreeNode parent2 = new DefaultMutableTreeNode("Node B");
DefaultMutableTreeNode child2 = new DefaultMutableTreeNode("Leaf C");
DefaultMutableTreeNode child2 = new DefaultMutableTreeNode("Leaf that is unnecessary verbose and ridiculously long C");
parent1.add(child);
parent1.add(child1);
@ -77,7 +77,11 @@ public class TreeDemo implements ComponentDemo {
return component;
}
});
DemoPanel panel = new DemoPanel(new OverlayScrollPane(tree), new BorderLayout(), 0);
JSplitPane splitPane = new JSplitPane();
splitPane.setLeftComponent(new OverlayScrollPane(tree));
splitPane.setRightComponent(new JPanel());
SwingUtilities.invokeLater(() -> splitPane.setDividerLocation(0.5));
DemoPanel panel = new DemoPanel(splitPane, new BorderLayout(), 0);
JPanel controlPanel = panel.addControls();
controlPanel.setLayout(new MigLayout("fillx, wrap 2", "[][grow]"));
controlPanel.add(new JCheckBox(PropertyKey.ENABLED) {
@ -117,6 +121,8 @@ public class TreeDemo implements ComponentDemo {
addActionListener(e -> tree.putClientProperty(DarkTreeUI.KEY_RENDER_BOOLEAN_AS_CHECKBOX, isSelected()));
}
}, "span");
controlPanel = panel.addControls();
controlPanel.add(new JLabel(DarkTreeUI.KEY_BOOLEAN_RENDER_TYPE + ":", JLabel.RIGHT));
controlPanel.add(new JComboBox<String>() {
{
@ -126,14 +132,14 @@ public class TreeDemo implements ComponentDemo {
addItemListener(e -> tree.putClientProperty(DarkTreeUI.KEY_BOOLEAN_RENDER_TYPE, e.getItem()));
}
});
controlPanel.add(new JLabel("JTree.lineStyle:", JLabel.RIGHT));
controlPanel.add(new JLabel(DarkTreeUI.KEY_LINE_STYLE + ":", JLabel.RIGHT));
controlPanel.add(new JComboBox<String>() {
{
addItem("Dashed");
addItem("None");
addItem("Line");
setSelectedItem("Line");
addItemListener(e -> tree.putClientProperty("JTree.lineStyle", e.getItem()));
addItem(DarkTreeUI.STYLE_LINE);
addItem(DarkTreeUI.STYLE_DASHED);
addItem(DarkTreeUI.STYLE_NONE);
setSelectedItem(DarkTreeUI.STYLE_LINE);
addItemListener(e -> tree.putClientProperty(DarkTreeUI.KEY_LINE_STYLE, e.getItem()));
}
});
tree.setLargeModel(true);

Loading…
Cancel
Save