39 |
39 |
40 static const int PAGE_SIZE = 10; |
40 static const int PAGE_SIZE = 10; |
41 |
41 |
42 // ------------------------------------------------------------------------------------------------- |
42 // ------------------------------------------------------------------------------------------------- |
43 // |
43 // |
44 chtype Interface::color_pair (Color fg, Color bg) |
44 chtype Interface::getColorPair(Color fg, Color bg) |
45 { |
45 { |
46 if (fg == DEFAULT && bg == DEFAULT) |
46 if (fg == DEFAULT && bg == DEFAULT) |
47 return 0; |
47 return 0; |
48 else |
48 else |
49 return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg)); |
49 return COLOR_PAIR(1 + (int(fg) * NUM_COLORS) + int(bg)); |
50 } |
50 } |
51 |
51 |
52 // ------------------------------------------------------------------------------------------------- |
52 // ------------------------------------------------------------------------------------------------- |
53 // |
53 // |
54 const String& Interface::current_input() |
54 const String& Interface::getCurrentInput() |
55 { |
55 { |
56 return m_inputHistory[m_inputCursor]; |
56 return m_inputHistory[m_inputCursor]; |
57 } |
57 } |
58 |
58 |
59 // ------------------------------------------------------------------------------------------------- |
59 // ------------------------------------------------------------------------------------------------- |
60 // |
60 // |
61 // Makes current_input() the lastmost input (so that we won't modify history) |
61 // Makes current_input() the lastmost input(so that we won't modify history) |
62 // |
62 // |
63 void Interface::detach_input() |
63 void Interface::detachInput() |
64 { |
64 { |
65 if (m_inputCursor > 0) |
65 if (m_inputCursor > 0) |
66 { |
66 { |
67 m_inputHistory[0] = current_input(); |
67 m_inputHistory[0] = getCurrentInput(); |
68 m_inputCursor = 0; |
68 m_inputCursor = 0; |
69 } |
69 } |
70 } |
70 } |
71 |
71 |
72 // ------------------------------------------------------------------------------------------------- |
72 // ------------------------------------------------------------------------------------------------- |
73 // A version of current_input() that allows changing the contents of it. |
73 // A version of current_input() that allows changing the contents of it. |
74 // |
74 // |
75 String& Interface::mutable_current_input() |
75 String& Interface::getEditableInput() |
76 { |
76 { |
77 detach_input(); |
77 detachInput(); |
78 return m_inputHistory[m_inputCursor]; |
78 return m_inputHistory[m_inputCursor]; |
79 } |
79 } |
80 |
80 |
81 // ------------------------------------------------------------------------------------------------- |
81 // ------------------------------------------------------------------------------------------------- |
82 // |
82 // |
83 void Interface::move_input_cursor (int delta) |
83 void Interface::moveInputCursor(int delta) |
84 { |
84 { |
85 // No input history when inputting addresses or passwords |
85 // No input history when inputting addresses or passwords |
86 if (m_inputState != INPUTSTATE_NORMAL) |
86 if (m_inputState != INPUTSTATE_NORMAL) |
87 { |
87 { |
88 m_inputCursor = 0; |
88 m_inputCursor = 0; |
89 return; |
89 return; |
90 } |
90 } |
91 |
91 |
92 int oldcursor = m_inputCursor; |
92 int oldcursor = m_inputCursor; |
93 m_inputCursor = clamp (m_inputCursor + delta, 0, m_inputHistory.size() - 1); |
93 m_inputCursor = clamp(m_inputCursor + delta, 0, m_inputHistory.size() - 1); |
94 |
94 |
95 if (m_inputCursor != oldcursor) |
95 if (m_inputCursor != oldcursor) |
96 { |
96 { |
97 m_cursorPosition = current_input().length(); |
97 m_cursorPosition = getCurrentInput().length(); |
98 m_needInputRender = true; |
98 m_needInputRender = true; |
99 } |
99 } |
100 } |
100 } |
101 |
101 |
102 // ------------------------------------------------------------------------------------------------- |
102 // ------------------------------------------------------------------------------------------------- |
103 // |
103 // |
104 String Interface::prompt_string() |
104 String Interface::getPromptString() |
105 { |
105 { |
106 String prompt; |
106 String prompt; |
107 |
107 |
108 switch (m_inputState) |
108 switch (m_inputState) |
109 { |
109 { |
144 } |
144 } |
145 |
145 |
146 // ------------------------------------------------------------------------------------------------- |
146 // ------------------------------------------------------------------------------------------------- |
147 // |
147 // |
148 Interface::Interface() : |
148 Interface::Interface() : |
149 m_inputCursor (0), |
149 m_inputCursor(0), |
150 m_cursorPosition (0), |
150 m_cursorPosition(0), |
151 m_inputPanning (0), |
151 m_inputPanning(0), |
152 m_needRefresh (false), |
152 m_needRefresh(false), |
153 m_needStatusBarRender (false), |
153 m_needStatusBarRender(false), |
154 m_needInputRender (false), |
154 m_needInputRender(false), |
155 m_needOutputRender (false), |
155 m_needOutputRender(false), |
156 m_needNicklistRender (false), |
156 m_needNicklistRender(false), |
157 m_outputScroll (0), |
157 m_outputScroll(0), |
158 m_inputState (INPUTSTATE_NORMAL), |
158 m_inputState(INPUTSTATE_NORMAL), |
159 m_disconnectCallback (nullptr) |
159 m_disconnectCallback(nullptr) |
160 { |
160 { |
161 ::initscr(); |
161 ::initscr(); |
162 ::raw(); |
162 ::raw(); |
163 ::keypad (stdscr, true); |
163 ::keypad(stdscr, true); |
164 ::noecho(); |
164 ::noecho(); |
165 ::refresh(); |
165 ::refresh(); |
166 ::timeout (0); |
166 ::timeout(0); |
167 m_inputHistory.clear(); |
167 m_inputHistory.clear(); |
168 m_inputHistory << ""; |
168 m_inputHistory << ""; |
169 m_outputLines.clear(); |
169 m_outputLines.clear(); |
170 m_outputLines << ColoredLine(); |
170 m_outputLines << ColoredLine(); |
171 m_session.set_interface (this); |
171 m_session.set_interface(this); |
172 reset_title(); |
172 resetTitle(); |
173 |
173 |
174 if (::has_colors()) |
174 if (::has_colors()) |
175 { |
175 { |
176 ::start_color(); |
176 ::start_color(); |
177 bool hasDefaultColors = (::use_default_colors() == OK); |
177 bool hasDefaultColors =(::use_default_colors() == OK); |
178 int defaultFg = hasDefaultColors ? -1 : COLOR_WHITE; |
178 int defaultFg = hasDefaultColors ? -1 : COLOR_WHITE; |
179 int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK; |
179 int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK; |
180 |
180 |
181 // Initialize color pairs |
181 // Initialize color pairs |
182 for (int i = 0; i < NUM_COLORS; ++i) |
182 for (int i : range<int>(NUM_COLORS)) |
183 for (int j = 0; j < NUM_COLORS; ++j) |
183 for (int j : range<int>(NUM_COLORS)) |
184 { |
184 { |
185 int pairnum = 1 + (i * NUM_COLORS + j); |
185 int pairnum = 1 + (i * NUM_COLORS + j); |
186 int fg = (i == DEFAULT) ? defaultFg : i; |
186 int fg =(i == DEFAULT) ? defaultFg : i; |
187 int bg = (j == DEFAULT) ? defaultBg : j; |
187 int bg =(j == DEFAULT) ? defaultBg : j; |
188 |
188 |
189 if (fg != -1 || bg != -1) |
189 if (fg != -1 || bg != -1) |
190 { |
190 { |
191 if (::init_pair (pairnum, fg, bg) == ERR) |
191 if (::init_pair(pairnum, fg, bg) == ERR) |
192 print_warning ("Unable to initialize color pair %d (%d, %d)\n", pairnum, fg, bg); |
192 printWarning("Unable to initialize color pair %d(%d, %d)\n", pairnum, fg, bg); |
193 } |
193 } |
194 } |
194 } |
195 } |
195 } |
196 |
196 |
197 render_full(); |
197 renderFull(); |
198 refresh(); |
198 refresh(); |
199 m_needRefresh = false; |
199 m_needRefresh = false; |
200 } |
200 } |
201 |
201 |
202 // ------------------------------------------------------------------------------------------------- |
202 // ------------------------------------------------------------------------------------------------- |
203 // |
203 // |
204 void Interface::render_titlebar() |
204 void Interface::renderTitlebar() |
205 { |
205 { |
206 if (m_title.length() <= COLS) |
206 if (m_title.length() <= COLS) |
207 { |
207 { |
208 chtype pair = color_pair (WHITE, BLUE); |
208 chtype pair = getColorPair(WHITE, BLUE); |
209 int startx = (COLS - m_title.length()) / 2; |
209 int startx =(COLS - m_title.length()) / 2; |
210 int endx = startx + m_title.length(); |
210 int endx = startx + m_title.length(); |
211 attron (pair); |
211 attron(pair); |
212 mvprintw (0, startx, "%s", m_title.chars()); |
212 mvprintw(0, startx, "%s", m_title.chars()); |
213 mvhline (0, 0, ' ', startx); |
213 mvhline(0, 0, ' ', startx); |
214 mvhline (0, endx, ' ', COLS - endx); |
214 mvhline(0, endx, ' ', COLS - endx); |
215 attroff (pair); |
215 attroff(pair); |
216 } |
216 } |
217 |
217 |
218 m_needRefresh = true; |
218 m_needRefresh = true; |
219 } |
219 } |
220 |
220 |
221 // ------------------------------------------------------------------------------------------------- |
221 // ------------------------------------------------------------------------------------------------- |
222 // |
222 // |
223 void Interface::set_title (const String& title) |
223 void Interface::setTitle(const String& title) |
224 { |
224 { |
225 m_title = title; |
225 m_title = title; |
226 render_titlebar(); |
226 renderTitlebar(); |
227 } |
227 } |
228 |
228 |
229 // ------------------------------------------------------------------------------------------------- |
229 // ------------------------------------------------------------------------------------------------- |
230 // |
230 // |
231 void Interface::safe_disconnect (std::function<void(bool)> afterwards) |
231 void Interface::safeDisconnect(std::function<void(bool)> afterwards) |
232 { |
232 { |
233 if (m_session.is_active()) |
233 if (m_session.is_active()) |
234 { |
234 { |
235 m_disconnectCallback = afterwards; |
235 m_disconnectCallback = afterwards; |
236 set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION); |
236 setInputState(INPUTSTATE_CONFIRM_DISCONNECTION); |
237 } |
237 } |
238 else |
238 else |
239 afterwards(false); |
239 afterwards(false); |
240 } |
240 } |
241 |
241 |
242 // ------------------------------------------------------------------------------------------------- |
242 // ------------------------------------------------------------------------------------------------- |
243 // |
243 // |
244 int Interface::nicklist_width() |
244 int Interface::nicklistWidth() |
245 { |
245 { |
246 // Allocate at least 12 characters, at most 24 characters, for the nicklist. If we cannot |
246 // Allocate at least 12 characters, at most 24 characters, for the nicklist. If we cannot |
247 // afford that (o_O) then we probably shouldn't draw the nicklist at all I think. |
247 // afford that(o_O) then we probably shouldn't draw the nicklist at all I think. |
248 int nicklistWidth = COLS / 4; |
248 int nicklistWidth = COLS / 4; |
249 |
249 |
250 if (nicklistWidth < 12) |
250 if (nicklistWidth < 12) |
251 return 0; |
251 return 0; |
252 |
252 |
253 return min (nicklistWidth, 24); |
253 return min(nicklistWidth, 24); |
254 } |
254 } |
255 |
255 |
256 // ------------------------------------------------------------------------------------------------- |
256 // ------------------------------------------------------------------------------------------------- |
257 // Renders the given colored line onto the screen. Will wrap if allowWrap is true. Returns the |
257 // Renders the given colored line onto the screen. Will wrap if allowWrap is true. Returns the |
258 // 'y' value for the next line. |
258 // 'y' value for the next line. |
259 // |
259 // |
260 int Interface::render_colorline (int y, int x0, int width, const ColoredLine& line, bool allowWrap) |
260 int Interface::renderColorline(int y, int x0, int width, const ColoredLine& line, bool allowWrap) |
261 { |
261 { |
262 int x = x0; |
262 int x = x0; |
263 |
263 |
264 for (int i = 0; i < line.data().size(); ++i) |
264 for (int byte : line.data()) |
265 { |
265 { |
266 int byte = line.data()[i]; |
|
267 |
|
268 if (x == x0 + width) |
266 if (x == x0 + width) |
269 { |
267 { |
270 if (not allowWrap) |
268 if (not allowWrap) |
271 return y; |
269 return y; |
272 |
270 |
273 x = x0; |
271 x = x0; |
274 ++y; |
272 ++y; |
275 } |
273 } |
276 |
274 |
277 if (byte < 256 && isprint (byte)) |
275 if (byte < 256 && isprint(byte)) |
278 { |
276 { |
279 mvaddch (y, x, char (byte)); |
277 mvaddch(y, x, char(byte)); |
280 ++x; |
278 ++x; |
281 } |
279 } |
282 else if (byte >= RLINE_ON_COLOR and byte < (RLINE_ON_COLOR + 16)) |
280 else if (byte >= RLINE_ON_COLOR and byte <(RLINE_ON_COLOR + 16)) |
283 { |
281 { |
284 auto attrfunction = (byte < RLINE_OFF_COLOR ? &attron : &attroff); |
282 auto attrfunction =(byte < RLINE_OFF_COLOR ? &attron : &attroff); |
285 (*attrfunction) (color_pair (Color ((byte - RLINE_ON_COLOR) & 7), DEFAULT)); |
283 (*attrfunction)(getColorPair(Color((byte - RLINE_ON_COLOR) & 7), DEFAULT)); |
286 } |
284 } |
287 else switch (byte) |
285 else switch (byte) |
288 { |
286 { |
289 case RLINE_ON_BOLD: |
287 case RLINE_ON_BOLD: |
290 attron (A_BOLD); |
288 attron(A_BOLD); |
291 break; |
289 break; |
292 |
290 |
293 case RLINE_OFF_BOLD: |
291 case RLINE_OFF_BOLD: |
294 attroff (A_BOLD); |
292 attroff(A_BOLD); |
295 break; |
293 break; |
296 } |
294 } |
297 } |
295 } |
298 |
296 |
299 return y + 1; |
297 return y + 1; |
300 } |
298 } |
301 |
299 |
302 // ------------------------------------------------------------------------------------------------- |
300 // ------------------------------------------------------------------------------------------------- |
303 // |
301 // |
304 void Interface::render_output() |
302 void Interface::renderOutput() |
305 { |
303 { |
306 if (m_outputLines.size() == 1) |
304 if (m_outputLines.size() == 1) |
307 return; |
305 return; |
308 |
306 |
309 m_outputScroll = clamp (m_outputScroll, 0, m_outputLines.size() - 1); |
307 m_outputScroll = clamp(m_outputScroll, 0, m_outputLines.size() - 1); |
310 |
308 |
311 int height = LINES - 3; |
309 int height = LINES - 3; |
312 int width = COLS - nicklist_width(); |
310 int width = COLS - nicklistWidth(); |
313 int printOffset = 0; |
311 int printOffset = 0; |
314 int end = m_outputLines.size() - 1 - m_outputScroll; |
312 int end = m_outputLines.size() - 1 - m_outputScroll; |
315 int start = end; |
313 int start = end; |
316 int usedHeight = 0; |
314 int usedHeight = 0; |
317 int y = 1; |
315 int y = 1; |
318 bool tightFit = false; |
316 bool tightFit = false; |
319 |
317 |
320 // Where to start? |
318 // Where to start? |
321 while (start > 0) |
319 while (start > 0) |
322 { |
320 { |
323 int rows = m_outputLines[start - 1].rows (width); |
321 int rows = m_outputLines[start - 1].rows(width); |
324 |
322 |
325 if (usedHeight + rows > height) |
323 if (usedHeight + rows > height) |
326 { |
324 { |
327 // This line won't fit anymore. |
325 // This line won't fit anymore. |
328 tightFit = true; |
326 tightFit = true; |
357 m_outputScroll = m_outputLines.size() - 1 - end; |
355 m_outputScroll = m_outputLines.size() - 1 - end; |
358 |
356 |
359 if (start < 0 or start == end or printOffset >= height) |
357 if (start < 0 or start == end or printOffset >= height) |
360 return; |
358 return; |
361 |
359 |
362 assert (start <= end and start - end <= height); |
360 assert(start <= end and start - end <= height); |
363 |
361 |
364 // Clear the display |
362 // Clear the display |
365 for (int i = y; i < y + height; ++i) |
363 for (int i : range(height)) |
366 mvhline (i, 0, ' ', width); |
364 mvhline(y + i, 0, ' ', width); |
367 |
365 |
368 // Print the lines |
366 // Print the lines |
369 y += printOffset; |
367 y += printOffset; |
370 |
368 |
371 for (int i = start; i < end; ++i) |
369 for (int i : range(start, end)) |
372 y = render_colorline (y, 0, width, m_outputLines[i], true); |
370 y = renderColorline(y, 0, width, m_outputLines[i], true); |
373 |
371 |
374 m_needOutputRender = false; |
372 m_needOutputRender = false; |
375 m_needRefresh = true; |
373 m_needRefresh = true; |
376 } |
374 } |
377 |
375 |
378 // ------------------------------------------------------------------------------------------------- |
376 // ------------------------------------------------------------------------------------------------- |
379 // |
377 // |
380 void Interface::render_nicklist() |
378 void Interface::renderNicklist() |
381 { |
379 { |
382 int width = nicklist_width(); |
380 int width = nicklistWidth(); |
383 int height = LINES- 3; |
381 int height = LINES- 3; |
384 int y = 1; |
382 int y = 1; |
385 int x = COLS - width; |
383 int x = COLS - width; |
386 |
384 |
387 if (width == 0) |
385 if (width > 0) |
388 return; |
386 return; |
389 |
387 |
390 for (int i = 0; i < height; ++i) |
388 for (int i : range(height)) |
391 { |
389 { |
392 mvhline (y, x, ' ', width); |
390 mvhline(y, x, ' ', width); |
393 |
391 |
394 if (i < m_playerNames.size()) |
392 if (i < m_playerNames.size()) |
395 render_colorline (y, x, width, m_playerNames[i], false); |
393 renderColorline(y, x, width, m_playerNames[i], false); |
396 |
394 |
397 y++; |
395 y++; |
398 } |
396 } |
399 |
397 |
400 m_needNicklistRender = false; |
398 m_needNicklistRender = false; |
401 m_needRefresh = true; |
399 m_needRefresh = true; |
402 } |
400 } |
403 |
401 |
404 // ------------------------------------------------------------------------------------------------- |
402 // ------------------------------------------------------------------------------------------------- |
405 // |
403 // |
406 void Interface::render_input() |
404 void Interface::renderInput() |
407 { |
405 { |
408 chtype promptColor = color_pair (WHITE, BLUE); |
406 chtype promptColor = getColorPair(WHITE, BLUE); |
409 |
407 |
410 // If we're asking the user if they want to disconnect, we don't render any input strings, |
408 // If we're asking the user if they want to disconnect, we don't render any input strings, |
411 // just the confirmation message. |
409 // just the confirmation message. |
412 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
410 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
413 { |
411 { |
414 attron (promptColor); |
412 attron(promptColor); |
415 mvhline (LINES - 2, 0, ' ', COLS); |
413 mvhline(LINES - 2, 0, ' ', COLS); |
416 mvprintw (LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
414 mvprintw(LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
417 attroff (promptColor); |
415 attroff(promptColor); |
418 m_needRefresh = true; |
416 m_needRefresh = true; |
419 return; |
417 return; |
420 } |
418 } |
421 |
419 |
422 String prompt = prompt_string(); |
420 String prompt = getPromptString(); |
423 int displayLength = COLS - prompt.length() - 2; |
421 int displayLength = COLS - prompt.length() - 2; |
424 String displayString = current_input(); |
422 String displayString = getCurrentInput(); |
425 int y = LINES - 2; |
423 int y = LINES - 2; |
426 |
424 |
427 // If we're inputting a password, replace it with asterisks |
425 // If we're inputting a password, replace it with asterisks |
428 if (m_inputState == INPUTSTATE_PASSWORD) |
426 if (m_inputState == INPUTSTATE_PASSWORD) |
429 { |
427 { |
430 for (int i = 0; i < displayString.length(); ++i) |
428 for (char &ch : displayString) |
431 displayString[i] = '*'; |
429 ch = '*'; |
432 } |
430 } |
433 |
431 |
434 // Ensure the cursor is within bounds |
432 // Ensure the cursor is within bounds |
435 m_cursorPosition = clamp (m_cursorPosition, 0, displayString.length()); |
433 m_cursorPosition = clamp(m_cursorPosition, 0, displayString.length()); |
436 |
434 |
437 // Ensure that the cursor is always in view, adjust panning if this is not the case |
435 // Ensure that the cursor is always in view, adjust panning if this is not the case |
438 if (m_cursorPosition > m_inputPanning + displayLength) |
436 if (m_cursorPosition > m_inputPanning + displayLength) |
439 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
437 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
440 else if (m_cursorPosition < m_inputPanning) |
438 else if (m_cursorPosition < m_inputPanning) |
441 m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left |
439 m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left |
442 |
440 |
443 // What part of the string to draw? |
441 // What part of the string to draw? |
444 int start = m_inputPanning; |
442 int start = m_inputPanning; |
445 int end = min<int> (displayString.length(), start + displayLength); |
443 int end = min<int>(displayString.length(), start + displayLength); |
446 assert (m_cursorPosition >= start and m_cursorPosition <= end); |
444 assert(m_cursorPosition >= start and m_cursorPosition <= end); |
447 |
445 |
448 // Render the input string |
446 // Render the input string |
449 mvhline (LINES - 2, 0, ' ', COLS); |
447 mvhline(LINES - 2, 0, ' ', COLS); |
450 mvprintw (y, prompt.length() + 1, "%s", displayString.mid (start, end).chars()); |
448 mvprintw(y, prompt.length() + 1, "%s", displayString.mid(start, end).chars()); |
451 |
449 |
452 // Render the prompt |
450 // Render the prompt |
453 attron (promptColor); |
451 attron(promptColor); |
454 mvprintw (y, 0, "%s", prompt.chars()); |
452 mvprintw(y, 0, "%s", prompt.chars()); |
455 attroff (promptColor); |
453 attroff(promptColor); |
456 |
454 |
457 // Store in memory where the cursor is now (so that we can re-draw it to position the terminal |
455 // Store in memory where the cursor is now(so that we can re-draw it to position the terminal |
458 // cursor). |
456 // cursor). |
459 m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; |
457 m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; |
460 m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning); |
458 m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning); |
461 m_needRefresh = true; |
459 m_needRefresh = true; |
462 m_needInputRender = false; |
460 m_needInputRender = false; |
463 } |
461 } |
464 |
462 |
465 // ------------------------------------------------------------------------------------------------- |
463 // ------------------------------------------------------------------------------------------------- |
466 // |
464 // |
467 void Interface::render_statusbar() |
465 void Interface::renderStatusBar() |
468 { |
466 { |
469 chtype color = color_pair (WHITE, BLUE); |
467 chtype color = getColorPair(WHITE, BLUE); |
470 int y = LINES - 1; |
468 int y = LINES - 1; |
471 attron (color); |
469 attron(color); |
472 mvhline (y, 0, ' ', COLS); |
470 mvhline(y, 0, ' ', COLS); |
473 mvprintw (y, 0, "%s", m_statusBarText.chars()); |
471 mvprintw(y, 0, "%s", m_statusBarText.chars()); |
474 attroff (color); |
472 attroff(color); |
475 m_needRefresh = true; |
473 m_needRefresh = true; |
476 m_needStatusBarRender = false; |
474 m_needStatusBarRender = false; |
477 } |
475 } |
478 |
476 |
479 // ------------------------------------------------------------------------------------------------- |
477 // ------------------------------------------------------------------------------------------------- |
480 // |
478 // |
481 void Interface::update_statusbar() |
479 void Interface::updateStatusBar() |
482 { |
480 { |
483 String text; |
481 String text; |
484 |
482 |
485 switch (m_session.state()) |
483 switch (m_session.state()) |
486 { |
484 { |
501 { |
499 { |
502 adminText = "No other admins"; |
500 adminText = "No other admins"; |
503 } |
501 } |
504 else |
502 else |
505 { |
503 { |
506 adminText.sprintf ("%d other admin%s", m_session.num_admins(), |
504 adminText.sprintf("%d other admin%s", m_session.num_admins(), |
507 m_session.num_admins() != 1 ? "s" : ""); |
505 m_session.num_admins() != 1 ? "s" : ""); |
508 } |
506 } |
509 |
507 |
510 text.sprintf ("%s | %s | %s", |
508 text.sprintf("%s | %s | %s", |
511 m_session.address().to_string (IPAddress::WITH_PORT).chars(), |
509 m_session.address().to_string(IPAddress::WITH_PORT).chars(), |
512 m_session.level().chars(), |
510 m_session.level().chars(), |
513 adminText.chars()); |
511 adminText.chars()); |
514 } |
512 } |
515 break; |
513 break; |
516 } |
514 } |
517 |
515 |
518 if (not text.is_empty()) |
516 if (not text.is_empty()) |
519 text += " | "; |
517 text += " | "; |
520 |
518 |
521 text += "Ctrl+N to connect, Ctrl+Q to "; |
519 text += "Ctrl+N to connect, Ctrl+Q to "; |
522 text += (m_session.state() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
520 text +=(m_session.state() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
523 |
521 |
524 if (text != m_statusBarText) |
522 if (text != m_statusBarText) |
525 { |
523 { |
526 m_statusBarText = text; |
524 m_statusBarText = text; |
527 m_needStatusBarRender = true; |
525 m_needStatusBarRender = true; |
528 } |
526 } |
529 } |
527 } |
530 |
528 |
531 // ------------------------------------------------------------------------------------------------- |
529 // ------------------------------------------------------------------------------------------------- |
532 // |
530 // |
533 void Interface::render_full() |
531 void Interface::renderFull() |
534 { |
532 { |
535 update_statusbar(); |
533 updateStatusBar(); |
536 render_titlebar(); |
534 renderTitlebar(); |
537 render_output(); |
535 renderOutput(); |
538 render_statusbar(); |
536 renderStatusBar(); |
539 render_input(); |
537 renderInput(); |
540 render_nicklist(); |
538 renderNicklist(); |
541 } |
539 } |
542 |
540 |
543 // ------------------------------------------------------------------------------------------------- |
541 // ------------------------------------------------------------------------------------------------- |
544 // |
542 // |
545 void Interface::position_cursor() |
543 void Interface::positionCursor() |
546 { |
544 { |
547 // This is only relevant if the input string is being drawn |
545 // This is only relevant if the input string is being drawn |
548 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
546 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
549 return; |
547 return; |
550 |
548 |
551 int y = LINES - 2; |
549 int y = LINES - 2; |
552 |
550 |
553 if (m_cursorCharacter.ch != '\0') |
551 if (m_cursorCharacter.ch != '\0') |
554 mvprintw (y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); |
552 mvprintw(y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); |
555 else |
553 else |
556 mvprintw (y, prompt_string().length(), " "); |
554 mvprintw(y, getPromptString().length(), " "); |
557 } |
555 } |
558 |
556 |
559 // ------------------------------------------------------------------------------------------------- |
557 // ------------------------------------------------------------------------------------------------- |
560 // |
558 // |
561 int Interface::find_previous_word() |
559 int Interface::findPreviousWord() |
562 { |
560 { |
563 const String& input = current_input(); |
561 const String& input = getCurrentInput(); |
564 int pos = m_cursorPosition; |
562 int pos = m_cursorPosition; |
565 |
563 |
566 // Move past whitespace |
564 // Move past whitespace |
567 while (pos > 0 and isspace (input[pos - 1])) |
565 while (pos > 0 and isspace(input[pos - 1])) |
568 pos--; |
566 pos--; |
569 |
567 |
570 // Move past the word |
568 // Move past the word |
571 while (pos > 0 and not isspace (input[pos - 1])) |
569 while (pos > 0 and not isspace(input[pos - 1])) |
572 pos--; |
570 pos--; |
573 |
571 |
574 return pos; |
572 return pos; |
575 } |
573 } |
576 |
574 |
577 // ------------------------------------------------------------------------------------------------- |
575 // ------------------------------------------------------------------------------------------------- |
578 // |
576 // |
579 int Interface::find_next_word() |
577 int Interface::findNextWord() |
580 { |
578 { |
581 const String& input = current_input(); |
579 const String& input = getCurrentInput(); |
582 int pos = m_cursorPosition; |
580 int pos = m_cursorPosition; |
583 |
581 |
584 // Move past current whitespace |
582 // Move past current whitespace |
585 while (pos < input.length() and isspace (input[pos])) |
583 while (pos < input.length() and isspace(input[pos])) |
586 pos++; |
584 pos++; |
587 |
585 |
588 // Move past the word |
586 // Move past the word |
589 while (input[pos] != '\0' and not isspace (input[pos])) |
587 while (input[pos] != '\0' and not isspace(input[pos])) |
590 pos++; |
588 pos++; |
591 |
589 |
592 return pos; |
590 return pos; |
593 } |
591 } |
594 |
592 |
595 // ------------------------------------------------------------------------------------------------- |
593 // ------------------------------------------------------------------------------------------------- |
596 // |
594 // |
597 void Interface::yank (int a, int b) |
595 void Interface::yank(int a, int b) |
598 { |
596 { |
599 if (a >= b) |
597 if (a >= b) |
600 return; |
598 return; |
601 |
599 |
602 if (m_cursorPosition > a and m_cursorPosition <= b) |
600 if (m_cursorPosition > a and m_cursorPosition <= b) |
603 m_cursorPosition = a; |
601 m_cursorPosition = a; |
604 |
602 |
605 String& input = mutable_current_input(); |
603 String& input = getEditableInput(); |
606 m_pasteBuffer = input.mid (a, b); |
604 m_pasteBuffer = input.mid(a, b); |
607 input.remove (a, b - a); |
605 input.remove(a, b - a); |
608 m_needInputRender = true; |
606 m_needInputRender = true; |
609 } |
607 } |
610 |
608 |
611 // ------------------------------------------------------------------------------------------------- |
609 // ------------------------------------------------------------------------------------------------- |
612 // |
610 // |
613 void Interface::handle_input() |
611 void Interface::handleInput() |
614 { |
612 { |
615 int ch = ::getch(); |
613 int ch = ::getch(); |
616 |
614 |
617 if (ch < 0) |
615 if (ch < 0) |
618 return; |
616 return; |
619 |
617 |
620 if (ch == KEY_RESIZE) |
618 if (ch == KEY_RESIZE) |
621 { |
619 { |
622 ::clear(); |
620 ::clear(); |
623 render_full(); |
621 renderFull(); |
624 return; |
622 return; |
625 } |
623 } |
626 |
624 |
627 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
625 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
628 { |
626 { |
706 } |
704 } |
707 break; |
705 break; |
708 |
706 |
709 case KEY_END: |
707 case KEY_END: |
710 case 'E' - 'A' + 1: // readline ^E |
708 case 'E' - 'A' + 1: // readline ^E |
711 if (m_cursorPosition != current_input().length()) |
709 if (m_cursorPosition != getCurrentInput().length()) |
712 { |
710 { |
713 m_cursorPosition = current_input().length(); |
711 m_cursorPosition = getCurrentInput().length(); |
714 m_needInputRender = true; |
712 m_needInputRender = true; |
715 } |
713 } |
716 break; |
714 break; |
717 |
715 |
718 case KEY_BACKSPACE: |
716 case KEY_BACKSPACE: |
719 case '\b': |
717 case '\b': |
720 if (m_cursorPosition > 0) |
718 if (m_cursorPosition > 0) |
721 { |
719 { |
722 mutable_current_input().remove_at (--m_cursorPosition); |
720 getEditableInput().remove_at(--m_cursorPosition); |
723 m_needInputRender = true; |
721 m_needInputRender = true; |
724 } |
722 } |
725 break; |
723 break; |
726 |
724 |
727 case KEY_DC: |
725 case KEY_DC: |
728 case 'D' - 'A' + 1: // readline ^D |
726 case 'D' - 'A' + 1: // readline ^D |
729 if (m_cursorPosition < current_input().length()) |
727 if (m_cursorPosition < getCurrentInput().length()) |
730 { |
728 { |
731 mutable_current_input().remove_at (m_cursorPosition); |
729 getEditableInput().remove_at(m_cursorPosition); |
732 m_needInputRender = true; |
730 m_needInputRender = true; |
733 } |
731 } |
734 break; |
732 break; |
735 |
733 |
736 case KEY_PPAGE: |
734 case KEY_PPAGE: |
737 m_outputScroll += min (PAGE_SIZE, LINES / 2); |
735 m_outputScroll += min(PAGE_SIZE, LINES / 2); |
738 m_needOutputRender = true; |
736 m_needOutputRender = true; |
739 break; |
737 break; |
740 |
738 |
741 case KEY_NPAGE: |
739 case KEY_NPAGE: |
742 m_outputScroll -= min (PAGE_SIZE, LINES / 2); |
740 m_outputScroll -= min(PAGE_SIZE, LINES / 2); |
743 m_needOutputRender = true; |
741 m_needOutputRender = true; |
744 break; |
742 break; |
745 |
743 |
746 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
744 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
747 if (m_cursorPosition > 0) |
745 if (m_cursorPosition > 0) |
748 { |
746 { |
749 yank (0, m_cursorPosition); |
747 yank(0, m_cursorPosition); |
750 m_cursorPosition = 0; |
748 m_cursorPosition = 0; |
751 } |
749 } |
752 break; |
750 break; |
753 |
751 |
754 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
752 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
755 yank (m_cursorPosition, mutable_current_input().length()); |
753 yank(m_cursorPosition, getEditableInput().length()); |
756 break; |
754 break; |
757 |
755 |
758 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
756 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
759 yank (find_previous_word(), m_cursorPosition); |
757 yank(findPreviousWord(), m_cursorPosition); |
760 break; |
758 break; |
761 |
759 |
762 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
760 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
763 if (not m_pasteBuffer.is_empty()) |
761 if (not m_pasteBuffer.is_empty()) |
764 { |
762 { |
765 mutable_current_input().insert (m_cursorPosition, m_pasteBuffer); |
763 getEditableInput().insert(m_cursorPosition, m_pasteBuffer); |
766 m_cursorPosition += m_pasteBuffer.length(); |
764 m_cursorPosition += m_pasteBuffer.length(); |
767 m_needInputRender = true; |
765 m_needInputRender = true; |
768 } |
766 } |
769 break; |
767 break; |
770 |
768 |
771 case '\t': |
769 case '\t': |
772 { |
770 { |
773 int space = current_input().find (" "); |
771 int space = getCurrentInput().find(" "); |
774 |
772 |
775 if (m_inputState == INPUTSTATE_NORMAL |
773 if (m_inputState == INPUTSTATE_NORMAL |
776 and m_cursorPosition > 0 |
774 and m_cursorPosition > 0 |
777 and (space == -1 or space >= m_cursorPosition)) |
775 and(space == -1 or space >= m_cursorPosition)) |
778 { |
776 { |
779 String start = current_input().mid (0, m_cursorPosition); |
777 String start = getCurrentInput().mid(0, m_cursorPosition); |
780 m_session.request_tab_complete (start); |
778 m_session.request_tab_complete(start); |
781 } |
779 } |
782 } |
780 } |
783 break; |
781 break; |
784 |
782 |
785 case '\n': |
783 case '\n': |
791 break; // handled above |
789 break; // handled above |
792 |
790 |
793 case INPUTSTATE_ADDRESS: |
791 case INPUTSTATE_ADDRESS: |
794 try |
792 try |
795 { |
793 { |
796 m_remoteAddress = IPAddress::from_string (current_input()); |
794 m_remoteAddress = IPAddress::from_string(getCurrentInput()); |
797 } |
795 } |
798 catch (std::exception& e) |
796 catch (std::exception& e) |
799 { |
797 { |
800 print ("%s\n", e.what()); |
798 print("%s\n", e.what()); |
801 return; |
799 return; |
802 } |
800 } |
803 |
801 |
804 if (m_remoteAddress.port == 0) |
802 if (m_remoteAddress.port == 0) |
805 m_remoteAddress.port = 10666; |
803 m_remoteAddress.port = 10666; |
806 |
804 |
807 set_input_state (INPUTSTATE_PASSWORD); |
805 setInputState(INPUTSTATE_PASSWORD); |
808 break; |
806 break; |
809 |
807 |
810 case INPUTSTATE_PASSWORD: |
808 case INPUTSTATE_PASSWORD: |
811 if (m_inputState == INPUTSTATE_PASSWORD and not current_input().is_empty()) |
809 if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().is_empty()) |
812 { |
810 { |
813 m_session.disconnect(); |
811 m_session.disconnect(); |
814 m_session.set_password (current_input()); |
812 m_session.set_password(getCurrentInput()); |
815 m_session.connect (m_remoteAddress); |
813 m_session.connect(m_remoteAddress); |
816 set_input_state (INPUTSTATE_NORMAL); |
814 setInputState(INPUTSTATE_NORMAL); |
817 } |
815 } |
818 break; |
816 break; |
819 |
817 |
820 case INPUTSTATE_NORMAL: |
818 case INPUTSTATE_NORMAL: |
821 if (current_input()[0] == '/') |
819 if (getCurrentInput()[0] == '/') |
822 { |
820 { |
823 handle_command(current_input()); |
821 handleCommand(getCurrentInput()); |
824 flush_input(); |
822 flushInput(); |
825 } |
823 } |
826 else if (m_session.send_command (current_input())) |
824 else if (m_session.send_command(getCurrentInput())) |
827 { |
825 { |
828 flush_input(); |
826 flushInput(); |
829 } |
827 } |
830 break; |
828 break; |
831 } |
829 } |
832 break; |
830 break; |
833 |
831 |
834 case 'N' - 'A' + 1: // ^N |
832 case 'N' - 'A' + 1: // ^N |
835 if (m_inputState == INPUTSTATE_NORMAL) |
833 if (m_inputState == INPUTSTATE_NORMAL) |
836 safe_disconnect ([&](bool){set_input_state (INPUTSTATE_ADDRESS);}); |
834 safeDisconnect([&](bool){setInputState(INPUTSTATE_ADDRESS);}); |
837 break; |
835 break; |
838 |
836 |
839 case '\x1b': // Escape |
837 case '\x1b': // Escape |
840 // We may have an alt key coming |
838 // We may have an alt key coming |
841 ch = ::getch(); |
839 ch = ::getch(); |
886 |
884 |
887 // ------------------------------------------------------------------------------------------------- |
885 // ------------------------------------------------------------------------------------------------- |
888 // |
886 // |
889 void Interface::render() |
887 void Interface::render() |
890 { |
888 { |
891 if (m_needStatusBarRender) render_statusbar(); |
889 if (m_needStatusBarRender) renderStatusBar(); |
892 if (m_needInputRender) render_input(); |
890 if (m_needInputRender) renderInput(); |
893 if (m_needOutputRender) render_output(); |
891 if (m_needOutputRender) renderOutput(); |
894 if (m_needNicklistRender) render_nicklist(); |
892 if (m_needNicklistRender) renderNicklist(); |
895 |
893 |
896 if (m_needRefresh) |
894 if (m_needRefresh) |
897 { |
895 { |
898 position_cursor(); |
896 positionCursor(); |
899 refresh(); |
897 refresh(); |
900 m_needRefresh = false; |
898 m_needRefresh = false; |
901 } |
899 } |
902 } |
900 } |
903 |
901 |
904 // ------------------------------------------------------------------------------------------------- |
902 // ------------------------------------------------------------------------------------------------- |
905 // |
903 // |
906 void Interface::vprint (const char* fmtstr, va_list args) |
904 void Interface::vprint(const char* fmtstr, va_list args) |
907 { |
905 { |
908 String message; |
906 String message; |
909 message.vsprintf (fmtstr, args); |
907 message.vsprintf(fmtstr, args); |
910 print_to_console (message); |
908 printToConsole(message); |
911 } |
909 } |
912 |
910 |
913 // ------------------------------------------------------------------------------------------------- |
911 // ------------------------------------------------------------------------------------------------- |
914 // |
912 // |
915 void __cdecl Interface::print_text (const char* fmtstr, ...) |
913 void __cdecl Interface::printText(const char* fmtstr, ...) |
916 { |
914 { |
917 va_list args; |
915 va_list args; |
918 va_start (args, fmtstr); |
916 va_start(args, fmtstr); |
919 vprint (fmtstr, args); |
917 vprint(fmtstr, args); |
920 va_end (args); |
918 va_end(args); |
921 } |
919 } |
922 |
920 |
923 // ------------------------------------------------------------------------------------------------- |
921 // ------------------------------------------------------------------------------------------------- |
924 // |
922 // |
925 void __cdecl Interface::print (const char* fmtstr, ...) |
923 void __cdecl Interface::print(const char* fmtstr, ...) |
926 { |
924 { |
927 va_list args; |
925 va_list args; |
928 va_start (args, fmtstr); |
926 va_start(args, fmtstr); |
929 print_to_console (TEXTCOLOR_BrightBlue); |
927 printToConsole(TEXTCOLOR_BrightBlue); |
930 vprint (fmtstr, args); |
928 vprint(fmtstr, args); |
931 va_end (args); |
929 va_end(args); |
932 } |
930 } |
933 |
931 |
934 // ------------------------------------------------------------------------------------------------- |
932 // ------------------------------------------------------------------------------------------------- |
935 // |
933 // |
936 void __cdecl Interface::print_warning (const char* fmtstr, ...) |
934 void __cdecl Interface::printWarning(const char* fmtstr, ...) |
937 { |
935 { |
938 va_list args; |
936 va_list args; |
939 va_start (args, fmtstr); |
937 va_start(args, fmtstr); |
940 print_to_console (TEXTCOLOR_BrightYellow "-!- "); |
938 printToConsole(TEXTCOLOR_BrightYellow "-!- "); |
941 vprint (fmtstr, args); |
939 vprint(fmtstr, args); |
942 va_end (args); |
940 va_end(args); |
943 } |
941 } |
944 |
942 |
945 // ------------------------------------------------------------------------------------------------- |
943 // ------------------------------------------------------------------------------------------------- |
946 // |
944 // |
947 void __cdecl Interface::print_error (const char* fmtstr, ...) |
945 void __cdecl Interface::printError(const char* fmtstr, ...) |
948 { |
946 { |
949 va_list args; |
947 va_list args; |
950 va_start (args, fmtstr); |
948 va_start(args, fmtstr); |
951 print_to_console (TEXTCOLOR_BrightRed "!!! "); |
949 printToConsole(TEXTCOLOR_BrightRed "!!! "); |
952 vprint (fmtstr, args); |
950 vprint(fmtstr, args); |
953 va_end (args); |
951 va_end(args); |
954 } |
952 } |
955 |
953 |
956 // ------------------------------------------------------------------------------------------------- |
954 // ------------------------------------------------------------------------------------------------- |
957 // |
955 // |
958 void Interface::print_to_console (String message) |
956 void Interface::printToConsole(String message) |
959 { |
957 { |
960 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". |
958 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". |
961 // Let's correct that on our end and hope this won't cause conflicts. |
959 // Let's correct that on our end and hope this won't cause conflicts. |
962 message.replace ("\\c", "\x1C"); |
960 message.replace("\\c", "\x1C"); |
963 |
961 |
964 for (int i = 0; i < message.length(); ++i) |
962 for (char ch : message) |
965 { |
963 { |
966 char ch = message[i]; |
|
967 |
|
968 if (ch == '\n') |
964 if (ch == '\n') |
969 { |
965 { |
970 m_outputLines.last().finalize(); |
966 m_outputLines.last().finalize(); |
971 m_outputLines << ColoredLine(); |
967 m_outputLines << ColoredLine(); |
972 continue; |
968 continue; |
973 } |
969 } |
974 |
970 |
975 if (m_outputLines.last().length() == 0) |
971 if (m_outputLines.last().length() == 0) |
976 { |
972 { |
977 time_t now; |
973 time_t now; |
978 time (&now); |
974 time(&now); |
979 char timestamp[32]; |
975 char timestamp[32]; |
980 strftime (timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime (&now)); |
976 strftime(timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime(&now)); |
981 |
977 |
982 for (char* cp = timestamp; *cp != '\0'; ++cp) |
978 for (char ch : String(timestamp)) |
983 m_outputLines.last().add_char (*cp); |
979 m_outputLines.last().add_char(ch); |
984 } |
980 } |
985 |
981 |
986 // Remove some lines if there's too many of them. 20,000 should be enough, I hope. |
982 // Remove some lines if there's too many of them. 20,000 should be enough, I hope. |
987 while (m_outputLines.size() > 20000) |
983 while (m_outputLines.size() > 20000) |
988 m_outputLines.remove_at(0); |
984 m_outputLines.remove_at(0); |
989 |
985 |
990 m_outputLines.last().add_char (ch); |
986 m_outputLines.last().add_char(ch); |
991 } |
987 } |
992 |
988 |
993 m_needOutputRender = true; |
989 m_needOutputRender = true; |
994 } |
990 } |
995 |
991 |
996 // ------------------------------------------------------------------------------------------------- |
992 // ------------------------------------------------------------------------------------------------- |
997 // |
993 // |
998 void Interface::connect (String address, String password) |
994 void Interface::connect(String address, String password) |
999 { |
995 { |
1000 try |
996 try |
1001 { |
997 { |
1002 m_remoteAddress = IPAddress::from_string (address); |
998 m_remoteAddress = IPAddress::from_string(address); |
1003 } |
999 } |
1004 catch (std::exception& e) |
1000 catch (std::exception& e) |
1005 { |
1001 { |
1006 print ("%s\n", e.what()); |
1002 print("%s\n", e.what()); |
1007 return; |
1003 return; |
1008 } |
1004 } |
1009 |
1005 |
1010 if (m_remoteAddress.port == 0) |
1006 if (m_remoteAddress.port == 0) |
1011 m_remoteAddress.port = 10666; |
1007 m_remoteAddress.port = 10666; |
1012 |
1008 |
1013 m_session.disconnect(); |
1009 m_session.disconnect(); |
1014 m_session.set_password (password); |
1010 m_session.set_password(password); |
1015 m_session.connect (m_remoteAddress); |
1011 m_session.connect(m_remoteAddress); |
1016 } |
1012 } |
1017 |
1013 |
1018 // ------------------------------------------------------------------------------------------------- |
1014 // ------------------------------------------------------------------------------------------------- |
1019 // |
1015 // |
1020 void Interface::set_player_names (const StringList& names) |
1016 void Interface::setPlayerNames(const StringList& names) |
1021 { |
1017 { |
1022 m_playerNames.clear(); |
1018 m_playerNames.clear(); |
1023 |
1019 |
1024 for (const String& name : names) |
1020 for (const String& name : names) |
1025 { |
1021 { |
1026 ColoredLine coloredname; |
1022 ColoredLine coloredname; |
1027 coloredname.add_string (name); |
1023 coloredname.add_string(name); |
1028 coloredname.finalize(); |
1024 coloredname.finalize(); |
1029 m_playerNames.append (coloredname); |
1025 m_playerNames.append(coloredname); |
1030 } |
1026 } |
1031 |
1027 |
1032 m_needNicklistRender = true; |
1028 m_needNicklistRender = true; |
1033 } |
1029 } |
1034 |
1030 |
1035 // ------------------------------------------------------------------------------------------------- |
1031 // ------------------------------------------------------------------------------------------------- |
1036 // |
1032 // |
1037 void Interface::tab_complete (const String& part, String complete) |
1033 void Interface::tabComplete(const String& part, String complete) |
1038 { |
1034 { |
1039 String& input = mutable_current_input(); |
1035 String& input = getEditableInput(); |
1040 |
1036 |
1041 if (input.starts_with (part)) |
1037 if (input.starts_with(part)) |
1042 { |
1038 { |
1043 if (input[part.length()] != ' ') |
1039 if (input[part.length()] != ' ') |
1044 complete += ' '; |
1040 complete += ' '; |
1045 |
1041 |
1046 input.replace (0, part.length(), complete); |
1042 input.replace(0, part.length(), complete); |
1047 m_cursorPosition = complete.length(); |
1043 m_cursorPosition = complete.length(); |
1048 m_needInputRender = true; |
1044 m_needInputRender = true; |
1049 } |
1045 } |
1050 } |
1046 } |
1051 |
1047 |
1052 // ------------------------------------------------------------------------------------------------- |
1048 // ------------------------------------------------------------------------------------------------- |
1053 // |
1049 // |
1054 void Interface::handle_command(const String& input) |
1050 void Interface::handleCommand(const String& input) |
1055 { |
1051 { |
1056 if (input[0] != '/') |
1052 if (input[0] != '/') |
1057 return; |
1053 return; |
1058 |
1054 |
1059 StringList args = input.right(input.length() - 1).split(" "); |
1055 StringList args = input.right(input.length() - 1).split(" "); |