// -*- mode:java; encoding:utf-8 -*-
// vim:set fileencoding=utf-8:
// @homepage@

package example;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.plaf.TextUI;
import javax.swing.text.*;
import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter;
import javax.swing.text.Highlighter.HighlightPainter;

public final class MainPanel extends JPanel {
 
private static final String TEXT = String.join("\n",
     
"Trail: Creating a GUI with JFC/Swing",
     
"Lesson: Learning Swing by Example",
     
"This lesson explains the concepts you need to",
     
" use Swing components in building a user interface.",
     
" First we examine the simplest Swing application you can write.",
     
" Then we present several progressively complicated examples of creating",
     
" user interfaces using components in the javax.swing package.",
     
" We cover several Swing components, such as buttons, labels, and text areas.",
     
" The handling of events is also discussed,",
     
" as are layout management and accessibility.",
     
" This lesson ends with a set of questions and exercises",
     
" so you can test yourself on what you've learned.",
     
"https://docs.oracle.com/javase/tutorial/uiswing/learn/index.html");

 
private MainPanel() {
   
super(new BorderLayout(5, 5));
   
JTextArea textArea = new JTextArea(TEXT) {
     
@Override public void updateUI() {
       
super.updateUI();
       
Caret caret = new RoundedSelectionCaret();
        caret
.setBlinkRate(UIManager.getInt("TextArea.caretBlinkRate"));
        setCaret
(caret);
       
((DefaultHighlighter) getHighlighter()).setDrawsLayeredHighlights(false);
     
}
   
};
   
JCheckBox check = new JCheckBox("setLineWrap / setWrapStyleWord:");
    check
.addActionListener(e -> {
     
boolean b = ((JCheckBox) e.getSource()).isSelected();
      textArea
.setLineWrap(b);
      textArea
.setWrapStyleWord(b);
   
});
    add
(new JScrollPane(textArea));
    add
(check, BorderLayout.SOUTH);
    setBorder
(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    setPreferredSize
(new Dimension(320, 240));
 
}

 
public static void main(String[] args) {
   
EventQueue.invokeLater(MainPanel::createAndShowGui);
 
}

 
private static void createAndShowGui() {
   
try {
     
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
   
} catch (UnsupportedLookAndFeelException ignored) {
     
Toolkit.getDefaultToolkit().beep();
   
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
     
Logger.getGlobal().severe(ex::getMessage);
     
return;
   
}
   
JFrame frame = new JFrame("@title@");
    frame
.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame
.getContentPane().add(new MainPanel());
    frame
.pack();
    frame
.setLocationRelativeTo(null);
    frame
.setVisible(true);
 
}
}

class RoundedSelectionCaret extends DefaultCaret {
 
@Override protected HighlightPainter getSelectionPainter() {
   
return new RoundedSelectionHighlightPainter();
 
}

 
@Override protected synchronized void damage(Rectangle r) {
   
JTextComponent c = getComponent();
   
int startOffset = c.getSelectionStart();
   
int endOffset = c.getSelectionEnd();
   
if (startOffset == endOffset) {
     
super.damage(r);
   
} else {
     
TextUI mapper = c.getUI();
     
try {
       
Rectangle p0 = mapper.modelToView(c, startOffset);
       
Rectangle p1 = mapper.modelToView(c, endOffset);
       
int h = (int) (p1.getMaxY() - p0.getMinY());
       
Rectangle rr = new Rectangle(0, p0.y, c.getWidth(), h);
        c
.repaint(rr);
     
} catch (BadLocationException ex) {
       
UIManager.getLookAndFeel().provideErrorFeedback(c);
     
}
   
}
 
}
}

class RoundedSelectionHighlightPainter extends DefaultHighlightPainter {
 
protected RoundedSelectionHighlightPainter() {
   
super(null);
 
}

 
@Override public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
   
Graphics2D g2 = (Graphics2D) g.create();
    g2
.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
   
Color color = c.getSelectionColor();
    g2
.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 200));
   
try {
     
Area area = getLinesArea(c, offs0, offs1);
     
for (Area a : GeomUtils.singularization(area)) {
       
List<Point2D> lst = GeomUtils.convertAreaToPoint2DList(a);
       
GeomUtils.flatteningStepsOnRightSide(lst, 3d * 2d);
        g2
.fill(GeomUtils.convertRoundedPath(lst, 3d));
     
}
   
} catch (BadLocationException ex) {
     
// can't render
     
Logger.getGlobal().severe(ex::getMessage);
   
}
    g2
.dispose();
 
}

 
private static Area getLinesArea(JTextComponent c, int offs0, int offs1)
     
