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::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 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, 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 String Interface::getPromptString() |
104 std::string Interface::getPromptString() |
105 { |
105 { |
106 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; |
194 } |
194 } |
195 } |
195 } |
196 |
196 |
197 renderFull(); |
197 renderFull(); |
198 refresh(); |
198 refresh(); |
199 m_needRefresh = false; |
199 this->m_needRefresh = false; |
|
200 } |
|
201 |
|
202 Interface::~Interface() |
|
203 { |
|
204 ::endwin(); |
200 } |
205 } |
201 |
206 |
202 // ------------------------------------------------------------------------------------------------- |
207 // ------------------------------------------------------------------------------------------------- |
203 // |
208 // |
204 void Interface::renderTitlebar() |
209 void Interface::renderTitlebar() |
205 { |
210 { |
206 if (m_title.length() <= COLS) |
211 if (static_cast<signed>(this->m_title.length()) <= COLS) |
207 { |
212 { |
208 chtype pair = getColorPair(WHITE, BLUE); |
213 chtype pair = getColorPair(WHITE, BLUE); |
209 int startx =(COLS - m_title.length()) / 2; |
214 int startx =(COLS - this->m_title.length()) / 2; |
210 int endx = startx + m_title.length(); |
215 int endx = startx + this->m_title.length(); |
211 attron(pair); |
216 attron(pair); |
212 mvprintw(0, startx, "%s", m_title.chars()); |
217 mvprintw(0, startx, "%s", this->m_title.data()); |
213 mvhline(0, 0, ' ', startx); |
218 mvhline(0, 0, ' ', startx); |
214 mvhline(0, endx, ' ', COLS - endx); |
219 mvhline(0, endx, ' ', COLS - endx); |
215 attroff(pair); |
220 attroff(pair); |
216 } |
221 } |
217 |
222 |
218 m_needRefresh = true; |
223 this->m_needRefresh = true; |
219 } |
224 } |
220 |
225 |
221 // ------------------------------------------------------------------------------------------------- |
226 // ------------------------------------------------------------------------------------------------- |
222 // |
227 // |
223 void Interface::setTitle(const String& title) |
228 void Interface::setTitle(const std::string& title) |
224 { |
229 { |
225 m_title = title; |
230 this->m_title = title; |
226 renderTitlebar(); |
231 renderTitlebar(); |
227 } |
232 } |
228 |
233 |
229 // ------------------------------------------------------------------------------------------------- |
234 // ------------------------------------------------------------------------------------------------- |
230 // |
235 // |
231 void Interface::safeDisconnect(std::function<void(bool)> afterwards) |
236 void Interface::safeDisconnect(std::function<void(bool)> afterwards) |
232 { |
237 { |
233 if (m_session.isActive()) |
238 if (this->m_session.isActive()) |
234 { |
239 { |
235 m_disconnectCallback = afterwards; |
240 this->m_disconnectCallback = afterwards; |
236 setInputState(INPUTSTATE_CONFIRM_DISCONNECTION); |
241 setInputState(INPUTSTATE_CONFIRM_DISCONNECTION); |
237 } |
242 } |
238 else |
243 else |
239 afterwards(false); |
244 afterwards(false); |
240 } |
245 } |
383 int x = COLS - width; |
388 int x = COLS - width; |
384 |
389 |
385 if (width > 0) |
390 if (width > 0) |
386 return; |
391 return; |
387 |
392 |
388 for (int i : range(height)) |
393 for (int i = 0; i < height; i += 1) |
389 { |
394 { |
390 mvhline(y, x, ' ', width); |
395 mvhline(y, x, ' ', width); |
391 |
396 |
392 if (i < m_playerNames.size()) |
397 if (i < static_cast<signed>(this->m_playerNames.size())) |
393 renderColorline(y, x, width, m_playerNames[i], false); |
398 renderColorline(y, x, width, this->m_playerNames[i], false); |
394 |
399 |
395 y++; |
400 y++; |
396 } |
401 } |
397 |
402 |
398 m_needNicklistRender = false; |
403 this->m_needNicklistRender = false; |
399 m_needRefresh = true; |
404 this->m_needRefresh = true; |
400 } |
405 } |
401 |
406 |
402 // ------------------------------------------------------------------------------------------------- |
407 // ------------------------------------------------------------------------------------------------- |
403 // |
408 // |
404 void Interface::renderInput() |
409 void Interface::renderInput() |
405 { |
410 { |
406 chtype promptColor = getColorPair(WHITE, BLUE); |
411 chtype promptColor = getColorPair(WHITE, BLUE); |
407 |
412 |
408 // 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, |
409 // just the confirmation message. |
414 // just the confirmation message. |
410 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
415 if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
411 { |
416 { |
412 attron(promptColor); |
417 attron(promptColor); |
413 mvhline(LINES - 2, 0, ' ', COLS); |
418 mvhline(LINES - 2, 0, ' ', COLS); |
414 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"); |
415 attroff(promptColor); |
420 attroff(promptColor); |
416 m_needRefresh = true; |
421 this->m_needRefresh = true; |
417 return; |
422 return; |
418 } |
423 } |
419 |
424 |
420 String prompt = getPromptString(); |
425 std::string prompt = getPromptString(); |
421 int displayLength = COLS - prompt.length() - 2; |
426 int displayLength = COLS - prompt.length() - 2; |
422 String displayString = getCurrentInput(); |
427 std::string displayString = getCurrentInput(); |
423 int y = LINES - 2; |
428 int y = LINES - 2; |
424 |
429 |
425 // If we're inputting a password, replace it with asterisks |
430 // If we're inputting a password, replace it with asterisks |
426 if (m_inputState == INPUTSTATE_PASSWORD) |
431 if (this->m_inputState == INPUTSTATE_PASSWORD) |
427 { |
432 { |
428 for (char &ch : displayString) |
433 for (char &ch : displayString) |
429 ch = '*'; |
434 ch = '*'; |
430 } |
435 } |
431 |
436 |
432 // Ensure the cursor is within bounds |
437 // Ensure the cursor is within bounds |
433 m_cursorPosition = clamp(m_cursorPosition, 0, displayString.length()); |
438 this->m_cursorPosition = clamp(this->m_cursorPosition, 0, static_cast<signed>(displayString.length())); |
434 |
439 |
435 // 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 |
436 if (m_cursorPosition > m_inputPanning + displayLength) |
441 if (this->m_cursorPosition > this->m_inputPanning + displayLength) |
437 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
442 this->m_inputPanning = this->m_cursorPosition - displayLength; // cursor went too far right |
438 else if (m_cursorPosition < m_inputPanning) |
443 else if (this->m_cursorPosition < this->m_inputPanning) |
439 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 |
440 |
445 |
441 // What part of the string to draw? |
446 // What part of the string to draw? |
442 int start = m_inputPanning; |
447 int start = this->m_inputPanning; |
443 int end = min<int>(displayString.length(), start + displayLength); |
448 int end = min<int>(displayString.length(), start + displayLength); |
444 assert(m_cursorPosition >= start and m_cursorPosition <= end); |
449 assert(this->m_cursorPosition >= start and this->m_cursorPosition <= end); |
445 |
450 |
446 // Render the input string |
451 // Render the input string |
447 mvhline(LINES - 2, 0, ' ', COLS); |
452 mvhline(LINES - 2, 0, ' ', COLS); |
448 mvprintw(y, prompt.length() + 1, "%s", displayString.mid(start, end).chars()); |
453 mvprintw(y, prompt.length() + 1, "%s", mid(displayString, start, end).data()); |
449 |
454 |
450 // Render the prompt |
455 // Render the prompt |
451 attron(promptColor); |
456 attron(promptColor); |
452 mvprintw(y, 0, "%s", prompt.chars()); |
457 mvprintw(y, 0, "%s", prompt.data()); |
453 attroff(promptColor); |
458 attroff(promptColor); |
454 |
459 |
455 // 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 |
456 // cursor). |
461 // cursor). |
457 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'; |
458 m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning); |
463 this->m_cursorCharacter.x = prompt.length() + (this->m_cursorPosition - this->m_inputPanning); |
459 m_needRefresh = true; |
464 this->m_needRefresh = true; |
460 m_needInputRender = false; |
465 this->m_needInputRender = false; |
461 } |
466 } |
462 |
467 |
463 // ------------------------------------------------------------------------------------------------- |
468 // ------------------------------------------------------------------------------------------------- |
464 // |
469 // |
465 void Interface::renderStatusBar() |
470 void Interface::renderStatusBar() |
466 { |
471 { |
467 chtype color = getColorPair(WHITE, BLUE); |
472 chtype color = getColorPair(WHITE, BLUE); |
468 int y = LINES - 1; |
473 int y = LINES - 1; |
469 attron(color); |
474 attron(color); |
470 mvhline(y, 0, ' ', COLS); |
475 mvhline(y, 0, ' ', COLS); |
471 mvprintw(y, 0, "%s", m_statusBarText.chars()); |
476 mvprintw(y, 0, "%s", this->m_statusBarText.data()); |
472 attroff(color); |
477 attroff(color); |
473 m_needRefresh = true; |
478 this->m_needRefresh = true; |
474 m_needStatusBarRender = false; |
479 this->m_needStatusBarRender = false; |
475 } |
480 } |
476 |
481 |
477 // ------------------------------------------------------------------------------------------------- |
482 // ------------------------------------------------------------------------------------------------- |
478 // |
483 // |
479 void Interface::updateStatusBar() |
484 void Interface::updateStatusBar() |
480 { |
485 { |
481 String text; |
486 std::string text; |
482 |
487 |
483 switch (m_session.getState()) |
488 switch (this->m_session.getState()) |
484 { |
489 { |
485 case RCON_DISCONNECTED: |
490 case RCON_DISCONNECTED: |
486 text = "Disconnected."; |
491 text = "Disconnected."; |
487 break; |
492 break; |
488 |
493 |
489 case RCON_CONNECTING: |
494 case RCON_CONNECTING: |
490 case RCON_AUTHENTICATING: |
495 case RCON_AUTHENTICATING: |
491 text = "Connecting to " + m_session.address().to_string(IPAddress::WITH_PORT) + "..."; |
496 text = "Connecting to " + net::ip_address_to_string(this->m_session.address()) + "..."; |
492 break; |
497 break; |
493 |
498 |
494 case RCON_CONNECTED: |
499 case RCON_CONNECTED: |
495 { |
500 { |
496 String adminText; |
501 std::string adminText; |
497 |
502 |
498 if (m_session.getAdminCount() == 0) |
503 if (this->m_session.getAdminCount() == 0) |
499 { |
504 { |
500 adminText = "No other admins"; |
505 adminText = "No other admins"; |
501 } |
506 } |
502 else |
507 else |
503 { |
508 { |
504 adminText.sprintf("%d other admin%s", m_session.getAdminCount(), |
509 adminText = zfc::sprintf("%d other admin%s", this->m_session.getAdminCount(), |
505 m_session.getAdminCount() != 1 ? "s" : ""); |
510 this->m_session.getAdminCount() != 1 ? "s" : ""); |
506 } |
511 } |
507 |
512 |
508 text.sprintf("%s | %s | %s", |
513 text = zfc::sprintf("%s | %s | %s", |
509 m_session.address().to_string(IPAddress::WITH_PORT).chars(), |
514 net::ip_address_to_string(this->m_session.address()).data(), |
510 m_session.getLevel().chars(), |
515 this->m_session.getLevel().data(), |
511 adminText.chars()); |
516 adminText.data()); |
512 } |
517 } |
513 break; |
518 break; |
514 } |
519 } |
515 |
520 |
516 if (not text.isEmpty()) |
521 if (not text.empty()) |
517 text += " | "; |
522 text += " | "; |
518 |
523 |
519 text += "Ctrl+N to connect, Ctrl+Q to "; |
524 text += "Ctrl+N to connect, Ctrl+Q to "; |
520 text +=(m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
525 text +=(this->m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
521 |
526 |
522 if (text != m_statusBarText) |
527 if (text != this->m_statusBarText) |
523 { |
528 { |
524 m_statusBarText = text; |
529 this->m_statusBarText = text; |
525 m_needStatusBarRender = true; |
530 this->m_needStatusBarRender = true; |
526 } |
531 } |
527 } |
532 } |
528 |
533 |
529 // ------------------------------------------------------------------------------------------------- |
534 // ------------------------------------------------------------------------------------------------- |
530 // |
535 // |
672 } |
699 } |
673 break; |
700 break; |
674 |
701 |
675 case KEY_LEFT: |
702 case KEY_LEFT: |
676 case 'B' - 'A' + 1: // readline ^B |
703 case 'B' - 'A' + 1: // readline ^B |
677 if (m_cursorPosition > 0) |
704 if (this->m_cursorPosition > 0) |
678 { |
705 { |
679 m_cursorPosition--; |
706 this->m_cursorPosition--; |
680 m_needInputRender = true; |
707 this->m_needInputRender = true; |
681 } |
708 } |
682 break; |
709 break; |
683 |
710 |
684 case KEY_RIGHT: |
711 case KEY_RIGHT: |
685 case 'F' - 'A' + 1: // readline ^F |
712 case 'F' - 'A' + 1: // readline ^F |
686 if (m_cursorPosition < getCurrentInput().length()) |
713 if (this->m_cursorPosition < static_cast<int>(getCurrentInput().length())) |
687 { |
714 { |
688 m_cursorPosition++; |
715 this->m_cursorPosition++; |
689 m_needInputRender = true; |
716 this->m_needInputRender = true; |
690 } |
717 } |
691 break; |
718 break; |
692 |
719 |
693 case KEY_DOWN: |
720 case KEY_DOWN: |
694 case KEY_UP: |
721 case KEY_UP: |
695 moveInputCursor(ch == KEY_DOWN ? -1 : 1); |
722 moveInputCursor(ch == KEY_DOWN ? -1 : 1); |
696 break; |
723 break; |
697 |
724 |
698 case KEY_HOME: |
725 case KEY_HOME: |
699 case 'A' - 'A' + 1: // readline ^A |
726 case 'A' - 'A' + 1: // readline ^A |
700 if (m_cursorPosition != 0) |
727 if (this->m_cursorPosition != 0) |
701 { |
728 { |
702 m_cursorPosition = 0; |
729 this->m_cursorPosition = 0; |
703 m_needInputRender = true; |
730 this->m_needInputRender = true; |
704 } |
731 } |
705 break; |
732 break; |
706 |
733 |
707 case KEY_END: |
734 case KEY_END: |
708 case 'E' - 'A' + 1: // readline ^E |
735 case 'E' - 'A' + 1: // readline ^E |
709 if (m_cursorPosition != getCurrentInput().length()) |
736 if (this->m_cursorPosition != static_cast<signed>(getCurrentInput().length())) |
710 { |
737 { |
711 m_cursorPosition = getCurrentInput().length(); |
738 this->m_cursorPosition = getCurrentInput().length(); |
712 m_needInputRender = true; |
739 this->m_needInputRender = true; |
713 } |
740 } |
714 break; |
741 break; |
715 |
742 |
716 case KEY_BACKSPACE: |
743 case KEY_BACKSPACE: |
717 case '\b': |
744 case '\b': |
718 if (m_cursorPosition > 0) |
745 if (this->m_cursorPosition > 0) |
719 { |
746 { |
720 getEditableInput().removeAt(--m_cursorPosition); |
747 std::string& input = getEditableInput(); |
721 m_needInputRender = true; |
748 input.erase(input.begin() + this->m_cursorPosition - 1); |
|
749 this->m_cursorPosition -= 1; |
|
750 this->m_needInputRender = true; |
722 } |
751 } |
723 break; |
752 break; |
724 |
753 |
725 case KEY_DC: |
754 case KEY_DC: |
726 case 'D' - 'A' + 1: // readline ^D |
755 case 'D' - 'A' + 1: // readline ^D |
727 if (m_cursorPosition < getCurrentInput().length()) |
756 if (this->m_cursorPosition < static_cast<signed>(getCurrentInput().length())) |
728 { |
757 { |
729 getEditableInput().removeAt(m_cursorPosition); |
758 std::string& input = getEditableInput(); |
730 m_needInputRender = true; |
759 input.erase(input.begin() + this->m_cursorPosition); |
|
760 this->m_needInputRender = true; |
731 } |
761 } |
732 break; |
762 break; |
733 |
763 |
734 case KEY_PPAGE: |
764 case KEY_PPAGE: |
735 m_outputScroll += min(PAGE_SIZE, LINES / 2); |
765 this->m_outputScroll += min(PAGE_SIZE, LINES / 2); |
736 m_needOutputRender = true; |
766 this->m_needOutputRender = true; |
737 break; |
767 break; |
738 |
768 |
739 case KEY_NPAGE: |
769 case KEY_NPAGE: |
740 m_outputScroll -= min(PAGE_SIZE, LINES / 2); |
770 this->m_outputScroll -= min(PAGE_SIZE, LINES / 2); |
741 m_needOutputRender = true; |
771 this->m_needOutputRender = true; |
742 break; |
772 break; |
743 |
773 |
744 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
774 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
745 if (m_cursorPosition > 0) |
775 if (this->m_cursorPosition > 0) |
746 { |
776 { |
747 yank(0, m_cursorPosition); |
777 yank(0, this->m_cursorPosition); |
748 m_cursorPosition = 0; |
778 this->m_cursorPosition = 0; |
749 } |
779 } |
750 break; |
780 break; |
751 |
781 |
752 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
782 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
753 yank(m_cursorPosition, getEditableInput().length()); |
783 yank(this->m_cursorPosition, getEditableInput().length()); |
754 break; |
784 break; |
755 |
785 |
756 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 |
757 yank(findPreviousWord(), m_cursorPosition); |
787 yank(findPreviousWord(), this->m_cursorPosition); |
758 break; |
788 break; |
759 |
789 |
760 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
790 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
761 if (not m_pasteBuffer.isEmpty()) |
791 if (not this->m_pasteBuffer.empty()) |
762 { |
792 { |
763 getEditableInput().insert(m_cursorPosition, m_pasteBuffer); |
793 getEditableInput().insert(this->m_cursorPosition, this->m_pasteBuffer); |
764 m_cursorPosition += m_pasteBuffer.length(); |
794 this->m_cursorPosition += this->m_pasteBuffer.length(); |
765 m_needInputRender = true; |
795 this->m_needInputRender = true; |
766 } |
796 } |
767 break; |
797 break; |
768 |
798 |
769 case '\t': |
799 case '\t': |
770 { |
800 { |
771 int space = getCurrentInput().find(" "); |
801 int space = getCurrentInput().find(" "); |
772 |
802 |
773 if (m_inputState == INPUTSTATE_NORMAL |
803 if (this->m_inputState == INPUTSTATE_NORMAL |
774 and m_cursorPosition > 0 |
804 and this->m_cursorPosition > 0 |
775 and(space == -1 or space >= m_cursorPosition)) |
805 and(space == -1 or space >= this->m_cursorPosition)) |
776 { |
806 { |
777 String start = getCurrentInput().mid(0, m_cursorPosition); |
807 std::string start = mid(getCurrentInput(), 0, this->m_cursorPosition); |
778 m_session.requestTabCompletion(start); |
808 this->m_session.requestTabCompletion(start); |
779 } |
809 } |
780 } |
810 } |
781 break; |
811 break; |
782 |
812 |
783 case '\n': |
813 case '\n': |
784 case '\r': |
814 case '\r': |
785 case KEY_ENTER: |
815 case KEY_ENTER: |
786 switch (m_inputState) |
816 switch (this->m_inputState) |
787 { |
817 { |
788 case INPUTSTATE_CONFIRM_DISCONNECTION: |
818 case INPUTSTATE_CONFIRM_DISCONNECTION: |
789 break; // handled above |
819 break; // handled above |
790 |
820 |
791 case INPUTSTATE_ADDRESS: |
821 case INPUTSTATE_ADDRESS: |
792 try |
822 if (this->tryResolveAddress(this->getCurrentInput(), &this->m_remoteAddress)) |
793 { |
823 { |
794 m_remoteAddress = IPAddress::from_string(getCurrentInput()); |
824 setInputState(INPUTSTATE_PASSWORD); |
795 } |
825 } |
796 catch (std::exception& e) |
826 break; |
|
827 |
|
828 case INPUTSTATE_PASSWORD: |
|
829 if (this->m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().empty()) |
797 { |
830 { |
798 print("%s\n", e.what()); |
831 this->m_session.disconnect(); |
799 return; |
832 this->m_session.setPassword(getCurrentInput()); |
800 } |
833 this->m_session.connect(this->m_remoteAddress); |
801 |
|
802 if (m_remoteAddress.port == 0) |
|
803 m_remoteAddress.port = 10666; |
|
804 |
|
805 setInputState(INPUTSTATE_PASSWORD); |
|
806 break; |
|
807 |
|
808 case INPUTSTATE_PASSWORD: |
|
809 if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().isEmpty()) |
|
810 { |
|
811 m_session.disconnect(); |
|
812 m_session.setPassword(getCurrentInput()); |
|
813 m_session.connect(m_remoteAddress); |
|
814 setInputState(INPUTSTATE_NORMAL); |
834 setInputState(INPUTSTATE_NORMAL); |
815 } |
835 } |
816 break; |
836 break; |
817 |
837 |
818 case INPUTSTATE_NORMAL: |
838 case INPUTSTATE_NORMAL: |
819 if (getCurrentInput()[0] == '/') |
839 if (getCurrentInput()[0] == '/') |
820 { |
840 { |
821 handleCommand(getCurrentInput()); |
841 handleCommand(getCurrentInput(), shouldquit); |
822 flushInput(); |
842 flushInput(); |
823 } |
843 } |
824 else if (m_session.sendCommand(getCurrentInput())) |
844 else if (this->m_session.sendCommand(getCurrentInput())) |
825 { |
845 { |
826 flushInput(); |
846 flushInput(); |
827 } |
847 } |
828 break; |
848 break; |
829 } |
849 } |
830 break; |
850 break; |
831 |
851 |
832 case 'N' - 'A' + 1: // ^N |
852 case 'N' - 'A' + 1: // ^N |
833 if (m_inputState == INPUTSTATE_NORMAL) |
853 if (this->m_inputState == INPUTSTATE_NORMAL) |
834 safeDisconnect([&](bool){setInputState(INPUTSTATE_ADDRESS);}); |
854 safeDisconnect([&](bool){setInputState(INPUTSTATE_ADDRESS);}); |
835 break; |
855 break; |
836 |
856 |
837 case '\x1b': // Escape |
857 case '\x1b': // Escape |
838 // We may have an alt key coming |
858 // We may have an alt key coming |
951 va_end(args); |
971 va_end(args); |
952 } |
972 } |
953 |
973 |
954 // ------------------------------------------------------------------------------------------------- |
974 // ------------------------------------------------------------------------------------------------- |
955 // |
975 // |
956 void Interface::printToConsole(String message) |
976 void Interface::printToConsole(std::string message) |
957 { |
977 { |
958 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". |
978 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". |
959 // Let's correct that on our end and hope this won't cause conflicts. |
979 // Let's correct that on our end and hope this won't cause conflicts. |
960 message.replace("\\c", "\x1C"); |
980 replace_all(message, "\\c", "\x1C"); |
961 |
981 |
962 for (char ch : message) |
982 for (char ch : message) |
963 { |
983 { |
964 if (ch == '\n') |
984 if (ch == '\n') |
965 { |
985 { |
966 m_outputLines.last().finalize(); |
986 zfc::last(this->m_outputLines).finalize(); |
967 m_outputLines << ColoredLine(); |
987 this->m_outputLines.push_back({}); |
968 continue; |
988 continue; |
969 } |
989 } |
970 |
990 |
971 if (m_outputLines.last().length() == 0) |
991 if (zfc::last(this->m_outputLines).length() == 0) |
972 { |
992 { |
973 time_t now; |
993 time_t now; |
974 time(&now); |
994 time(&now); |
975 char timestamp[32]; |
995 char timestamp[32]; |
976 strftime(timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime(&now)); |
996 strftime(timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime(&now)); |
977 |
997 |
978 for (char ch : String(timestamp)) |
998 for (char ch : std::string(timestamp)) |
979 m_outputLines.last().addChar(ch); |
999 zfc::last(this->m_outputLines).addChar(ch); |
980 } |
1000 } |
981 |
1001 |
982 // 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. |
983 while (m_outputLines.size() > 20000) |
1003 while (this->m_outputLines.size() > 20000) |
984 m_outputLines.remove_at(0); |
1004 this->m_outputLines.erase(this->m_outputLines.begin()); |
985 |
1005 |
986 m_outputLines.last().addChar(ch); |
1006 zfc::last(this->m_outputLines).addChar(ch); |
987 } |
1007 } |
988 |
1008 |
989 m_needOutputRender = true; |
1009 this->m_needOutputRender = true; |
990 } |
1010 } |
991 |
1011 |
992 // ------------------------------------------------------------------------------------------------- |
1012 // ------------------------------------------------------------------------------------------------- |
993 // |
1013 // |
994 void Interface::connect(String address, String password) |
1014 void Interface::connect(std::string address_string, std::string password) |
995 { |
1015 { |
996 try |
1016 if (this->tryResolveAddress(address_string, &this->m_remoteAddress)) |
997 { |
1017 { |
998 m_remoteAddress = IPAddress::from_string(address); |
1018 this->m_session.disconnect(); |
999 } |
1019 this->m_session.setPassword(password); |
1000 catch (std::exception& e) |
1020 this->m_session.connect(this->m_remoteAddress); |
1001 { |
1021 } |
1002 print("%s\n", e.what()); |
1022 } |
1003 return; |
1023 |
1004 } |
1024 // ------------------------------------------------------------------------------------------------- |
1005 |
1025 // |
1006 if (m_remoteAddress.port == 0) |
1026 void Interface::setPlayerNames(const std::vector<std::string>& names) |
1007 m_remoteAddress.port = 10666; |
1027 { |
1008 |
1028 this->m_playerNames.clear(); |
1009 m_session.disconnect(); |
1029 |
1010 m_session.setPassword(password); |
1030 for (const std::string& name : names) |
1011 m_session.connect(m_remoteAddress); |
|
1012 } |
|
1013 |
|
1014 // ------------------------------------------------------------------------------------------------- |
|
1015 // |
|
1016 void Interface::setPlayerNames(const StringList& names) |
|
1017 { |
|
1018 m_playerNames.clear(); |
|
1019 |
|
1020 for (const String& name : names) |
|
1021 { |
1031 { |
1022 ColoredLine coloredname; |
1032 ColoredLine coloredname; |
1023 coloredname.addString(name); |
1033 coloredname.addString(name); |
1024 coloredname.finalize(); |
1034 coloredname.finalize(); |
1025 m_playerNames.append(coloredname); |
1035 this->m_playerNames.push_back(coloredname); |
1026 } |
1036 } |
1027 |
1037 |
1028 m_needNicklistRender = true; |
1038 this->m_needNicklistRender = true; |
1029 } |
1039 } |
1030 |
1040 |
1031 // ------------------------------------------------------------------------------------------------- |
1041 // ------------------------------------------------------------------------------------------------- |
1032 // |
1042 // |
1033 void Interface::tabComplete(const String& part, String complete) |
1043 void Interface::tabComplete(const std::string& part, std::string complete) |
1034 { |
1044 { |
1035 String& input = getEditableInput(); |
1045 std::string& input = getEditableInput(); |
1036 |
1046 |
1037 if (input.startsWith(part)) |
1047 if (starts_with(input, part)) |
1038 { |
1048 { |
1039 if (input[part.length()] != ' ') |
1049 if (input[part.length()] != ' ') |
1040 complete += ' '; |
1050 complete += ' '; |
1041 |
1051 |
1042 input.replace(0, part.length(), complete); |
1052 input.replace(0, part.length(), complete); |
1043 m_cursorPosition = complete.length(); |
1053 this->m_cursorPosition = complete.length(); |
1044 m_needInputRender = true; |
1054 this->m_needInputRender = true; |
1045 } |
1055 } |
1046 } |
1056 } |
1047 |
1057 |
1048 // ------------------------------------------------------------------------------------------------- |
1058 // ------------------------------------------------------------------------------------------------- |
1049 // |
1059 // |
1050 void Interface::handleCommand(const String& input) |
1060 void Interface::handleCommand(const std::string& input, bool* shouldquit) |
1051 { |
1061 { |
1052 if (input[0] != '/') |
1062 if (input[0] != '/') |
1053 return; |
1063 return; |
1054 |
1064 |
1055 StringList args = input.right(input.length() - 1).split(" "); |
1065 std::vector<std::string> args = split(right(input, input.length() - 1), " "); |
1056 String command = args[0].toLowerCase(); |
1066 std::string command = to_lowercase(args[0]); |
1057 args.remove_at(0); |
1067 args.erase(args.begin()); |
1058 |
1068 |
1059 if (command == "connect") |
1069 if (command == "connect") |
1060 { |
1070 { |
1061 if (args.size() != 2) |
1071 if (args.size() != 2) |
1062 { |
1072 { |
1063 printError("Usage: /connect <address> <password>\n"); |
1073 printError("Usage: /connect <address> <password>\n"); |
1064 } |
1074 } |
1065 else |
1075 else |
1066 { |
1076 { |
1067 IPAddress address; |
1077 this->connect(args[0], args[1]); |
1068 |
|
1069 try |
|
1070 { |
|
1071 address = IPAddress::from_string(args[0]); |
|
1072 } |
|
1073 catch (std::exception& e) |
|
1074 { |
|
1075 printError("%s\n", e.what()); |
|
1076 return; |
|
1077 } |
|
1078 |
|
1079 if (address.port == 0) |
|
1080 address.port = 10666; |
|
1081 |
|
1082 m_session.setPassword(args[1]); |
|
1083 m_session.disconnect(); |
|
1084 m_session.connect(m_remoteAddress = address); |
|
1085 } |
1078 } |
1086 } |
1079 } |
1087 else if (command == "disconnect") |
1080 else if (command == "disconnect") |
1088 { |
1081 { |
1089 m_session.disconnect(); |
1082 this->m_session.disconnect(); |
1090 } |
1083 } |
1091 else if (command == "quit") |
1084 else if (command == "quit") |
1092 { |
1085 { |
1093 m_session.disconnect(); |
1086 this->m_session.disconnect(); |
1094 endwin(); |
1087 *shouldquit = true; |
1095 throw Exitception(); |
|
1096 } |
1088 } |
1097 else if (command == "watch") |
1089 else if (command == "watch") |
1098 { |
1090 { |
1099 if (not args.is_empty()) |
1091 if (not args.empty()) |
1100 m_session.requestWatch(args); |
1092 m_session.requestWatch(args); |
1101 else |
1093 else |
1102 printError("No CVars to watch.\n"); |
1094 printError("No CVars to watch.\n"); |
1103 } |
1095 } |
1104 else |
1096 else |
1105 printError("Unknown command: %s\n", command.chars()); |
1097 printError("Unknown command: %s\n", command.data()); |
1106 } |
1098 } |
1107 |
1099 |
1108 // ------------------------------------------------------------------------------------------------- |
1100 // ------------------------------------------------------------------------------------------------- |
1109 // |
1101 // |
1110 void Interface::disconnected() |
1102 void Interface::disconnected() |
1111 { |
1103 { |
1112 print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).chars()); |
1104 print("Disconnected from %s\n", net::ip_address_to_string(this->m_session.address()).data()); |
1113 resetTitle(); |
1105 resetTitle(); |
1114 renderFull(); |
1106 renderFull(); |
1115 } |
1107 } |
1116 |
1108 |
1117 // ------------------------------------------------------------------------------------------------- |
1109 // ------------------------------------------------------------------------------------------------- |
1118 // |
1110 // |
1119 void Interface::resetTitle() |
1111 void Interface::resetTitle() |
1120 { |
1112 { |
1121 m_title.sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1113 this->m_title = sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1122 } |
1114 } |
1123 |
1115 |
1124 // ------------------------------------------------------------------------------------------------- |
1116 // ------------------------------------------------------------------------------------------------- |
1125 // |
1117 // |
1126 void Interface::flushInput() |
1118 void Interface::flushInput() |
1127 { |
1119 { |
1128 m_inputHistory.insert(0, ""); |
1120 this->m_inputHistory.insert(this->m_inputHistory.begin(), ""); |
1129 m_inputCursor = 0; |
1121 this->m_inputCursor = 0; |
1130 m_needInputRender = true; |
1122 this->m_needInputRender = true; |
1131 } |
1123 } |
1132 |
1124 |
1133 END_ZFC_NAMESPACE |
1125 END_ZFC_NAMESPACE |