* System.in/out to a command-line ECMA-48 / ANSI X3.64 type terminal
(tested on Linux + xterm). I/O is handled through terminal escape
sequences generated by the library itself: ncurses is not required
- or linked to. xterm mouse tracking using UTF8 coordinates is
- supported. For the demo application, this is the default backend on
- non-Windows platforms.
+ or linked to. xterm mouse tracking using UTF8 and SGR coordinates
+ are supported. For the demo application, this is the default
+ backend on non-Windows platforms.
* Java Swing UI. This backend can be selected by setting
jexer.Swing=true. The default window size for Swing is 132x40,
input (see the ENABLE_LINE_INPUT flag for GetConsoleMode() and
SetConsoleMode()).
+ - TTerminalWindow launches 'script -fqe /dev/null' on non-Windows
+ platforms. This is a workaround for the C library behavior of
+ checking for a tty: script launches $SHELL in a pseudo-tty. This
+ works on Linux but might not on other Posix-y platforms.
+
ECMA48 Backend
--------------
-
- - Mouse support for BackendType.ECMA48/XTERM currently requires UTF-8
- coordinates (1005 mode). Terminals that support UTF-8 mouse coordinates
- include xterm, rxvt-unicode, gnome-terminal, and konsole. Due to Java's
- InputStreamReader requirement of a valid UTF-8 stream, one must assume
- the terminal will always generate correct UTF-8 bytes. Mode 1006 (SGR)
- will be supported in a future release.
+
+ - Java's InputStreamReader requires a valid UTF-8 stream. The
+ default X10 encoding for mouse coordinates outside (160,94) can
+ corrupt that stream, at best putting garbage keyboard events in
+ the input queue but at worst causing the backend reader thread to
+ throw an Exception and exit and make the entire UI unusable.
+ Mouse support therefore requires a terminal that can deliver
+ either UTF-8 coordinates (1005 mode) or SGR coordinates (1006
+ mode). Most modern terminals can do this.
+
+ Use of 'stty'
+ -------------
+
+ - jexer.session.TTYSession calls 'stty size' once every second to
+ check the current window size, performing the same function as
+ ioctl(TIOCGWINSZ) but without requiring a native library.
+
+ - jexer.io.ECMA48Terminal calls 'stty' to perform the equivalent of
+ cfmakeraw(). The terminal is (blindly!) put back in 'stty sane
+ cooked' mode when exiting.
+
+
+System Properties
+-----------------
+
+The following properties control features of Jexer:
+
+ jexer.Swing
+ -----------
+
+ Used only by jexer.demos.Demo1. If true, use the Swing interface
+ for the demo application. Default: true on Windows platforms
+ (os.name starts with "Windows"), false on non-Windows platforms.
+
+ jexer.Swing.cursorStyle
+ -----------------------
+
+ Used by jexer.io.SwingScreen. Selects the cursor style to draw.
+ Valid values are: underline, block, outline. Default: underline.
0.0.2: STABILIZE EXISTING
-- TTerminalWindow
- - Expose shell commands as properties
-- Swing:
- - Blinking cursor
- - Block cursor
- ECMA48Backend running on socket
-- TFileOpen
-- Document any properties used
- - Expose use of 'stty'
0.0.3: FINISH PORTING
- Also add keyboard navigation
- TDirectoryList
- Also add keyboard navigation
+- TFileOpen
0.0.4: NEW STUFF
0.0.5: BUG HUNT
- TSubMenu keyboard mnemonic not working
-- ECMA48Terminal
- - Mode 1006 mouse coordinates
0.1.0: BETA RELEASE
![Several Windows Open Including A Terminal](/screenshots/screenshot1.png?raw=true "Several Windows Open Including A Terminal")
![Yo Dawg...](/screenshots/yodawg.png?raw=true "Yo Dawg, I heard you like text windowing systems, so I ran a text windowing system inside your text windowing system so you can have a terminal in your terminal.")
-
CSI_ENTRY,
CSI_PARAM,
// CSI_INTERMEDIATE,
- MOUSE
+ MOUSE,
+ MOUSE_SGR,
}
/**
eventMouseWheelUp, eventMouseWheelDown);
}
+ /**
+ * Produce mouse events based on "Any event tracking" and SGR
+ * coordinates. See
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
+ *
+ * @param release if true, this was a release ('m')
+ * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
+ */
+ private TInputEvent parseMouseSGR(final boolean release) {
+ // SGR extended coordinates - mode 1006
+ if (params.size() < 3) {
+ // Invalid position, bail out.
+ return null;
+ }
+ int buttons = Integer.parseInt(params.get(0));
+ int x = Integer.parseInt(params.get(1)) - 1;
+ int y = Integer.parseInt(params.get(2)) - 1;
+
+ // Clamp X and Y to the physical screen coordinates.
+ if (x >= windowResize.getWidth()) {
+ x = windowResize.getWidth() - 1;
+ }
+ if (y >= windowResize.getHeight()) {
+ y = windowResize.getHeight() - 1;
+ }
+
+ TMouseEvent.Type eventType = TMouseEvent.Type.MOUSE_DOWN;
+ boolean eventMouse1 = false;
+ boolean eventMouse2 = false;
+ boolean eventMouse3 = false;
+ boolean eventMouseWheelUp = false;
+ boolean eventMouseWheelDown = false;
+
+ if (release) {
+ eventType = TMouseEvent.Type.MOUSE_UP;
+ }
+
+ switch (buttons) {
+ case 0:
+ eventMouse1 = true;
+ break;
+ case 1:
+ eventMouse2 = true;
+ break;
+ case 2:
+ eventMouse3 = true;
+ break;
+ case 35:
+ // Motion only, no buttons down
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 32:
+ // Dragging with mouse1 down
+ eventMouse1 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 33:
+ // Dragging with mouse2 down
+ eventMouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 34:
+ // Dragging with mouse3 down
+ eventMouse3 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 96:
+ // Dragging with mouse2 down after wheelUp
+ eventMouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 97:
+ // Dragging with mouse2 down after wheelDown
+ eventMouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 64:
+ eventMouseWheelUp = true;
+ break;
+
+ case 65:
+ eventMouseWheelDown = true;
+ break;
+
+ default:
+ // Unknown, bail out
+ return null;
+ }
+ return new TMouseEvent(eventType, x, y, x, y,
+ eventMouse1, eventMouse2, eventMouse3,
+ eventMouseWheelUp, eventMouseWheelDown);
+ }
+
/**
* Return any events in the IO queue.
*
// Mouse position
state = ParseState.MOUSE;
return;
+ case '<':
+ // Mouse position, SGR (1006) coordinates
+ state = ParseState.MOUSE_SGR;
+ return;
default:
break;
}
reset();
return;
+ case MOUSE_SGR:
+ // Numbers - parameter values
+ if ((ch >= '0') && (ch <= '9')) {
+ params.set(params.size() - 1,
+ params.get(params.size() - 1) + ch);
+ return;
+ }
+ // Parameter separator
+ if (ch == ';') {
+ params.add("");
+ return;
+ }
+
+ switch (ch) {
+ case 'M':
+ // Generate a mouse press event
+ TInputEvent event = parseMouseSGR(false);
+ if (event != null) {
+ events.add(event);
+ }
+ reset();
+ return;
+ case 'm':
+ // Generate a mouse release event
+ event = parseMouseSGR(true);
+ if (event != null) {
+ events.add(event);
+ }
+ reset();
+ return;
+ default:
+ break;
+ }
+
+ // Unknown keystroke, ignore
+ reset();
+ return;
+
case CSI_PARAM:
// Numbers - parameter values
if ((ch >= '0') && (ch <= '9')) {
* @param on if true, enable metaSendsEscape
* @return the string to emit to xterm
*/
- public String xtermMetaSendsEscape(final boolean on) {
+ private String xtermMetaSendsEscape(final boolean on) {
if (on) {
return "\033[?1036h\033[?1034l";
}
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[31;1m"
*/
- public String addHeaderSGR(String str) {
+ private String addHeaderSGR(String str) {
if (str.length() > 0) {
// Nix any trailing ';' because that resets all attributes
while (str.endsWith(":")) {
}
/**
- * Create a SGR parameter sequence for a single color change.
+ * Create a SGR parameter sequence for a single color change. Note
+ * package private access.
*
* @param color one of the Color.WHITE, Color.BLUE, etc. constants
* @param foreground if true, this is a foreground color
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[42m"
*/
- public String color(final Color color, final boolean foreground) {
+ String color(final Color color, final boolean foreground) {
return color(color, foreground, true);
}
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[42m"
*/
- public String color(final Color color, final boolean foreground,
+ private String color(final Color color, final boolean foreground,
final boolean header) {
int ecmaColor = color.getValue();
}
/**
- * Create a SGR parameter sequence for both foreground and
- * background color change.
+ * Create a SGR parameter sequence for both foreground and background
+ * color change. Note package private access.
*
* @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
* @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[31;42m"
*/
- public String color(final Color foreColor, final Color backColor) {
+ String color(final Color foreColor, final Color backColor) {
return color(foreColor, backColor, true);
}
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[31;42m"
*/
- public String color(final Color foreColor, final Color backColor,
+ private String color(final Color foreColor, final Color backColor,
final boolean header) {
int ecmaForeColor = foreColor.getValue();
/**
* Create a SGR parameter sequence for foreground, background, and
* several attributes. This sequence first resets all attributes to
- * default, then sets attributes as per the parameters.
+ * default, then sets attributes as per the parameters. Note package
+ * private access.
*
* @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
* @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[0;1;31;42m"
*/
- public String color(final Color foreColor, final Color backColor,
+ String color(final Color foreColor, final Color backColor,
final boolean bold, final boolean reverse, final boolean blink,
final boolean underline) {
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[7m"
*/
- public String reverse(final boolean on) {
+ private String reverse(final boolean on) {
if (on) {
return "\033[7m";
}
}
/**
- * Create a SGR parameter sequence to reset to defaults.
+ * Create a SGR parameter sequence to reset to defaults. Note package
+ * private access.
*
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[0m"
*/
- public String normal() {
+ String normal() {
return normal(true);
}
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[0m"
*/
- public String normal(final boolean header) {
+ private String normal(final boolean header) {
if (header) {
return "\033[0;37;40m";
}
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[1m"
*/
- public String bold(final boolean on) {
+ private String bold(final boolean on) {
return bold(on, true);
}
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[1m"
*/
- public String bold(final boolean on, final boolean header) {
+ private String bold(final boolean on, final boolean header) {
if (header) {
if (on) {
return "\033[1m";
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[5m"
*/
- public String blink(final boolean on) {
+ private String blink(final boolean on) {
return blink(on, true);
}
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[5m"
*/
- public String blink(final boolean on, final boolean header) {
+ private String blink(final boolean on, final boolean header) {
if (header) {
if (on) {
return "\033[5m";
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[4m"
*/
- public String underline(final boolean on) {
+ private String underline(final boolean on) {
if (on) {
return "\033[4m";
}
}
/**
- * Create a SGR parameter sequence for enabling the visible cursor.
+ * Create a SGR parameter sequence for enabling the visible cursor. Note
+ * package private access.
*
* @param on if true, turn on cursor
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- public String cursor(final boolean on) {
+ String cursor(final boolean on) {
if (on && !cursorOn) {
cursorOn = true;
return "\033[?25h";
/**
* Clear the line from the cursor (inclusive) to the end of the screen.
* Because some terminals use back-color-erase, set the color to
- * white-on-black beforehand.
+ * white-on-black beforehand. Note package private access.
*
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- public String clearRemainingLine() {
+ String clearRemainingLine() {
return "\033[0;37;40m\033[K";
}
*
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- public String clearPreceedingLine() {
+ private String clearPreceedingLine() {
return "\033[0;37;40m\033[1K";
}
*
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- public String clearLine() {
+ private String clearLine() {
return "\033[0;37;40m\033[2K";
}
*
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- public String home() {
+ private String home() {
return "\033[H";
}
/**
- * Move the cursor to (x, y).
+ * Move the cursor to (x, y). Note package private access.
*
* @param x column coordinate. 0 is the left-most column.
* @param y row coordinate. 0 is the top-most row.
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- public String gotoXY(final int x, final int y) {
+ String gotoXY(final int x, final int y) {
return String.format("\033[%d;%dH", y + 1, x + 1);
}
/**
* Tell (u)xterm that we want to receive mouse events based on "Any event
- * tracking" and UTF-8 coordinates. See
+ * tracking", UTF-8 coordinates, and then SGR coordinates. Ideally we
+ * will end up with SGR coordinates with UTF-8 coordinates as a fallback.
+ * See
* http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
*
* Note that this also sets the alternate/primary screen buffer.
* buffer.
* @return the string to emit to xterm
*/
- public String mouse(final boolean on) {
+ private String mouse(final boolean on) {
if (on) {
- return "\033[?1003;1005h\033[?1049h";
+ return "\033[?1003;1005;1006h\033[?1049h";
}
- return "\033[?1003;1005l\033[?1049l";
+ return "\033[?1003;1006;1005l\033[?1049l";
}
/**
*/
package jexer.io;
-import jexer.bits.Cell;
-import jexer.bits.CellAttributes;
-import jexer.session.SwingSessionInfo;
-
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.InputStream;
+import java.util.Date;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
+import jexer.bits.Cell;
+import jexer.bits.CellAttributes;
+import jexer.session.SwingSessionInfo;
+
/**
* This Screen implementation draws to a Java Swing JFrame.
*/
public final class SwingScreen extends Screen {
+ /**
+ * Cursor style to draw.
+ */
+ public enum CursorStyle {
+ /**
+ * Use an underscore for the cursor.
+ */
+ UNDERLINE,
+
+ /**
+ * Use a solid block for the cursor.
+ */
+ BLOCK,
+
+ /**
+ * Use an outlined block for the cursor.
+ */
+ OUTLINE
+ }
+
private static Color MYBLACK;
private static Color MYRED;
private static Color MYGREEN;
private int maxDescent = 0;
/**
- * Top pixel value.
+ * Top pixel absolute location.
*/
private int top = 30;
/**
- * Left pixel value.
+ * Left pixel absolute location.
*/
private int left = 30;
+ /**
+ * The cursor style to draw.
+ */
+ private CursorStyle cursorStyle = CursorStyle.UNDERLINE;
+
+ /**
+ * The number of millis to wait before switching the blink from
+ * visible to invisible.
+ */
+ private long blinkMillis = 500;
+
+ /**
+ * If true, the cursor should be visible right now based on the blink
+ * time.
+ */
+ private boolean cursorBlinkVisible = true;
+
+ /**
+ * The time that the blink last flipped from visible to invisible or
+ * from invisible to visible.
+ */
+ private long lastBlinkTime = 0;
+
/**
* Convert a CellAttributes foreground color to an Swing Color.
*
this.screen = screen;
setDOSColors();
+ // Figure out my cursor style
+ String cursorStyleString = System.getProperty("jexer.Swing.cursorStyle",
+ "underline").toLowerCase();
+
+ if (cursorStyleString.equals("underline")) {
+ cursorStyle = CursorStyle.UNDERLINE;
+ } else if (cursorStyleString.equals("outline")) {
+ cursorStyle = CursorStyle.OUTLINE;
+ } else if (cursorStyleString.equals("block")) {
+ cursorStyle = CursorStyle.BLOCK;
+ }
+
setTitle("Jexer Application");
setBackground(Color.black);
- // setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
- // setFont(new Font("Liberation Mono", Font.BOLD, 16));
- // setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16));
try {
// Always try to use Terminus, the one decent font.
return;
}
+ // See if it is time to flip the blink time.
+ long nowTime = (new Date()).getTime();
+ if (nowTime > blinkMillis + lastBlinkTime) {
+ lastBlinkTime = nowTime;
+ cursorBlinkVisible = !cursorBlinkVisible;
+ }
+
int xCellMin = 0;
int xCellMax = screen.width;
int yCellMin = 0;
}
// Draw the cursor if it is visible
- if ((cursorVisible)
+ if (cursorVisible
&& (cursorY <= screen.height - 1)
&& (cursorX <= screen.width - 1)
+ && cursorBlinkVisible
) {
int xPixel = cursorX * textWidth + left;
int yPixel = cursorY * textHeight + top;
Cell lCell = screen.logical[cursorX][cursorY];
gr.setColor(attrToForegroundColor(lCell));
- gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
+ switch (cursorStyle) {
+ case UNDERLINE:
+ gr.fillRect(xPixel, yPixel + textHeight - 2,
+ textWidth, 2);
+ break;
+ case BLOCK:
+ gr.fillRect(xPixel, yPixel, textWidth, textHeight);
+ break;
+ case OUTLINE:
+ gr.drawRect(xPixel, yPixel, textWidth - 1,
+ textHeight - 1);
+ break;
+ }
}
dirty = false;
*/
@Override
public void putCursor(final boolean visible, final int x, final int y) {
- if ((cursorVisible)
+
+ if ((visible == cursorVisible) && ((x == cursorX) && (y == cursorY))) {
+ // See if it is time to flip the blink time.
+ long nowTime = (new Date()).getTime();
+ if (nowTime < frame.blinkMillis + frame.lastBlinkTime) {
+ // Nothing has changed, so don't do anything.
+ return;
+ }
+ }
+
+ if (cursorVisible
&& (cursorY <= height - 1)
&& (cursorX <= width - 1)
) {
*/
private enum MouseEncoding {
X10,
- UTF8
+ UTF8,
+ SGR
}
/**
mouseProtocol, mouseEncoding, mouse);
*/
- if (mouseEncoding != MouseEncoding.UTF8) {
+ if (mouseEncoding == MouseEncoding.X10) {
// We will support X10 but only for (160,94) and smaller.
if ((mouse.getX() >= 160) || (mouse.getY() >= 94)) {
return;
// Now encode the event
StringBuilder sb = new StringBuilder(6);
- sb.append((char) 0x1B);
- sb.append('[');
- sb.append('M');
- if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
- sb.append((char) (0x03 + 32));
- } else if (mouse.isMouse1()) {
- sb.append((char) (0x00 + 32));
- } else if (mouse.isMouse2()) {
- sb.append((char) (0x01 + 32));
- } else if (mouse.isMouse3()) {
- sb.append((char) (0x02 + 32));
- } else if (mouse.isMouseWheelUp()) {
- sb.append((char) (0x04 + 64));
- } else if (mouse.isMouseWheelDown()) {
- sb.append((char) (0x05 + 64));
+ if (mouseEncoding == MouseEncoding.SGR) {
+ sb.append((char) 0x1B);
+ sb.append("[<");
+
+ if (mouse.isMouse1()) {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+ sb.append("32;");
+ } else {
+ sb.append("0;");
+ }
+ } else if (mouse.isMouse2()) {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+ sb.append("33;");
+ } else {
+ sb.append("1;");
+ }
+ } else if (mouse.isMouse3()) {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+ sb.append("34;");
+ } else {
+ sb.append("2;");
+ }
+ } else if (mouse.isMouseWheelUp()) {
+ sb.append("64;");
+ } else if (mouse.isMouseWheelDown()) {
+ sb.append("65;");
+ } else {
+ // This is motion with no buttons down.
+ sb.append("35;");
+ }
+
+ sb.append(String.format("%d;%d", mouse.getX() + 1,
+ mouse.getY() + 1));
+
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+ sb.append("m");
+ } else {
+ sb.append("M");
+ }
+
} else {
- sb.append((char) (0x03 + 32));
- }
+ // X10 and UTF8 encodings
+ sb.append((char) 0x1B);
+ sb.append('[');
+ sb.append('M');
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+ sb.append((char) (0x03 + 32));
+ } else if (mouse.isMouse1()) {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+ sb.append((char) (0x00 + 32 + 32));
+ } else {
+ sb.append((char) (0x00 + 32));
+ }
+ } else if (mouse.isMouse2()) {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+ sb.append((char) (0x01 + 32 + 32));
+ } else {
+ sb.append((char) (0x01 + 32));
+ }
+ } else if (mouse.isMouse3()) {
+ if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+ sb.append((char) (0x02 + 32 + 32));
+ } else {
+ sb.append((char) (0x02 + 32));
+ }
+ } else if (mouse.isMouseWheelUp()) {
+ sb.append((char) (0x04 + 64));
+ } else if (mouse.isMouseWheelDown()) {
+ sb.append((char) (0x05 + 64));
+ } else {
+ // This is motion with no buttons down.
+ sb.append((char) (0x03 + 32));
+ }
- sb.append((char) (mouse.getX() + 33));
- sb.append((char) (mouse.getY() + 33));
+ sb.append((char) (mouse.getX() + 33));
+ sb.append((char) (mouse.getY() + 33));
+ }
// System.err.printf("Would write: \'%s\'\n", sb.toString());
writeRemote(sb.toString());
}
break;
+ case 1006:
+ if ((type == DeviceType.XTERM)
+ && (decPrivateModeFlag == true)
+ ) {
+ // Mouse: SGR coordinates
+ if (value == true) {
+ mouseEncoding = MouseEncoding.SGR;
+ } else {
+ mouseEncoding = MouseEncoding.X10;
+ }
+ }
+ break;
+
default:
break;