201 |
201 |
202 // ------------------------------------------------------------------------------------------------- |
202 // ------------------------------------------------------------------------------------------------- |
203 // |
203 // |
204 void Interface::renderTitlebar() |
204 void Interface::renderTitlebar() |
205 { |
205 { |
206 if (m_title.length() <= COLS) |
206 if (static_cast<signed>(m_title.length()) <= COLS) |
207 { |
207 { |
208 chtype pair = getColorPair(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.data()); |
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 |
428 for (char &ch : displayString) |
428 for (char &ch : displayString) |
429 ch = '*'; |
429 ch = '*'; |
430 } |
430 } |
431 |
431 |
432 // Ensure the cursor is within bounds |
432 // Ensure the cursor is within bounds |
433 m_cursorPosition = clamp(m_cursorPosition, 0, displayString.length()); |
433 m_cursorPosition = clamp(m_cursorPosition, 0, static_cast<signed>(displayString.length())); |
434 |
434 |
435 // 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 |
436 if (m_cursorPosition > m_inputPanning + displayLength) |
436 if (m_cursorPosition > m_inputPanning + displayLength) |
437 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
437 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
438 else if (m_cursorPosition < m_inputPanning) |
438 else if (m_cursorPosition < m_inputPanning) |
443 int end = min<int>(displayString.length(), start + displayLength); |
443 int end = min<int>(displayString.length(), start + displayLength); |
444 assert(m_cursorPosition >= start and m_cursorPosition <= end); |
444 assert(m_cursorPosition >= start and m_cursorPosition <= end); |
445 |
445 |
446 // Render the input string |
446 // Render the input string |
447 mvhline(LINES - 2, 0, ' ', COLS); |
447 mvhline(LINES - 2, 0, ' ', COLS); |
448 mvprintw(y, prompt.length() + 1, "%s", displayString.mid(start, end).chars()); |
448 mvprintw(y, prompt.length() + 1, "%s", mid(displayString, start, end).data()); |
449 |
449 |
450 // Render the prompt |
450 // Render the prompt |
451 attron(promptColor); |
451 attron(promptColor); |
452 mvprintw(y, 0, "%s", prompt.chars()); |
452 mvprintw(y, 0, "%s", prompt.data()); |
453 attroff(promptColor); |
453 attroff(promptColor); |
454 |
454 |
455 // 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 |
456 // cursor). |
456 // cursor). |
457 m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; |
457 m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; |
466 { |
466 { |
467 chtype color = getColorPair(WHITE, BLUE); |
467 chtype color = getColorPair(WHITE, BLUE); |
468 int y = LINES - 1; |
468 int y = LINES - 1; |
469 attron(color); |
469 attron(color); |
470 mvhline(y, 0, ' ', COLS); |
470 mvhline(y, 0, ' ', COLS); |
471 mvprintw(y, 0, "%s", m_statusBarText.chars()); |
471 mvprintw(y, 0, "%s", m_statusBarText.data()); |
472 attroff(color); |
472 attroff(color); |
473 m_needRefresh = true; |
473 m_needRefresh = true; |
474 m_needStatusBarRender = false; |
474 m_needStatusBarRender = false; |
475 } |
475 } |
476 |
476 |
499 { |
499 { |
500 adminText = "No other admins"; |
500 adminText = "No other admins"; |
501 } |
501 } |
502 else |
502 else |
503 { |
503 { |
504 adminText.sprintf("%d other admin%s", m_session.getAdminCount(), |
504 adminText = zfc::sprintf("%d other admin%s", m_session.getAdminCount(), |
505 m_session.getAdminCount() != 1 ? "s" : ""); |
505 m_session.getAdminCount() != 1 ? "s" : ""); |
506 } |
506 } |
507 |
507 |
508 text.sprintf("%s | %s | %s", |
508 text = zfc::sprintf("%s | %s | %s", |
509 m_session.address().to_string(IPAddress::WITH_PORT).chars(), |
509 m_session.address().to_string(IPAddress::WITH_PORT).data(), |
510 m_session.getLevel().chars(), |
510 m_session.getLevel().data(), |
511 adminText.chars()); |
511 adminText.data()); |
512 } |
512 } |
513 break; |
513 break; |
514 } |
514 } |
515 |
515 |
516 if (not text.isEmpty()) |
516 if (not text.empty()) |
517 text += " | "; |
517 text += " | "; |
518 |
518 |
519 text += "Ctrl+N to connect, Ctrl+Q to "; |
519 text += "Ctrl+N to connect, Ctrl+Q to "; |
520 text +=(m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
520 text +=(m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
521 |
521 |
578 { |
578 { |
579 const String& input = getCurrentInput(); |
579 const String& input = getCurrentInput(); |
580 int pos = m_cursorPosition; |
580 int pos = m_cursorPosition; |
581 |
581 |
582 // Move past current whitespace |
582 // Move past current whitespace |
583 while (pos < input.length() and isspace(input[pos])) |
583 while (pos < static_cast<signed>(input.length()) and isspace(input[pos])) |
584 pos++; |
584 pos++; |
585 |
585 |
586 // Move past the word |
586 // Move past the word |
587 while (input[pos] != '\0' and not isspace(input[pos])) |
587 while (input[pos] != '\0' and not isspace(input[pos])) |
588 pos++; |
588 pos++; |
599 |
599 |
600 if (m_cursorPosition > a and m_cursorPosition <= b) |
600 if (m_cursorPosition > a and m_cursorPosition <= b) |
601 m_cursorPosition = a; |
601 m_cursorPosition = a; |
602 |
602 |
603 String& input = getEditableInput(); |
603 String& input = getEditableInput(); |
604 m_pasteBuffer = input.mid(a, b); |
604 m_pasteBuffer = mid(input, a, b); |
605 input.remove(a, b - a); |
605 input = remove_range(input, a, b - a); |
606 m_needInputRender = true; |
606 m_needInputRender = true; |
607 } |
607 } |
608 |
608 |
609 // ------------------------------------------------------------------------------------------------- |
609 // ------------------------------------------------------------------------------------------------- |
610 // |
610 // |
635 return; |
635 return; |
636 } |
636 } |
637 |
637 |
638 if (ch >= 0x20 and ch <= 0x7E) |
638 if (ch >= 0x20 and ch <= 0x7E) |
639 { |
639 { |
640 getEditableInput().insert(m_cursorPosition++, char(ch)); |
640 std::string& input = getEditableInput(); |
|
641 input.insert(input.begin() + m_cursorPosition, char(ch)); |
|
642 m_cursorPosition += 1; |
641 m_needInputRender = true; |
643 m_needInputRender = true; |
642 } |
644 } |
643 else switch (ch) |
645 else switch (ch) |
644 { |
646 { |
645 case 'Q' - 'A' + 1: // ^Q |
647 case 'Q' - 'A' + 1: // ^Q |
704 } |
706 } |
705 break; |
707 break; |
706 |
708 |
707 case KEY_END: |
709 case KEY_END: |
708 case 'E' - 'A' + 1: // readline ^E |
710 case 'E' - 'A' + 1: // readline ^E |
709 if (m_cursorPosition != getCurrentInput().length()) |
711 if (m_cursorPosition != static_cast<signed>(getCurrentInput().length())) |
710 { |
712 { |
711 m_cursorPosition = getCurrentInput().length(); |
713 m_cursorPosition = getCurrentInput().length(); |
712 m_needInputRender = true; |
714 m_needInputRender = true; |
713 } |
715 } |
714 break; |
716 break; |
715 |
717 |
716 case KEY_BACKSPACE: |
718 case KEY_BACKSPACE: |
717 case '\b': |
719 case '\b': |
718 if (m_cursorPosition > 0) |
720 if (m_cursorPosition > 0) |
719 { |
721 { |
720 getEditableInput().removeAt(--m_cursorPosition); |
722 String& input = getEditableInput(); |
|
723 input.erase(input.begin() + m_cursorPosition); |
|
724 m_cursorPosition -= 1; |
721 m_needInputRender = true; |
725 m_needInputRender = true; |
722 } |
726 } |
723 break; |
727 break; |
724 |
728 |
725 case KEY_DC: |
729 case KEY_DC: |
726 case 'D' - 'A' + 1: // readline ^D |
730 case 'D' - 'A' + 1: // readline ^D |
727 if (m_cursorPosition < getCurrentInput().length()) |
731 if (m_cursorPosition < static_cast<signed>(getCurrentInput().length())) |
728 { |
732 { |
729 getEditableInput().removeAt(m_cursorPosition); |
733 String& input = getEditableInput(); |
|
734 input.erase(input.begin() + m_cursorPosition); |
730 m_needInputRender = true; |
735 m_needInputRender = true; |
731 } |
736 } |
732 break; |
737 break; |
733 |
738 |
734 case KEY_PPAGE: |
739 case KEY_PPAGE: |
756 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
761 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
757 yank(findPreviousWord(), m_cursorPosition); |
762 yank(findPreviousWord(), m_cursorPosition); |
758 break; |
763 break; |
759 |
764 |
760 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
765 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
761 if (not m_pasteBuffer.isEmpty()) |
766 if (not m_pasteBuffer.empty()) |
762 { |
767 { |
763 getEditableInput().insert(m_cursorPosition, m_pasteBuffer); |
768 getEditableInput().insert(m_cursorPosition, m_pasteBuffer); |
764 m_cursorPosition += m_pasteBuffer.length(); |
769 m_cursorPosition += m_pasteBuffer.length(); |
765 m_needInputRender = true; |
770 m_needInputRender = true; |
766 } |
771 } |
772 |
777 |
773 if (m_inputState == INPUTSTATE_NORMAL |
778 if (m_inputState == INPUTSTATE_NORMAL |
774 and m_cursorPosition > 0 |
779 and m_cursorPosition > 0 |
775 and(space == -1 or space >= m_cursorPosition)) |
780 and(space == -1 or space >= m_cursorPosition)) |
776 { |
781 { |
777 String start = getCurrentInput().mid(0, m_cursorPosition); |
782 String start = mid(getCurrentInput(), 0, m_cursorPosition); |
778 m_session.requestTabCompletion(start); |
783 m_session.requestTabCompletion(start); |
779 } |
784 } |
780 } |
785 } |
781 break; |
786 break; |
782 |
787 |
804 |
809 |
805 setInputState(INPUTSTATE_PASSWORD); |
810 setInputState(INPUTSTATE_PASSWORD); |
806 break; |
811 break; |
807 |
812 |
808 case INPUTSTATE_PASSWORD: |
813 case INPUTSTATE_PASSWORD: |
809 if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().isEmpty()) |
814 if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().empty()) |
810 { |
815 { |
811 m_session.disconnect(); |
816 m_session.disconnect(); |
812 m_session.setPassword(getCurrentInput()); |
817 m_session.setPassword(getCurrentInput()); |
813 m_session.connect(m_remoteAddress); |
818 m_session.connect(m_remoteAddress); |
814 setInputState(INPUTSTATE_NORMAL); |
819 setInputState(INPUTSTATE_NORMAL); |
902 // ------------------------------------------------------------------------------------------------- |
907 // ------------------------------------------------------------------------------------------------- |
903 // |
908 // |
904 void Interface::vprint(const char* fmtstr, va_list args) |
909 void Interface::vprint(const char* fmtstr, va_list args) |
905 { |
910 { |
906 String message; |
911 String message; |
907 message.vsprintf(fmtstr, args); |
912 message = vsprintf(fmtstr, args); |
908 printToConsole(message); |
913 printToConsole(message); |
909 } |
914 } |
910 |
915 |
911 // ------------------------------------------------------------------------------------------------- |
916 // ------------------------------------------------------------------------------------------------- |
912 // |
917 // |
955 // |
960 // |
956 void Interface::printToConsole(String message) |
961 void Interface::printToConsole(String message) |
957 { |
962 { |
958 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". |
963 // 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. |
964 // Let's correct that on our end and hope this won't cause conflicts. |
960 message.replace("\\c", "\x1C"); |
965 replace_all(message, "\\c", "\x1C"); |
961 |
966 |
962 for (char ch : message) |
967 for (char ch : message) |
963 { |
968 { |
964 if (ch == '\n') |
969 if (ch == '\n') |
965 { |
970 { |
1032 // |
1037 // |
1033 void Interface::tabComplete(const String& part, String complete) |
1038 void Interface::tabComplete(const String& part, String complete) |
1034 { |
1039 { |
1035 String& input = getEditableInput(); |
1040 String& input = getEditableInput(); |
1036 |
1041 |
1037 if (input.startsWith(part)) |
1042 if (starts_with(input, part)) |
1038 { |
1043 { |
1039 if (input[part.length()] != ' ') |
1044 if (input[part.length()] != ' ') |
1040 complete += ' '; |
1045 complete += ' '; |
1041 |
1046 |
1042 input.replace(0, part.length(), complete); |
1047 input.replace(0, part.length(), complete); |
1050 void Interface::handleCommand(const String& input) |
1055 void Interface::handleCommand(const String& input) |
1051 { |
1056 { |
1052 if (input[0] != '/') |
1057 if (input[0] != '/') |
1053 return; |
1058 return; |
1054 |
1059 |
1055 StringList args = input.right(input.length() - 1).split(" "); |
1060 StringList args = split(right(input, input.length() - 1), " "); |
1056 String command = args[0].toLowerCase(); |
1061 String command = to_lowercase(args[0]); |
1057 args.erase(args.begin()); |
1062 args.erase(args.begin()); |
1058 |
1063 |
1059 if (command == "connect") |
1064 if (command == "connect") |
1060 { |
1065 { |
1061 if (args.size() != 2) |
1066 if (args.size() != 2) |
1093 m_session.disconnect(); |
1098 m_session.disconnect(); |
1094 endwin(); |
1099 endwin(); |
1095 throw Exitception(); |
1100 throw Exitception(); |
1096 } |
1101 } |
1097 else |
1102 else |
1098 printError("Unknown command: %s\n", command.chars()); |
1103 printError("Unknown command: %s\n", command.data()); |
1099 } |
1104 } |
1100 |
1105 |
1101 // ------------------------------------------------------------------------------------------------- |
1106 // ------------------------------------------------------------------------------------------------- |
1102 // |
1107 // |
1103 void Interface::disconnected() |
1108 void Interface::disconnected() |
1104 { |
1109 { |
1105 print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).chars()); |
1110 print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).data()); |
1106 resetTitle(); |
1111 resetTitle(); |
1107 renderFull(); |
1112 renderFull(); |
1108 } |
1113 } |
1109 |
1114 |
1110 // ------------------------------------------------------------------------------------------------- |
1115 // ------------------------------------------------------------------------------------------------- |
1111 // |
1116 // |
1112 void Interface::resetTitle() |
1117 void Interface::resetTitle() |
1113 { |
1118 { |
1114 m_title.sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1119 m_title = sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1115 } |
1120 } |
1116 |
1121 |
1117 // ------------------------------------------------------------------------------------------------- |
1122 // ------------------------------------------------------------------------------------------------- |
1118 // |
1123 // |
1119 void Interface::flushInput() |
1124 void Interface::flushInput() |