1 |
/* |
2 |
* LSCP Shell |
3 |
* |
4 |
* Copyright (c) 2014 Christian Schoenebeck |
5 |
* |
6 |
* This program is part of LinuxSampler and released under the same terms. |
7 |
*/ |
8 |
|
9 |
#ifndef CCURSOR_H |
10 |
#define CCURSOR_H |
11 |
|
12 |
#include <stdio.h> |
13 |
#include <stdlib.h> |
14 |
#include <iostream> |
15 |
#include <sstream> |
16 |
#include <string> |
17 |
|
18 |
#include "TerminalCtrl.h" |
19 |
#include "KeyboardReader.h" |
20 |
|
21 |
/** |
22 |
* Writes (and reads) "ANSI escape codes" to/from stdout/stdin to control the |
23 |
* position and appearance of the text input cursor on comand line terminals. |
24 |
* |
25 |
* This is a lite-weight class to prevent a dependency to i.e. libncurses. |
26 |
*/ |
27 |
class CCursor { |
28 |
public: |
29 |
CCursor() : m_row(-1), m_col(-1) {} |
30 |
virtual ~CCursor() {} |
31 |
|
32 |
/** |
33 |
* Returns a CCursor storing the current position of the cursor on screen. |
34 |
* That position can be altered with CCursors methods or restored at any |
35 |
* time by calling restore(). |
36 |
*/ |
37 |
static CCursor now() { |
38 |
CCursor cursor; |
39 |
TerminalSetting setting = TerminalCtrl::now(); |
40 |
TerminalCtrl::echoInput(false); |
41 |
|
42 |
// Workaround: obviously only one thread can read from stdin, so if |
43 |
// there is already 'KeyboardReader' listening to stdin, then use its |
44 |
// read buffers, otherwise read "directly" from stdin with |
45 |
// 'TerminalCtrl' class. |
46 |
KeyboardReader* reader = KeyboardReader::singleton(); |
47 |
if (reader) reader->callback(false); // disable its callback for a moment (so it won't deliver the input to somewhere) |
48 |
setCode("6n"); |
49 |
std::string s = (reader) ? reader->popStringToDelimiterSync('R') : TerminalCtrl::getStringToDelimiter('R'); |
50 |
if (reader) reader->callback(true); // re-enable its callback |
51 |
|
52 |
TerminalCtrl::restore(setting); |
53 |
char c; |
54 |
int row, col; |
55 |
int res = sscanf(s.c_str(), "%c[%d;%d", &c, &row, &col); |
56 |
if (res == 3) { |
57 |
cursor.m_row = row - 1; |
58 |
cursor.m_col = col - 1; |
59 |
} |
60 |
return cursor; |
61 |
} |
62 |
|
63 |
/** |
64 |
* Move the cursor on the terminal screen to the location stored in this |
65 |
* CCcursor object. |
66 |
*/ |
67 |
const CCursor& restore() const { |
68 |
if (!valid()) return *this; |
69 |
std::stringstream ss; |
70 |
ss << m_row+1 << ";" << m_col+1 << "f"; |
71 |
setCode(ss.str()); |
72 |
return *this; |
73 |
} |
74 |
|
75 |
/// Returns false if this object stores an invalid screen position. |
76 |
bool valid() const { |
77 |
return m_row >= 0 && m_col >= 0; |
78 |
} |
79 |
|
80 |
/** |
81 |
* Move the cursor on the terminal screen directly to the requested position |
82 |
* given by @a row and @a col. |
83 |
* |
84 |
* @returns CCursor object with new position |
85 |
*/ |
86 |
CCursor& moveTo(int row, int col) { |
87 |
if (row < 0 || col < 0) return *this; |
88 |
m_row = row; |
89 |
m_col = col; |
90 |
return const_cast<CCursor&>(restore()); |
91 |
} |
92 |
|
93 |
CCursor& toColumn(int n) { |
94 |
if (n < 0) return *this; |
95 |
m_col = n; |
96 |
return const_cast<CCursor&>(restore()); |
97 |
} |
98 |
|
99 |
CCursor& toRow(int n) { |
100 |
if (n < 0) return *this; |
101 |
m_row = n; |
102 |
return const_cast<CCursor&>(restore()); |
103 |
} |
104 |
|
105 |
CCursor& up(int n = 1) { |
106 |
if (n < 1) return *this; |
107 |
std::stringstream ss; |
108 |
ss << n << "A"; |
109 |
setCode(ss.str()); |
110 |
*this = now(); |
111 |
return *this; |
112 |
} |
113 |
|
114 |
CCursor& down(int n = 1) { |
115 |
if (n < 1) return *this; |
116 |
std::stringstream ss; |
117 |
ss << n << "B"; |
118 |
setCode(ss.str()); |
119 |
*this = now(); |
120 |
return *this; |
121 |
} |
122 |
|
123 |
CCursor& right(int n = 1) { |
124 |
if (n < 1) return *this; |
125 |
std::stringstream ss; |
126 |
ss << n << "C"; |
127 |
setCode(ss.str()); |
128 |
*this = now(); |
129 |
return *this; |
130 |
} |
131 |
|
132 |
CCursor& left(int n = 1) { |
133 |
if (n < 1) return *this; |
134 |
std::stringstream ss; |
135 |
ss << n << "D"; |
136 |
setCode(ss.str()); |
137 |
*this = now(); |
138 |
return *this; |
139 |
} |
140 |
|
141 |
int column() const { |
142 |
return m_col; |
143 |
} |
144 |
|
145 |
int row() const { |
146 |
return m_row; |
147 |
} |
148 |
|
149 |
CCursor& hide() { |
150 |
setCode("?25l"); |
151 |
return *this; |
152 |
} |
153 |
|
154 |
CCursor& show() { |
155 |
setCode("?25h"); |
156 |
return *this; |
157 |
} |
158 |
|
159 |
// NOTE: not ANSI.SYS! |
160 |
CCursor& lineDown(int n = 1) { |
161 |
if (n < 1) return *this; |
162 |
std::stringstream ss; |
163 |
ss << n << "E"; |
164 |
setCode(ss.str()); |
165 |
*this = now(); |
166 |
return *this; |
167 |
} |
168 |
|
169 |
// NOTE: not ANSI.SYS! |
170 |
CCursor& lineUp(int n = 1) { |
171 |
if (n < 1) return *this; |
172 |
std::stringstream ss; |
173 |
ss << n << "F"; |
174 |
setCode(ss.str()); |
175 |
*this = now(); |
176 |
return *this; |
177 |
} |
178 |
|
179 |
// NOTE: not ANSI.SYS! |
180 |
CCursor& scrollUp(int nLines = 1) { |
181 |
if (nLines < 1) return *this; |
182 |
std::stringstream ss; |
183 |
ss << nLines << "S"; |
184 |
setCode(ss.str()); |
185 |
*this = now(); |
186 |
return *this; |
187 |
} |
188 |
|
189 |
// NOTE: not ANSI.SYS! |
190 |
CCursor& scrollDown(int nLines = 1) { |
191 |
if (nLines < 1) return *this; |
192 |
std::stringstream ss; |
193 |
ss << nLines << "T"; |
194 |
setCode(ss.str()); |
195 |
*this = now(); |
196 |
return *this; |
197 |
} |
198 |
|
199 |
CCursor& clearScreen() { |
200 |
restore(); |
201 |
setCode("2J"); |
202 |
restore(); // allegedly cursor moves to beginning of screen on some systems, so restore |
203 |
return *this; |
204 |
} |
205 |
|
206 |
CCursor& clearVerticalToTop() { |
207 |
restore(); |
208 |
setCode("1J"); |
209 |
return *this; |
210 |
} |
211 |
|
212 |
CCursor& clearVerticalToBottom() { |
213 |
restore(); |
214 |
setCode("0J"); |
215 |
return *this; |
216 |
} |
217 |
|
218 |
CCursor& clearLineToRight() { |
219 |
restore(); |
220 |
setCode("0K"); |
221 |
return *this; |
222 |
} |
223 |
|
224 |
CCursor& clearLineToLeft() { |
225 |
restore(); |
226 |
setCode("1K"); |
227 |
return *this; |
228 |
} |
229 |
|
230 |
CCursor& clearLine() { |
231 |
restore(); |
232 |
setCode("2K"); |
233 |
return *this; |
234 |
} |
235 |
|
236 |
//static void save() { setCode("s"); } // barely supported by any terminal |
237 |
//static void restore() { setCode("u"); } // barely supported by any terminal |
238 |
protected: |
239 |
static void setCode(std::string sCode) { |
240 |
std::cout << modStr(sCode) << std::flush; |
241 |
} |
242 |
static std::string modStr(std::string code) { |
243 |
return "\033[" + code; |
244 |
} |
245 |
|
246 |
private: |
247 |
int m_row; |
248 |
int m_col; |
249 |
}; |
250 |
|
251 |
#endif // CCURSOR_H |