1 module dcc.curses.uput;
2 
3 private import std.string;
4 private import std.utf : count;
5 private import std.conv : to;
6 
7 private import deimos.ncurses.ncurses;
8 
9 int _uput_default_exit;
10 string uput(WINDOW* w, int y, int x, int length, string str, string prompt, out int exit = _uput_default_exit) {
11 /*
12 +------------------------[ WHAT YOU PUT IN ]-------------------------------+
13 |UPUT(y, x, length, fg, bg, str, exit)                                     |
14 +--------------------------------------------------------------------------+
15 |y -> Row where INPUT will start                                           |
16 |x -> Column where INPUT will start                                        |
17 |length -> Maximum length of INPUT                                         |
18 |str -> String to be edited                                                |
19 +---------------------[ WHAT YOU GET BACK ]--------------------------------+
20 |                                                                          |
21 | If UPUT is exited by the user pressing ESCAPE, then the FUNCTION will    |
22 | return the original string it was given (ie: no changes are made).  If   |
23 | UPUT is exited any other way (TAB, SHIFT-TAB, UP, DOWN, ENTER), then     |
24 | the edited string is returned.                                           |
25 |                                                                          |
26 | In either case, the SHARED variable "keyflag%" is returned with a value  |
27 | which is dependent on HOW UPUT was exited, following the chart below     |
28 |                                      +-----------------------------------+
29 | ESCAPE     -> keyflag = 5            |The values are based on the KEYPAD!|
30 | ENTER      -> keyflag = 0            +--------------+--------+-----------+
31 | UP ARROW   -> keyflag = 8            |       (7)    | UP(8)  | (9)       |
32 | DOWN ARROW -> keyflag = 2            +--------------+--------+-----------+
33 | TAB        -> keyflag = 6            |   SHFT-TAB(4)| ESC(5) |TAB(6)     |
34 | SHIFT-TAB  -> keyflag = 4            +--------------+--------+-----------+
35 |                                      |       (1)    | DOWN(2)|  (3)      |
36 |                                      +--------------+--------+-----------|
37 |                                      |    ENTER(0)  |                    |
38 +--------------------------------------+-----------------------------------+
39 */
40 	int exitflag = 0, curspos = cast(int)str.count, counter;
41 	dchar ky;
42 	string original = cast(string)str.dup;
43 
44 	string display = str;
45 	if (display.length > length) {
46 		display = display[0 .. length];
47 	}
48 
49 	int original_x = x;
50 	x += prompt.count;
51 
52 	keypad(w, true);
53 
54 	void fill(char c) {
55 		wmove(w, y, original_x);
56 		for (counter=0; counter < length; counter++) {
57 			waddch(w, ' ');
58 		}
59 		wmove(w, y, x);
60 	}
61 
62 	void print(string str, int offset = -1) {
63 		mvwprintw(w, y, original_x, "%s", prompt.toStringz());
64 
65 		if (offset >= 0) {
66 			wmove(w, y, x + offset);
67 		}
68 
69 		waddstr(w, str.toStringz());
70 	}
71 
72 	void move(int offset) {
73 		wmove(w, y, x + offset);
74 	}
75 
76 	int scroll_offset = 0;
77 
78 	do {
79 		fill(' ');
80 		print(display);
81 
82 		move(curspos);
83 
84 		curs_set(2);
85 		
86 		wrefresh(w);
87 		wget_wch(w, &ky);
88 		
89 		switch (ky) {
90 			case KEY_LEFT:
91 				if (curspos > 0) {
92 					curspos--;
93 				} else if (scroll_offset > 0) {
94 					scroll_offset--;
95 				}
96 
97 				break;
98 			case KEY_RIGHT:
99 				if (curspos < length - 1) {
100 					curspos++;
101 				} else if (str.count > length && scroll_offset + length <= str.count) {
102 					scroll_offset++;
103 				}
104 				break;
105 			case KEY_HOME:
106 				//case KEY_A1: =KEY_HOME on Linux so not required 
107 				curspos = 0;
108 				scroll_offset = 0;
109 				break;
110 			case KEY_END:
111 				//case KEY_C1: =KEY_END on Linux so not required
112 				str = str.stripRight();
113 				curspos = cast(int)str.count;
114 				if (curspos > length) {
115 					curspos = length - 1;
116 					scroll_offset = curspos - length + 1;
117 				}
118 				break;
119 			case KEY_DC: //delete key
120 				if (curspos > str.count - 1) {
121 					break;
122 				}
123 
124 				dstring utf32 = to!dstring(str.dup);
125 				str = to!string(utf32[0 .. curspos] ~ utf32[curspos + 1 .. $]);
126 				break;
127 			case 127:
128 			case KEY_BACKSPACE:
129 				if (curspos > 0) {
130 					dstring utf32 = to!dstring(str.dup);
131 					str = to!string(utf32[0 .. curspos - 1] ~ utf32[curspos .. $]);
132 					curspos--;
133 					
134 					if (scroll_offset > 0) {
135 						scroll_offset--;
136 					}
137 				}
138 				break;
139 			case 10: // enter
140 				exitflag = 10;
141 				break;
142 			case KEY_UP: // up-arrow
143 				exitflag = 8;
144 				break;
145 			case KEY_DOWN: // down-arrow
146 				exitflag = 2;
147 				break;
148 			case 9: // tab
149 				exitflag = 6;
150 				break;
151 			case KEY_BTAB: // shift-tab
152 				exitflag = 4;
153 				break;
154 			case 27: //esc
155 				str = original;
156 				exitflag = 5;
157 				break;
158 			default:
159 				if (curspos < str.count) {
160 					dstring utf32 = to!dstring(str.dup);
161 					ulong pos = curspos;
162 					str = to!string(utf32[0 .. pos] ~ ky ~ utf32[pos .. $]);
163 				} else {
164 					str ~= ky;
165 				}
166 
167 				curspos++;
168 
169 				if (curspos >= length - 1) {
170 					scroll_offset++;
171 				}
172 		}
173 
174 		dstring utf32 = to!dstring(str.dup);
175 		auto end = utf32.length < scroll_offset + length - 1 ? utf32.length : scroll_offset + length - 1;
176 		display = to!string(utf32[scroll_offset .. end]);
177 	} while (!exitflag);
178 
179 	exit = exitflag;
180 	return str.stripRight();
181 }