Browse Source

Added numbered text component.

Signed-off-by: weisj <weisj@arcor.de>
pull/15/head
weisj 5 years ago
parent
commit
687a7e3c89
  1. 1
      src/main/java/com/github/weisj/darklaf/components/OverlayScrollPane.java
  2. 9
      src/main/java/com/github/weisj/darklaf/components/tabframe/ToggleSplitPane.java
  3. 103
      src/main/java/com/github/weisj/darklaf/components/text/LineHighlighter.java
  4. 56
      src/main/java/com/github/weisj/darklaf/components/text/NonWrappingEditorPane.java
  5. 51
      src/main/java/com/github/weisj/darklaf/components/text/NonWrappingTextArea.java
  6. 56
      src/main/java/com/github/weisj/darklaf/components/text/NonWrappingTextPane.java
  7. 49
      src/main/java/com/github/weisj/darklaf/components/text/NumberedTextComponent.java
  8. 117
      src/main/java/com/github/weisj/darklaf/components/text/NumberingPane.java
  9. 58
      src/main/java/com/github/weisj/darklaf/icons/ScaledIcon.java
  10. 2
      src/main/java/com/github/weisj/darklaf/theme/Theme.java
  11. 37
      src/main/java/com/github/weisj/darklaf/ui/numberingpane/DarkNumberingPaneBorder.java
  12. 342
      src/main/java/com/github/weisj/darklaf/ui/numberingpane/DarkNumberingPaneUI.java
  13. 28
      src/main/java/com/github/weisj/darklaf/ui/rootpane/DarkTitlePane.java
  14. 6
      src/main/java/com/github/weisj/darklaf/ui/splitpane/DarkSplitPaneUI.java
  15. 121
      src/main/java/com/github/weisj/darklaf/ui/text/DarkCaret.java
  16. 8
      src/main/java/com/github/weisj/darklaf/util/Pair.java
  17. 31
      src/main/resources/com/github/weisj/darklaf/properties/ui/numberingPane.properties
  18. 2
      src/test/java/ScrollPaneDemo.java
  19. 31
      src/test/java/TabFrameDemo.java
  20. 42
      src/test/java/TestResources.java

1
src/main/java/com/github/weisj/darklaf/components/OverlayScrollPane.java

