From ae53ad6e4ea8dc7c239d48499c3ddbfe04a3c646 Mon Sep 17 00:00:00 2001 From: weisj Date: Sun, 20 Oct 2019 03:40:38 +0200 Subject: [PATCH] Added DnD support to JTabFrame. --- .../darklaf/components/ScrollPopupMenu.java | 10 +- .../darklaf/components/TextFieldHistory.java | 100 ---- .../{TabFrame.java => JTabFrame.java} | 80 ++- .../components/tabframe/PanelPopup.java | 8 +- .../tabframe/TabFrameContentPane.java | 2 +- .../components/tabframe/TabFramePopup.java | 30 +- .../components/tabframe/TabFrameTab.java | 4 +- .../tabframe/TabFrameTabContainer.java | 8 +- .../components/tabframe/TabFrameTabLabel.java | 12 +- .../components/tabframe/TabFrameUI.java | 29 +- .../components/tabframe/TabbedPopup.java | 2 +- .../darklaf/components/text/SearchEvent.java | 40 ++ .../components/text/SearchListener.java | 31 ++ .../components/text/SearchTextField.java | 119 ++++ .../text/SearchTextFieldWithHistory.java | 174 ++++++ .../text/TextFieldHistoryPopup.java | 157 ++++++ .../components/tooltip/ToolTipContext.java | 1 + .../components/{ => uiresource}/Insets2D.java | 2 +- .../{ => uiresource}/JLabelUIResource.java | 2 +- .../{ => uiresource}/JPanelUIResource.java | 2 +- .../{ => uiresource}/UIResourceWrapper.java | 2 +- .../colorchooser/DarkColorChooserPanel.java | 2 - .../ui/tabbedpane/DarkTabbedPaneUI.java | 13 +- .../tabbedpane/TabbedPaneTransferHandler.java | 4 + .../darklaf/ui/tabframe/DarkPanelPopupUI.java | 24 +- .../DarkTabFrameComponentPopupMenu.java | 3 +- .../tabframe/DarkTabFrameTabContainerUI.java | 130 +++-- .../ui/tabframe/DarkTabFrameTabLabelUI.java | 90 ++-- .../darklaf/ui/tabframe/DarkTabFrameUI.java | 437 ++++++++++++++- .../ui/tabframe/DarkTabbedPopupUI.java | 2 +- .../darklaf/ui/tabframe/TabDragListener.java | 60 +++ .../darklaf/ui/tabframe/TabFrameLayout.java | 232 ++++++-- .../ui/tabframe/TabFrameTransferHandler.java | 508 ++++++++++++++++++ .../darklaf/ui/tooltip/DarkTooltipBorder.java | 3 +- .../com/weis/darklaf/util/PropertyLoader.java | 35 +- .../weis/darklaf/util/SwingXUtilities.java | 48 ++ .../pbjar/jxlayer/plaf/ext/MouseEventUI.java | 10 - .../jxlayer/repaint/RepaintManagerUtils.java | 1 - .../darklaf/properties/ui/tabFrame.properties | 2 + .../intellij/intellij_defaults.properties | 2 +- src/test/java/TabFrameDemo.java | 5 +- src/test/java/ToolBarDemo.java | 2 - src/test/java/UIDemo.java | 13 +- 43 files changed, 2091 insertions(+), 350 deletions(-) delete mode 100644 src/main/java/com/weis/darklaf/components/TextFieldHistory.java rename src/main/java/com/weis/darklaf/components/tabframe/{TabFrame.java => JTabFrame.java} (92%) create mode 100644 src/main/java/com/weis/darklaf/components/text/SearchEvent.java create mode 100644 src/main/java/com/weis/darklaf/components/text/SearchListener.java create mode 100644 src/main/java/com/weis/darklaf/components/text/SearchTextField.java create mode 100644 src/main/java/com/weis/darklaf/components/text/SearchTextFieldWithHistory.java create mode 100644 src/main/java/com/weis/darklaf/components/text/TextFieldHistoryPopup.java rename src/main/java/com/weis/darklaf/components/{ => uiresource}/Insets2D.java (98%) rename src/main/java/com/weis/darklaf/components/{ => uiresource}/JLabelUIResource.java (97%) rename src/main/java/com/weis/darklaf/components/{ => uiresource}/JPanelUIResource.java (97%) rename src/main/java/com/weis/darklaf/components/{ => uiresource}/UIResourceWrapper.java (97%) create mode 100644 src/main/java/com/weis/darklaf/ui/tabframe/TabDragListener.java create mode 100644 src/main/java/com/weis/darklaf/ui/tabframe/TabFrameTransferHandler.java create mode 100644 src/main/java/com/weis/darklaf/util/SwingXUtilities.java diff --git a/src/main/java/com/weis/darklaf/components/ScrollPopupMenu.java b/src/main/java/com/weis/darklaf/components/ScrollPopupMenu.java index a59c3785..ff11b23e 100644 --- a/src/main/java/com/weis/darklaf/components/ScrollPopupMenu.java +++ b/src/main/java/com/weis/darklaf/components/ScrollPopupMenu.java @@ -93,6 +93,14 @@ public class ScrollPopupMenu extends JPopupMenu { return overlayScrollPane; } + /** + * Set the maximum height of the popup. If the size is larger than the specified maximum height the content will be + * wrapped inside a scroll pane. + *

+ * Note: A value of <= 0 indicates that the height should not be limited. + * + * @param maxHeight the max height to use. + */ public void setMaxHeight(final int maxHeight) { this.maxHeight = maxHeight; } @@ -109,7 +117,7 @@ public class ScrollPopupMenu extends JPopupMenu { return; } final Dimension prefSize = getPreferredSize(); - if (maxHeight == 0 || prefSize.height <= maxHeight) { + if (maxHeight <= 0 || prefSize.height <= maxHeight) { setBounds(0, 0, prefSize.width, prefSize.height); popWin.setContentPane(this); setBorderPainted(true); diff --git a/src/main/java/com/weis/darklaf/components/TextFieldHistory.java b/src/main/java/com/weis/darklaf/components/TextFieldHistory.java deleted file mode 100644 index a5d10c77..00000000 --- a/src/main/java/com/weis/darklaf/components/TextFieldHistory.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2019 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.weis.darklaf.components; - -import com.weis.darklaf.decorators.PlainAction; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.LinkedList; - -/** - * @author Jannis Weis - */ -public class TextFieldHistory extends ScrollPopupMenu implements ActionListener { - - private final LinkedHashSet history; - private JTextField textField; - - /** - * Create Scroll Popup Menu. - * - * @param textField the text field. - * @param length the length of the history. - * @param maxH maximum height. - */ - public TextFieldHistory(final JTextField textField, final int length, final int maxH) { - super(maxH); - this.history = new LinkedHashSet<>(length); - this.textField = textField; - if (textField != null) { - textField.addActionListener(this); - } - } - - public void setTextField(final JTextField textField) { - if (this.textField != null) { - textField.removeActionListener(this); - } - this.textField = textField; - if (this.textField != null) { - textField.addActionListener(this); - } - } - - @Override - public void actionPerformed(final ActionEvent e) { - var text = textField.getText(); - if (!text.isBlank()) { - history.remove(text); - history.add(text); - } - } - - @Override - public void show(final Component invoker, final int x, final int y) { - if (history.size() == 0) return; - super.show(invoker, x, y); - } - - @Override - protected void showPopup() { - if (history.size() == 0) { - firePopupMenuCanceled(); - return; - } - this.removeAll(); - LinkedList list = new LinkedList<>(history); - Iterator itr = list.descendingIterator(); - while (itr.hasNext()) { - String item = itr.next(); - add(new JMenuItem(new PlainAction(item, () -> textField.setText(item)))); - } - super.showPopup(); - } -} diff --git a/src/main/java/com/weis/darklaf/components/tabframe/TabFrame.java b/src/main/java/com/weis/darklaf/components/tabframe/JTabFrame.java similarity index 92% rename from src/main/java/com/weis/darklaf/components/tabframe/TabFrame.java rename to src/main/java/com/weis/darklaf/components/tabframe/JTabFrame.java index 38b56f5e..3893578f 100644 --- a/src/main/java/com/weis/darklaf/components/tabframe/TabFrame.java +++ b/src/main/java/com/weis/darklaf/components/tabframe/JTabFrame.java @@ -24,6 +24,7 @@ package com.weis.darklaf.components.tabframe; import com.weis.darklaf.components.alignment.Alignment; +import com.weis.darklaf.ui.tabframe.TabFrameTransferHandler; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -39,7 +40,7 @@ import java.util.Objects; * * @author Jannis Weis */ -public class TabFrame extends JComponent { +public class JTabFrame extends JComponent { private final JComponent bottomTabs = createTabContainer(); private final JComponent topTabs = createTabContainer(); @@ -54,13 +55,17 @@ public class TabFrame extends JComponent { private int tabSize = -1; private int maxTabWidth = -1; + private boolean inTransfer; + private Alignment transferAlign; + private int transferIndex; + private boolean dndEnabled; /** - * Creates new {@link TabFrame}. + * Creates new {@link JTabFrame}. * A TabFrame displays one center component and multiple popups around * that can be toggles with a TabbedPane like tabArea along the border. */ - public TabFrame() { + public JTabFrame() { super(); updateUI(); add(content.getComponent()); @@ -75,6 +80,7 @@ public class TabFrame extends JComponent { popupLists[i] = new ArrayList<>(); } selectedIndices = new int[count]; + setDndEnabled(true); } @Override @@ -850,12 +856,48 @@ public class TabFrame extends JComponent { return a; } + public boolean isInTransfer() { + return inTransfer; + } + + public void initTransfer(final Alignment a, final int index) { + getContentPane().getComponent().setEnabled(false); + this.inTransfer = true; + this.transferAlign = a; + this.transferIndex = index; + } + + public void endTransfer() { + getContentPane().getComponent().setEnabled(true); + inTransfer = false; + transferAlign = null; + transferIndex = -10; + } + + public TabFramePosition getTransferInfo() { + return new TabFramePosition(transferAlign, transferIndex); + } + + public boolean isDndEnabled() { + return dndEnabled && getTransferHandler() instanceof TabFrameTransferHandler; + } + + public void setDndEnabled(final boolean dndEnabled) { + var old = this.dndEnabled; + this.dndEnabled = dndEnabled; + if (getDropTarget() != null) { + getDropTarget().setActive(dndEnabled); + } + firePropertyChange("dndEnabled", old, dndEnabled); + } + /** * This class represents a position inside the tabFrame. */ public static class TabFramePosition { - private final Alignment a; - private final int index; + private Alignment a; + private int index; + private Point point; @Contract(pure = true) public TabFramePosition(final Alignment a, final int index) { @@ -863,6 +905,29 @@ public class TabFrame extends JComponent { this.index = index; } + @Contract(pure = true) + public TabFramePosition(final Alignment a, final int index, final Point p) { + this.a = a; + this.index = index; + this.point = p; + } + + public void setAlignment(final Alignment a) { + this.a = a; + } + + public void setIndex(final int index) { + this.index = index; + } + + public Point getPoint() { + return point; + } + + public void setPoint(final Point point) { + this.point = point; + } + /** * The alignment position. * This specifies at what location the tab is placed. @@ -881,5 +946,10 @@ public class TabFrame extends JComponent { public int getIndex() { return index; } + + @Override + public String toString() { + return "[" + a + "," + index + "]"; + } } } diff --git a/src/main/java/com/weis/darklaf/components/tabframe/PanelPopup.java b/src/main/java/com/weis/darklaf/components/tabframe/PanelPopup.java index c7c9699e..fc2ef0e6 100644 --- a/src/main/java/com/weis/darklaf/components/tabframe/PanelPopup.java +++ b/src/main/java/com/weis/darklaf/components/tabframe/PanelPopup.java @@ -30,7 +30,7 @@ import javax.swing.*; import java.awt.*; /** - * Popup Component for {@link TabFrame}. + * Popup Component for {@link JTabFrame}. * * @author Jannis Weis * @since 2019 @@ -39,7 +39,7 @@ public class PanelPopup extends JPanel implements TabFramePopup { private Component content; private boolean open; - private TabFrame parent; + private JTabFrame parent; private String title; private Icon icon; private Alignment alignment; @@ -137,12 +137,12 @@ public class PanelPopup extends JPanel implements TabFramePopup { } @Override - public TabFrame getTabFrame() { + public JTabFrame getTabFrame() { return parent; } @Override - public void setTabFrame(final TabFrame parent) { + public void setTabFrame(final JTabFrame parent) { var old = this.parent; this.parent = parent; firePropertyChange("tabFrame", old, parent); diff --git a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameContentPane.java b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameContentPane.java index 15a0cb61..09036c25 100644 --- a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameContentPane.java +++ b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameContentPane.java @@ -34,7 +34,7 @@ import java.awt.*; import java.util.function.BiConsumer; /** - * Content pane for {@link TabFrame}. + * Content pane for {@link JTabFrame}. * * @author Jannis Weis */ diff --git a/src/main/java/com/weis/darklaf/components/tabframe/TabFramePopup.java b/src/main/java/com/weis/darklaf/components/tabframe/TabFramePopup.java index b7064cdd..5c87b7d3 100644 --- a/src/main/java/com/weis/darklaf/components/tabframe/TabFramePopup.java +++ b/src/main/java/com/weis/darklaf/components/tabframe/TabFramePopup.java @@ -62,21 +62,21 @@ public interface TabFramePopup { } /** - * Get the {{@link TabFrame}} this popup belongs to. + * Get the {{@link JTabFrame}} this popup belongs to. * - * @return the {{@link TabFrame}}. + * @return the {{@link JTabFrame}}. */ - TabFrame getTabFrame(); + JTabFrame getTabFrame(); /** - * Sets the {{@link TabFrame}} this popup belongs to. + * Sets the {{@link JTabFrame}} this popup belongs to. * - * @param tabFrame the {{@link TabFrame}}. + * @param tabFrame the {{@link JTabFrame}}. */ - void setTabFrame(TabFrame tabFrame); + void setTabFrame(JTabFrame tabFrame); /** - * Gets the alignment position in the {{@link TabFrame}}. + * Gets the alignment position in the {{@link JTabFrame}}. * * @return the alignment position. */ @@ -90,20 +90,20 @@ public interface TabFramePopup { int getIndex(); /** - * Set the index of the popup. - * This method should only be called from {{@link TabFrame}}. + * Sets the alignment position in the {{@link JTabFrame}}. This method should only be called from {{@link + * JTabFrame}}. * - * @param index the index. + * @param alignment the alignment position. */ - void setIndex(int index); + void setAlignment(Alignment alignment); /** - * Sets the alignment position in the {{@link TabFrame}}. - * This method should only be called from {{@link TabFrame}}. + * Set the index of the popup. + * This method should only be called from {{@link JTabFrame}}. * - * @param alignment the alignment position. + * @param index the index. */ - void setAlignment(Alignment alignment); + void setIndex(int index); /** * Open the popup. diff --git a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTab.java b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTab.java index 6d5d75cf..f90c9d18 100644 --- a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTab.java +++ b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTab.java @@ -97,13 +97,13 @@ public interface TabFrameTab { * * @return the TabFrame. */ - TabFrame getTabFrame(); + JTabFrame getTabFrame(); /** * Set the tab frame this tab currently belongs to. * * @param tabFrame the TabFrame. */ - void setTabFrame(TabFrame tabFrame); + void setTabFrame(JTabFrame tabFrame); } diff --git a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabContainer.java b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabContainer.java index 0243f4ce..55cb921b 100644 --- a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabContainer.java +++ b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabContainer.java @@ -31,14 +31,14 @@ import java.awt.*; public class TabFrameTabContainer extends JPanel implements TabFrameTab { protected final TabFrameTab oldTab; - private TabFrame parent; + private JTabFrame parent; private Component content; private Alignment orientation; private boolean selected; private int index; private int accelerator; - public TabFrameTabContainer(final TabFrame parent, final JComponent content, final TabFrameTab oldTab, + public TabFrameTabContainer(final JTabFrame parent, final JComponent content, final TabFrameTab oldTab, final Alignment alignment, final int index) { super(new BorderLayout()); this.parent = parent; @@ -140,12 +140,12 @@ public class TabFrameTabContainer extends JPanel implements TabFrameTab { } @Override - public TabFrame getTabFrame() { + public JTabFrame getTabFrame() { return parent; } @Override - public void setTabFrame(final TabFrame parent) { + public void setTabFrame(final JTabFrame parent) { var old = this.parent; this.parent = parent; firePropertyChange("tabFrame", old, parent); diff --git a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabLabel.java b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabLabel.java index f478cb3f..99e7b95a 100644 --- a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabLabel.java +++ b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabLabel.java @@ -34,13 +34,13 @@ import java.awt.*; import java.util.Objects; /** - * Tab Component for {@link TabFrame}. + * Tab Component for {@link JTabFrame}. * * @author Jannis Weis */ public class TabFrameTabLabel extends JLabel implements TabFrameTab { - private TabFrame parent; + private JTabFrame parent; private Alignment orientation; private String title; private boolean selected; @@ -48,7 +48,7 @@ public class TabFrameTabLabel extends JLabel implements TabFrameTab { private int index; /** - * Create new TabComponent for the frame of {@link TabFrame}. + * Create new TabComponent for the frame of {@link JTabFrame}. * * @param title the title. * @param icon the icon. @@ -57,7 +57,7 @@ public class TabFrameTabLabel extends JLabel implements TabFrameTab { * @param parent the parent layout manager. */ public TabFrameTabLabel(final String title, final Icon icon, final Alignment orientation, - final int index, @NotNull final TabFrame parent) { + final int index, @NotNull final JTabFrame parent) { this.index = index; this.accelerator = -1; this.parent = parent; @@ -131,12 +131,12 @@ public class TabFrameTabLabel extends JLabel implements TabFrameTab { } @Override - public TabFrame getTabFrame() { + public JTabFrame getTabFrame() { return parent; } @Override - public void setTabFrame(final TabFrame parent) { + public void setTabFrame(final JTabFrame parent) { var old = this.parent; this.parent = parent; firePropertyChange("tabFrame", old, parent); diff --git a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameUI.java b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameUI.java index fddf549a..ae6c06a4 100644 --- a/src/main/java/com/weis/darklaf/components/tabframe/TabFrameUI.java +++ b/src/main/java/com/weis/darklaf/components/tabframe/TabFrameUI.java @@ -23,9 +23,36 @@ */ package com.weis.darklaf.components.tabframe; +import com.weis.darklaf.components.alignment.Alignment; + import javax.swing.plaf.ComponentUI; +import java.awt.*; public abstract class TabFrameUI extends ComponentUI { - public abstract int getTabSize(TabFrame tabFrame); + public abstract int getTabSize(JTabFrame tabFrame); + + public abstract void clearTargetIndicator(); + + public abstract void clearSourceIndicator(); + + public abstract Color getDragBorderColor(); + + public abstract void setSourceIndicator(Alignment a, int tabIndex); + + public abstract void setTargetIndicator(Alignment a, int tabIndex); + + public abstract JTabFrame.TabFramePosition getTabIndexAt(JTabFrame tabFrame, Point p); + + public abstract JTabFrame.TabFramePosition getNearestTabIndexAt(JTabFrame tabFrame, Point p); + + public abstract void setDropSize(final int width, final int height); + + public abstract int getTabWidth(JTabFrame tabFrame, Alignment a, int index); + + public abstract int getTabHeight(JTabFrame tabFrame, Alignment a, int index); + + public abstract Rectangle getTabContainerBounds(JTabFrame tabFrame, Alignment a); + + public abstract JTabFrame.TabFramePosition getDropPosition(JTabFrame tabFrame, Point p); } diff --git a/src/main/java/com/weis/darklaf/components/tabframe/TabbedPopup.java b/src/main/java/com/weis/darklaf/components/tabframe/TabbedPopup.java index 2c43179a..f4e53c8e 100644 --- a/src/main/java/com/weis/darklaf/components/tabframe/TabbedPopup.java +++ b/src/main/java/com/weis/darklaf/components/tabframe/TabbedPopup.java @@ -30,7 +30,7 @@ import java.util.Collection; import java.util.List; /** - * Tabbed Popup Component for {@link TabFrame}. + * Tabbed Popup Component for {@link JTabFrame}. * * @author Jannis Weis */ diff --git a/src/main/java/com/weis/darklaf/components/text/SearchEvent.java b/src/main/java/com/weis/darklaf/components/text/SearchEvent.java new file mode 100644 index 00000000..11076cdf --- /dev/null +++ b/src/main/java/com/weis/darklaf/components/text/SearchEvent.java @@ -0,0 +1,40 @@ +/* + * MIT License + * + * Copyright (c) 2019 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.weis.darklaf.components.text; + +import java.awt.event.ActionEvent; + +public class SearchEvent extends ActionEvent { + + private final String text; + + public SearchEvent(final Object source, final int id, final String command, final String text) { + super(source, id, command); + this.text = text; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/com/weis/darklaf/components/text/SearchListener.java b/src/main/java/com/weis/darklaf/components/text/SearchListener.java new file mode 100644 index 00000000..20b5ac46 --- /dev/null +++ b/src/main/java/com/weis/darklaf/components/text/SearchListener.java @@ -0,0 +1,31 @@ +/* + * MIT License + * + * Copyright (c) 2019 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.weis.darklaf.components.text; + +import java.util.EventListener; + +public interface SearchListener extends EventListener { + + void searchPerformed(final SearchEvent e); +} diff --git a/src/main/java/com/weis/darklaf/components/text/SearchTextField.java b/src/main/java/com/weis/darklaf/components/text/SearchTextField.java new file mode 100644 index 00000000..2aa22b72 --- /dev/null +++ b/src/main/java/com/weis/darklaf/components/text/SearchTextField.java @@ -0,0 +1,119 @@ +/* + * MIT License + * + * Copyright (c) 2019 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.weis.darklaf.components.text; + +import javax.swing.*; +import javax.swing.text.Document; + +public class SearchTextField extends JTextField { + + public static final int SEARCH = 0; + + /** + * Constructs a new TextField. A default model is created, the initial string is null, + * and the number of columns is set to 0. + */ + public SearchTextField() { + this(null, null, 0); + } + + /** + * Constructs a new JTextField that uses the given text storage model and the given number of columns. + * This is the constructor through which the other constructors feed. If the document is null, a + * default model is created. + * + * @param doc the text storage to use; if this is null, a default will be provided by calling the + * createDefaultModel method + * @param text the initial string to display, or null + * @param columns the number of columns to use to calculate the preferred width >= 0; if columns is + * set to zero, the preferred width will be whatever naturally results from the component + * implementation + * @throws IllegalArgumentException if columns < 0 + */ + public SearchTextField(final Document doc, final String text, final int columns) { + super(doc, text, columns); + putClientProperty("JTextField.variant", "search"); + addActionListener(e -> { + var list = listenerList.getListeners(SearchListener.class); + var evt = new SearchEvent(SearchTextField.this, SEARCH, "search", getText()); + for (var listener : list) { + if (listener != null) { + listener.searchPerformed(evt); + } + } + }); + } + + /** + * Constructs a new TextField initialized with the specified text. A default model is created and the + * number of columns is 0. + * + * @param text the text to be displayed, or null + */ + public SearchTextField(final String text) { + this(null, text, 0); + } + + /** + * Constructs a new empty TextField with the specified number of columns. A default model is created + * and the initial string is set to + * null. + * + * @param columns the number of columns to use to calculate the preferred width; if columns is set to zero, the + * preferred width will be whatever naturally results from the component implementation + */ + public SearchTextField(final int columns) { + this(null, null, columns); + } + + /** + * Constructs a new TextField initialized with the specified text and columns. A default model is + * created. + * + * @param text the text to be displayed, or null + * @param columns the number of columns to use to calculate the preferred width; if columns is set to zero, the + * preferred width will be whatever naturally results from the component implementation + */ + public SearchTextField(final String text, final int columns) { + this(null, text, columns); + } + + /** + * Add a {@link SearchListener} to this search text field. + * + * @param listener the listen to add. + */ + public void addSearchListener(final SearchListener listener) { + listenerList.add(SearchListener.class, listener); + } + + /** + * Remove a {@link SearchListener} from this search text field. + * + * @param listener the listener to remove + */ + public void removeSearchListener(final SearchListener listener) { + listenerList.remove(SearchListener.class, listener); + } +} diff --git a/src/main/java/com/weis/darklaf/components/text/SearchTextFieldWithHistory.java b/src/main/java/com/weis/darklaf/components/text/SearchTextFieldWithHistory.java new file mode 100644 index 00000000..3bbf67d7 --- /dev/null +++ b/src/main/java/com/weis/darklaf/components/text/SearchTextFieldWithHistory.java @@ -0,0 +1,174 @@ +/* + * MIT License + * + * Copyright (c) 2019 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.weis.darklaf.components.text; + +import javax.swing.text.Document; +import java.util.List; + +/** + * {@link SearchTextField} that has a popup that displays the search history. A search entry is added + * + * @author Jannis Weis + */ +public class SearchTextFieldWithHistory extends SearchTextField { + + protected TextFieldHistoryPopup history; + + /** + * Constructs a new TextField. A default model is created, the initial string is null, + * and the number of columns is set to 0. + */ + public SearchTextFieldWithHistory() { + this(null, null, 0); + } + + /** + * Constructs a new JTextField that uses the given text storage model and the given number of columns. + * This is the constructor through which the other constructors feed. If the document is null, a + * default model is created. + * + * @param doc the text storage to use; if this is null, a default will be provided by calling the + * createDefaultModel method + * @param text the initial string to display, or null + * @param columns the number of columns to use to calculate the preferred width >= 0; if columns is + * set to zero, the preferred width will be whatever naturally results from the component + * implementation + * @throws IllegalArgumentException if columns < 0 + */ + public SearchTextFieldWithHistory(final Document doc, final String text, final int columns) { + super(doc, text, columns); + history = new TextFieldHistoryPopup(this, 100, 800); + putClientProperty("JTextField.Search.FindPopup", history); + } + + /** + * Constructs a new TextField initialized with the specified text. A default model is created and the + * number of columns is 0. + * + * @param text the text to be displayed, or null + */ + public SearchTextFieldWithHistory(final String text) { + this(null, text, 0); + } + + /** + * Constructs a new empty TextField with the specified number of columns. A default model is created + * and the initial string is set to + * null. + * + * @param columns the number of columns to use to calculate the preferred width; if columns is set to zero, the + * preferred width will be whatever naturally results from the component implementation + */ + public SearchTextFieldWithHistory(final int columns) { + this(null, null, columns); + } + + /** + * Constructs a new TextField initialized with the specified text and columns. A default model is + * created. + * + * @param text the text to be displayed, or null + * @param columns the number of columns to use to calculate the preferred width; if columns is set to zero, the + * preferred width will be whatever naturally results from the component implementation + */ + public SearchTextFieldWithHistory(final String text, final int columns) { + this(null, text, columns); + } + + /** + * Set the maximum height of the popup. If the size is larger than the specified maximum height the content will be + * wrapped inside a scroll pane. + *

+ * Note: A value of <= 0 indicates that the height should not be limited. + * + * @param maximumHeight the max height to use. + */ + public void setMaximumHeight(final int maximumHeight) { + history.setMaxHeight(maximumHeight); + } + + /** + * Get the history as a list. + * + * @return the history. + */ + public List getHistory() { + return history.getHistory(); + } + + /** + * Clear all entries from the history. + */ + public void clearHistory() { + history.clearHistory(); + } + + /** + * Add entry to the history. If the size is greater than the capacity the oldest entry will be deleted. + * + * @param entry the entry to add. + * @see #getLength() getLength + * @see #setCapacity(int) setCapacity + * @see #getCapacity() getCapacity + */ + public void addEntry(final String entry) { + history.addEntry(entry); + } + + /** + * Get the capacity of the history. + * + * @return the capacity. + * @see #setCapacity(int) setCapacity() + */ + public int getCapacity() { + return history.getCapacity(); + } + + /** + * Set the capacity of the history. If the size grows larger than the capacity the oldest entry will be deleted. + * + * @param capacity the capacity. + * @throws IllegalArgumentException if capacity < 0 + */ + public void setCapacity(final int capacity) throws IllegalArgumentException { + history.setCapacity(capacity); + } + + /** + * Get the current length of the history. + * + * @return the current length of the history. + */ + public int getLength() { + return history.getLength(); + } + + public void setHistoryLength(final int length) { + if (length < 0) throw new IllegalArgumentException("History can't have negative size"); + if (length == 0) { + putClientProperty("JTextField.Search.FindPopup", null); + } + } +} diff --git a/src/main/java/com/weis/darklaf/components/text/TextFieldHistoryPopup.java b/src/main/java/com/weis/darklaf/components/text/TextFieldHistoryPopup.java new file mode 100644 index 00000000..e4f9a81c --- /dev/null +++ b/src/main/java/com/weis/darklaf/components/text/TextFieldHistoryPopup.java @@ -0,0 +1,157 @@ +/* + * MIT License + * + * Copyright (c) 2019 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.weis.darklaf.components.text; + +import com.weis.darklaf.components.ScrollPopupMenu; +import com.weis.darklaf.decorators.PlainAction; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Jannis Weis + */ +public class TextFieldHistoryPopup extends ScrollPopupMenu implements SearchListener { + + private final Set history; + private final JTextField textField; + private int capacity; + + /** + * Create a search popup Menu. + * + * @param textField the text field. + * @param capacity the length of the history. + * @param maxH maximum height. + */ + public TextFieldHistoryPopup(@NotNull final SearchTextField textField, final int capacity, final int maxH) { + super(maxH); + this.textField = textField; + textField.addSearchListener(this); + setCapacity(capacity); + this.history = Collections.newSetFromMap(new LinkedHashMap<>() { + protected boolean removeEldestEntry(final Map.Entry eldest) { + return size() > capacity; + } + }); + } + + /** + * Get the history as a list. + * + * @return the history. + */ + public List getHistory() { + return new ArrayList<>(history); + } + + /** + * Get the capacity of the history. + * + * @return the capacity. + * @see #setCapacity(int) setCapacity() + */ + public int getCapacity() { + return capacity; + } + + /** + * Set the capacity of the history. If the size grows larger than the capacity the oldest entry will be deleted. + * + * @param capacity the capacity. + * @throws IllegalArgumentException if capacity < 0 + */ + public void setCapacity(final int capacity) throws IllegalArgumentException { + if (capacity < 0) throw new IllegalArgumentException("Negative history size is not supported"); + this.capacity = capacity; + } + + /** + * Get the current length of the history. + * + * @return the current length of the history. + */ + public int getLength() { + return history.size(); + } + + @Override + public void searchPerformed(@NotNull final SearchEvent e) { + var text = e.getText(); + if (!text.isBlank()) { + addEntry(text); + } + } + + @Override + public void show(final Component invoker, final int x, final int y) { + if (history.size() == 0) return; + super.show(invoker, x, y); + } + + @Override + protected void showPopup() { + if (history.size() == 0) { + firePopupMenuCanceled(); + return; + } + this.removeAll(); + LinkedList list = new LinkedList<>(history); + Iterator itr = list.descendingIterator(); + while (itr.hasNext()) { + String item = itr.next(); + add(new JMenuItem(new PlainAction(item, () -> textField.setText(item)))); + } + super.showPopup(); + } + + /** + * Add entry to the history. If the size is greater than the capacity the oldest entry will be deleted. + * + * @param entry the entry to add. + * @see #getLength() getLength + * @see #setCapacity(int) setCapacity + * @see #getCapacity() getCapacity + */ + public void addEntry(final String entry) { + history.remove(entry); + history.add(entry); + } + + /** + * Clear all entries from the history. + */ + public void clearHistory() { + history.clear(); + } +} diff --git a/src/main/java/com/weis/darklaf/components/tooltip/ToolTipContext.java b/src/main/java/com/weis/darklaf/components/tooltip/ToolTipContext.java index 7bea748c..b017af7d 100644 --- a/src/main/java/com/weis/darklaf/components/tooltip/ToolTipContext.java +++ b/src/main/java/com/weis/darklaf/components/tooltip/ToolTipContext.java @@ -429,6 +429,7 @@ public class ToolTipContext implements ToolTipListener { if (valid && !updatePosition && lastPos != null && !Objects.equals(rect, lastRect)) { + System.out.println("here"); return lastPos; } getToolTip().setTipText(c.getToolTipText(event)); diff --git a/src/main/java/com/weis/darklaf/components/Insets2D.java b/src/main/java/com/weis/darklaf/components/uiresource/Insets2D.java similarity index 98% rename from src/main/java/com/weis/darklaf/components/Insets2D.java rename to src/main/java/com/weis/darklaf/components/uiresource/Insets2D.java index 7faca0f7..26767903 100644 --- a/src/main/java/com/weis/darklaf/components/Insets2D.java +++ b/src/main/java/com/weis/darklaf/components/uiresource/Insets2D.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.weis.darklaf.components; +package com.weis.darklaf.components.uiresource; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/com/weis/darklaf/components/JLabelUIResource.java b/src/main/java/com/weis/darklaf/components/uiresource/JLabelUIResource.java similarity index 97% rename from src/main/java/com/weis/darklaf/components/JLabelUIResource.java rename to src/main/java/com/weis/darklaf/components/uiresource/JLabelUIResource.java index 41404c72..f433cc87 100644 --- a/src/main/java/com/weis/darklaf/components/JLabelUIResource.java +++ b/src/main/java/com/weis/darklaf/components/uiresource/JLabelUIResource.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.weis.darklaf.components; +package com.weis.darklaf.components.uiresource; import javax.swing.*; import javax.swing.plaf.UIResource; diff --git a/src/main/java/com/weis/darklaf/components/JPanelUIResource.java b/src/main/java/com/weis/darklaf/components/uiresource/JPanelUIResource.java similarity index 97% rename from src/main/java/com/weis/darklaf/components/JPanelUIResource.java rename to src/main/java/com/weis/darklaf/components/uiresource/JPanelUIResource.java index 9b113ef4..a3e8d4d3 100644 --- a/src/main/java/com/weis/darklaf/components/JPanelUIResource.java +++ b/src/main/java/com/weis/darklaf/components/uiresource/JPanelUIResource.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.weis.darklaf.components; +package com.weis.darklaf.components.uiresource; import javax.swing.*; import javax.swing.plaf.UIResource; diff --git a/src/main/java/com/weis/darklaf/components/UIResourceWrapper.java b/src/main/java/com/weis/darklaf/components/uiresource/UIResourceWrapper.java similarity index 97% rename from src/main/java/com/weis/darklaf/components/UIResourceWrapper.java rename to src/main/java/com/weis/darklaf/components/uiresource/UIResourceWrapper.java index 781ff83e..d5ce62fb 100644 --- a/src/main/java/com/weis/darklaf/components/UIResourceWrapper.java +++ b/src/main/java/com/weis/darklaf/components/uiresource/UIResourceWrapper.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.weis.darklaf.components; +package com.weis.darklaf.components.uiresource; import javax.swing.border.Border; import java.awt.*; diff --git a/src/main/java/com/weis/darklaf/ui/colorchooser/DarkColorChooserPanel.java b/src/main/java/com/weis/darklaf/ui/colorchooser/DarkColorChooserPanel.java index 16f70347..1e9609b8 100644 --- a/src/main/java/com/weis/darklaf/ui/colorchooser/DarkColorChooserPanel.java +++ b/src/main/java/com/weis/darklaf/ui/colorchooser/DarkColorChooserPanel.java @@ -201,7 +201,6 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements protected void update() { try { if (isChanging) return; - System.out.println("here"); var hexStr = String.format("%1$-" + 8 + "s", field.getText()).replaceAll(" ", "F"); var alpha = isColorTransparencySelectionEnabled() ? Integer.valueOf(hexStr.substring(6, 8), 16) : 255; @@ -210,7 +209,6 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements Integer.valueOf(hexStr.substring(2, 4), 16), Integer.valueOf(hexStr.substring(4, 6), 16), alpha); - System.out.println(c); colorWheelPanel.setColor(c, textHex); } catch (NumberFormatException | IndexOutOfBoundsException ignore) {} } diff --git a/src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabbedPaneUI.java b/src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabbedPaneUI.java index 4657884b..c38c9216 100644 --- a/src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabbedPaneUI.java +++ b/src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabbedPaneUI.java @@ -1,6 +1,6 @@ package com.weis.darklaf.ui.tabbedpane; -import com.weis.darklaf.components.UIResourceWrapper; +import com.weis.darklaf.components.uiresource.UIResourceWrapper; import com.weis.darklaf.util.DarkUIUtil; import com.weis.darklaf.util.GraphicsContext; import org.jetbrains.annotations.Contract; @@ -20,7 +20,7 @@ import java.util.TooManyListenersException; */ public class DarkTabbedPaneUI extends DarkTabbedPaneUIBridge { - protected static final TabbedPaneTransferHandler TRANSFER_HANDLER = new TabbedPaneTransferHandler(); + protected static final TabbedPaneTransferHandler TRANSFER_HANDLER = new TabbedPaneTransferHandler.UIResource(); protected final FocusListener focusListener = new FocusListener() { @Override public void focusGained(final FocusEvent e) { @@ -110,8 +110,15 @@ public class DarkTabbedPaneUI extends DarkTabbedPaneUIBridge { @Override public void uninstallUI(final JComponent c) { - super.uninstallUI(c); scrollableTabSupport = null; + if (tabPane.getTransferHandler() instanceof TabbedPaneTransferHandler.UIResource) { + tabPane.setTransferHandler(null); + if (tabPane.getDropTarget() != null) { + tabPane.getDropTarget().removeDropTargetListener(TRANSFER_HANDLER); + tabPane.getDropTarget().setActive(false); + } + } + super.uninstallUI(c); } @Override diff --git a/src/main/java/com/weis/darklaf/ui/tabbedpane/TabbedPaneTransferHandler.java b/src/main/java/com/weis/darklaf/ui/tabbedpane/TabbedPaneTransferHandler.java index 3ed4fb52..36aa7cb8 100644 --- a/src/main/java/com/weis/darklaf/ui/tabbedpane/TabbedPaneTransferHandler.java +++ b/src/main/java/com/weis/darklaf/ui/tabbedpane/TabbedPaneTransferHandler.java @@ -450,4 +450,8 @@ public class TabbedPaneTransferHandler extends TransferHandler implements DropTa currentTransferable = null; } } + + public static class UIResource extends TabbedPaneTransferHandler { + + } } diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/DarkPanelPopupUI.java b/src/main/java/com/weis/darklaf/ui/tabframe/DarkPanelPopupUI.java index 68c5af39..ba1bf9b1 100644 --- a/src/main/java/com/weis/darklaf/ui/tabframe/DarkPanelPopupUI.java +++ b/src/main/java/com/weis/darklaf/ui/tabframe/DarkPanelPopupUI.java @@ -23,12 +23,12 @@ */ package com.weis.darklaf.ui.tabframe; -import com.weis.darklaf.components.JLabelUIResource; import com.weis.darklaf.components.alignment.Alignment; import com.weis.darklaf.components.border.MutableLineBorder; +import com.weis.darklaf.components.tabframe.JTabFrame; import com.weis.darklaf.components.tabframe.PanelPopup; -import com.weis.darklaf.components.tabframe.TabFrame; import com.weis.darklaf.components.tooltip.ToolTipContext; +import com.weis.darklaf.components.uiresource.JLabelUIResource; import com.weis.darklaf.ui.panel.DarkPanelUI; import com.weis.darklaf.util.DarkUIUtil; import org.jetbrains.annotations.Contract; @@ -250,19 +250,19 @@ public class DarkPanelPopupUI extends DarkPanelUI implements PropertyChangeListe label.setIcon((Icon) evt.getNewValue()); label.repaint(); } else if ("visibleTab".equals(key)) { - if (evt.getNewValue() instanceof TabFrame.TabFramePosition) { - if (((TabFrame.TabFramePosition) evt.getNewValue()).getAlignment() == popupComponent.getAlignment()) { + if (evt.getNewValue() instanceof JTabFrame.TabFramePosition) { + if (((JTabFrame.TabFramePosition) evt.getNewValue()).getAlignment() == popupComponent.getAlignment()) { updateBorder(true); } } } else if ("tabFrame".equals(key)) { var oldVal = evt.getOldValue(); var newVal = evt.getNewValue(); - if (oldVal instanceof TabFrame) { - ((TabFrame) oldVal).removePropertyChangeListener(this); + if (oldVal instanceof JTabFrame) { + ((JTabFrame) oldVal).removePropertyChangeListener(this); } - if (newVal instanceof TabFrame) { - ((TabFrame) newVal).addPropertyChangeListener(this); + if (newVal instanceof JTabFrame) { + ((JTabFrame) newVal).addPropertyChangeListener(this); } } else if ("peerInsets".equals(key)) { updateBorder(false); @@ -289,8 +289,10 @@ public class DarkPanelPopupUI extends DarkPanelUI implements PropertyChangeListe popupComponent.doLayout(); popupComponent.repaint(); if (notifyPeer) { - var peer = tabFrame.getPopupComponentAt(tabFrame.getPeer(popupComponent.getAlignment())); - peer.firePropertyChange("peerInsets", 0, 1); + try { + var peer = tabFrame.getPopupComponentAt(tabFrame.getPeer(popupComponent.getAlignment())); + peer.firePropertyChange("peerInsets", 0, 1); + } catch (IndexOutOfBoundsException ignored) {/*may happen during transfer*/} } } } @@ -353,7 +355,7 @@ public class DarkPanelPopupUI extends DarkPanelUI implements PropertyChangeListe public void eventDispatched(@NotNull final AWTEvent event) { if (event.getID() == FocusEvent.FOCUS_GAINED) { var focusOwner = FocusManager.getCurrentManager().getFocusOwner(); - if (focusOwner instanceof TabFrame) return; + if (focusOwner instanceof JTabFrame) return; if (focusOwner instanceof JRootPane) return; boolean focus = DarkUIUtil.hasFocus(popupComponent); setHeaderBackground(focus); diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameComponentPopupMenu.java b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameComponentPopupMenu.java index 98ac79a0..49887e46 100644 --- a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameComponentPopupMenu.java +++ b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameComponentPopupMenu.java @@ -30,10 +30,11 @@ import com.weis.darklaf.icons.EmptyIcon; import org.jetbrains.annotations.NotNull; import javax.swing.*; +import javax.swing.plaf.UIResource; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -public class DarkTabFrameComponentPopupMenu extends JXPopupMenu implements PropertyChangeListener { +public class DarkTabFrameComponentPopupMenu extends JXPopupMenu implements PropertyChangeListener, UIResource { private final TabFrameTab tab; private JMenuItem[] actions; diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabContainerUI.java b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabContainerUI.java index c2185fca..b7abbbb3 100644 --- a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabContainerUI.java +++ b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabContainerUI.java @@ -23,7 +23,7 @@ */ package com.weis.darklaf.ui.tabframe; -import com.weis.darklaf.components.tabframe.TabFrame; +import com.weis.darklaf.components.tabframe.JTabFrame; import com.weis.darklaf.components.tabframe.TabFrameTabContainer; import com.weis.darklaf.decorators.HoverListener; import com.weis.darklaf.ui.panel.DarkPanelUI; @@ -38,6 +38,7 @@ import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -53,6 +54,7 @@ public class DarkTabFrameTabContainerUI extends DarkPanelUI implements PropertyC } } }; + private MouseMotionListener dragListener; private HoverListener hoverListener; private Color selectedColor; private Color hoverColor; @@ -73,90 +75,50 @@ public class DarkTabFrameTabContainerUI extends DarkPanelUI implements PropertyC } protected void installListeners() { + dragListener = new TabDragListener(tabContainer); hoverListener = new HoverListener(tabContainer); tabContainer.addMouseListener(hoverListener); tabContainer.addPropertyChangeListener(this); tabContainer.addMouseListener(mouseListener); + tabContainer.addMouseMotionListener(dragListener); var cont = tabContainer.getContent(); if (cont != null) { cont.addMouseListener(hoverListener); cont.addMouseListener(mouseListener); + cont.addMouseMotionListener(dragListener); } } - protected void installAccelerator(final TabFrame tabFrame) { - if (tabFrame == null) return; - int acc = tabContainer.getAccelerator(); - if (acc < 0) return; - tabFrame.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) - .put(KeyStroke.getKeyStroke(UIManager.getString("TabFrame.acceleratorKeyCode") + " " + acc), - "accelerator_" + acc); - tabFrame.getActionMap().put("accelerator_" + acc, createAcceleratorAction(tabFrame)); - } - - protected Action createAcceleratorAction(final TabFrame tabFrame) { - return new AbstractAction() { - @Override - public void actionPerformed(final ActionEvent e) { - var a = tabContainer.getOrientation(); - var index = tabContainer.getIndex(); - if (!tabContainer.isSelected()) { - tabFrame.toggleTab(a, index, true); - } else { - var popup = tabFrame.getPopupComponentAt(a, index); - if (!DarkUIUtil.hasFocus(popup)) { - popup.requestFocus(); - } else { - tabFrame.toggleTab(a, index, false); - } - } - } - }; - } - @Override public void uninstallUI(final JComponent c) { super.uninstallUI(c); + uninstallListeners(); + uninstallAccelerator(tabContainer.getTabFrame()); + tabContainer = null; + } + + protected void uninstallListeners() { tabContainer.removeMouseListener(hoverListener); tabContainer.removeMouseListener(mouseListener); tabContainer.removePropertyChangeListener(this); + tabContainer.removeMouseMotionListener(dragListener); var cont = tabContainer.getContent(); if (cont != null) { cont.removeMouseListener(hoverListener); cont.removeMouseListener(mouseListener); + cont.removeMouseMotionListener(dragListener); } + dragListener = null; hoverListener = null; - uninstallAccelerator(tabContainer.getTabFrame()); - tabContainer = null; } - protected void installDefaults(final JPanel p) { - super.installDefaults(p); - tabContainer.setOpaque(true); - selectedColor = UIManager.getColor("TabFrameTab.selectedBackground"); - hoverColor = UIManager.getColor("TabFrameTab.hoverBackground"); - } - - protected void uninstallAccelerator(final TabFrame tabFrame) { + protected void uninstallAccelerator(final JTabFrame tabFrame) { if (tabFrame == null) return; int acc = tabContainer.getAccelerator(); String accAction = "accelerator_" + acc; tabFrame.getActionMap().remove(accAction); } - @Override - public void paint(@NotNull final Graphics g, @NotNull final JComponent c) { - g.setColor(getBackground(tabContainer)); - g.fillRect(0, 0, c.getWidth(), c.getHeight()); - super.paint(g, c); - } - - public Color getBackground(@NotNull final TabFrameTabContainer tab) { - return tab.isSelected() - ? selectedColor - : hoverListener.isHover() ? hoverColor : tab.getBackground(); - } - @Override public void propertyChange(@NotNull final PropertyChangeEvent evt) { var key = evt.getPropertyName(); @@ -166,10 +128,12 @@ public class DarkTabFrameTabContainerUI extends DarkPanelUI implements PropertyC if (oldVal instanceof Component) { ((Component) oldVal).removeMouseListener(mouseListener); ((Component) oldVal).removeMouseListener(hoverListener); + ((Component) oldVal).removeMouseMotionListener(dragListener); } if (newVal instanceof Component) { ((Component) newVal).addMouseListener(mouseListener); ((Component) newVal).addMouseListener(hoverListener); + ((Component) newVal).addMouseMotionListener(dragListener); } } else if ("selected".equals(key)) { if (tabContainer == null) return; @@ -179,12 +143,62 @@ public class DarkTabFrameTabContainerUI extends DarkPanelUI implements PropertyC uninstallAccelerator(tabContainer.getTabFrame()); installAccelerator(tabContainer.getTabFrame()); } else if ("tabFrame".equals(key)) { - if (evt.getOldValue() instanceof TabFrame) { - uninstallAccelerator((TabFrame) evt.getOldValue()); + if (evt.getOldValue() instanceof JTabFrame) { + uninstallAccelerator((JTabFrame) evt.getOldValue()); } - if (evt.getNewValue() instanceof TabFrame) { - installAccelerator((TabFrame) evt.getNewValue()); + if (evt.getNewValue() instanceof JTabFrame) { + installAccelerator((JTabFrame) evt.getNewValue()); } } } + + protected void installDefaults(final JPanel p) { + super.installDefaults(p); + tabContainer.setOpaque(true); + selectedColor = UIManager.getColor("TabFrameTab.selectedBackground"); + hoverColor = UIManager.getColor("TabFrameTab.hoverBackground"); + } + + protected void installAccelerator(final JTabFrame tabFrame) { + if (tabFrame == null) return; + int acc = tabContainer.getAccelerator(); + if (acc < 0) return; + tabFrame.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) + .put(KeyStroke.getKeyStroke(UIManager.getString("TabFrame.acceleratorKeyCode") + " " + acc), + "accelerator_" + acc); + tabFrame.getActionMap().put("accelerator_" + acc, createAcceleratorAction(tabFrame)); + } + + @Override + public void paint(@NotNull final Graphics g, @NotNull final JComponent c) { + g.setColor(getBackground(tabContainer)); + g.fillRect(0, 0, c.getWidth(), c.getHeight()); + super.paint(g, c); + } + + public Color getBackground(@NotNull final TabFrameTabContainer tab) { + return tab.isSelected() + ? selectedColor + : hoverListener.isHover() ? hoverColor : tab.getBackground(); + } + + protected Action createAcceleratorAction(final JTabFrame tabFrame) { + return new AbstractAction() { + @Override + public void actionPerformed(final ActionEvent e) { + var a = tabContainer.getOrientation(); + var index = tabContainer.getIndex(); + if (!tabContainer.isSelected()) { + tabFrame.toggleTab(a, index, true); + } else { + var popup = tabFrame.getPopupComponentAt(a, index); + if (!DarkUIUtil.hasFocus(popup)) { + popup.requestFocus(); + } else { + tabFrame.toggleTab(a, index, false); + } + } + } + }; + } } diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java index 44e74a1e..d8ff0822 100644 --- a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java +++ b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java @@ -24,7 +24,7 @@ package com.weis.darklaf.ui.tabframe; import com.weis.darklaf.components.alignment.Alignment; -import com.weis.darklaf.components.tabframe.TabFrame; +import com.weis.darklaf.components.tabframe.JTabFrame; import com.weis.darklaf.components.tabframe.TabFrameTabLabel; import com.weis.darklaf.decorators.HoverListener; import com.weis.darklaf.icons.RotatableIcon; @@ -36,6 +36,7 @@ import sun.swing.SwingUtilities2; import javax.swing.*; import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.View; import java.awt.*; @@ -43,6 +44,7 @@ import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -51,13 +53,14 @@ public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChang private TabFrameTabLabel tabComponent; private final MouseListener mouseListener = new MouseAdapter() { @Override - public void mousePressed(final MouseEvent e) { + public void mouseClicked(final MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { tabComponent.getTabFrame().toggleTab(tabComponent.getOrientation(), tabComponent.getIndex(), !tabComponent.isSelected()); } } }; + private MouseMotionListener dragListener; private HoverListener hoverListener; private Color defaultFontColor; private Color selectedFontColor; @@ -66,6 +69,7 @@ public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChang private RotatableIcon rotatableIcon = new RotatableIcon(); private Rectangle paintIconR = new Rectangle(); private Rectangle paintTextR = new Rectangle(); + private boolean printing; @NotNull @Contract("_ -> new") @@ -119,9 +123,10 @@ public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChang @Override public void uninstallUI(final JComponent c) { super.uninstallUI(c); - uninstallAccelerator(tabComponent.getTabFrame()); - tabComponent.removeMouseListener(hoverListener); - tabComponent.removeMouseListener(mouseListener); + uninstallListeners(tabComponent); + if (tabComponent.getComponentPopupMenu() instanceof UIResource) { + tabComponent.setComponentPopupMenu(null); + } hoverListener = null; tabComponent = null; } @@ -142,13 +147,54 @@ public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChang @Override protected void installListeners(final JLabel c) { super.installListeners(c); + dragListener = new TabDragListener(tabComponent); hoverListener = new HoverListener(tabComponent); tabComponent.addMouseListener(hoverListener); tabComponent.addMouseListener(mouseListener); installAccelerator(tabComponent.getTabFrame()); + tabComponent.addMouseMotionListener(dragListener); + } + + @Override + protected void uninstallListeners(final JLabel c) { + super.uninstallListeners(c); + tabComponent.removeMouseListener(hoverListener); + tabComponent.removeMouseListener(mouseListener); + uninstallAccelerator(tabComponent.getTabFrame()); + tabComponent.removeMouseMotionListener(dragListener); + dragListener = null; + } + + @Override + public void propertyChange(final PropertyChangeEvent e) { + super.propertyChange(e); + var key = e.getPropertyName(); + if ("selected".equals(key)) { + tabComponent.setForeground(Boolean.TRUE.equals(e.getNewValue()) + ? selectedFontColor : defaultFontColor); + tabComponent.repaint(); + } else if ("title".equals(key)) { + updateText(); + } else if ("accelerator".equals(key)) { + updateText(); + if (tabComponent == null) return; + uninstallAccelerator(tabComponent.getTabFrame()); + installAccelerator(tabComponent.getTabFrame()); + } else if ("orientation".equals(key)) { + rotatableIcon.setOrientation(mapOrientation(tabComponent.getOrientation())); + } else if ("tabFrame".equals(key)) { + if (e.getOldValue() instanceof JTabFrame) { + uninstallAccelerator((JTabFrame) e.getOldValue()); + } + if (e.getNewValue() instanceof JTabFrame) { + installAccelerator((JTabFrame) e.getNewValue()); + } + } else if ("paintingForPrint".equals(key)) { + printing = Boolean.TRUE.equals(e.getNewValue()); + } } - protected void installAccelerator(final TabFrame tabFrame) { + protected void installAccelerator(final JTabFrame tabFrame) { if (tabFrame == null) return; int acc = tabComponent.getAccelerator(); if (acc < 0) return; @@ -158,7 +204,7 @@ public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChang tabFrame.getActionMap().put("accelerator_" + acc, createAcceleratorAction(tabFrame)); } - protected Action createAcceleratorAction(final TabFrame tabFrame) { + protected Action createAcceleratorAction(final JTabFrame tabFrame) { return new AbstractAction() { @Override public void actionPerformed(final ActionEvent e) { @@ -178,33 +224,6 @@ public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChang }; } - @Override - public void propertyChange(final PropertyChangeEvent e) { - super.propertyChange(e); - var key = e.getPropertyName(); - if ("selected".equals(key)) { - tabComponent.setForeground(Boolean.TRUE.equals(e.getNewValue()) - ? selectedFontColor : defaultFontColor); - tabComponent.repaint(); - } else if ("title".equals(key)) { - updateText(); - } else if ("accelerator".equals(key)) { - updateText(); - if (tabComponent == null) return; - uninstallAccelerator(tabComponent.getTabFrame()); - installAccelerator(tabComponent.getTabFrame()); - } else if ("orientation".equals(key)) { - rotatableIcon.setOrientation(mapOrientation(tabComponent.getOrientation())); - } else if ("tabFrame".equals(key)) { - if (e.getOldValue() instanceof TabFrame) { - uninstallAccelerator((TabFrame) e.getOldValue()); - } - if (e.getNewValue() instanceof TabFrame) { - installAccelerator((TabFrame) e.getNewValue()); - } - } - } - protected void updateText() { var title = tabComponent.getTitle(); title = title == null ? "" : title; @@ -218,7 +237,7 @@ public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChang } } - protected void uninstallAccelerator(final TabFrame tabFrame) { + protected void uninstallAccelerator(final JTabFrame tabFrame) { if (tabFrame == null) return; int acc = tabComponent.getAccelerator(); String accAction = "accelerator_" + acc; @@ -226,6 +245,7 @@ public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChang } public Color getBackground(@NotNull final TabFrameTabLabel tab) { + if (printing || tab.getTabFrame().isInTransfer()) return tab.getBackground(); return tab.isSelected() ? selectedColor : hoverListener.isHover() ? hoverColor : tab.getBackground(); diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameUI.java b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameUI.java index db720c50..b3c29c89 100644 --- a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameUI.java +++ b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameUI.java @@ -23,11 +23,14 @@ */ package com.weis.darklaf.ui.tabframe; +import com.weis.darklaf.components.alignment.Alignment; import com.weis.darklaf.components.border.MutableLineBorder; +import com.weis.darklaf.components.tabframe.JTabFrame; import com.weis.darklaf.components.tabframe.PopupContainer; -import com.weis.darklaf.components.tabframe.TabFrame; import com.weis.darklaf.components.tabframe.TabFramePopup; +import com.weis.darklaf.components.tabframe.TabFrameTab; import com.weis.darklaf.components.tabframe.TabFrameUI; +import com.weis.darklaf.components.uiresource.JPanelUIResource; import com.weis.darklaf.util.DarkUIUtil; import org.jdesktop.jxlayer.JXLayer; import org.jetbrains.annotations.Contract; @@ -40,27 +43,39 @@ import javax.swing.plaf.ComponentUI; import java.awt.*; import java.awt.event.AWTEventListener; import java.awt.event.MouseEvent; +import java.util.TooManyListenersException; /** - * UI class for {@link TabFrame}. + * UI class for {@link JTabFrame}. * * @author Jannis Weis * @since 2018 */ public class DarkTabFrameUI extends TabFrameUI implements AWTEventListener { - private TabFrame tabFrame; + protected static final TabFrameTransferHandler TRANSFER_HANDLER = new TabFrameTransferHandler.UIResource(); + private final Rectangle calcRect = new Rectangle(); private JXLayer rotatePaneLeft; private JXLayer rotatePaneRight; - - private LayoutManager layout; + private JTabFrame tabFrame; + private JComponent dropComponentTop; + private JComponent dropComponentBottom; + private JComponent dropComponentRight; + private JComponent dropComponentLeft; private MutableLineBorder topBorder; private MutableLineBorder bottomBorder; private MutableLineBorder leftBorder; private MutableLineBorder rightBorder; private Color lineColor; + private TabFrameLayout layout; private int tabHeight; + private Color dragBorderColor; + private Dimension dropSize = new Dimension(); + private Alignment sourceAlign; + private int sourceIndex; + private Alignment destAlign; + private int destIndex; @NotNull @Contract("_ -> new") @@ -70,10 +85,11 @@ public class DarkTabFrameUI extends TabFrameUI implements AWTEventListener { @Override public void installUI(@NotNull final JComponent c) { - tabFrame = (TabFrame) c; + tabFrame = (JTabFrame) c; installDefaults(); installComponents(); installListeners(); + installDnD(); } protected void installDefaults() { @@ -86,6 +102,19 @@ public class DarkTabFrameUI extends TabFrameUI implements AWTEventListener { rightBorder = new MutableLineBorder.UIResource(0, 0, 1, 0, lineColor); leftBorder = new MutableLineBorder.UIResource(0, 0, 1, 0, lineColor); + dropComponentTop = new JPanelUIResource(); + dropComponentBottom = new JPanelUIResource(); + dropComponentLeft = new JPanelUIResource(); + dropComponentRight = new JPanelUIResource(); + + dragBorderColor = UIManager.getColor("TabFrame.dragBorderColor"); + Color dropColor = UIManager.getColor("TabFrame.dropBackground"); + + dropComponentTop.setBackground(dropColor); + dropComponentBottom.setBackground(dropColor); + dropComponentLeft.setBackground(dropColor); + dropComponentRight.setBackground(dropColor); + tabFrame.getTopTabContainer().setBorder(topBorder); tabFrame.getBottomTabContainer().setBorder(bottomBorder); tabFrame.getRightTabContainer().setBorder(rightBorder); @@ -107,13 +136,28 @@ public class DarkTabFrameUI extends TabFrameUI implements AWTEventListener { tabFrame.add(tabFrame.getBottomTabContainer()); tabFrame.add(rotatePaneLeft); tabFrame.add(rotatePaneRight); + + tabFrame.getTopTabContainer().add(dropComponentTop); + tabFrame.getBottomTabContainer().add(dropComponentBottom); + tabFrame.getLeftTabContainer().add(dropComponentLeft); + tabFrame.getRightTabContainer().add(dropComponentRight); + } + + protected void installDnD() { + tabFrame.setTransferHandler(TRANSFER_HANDLER); + try { + tabFrame.getDropTarget().addDropTargetListener(TRANSFER_HANDLER); + tabFrame.getDropTarget().setActive(tabFrame.isDndEnabled()); + } catch (TooManyListenersException e) { + e.printStackTrace(); + } } protected void installListeners() { Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK); } - protected LayoutManager createLayout() { + protected TabFrameLayout createLayout() { return new TabFrameLayout(tabFrame, this); } @@ -132,22 +176,344 @@ public class DarkTabFrameUI extends TabFrameUI implements AWTEventListener { rotatePaneLeft = null; rotatePaneRight = null; uninstallListeners(); + if (tabFrame.getTransferHandler() instanceof TabFrameTransferHandler.UIResource) { + tabFrame.setTransferHandler(null); + if (tabFrame.getDropTarget() != null) { + tabFrame.getDropTarget().removeDropTargetListener(TRANSFER_HANDLER); + tabFrame.getDropTarget().setActive(false); + } + } } protected void uninstallListeners() { Toolkit.getDefaultToolkit().removeAWTEventListener(this); } - public void updateBorders(final int topCount, final int bottomCount, - final int leftCount, final int rightCount) { - leftBorder.setRight(topCount > 0 ? 0 : 1); - leftBorder.setLeft(bottomCount > 0 ? 0 : 1); - rightBorder.setLeft(topCount > 0 ? 0 : 1); - rightBorder.setRight(bottomCount > 0 ? 0 : 1); + public int getTabSize(final JTabFrame tabFrame) { + return tabHeight; } - public int getTabSize(final TabFrame tabFrame) { - return tabHeight; + + @Override + public void clearTargetIndicator() { + destIndex = -10; + destAlign = null; + dropSize.setSize(0, 0); + tabFrame.doLayout(); + } + + @Override + public void clearSourceIndicator() { + sourceIndex = -10; + sourceAlign = null; + tabFrame.doLayout(); + } + + @Override + public Color getDragBorderColor() { + return dragBorderColor; + } + + @Override + public void setSourceIndicator(final Alignment a, final int tabIndex) { + sourceAlign = a; + sourceIndex = tabIndex; + tabFrame.doLayout(); + } + + @Override + public void setTargetIndicator(final Alignment a, final int tabIndex) { + destAlign = a; + destIndex = tabIndex; + tabFrame.doLayout(); + } + + @Override + public JTabFrame.TabFramePosition getTabIndexAt(final JTabFrame tabFrame, @NotNull final Point p) { + Component tabComp = null; + Alignment a = null; + Point pos = null; + if (!layout.isDraggedOver(Alignment.NORTH)) { + getTopContainer().getBounds(calcRect); + if (p.y >= calcRect.y && p.y <= calcRect.y + calcRect.height) { + tabComp = getTopContainer(); + a = Alignment.NORTH; + pos = SwingUtilities.convertPoint(tabFrame, p, tabComp); + if (pos.x > tabComp.getWidth() / 2) { + a = Alignment.NORTH_EAST; + } + } + } + if (tabComp == null && !layout.isDraggedOver(Alignment.SOUTH)) { + getBottomContainer().getBounds(calcRect); + if (p.y >= calcRect.y && p.y <= calcRect.y + calcRect.height) { + tabComp = getBottomContainer(); + a = Alignment.SOUTH; + pos = SwingUtilities.convertPoint(tabFrame, p, tabComp); + if (pos.x <= tabComp.getWidth() / 2) { + a = Alignment.SOUTH_WEST; + } + } + } + if (tabComp == null && !layout.isDraggedOver(Alignment.WEST)) { + getLeftContainer().getBounds(calcRect); + if (p.x >= calcRect.x && p.x <= calcRect.x + calcRect.width) { + tabComp = getLeftContainer(); + a = Alignment.WEST; + pos = SwingUtilities.convertPoint(tabFrame, p, tabComp); + if (pos.y < tabComp.getHeight() / 2) { + a = Alignment.NORTH_WEST; + } + int tmp = pos.x; + pos.x = tabComp.getHeight() - pos.y; + pos.y = tmp; + } + } + if (tabComp == null && !layout.isDraggedOver(Alignment.EAST)) { + getRightContainer().getBounds(calcRect); + if (p.x >= calcRect.x && p.x <= calcRect.x + calcRect.width) { + tabComp = getRightContainer(); + a = Alignment.EAST; + pos = SwingUtilities.convertPoint(tabFrame, p, tabComp); + if (pos.y > tabComp.getHeight() / 2) { + a = Alignment.SOUTH_EAST; + } + int tmp = pos.x; + //noinspection SuspiciousNameCombination + pos.x = pos.y; + pos.y = tmp; + } + } + if (tabComp == null) { + var tab = maybeRestoreTabContainer(tabFrame, p); + if (tab.getAlignment() != null) { + return tab; + } + } else { + layout.setDraggedOver(false); + } + if (tabComp == null) { + return new JTabFrame.TabFramePosition(null, -1); + } + var tabs = tabFrame.tabsForAlignment(a); + for (var tab : tabs) { + var rect = getTabRect(tab, a, tabComp, true); + if (rect.contains(pos)) { + return new JTabFrame.TabFramePosition(a, tab.getIndex(), pos); + } + } + return new JTabFrame.TabFramePosition(a, -1, pos); + } + + @Override + public JTabFrame.TabFramePosition getNearestTabIndexAt(final JTabFrame tabFrame, final Point pos) { + var tab = getTabIndexAt(tabFrame, pos); + if (tab.getAlignment() != null && tab.getIndex() == -1) { + var p = tab.getPoint(); + var a = tab.getAlignment(); + if (tabFrame.getTabCountAt(a) == 0) { + tab.setIndex(-1); + return tab; + } + int w = a == destAlign && destIndex == -1 ? dropSize.width : 0; + var comp = getTabContainer(a); + switch (a) { + case NORTH: + case SOUTH_WEST: + if (p.x > getLeftContainer().getWidth() + w) { + tab.setIndex(tabFrame.getTabCountAt(a) - 1); + } + break; + case NORTH_EAST: + case SOUTH: + if (p.x < comp.getWidth() - getRightContainer().getWidth() - w) { + tab.setIndex(tabFrame.getTabCountAt(a) - 1); + } + break; + case EAST: + case WEST: + if (p.x > w) { + tab.setIndex(tabFrame.getTabCountAt(a) - 1); + } + break; + case SOUTH_EAST: + case NORTH_WEST: + if (p.x < comp.getHeight() - w) { + tab.setIndex(tabFrame.getTabCountAt(a) - 1); + } + break; + } + } + return tab; + } + + public void setDropSize(final int width, final int height) { + dropSize.setSize(width, height); + } + + @Override + public int getTabWidth(@NotNull final JTabFrame tabFrame, final Alignment a, final int index) { + return layout.getTabWidth(tabFrame.getTabComponentAt(a, index).getComponent()); + } + + @Override + public int getTabHeight(@NotNull final JTabFrame tabFrame, final Alignment a, final int index) { + return tabFrame.getTabSize(); + } + + public Rectangle getTabContainerBounds(final JTabFrame tabFrame, @NotNull final Alignment a) { + switch (a) { + case NORTH: + case NORTH_EAST: + var rect = getTopContainer().getBounds(); + rect.x = 0; + rect.width = tabFrame.getWidth(); + return rect; + case EAST: + case SOUTH_EAST: + return getRightContainer().getBounds(); + case SOUTH: + case SOUTH_WEST: + var rect2 = getTopContainer().getBounds(); + rect2.x = 0; + rect2.width = tabFrame.getWidth(); + return rect2; + case WEST: + case NORTH_WEST: + return getLeftContainer().getBounds(); + default: + case CENTER: + return tabFrame.getContentPane().getComponent().getBounds(); + } + } + + public JTabFrame.TabFramePosition getDropPosition(final JTabFrame tabFrame, final Point p) { + var tab = getNearestTabIndexAt(tabFrame, p); + if (tab.getAlignment() != null) { + var a = tab.getAlignment(); + int index = tab.getIndex(); + if (index >= 0) { + var rect = getTabRect(tabFrame.getTabComponentAt(a, index), a, tabFrame.getTabContainer(a), false); + var pos = tab.getPoint(); + if (isForward(a)) { + if (pos.x <= rect.x + rect.width / 2 && pos.x >= rect.x) { + tab.setIndex(tab.getIndex() - 1); + } + } else { + if (pos.x >= rect.x + rect.width / 2) { + tab.setIndex(tab.getIndex() - 1); + } + } + } + } + return tab; + } + + public Alignment getSourceAlign() { + return sourceAlign; + } + + public int getSourceIndex() { + return sourceIndex; + } + + public Alignment getDestAlign() { + return destAlign; + } + + public int getDestIndex() { + return destIndex; + } + + protected JTabFrame.TabFramePosition maybeRestoreTabContainer(@NotNull final JTabFrame tabFrame, final Point p) { + Alignment a = null; + int size = tabFrame.getTabSize(); + int threshold = size; + int index = -10; + if (tabFrame.getTopTabCount() == 0) { + if (layout.isDraggedOver(Alignment.NORTH)) threshold *= 2; + if (p.y < threshold) { + a = p.x >= tabFrame.getWidth() / 2 ? Alignment.NORTH_EAST : Alignment.NORTH; + } + if (p.y < size) index = -1; + } + if (a == null && tabFrame.getBottomTabCount() == 0) { + if (layout.isDraggedOver(Alignment.SOUTH)) threshold *= 2; + if (p.y > tabFrame.getHeight() - threshold) { + a = p.x >= tabFrame.getWidth() / 2 ? Alignment.SOUTH : Alignment.SOUTH_WEST; + } + if (p.y > tabFrame.getHeight() - size) index = -1; + } + if (a == null && tabFrame.getLeftTabCount() == 0) { + if (layout.isDraggedOver(Alignment.WEST)) threshold *= 2; + if (p.x < threshold) { + a = p.y >= tabFrame.getHeight() / 2 ? Alignment.WEST : Alignment.NORTH_WEST; + } + if (p.x < size) index = -1; + } + if (a == null && tabFrame.getRightTabCount() == 0) { + if (layout.isDraggedOver(Alignment.EAST)) threshold *= 2; + if (p.x > tabFrame.getWidth() - threshold) { + a = p.y >= tabFrame.getHeight() / 2 ? Alignment.SOUTH_EAST : Alignment.EAST; + } + if (p.x > tabFrame.getWidth() - size) index = -1; + } + layout.setDraggedOver(false); + if (a != null) { + layout.setDraggedOver(a, true); + } + return new JTabFrame.TabFramePosition(a, index); + } + + protected Rectangle getTabRect(@NotNull final TabFrameTab tab, final Alignment a, final Component tabComp, + final boolean includeDropRect) { + tab.getComponent().getBounds(calcRect); + SwingUtilities.convertRectangle(tab.getComponent(), calcRect, tabComp); + if (includeDropRect && a == destAlign) { + if (tab.getIndex() == destIndex && destIndex >= 0) { + if (isForward(a)) { + calcRect.width += dropSize.width; + } else { + calcRect.x -= dropSize.width; + calcRect.width += dropSize.width; + } + } + } + return calcRect; + } + + protected boolean isForward(@NotNull final Alignment a) { + switch (a) { + case NORTH: + case EAST: + case WEST: + case SOUTH_WEST: + return true; + case NORTH_WEST: + case SOUTH: + case NORTH_EAST: + case SOUTH_EAST: + default: + return false; + } + } + + protected Component getTabContainer(@NotNull final Alignment a) { + switch (a) { + case NORTH: + case NORTH_EAST: + return getTopContainer(); + case EAST: + case SOUTH_EAST: + return getRightContainer(); + case SOUTH: + case SOUTH_WEST: + return getBottomContainer(); + case WEST: + case NORTH_WEST: + return getLeftContainer(); + } + return null; } public JComponent getLeftContainer() { @@ -181,10 +547,49 @@ public class DarkTabFrameUI extends TabFrameUI implements AWTEventListener { parent2.getPopup().requestFocus(); return; } - var parent3 = DarkUIUtil.getParentOfType(TabFrame.class, comp); + var parent3 = DarkUIUtil.getParentOfType(JTabFrame.class, comp); if (parent3 != null && comp != null && !comp.hasFocus()) { parent3.requestFocus(); } } } + + public Dimension getDropSize() { + return dropSize; + } + + public JComponent getDropComponent(@NotNull final Alignment a) { + switch (a) { + default: + case CENTER: + case NORTH: + case NORTH_EAST: + return getDropComponentTop(); + case EAST: + case SOUTH_EAST: + return getDropComponentRight(); + case SOUTH: + case SOUTH_WEST: + return getDropComponentBottom(); + case WEST: + case NORTH_WEST: + return getDropComponentLeft(); + } + } + + public JComponent getDropComponentTop() { + return dropComponentTop; + } + + public JComponent getDropComponentRight() { + return dropComponentRight; + } + + public JComponent getDropComponentBottom() { + return dropComponentBottom; + } + + public JComponent getDropComponentLeft() { + return dropComponentLeft; + } } diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabbedPopupUI.java b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabbedPopupUI.java index a8a32c71..6611dd4a 100644 --- a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabbedPopupUI.java +++ b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabbedPopupUI.java @@ -23,9 +23,9 @@ */ package com.weis.darklaf.ui.tabframe; -import com.weis.darklaf.components.JPanelUIResource; import com.weis.darklaf.components.border.MutableLineBorder; import com.weis.darklaf.components.tabframe.TabbedPopup; +import com.weis.darklaf.components.uiresource.JPanelUIResource; import com.weis.darklaf.ui.tabbedpane.DarkTabbedPaneUI; import com.weis.darklaf.ui.tabbedpane.MoreTabsButton; import com.weis.darklaf.ui.tabbedpane.NewTabButton; diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/TabDragListener.java b/src/main/java/com/weis/darklaf/ui/tabframe/TabDragListener.java new file mode 100644 index 00000000..0630e994 --- /dev/null +++ b/src/main/java/com/weis/darklaf/ui/tabframe/TabDragListener.java @@ -0,0 +1,60 @@ +/* + * MIT License + * + * Copyright (c) 2019 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.weis.darklaf.ui.tabframe; + +import com.weis.darklaf.components.tabframe.TabFrameTab; +import com.weis.darklaf.util.DarkUIUtil; +import com.weis.darklaf.util.SwingXUtilities; +import org.jdesktop.jxlayer.JXLayer; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class TabDragListener extends MouseAdapter { + + private final TabFrameTab tabComponent; + + public TabDragListener(final TabFrameTab tabComponent) { + this.tabComponent = tabComponent; + } + + @Override + public void mouseDragged(@NotNull final MouseEvent e) { + var th = tabComponent.getTabFrame().getTransferHandler(); + if (th != null && tabComponent.getTabFrame().isDndEnabled()) { + var p = e.getPoint(); + p = SwingXUtilities.convertPointToParent(tabComponent.getComponent(), p); + JXLayer layer = DarkUIUtil.getParentOfType(JXLayer.class, tabComponent.getComponent()); + p = SwingUtilities.convertPoint(layer != null ? layer : tabComponent.getComponent().getParent(), + p, tabComponent.getTabFrame()); + tabComponent.getTabFrame().initTransfer(tabComponent.getOrientation(), tabComponent.getIndex()); + th.exportAsDrag(tabComponent.getTabFrame(), + new MouseEvent(tabComponent.getTabFrame(), e.getID(), e.getWhen(), e.getModifiersEx(), + p.x, p.y, e.getClickCount(), e.isPopupTrigger(), e.getButton()), + TransferHandler.MOVE); + } + } +} diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/TabFrameLayout.java b/src/main/java/com/weis/darklaf/ui/tabframe/TabFrameLayout.java index 52826a1e..b6c743de 100644 --- a/src/main/java/com/weis/darklaf/ui/tabframe/TabFrameLayout.java +++ b/src/main/java/com/weis/darklaf/ui/tabframe/TabFrameLayout.java @@ -24,11 +24,12 @@ package com.weis.darklaf.ui.tabframe; import com.weis.darklaf.components.alignment.Alignment; -import com.weis.darklaf.components.tabframe.TabFrame; +import com.weis.darklaf.components.tabframe.JTabFrame; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import java.awt.*; +import java.util.Arrays; /** * @author Jannis Weis @@ -36,15 +37,21 @@ import java.awt.*; */ public class TabFrameLayout implements LayoutManager { - private final TabFrame tabFrame; + private final JTabFrame tabFrame; private DarkTabFrameUI ui; - private int maxTabHeight; - private int maxTabWidth; + private int[] shift; + private boolean[] draggedOver; + private int topHeight; + private int leftHeight; + private int rightHeight; + private int bottomHeight; @Contract(pure = true) - public TabFrameLayout(@NotNull final TabFrame tabFrame, final DarkTabFrameUI ui) { + public TabFrameLayout(@NotNull final JTabFrame tabFrame, final DarkTabFrameUI ui) { this.tabFrame = tabFrame; this.ui = ui; + shift = new int[4]; + draggedOver = new boolean[4]; } @Override @@ -82,13 +89,27 @@ public class TabFrameLayout implements LayoutManager { int bottomSize = tabFrame.getBottomTabCount(); int leftSize = tabFrame.getLeftTabCount(); int rightSize = tabFrame.getRightTabCount(); + + if (isDraggedOver(Alignment.NORTH)) topSize++; + if (isDraggedOver(Alignment.SOUTH)) bottomSize++; + if (isDraggedOver(Alignment.EAST)) rightSize++; + if (isDraggedOver(Alignment.WEST)) leftSize++; + + ui.getDropComponentBottom().setSize(0, 0); + ui.getDropComponentLeft().setSize(0, 0); + ui.getDropComponentRight().setSize(0, 0); + ui.getDropComponentTop().setSize(0, 0); + + topHeight = calculateMaxTabSize(Alignment.NORTH); + bottomHeight = calculateMaxTabSize(Alignment.SOUTH); + leftHeight = calculateMaxTabSize(Alignment.WEST); + rightHeight = calculateMaxTabSize(Alignment.EAST); + layoutTopTab(dim, topSize, leftSize, rightSize); layoutBottomTab(dim, bottomSize, leftSize, rightSize); layoutLeftTab(dim, leftSize); layoutRightTab(dim, rightSize); - ui.updateBorders(topSize, bottomSize, leftSize, rightSize); - var leftPane = ui.getLeftContainer(); var rightPane = ui.getRightContainer(); var topPane = ui.getTopContainer(); @@ -99,33 +120,63 @@ public class TabFrameLayout implements LayoutManager { } protected void layoutTopTab(final Dimension dim, final int topSize, final int leftSize, final int rightSize) { + var topComp = tabFrame.getTopTabContainer(); if (topSize > 0) { - tabFrame.getTopTabContainer().setBounds(0, 0, dim.width, tabFrame.getTabSize()); - layoutHorizontal(dim, Alignment.NORTH, Alignment.NORTH_EAST, 0, leftSize, rightSize); + topComp.setBounds(0, 0, dim.width, tabFrame.getTabSize()); + layoutHorizontal(dim, Alignment.NORTH, Alignment.NORTH_EAST, 0, leftSize, rightSize, topHeight); + } else if (draggedOver[getIndex(Alignment.NORTH)]) { + int size = tabFrame.getTabSize(); + topComp.setBounds(0, 0, dim.width, size); + if (ui.getDestIndex() >= -1) { + layoutHorizontalDrop(Alignment.NORTH, leftSize, rightSize, size, 0); + } } else { - tabFrame.getTopTabContainer().setBounds(0, 0, 0, 0); + topComp.setBounds(0, 0, 0, 0); } } protected void layoutBottomTab(final Dimension dim, final int bottomSize, final int leftSize, final int rightSize) { + var bottomComp = tabFrame.getBottomTabContainer(); if (bottomSize > 0) { + bottomComp.setBounds(0, dim.height - bottomHeight, dim.width, bottomHeight); + layoutHorizontal(dim, Alignment.SOUTH_WEST, Alignment.SOUTH, 1, leftSize, rightSize, bottomHeight); + } else if (draggedOver[getIndex(Alignment.SOUTH)]) { int size = tabFrame.getTabSize(); - tabFrame.getBottomTabContainer().setBounds(0, dim.height - size, dim.width, size); - layoutHorizontal(dim, Alignment.SOUTH_WEST, Alignment.SOUTH, 1, leftSize, rightSize); + bottomComp.setBounds(0, dim.height - size, dim.width, size); + if (ui.getDestIndex() >= -1) { + layoutHorizontalDrop(Alignment.SOUTH_WEST, leftSize, rightSize, size, 1); + } + } else { + bottomComp.setBounds(0, 0, 0, 0); + } + } + + protected void layoutHorizontalDrop(final Alignment left, final int leftSize, final int rightSize, + final int size, final int yOff) { + var a = ui.getDestAlign(); + var dropSize = ui.getDropSize(); + var dropComp = ui.getDropComponent(left); + var tabComp = ui.getTabContainer(left); + if (a == left) { + int x = leftSize > 0 ? leftHeight : 0; + dropComp.setBounds(x, yOff, dropSize.width, size); } else { - tabFrame.getBottomTabContainer().setBounds(0, 0, 0, 0); + int x = rightSize > 0 ? tabComp.getWidth() - rightHeight : tabComp.getWidth(); + dropComp.setBounds(x - dropSize.width, yOff, dropSize.width, size); } } protected void layoutHorizontal(final Dimension dim, final Alignment left, final Alignment right, - final int yOff, final int leftSize, final int rightSize) { - int tabHeight = calculateMaxTabSize(left); - var start = new Point(leftSize > 0 ? tabHeight : 0, yOff); + final int yOff, final int leftSize, final int rightSize, final int tabHeight) { + var start = new Point(leftSize > 0 ? leftHeight : 0, yOff); int leftEnd = layoutTabArea(start, left, true, tabHeight - 1); - start.x = rightSize > 0 ? dim.width - tabHeight : dim.width; + start.x = rightSize > 0 ? dim.width - rightHeight : dim.width; int rightStart = layoutTabArea(start, right, false, tabHeight - 1); if (rightStart < leftEnd) { + shift[getIndex(left)] = leftEnd - rightStart; shift(leftEnd - rightStart, right); + } else { + shift[getIndex(left)] = 0; } } @@ -133,19 +184,26 @@ public class TabFrameLayout implements LayoutManager { var leftPane = ui.getLeftContainer(); var topPane = tabFrame.getTopTabContainer(); var bottomPane = tabFrame.getBottomTabContainer(); - int tabWidth = calculateMaxTabSize(Alignment.WEST); - if (leftSize > 0) { + if (leftSize > 0 || draggedOver[getIndex(Alignment.WEST)]) { + int size = leftSize > 0 ? leftHeight : tabFrame.getTabSize(); int height = dim.height - topPane.getHeight() - bottomPane.getHeight(); - leftPane.setBounds(0, topPane.getHeight(), tabWidth, height + (height % 2)); + leftPane.setBounds(0, topPane.getHeight(), size, height + (height % 2)); tabFrame.getLeftTabContainer().setPreferredSize(new Dimension(leftPane.getHeight(), leftPane.getWidth())); tabFrame.getLeftTabContainer().setSize(tabFrame.getLeftTabContainer().getPreferredSize()); - var start = new Point(leftPane.getHeight(), 0); - int topStart = layoutTabArea(start, Alignment.NORTH_WEST, false, tabWidth - 1); - start.x = 0; - int bottomEnd = layoutTabArea(start, Alignment.WEST, true, tabWidth - 1); - if (bottomEnd > topStart) { - shift(topStart - bottomEnd, Alignment.WEST); + if (leftSize > 0) { + var start = new Point(leftPane.getHeight(), 0); + int topStart = layoutTabArea(start, Alignment.NORTH_WEST, false, size - 1); + start.x = 0; + int bottomEnd = layoutTabArea(start, Alignment.WEST, true, size - 1); + if (bottomEnd > topStart) { + shift[getIndex(Alignment.WEST)] = topStart - bottomEnd; + shift(topStart - bottomEnd, Alignment.WEST); + } else { + shift[getIndex(Alignment.WEST)] = 0; + } + } else if (ui.getDestIndex() >= -1) { + layoutVerticalDrop(Alignment.WEST, size); } } else { tabFrame.getLeftTabContainer().setBounds(0, 0, 0, 0); @@ -153,26 +211,45 @@ public class TabFrameLayout implements LayoutManager { } } + protected void layoutVerticalDrop(final Alignment left, final int size) { + var comp = ui.getDropComponent(left); + var a = ui.getDestAlign(); + var dropSize = ui.getDropSize(); + var tabComp = tabFrame.getTabContainer(left); + if (a == left) { + comp.setBounds(0, 0, dropSize.width, size); + } else { + comp.setBounds(tabComp.getWidth() - dropSize.width, 0, dropSize.width, size); + } + } + protected void layoutRightTab(final Dimension dim, final int rightSize) { var rightPane = ui.getRightContainer(); var topPane = tabFrame.getTopTabContainer(); var bottomPane = tabFrame.getBottomTabContainer(); - int tabWidth = calculateMaxTabSize(Alignment.EAST); - if (rightSize > 0) { + if (rightSize > 0 || draggedOver[getIndex(Alignment.EAST)]) { + int size = rightSize > 0 ? rightHeight : tabFrame.getTabSize(); int height = dim.height - topPane.getHeight() - bottomPane.getHeight(); - rightPane.setBounds(dim.width - tabWidth, topPane.getHeight(), tabWidth, height + (height % 2)); + rightPane.setBounds(dim.width - rightHeight, topPane.getHeight(), size, height + (height % 2)); tabFrame.getRightTabContainer().setPreferredSize(new Dimension(rightPane.getHeight(), rightPane.getWidth())); tabFrame.getRightTabContainer().setSize(tabFrame.getRightTabContainer().getPreferredSize()); - var start = new Point(0, 0); - int topEnd = layoutTabArea(start, Alignment.EAST, true, tabWidth - 1); - start.x = tabFrame.getRightTabContainer().getWidth(); - var bottomStart = layoutTabArea(start, Alignment.SOUTH_EAST, false, tabWidth - 1); - if (bottomStart < topEnd) { - shift(topEnd - bottomStart, Alignment.SOUTH_EAST); + if (rightSize > 0) { + var start = new Point(0, 0); + int topEnd = layoutTabArea(start, Alignment.EAST, true, size - 1); + start.x = tabFrame.getRightTabContainer().getWidth(); + var bottomStart = layoutTabArea(start, Alignment.SOUTH_EAST, false, size - 1); + if (bottomStart < topEnd) { + shift[getIndex(Alignment.EAST)] = topEnd - bottomStart; + shift(topEnd - bottomStart, Alignment.SOUTH_EAST); + } else { + shift[getIndex(Alignment.EAST)] = 0; + } + } else if (ui.getDestIndex() >= -1) { + layoutVerticalDrop(Alignment.EAST, size); } } else { tabFrame.getRightTabContainer().setBounds(0, 0, 0, 0); - topPane.setBounds(0, 0, 0, 0); + rightPane.setBounds(0, 0, 0, 0); } } @@ -182,31 +259,102 @@ public class TabFrameLayout implements LayoutManager { pos.x += shift; c.getComponent().setLocation(pos); } + if (a == ui.getDestAlign()) { + var dropComp = ui.getDropComponent(a); + var pos = dropComp.getLocation(); + pos.x += shift; + dropComp.setLocation(pos); + } + } + + public int getShift(@NotNull final Alignment a) { + switch (a) { + case NORTH: + case EAST: + case SOUTH_WEST: + case NORTH_WEST: + return 0; + } + return shift[getIndex(a)]; + } + + protected int getIndex(@NotNull final Alignment a) { + switch (a) { + case NORTH: + case NORTH_EAST: + return 0; + case EAST: + case SOUTH_EAST: + return 1; + case SOUTH: + case SOUTH_WEST: + return 2; + case WEST: + case NORTH_WEST: + return 3; + } + return 0; + } + + public void setDraggedOver(final Alignment a, final boolean b) { + draggedOver[getIndex(a)] = b; } protected int layoutTabArea(@NotNull final Point start, @NotNull final Alignment a, final boolean forward, final int size) { int x = start.x; int y = start.y; + int sourceIndex = a == ui.getSourceAlign() ? ui.getSourceIndex() : -10; + int destIndex = a == ui.getDestAlign() ? ui.getDestIndex() : -10; + var bounds = new Rectangle(0, 0, 0, 0); + int index = 0; + var dropComp = ui.getDropComponent(a); + if (destIndex == -1) { + if (forward) { + dropComp.setBounds(x, y, ui.getDropSize().width, size); + x += ui.getDropSize().width; + } else { + x -= ui.getDropSize().width; + dropComp.setBounds(x, y, ui.getDropSize().width, size); + } + } for (var c : tabFrame.tabsForAlignment(a)) { - bounds.width = getTabWidth(c.getComponent()); + index = c.getIndex(); + bounds.width = index == sourceIndex ? 0 : getTabWidth(c.getComponent()); bounds.height = size; if (forward) { bounds.x = x; bounds.y = y; x += bounds.width; + if (index == destIndex) { + dropComp.setBounds(x, y, ui.getDropSize().width, size); + x += ui.getDropSize().width; + } } else { x -= bounds.width; bounds.x = x; bounds.y = y; + if (index == destIndex) { + x -= ui.getDropSize().width; + dropComp.setBounds(x, y, ui.getDropSize().width, size); + } } c.getComponent().setBounds(bounds); } + if (destIndex == index + 1) { + if (forward) { + dropComp.setBounds(x, y, ui.getDropSize().width, size); + x += ui.getDropSize().width; + } else { + x -= ui.getDropSize().width; + dropComp.setBounds(x, y, ui.getDropSize().width, size); + } + } return x; } - protected int getTabWidth(@NotNull final Component c) { + public int getTabWidth(@NotNull final Component c) { int maxWidth = tabFrame.getMaxTabWidth(); int width = c.getPreferredSize().width; if (maxWidth < 0) { @@ -226,4 +374,12 @@ public class TabFrameLayout implements LayoutManager { } return max; } + + public void setDraggedOver(final boolean b) { + Arrays.fill(draggedOver, b); + } + + public boolean isDraggedOver(final Alignment a) { + return draggedOver[getIndex(a)]; + } } diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/TabFrameTransferHandler.java b/src/main/java/com/weis/darklaf/ui/tabframe/TabFrameTransferHandler.java new file mode 100644 index 00000000..01f90839 --- /dev/null +++ b/src/main/java/com/weis/darklaf/ui/tabframe/TabFrameTransferHandler.java @@ -0,0 +1,508 @@ +/* + * MIT License + * + * Copyright (c) 2019 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.weis.darklaf.ui.tabframe; + +import com.weis.darklaf.components.alignment.Alignment; +import com.weis.darklaf.components.tabframe.JTabFrame; +import com.weis.darklaf.components.tabframe.TabFramePopup; +import com.weis.darklaf.components.tabframe.TabFrameTab; +import com.weis.darklaf.components.tabframe.TabFrameUI; +import com.weis.darklaf.util.ImageUtil; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragGestureRecognizer; +import java.awt.dnd.DragSource; +import java.awt.dnd.DragSourceContext; +import java.awt.dnd.DragSourceDragEvent; +import java.awt.dnd.DragSourceDropEvent; +import java.awt.dnd.DragSourceEvent; +import java.awt.dnd.DragSourceListener; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetListener; +import java.awt.event.InputEvent; +import java.awt.event.MouseEvent; + + +/** + * @author Robert Futrell + * @author Jannis Weis + */ +public class TabFrameTransferHandler extends TransferHandler implements DropTargetListener, SwingConstants { + + private static final String MIME_TYPE = DataFlavor.javaJVMLocalObjectMimeType + + ";class=com.weis.darklaf.components.tabframe.TabFrame"; + private static TabbedPaneDragGestureRecognizer recognizer = null; + private final Timer timer; + private final Timer startTimer; + /** + * The location of the mouse cursor throughout the drag-and-drop. This is here because of a deficiency in + * TransferHandler's design; you have no way of knowing the exact drop location in the component with a plain + * TransferHandler unless you implement DropTargetListener and get it that way. + */ + protected Point mouseLocation; + private DataFlavor tabFlavor; + private TabTransferable currentTransferable; + private JTabFrame lastTabFrame; + + + public TabFrameTransferHandler() { + try { + tabFlavor = new DataFlavor(MIME_TYPE); + } catch (ClassNotFoundException ignored) { + } + timer = new Timer(100, e -> { + if (lastTabFrame != null) { + var p = MouseInfo.getPointerInfo().getLocation(); + SwingUtilities.convertPointFromScreen(p, lastTabFrame); + var evt = new DropTargetDragEvent(lastTabFrame.getDropTarget().getDropTargetContext(), p, MOVE, MOVE); + dragOver(evt); + } + }); + timer.setRepeats(true); + startTimer = new Timer(200, e -> { + /* + * Drag Exit can be funky. Ensure that the timer is really running. + */ + if (!timer.isRunning()) { + timer.start(); + } + }); + startTimer.setRepeats(false); + } + + @Contract("null -> null") + private TabFrameUI getUI(final Component c) { + if (c instanceof JTabFrame) return ((JTabFrame) c).getUI(); + return null; + } + + protected JTabFrame.TabFramePosition getDropPosition(final Point p, final JTabFrame tabFrame) { + return getUI(tabFrame).getDropPosition(tabFrame, p); + } + + public void exportAsDrag(final JComponent comp, final InputEvent e, final int a) { + int srcActions = getSourceActions(comp); + int action = a; + + // only mouse events supported for drag operations + if (!(e instanceof MouseEvent) + // only support known actions + || !(action == COPY || action == MOVE || action == LINK) + // only support valid source actions + || (srcActions & action) == 0) { + + action = NONE; + } + + if (action != NONE && !GraphicsEnvironment.isHeadless()) { + if (recognizer == null) { + recognizer = new TabbedPaneDragGestureRecognizer(new TabbedPaneDragHandler()); + } + recognizer.gestured(comp, (MouseEvent) e, srcActions, action); + } else { + exportDone(comp, null, NONE); + } + } + + /** + * Called when the drag-and-drop operation has just completed. This creates a new tab identical to the one + * "dragged" and places it in the destination JTabbedPane. + * + * @param c The component receiving the "drop" (the instance of + * JTabbedPane). + * @param t The data being transfered (information about the tab and the component contained by the tab). + * @return Whether or not the import was successful. + */ + @Override + public boolean importData(final JComponent c, @NotNull final Transferable t) { + boolean successful = false; + if (hasTabFlavor(t.getTransferDataFlavors()) && mouseLocation != null) { + try { + JTabFrame tabFrame = (JTabFrame) c; + var tab = getDropPosition(mouseLocation, tabFrame); + Alignment a = tab.getAlignment(); + int index = tab.getIndex(); + TabTransferable.TabTransferData td = (TabTransferable.TabTransferData) t.getTransferData(tabFlavor); + + if (tabFrame == td.sourceTabFrame && td.tabAlignment == a) { + if (index >= td.tabIndex) { + index--; + } + } + index++; + + if (tabFrame == td.sourceTabFrame && a == td.tabAlignment && index == td.tabIndex) { + //Nothing to do. Just select the tab to be sure. + if (td.wasSelected) { + selectTab(td.sourceTabFrame, a, index); + } + return false; + } + if (a == null || index < 0 || index > tabFrame.getTabCountAt(a)) { + return false; + } + var tabComp = td.sourceTabFrame.getTabComponentAt(td.tabAlignment, td.tabIndex); + var popupComp = td.sourceTabFrame.getPopupComponentAt(td.tabAlignment, td.tabIndex); + td.sourceTabFrame.removeTab(td.tabAlignment, td.tabIndex); + tabFrame.insertTab((TabFramePopup) popupComp, tabComp, a, index); + tabFrame.toggleTab(a, index, td.wasSelected); + SwingUtilities.invokeLater(() -> td.tab.getComponent().repaint()); + + successful = true; + var ui = getUI(c); + if (ui != null) { + ui.clearTargetIndicator(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return successful; + } + + /** + * Overridden to include a check for a TabData flavor. + */ + @Override + public boolean canImport(final JComponent c, final DataFlavor[] flavors) { + return hasTabFlavor(flavors); + } + + /** + * We can only move tabs, we cannot copy them. + * + * @param c This parameter is ignored. + * @return TransferHandler.MOVE, as we can only move tabs. + */ + @Override + public int getSourceActions(final JComponent c) { + return MOVE; + } + + protected boolean hasTabFlavor(final DataFlavor[] flavors) { + if (tabFlavor == null) { + return false; + } + for (DataFlavor flavor : flavors) { + if (tabFlavor.equals(flavor)) { + return true; + } + } + return false; + } + + protected void selectTab(final JTabFrame tabbedPane, final Alignment a, final int index) { + SwingUtilities.invokeLater(() -> tabbedPane.toggleTab(a, index, true)); + } + + protected Transferable createTransferable(final JComponent c, @NotNull final DragGestureEvent dge) { + JTabFrame tabFrame = (JTabFrame) c; + if (tabFrame.isInTransfer()) { + currentTransferable = new TabTransferable(tabFrame, tabFrame.getTransferInfo()); + } else { + var ind = getUI(tabFrame).getTabIndexAt(tabFrame, dge.getDragOrigin()); + tabFrame.initTransfer(ind.getAlignment(), ind.getIndex()); + currentTransferable = new TabTransferable(tabFrame, ind); + } + var ui = getUI(c); + createDragImage(ui); + var a = currentTransferable.transferData.tabAlignment; + int index = currentTransferable.transferData.tabIndex; + ui.setSourceIndicator(a, index); + startTimer.start(); + lastTabFrame = currentTransferable.transferData.sourceTabFrame; + return currentTransferable; + } + + protected void createDragImage(@NotNull final TabFrameUI ui) { + var comp = currentTransferable.transferData.tab.getComponent(); + Image tabImage = ImageUtil.scaledImageFromComponent(comp, new Rectangle(0, 0, comp.getWidth(), + comp.getHeight())); + int w = tabImage.getWidth(null); + int h = tabImage.getHeight(null); + var g = tabImage.getGraphics(); + + g.setColor(ui.getDragBorderColor()); + + int lw = 2; + g.fillRect(0, 0, w, lw); + g.fillRect(0, 0, lw, h); + g.fillRect(w - lw, 0, lw, h); + g.fillRect(0, h - lw, w, lw); + g.dispose(); + + setDragImageOffset(new Point(w / 2, h / 2)); + setDragImage(tabImage); + } + + @Override + public void dragEnter(final DropTargetDragEvent e) { + timer.stop(); + startTimer.stop(); + } + + @Override + public void dragOver(@NotNull final DropTargetDragEvent e) { + e.getDropTargetContext().getComponent().setCursor(Cursor.getDefaultCursor()); + mouseLocation = e.getLocation(); + + Component c = e.getDropTargetContext().getComponent(); + JTabFrame destTabFrame = (JTabFrame) c; + + var ui = getUI(destTabFrame); + if (ui != null) { + TabTransferable t = currentTransferable; + if (t != null) { + var tab = getDropPosition(mouseLocation, destTabFrame); + if (tab.getAlignment() == null) { + ui.clearTargetIndicator(); + } else { + try { + var sourceTab = currentTransferable.transferData.sourceTabFrame; + var sourceIndex = currentTransferable.transferData.tabIndex; + var sourceAlign = currentTransferable.transferData.tabAlignment; + int w = getUI(sourceTab).getTabWidth(sourceTab, sourceAlign, sourceIndex); + int h = getUI(sourceTab).getTabHeight(sourceTab, sourceAlign, sourceIndex); + ui.setDropSize(w, h); + ui.setTargetIndicator(tab.getAlignment(), tab.getIndex()); + } catch (IndexOutOfBoundsException ex) { + ui.clearTargetIndicator(); + } + } + } + } + lastTabFrame = destTabFrame; + startTimer.restart(); + } + + @Override + public void dropActionChanged(final DropTargetDragEvent e) { + } + + @Override + public void dragExit(@NotNull final DropTargetEvent e) { + Component c = e.getDropTargetContext().getComponent(); + var ui = getUI(c); + if (ui != null) { + ui.clearTargetIndicator(); + } + lastTabFrame = (JTabFrame) c; + startTimer.start(); + } + + @Override + public void drop(@NotNull final DropTargetDropEvent e) { + Component c = e.getDropTargetContext().getComponent(); + var ui = getUI(c); + if (ui != null) { + ui.clearTargetIndicator(); + } + timer.stop(); + startTimer.stop(); + } + + protected static class TabbedPaneDragGestureRecognizer extends DragGestureRecognizer { + + protected TabbedPaneDragGestureRecognizer(final DragGestureListener dgl) { + super(DragSource.getDefaultDragSource(), null, NONE, dgl); + } + + void gestured(final JComponent c, final MouseEvent e, final int srcActions, final int action) { + setComponent(c); + setSourceActions(srcActions); + appendEvent(e); + fireDragGestureRecognized(action, e.getPoint()); + } + + /** + * register this DragGestureRecognizer's Listeners with the Component + */ + protected void registerListeners() { + } + + /** + * unregister this DragGestureRecognizer's Listeners with the Component + *

+ * subclasses must override this method + */ + protected void unregisterListeners() { + } + } + + public static class UIResource extends TabFrameTransferHandler { + + } + + /** + * Transferable representing a tab from a tabbed pane and its contents. + */ + public class TabTransferable implements Transferable { + + private final TabTransferData transferData; + + public TabTransferable(@NotNull final JTabFrame tabFrame, @NotNull final JTabFrame.TabFramePosition ind) { + transferData = new TabTransferData(tabFrame, ind.getAlignment(), ind.getIndex()); + } + + public TabFrameTab getTab() { + return transferData.tab; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[]{tabFlavor}; + } + + @Override + public boolean isDataFlavorSupported(final DataFlavor flavor) { + return tabFlavor.equals(flavor); + } + + @NotNull + @Override + public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) { + throw new UnsupportedFlavorException(flavor); + } + return transferData; + } + + /** + * The data remembered about the tab. + */ + public class TabTransferData { + + private final JTabFrame sourceTabFrame; + private final int tabIndex; + private final Alignment tabAlignment; + private final TabFrameTab tab; + private final boolean wasSelected; + + @Contract(pure = true) + public TabTransferData(@NotNull final JTabFrame tabbedPane, final Alignment tabAlignment, + final int tabIndex) { + this.sourceTabFrame = tabbedPane; + this.tabAlignment = tabAlignment; + this.tabIndex = tabIndex; + this.tab = tabbedPane.getTabComponentAt(tabAlignment, tabIndex); + this.wasSelected = tab.isSelected(); + } + + } + + } + + protected class TabbedPaneDragHandler implements DragGestureListener, DragSourceListener { + + private boolean scrolls; + + // --- DragGestureListener methods ----------------------------------- + + /** + * a Drag gesture has been recognized + */ + public void dragGestureRecognized(@NotNull final DragGestureEvent dge) { + JComponent c = (JComponent) dge.getComponent(); + TabFrameTransferHandler th = (TabFrameTransferHandler) c.getTransferHandler(); + Transferable t = th.createTransferable(c, dge); + if (t != null) { + scrolls = c.getAutoscrolls(); + c.setAutoscrolls(false); + try { + Image im = th.getDragImage(); + if (im == null) { + dge.startDrag(Cursor.getDefaultCursor(), t, this); + } else { + dge.startDrag(Cursor.getDefaultCursor(), im, th.getDragImageOffset(), t, this); + } + return; + } catch (RuntimeException re) { + c.setAutoscrolls(scrolls); + } + } + + th.exportDone(c, t, NONE); + } + + // --- DragSourceListener methods ----------------------------------- + + /** + * as the hotspot enters a platform dependent drop site + */ + public void dragEnter(final DragSourceDragEvent dsde) { + } + + /** + * as the hotspot moves over a platform dependent drop site + */ + public void dragOver(final DragSourceDragEvent dsde) { + } + + public void dropActionChanged(final DragSourceDragEvent dsde) { + } + + /** + * as the hotspot exits a platform dependent drop site + */ + public void dragExit(final DragSourceEvent dsde) { + } + + /** + * as the operation completes + */ + public void dragDropEnd(@NotNull final DragSourceDropEvent dsde) { + DragSourceContext dsc = dsde.getDragSourceContext(); + JComponent c = (JComponent) dsc.getComponent(); + if (dsde.getDropSuccess()) { + ((TabFrameTransferHandler) c.getTransferHandler()).exportDone(c, dsc.getTransferable(), + dsde.getDropAction()); + } else { + ((TabFrameTransferHandler) c.getTransferHandler()).exportDone(c, dsc.getTransferable(), NONE); + } + c.setAutoscrolls(scrolls); + + var ui = getUI(currentTransferable.transferData.sourceTabFrame); + if (ui != null) { + ui.clearSourceIndicator(); + } + if (!dsde.getDropSuccess() && currentTransferable.transferData.wasSelected) { + selectTab(currentTransferable.transferData.sourceTabFrame, + currentTransferable.transferData.tabAlignment, + currentTransferable.transferData.tabIndex); + } + currentTransferable.transferData.sourceTabFrame.endTransfer(); + currentTransferable = null; + } + } +} diff --git a/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java b/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java index 67bd1760..ca35b19e 100644 --- a/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java +++ b/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java @@ -55,8 +55,7 @@ public class DarkTooltipBorder implements Border, UIResource { return; } if (c instanceof JToolTip && ((JToolTip) c).getTipText() == null) return; - g.setColor(Color.RED); - g.drawRect(x, y, width, height); + System.out.println(bubbleBorder.getPointerSide()); var ins = shadowBorder.getBorderInsets(c); adjustInsets(ins); var bubbleArea = bubbleBorder.getInnerArea(x + ins.left, y + ins.top, diff --git a/src/main/java/com/weis/darklaf/util/PropertyLoader.java b/src/main/java/com/weis/darklaf/util/PropertyLoader.java index b90febbb..65fb7f92 100644 --- a/src/main/java/com/weis/darklaf/util/PropertyLoader.java +++ b/src/main/java/com/weis/darklaf/util/PropertyLoader.java @@ -40,6 +40,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -62,13 +63,15 @@ public final class PropertyLoader { private static final Collection objectsToLoad = new HashSet<>(); public static void finish() { + var cache = new HashMap(); for (var request : objectsToLoad) { try { - request.resolve(); + request.resolve(cache); } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Could not load" + request, e.getMessage()); } } + cache.clear(); reset(); } @@ -268,20 +271,26 @@ public final class PropertyLoader { this.value = value; } - private void resolve() { - Object obj = parseObject(value); + private void resolve(@NotNull final Map cache) { var defaults = UIManager.getLookAndFeelDefaults(); - if (obj == null) { - obj = parseValue(key, value, true, defaults); - if (obj instanceof ObjectRequest) { - LOGGER.severe("Failed to resolve object. " + this); - return; - } - } - if (obj == null) { - defaults.remove(key); + if (cache.containsKey(value)) { + defaults.put(key, cache.get(value)); } else { - defaults.put(key, obj); + Object obj = parseObject(value); + if (obj == null) { + obj = parseValue(key, value, true, defaults); + if (obj instanceof ObjectRequest) { + LOGGER.severe("Failed to resolve object. " + this); + return; + } + } else { + cache.put(value, obj); + } + if (obj == null) { + defaults.remove(key); + } else { + defaults.put(key, obj); + } } } diff --git a/src/main/java/com/weis/darklaf/util/SwingXUtilities.java b/src/main/java/com/weis/darklaf/util/SwingXUtilities.java new file mode 100644 index 00000000..08569fb0 --- /dev/null +++ b/src/main/java/com/weis/darklaf/util/SwingXUtilities.java @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2019 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.weis.darklaf.util; + +import org.jdesktop.jxlayer.JXLayer; +import org.jetbrains.annotations.NotNull; +import org.pbjar.jxlayer.plaf.ext.TransformUI; + +import javax.swing.*; +import java.awt.*; + +public class SwingXUtilities { + + @NotNull + public static Point convertPointToParent(final Component source, final Point p) { + JXLayer layer = DarkUIUtil.getParentOfType(JXLayer.class, source); + if (layer != null && layer.getUI() instanceof TransformUI) { + var ui = (TransformUI) layer.getUI(); + var pos = SwingUtilities.convertPoint(source, p, layer); + //noinspection unchecked + var transform = ui.getPreferredTransform(layer.getSize(), layer); + transform.transform(pos, pos); + return pos; + } + return SwingUtilities.convertPoint(source, p, source.getParent()); + } +} diff --git a/src/main/java/org/pbjar/jxlayer/plaf/ext/MouseEventUI.java b/src/main/java/org/pbjar/jxlayer/plaf/ext/MouseEventUI.java index 7d8efc1a..41f46dcc 100644 --- a/src/main/java/org/pbjar/jxlayer/plaf/ext/MouseEventUI.java +++ b/src/main/java/org/pbjar/jxlayer/plaf/ext/MouseEventUI.java @@ -244,16 +244,6 @@ public class MouseEventUI extends AbstractLayerUI { if (mouseEvent != null) { Component target = mouseEvent.getComponent(); target.dispatchEvent(mouseEvent); - /* - * Used to check the re dispatching behavior - */ - // switch (mouseEvent.getID()) { - // case (MouseEvent.MOUSE_PRESSED): - // System.out.println(); - // case (MouseEvent.MOUSE_RELEASED): - // case (MouseEvent.MOUSE_CLICKED): - // System.out.println("Dispatched mouse event " + mouseEvent); - // } } } diff --git a/src/main/java/org/pbjar/jxlayer/repaint/RepaintManagerUtils.java b/src/main/java/org/pbjar/jxlayer/repaint/RepaintManagerUtils.java index d9620142..931dc88f 100644 --- a/src/main/java/org/pbjar/jxlayer/repaint/RepaintManagerUtils.java +++ b/src/main/java/org/pbjar/jxlayer/repaint/RepaintManagerUtils.java @@ -133,7 +133,6 @@ public final class RepaintManagerUtils { @NotNull final Class clazz, final RepaintManager delegate) { try { RepaintManager newManager = clazz.getConstructor(RepaintManager.class).newInstance(delegate); - System.out.println("Created " + newManager.getClass().getName()); return newManager; } catch (Throwable t) { throw new RuntimeException("Cannot instantiate " + clazz.getName(), t); diff --git a/src/main/resources/com/weis/darklaf/properties/ui/tabFrame.properties b/src/main/resources/com/weis/darklaf/properties/ui/tabFrame.properties index 4db9f266..403ec928 100644 --- a/src/main/resources/com/weis/darklaf/properties/ui/tabFrame.properties +++ b/src/main/resources/com/weis/darklaf/properties/ui/tabFrame.properties @@ -59,6 +59,8 @@ TabFrameUI = com.weis.darklaf.ui.tabframe. TabFrame.line = %borderSecondary TabFrame.tabHeight = 24 TabFrame.acceleratorKeyCode = alt pressed +TabFrame.dropBackground = %dropBackground +TabFrame.dragBorderColor = %borderTertiary #Icons TabFramePopup.close.icon = navigation/collapse.svg[aware] diff --git a/src/main/resources/com/weis/darklaf/theme/intellij/intellij_defaults.properties b/src/main/resources/com/weis/darklaf/theme/intellij/intellij_defaults.properties index 0b66e165..f138cc4c 100644 --- a/src/main/resources/com/weis/darklaf/theme/intellij/intellij_defaults.properties +++ b/src/main/resources/com/weis/darklaf/theme/intellij/intellij_defaults.properties @@ -38,7 +38,7 @@ %backgroundHoverColorful = CCCFD5 %backgroundSelectedColorful = c5cddc -%dropBackground = F7F6F0 +%dropBackground = FCFAED %dropForeground = C0C0C0 ####Border#### diff --git a/src/test/java/TabFrameDemo.java b/src/test/java/TabFrameDemo.java index f22d1619..0937e933 100644 --- a/src/test/java/TabFrameDemo.java +++ b/src/test/java/TabFrameDemo.java @@ -1,7 +1,7 @@ import com.weis.darklaf.LafManager; import com.weis.darklaf.components.OverlayScrollPane; import com.weis.darklaf.components.alignment.Alignment; -import com.weis.darklaf.components.tabframe.TabFrame; +import com.weis.darklaf.components.tabframe.JTabFrame; import com.weis.darklaf.components.tabframe.TabbedPopup; import com.weis.darklaf.icons.IconLoader; @@ -42,7 +42,7 @@ public class TabFrameDemo { frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - var tabFrame = new TabFrame(); + var tabFrame = new JTabFrame(); for (var o : Alignment.values()) { if (o != Alignment.CENTER) { for (int i = 0; i < 2; i++) { @@ -61,7 +61,6 @@ public class TabFrameDemo { panel.add(label); tabbedPopup.getTabbedPane().addTab("Tab " + i, panel); } - /* Activate for custom tab demo. tabFrame.setUserTabComponentAt(new JLabel("NORTH (custom tab)") {{ setBorder(new EmptyBorder(0, 5, 0, 5)); diff --git a/src/test/java/ToolBarDemo.java b/src/test/java/ToolBarDemo.java index 1b57a47c..70e248b2 100644 --- a/src/test/java/ToolBarDemo.java +++ b/src/test/java/ToolBarDemo.java @@ -37,7 +37,6 @@ */ import com.weis.darklaf.LafManager; -import com.weis.darklaf.theme.Theme; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -109,7 +108,6 @@ public class ToolBarDemo extends JPanel implements ActionListener { button.setIcon(new ImageIcon(imageURL, altText)); } else { //no image found button.setText(altText); - System.err.println("Resource not found: " + imgLocation); } return button; diff --git a/src/test/java/UIDemo.java b/src/test/java/UIDemo.java index 1f3dc82c..9f1db0a8 100644 --- a/src/test/java/UIDemo.java +++ b/src/test/java/UIDemo.java @@ -1,5 +1,6 @@ import com.weis.darklaf.LafManager; -import com.weis.darklaf.components.TextFieldHistory; +import com.weis.darklaf.components.text.SearchTextField; +import com.weis.darklaf.components.text.SearchTextFieldWithHistory; import com.weis.darklaf.components.tristate.TristateCheckBox; import com.weis.darklaf.icons.IconLoader; import org.jdesktop.swingx.JXStatusBar; @@ -141,14 +142,8 @@ public final class UIDemo { add(new JTextField("TextField") {{ putClientProperty("JTextField.alternativeArc", Boolean.TRUE); }}); - add(new JTextField("SearchField") {{ - putClientProperty("JTextField.variant", "search"); - }}); - add(new JTextField("SearchFieldWithHistory") {{ - putClientProperty("JTextField.variant", "search"); - putClientProperty("JTextField.Search.FindPopup", - new TextFieldHistory(this, 50, 100)); - }}); + add(new SearchTextField("SearchField")); + add(new SearchTextFieldWithHistory("SearchFieldWithHistory")); add(new JPasswordField("Password")); add(new JPasswordField("VeryStrongPassword") {{ putClientProperty("JTextField.alternativeArc", Boolean.TRUE);