51 |
51 |
52 // ------------------------------------------------------------------------------------------------- |
52 // ------------------------------------------------------------------------------------------------- |
53 // |
53 // |
54 const std::string& Interface::getCurrentInput() |
54 const std::string& Interface::getCurrentInput() |
55 { |
55 { |
56 return m_inputHistory[m_inputCursor]; |
56 return this->m_inputHistory[this->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::detachInput() |
63 void Interface::detachInput() |
64 { |
64 { |
65 if (m_inputCursor > 0) |
65 if (this->m_inputCursor > 0) |
66 { |
66 { |
67 m_inputHistory[0] = getCurrentInput(); |
67 this->m_inputHistory[0] = getCurrentInput(); |
68 m_inputCursor = 0; |
68 this->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 std::string& Interface::getEditableInput() |
75 std::string& Interface::getEditableInput() |
76 { |
76 { |
77 detachInput(); |
77 detachInput(); |
78 return m_inputHistory[m_inputCursor]; |
78 return this->m_inputHistory[this->m_inputCursor]; |
79 } |
79 } |
80 |
80 |
81 // ------------------------------------------------------------------------------------------------- |
81 // ------------------------------------------------------------------------------------------------- |
82 // |
82 // |
83 void Interface::moveInputCursor(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 (this->m_inputState != INPUTSTATE_NORMAL) |
87 { |
87 { |
88 m_inputCursor = 0; |
88 this->m_inputCursor = 0; |
89 return; |
89 return; |
90 } |
90 } |
91 |
91 |
92 int oldcursor = m_inputCursor; |
92 int oldcursor = this->m_inputCursor; |
93 m_inputCursor = clamp(m_inputCursor + delta, 0, static_cast<int>(m_inputHistory.size() - 1)); |
93 this->m_inputCursor = clamp(this->m_inputCursor + delta, 0, static_cast<int>(this->m_inputHistory.size() - 1)); |
94 |
94 |
95 if (m_inputCursor != oldcursor) |
95 if (this->m_inputCursor != oldcursor) |
96 { |
96 { |
97 m_cursorPosition = getCurrentInput().length(); |
97 this->m_cursorPosition = getCurrentInput().length(); |
98 m_needInputRender = true; |
98 this->m_needInputRender = true; |
99 } |
99 } |
100 } |
100 } |
101 |
101 |
102 // ------------------------------------------------------------------------------------------------- |
102 // ------------------------------------------------------------------------------------------------- |
103 // |
103 // |
104 std::string Interface::getPromptString() |
104 std::string Interface::getPromptString() |
105 { |
105 { |
106 std::string prompt; |
106 std::string prompt; |
107 |
107 |
108 switch (m_inputState) |
108 switch (this->m_inputState) |
109 { |
109 { |
110 case INPUTSTATE_NORMAL: prompt = ">"; break; |
110 case INPUTSTATE_NORMAL: prompt = ">"; break; |
111 case INPUTSTATE_ADDRESS: prompt = "address:"; break; |
111 case INPUTSTATE_ADDRESS: prompt = "address:"; break; |
112 case INPUTSTATE_PASSWORD: prompt = "password:"; break; |
112 case INPUTSTATE_PASSWORD: prompt = "password:"; break; |
113 case INPUTSTATE_CONFIRM_DISCONNECTION: break; |
113 case INPUTSTATE_CONFIRM_DISCONNECTION: break; |
120 // |
120 // |
121 void Interface::setInputState(InputState newstate) |
121 void Interface::setInputState(InputState newstate) |
122 { |
122 { |
123 // Clear the input row(unless going to or from confirm state) |
123 // Clear the input row(unless going to or from confirm state) |
124 if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION |
124 if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION |
125 and m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) |
125 and this->m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) |
126 { |
126 { |
127 m_inputCursor = 0; |
127 this->m_inputCursor = 0; |
128 getEditableInput().clear(); |
128 getEditableInput().clear(); |
129 } |
129 } |
130 |
130 |
131 switch (newstate) |
131 switch (newstate) |
132 { |
132 { |
133 case INPUTSTATE_ADDRESS: |
133 case INPUTSTATE_ADDRESS: |
134 if (m_remoteAddress.host != 0) |
134 if (this->m_remoteAddress.host != 0) |
135 getEditableInput() = net::ip_address_to_string(m_remoteAddress); |
135 getEditableInput() = net::ip_address_to_string(this->m_remoteAddress); |
136 break; |
136 break; |
137 |
137 |
138 default: |
138 default: |
139 break; |
139 break; |
140 } |
140 } |
141 |
141 |
142 m_inputState = newstate; |
142 this->m_inputState = newstate; |
143 m_needInputRender = true; |
143 this->m_needInputRender = true; |
144 } |
144 } |
145 |
145 |
146 // ------------------------------------------------------------------------------------------------- |
146 // ------------------------------------------------------------------------------------------------- |
147 // |
147 // |
148 Interface::Interface() : |
148 Interface::Interface() : |
206 |
206 |
207 // ------------------------------------------------------------------------------------------------- |
207 // ------------------------------------------------------------------------------------------------- |
208 // |
208 // |
209 void Interface::renderTitlebar() |
209 void Interface::renderTitlebar() |
210 { |
210 { |
211 if (static_cast<signed>(m_title.length()) <= COLS) |
211 if (static_cast<signed>(this->m_title.length()) <= COLS) |
212 { |
212 { |
213 chtype pair = getColorPair(WHITE, BLUE); |
213 chtype pair = getColorPair(WHITE, BLUE); |
214 int startx =(COLS - m_title.length()) / 2; |
214 int startx =(COLS - this->m_title.length()) / 2; |
215 int endx = startx + m_title.length(); |
215 int endx = startx + this->m_title.length(); |
216 attron(pair); |
216 attron(pair); |
217 mvprintw(0, startx, "%s", m_title.data()); |
217 mvprintw(0, startx, "%s", this->m_title.data()); |
218 mvhline(0, 0, ' ', startx); |
218 mvhline(0, 0, ' ', startx); |
219 mvhline(0, endx, ' ', COLS - endx); |
219 mvhline(0, endx, ' ', COLS - endx); |
220 attroff(pair); |
220 attroff(pair); |
221 } |
221 } |
222 |
222 |
223 m_needRefresh = true; |
223 this->m_needRefresh = true; |
224 } |
224 } |
225 |
225 |
226 // ------------------------------------------------------------------------------------------------- |
226 // ------------------------------------------------------------------------------------------------- |
227 // |
227 // |
228 void Interface::setTitle(const std::string& title) |
228 void Interface::setTitle(const std::string& title) |
229 { |
229 { |
230 m_title = title; |
230 this->m_title = title; |
231 renderTitlebar(); |
231 renderTitlebar(); |
232 } |
232 } |
233 |
233 |
234 // ------------------------------------------------------------------------------------------------- |
234 // ------------------------------------------------------------------------------------------------- |
235 // |
235 // |
236 void Interface::safeDisconnect(std::function<void(bool)> afterwards) |
236 void Interface::safeDisconnect(std::function<void(bool)> afterwards) |
237 { |
237 { |
238 if (m_session.isActive()) |
238 if (this->m_session.isActive()) |
239 { |
239 { |
240 m_disconnectCallback = afterwards; |
240 this->m_disconnectCallback = afterwards; |
241 setInputState(INPUTSTATE_CONFIRM_DISCONNECTION); |
241 setInputState(INPUTSTATE_CONFIRM_DISCONNECTION); |
242 } |
242 } |
243 else |
243 else |
244 afterwards(false); |
244 afterwards(false); |
245 } |
245 } |
304 |
304 |
305 // ------------------------------------------------------------------------------------------------- |
305 // ------------------------------------------------------------------------------------------------- |
306 // |
306 // |
307 void Interface::renderOutput() |
307 void Interface::renderOutput() |
308 { |
308 { |
309 if (m_outputLines.size() == 1) |
309 if (this->m_outputLines.size() == 1) |
310 return; |
310 return; |
311 |
311 |
312 m_outputScroll = clamp(m_outputScroll, 0, static_cast<signed>(m_outputLines.size() - 1)); |
312 this->m_outputScroll = clamp(this->m_outputScroll, 0, static_cast<signed>(this->m_outputLines.size() - 1)); |
313 |
313 |
314 int height = LINES - 3; |
314 int height = LINES - 3; |
315 int width = COLS - nicklistWidth(); |
315 int width = COLS - nicklistWidth(); |
316 int printOffset = 0; |
316 int printOffset = 0; |
317 int end = m_outputLines.size() - 1 - m_outputScroll; |
317 int end = this->m_outputLines.size() - 1 - this->m_outputScroll; |
318 int start = end; |
318 int start = end; |
319 int usedHeight = 0; |
319 int usedHeight = 0; |
320 int y = 1; |
320 int y = 1; |
321 bool tightFit = false; |
321 bool tightFit = false; |
322 |
322 |
323 // Where to start? |
323 // Where to start? |
324 while (start > 0) |
324 while (start > 0) |
325 { |
325 { |
326 int rows = m_outputLines[start - 1].rows(width); |
326 int rows = this->m_outputLines[start - 1].rows(width); |
327 |
327 |
328 if (usedHeight + rows > height) |
328 if (usedHeight + rows > height) |
329 { |
329 { |
330 // This line won't fit anymore. |
330 // This line won't fit anymore. |
331 tightFit = true; |
331 tightFit = true; |
392 |
392 |
393 for (int i = 0; i < height; i += 1) |
393 for (int i = 0; i < height; i += 1) |
394 { |
394 { |
395 mvhline(y, x, ' ', width); |
395 mvhline(y, x, ' ', width); |
396 |
396 |
397 if (i < static_cast<signed>(m_playerNames.size())) |
397 if (i < static_cast<signed>(this->m_playerNames.size())) |
398 renderColorline(y, x, width, m_playerNames[i], false); |
398 renderColorline(y, x, width, this->m_playerNames[i], false); |
399 |
399 |
400 y++; |
400 y++; |
401 } |
401 } |
402 |
402 |
403 m_needNicklistRender = false; |
403 this->m_needNicklistRender = false; |
404 m_needRefresh = true; |
404 this->m_needRefresh = true; |
405 } |
405 } |
406 |
406 |
407 // ------------------------------------------------------------------------------------------------- |
407 // ------------------------------------------------------------------------------------------------- |
408 // |
408 // |
409 void Interface::renderInput() |
409 void Interface::renderInput() |
410 { |
410 { |
411 chtype promptColor = getColorPair(WHITE, BLUE); |
411 chtype promptColor = getColorPair(WHITE, BLUE); |
412 |
412 |
413 // If we're asking the user if they want to disconnect, we don't render any input strings, |
413 // If we're asking the user if they want to disconnect, we don't render any input strings, |
414 // just the confirmation message. |
414 // just the confirmation message. |
415 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
415 if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
416 { |
416 { |
417 attron(promptColor); |
417 attron(promptColor); |
418 mvhline(LINES - 2, 0, ' ', COLS); |
418 mvhline(LINES - 2, 0, ' ', COLS); |
419 mvprintw(LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
419 mvprintw(LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
420 attroff(promptColor); |
420 attroff(promptColor); |
421 m_needRefresh = true; |
421 this->m_needRefresh = true; |
422 return; |
422 return; |
423 } |
423 } |
424 |
424 |
425 std::string prompt = getPromptString(); |
425 std::string prompt = getPromptString(); |
426 int displayLength = COLS - prompt.length() - 2; |
426 int displayLength = COLS - prompt.length() - 2; |
427 std::string displayString = getCurrentInput(); |
427 std::string displayString = getCurrentInput(); |
428 int y = LINES - 2; |
428 int y = LINES - 2; |
429 |
429 |
430 // If we're inputting a password, replace it with asterisks |
430 // If we're inputting a password, replace it with asterisks |
431 if (m_inputState == INPUTSTATE_PASSWORD) |
431 if (this->m_inputState == INPUTSTATE_PASSWORD) |
432 { |
432 { |
433 for (char &ch : displayString) |
433 for (char &ch : displayString) |
434 ch = '*'; |
434 ch = '*'; |
435 } |
435 } |
436 |
436 |
437 // Ensure the cursor is within bounds |
437 // Ensure the cursor is within bounds |
438 m_cursorPosition = clamp(m_cursorPosition, 0, static_cast<signed>(displayString.length())); |
438 this->m_cursorPosition = clamp(this->m_cursorPosition, 0, static_cast<signed>(displayString.length())); |
439 |
439 |
440 // Ensure that the cursor is always in view, adjust panning if this is not the case |
440 // Ensure that the cursor is always in view, adjust panning if this is not the case |
441 if (m_cursorPosition > m_inputPanning + displayLength) |
441 if (this->m_cursorPosition > this->m_inputPanning + displayLength) |
442 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
442 this->m_inputPanning = this->m_cursorPosition - displayLength; // cursor went too far right |
443 else if (m_cursorPosition < m_inputPanning) |
443 else if (this->m_cursorPosition < this->m_inputPanning) |
444 m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left |
444 this->m_inputPanning = this->m_cursorPosition; // cursor went past the pan value to the left |
445 |
445 |
446 // What part of the string to draw? |
446 // What part of the string to draw? |
447 int start = m_inputPanning; |
447 int start = this->m_inputPanning; |
448 int end = min<int>(displayString.length(), start + displayLength); |
448 int end = min<int>(displayString.length(), start + displayLength); |
449 assert(m_cursorPosition >= start and m_cursorPosition <= end); |
449 assert(this->m_cursorPosition >= start and this->m_cursorPosition <= end); |
450 |
450 |
451 // Render the input string |
451 // Render the input string |
452 mvhline(LINES - 2, 0, ' ', COLS); |
452 mvhline(LINES - 2, 0, ' ', COLS); |
453 mvprintw(y, prompt.length() + 1, "%s", mid(displayString, start, end).data()); |
453 mvprintw(y, prompt.length() + 1, "%s", mid(displayString, start, end).data()); |
454 |
454 |
457 mvprintw(y, 0, "%s", prompt.data()); |
457 mvprintw(y, 0, "%s", prompt.data()); |
458 attroff(promptColor); |
458 attroff(promptColor); |
459 |
459 |
460 // Store in memory where the cursor is now(so that we can re-draw it to position the terminal |
460 // Store in memory where the cursor is now(so that we can re-draw it to position the terminal |
461 // cursor). |
461 // cursor). |
462 m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; |
462 this->m_cursorCharacter.ch = this->m_cursorPosition != 0 ? displayString[this->m_cursorPosition - 1] : '\0'; |
463 m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning); |
463 this->m_cursorCharacter.x = prompt.length() + (this->m_cursorPosition - this->m_inputPanning); |
464 m_needRefresh = true; |
464 this->m_needRefresh = true; |
465 m_needInputRender = false; |
465 this->m_needInputRender = false; |
466 } |
466 } |
467 |
467 |
468 // ------------------------------------------------------------------------------------------------- |
468 // ------------------------------------------------------------------------------------------------- |
469 // |
469 // |
470 void Interface::renderStatusBar() |
470 void Interface::renderStatusBar() |
471 { |
471 { |
472 chtype color = getColorPair(WHITE, BLUE); |
472 chtype color = getColorPair(WHITE, BLUE); |
473 int y = LINES - 1; |
473 int y = LINES - 1; |
474 attron(color); |
474 attron(color); |
475 mvhline(y, 0, ' ', COLS); |
475 mvhline(y, 0, ' ', COLS); |
476 mvprintw(y, 0, "%s", m_statusBarText.data()); |
476 mvprintw(y, 0, "%s", this->m_statusBarText.data()); |
477 attroff(color); |
477 attroff(color); |
478 m_needRefresh = true; |
478 this->m_needRefresh = true; |
479 m_needStatusBarRender = false; |
479 this->m_needStatusBarRender = false; |
480 } |
480 } |
481 |
481 |
482 // ------------------------------------------------------------------------------------------------- |
482 // ------------------------------------------------------------------------------------------------- |
483 // |
483 // |
484 void Interface::updateStatusBar() |
484 void Interface::updateStatusBar() |
485 { |
485 { |
486 std::string text; |
486 std::string text; |
487 |
487 |
488 switch (m_session.getState()) |
488 switch (this->m_session.getState()) |
489 { |
489 { |
490 case RCON_DISCONNECTED: |
490 case RCON_DISCONNECTED: |
491 text = "Disconnected."; |
491 text = "Disconnected."; |
492 break; |
492 break; |
493 |
493 |
494 case RCON_CONNECTING: |
494 case RCON_CONNECTING: |
495 case RCON_AUTHENTICATING: |
495 case RCON_AUTHENTICATING: |
496 text = "Connecting to " + net::ip_address_to_string(m_session.address()) + "..."; |
496 text = "Connecting to " + net::ip_address_to_string(this->m_session.address()) + "..."; |
497 break; |
497 break; |
498 |
498 |
499 case RCON_CONNECTED: |
499 case RCON_CONNECTED: |
500 { |
500 { |
501 std::string adminText; |
501 std::string adminText; |
502 |
502 |
503 if (m_session.getAdminCount() == 0) |
503 if (this->m_session.getAdminCount() == 0) |
504 { |
504 { |
505 adminText = "No other admins"; |
505 adminText = "No other admins"; |
506 } |
506 } |
507 else |
507 else |
508 { |
508 { |
509 adminText = zfc::sprintf("%d other admin%s", m_session.getAdminCount(), |
509 adminText = zfc::sprintf("%d other admin%s", this->m_session.getAdminCount(), |
510 m_session.getAdminCount() != 1 ? "s" : ""); |
510 this->m_session.getAdminCount() != 1 ? "s" : ""); |
511 } |
511 } |
512 |
512 |
513 text = zfc::sprintf("%s | %s | %s", |
513 text = zfc::sprintf("%s | %s | %s", |
514 net::ip_address_to_string(m_session.address()).data(), |
514 net::ip_address_to_string(this->m_session.address()).data(), |
515 m_session.getLevel().data(), |
515 this->m_session.getLevel().data(), |
516 adminText.data()); |
516 adminText.data()); |
517 } |
517 } |
518 break; |
518 break; |
519 } |
519 } |
520 |
520 |
521 if (not text.empty()) |
521 if (not text.empty()) |
522 text += " | "; |
522 text += " | "; |
523 |
523 |
524 text += "Ctrl+N to connect, Ctrl+Q to "; |
524 text += "Ctrl+N to connect, Ctrl+Q to "; |
525 text +=(m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
525 text +=(this->m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
526 |
526 |
527 if (text != m_statusBarText) |
527 if (text != this->m_statusBarText) |
528 { |
528 { |
529 m_statusBarText = text; |
529 this->m_statusBarText = text; |
530 m_needStatusBarRender = true; |
530 this->m_needStatusBarRender = true; |
531 } |
531 } |
532 } |
532 } |
533 |
533 |
534 // ------------------------------------------------------------------------------------------------- |
534 // ------------------------------------------------------------------------------------------------- |
535 // |
535 // |
546 // ------------------------------------------------------------------------------------------------- |
546 // ------------------------------------------------------------------------------------------------- |
547 // |
547 // |
548 void Interface::positionCursor() |
548 void Interface::positionCursor() |
549 { |
549 { |
550 // This is only relevant if the input string is being drawn |
550 // This is only relevant if the input string is being drawn |
551 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
551 if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
552 return; |
552 return; |
553 |
553 |
554 int y = LINES - 2; |
554 int y = LINES - 2; |
555 |
555 |
556 if (m_cursorCharacter.ch != '\0') |
556 if (this->m_cursorCharacter.ch != '\0') |
557 mvprintw(y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); |
557 mvprintw(y, this->m_cursorCharacter.x, "%c", this->m_cursorCharacter.ch); |
558 else |
558 else |
559 mvprintw(y, getPromptString().length(), " "); |
559 mvprintw(y, getPromptString().length(), " "); |
560 } |
560 } |
561 |
561 |
562 // ------------------------------------------------------------------------------------------------- |
562 // ------------------------------------------------------------------------------------------------- |
563 // |
563 // |
564 int Interface::findPreviousWord() |
564 int Interface::findPreviousWord() |
565 { |
565 { |
566 const std::string& input = getCurrentInput(); |
566 const std::string& input = getCurrentInput(); |
567 int pos = m_cursorPosition; |
567 int pos = this->m_cursorPosition; |
568 |
568 |
569 // Move past whitespace |
569 // Move past whitespace |
570 while (pos > 0 and isspace(input[pos - 1])) |
570 while (pos > 0 and isspace(input[pos - 1])) |
571 pos--; |
571 pos--; |
572 |
572 |
600 void Interface::yank(int a, int b) |
600 void Interface::yank(int a, int b) |
601 { |
601 { |
602 if (a >= b) |
602 if (a >= b) |
603 return; |
603 return; |
604 |
604 |
605 if (m_cursorPosition > a and m_cursorPosition <= b) |
605 if (this->m_cursorPosition > a and this->m_cursorPosition <= b) |
606 m_cursorPosition = a; |
606 this->m_cursorPosition = a; |
607 |
607 |
608 std::string& input = getEditableInput(); |
608 std::string& input = getEditableInput(); |
609 m_pasteBuffer = mid(input, a, b); |
609 this->m_pasteBuffer = mid(input, a, b); |
610 input = remove_range(input, a, b - a); |
610 input = remove_range(input, a, b - a); |
611 m_needInputRender = true; |
611 this->m_needInputRender = true; |
612 } |
612 } |
613 |
613 |
614 bool Interface::tryResolveAddress(const std::string &address_string, net::ip_address* target) |
614 bool Interface::tryResolveAddress(const std::string &address_string, net::ip_address* target) |
615 { |
615 { |
616 std::stringstream errors; |
616 std::stringstream errors; |
699 } |
699 } |
700 break; |
700 break; |
701 |
701 |
702 case KEY_LEFT: |
702 case KEY_LEFT: |
703 case 'B' - 'A' + 1: // readline ^B |
703 case 'B' - 'A' + 1: // readline ^B |
704 if (m_cursorPosition > 0) |
704 if (this->m_cursorPosition > 0) |
705 { |
705 { |
706 m_cursorPosition--; |
706 this->m_cursorPosition--; |
707 m_needInputRender = true; |
707 this->m_needInputRender = true; |
708 } |
708 } |
709 break; |
709 break; |
710 |
710 |
711 case KEY_RIGHT: |
711 case KEY_RIGHT: |
712 case 'F' - 'A' + 1: // readline ^F |
712 case 'F' - 'A' + 1: // readline ^F |
713 if (m_cursorPosition < static_cast<int>(getCurrentInput().length())) |
713 if (this->m_cursorPosition < static_cast<int>(getCurrentInput().length())) |
714 { |
714 { |
715 m_cursorPosition++; |
715 this->m_cursorPosition++; |
716 m_needInputRender = true; |
716 this->m_needInputRender = true; |
717 } |
717 } |
718 break; |
718 break; |
719 |
719 |
720 case KEY_DOWN: |
720 case KEY_DOWN: |
721 case KEY_UP: |
721 case KEY_UP: |
722 moveInputCursor(ch == KEY_DOWN ? -1 : 1); |
722 moveInputCursor(ch == KEY_DOWN ? -1 : 1); |
723 break; |
723 break; |
724 |
724 |
725 case KEY_HOME: |
725 case KEY_HOME: |
726 case 'A' - 'A' + 1: // readline ^A |
726 case 'A' - 'A' + 1: // readline ^A |
727 if (m_cursorPosition != 0) |
727 if (this->m_cursorPosition != 0) |
728 { |
728 { |
729 m_cursorPosition = 0; |
729 this->m_cursorPosition = 0; |
730 m_needInputRender = true; |
730 this->m_needInputRender = true; |
731 } |
731 } |
732 break; |
732 break; |
733 |
733 |
734 case KEY_END: |
734 case KEY_END: |
735 case 'E' - 'A' + 1: // readline ^E |
735 case 'E' - 'A' + 1: // readline ^E |
736 if (m_cursorPosition != static_cast<signed>(getCurrentInput().length())) |
736 if (this->m_cursorPosition != static_cast<signed>(getCurrentInput().length())) |
737 { |
737 { |
738 m_cursorPosition = getCurrentInput().length(); |
738 this->m_cursorPosition = getCurrentInput().length(); |
739 m_needInputRender = true; |
739 this->m_needInputRender = true; |
740 } |
740 } |
741 break; |
741 break; |
742 |
742 |
743 case KEY_BACKSPACE: |
743 case KEY_BACKSPACE: |
744 case '\b': |
744 case '\b': |
745 if (m_cursorPosition > 0) |
745 if (this->m_cursorPosition > 0) |
746 { |
746 { |
747 std::string& input = getEditableInput(); |
747 std::string& input = getEditableInput(); |
748 input.erase(input.begin() + m_cursorPosition - 1); |
748 input.erase(input.begin() + this->m_cursorPosition - 1); |
749 m_cursorPosition -= 1; |
749 this->m_cursorPosition -= 1; |
750 m_needInputRender = true; |
750 this->m_needInputRender = true; |
751 } |
751 } |
752 break; |
752 break; |
753 |
753 |
754 case KEY_DC: |
754 case KEY_DC: |
755 case 'D' - 'A' + 1: // readline ^D |
755 case 'D' - 'A' + 1: // readline ^D |
756 if (m_cursorPosition < static_cast<signed>(getCurrentInput().length())) |
756 if (this->m_cursorPosition < static_cast<signed>(getCurrentInput().length())) |
757 { |
757 { |
758 std::string& input = getEditableInput(); |
758 std::string& input = getEditableInput(); |
759 input.erase(input.begin() + m_cursorPosition); |
759 input.erase(input.begin() + this->m_cursorPosition); |
760 m_needInputRender = true; |
760 this->m_needInputRender = true; |
761 } |
761 } |
762 break; |
762 break; |
763 |
763 |
764 case KEY_PPAGE: |
764 case KEY_PPAGE: |
765 m_outputScroll += min(PAGE_SIZE, LINES / 2); |
765 this->m_outputScroll += min(PAGE_SIZE, LINES / 2); |
766 m_needOutputRender = true; |
766 this->m_needOutputRender = true; |
767 break; |
767 break; |
768 |
768 |
769 case KEY_NPAGE: |
769 case KEY_NPAGE: |
770 m_outputScroll -= min(PAGE_SIZE, LINES / 2); |
770 this->m_outputScroll -= min(PAGE_SIZE, LINES / 2); |
771 m_needOutputRender = true; |
771 this->m_needOutputRender = true; |
772 break; |
772 break; |
773 |
773 |
774 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
774 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
775 if (m_cursorPosition > 0) |
775 if (this->m_cursorPosition > 0) |
776 { |
776 { |
777 yank(0, m_cursorPosition); |
777 yank(0, this->m_cursorPosition); |
778 m_cursorPosition = 0; |
778 this->m_cursorPosition = 0; |
779 } |
779 } |
780 break; |
780 break; |
781 |
781 |
782 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
782 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
783 yank(m_cursorPosition, getEditableInput().length()); |
783 yank(this->m_cursorPosition, getEditableInput().length()); |
784 break; |
784 break; |
785 |
785 |
786 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
786 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
787 yank(findPreviousWord(), m_cursorPosition); |
787 yank(findPreviousWord(), this->m_cursorPosition); |
788 break; |
788 break; |
789 |
789 |
790 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
790 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
791 if (not m_pasteBuffer.empty()) |
791 if (not this->m_pasteBuffer.empty()) |
792 { |
792 { |
793 getEditableInput().insert(m_cursorPosition, m_pasteBuffer); |
793 getEditableInput().insert(this->m_cursorPosition, this->m_pasteBuffer); |
794 m_cursorPosition += m_pasteBuffer.length(); |
794 this->m_cursorPosition += this->m_pasteBuffer.length(); |
795 m_needInputRender = true; |
795 this->m_needInputRender = true; |
796 } |
796 } |
797 break; |
797 break; |
798 |
798 |
799 case '\t': |
799 case '\t': |
800 { |
800 { |
801 int space = getCurrentInput().find(" "); |
801 int space = getCurrentInput().find(" "); |
802 |
802 |
803 if (m_inputState == INPUTSTATE_NORMAL |
803 if (this->m_inputState == INPUTSTATE_NORMAL |
804 and m_cursorPosition > 0 |
804 and this->m_cursorPosition > 0 |
805 and(space == -1 or space >= m_cursorPosition)) |
805 and(space == -1 or space >= this->m_cursorPosition)) |
806 { |
806 { |
807 std::string start = mid(getCurrentInput(), 0, m_cursorPosition); |
807 std::string start = mid(getCurrentInput(), 0, this->m_cursorPosition); |
808 m_session.requestTabCompletion(start); |
808 this->m_session.requestTabCompletion(start); |
809 } |
809 } |
810 } |
810 } |
811 break; |
811 break; |
812 |
812 |
813 case '\n': |
813 case '\n': |
814 case '\r': |
814 case '\r': |
815 case KEY_ENTER: |
815 case KEY_ENTER: |
816 switch (m_inputState) |
816 switch (this->m_inputState) |
817 { |
817 { |
818 case INPUTSTATE_CONFIRM_DISCONNECTION: |
818 case INPUTSTATE_CONFIRM_DISCONNECTION: |
819 break; // handled above |
819 break; // handled above |
820 |
820 |
821 case INPUTSTATE_ADDRESS: |
821 case INPUTSTATE_ADDRESS: |
824 setInputState(INPUTSTATE_PASSWORD); |
824 setInputState(INPUTSTATE_PASSWORD); |
825 } |
825 } |
826 break; |
826 break; |
827 |
827 |
828 case INPUTSTATE_PASSWORD: |
828 case INPUTSTATE_PASSWORD: |
829 if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().empty()) |
829 if (this->m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().empty()) |
830 { |
830 { |
831 m_session.disconnect(); |
831 this->m_session.disconnect(); |
832 m_session.setPassword(getCurrentInput()); |
832 this->m_session.setPassword(getCurrentInput()); |
833 m_session.connect(m_remoteAddress); |
833 this->m_session.connect(this->m_remoteAddress); |
834 setInputState(INPUTSTATE_NORMAL); |
834 setInputState(INPUTSTATE_NORMAL); |
835 } |
835 } |
836 break; |
836 break; |
837 |
837 |
838 case INPUTSTATE_NORMAL: |
838 case INPUTSTATE_NORMAL: |
839 if (getCurrentInput()[0] == '/') |
839 if (getCurrentInput()[0] == '/') |
840 { |
840 { |
841 handleCommand(getCurrentInput(), shouldquit); |
841 handleCommand(getCurrentInput(), shouldquit); |
842 flushInput(); |
842 flushInput(); |
843 } |
843 } |
844 else if (m_session.sendCommand(getCurrentInput())) |
844 else if (this->m_session.sendCommand(getCurrentInput())) |
845 { |
845 { |
846 flushInput(); |
846 flushInput(); |
847 } |
847 } |
848 break; |
848 break; |
849 } |
849 } |
850 break; |
850 break; |
851 |
851 |
852 case 'N' - 'A' + 1: // ^N |
852 case 'N' - 'A' + 1: // ^N |
853 if (m_inputState == INPUTSTATE_NORMAL) |
853 if (this->m_inputState == INPUTSTATE_NORMAL) |
854 safeDisconnect([&](bool){setInputState(INPUTSTATE_ADDRESS);}); |
854 safeDisconnect([&](bool){setInputState(INPUTSTATE_ADDRESS);}); |
855 break; |
855 break; |
856 |
856 |
857 case '\x1b': // Escape |
857 case '\x1b': // Escape |
858 // We may have an alt key coming |
858 // We may have an alt key coming |
863 switch (ch) |
863 switch (ch) |
864 { |
864 { |
865 case 'b': |
865 case 'b': |
866 case 'B': |
866 case 'B': |
867 // readline alt-b - move one word to the left |
867 // readline alt-b - move one word to the left |
868 m_cursorPosition = findPreviousWord(); |
868 this->m_cursorPosition = findPreviousWord(); |
869 m_needInputRender = true; |
869 this->m_needInputRender = true; |
870 break; |
870 break; |
871 |
871 |
872 case 'f': |
872 case 'f': |
873 case 'F': |
873 case 'F': |
874 // readline alt-f - move one word to the right |
874 // readline alt-f - move one word to the right |
875 m_cursorPosition = findNextWord(); |
875 this->m_cursorPosition = findNextWord(); |
876 m_needInputRender = true; |
876 this->m_needInputRender = true; |
877 break; |
877 break; |
878 |
878 |
879 case 'd': |
879 case 'd': |
880 case 'D': |
880 case 'D': |
881 // readline alt-d - delete from here till next word boundary |
881 // readline alt-d - delete from here till next word boundary |
882 yank(m_cursorPosition, findNextWord()); |
882 yank(this->m_cursorPosition, findNextWord()); |
883 break; |
883 break; |
884 |
884 |
885 case KEY_BACKSPACE: // alt+backspace, remove previous word |
885 case KEY_BACKSPACE: // alt+backspace, remove previous word |
886 case '\b': |
886 case '\b': |
887 yank(findPreviousWord(), m_cursorPosition); |
887 yank(findPreviousWord(), this->m_cursorPosition); |
888 break; |
888 break; |
889 } |
889 } |
890 } |
890 } |
891 else |
891 else |
892 { |
892 { |
893 // No alt-key, handle pure escape |
893 // No alt-key, handle pure escape |
894 if (m_inputState == INPUTSTATE_PASSWORD) |
894 if (this->m_inputState == INPUTSTATE_PASSWORD) |
895 setInputState(INPUTSTATE_ADDRESS); |
895 setInputState(INPUTSTATE_ADDRESS); |
896 else if (m_inputState == INPUTSTATE_ADDRESS) |
896 else if (this->m_inputState == INPUTSTATE_ADDRESS) |
897 setInputState(INPUTSTATE_NORMAL); |
897 setInputState(INPUTSTATE_NORMAL); |
898 } |
898 } |
899 break; |
899 break; |
900 } |
900 } |
901 |
901 |
981 |
981 |
982 for (char ch : message) |
982 for (char ch : message) |
983 { |
983 { |
984 if (ch == '\n') |
984 if (ch == '\n') |
985 { |
985 { |
986 zfc::last(m_outputLines).finalize(); |
986 zfc::last(this->m_outputLines).finalize(); |
987 m_outputLines.push_back({}); |
987 this->m_outputLines.push_back({}); |
988 continue; |
988 continue; |
989 } |
989 } |
990 |
990 |
991 if (zfc::last(m_outputLines).length() == 0) |
991 if (zfc::last(this->m_outputLines).length() == 0) |
992 { |
992 { |
993 time_t now; |
993 time_t now; |
994 time(&now); |
994 time(&now); |
995 char timestamp[32]; |
995 char timestamp[32]; |
996 strftime(timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime(&now)); |
996 strftime(timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime(&now)); |
997 |
997 |
998 for (char ch : std::string(timestamp)) |
998 for (char ch : std::string(timestamp)) |
999 zfc::last(m_outputLines).addChar(ch); |
999 zfc::last(this->m_outputLines).addChar(ch); |
1000 } |
1000 } |
1001 |
1001 |
1002 // Remove some lines if there's too many of them. 20,000 should be enough, I hope. |
1002 // Remove some lines if there's too many of them. 20,000 should be enough, I hope. |
1003 while (m_outputLines.size() > 20000) |
1003 while (this->m_outputLines.size() > 20000) |
1004 m_outputLines.erase(m_outputLines.begin()); |
1004 this->m_outputLines.erase(this->m_outputLines.begin()); |
1005 |
1005 |
1006 zfc::last(m_outputLines).addChar(ch); |
1006 zfc::last(this->m_outputLines).addChar(ch); |
1007 } |
1007 } |
1008 |
1008 |
1009 m_needOutputRender = true; |
1009 this->m_needOutputRender = true; |
1010 } |
1010 } |
1011 |
1011 |
1012 // ------------------------------------------------------------------------------------------------- |
1012 // ------------------------------------------------------------------------------------------------- |
1013 // |
1013 // |
1014 void Interface::connect(std::string address_string, std::string password) |
1014 void Interface::connect(std::string address_string, std::string password) |
1015 { |
1015 { |
1016 if (this->tryResolveAddress(address_string, &this->m_remoteAddress)) |
1016 if (this->tryResolveAddress(address_string, &this->m_remoteAddress)) |
1017 { |
1017 { |
1018 m_session.disconnect(); |
1018 this->m_session.disconnect(); |
1019 m_session.setPassword(password); |
1019 this->m_session.setPassword(password); |
1020 m_session.connect(m_remoteAddress); |
1020 this->m_session.connect(this->m_remoteAddress); |
1021 } |
1021 } |
1022 } |
1022 } |
1023 |
1023 |
1024 // ------------------------------------------------------------------------------------------------- |
1024 // ------------------------------------------------------------------------------------------------- |
1025 // |
1025 // |
1026 void Interface::setPlayerNames(const std::vector<std::string>& names) |
1026 void Interface::setPlayerNames(const std::vector<std::string>& names) |
1027 { |
1027 { |
1028 m_playerNames.clear(); |
1028 this->m_playerNames.clear(); |
1029 |
1029 |
1030 for (const std::string& name : names) |
1030 for (const std::string& name : names) |
1031 { |
1031 { |
1032 ColoredLine coloredname; |
1032 ColoredLine coloredname; |
1033 coloredname.addString(name); |
1033 coloredname.addString(name); |
1034 coloredname.finalize(); |
1034 coloredname.finalize(); |
1035 m_playerNames.push_back(coloredname); |
1035 this->m_playerNames.push_back(coloredname); |
1036 } |
1036 } |
1037 |
1037 |
1038 m_needNicklistRender = true; |
1038 this->m_needNicklistRender = true; |
1039 } |
1039 } |
1040 |
1040 |
1041 // ------------------------------------------------------------------------------------------------- |
1041 // ------------------------------------------------------------------------------------------------- |
1042 // |
1042 // |
1043 void Interface::tabComplete(const std::string& part, std::string complete) |
1043 void Interface::tabComplete(const std::string& part, std::string complete) |
1077 this->connect(args[0], args[1]); |
1077 this->connect(args[0], args[1]); |
1078 } |
1078 } |
1079 } |
1079 } |
1080 else if (command == "disconnect") |
1080 else if (command == "disconnect") |
1081 { |
1081 { |
1082 m_session.disconnect(); |
1082 this->m_session.disconnect(); |
1083 } |
1083 } |
1084 else if (command == "quit") |
1084 else if (command == "quit") |
1085 { |
1085 { |
1086 m_session.disconnect(); |
1086 this->m_session.disconnect(); |
1087 *shouldquit = true; |
1087 *shouldquit = true; |
1088 } |
1088 } |
1089 else |
1089 else |
1090 printError("Unknown command: %s\n", command.data()); |
1090 printError("Unknown command: %s\n", command.data()); |
1091 } |
1091 } |
1092 |
1092 |
1093 // ------------------------------------------------------------------------------------------------- |
1093 // ------------------------------------------------------------------------------------------------- |
1094 // |
1094 // |
1095 void Interface::disconnected() |
1095 void Interface::disconnected() |
1096 { |
1096 { |
1097 print("Disconnected from %s\n", net::ip_address_to_string(m_session.address()).data()); |
1097 print("Disconnected from %s\n", net::ip_address_to_string(this->m_session.address()).data()); |
1098 resetTitle(); |
1098 resetTitle(); |
1099 renderFull(); |
1099 renderFull(); |
1100 } |
1100 } |
1101 |
1101 |
1102 // ------------------------------------------------------------------------------------------------- |
1102 // ------------------------------------------------------------------------------------------------- |
1103 // |
1103 // |
1104 void Interface::resetTitle() |
1104 void Interface::resetTitle() |
1105 { |
1105 { |
1106 m_title = sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1106 this->m_title = sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1107 } |
1107 } |
1108 |
1108 |
1109 // ------------------------------------------------------------------------------------------------- |
1109 // ------------------------------------------------------------------------------------------------- |
1110 // |
1110 // |
1111 void Interface::flushInput() |
1111 void Interface::flushInput() |
1112 { |
1112 { |
1113 m_inputHistory.insert(m_inputHistory.begin(), ""); |
1113 this->m_inputHistory.insert(this->m_inputHistory.begin(), ""); |
1114 m_inputCursor = 0; |
1114 this->m_inputCursor = 0; |
1115 m_needInputRender = true; |
1115 this->m_needInputRender = true; |
1116 } |
1116 } |
1117 |
1117 |
1118 END_ZFC_NAMESPACE |
1118 END_ZFC_NAMESPACE |