@ -301,6 +301,7 @@ public class OverlayScrollPane extends JLayeredPane {
&& scrollPane.verticalScrollBar.getBounds().contains(x, y);
}
@Contract(pure = true)
@Override
public boolean isOpaque() {
return false;

9
src/main/java/com/github/weisj/darklaf/components/tabframe/ToggleSplitPane.java

@ -24,6 +24,7 @@
package com.github.weisj.darklaf.components.tabframe;
import javax.swing.*;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;
public class ToggleSplitPane extends JSplitPane {
@ -61,11 +62,19 @@ public class ToggleSplitPane extends JSplitPane {
setEnabled(false);
disabledPos = super.getDividerLocation();
disabledMax = getMaximumDividerLocation();
getDivider().setEnabled(false);
setComponentZOrder(getDivider(), getComponentCount() - 1);
} else {
setEnabled(lastEnabled);
getDivider().setEnabled(lastEnabled);
setComponentZOrder(getDivider(), 0);
}
}
protected BasicSplitPaneDivider getDivider() {
return ((BasicSplitPaneUI) getUI()).getDivider();
}
@Override
public void setEnabled(final boolean enabled) {
((BasicSplitPaneUI) getUI()).getDivider().setEnabled(enabled);

103
src/main/java/com/github/weisj/darklaf/components/text/LineHighlighter.java

@ -0,0 +1,103 @@
/*
* 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.github.weisj.darklaf.components.text;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import java.awt.*;
public class LineHighlighter implements Highlighter.HighlightPainter, ChangeListener {
private JTextComponent component;
private Color color;
private Rectangle lastView;
/**
* Manually control the line color
*
* @param component text component that requires background line painting
* @param color the color of the background line
*/
public LineHighlighter(final JTextComponent component, final Color color) {
this.component = component;
setColor(color);
}
/*
* You can reset the line color at any time
*
* @param color the color of the background line
*/
public void setColor(final Color color) {
this.color = color;
}
public void paint(@NotNull final Graphics g, final int p0, final int p1, final Shape bounds,
@NotNull final JTextComponent c) {
try {
Rectangle r = c.modelToView2D(c.getCaretPosition()).getBounds();
g.setColor(color);
g.fillRect(0, r.y, c.getWidth(), r.height);
if (lastView == null) { lastView = r; }
} catch (BadLocationException ble) {
ble.printStackTrace();
}
}
public void setComponent(final JTextComponent component) {
this.component = component;
}
@Override
public void stateChanged(final ChangeEvent e) {
resetHighlight();
}
/*
* Caret position has changed, remove the highlight
*/
private void resetHighlight() {
// Use invokeLater to make sure updates to the Document are completed,
// otherwise Undo processing causes the modelToView method to loop.
if (component == null) return;
SwingUtilities.invokeLater(() -> {
try {
int offset = component.getCaretPosition();
Rectangle currentView = component.modelToView2D(offset).getBounds();
// Remove the highlighting from the previously highlighted line
if (lastView.y != currentView.y) {
component.repaint(0, lastView.y, component.getWidth(), lastView.height);
lastView = currentView;
}
} catch (BadLocationException ignored) {}
});
}
}

56
src/main/java/com/github/weisj/darklaf/components/text/NonWrappingEditorPane.java

@ -0,0 +1,56 @@
/*
* 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.github.weisj.darklaf.components.text;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
/**
* JTextPane that keeps the original width of text.
*
* @author Jannis Weis
* @since 2018
*/
public class NonWrappingEditorPane extends JEditorPane {
@Override
public Dimension getPreferredSize() {
// Avoid substituting the minimum width for the preferred width
// when the viewport is too narrow.
return getUI().getPreferredSize(this);
}
@Override
public boolean getScrollableTracksViewportWidth() {
final Component parent = getParent();
final ComponentUI ui = getUI();
return parent == null || (ui.getPreferredSize(this).width <= parent.getSize().width);
}
@Override
public int getScrollableUnitIncrement(final Rectangle visibleRect, final int orientation, final int direction) {
return getFont().getSize();
}
}

51
src/main/java/com/github/weisj/darklaf/components/text/NonWrappingTextArea.java

@ -0,0 +1,51 @@
/*
* 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.github.weisj.darklaf.components.text;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
/**
* JTextPane that keeps the original width of text.
*
* @author Jannis Weis
* @since 2018
*/
public class NonWrappingTextArea extends JTextArea {
@Override
public Dimension getPreferredSize() {
// Avoid substituting the minimum width for the preferred width
// when the viewport is too narrow.
return getUI().getPreferredSize(this);
}
@Override
public boolean getScrollableTracksViewportWidth() {
final Component parent = getParent();
final ComponentUI ui = getUI();
return parent == null || (ui.getPreferredSize(this).width <= parent.getSize().width);
}
}

56
src/main/java/com/github/weisj/darklaf/components/text/NonWrappingTextPane.java

@ -0,0 +1,56 @@
/*
* 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.github.weisj.darklaf.components.text;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
/**
* JTextPane that keeps the original width of text.
*
* @author Jannis Weis
* @since 2018
*/
public class NonWrappingTextPane extends JTextPane {
@Override
public Dimension getPreferredSize() {
// Avoid substituting the minimum width for the preferred width
// when the viewport is too narrow.
return getUI().getPreferredSize(this);
}
@Override
public boolean getScrollableTracksViewportWidth() {
final Component parent = getParent();
final ComponentUI ui = getUI();
return parent == null || (ui.getPreferredSize(this).width <= parent.getSize().width);
}
@Override
public int getScrollableUnitIncrement(final Rectangle visibleRect, final int orientation, final int direction) {
return getFont().getSize();
}
}

49
src/main/java/com/github/weisj/darklaf/components/text/NumberedTextComponent.java

@ -0,0 +1,49 @@
/*
* 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.github.weisj.darklaf.components.text;
import com.github.weisj.darklaf.components.OverlayScrollPane;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
public class NumberedTextComponent extends JPanel {
private NumberingPane numberingPane;
public NumberedTextComponent(final JTextComponent textComponent) {
super(new BorderLayout());
OverlayScrollPane overlayScrollPane = new OverlayScrollPane(textComponent);
numberingPane = new NumberingPane();
numberingPane.setTextComponent(textComponent);
overlayScrollPane.getVerticalScrollBar().setBlockIncrement(textComponent.getFont().getSize());
overlayScrollPane.getScrollPane().setRowHeaderView(numberingPane);
add(overlayScrollPane, BorderLayout.CENTER);
}
public NumberingPane getNumberingPane() {
return numberingPane;
}
}

117
src/main/java/com/github/weisj/darklaf/components/text/NumberingPane.java

@ -0,0 +1,117 @@
/*
* 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.github.weisj.darklaf.components.text;
import javax.swing.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.Segment;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class NumberingPane extends JComponent {
private JTextComponent textComponent;
private Map<Position, Icon> iconMap;
public NumberingPane() {
iconMap = new HashMap<>();
updateUI();
}
@Override
public void updateUI() {
setUI(UIManager.getUI(this));
}
@Override
public String getUIClassID() {
return "NumberingPaneUI";
}
public JTextComponent getTextComponent() {
return textComponent;
}
public void setTextComponent(final JTextComponent textComponent) {
var old = this.textComponent;
this.textComponent = textComponent;
firePropertyChange("editorPane", old, textComponent);
}
public int getIconCount() {
return iconMap.size();
}
public List<Map.Entry<Position, Icon>> getIconsInRange(final int startOff, final int endOff) {
return iconMap.entrySet().stream()
.filter(e -> {
var pos = e.getKey();
return pos.getOffset() >= startOff && pos.getOffset() <= endOff;
})
.collect(Collectors.toList());
}
public Position addIconAtLine(final int lineIndex, final Icon icon) throws BadLocationException {
return addIconAtLine(lineIndex, icon, true);
}
public Position addIconAtLine(final int lineIndex, final Icon icon, final boolean atTextStart) throws BadLocationException {
int offset = textComponent.getDocument().getDefaultRootElement().getElement(lineIndex).getStartOffset();
if (atTextStart) {
var doc = textComponent.getDocument();
var txt = new Segment();
txt.setPartialReturn(true);
String str = doc.getText(offset, 1);
while (str.isBlank()) {
offset++;
str = doc.getText(offset, 1);
}
}
return addIconAtOffset(offset, icon);
}
public Position addIconAtOffset(final int offset, final Icon icon) throws BadLocationException {
var doc = textComponent.getDocument();
var pos = doc.createPosition(offset);
if (icon != null) {
iconMap.put(pos, icon);
}
firePropertyChange("icons", null, icon);
return pos;
}
public Collection<Icon> getIcons() {
return iconMap.values();
}
public void removeIconAt(final Position position) {
var icon = iconMap.remove(position);
firePropertyChange("icons", icon, icon);
}
}

58
src/main/java/com/github/weisj/darklaf/icons/ScaledIcon.java

@ -0,0 +1,58 @@
/*
* 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.github.weisj.darklaf.icons;
import com.github.weisj.darklaf.util.GraphicsUtil;
import org.jetbrains.annotations.Contract;
import javax.swing.*;
import java.awt.*;
public class ScaledIcon implements Icon {
private Image img;
@Contract(pure = true)
public ScaledIcon(final Image img) {
this.img = img;
}
@Override
public void paintIcon(final Component c, final Graphics g2, final int x, final int y) {
var g = (Graphics2D) g2;
g.translate(x, y);
g.scale(1.0 / GraphicsUtil.SCALE_X, 1.0 / GraphicsUtil.SCALE_Y);
g.drawImage(img, 0, 0, img.getWidth(null), img.getHeight(null), null);
}
@Override
public int getIconWidth() {
return (int) (img.getWidth(null) / GraphicsUtil.SCALE_X);
}
@Override
public int getIconHeight() {
return (int) (img.getHeight(null) / GraphicsUtil.SCALE_Y);
}
}

2
src/main/java/com/github/weisj/darklaf/theme/Theme.java

@ -48,7 +48,7 @@ public abstract class Theme {
private static final Logger LOGGER = Logger.getLogger(Theme.class.getName());
private static final String[] UI_PROPERTIES = new String[]{
"borders", "button", "checkBox", "colorChooser", "comboBox", "fileChooser", "tristate",
"internalFrame", "label", "list", "menu", "menuBar", "menuItem", "optionPane", "panel",
"internalFrame", "label", "list", "menu", "menuBar", "menuItem", "numberingPane", "optionPane", "panel",
"popupMenu", "progressBar", "radioButton", "rootPane", "scrollBar", "scrollPane", "separator",
"slider", "spinner", "splitPane", "statusBar", "tabbedPane", "tabFrame", "table", "taskPane", "text",
"toggleButton", "toolBar", "toolTip", "tree",

37
src/main/java/com/github/weisj/darklaf/ui/numberingpane/DarkNumberingPaneBorder.java

@ -0,0 +1,37 @@
/*
* 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.github.weisj.darklaf.ui.numberingpane;
import com.github.weisj.darklaf.components.border.MutableLineBorder;
import javax.swing.*;
import javax.swing.plaf.UIResource;
public class DarkNumberingPaneBorder extends MutableLineBorder implements UIResource {
public DarkNumberingPaneBorder() {
super(0, 0, 0, 1, null);
setColor(UIManager.getColor("NumberingPane.borderColor"));
}
}

342
src/main/java/com/github/weisj/darklaf/ui/numberingpane/DarkNumberingPaneUI.java

@ -0,0 +1,342 @@
/*
* 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.github.weisj.darklaf.ui.numberingpane;
import com.github.weisj.darklaf.components.text.LineHighlighter;
import com.github.weisj.darklaf.components.text.NumberingPane;
import com.github.weisj.darklaf.util.DarkUIUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import java.awt.*;
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;
public class DarkNumberingPaneUI extends ComponentUI {
protected static final int OUTER_PAD = 7;
protected static final int PAD = 5;
protected Handler handler;
protected NumberingPane numberingPane;
protected Color backgroundHighlight;
protected Color foregroundHighlight;
protected LineHighlighter currentLinePainter;
protected int textWidth = 0;
protected JTextComponent textComponent;
protected JViewport viewport;
protected int maxIconWidth = 0;
@NotNull
@Contract("_ -> new")
public static ComponentUI createUI(final JComponent c) {
return new DarkNumberingPaneUI();
}
@Override
public void installUI(final JComponent c) {
numberingPane = (NumberingPane) c;
super.installUI(c);
installDefaults(c);
installListeners(c);
}
protected void installDefaults(final JComponent c) {
LookAndFeel.installColorsAndFont(c, "NumberingPane.background",
"NumberingPane.foreground",
"NumberingPane.font");
foregroundHighlight = UIManager.getColor("NumberingPane.currentLineForeground");
backgroundHighlight = UIManager.getColor("NumberingPane.currentLineBackground");
LookAndFeel.installBorder(c, "NumberingPane.border");
}
protected void installListeners(final JComponent c) {
currentLinePainter = new LineHighlighter(null, backgroundHighlight);
numberingPane.addMouseListener(getMouseListener());
numberingPane.addMouseMotionListener(getMouseMotionListener());
numberingPane.addPropertyChangeListener(getPropertyChangeListener());
}
protected MouseListener getMouseListener() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
protected MouseMotionListener getMouseMotionListener() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
protected PropertyChangeListener getPropertyChangeListener() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
@Override
public void uninstallUI(final JComponent c) {
super.uninstallUI(c);
uninstallListeners(c);
currentLinePainter.setComponent(null);
currentLinePainter = null;
numberingPane = null;
}
protected void uninstallListeners(final JComponent c) {
if (textComponent != null) {
textComponent.getCaret().removeChangeListener(getChangeListener());
textComponent.getCaret().removeChangeListener(currentLinePainter);
textComponent.removePropertyChangeListener(getPropertyChangeListener());
}
numberingPane.removePropertyChangeListener(getPropertyChangeListener());
numberingPane.removeMouseListener(getMouseListener());
numberingPane.removeMouseMotionListener(getMouseMotionListener());
}
protected ChangeListener getChangeListener() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
@Override
public void paint(final Graphics g, final JComponent c) {
super.paint(g, c);
if (textComponent == null || viewport == null) return;
var metrics = textComponent.getFontMetrics(textComponent.getFont());
int descent = metrics.getDescent();
var doc = textComponent.getDocument();
var viewRect = viewport.getViewRect();
var p = viewRect.getLocation();
int startIndex = textComponent.viewToModel2D(p);
p.y += viewRect.height;
int endIndex = textComponent.viewToModel2D(p);
int currOffset = textComponent.getCaretPosition();
Element root = doc.getDefaultRootElement();
int startLine = root.getElementIndex(startIndex);
int endLine = root.getElementIndex(endIndex);
int yCurr = drawHighlightBackground(g, currOffset);
drawNumbering(g, startLine, endLine, yCurr, root, descent);
paintIcons(g, startLine, endLine, root);
}
@Override
public Dimension getPreferredSize(final JComponent c) {
if (textComponent == null || viewport == null) return super.getPreferredSize(c);
int lines = textComponent.getDocument().getDefaultRootElement().getElementCount();
int pad = 2 * OUTER_PAD;
if (maxIconWidth > 0) pad += PAD;
textWidth = numberingPane.getFontMetrics(numberingPane.getFont()).stringWidth(String.valueOf(lines));
return new Dimension(maxIconWidth + pad + textWidth, viewport.getView().getHeight());
}
protected int drawHighlightBackground(@NotNull final Graphics g, final int currOffset) {
g.setColor(backgroundHighlight);
Rectangle rect;
try {
rect = textComponent.modelToView2D(currOffset).getBounds();
} catch (BadLocationException e) {
rect = new Rectangle(0, 0, 0, 0);
}
g.fillRect(0, rect.y, numberingPane.getWidth(), rect.height);
return rect.y;
}
protected void drawNumbering(@NotNull final Graphics g, final int startLine, final int endLine, final int yCur,
@NotNull final Element root, final int descent) {
g.setColor(numberingPane.getForeground());
int digits = String.valueOf(root.getElementCount()).length();
for (int i = startLine; i <= endLine; i++) {
int off = root.getElement(i).getStartOffset();
try {
Rectangle lineRect = textComponent.modelToView2D(off).getBounds();
g.setColor(lineRect.y == yCur ? foregroundHighlight : numberingPane.getForeground());
g.drawString(String.format("%1$" + digits + "d", i), OUTER_PAD,
lineRect.y + lineRect.height - descent);
} catch (BadLocationException ignored) { }
}
}
protected void paintIcons(final Graphics g, final int startLine, final int endLine, @NotNull final Element root) {
var icons = numberingPane.getIconsInRange(root.getElement(startLine).getStartOffset(),
root.getElement(endLine).getEndOffset());
for (var icon : icons) {
Rectangle lineRect;
try {
lineRect = textComponent.modelToView2D(icon.getKey().getOffset()).getBounds();
int h = icon.getValue().getIconHeight();
int x = OUTER_PAD + PAD + textWidth;
int y = lineRect.y + lineRect.height / 2 - h / 2;
icon.getValue().paintIcon(numberingPane, g, x, y);
} catch (BadLocationException ignored) {
}
}
}
protected int calculateMaxIconWidth() {
var icons = numberingPane.getIcons();
int max = 0;
for (var icon : icons) {
max = Math.max(icon.getIconWidth(), max);
}
return max;
}
protected class Handler extends MouseAdapter implements PropertyChangeListener, ChangeListener {
protected int selectionLineStart;
protected int selectionLineEnd;
protected Object currentHighlight;
@Override
public void mouseClicked(final MouseEvent e) {
if (textComponent == null) return;
var p = e.getPoint();
int iconCount = numberingPane.getIconCount();
int width = numberingPane.getWidth();
if (iconCount > 0 && p.x > PAD + OUTER_PAD + textWidth && p.x <= width - PAD) {
int offset = textComponent.viewToModel2D(new Point(0, p.y));
var doc = textComponent.getDocument();
int start = doc.getDefaultRootElement().getElementIndex(offset);
int startOffset = doc.getDefaultRootElement().getElement(start).getStartOffset();
int endOffset = doc.getDefaultRootElement().getElement(start).getEndOffset();
var icons = numberingPane.getIconsInRange(startOffset, endOffset);
if (!icons.isEmpty()) {
var icon = icons.get(0).getValue();
Rectangle lineRect;
try {
lineRect = textComponent.modelToView2D(start).getBounds();
int h = icon.getIconHeight();
int x = OUTER_PAD + PAD + textWidth;
int y = lineRect.y + lineRect.height / 2 - h / 2;
if (p.x >= x && p.y >= y && p.y <= y + h) {
System.out.println("clicked icon" + start);
return;
}
} catch (BadLocationException ignored) { }
}
System.out.println("clicked line: " + start);
}
}
@Override
public void mousePressed(final MouseEvent e) {
if (textComponent == null) return;
var p = e.getPoint();
if (p.x <= OUTER_PAD + textWidth) {
selectionLineStart = textComponent.viewToModel2D(new Point(0, p.y));
selectionLineEnd = textComponent.viewToModel2D(new Point(textComponent.getWidth(), p.y));
textComponent.getCaret().setDot(selectionLineEnd + 1);
textComponent.getCaret().moveDot(Math.min(selectionLineStart,
textComponent.getDocument().getLength()));
}
}
@Override
public void stateChanged(final ChangeEvent e) {
numberingPane.repaint();
}
@Override
public void propertyChange(@NotNull final PropertyChangeEvent evt) {
var key = evt.getPropertyName();
if ("caret".equals(key)) {
if (evt.getNewValue() instanceof Caret) {
var oldCaret = evt.getOldValue();
if (oldCaret instanceof Caret) {
((Caret) oldCaret).removeChangeListener(getChangeListener());
((Caret) oldCaret).removeChangeListener(currentLinePainter);
}
var newCaret = evt.getNewValue();
if (newCaret instanceof Caret) {
((Caret) newCaret).addChangeListener(getChangeListener());
((Caret) newCaret).addChangeListener(currentLinePainter);
}
}
} else if ("font".equals(key)) {
var font = textComponent.getFont();
numberingPane.setFont(font.deriveFont(Math.max(font.getSize() - 1, 1.0f)));
} else if ("editorPane".equals(key)) {
var newPane = evt.getNewValue();
if (textComponent != null) {
currentLinePainter.setComponent(null);
textComponent.getHighlighter().removeHighlight(currentHighlight);
textComponent.getCaret().removeChangeListener(getChangeListener());
textComponent.getCaret().removeChangeListener(currentLinePainter);
textComponent.removePropertyChangeListener(getPropertyChangeListener());
}
if (newPane instanceof JTextComponent) {
textComponent = (JTextComponent) newPane;
try {
currentHighlight = textComponent.getHighlighter().addHighlight(0, 0, currentLinePainter);
textComponent.getCaret().addChangeListener(currentLinePainter);
currentLinePainter.setComponent(textComponent);
} catch (BadLocationException ignored) {}
textComponent.addPropertyChangeListener(getPropertyChangeListener());
textComponent.getCaret().addChangeListener(getChangeListener());
var font = textComponent.getFont();
numberingPane.setFont(font.deriveFont(Math.max(font.getSize() - 1, 1.0f)));
}
} else if ("icons".equals(key)) {
var oldVal = evt.getOldValue();
var newVal = evt.getNewValue();
if (oldVal instanceof Icon) {
maxIconWidth = calculateMaxIconWidth();
}
if (newVal instanceof Icon) {
maxIconWidth = Math.max(maxIconWidth, ((Icon) newVal).getIconWidth());
}
} else if ("ancestor".equals(key)) {
if (evt.getSource() == numberingPane) {
var parent = DarkUIUtil.getParentOfType(JScrollPane.class, (Component) evt.getNewValue());
if (parent != null) {
viewport = parent.getViewport();
} else {
viewport = null;
}
}
}
}
}
}

28
src/main/java/com/github/weisj/darklaf/ui/rootpane/DarkTitlePane.java

@ -25,6 +25,7 @@
package com.github.weisj.darklaf.ui.rootpane;
import com.github.weisj.darklaf.decorators.AncestorAdapter;
import com.github.weisj.darklaf.icons.ScaledIcon;
import com.github.weisj.darklaf.platform.windows.JNIDecorations;
import com.github.weisj.darklaf.util.GraphicsUtil;
import org.jetbrains.annotations.Contract;
@ -767,31 +768,4 @@ public class DarkTitlePane extends JComponent {
}
}
protected static class ScaledIcon implements Icon {
private Image img;
@Contract(pure = true)
protected ScaledIcon(final Image img) {
this.img = img;
}
@Override
public void paintIcon(final Component c, final Graphics g2, final int x, final int y) {
var g = (Graphics2D) g2;
g.translate(x, y);
g.scale(1.0 / GraphicsUtil.SCALE_X, 1.0 / GraphicsUtil.SCALE_Y);
g.drawImage(img, 0, 0, img.getWidth(null), img.getHeight(null), null);
}
@Override
public int getIconWidth() {
return (int) (img.getWidth(null) / GraphicsUtil.SCALE_X);
}
@Override
public int getIconHeight() {
return (int) (img.getHeight(null) / GraphicsUtil.SCALE_Y);
}
}
}

6
src/main/java/com/github/weisj/darklaf/ui/splitpane/DarkSplitPaneUI.java

@ -182,6 +182,12 @@ public class DarkSplitPaneUI extends BasicSplitPaneUI implements PropertyChangeL
}
}
@Override
public boolean contains(final Point p) {
if (!isEnabled()) return false;
return super.contains(p);
}
@Override
protected void dragDividerTo(final int location) {
super.dragDividerTo(location + DIVIDER_DRAG_OFFSET);

121
src/main/java/com/github/weisj/darklaf/ui/text/DarkCaret.java

@ -250,61 +250,61 @@ public class DarkCaret extends DefaultCaret implements UIResource {
@Override
public void paint(final Graphics g) {
if (isVisible() || alwaysVisible) {
JTextComponent textArea = getComponent();
g.setColor(textArea.getCaretColor());
TextUI mapper = textArea.getUI();
Rectangle r;
try {
JTextComponent textArea = getComponent();
g.setColor(textArea.getCaretColor());
TextUI mapper = textArea.getUI();
Rectangle r = mapper.modelToView2D(textArea, getDot(), Position.Bias.Forward).getBounds();
validateWidth(r);
if (width > 0 && height > 0 &&
!contains(r.x, r.y, r.width, r.height)) {
Rectangle clip = g.getClipBounds();
if (clip != null && !clip.contains(this)) {
// Clip doesn't contain the old location, force it
// to be repainted lest we leave a caret around.
repaint();
}
damage(r);
r = mapper.modelToView2D(textArea, getDot(), Position.Bias.Forward).getBounds();
} catch (BadLocationException ex) {
r = new Rectangle(0, 0, 0, 0);
}
validateWidth(r);
if (width > 0 && height > 0 &&
!contains(r.x, r.y, r.width, r.height)) {
Rectangle clip = g.getClipBounds();
if (clip != null && !clip.contains(this)) {
// Clip doesn't contain the old location, force it
// to be repainted lest we leave a caret around.
repaint();
}
damage(r);
}
// Need to subtract 2 from height, otherwise
// the caret will expand too far vertically.
r.height -= 2;
r.y += 1;
// Need to subtract 2 from height, otherwise
// the caret will expand too far vertically.
r.height -= 2;
r.y += 1;
Color textAreaBg = textArea.getBackground();
switch (style) {
case BLOCK_STYLE:
if (textAreaBg == null) {
textAreaBg = Color.white;
}
g.setXORMode(textAreaBg);
g.fillRect(r.x, r.y, r.width, r.height);
break;
case BLOCK_BORDER_STYLE:
DarkUIUtil.drawRect(g, r.x, r.y, r.width - 1, r.height, 1);
break;
case UNDERLINE_STYLE:
if (textAreaBg == null) {
textAreaBg = Color.white;
}
g.setXORMode(textAreaBg);
int y = r.y + r.height;
g.drawLine(r.x, y, r.x + r.width - 1, y);
break;
case THICK_VERTICAL_LINE_STYLE:
g.drawLine(r.x, r.y, r.x, r.y + r.height);
r.x++;
g.drawLine(r.x, r.y, r.x, r.y + r.height);
break;
case VERTICAL_LINE_STYLE:
g.drawLine(r.x, r.y, r.x, r.y + r.height);
break;
}
} catch (BadLocationException ble) {
ble.printStackTrace();
Color textAreaBg = textArea.getBackground();
switch (style) {
case BLOCK_STYLE:
if (textAreaBg == null) {
textAreaBg = Color.white;
}
g.setXORMode(textAreaBg);
g.fillRect(r.x, r.y, r.width, r.height);
break;
case BLOCK_BORDER_STYLE:
DarkUIUtil.drawRect(g, r.x, r.y, r.width - 1, r.height, 1);
break;
case UNDERLINE_STYLE:
if (textAreaBg == null) {
textAreaBg = Color.white;
}
g.setXORMode(textAreaBg);
int y = r.y + r.height;
g.drawLine(r.x, y, r.x + r.width - 1, y);
break;
case THICK_VERTICAL_LINE_STYLE:
g.drawLine(r.x, r.y, r.x, r.y + r.height);
r.x++;
g.drawLine(r.x, r.y, r.x, r.y + r.height);
break;
case VERTICAL_LINE_STYLE:
g.drawLine(r.x, r.y, r.x, r.y + r.height);
break;
}
}
}
@ -340,24 +340,21 @@ public class DarkCaret extends DefaultCaret implements UIResource {
private void validateWidth(final Rectangle rect) {
if (rect != null && rect.width <= 1) {
JTextComponent textArea = getComponent();
try {
JTextComponent textArea = getComponent();
textArea.getDocument().getText(getDot(), 1, seg);
Font font = textArea.getFont();
FontMetrics fm = textArea.getFontMetrics(font);
rect.width = fm.charWidth(seg.array[seg.offset]);
if (rect.width == 0) {
rect.width = fm.charWidth(' ');
}
} catch (BadLocationException ble) {
// This shouldn't ever happen.
ble.printStackTrace();
rect.width = 8;
}
Font font = textArea.getFont();
FontMetrics fm = textArea.getFontMetrics(font);
rect.width = fm.charWidth(seg.array[seg.offset]);
if (rect.width == 0) {
rect.width = fm.charWidth(' ');
}
}
}
}

8
src/main/java/com/github/weisj/darklaf/util/Pair.java

@ -43,4 +43,12 @@ public class Pair<T, H> {
public T getFirst() {
return first;
}
public void setFirst(final T first) {
this.first = first;
}
public void setSecond(final H second) {
this.second = second;
}
}

31
src/main/resources/com/github/weisj/darklaf/properties/ui/numberingPane.properties

@ -0,0 +1,31 @@
# suppress inspection "UnusedProperty" for whole file
#
# 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.
#
NumberingPaneUI = com.github.weisj.darklaf.ui.numberingpane.DarkNumberingPaneUI
NumberingPane.border = com.github.weisj.darklaf.ui.numberingpane.DarkNumberingPaneBorder
NumberingPane.borderColor = %border
NumberingPane.foreground = %textForegroundSecondary
NumberingPane.background = %textBackgroundSecondaryInactive
NumberingPane.currentLineForeground = %textForeground
NumberingPane.currentLineBackground = %textSelectionBackgroundSecondary

2
src/test/java/ScrollPaneDemo.java

@ -17,7 +17,7 @@ public final class ScrollPaneDemo extends MultiSplitLayout {
frame.setSize(500, 500);
var overlayScroll = new JScrollPane(new JEditorPane() {{
// setEditorKit(new HTMLEditorKit());
setText(TestResources.LOREM_IPSUM);
setText(TestResources.LOREM_IPSUM.repeat(10));
setFont(Font.getFont(Font.MONOSPACED));
// setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
}});

31
src/test/java/TabFrameDemo.java

@ -1,10 +1,12 @@
import com.github.weisj.darklaf.LafManager;
import com.github.weisj.darklaf.components.OverlayScrollPane;
import com.github.weisj.darklaf.components.SelectableTreeNode;
import com.github.weisj.darklaf.components.alignment.Alignment;
import com.github.weisj.darklaf.components.tabframe.JTabFrame;
import com.github.weisj.darklaf.components.tabframe.TabbedPopup;
import com.github.weisj.darklaf.components.text.NonWrappingEditorPane;
import com.github.weisj.darklaf.components.text.NumberedTextComponent;
import com.github.weisj.darklaf.icons.IconLoader;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
@ -74,14 +76,8 @@ public class TabFrameDemo {
*/
tabFrame.setAcceleratorAt(1, Alignment.NORTH_WEST, 0);
var contentPane = new JPanel(new BorderLayout());
var overlayScroll = new OverlayScrollPane(new JTextPane() {{
setText(TestResources.LOREM_IPSUM);
setFont(Font.getFont(Font.MONOSPACED));
}});
contentPane.add(overlayScroll, BorderLayout.CENTER);
frame.setContentPane(tabFrame);
tabFrame.setContent(contentPane);
tabFrame.setContent(createTextArea());
frame.pack();
frame.setSize(1000, 500);
@ -90,6 +86,7 @@ public class TabFrameDemo {
});
}
@NotNull
protected static JTree createTree() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("States");
DefaultMutableTreeNode parent1 = new DefaultMutableTreeNode("Andhra Pradesh");
@ -113,4 +110,22 @@ public class TabFrameDemo {
// tree.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
return tree;
}
@NotNull
private static Component createTextArea() {
var numberPane = new NumberedTextComponent(new NonWrappingEditorPane() {{
setText((TestResources.LOREM_IPSUM).repeat(10));
setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
}});
var numbering = numberPane.getNumberingPane();
Icon icon = IconLoader.get().getIcon("navigation/arrowRight.svg");
// try {
// numbering.addIconAtLine(5, icon);
// numbering.addIconAtLine(10, icon);
// numbering.addIconAtLine(15, icon);
// } catch (BadLocationException e) {
// e.printStackTrace();
// }
return numberPane;
}
}

