diff --git a/.gitignore b/.gitignore index 8483905c6..50a4dc8df 100644 --- a/.gitignore +++ b/.gitignore @@ -238,3 +238,4 @@ /installers/nuts-installer/dist/portable-jar/nuts-*.jar /installers/nuts-builder-tool/target /tutorials/naf/nuts-spring-boot-integration-hello-world/nuts-spring-boot-integration-hello-world.iml +/libraries/nlib-swing/target diff --git a/core/nuts-runtime/src/main/java/net/thevpc/nuts/runtime/standalone/io/printstream/NPrintStreamRaw.java b/core/nuts-runtime/src/main/java/net/thevpc/nuts/runtime/standalone/io/printstream/NPrintStreamRaw.java index 766d24022..58abf47a8 100644 --- a/core/nuts-runtime/src/main/java/net/thevpc/nuts/runtime/standalone/io/printstream/NPrintStreamRaw.java +++ b/core/nuts-runtime/src/main/java/net/thevpc/nuts/runtime/standalone/io/printstream/NPrintStreamRaw.java @@ -141,6 +141,12 @@ protected NPrintStream convertImpl(NTerminalMode other) { case FILTERED: { return new NPrintStreamFiltered(this, getSession(), bindings); } + case ANSI: { + if(this.getTerminalMode()==NTerminalMode.INHERITED){ + return this; + } + break; + } } throw new NIllegalArgumentException(getSession(), NMsg.ofC("unsupported %s -> %s", getTerminalMode(), other)); } diff --git a/libraries/nlib-swing/pom.xml b/libraries/nlib-swing/pom.xml new file mode 100644 index 000000000..d0025b8bb --- /dev/null +++ b/libraries/nlib-swing/pom.xml @@ -0,0 +1,219 @@ + + + 4.0.0 + net.thevpc.nuts.lib + nlib-swing + 0.8.4.0 + jar + Nuts Community Library for Swing Support + https://github.com/thevpc/nuts + Swing Lib + + UTF-8 + 1.8 + 1.8 + github + + + scm:git:git://github.com/thevpc/nuts.git + scm:git:ssh://github.com:thevpc/nuts.git + https://github.com/thevpc/nuts/tree/master + + + + vpc open source initiative + https://thevpc.net + + + + + vpc + Taha Ben Salah + taha.bensalah@gmail.com + https://tahabensalah.net + thevpc open source initiative + https://thevpc.net + + architect + developer + + Africa/Tunis + + https://gravatar.com/avatar/977025550163b4a91397007f6ea9ee17 + + + + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + + + + + + org.junit.jupiter + junit-jupiter-api + 5.8.2 + test + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + true + true + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + + deploy + + false + + + + + maven-dependency-plugin + 3.0.2 + + + process-sources + + copy-dependencies + + + ${targetdirectory} + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.4.0 + + + + app.category + a + Category: + + + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + net.thevpc.nuts.lib.talkagent + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + + diff --git a/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/AnsiTermPane.java b/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/AnsiTermPane.java new file mode 100644 index 000000000..e7422dfe9 --- /dev/null +++ b/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/AnsiTermPane.java @@ -0,0 +1,368 @@ +package net.thevpc.nuts.lib.nswing; + +import javax.swing.*; +import javax.swing.text.*; +import java.awt.*; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * thanks to https://stackoverflow.com/questions/6913983/jtextpane-removing-first-line + */ +public class AnsiTermPane extends JTextPane { + public static Color colorForeground = Color.BLACK;//cReset; + public static Color colorBackground = null;//cReset; + public Color D_Black = Color.getHSBColor(0.000f, 0.000f, 0.000f); + public Color D_Red = Color.getHSBColor(0.000f, 1.000f, 0.502f); + public Color D_Blue = Color.getHSBColor(0.667f, 1.000f, 0.502f); + public Color D_Magenta = Color.getHSBColor(0.833f, 1.000f, 0.502f); + public Color D_Green = Color.getHSBColor(0.333f, 1.000f, 0.502f); + public Color D_Yellow = Color.getHSBColor(0.167f, 1.000f, 0.502f); + public Color D_Cyan = Color.getHSBColor(0.500f, 1.000f, 0.502f); + public Color D_White = Color.getHSBColor(0.000f, 0.000f, 0.753f); + public Color B_Black = Color.getHSBColor(0.000f, 0.000f, 0.502f); + public Color B_Red = Color.getHSBColor(0.000f, 1.000f, 1.000f); + public Color B_Blue = Color.getHSBColor(0.667f, 1.000f, 1.000f); + public Color B_Magenta = Color.getHSBColor(0.833f, 1.000f, 1.000f); + public Color B_Green = Color.getHSBColor(0.333f, 1.000f, 1.000f); + public Color B_Yellow = Color.getHSBColor(0.167f, 1.000f, 1.000f).darker(); + public Color B_Cyan = Color.getHSBColor(0.500f, 1.000f, 1.000f); + public Color B_White = Color.getHSBColor(0.000f, 0.000f, 1.000f); + public Color cReset = Color.BLACK;//Color.getHSBColor(0.000f, 0.000f, 1.000f); + public Color[] COLS = new Color[]{ + D_Black, D_Red, D_Green, D_Yellow, D_Blue, D_Magenta, D_Cyan, D_White, + B_Black, B_Red, B_Green, B_Yellow, B_Blue, B_Magenta, B_Cyan, B_White, + }; + // public static Color colorCurrent = Color.WHITE;//cReset; + String remaining = ""; + PrintStream ps; + + public AnsiTermPane(boolean darkMode) { + setDarkMode(darkMode); + } + + public String colorName(Color c) { + if (c.equals(cReset)) { + return "reset"; + } + if (c.equals(D_Black)) { + return "D_Black"; + } + if (c.equals(D_Red)) { + return "D_Red"; + } + if (c.equals(D_Green)) { + return "D_Green"; + } + if (c.equals(D_Yellow)) { + return "D_Yellow"; + } + if (c.equals(D_Blue)) { + return "D_Blue"; + } + if (c.equals(D_Magenta)) { + return "D_Magenta"; + } + if (c.equals(D_Cyan)) { + return "D_Cyan"; + } + if (c.equals(D_White)) { + return "D_White"; + } + if (c.equals(B_Black)) { + return "B_Black"; + } + if (c.equals(B_Red)) { + return "B_Red"; + } + if (c.equals(B_Green)) { + return "B_Green"; + } + if (c.equals(B_Yellow)) { + return "B_Yellow"; + } + if (c.equals(B_Blue)) { + return "B_Blue"; + } + if (c.equals(B_Magenta)) { + return "B_Magenta"; + } + if (c.equals(B_Cyan)) { + return "B_Cyan"; + } + if (c.equals(B_White)) { + return "B_White"; + } + return "?"; + } + + public void setDarkMode(boolean darkMode) { + cReset = darkMode ? Color.WHITE : Color.BLACK; +// setForeground(Color.WHITE); + setFont(new Font("Courier New", Font.PLAIN, 14)); + setForeground(cReset); + colorForeground = cReset; + if (darkMode) { + D_Blue = new Color(124, 124, 220); + B_Blue = new Color(162, 162, 225); + B_White = new Color(255, 255, 255); + D_Red = new Color(200, 0, 0); + D_Magenta = new Color(142, 57, 137); + setBackground(new Color(22, 22, 22)); + } else { + D_Blue = Color.getHSBColor(0.667f, 1.000f, 0.502f); + B_Blue = Color.getHSBColor(0.667f, 1.000f, 1.000f); + B_White = new Color(0, 0, 0); + D_Red = Color.getHSBColor(0.000f, 1.000f, 0.502f); + D_Magenta = Color.getHSBColor(0.833f, 1.000f, 0.502f); + setBackground(new Color(250, 250, 250)); + } + COLS = new Color[]{ + D_Black, D_Red, D_Green, D_Yellow, D_Blue, D_Magenta, D_Cyan, D_White, + B_Black, B_Red, B_Green, B_Yellow, B_Blue, B_Magenta, B_Cyan, B_White, + }; + } + + public void clearLastLine() { + Element root = getDocument().getDefaultRootElement(); + if (root.getElementCount() > 0) { + Element first = root.getElement(root.getElementCount() - 1); + try { + getDocument().remove(first.getStartOffset(), first.getEndOffset()); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + } + + public Color color256(int c) { + if (c < 0) { + c = 0; + } + if (c < 16) { + c = Math.abs(c) % COLS.length; + if (c == 0) { + return cReset; + } else { + return COLS[c]; + } + } + if (c <= 231) { + c = c - 16; + int r = (c / 36); + int g = ((c % 36) / 6); + int b = (c % 6); + // 36 * r + 6 * g + b (0 ≤ r, g, b ≤ 5) + r = r * 255 / 5; + g = g * 255 / 5; + b = b * 255 / 5; + return new Color(r, g, b); + } else { + int i = c - 232; + int r = 255 * i / 24; + return new Color(r, r, r); + } + } + + + public void append(int c, String s) { + append(color256(c), s); + } + + public void append(Color c, String s) { +// System.out.println(">>"+colorName(c)+" : "+s); + StyleContext sc = StyleContext.getDefaultStyleContext(); + AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, c); + int len = getDocument().getLength(); // same value as getText().length(); + boolean editable = isEditable(); + setEditable(true); + setCaretPosition(len); // place caret at the end (with no selection) + setCharacterAttributes(aset, false); + replaceSelection(s); // there is no selection, so inserts at caret + setEditable(editable); + } + + public PrintStream asPrintStream() { + if (ps == null) { + ps = new PrintStream( + new OutputStream() { + @Override + public void write(int b) throws IOException { + UI.withinGUI(()->{ + appendANSI(String.valueOf((char) b)); + }); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + UI.withinGUI(()->{ + appendANSI(new String(b, off, len)); + }); + } + } + ); + } + return ps; + } + + public void printlnAnsi(String s) { + appendANSI(s); + appendANSI("\n"); + } + + public int endOfEscape(String s, int pos) { + for (int i = pos; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '\u001B' || (c >= '0' && c <= '9') || c == ';' || c == '[') { + continue; + } else { + return i; + } + } + return -1; + } + + public void appendANSI(String s) { // convert ANSI color codes first + int aPos = 0; // current char position in addString + int aIndex = 0; // index of next Escape sequence + int mIndex = 0; // index of "m" terminating Escape sequence + String tmpString = ""; + boolean stillSearching = true; // true until no more Escape sequences + String addString = remaining + s; + remaining = ""; + + if (addString.length() > 0) { + aIndex = addString.indexOf("\u001B"); // find first escape + if (aIndex == -1) { // no escape/color change in this string, so just send it with current color + append(colorForeground, addString); + return; + } +// otherwise There is an escape character in the string, so we must process it + + if (aIndex > 0) { // Escape is not first char, so send text up to first escape + tmpString = addString.substring(0, aIndex); + append(colorForeground, tmpString); + aPos = aIndex; + } +// aPos is now at the beginning of the first escape sequence + + stillSearching = true; + while (stillSearching) { + mIndex = endOfEscape(addString, aPos); // find the end of the escape sequence + if (mIndex < 0) { // the buffer ends halfway through the ansi string! + remaining = addString.substring(aPos); + stillSearching = false; + continue; + } else { + tmpString = addString.substring(aPos, mIndex + 1); + getANSIColor(tmpString); + } + aPos = mIndex + 1; +// now we have the color, send text that is in that color (up to next escape) + + aIndex = addString.indexOf("\u001B", aPos); + + if (aIndex == -1) { // if that was the last sequence of the input, send remaining text + tmpString = addString.substring(aPos); + append(colorForeground, tmpString); + stillSearching = false; + continue; // jump out of loop early, as the whole string has been sent now + } + + // there is another escape sequence, so send part of the string and prepare for the next + tmpString = addString.substring(aPos, aIndex); + aPos = aIndex; + append(colorForeground, tmpString); + + } // while there's text in the input buffer + } + } + + public void getANSIColor(String ANSIColor) { + Pattern p = Pattern.compile("\u001B\\[(?\\d+)(;(?\\d+)(;(?\\d+)(;(?\\d+)(;(?\\d+))?)?)?)?m"); + Matcher m = p.matcher(ANSIColor); + if (m.find()) { + int a = Integer.parseInt(m.group("a")); + int b = m.group("b") == null ? -1 : Integer.parseInt(m.group("b")); + int c = m.group("c") == null ? -1 : Integer.parseInt(m.group("c")); + int d = m.group("d") == null ? -1 : Integer.parseInt(m.group("d")); + int e = m.group("e") == null ? -1 : Integer.parseInt(m.group("e")); + switch (a) { + case 0: { + colorForeground = color256(0); + break; + } + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: { + colorForeground = color256(a - 30); + break; + } + case 38: { + switch (b) { + case 5: { + colorForeground = color256(c); + break; + } + case 2: { + int rr = valid255(c); + int gg = valid255(d); + int bb = valid255(e); + colorForeground = new Color(rr, gg, bb); + break; + } + } + break; + } + case 48: { + switch (b) { + case 5: { + if (c == 0) { + colorBackground = null; + } else { + colorBackground = color256(c); + } + break; + } + case 2: { + int rr = valid255(c); + int gg = valid255(d); + int bb = valid255(e); + colorBackground = new Color(rr, gg, bb); + break; + } + } + } + } + return; + } + switch (ANSIColor) { + default: { + //colorCurrent = cReset; + break; + } + } + } + + private int valid255(int c) { + if (c < 0) { + return 0; + } + if (c > 255) { + return 255; + } + return c; + } + + public void clearScreen() { + setText(""); + } +} diff --git a/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/GBC.java b/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/GBC.java new file mode 100644 index 000000000..38214f400 --- /dev/null +++ b/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/GBC.java @@ -0,0 +1,155 @@ +package net.thevpc.nuts.lib.nswing; + +import java.awt.*; + +public class GBC { + GridBagConstraints b = new GridBagConstraints(); + + public static GBC of(int x, int y) { + return of().at(x, y); + } + + public static GBC ofAt(int x, int y) { + return of().at(x, y); + } + + public GBC ipad(int x, int y) { + b.ipadx = x; + b.ipady = y; + return this; + } + + public GBC weightx(int x) { + b.weightx = x; + return this; + } + + public GBC weighty(int y) { + b.weighty = y; + return this; + } + + public GBC weight(int x) { + return weight(x,x); + } + + public GBC weight(int x, int y) { + b.weightx = x; + b.weighty = y; + return this; + } + + public GBC at(int x, int y) { + b.gridx = x; + b.gridy = y; + return this; + } + + public static GBC of() { + return new GBC(); + } + + public GBC fillNone() { + b.fill = GridBagConstraints.NONE; + return this; + } + + public GBC fillVertical() { + b.fill = GridBagConstraints.VERTICAL; + return this; + } + + public GBC fillHorizontal() { + b.fill = GridBagConstraints.HORIZONTAL; + return this; + } + + public GBC fillBoth() { + b.fill = GridBagConstraints.BOTH; + return this; + } + + public GridBagConstraints build() { + return b; + } + + public GBC anchorWest() { + return anchor(GridBagConstraints.WEST); + } + + public GBC anchorEast() { + return anchor(GridBagConstraints.EAST); + } + + public GBC anchorSouth() { + return anchor(GridBagConstraints.SOUTH); + } + + public GBC anchorCenter() { + return anchor(GridBagConstraints.CENTER); + } + + public GBC anchorNorthWest() { + return anchor(GridBagConstraints.NORTHWEST); + } + + public GBC anchorNorthEast() { + return anchor(GridBagConstraints.NORTHEAST); + } + public GBC anchorNorth() { + return anchor(GridBagConstraints.NORTH); + } + + public GBC anchor(int a) { + this.b.anchor = a; + return this; + } + + public GBC colspanReminder() { + b.gridwidth = GridBagConstraints.REMAINDER; + return this; + } + + public GBC colspanRelative() { + b.gridwidth = GridBagConstraints.RELATIVE; + return this; + } + + public GBC colspan(int c) { + b.gridwidth = c; + return this; + } + + public GBC rowspan(int c) { + b.gridheight = c; + return this; + } + + public GBC rowspanReminder() { + b.gridheight = GridBagConstraints.REMAINDER; + return this; + } + + public GBC rowspanRelative() { + b.gridheight = GridBagConstraints.RELATIVE; + return this; + } + + public GBC insets(int v, int h) { + return insets(new Insets(v, h, v, h)); + } + + public GBC insets(int top,int left,int bottom,int right) { + return insets(new Insets(top, left, bottom, right)); + } + + public GBC insets(int i) { + return insets(new Insets(i, i, i, i)); + } + + public GBC insets(Insets i) { + b.insets = i; + return this; + } + +} diff --git a/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/PlaceholderTextField.java b/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/PlaceholderTextField.java new file mode 100644 index 000000000..427648ce8 --- /dev/null +++ b/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/PlaceholderTextField.java @@ -0,0 +1,60 @@ +package net.thevpc.nuts.lib.nswing; + +import javax.swing.*; +import javax.swing.text.Document; +import java.awt.*; + +public class PlaceholderTextField extends JTextField { + + + private String placeholder; + + public PlaceholderTextField() { + } + + public PlaceholderTextField( + final Document pDoc, + final String pText, + final int pColumns) { + super(pDoc, pText, pColumns); + } + + public PlaceholderTextField(final int pColumns) { + super(pColumns); + } + + public PlaceholderTextField(final String pText) { + super(pText); + } + + public PlaceholderTextField(final String pText, final int pColumns) { + super(pText, pColumns); + } + + public String getPlaceholder() { + return placeholder; + } + + @Override + protected void paintComponent(final Graphics pG) { + super.paintComponent(pG); + + if (placeholder == null || placeholder.length() == 0 || getText().length() > 0) { + return; + } + + final Graphics2D g = (Graphics2D) pG; + g.setRenderingHint( + RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(getDisabledTextColor()); + g.drawString(placeholder, getInsets().left, pG.getFontMetrics() + .getMaxAscent() + getInsets().top); + } + + public PlaceholderTextField setPlaceholder(final String s) { + placeholder = s; + return this; + } + +} diff --git a/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/UI.java b/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/UI.java new file mode 100644 index 000000000..623296dc9 --- /dev/null +++ b/libraries/nlib-swing/src/main/java/net/thevpc/nuts/lib/nswing/UI.java @@ -0,0 +1,24 @@ +package net.thevpc.nuts.lib.nswing; + +import javax.swing.*; +import java.lang.reflect.InvocationTargetException; + +public class UI { + public static void async(Runnable r){ + new Thread(r).start(); + } + + public static void withinGUI(Runnable r){ + if(SwingUtilities.isEventDispatchThread()){ + r.run(); + }else{ + try { + SwingUtilities.invokeAndWait(r); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/libraries/pom.xml b/libraries/pom.xml index 3e4b22547..069d09afb 100755 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -15,6 +15,7 @@ nlib-template nlib-tomcat-classloader nlib-spring-boot + nlib-swing