throws BadLocationException {
   
TextUI mapper = c.getUI();
   
Area area = new Area();
   
int cur = offs0;
   
do {
     
int startOffset = Utilities.getRowStart(c, cur);
     
int endOffset = Utilities.getRowEnd(c, cur);
     
Rectangle p0 = mapper.modelToView(c, Math.max(startOffset, offs0));
     
Rectangle p1 = mapper.modelToView(c, Math.min(endOffset, offs1));
     
if (p0.x != p1.x) {
        area
.add(new Area(p0.union(p1)));
     
} else {
        p0
.width += 6;
        area
.add(new Area(p0));
     
}
      cur
= endOffset + 1;
   
} while (cur < offs1);
   
return area;
 
}
}

final class GeomUtils {
 
private GeomUtils() {
   
/* Singleton */
 
}

 
public static List<Point2D> convertAreaToPoint2DList(Area area) {
   
List<Point2D> list = new ArrayList<>();
   
PathIterator pi = area.getPathIterator(null);
   
double[] cd = new double[6];
   
while (!pi.isDone()) {
     
switch (pi.currentSegment(cd)) {
       
case PathIterator.SEG_MOVETO:
       
case PathIterator.SEG_LINETO:
          list
.add(new Point2D.Double(cd[0], cd[1]));
         
break;
       
default:
         
break;
     
}
      pi
.next();
   
}
   
return list;
 
}

 
public static void flatteningStepsOnRightSide(List<Point2D> list, double arc) {
   
int sz = list.size();
   
for (int i = 0; i < sz; i++) {
     
int i1 = (i + 1) % sz;
     
int i2 = (i + 2) % sz;
     
int i3 = (i + 3) % sz;
     
Point2D pt0 = list.get(i);
     
Point2D pt1 = list.get(i1);
     
Point2D pt2 = list.get(i2);
     
Point2D pt3 = list.get(i3);
     
double dx1 = pt2.getX() - pt1.getX();
     
if (Math.abs(dx1) > 1.0e-1 && Math.abs(dx1) < arc) {
       
double max = Math.max(pt0.getX(), pt2.getX());
        replace
(list, i, max, pt0.getY());
        replace
(list, i1, max, pt1.getY());
        replace
(list, i2, max, pt2.getY());
        replace
(list, i3, max, pt3.getY());
     
}
   
}
 
}

 
private static void replace(List<Point2D> list, int i, double x, double y) {
    list
.remove(i);
    list
.add(i, new Point2D.Double(x, y));
 
}

 
/**
   * Rounding the corners of a Rectilinear Polygon.
   */

 
public static Path2D convertRoundedPath(List<Point2D> list, double arc) {
   
double kappa = 4d * (Math.sqrt(2d) - 1d) / 3d; // = 0.55228...;
   
double akv = arc - arc * kappa;
   
int sz = list.size();
   
Point2D pt0 = list.get(0);
   
Path2D path = new Path2D.Double();
    path
.moveTo(pt0.getX() + arc, pt0.getY());
   
for (int i = 0; i < sz; i++) {
     
Point2D prv = list.get((i - 1 + sz) % sz);
     
Point2D cur = list.get(i);
     
Point2D nxt = list.get((i + 1) % sz);
     
double dx0 = signum(cur.getX() - prv.getX(), arc);
     
double dy0 = signum(cur.getY() - prv.getY(), arc);
     
double dx1 = signum(nxt.getX() - cur.getX(), arc);
     
double dy1 = signum(nxt.getY() - cur.getY(), arc);
      path
.curveTo(
          cur
.getX() - dx0 * akv, cur.getY() - dy0 * akv,
          cur
.getX() + dx1 * akv, cur.getY() + dy1 * akv,
          cur
.getX() + dx1 * arc, cur.getY() + dy1 * arc);
      path
.lineTo(nxt.getX() - dx1 * arc, nxt.getY() - dy1 * arc);
   
}
    path
.closePath();
   
return path;
 
}

 
private static double signum(double v, double arc) {
   
return Math.abs(v) < arc ? 0d : Math.signum(v);
 
}

 
public static List<Area> singularization(Area rect) {
   
List<Area> list = new ArrayList<>();
   
Path2D path = new Path2D.Double();
   
PathIterator pi = rect.getPathIterator(null);
   
double[] cd = new double[6];
   
while (!pi.isDone()) {
     
switch (pi.currentSegment(cd)) {
       
case PathIterator.SEG_MOVETO:
          path
.moveTo(cd[0], cd[1]);
         
break;
       
case PathIterator.SEG_LINETO:
          path
.lineTo(cd[0], cd[1]);
         
break;
       
case PathIterator.SEG_QUADTO:
          path
.quadTo(cd[0], cd[1], cd[2], cd[3]);
         
break;
       
case PathIterator.SEG_CUBICTO:
          path
.curveTo(cd[0], cd[1], cd[2], cd[3], cd[4], cd[5]);
         
break;
       
case PathIterator.SEG_CLOSE:
          path
.closePath();
          list
.add(new Area(path));
          path
.reset();
         
break;
       
default:
         
break;
     
}
      pi
.next();
   
}
   
return list;
 
}
}