42
src/test/java/TestResources.java

@ -23,28 +23,28 @@
*/
public class TestResources {
public static final String LOREM_IPSUM =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempor quis nibh a semper. Nullam"
+ " auctor, erat non viverra commodo, libero orci aliquam quam, ac interdum nunc est sed "
+ "ligula. Aliquam vel velit non dolor accumsan blandit id eu metus. Aenean iaculis urna in "
+ "placerat aliquam. Aliquam dui quam, bibendum sed magna in, cursus ornare est. Quisque "
+ "tempor nunc quis nunc tempor convallis. Vestibulum tristique luctus ante, ac hendrerit dui"
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempor quis nibh a semper. Nullam\n"
+ " auctor, erat non viverra commodo, libero orci aliquam quam, ac interdum nunc est sed\n "
+ "ligula. Aliquam vel velit non dolor accumsan blandit id eu metus. Aenean iaculis urna in\n "
+ "placerat aliquam. Aliquam dui quam, bibendum sed magna in, cursus ornare est. Quisque\n "
+ "tempor nunc quis nunc tempor convallis. Vestibulum tristique luctus ante, ac hendrerit dui\n"
+ ".\n\n"
+ "Donec ut maximus augue. Nam eleifend maximus scelerisque. Duis varius accumsan est, non "
+ "aliquam dolor. Aenean iaculis nibh in aliquam viverra. Sed laoreet, urna ut facilisis "
+ "convallis, arcu turpis vestibulum augue, id convallis tellus metus nec orci. Lorem ipsum "
+ "dolor sit amet, consectetur adipiscing elit. Donec hendrerit purus velit, at blandit elit "
+ "luctus ut. Proin diam nisl, sodales vitae dignissim nec, eleifend eu libero. Maecenas odio"
+ " ligula, fermentum eget nisl vel, cursus tristique est. In nec nibh nec dui tempor "
+ "ullamcorper. Praesent tincidunt luctus sem, ut luctus dolor commodo non. Nulla consectetur"
+ " facilisis dolor, in facilisis ligula fringilla et. Cras id placerat libero. Donec "
+ "vehicula orci a quam rutrum, eu efficitur lorem iaculis. Aenean varius nisi in dictum "
+ "Donec ut maximus augue. Nam eleifend maximus scelerisque. Duis varius accumsan est, non\n "
+ "aliquam dolor. Aenean iaculis nibh in aliquam viverra. Sed laoreet, urna ut facilisis\n "
+ "convallis, arcu turpis vestibulum augue, id convallis tellus metus nec orci. Lorem ipsum\n "
+ "dolor sit amet, consectetur adipiscing elit. Donec hendrerit purus velit, at blandit elit\n "
+ "luctus ut. Proin diam nisl, sodales vitae dignissim nec, eleifend eu libero. Maecenas odio\n"
+ " ligula, fermentum eget nisl vel, cursus tristique est. In nec nibh nec dui tempor\n "
+ "ullamcorper. Praesent tincidunt luctus sem, ut luctus dolor commodo non. Nulla consectetur\n"
+ " facilisis dolor, in facilisis ligula fringilla et. Cras id placerat libero. Donec\n "
+ "vehicula orci a quam rutrum, eu efficitur lorem iaculis. Aenean varius nisi in dictum\n "
+ "accumsan.\n\n"
+ "Nulla massa ipsum, consectetur non gravida ut, blandit quis velit. Ut pretium quam aliquam"
+ " diam porttitor mattis. Nam ullamcorper, felis ut iaculis iaculis, nunc odio pulvinar "
+ "enim, vitae iaculis turpis sapien iaculis metus. Donec rutrum varius augue in dictum. Cras"
+ " vestibulum vitae mauris ut finibus. Ut dictum imperdiet lorem et imperdiet. Vivamus "
+ "semper tempor dolor eu porta. Sed at vehicula nisl. Pellentesque ut lorem tincidunt, "
+ "elementum ligula at, porta turpis. Praesent feugiat dolor diam, at facilisis metus gravida"
+ " non. Aliquam quis pellentesque nibh. Sed vestibulum porttitor nisi. In vitae malesuada "
+ "Nulla massa ipsum, consectetur non gravida ut, blandit quis velit. Ut pretium quam aliquam\n"
+ " diam porttitor mattis. Nam ullamcorper, felis ut iaculis iaculis, nunc odio pulvinar \n"
+ "enim, vitae iaculis turpis sapien iaculis metus. Donec rutrum varius augue in dictum. Cras\n"
+ " vestibulum vitae mauris ut finibus. Ut dictum imperdiet lorem et imperdiet. Vivamus \n"
+ "semper tempor dolor eu porta. Sed at vehicula nisl. Pellentesque ut lorem tincidunt, \n"
+ "elementum ligula at, porta turpis. Praesent feugiat dolor diam, at facilisis metus gravida\n"
+ " non. Aliquam quis pellentesque nibh. Sed vestibulum porttitor nisi. In vitae malesuada\n "
+ "sapien.";
}

Loading…
Cancel
Save