51 |
51 |
52 // ------------------------------------------------------------------------------------------------- |
52 // ------------------------------------------------------------------------------------------------- |
53 // |
53 // |
54 const String& Interface::current_input() |
54 const String& Interface::current_input() |
55 { |
55 { |
56 return InputHistory[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::detach_input() |
64 { |
64 { |
65 if (InputCursor > 0) |
65 if (m_inputCursor > 0) |
66 { |
66 { |
67 InputHistory[0] = current_input(); |
67 m_inputHistory[0] = current_input(); |
68 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::mutable_current_input() |
76 { |
76 { |
77 detach_input(); |
77 detach_input(); |
78 return InputHistory[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::move_input_cursor (int delta) |
84 { |
84 { |
85 // No input history when inputting addresses or passwords |
85 // No input history when inputting addresses or passwords |
86 if (CurrentInputState != INPUTSTATE_NORMAL) |
86 if (m_inputState != INPUTSTATE_NORMAL) |
87 { |
87 { |
88 InputCursor = 0; |
88 m_inputCursor = 0; |
89 return; |
89 return; |
90 } |
90 } |
91 |
91 |
92 int oldcursor = InputCursor; |
92 int oldcursor = m_inputCursor; |
93 InputCursor = clamp (InputCursor + delta, 0, InputHistory.size() - 1); |
93 m_inputCursor = clamp (m_inputCursor + delta, 0, m_inputHistory.size() - 1); |
94 |
94 |
95 if (InputCursor != oldcursor) |
95 if (m_inputCursor != oldcursor) |
96 { |
96 { |
97 CursorPosition = current_input().length(); |
97 m_cursorPosition = current_input().length(); |
98 NeedInputRender = true; |
98 m_needInputRender = true; |
99 } |
99 } |
100 } |
100 } |
101 |
101 |
102 // ------------------------------------------------------------------------------------------------- |
102 // ------------------------------------------------------------------------------------------------- |
103 // |
103 // |
104 String Interface::prompt_string() |
104 String Interface::prompt_string() |
105 { |
105 { |
106 String prompt; |
106 String prompt; |
107 |
107 |
108 switch (CurrentInputState) |
108 switch (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::set_input_state (InputState newstate) |
121 void Interface::set_input_state (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 CurrentInputState != INPUTSTATE_CONFIRM_DISCONNECTION) |
125 and m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) |
126 { |
126 { |
127 InputCursor = 0; |
127 m_inputCursor = 0; |
128 mutable_current_input().clear(); |
128 mutable_current_input().clear(); |
129 } |
129 } |
130 |
130 |
131 switch (newstate) |
131 switch (newstate) |
132 { |
132 { |
133 case INPUTSTATE_ADDRESS: |
133 case INPUTSTATE_ADDRESS: |
134 if (CurrentAddress.host != 0) |
134 if (m_remoteAddress.host != 0) |
135 mutable_current_input() = CurrentAddress.to_string (IPAddress::WITH_PORT); |
135 mutable_current_input() = m_remoteAddress.to_string (IPAddress::WITH_PORT); |
136 break; |
136 break; |
137 |
137 |
138 default: |
138 default: |
139 break; |
139 break; |
140 } |
140 } |
141 |
141 |
142 CurrentInputState = newstate; |
142 m_inputState = newstate; |
143 NeedInputRender = true; |
143 m_needInputRender = true; |
144 } |
144 } |
145 |
145 |
146 // ------------------------------------------------------------------------------------------------- |
146 // ------------------------------------------------------------------------------------------------- |
147 // |
147 // |
148 Interface::Interface() : |
148 Interface::Interface() : |
149 InputCursor (0), |
149 m_inputCursor (0), |
150 CursorPosition (0), |
150 m_cursorPosition (0), |
151 InputPanning (0), |
151 m_inputPanning (0), |
152 NeedRefresh (false), |
152 m_needRefresh (false), |
153 NeedStatusBarRender (false), |
153 m_needStatusBarRender (false), |
154 NeedInputRender (false), |
154 m_needInputRender (false), |
155 NeedOutputRender (false), |
155 m_needOutputRender (false), |
156 NeedNicklistRender (false), |
156 m_needNicklistRender (false), |
157 OutputScroll (0), |
157 m_outputScroll (0), |
158 CurrentInputState (INPUTSTATE_NORMAL), |
158 m_inputState (INPUTSTATE_NORMAL), |
159 DisconnectConfirmFunction (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 InputHistory.clear(); |
167 m_inputHistory.clear(); |
168 InputHistory << ""; |
168 m_inputHistory << ""; |
169 OutputLines.clear(); |
169 m_outputLines.clear(); |
170 OutputLines << ColoredLine(); |
170 m_outputLines << ColoredLine(); |
171 Session.set_interface (this); |
171 m_session.set_interface (this); |
172 reset_title(); |
172 reset_title(); |
173 |
173 |
174 if (::has_colors()) |
174 if (::has_colors()) |
175 { |
175 { |
176 ::start_color(); |
176 ::start_color(); |
194 } |
194 } |
195 } |
195 } |
196 |
196 |
197 render_full(); |
197 render_full(); |
198 refresh(); |
198 refresh(); |
199 NeedRefresh = false; |
199 m_needRefresh = false; |
200 } |
200 } |
201 |
201 |
202 // ------------------------------------------------------------------------------------------------- |
202 // ------------------------------------------------------------------------------------------------- |
203 // |
203 // |
204 void Interface::render_titlebar() |
204 void Interface::render_titlebar() |
205 { |
205 { |
206 if (Title.length() <= COLS) |
206 if (m_title.length() <= COLS) |
207 { |
207 { |
208 chtype pair = color_pair (WHITE, BLUE); |
208 chtype pair = color_pair (WHITE, BLUE); |
209 int startx = (COLS - Title.length()) / 2; |
209 int startx = (COLS - m_title.length()) / 2; |
210 int endx = startx + Title.length(); |
210 int endx = startx + m_title.length(); |
211 attron (pair); |
211 attron (pair); |
212 mvprintw (0, startx, "%s", 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 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::set_title (const String& title) |
224 { |
224 { |
225 Title = title; |
225 m_title = title; |
226 render_titlebar(); |
226 render_titlebar(); |
227 } |
227 } |
228 |
228 |
229 // ------------------------------------------------------------------------------------------------- |
229 // ------------------------------------------------------------------------------------------------- |
230 // |
230 // |
231 void Interface::safe_disconnect (std::function<void(bool)> afterwards) |
231 void Interface::safe_disconnect (std::function<void(bool)> afterwards) |
232 { |
232 { |
233 if (Session.is_active()) |
233 if (m_session.is_active()) |
234 { |
234 { |
235 DisconnectConfirmFunction = afterwards; |
235 m_disconnectCallback = afterwards; |
236 set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION); |
236 set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION); |
237 } |
237 } |
238 else |
238 else |
239 afterwards(false); |
239 afterwards(false); |
240 } |
240 } |
405 } |
405 } |
406 |
406 |
407 y++; |
407 y++; |
408 } |
408 } |
409 |
409 |
410 NeedNicklistRender = false; |
410 m_needNicklistRender = false; |
411 NeedRefresh = true; |
411 m_needRefresh = true; |
412 } |
412 } |
413 |
413 |
414 // ------------------------------------------------------------------------------------------------- |
414 // ------------------------------------------------------------------------------------------------- |
415 // |
415 // |
416 void Interface::render_input() |
416 void Interface::render_input() |
417 { |
417 { |
418 chtype promptColor = color_pair (WHITE, BLUE); |
418 chtype promptColor = color_pair (WHITE, BLUE); |
419 |
419 |
420 // If we're asking the user if they want to disconnect, we don't render any input strings, |
420 // If we're asking the user if they want to disconnect, we don't render any input strings, |
421 // just the confirmation message. |
421 // just the confirmation message. |
422 if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
422 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
423 { |
423 { |
424 attron (promptColor); |
424 attron (promptColor); |
425 mvhline (LINES - 2, 0, ' ', COLS); |
425 mvhline (LINES - 2, 0, ' ', COLS); |
426 mvprintw (LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
426 mvprintw (LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
427 attroff (promptColor); |
427 attroff (promptColor); |
428 NeedRefresh = true; |
428 m_needRefresh = true; |
429 return; |
429 return; |
430 } |
430 } |
431 |
431 |
432 String prompt = prompt_string(); |
432 String prompt = prompt_string(); |
433 int displayLength = COLS - prompt.length() - 2; |
433 int displayLength = COLS - prompt.length() - 2; |
434 String displayString = current_input(); |
434 String displayString = current_input(); |
435 int y = LINES - 2; |
435 int y = LINES - 2; |
436 |
436 |
437 // If we're inputting a password, replace it with asterisks |
437 // If we're inputting a password, replace it with asterisks |
438 if (CurrentInputState == INPUTSTATE_PASSWORD) |
438 if (m_inputState == INPUTSTATE_PASSWORD) |
439 { |
439 { |
440 for (int i = 0; i < displayString.length(); ++i) |
440 for (int i = 0; i < displayString.length(); ++i) |
441 displayString[i] = '*'; |
441 displayString[i] = '*'; |
442 } |
442 } |
443 |
443 |
444 // Ensure the cursor is within bounds |
444 // Ensure the cursor is within bounds |
445 CursorPosition = clamp (CursorPosition, 0, displayString.length()); |
445 m_cursorPosition = clamp (m_cursorPosition, 0, displayString.length()); |
446 |
446 |
447 // Ensure that the cursor is always in view, adjust panning if this is not the case |
447 // Ensure that the cursor is always in view, adjust panning if this is not the case |
448 if (CursorPosition > InputPanning + displayLength) |
448 if (m_cursorPosition > m_inputPanning + displayLength) |
449 InputPanning = CursorPosition - displayLength; // cursor went too far right |
449 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
450 else if (CursorPosition < InputPanning) |
450 else if (m_cursorPosition < m_inputPanning) |
451 InputPanning = CursorPosition; // cursor went past the pan value to the left |
451 m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left |
452 |
452 |
453 // What part of the string to draw? |
453 // What part of the string to draw? |
454 int start = InputPanning; |
454 int start = m_inputPanning; |
455 int end = min<int> (displayString.length(), start + displayLength); |
455 int end = min<int> (displayString.length(), start + displayLength); |
456 assert (CursorPosition >= start and CursorPosition <= end); |
456 assert (m_cursorPosition >= start and m_cursorPosition <= end); |
457 |
457 |
458 // Render the input string |
458 // Render the input string |
459 mvhline (LINES - 2, 0, ' ', COLS); |
459 mvhline (LINES - 2, 0, ' ', COLS); |
460 mvprintw (y, prompt.length() + 1, "%s", displayString.mid (start, end).chars()); |
460 mvprintw (y, prompt.length() + 1, "%s", displayString.mid (start, end).chars()); |
461 |
461 |
464 mvprintw (y, 0, "%s", prompt.chars()); |
464 mvprintw (y, 0, "%s", prompt.chars()); |
465 attroff (promptColor); |
465 attroff (promptColor); |
466 |
466 |
467 // Store in memory where the cursor is now (so that we can re-draw it to position the terminal |
467 // Store in memory where the cursor is now (so that we can re-draw it to position the terminal |
468 // cursor). |
468 // cursor). |
469 CursorCharacter.ch = CursorPosition != 0 ? displayString[CursorPosition - 1] : '\0'; |
469 m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; |
470 CursorCharacter.x = prompt.length() + (CursorPosition - InputPanning); |
470 m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning); |
471 NeedRefresh = true; |
471 m_needRefresh = true; |
472 NeedInputRender = false; |
472 m_needInputRender = false; |
473 } |
473 } |
474 |
474 |
475 // ------------------------------------------------------------------------------------------------- |
475 // ------------------------------------------------------------------------------------------------- |
476 // |
476 // |
477 void Interface::render_statusbar() |
477 void Interface::render_statusbar() |
478 { |
478 { |
479 chtype color = color_pair (WHITE, BLUE); |
479 chtype color = color_pair (WHITE, BLUE); |
480 int y = LINES - 1; |
480 int y = LINES - 1; |
481 attron (color); |
481 attron (color); |
482 mvhline (y, 0, ' ', COLS); |
482 mvhline (y, 0, ' ', COLS); |
483 mvprintw (y, 0, "%s", StatusBarText.chars()); |
483 mvprintw (y, 0, "%s", m_statusBarText.chars()); |
484 attroff (color); |
484 attroff (color); |
485 NeedRefresh = true; |
485 m_needRefresh = true; |
486 NeedStatusBarRender = false; |
486 m_needStatusBarRender = false; |
487 } |
487 } |
488 |
488 |
489 // ------------------------------------------------------------------------------------------------- |
489 // ------------------------------------------------------------------------------------------------- |
490 // |
490 // |
491 void Interface::update_statusbar() |
491 void Interface::update_statusbar() |
492 { |
492 { |
493 String text; |
493 String text; |
494 |
494 |
495 switch (Session.state()) |
495 switch (m_session.state()) |
496 { |
496 { |
497 case RCON_DISCONNECTED: |
497 case RCON_DISCONNECTED: |
498 text = "Disconnected."; |
498 text = "Disconnected."; |
499 break; |
499 break; |
500 |
500 |
501 case RCON_CONNECTING: |
501 case RCON_CONNECTING: |
502 case RCON_AUTHENTICATING: |
502 case RCON_AUTHENTICATING: |
503 text = "Connecting to " + Session.address().to_string (IPAddress::WITH_PORT) + "..."; |
503 text = "Connecting to " + m_session.address().to_string (IPAddress::WITH_PORT) + "..."; |
504 break; |
504 break; |
505 |
505 |
506 case RCON_CONNECTED: |
506 case RCON_CONNECTED: |
507 { |
507 { |
508 String adminText; |
508 String adminText; |
509 |
509 |
510 if (Session.num_admins() == 0) |
510 if (m_session.num_admins() == 0) |
511 { |
511 { |
512 adminText = "No other admins"; |
512 adminText = "No other admins"; |
513 } |
513 } |
514 else |
514 else |
515 { |
515 { |
516 adminText.sprintf ("%d other admin%s", Session.num_admins(), |
516 adminText.sprintf ("%d other admin%s", m_session.num_admins(), |
517 Session.num_admins() != 1 ? "s" : ""); |
517 m_session.num_admins() != 1 ? "s" : ""); |
518 } |
518 } |
519 |
519 |
520 text.sprintf ("%s | %s | %s", |
520 text.sprintf ("%s | %s | %s", |
521 Session.address().to_string (IPAddress::WITH_PORT).chars(), |
521 m_session.address().to_string (IPAddress::WITH_PORT).chars(), |
522 Session.level().chars(), |
522 m_session.level().chars(), |
523 adminText.chars()); |
523 adminText.chars()); |
524 } |
524 } |
525 break; |
525 break; |
526 } |
526 } |
527 |
527 |
528 if (not text.is_empty()) |
528 if (not text.is_empty()) |
529 text += " | "; |
529 text += " | "; |
530 |
530 |
531 text += "Ctrl+N to connect, Ctrl+Q to "; |
531 text += "Ctrl+N to connect, Ctrl+Q to "; |
532 text += (Session.state() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
532 text += (m_session.state() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
533 |
533 |
534 if (text != StatusBarText) |
534 if (text != m_statusBarText) |
535 { |
535 { |
536 StatusBarText = text; |
536 m_statusBarText = text; |
537 NeedStatusBarRender = true; |
537 m_needStatusBarRender = true; |
538 } |
538 } |
539 } |
539 } |
540 |
540 |
541 // ------------------------------------------------------------------------------------------------- |
541 // ------------------------------------------------------------------------------------------------- |
542 // |
542 // |
553 // ------------------------------------------------------------------------------------------------- |
553 // ------------------------------------------------------------------------------------------------- |
554 // |
554 // |
555 void Interface::position_cursor() |
555 void Interface::position_cursor() |
556 { |
556 { |
557 // This is only relevant if the input string is being drawn |
557 // This is only relevant if the input string is being drawn |
558 if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
558 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
559 return; |
559 return; |
560 |
560 |
561 int y = LINES - 2; |
561 int y = LINES - 2; |
562 |
562 |
563 if (CursorCharacter.ch != '\0') |
563 if (m_cursorCharacter.ch != '\0') |
564 mvprintw (y, CursorCharacter.x, "%c", CursorCharacter.ch); |
564 mvprintw (y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); |
565 else |
565 else |
566 mvprintw (y, prompt_string().length(), " "); |
566 mvprintw (y, prompt_string().length(), " "); |
567 } |
567 } |
568 |
568 |
569 // ------------------------------------------------------------------------------------------------- |
569 // ------------------------------------------------------------------------------------------------- |
570 // |
570 // |
571 int Interface::find_previous_word() |
571 int Interface::find_previous_word() |
572 { |
572 { |
573 const String& input = current_input(); |
573 const String& input = current_input(); |
574 int pos = CursorPosition; |
574 int pos = m_cursorPosition; |
575 |
575 |
576 // Move past whitespace |
576 // Move past whitespace |
577 while (pos > 0 and isspace (input[pos - 1])) |
577 while (pos > 0 and isspace (input[pos - 1])) |
578 pos--; |
578 pos--; |
579 |
579 |
684 } |
684 } |
685 break; |
685 break; |
686 |
686 |
687 case KEY_LEFT: |
687 case KEY_LEFT: |
688 case 'B' - 'A' + 1: // readline ^B |
688 case 'B' - 'A' + 1: // readline ^B |
689 if (CursorPosition > 0) |
689 if (m_cursorPosition > 0) |
690 { |
690 { |
691 CursorPosition--; |
691 m_cursorPosition--; |
692 NeedInputRender = true; |
692 m_needInputRender = true; |
693 } |
693 } |
694 break; |
694 break; |
695 |
695 |
696 case KEY_RIGHT: |
696 case KEY_RIGHT: |
697 case 'F' - 'A' + 1: // readline ^F |
697 case 'F' - 'A' + 1: // readline ^F |
698 if (CursorPosition < current_input().length()) |
698 if (m_cursorPosition < current_input().length()) |
699 { |
699 { |
700 CursorPosition++; |
700 m_cursorPosition++; |
701 NeedInputRender = true; |
701 m_needInputRender = true; |
702 } |
702 } |
703 break; |
703 break; |
704 |
704 |
705 case KEY_DOWN: |
705 case KEY_DOWN: |
706 case KEY_UP: |
706 case KEY_UP: |
707 move_input_cursor (ch == KEY_DOWN ? -1 : 1); |
707 move_input_cursor (ch == KEY_DOWN ? -1 : 1); |
708 break; |
708 break; |
709 |
709 |
710 case KEY_HOME: |
710 case KEY_HOME: |
711 case 'A' - 'A' + 1: // readline ^A |
711 case 'A' - 'A' + 1: // readline ^A |
712 if (CursorPosition != 0) |
712 if (m_cursorPosition != 0) |
713 { |
713 { |
714 CursorPosition = 0; |
714 m_cursorPosition = 0; |
715 NeedInputRender = true; |
715 m_needInputRender = true; |
716 } |
716 } |
717 break; |
717 break; |
718 |
718 |
719 case KEY_END: |
719 case KEY_END: |
720 case 'E' - 'A' + 1: // readline ^E |
720 case 'E' - 'A' + 1: // readline ^E |
721 if (CursorPosition != current_input().length()) |
721 if (m_cursorPosition != current_input().length()) |
722 { |
722 { |
723 CursorPosition = current_input().length(); |
723 m_cursorPosition = current_input().length(); |
724 NeedInputRender = true; |
724 m_needInputRender = true; |
725 } |
725 } |
726 break; |
726 break; |
727 |
727 |
728 case KEY_BACKSPACE: |
728 case KEY_BACKSPACE: |
729 case '\b': |
729 case '\b': |
730 if (CursorPosition > 0) |
730 if (m_cursorPosition > 0) |
731 { |
731 { |
732 mutable_current_input().remove_at (--CursorPosition); |
732 mutable_current_input().remove_at (--m_cursorPosition); |
733 NeedInputRender = true; |
733 m_needInputRender = true; |
734 } |
734 } |
735 break; |
735 break; |
736 |
736 |
737 case KEY_DC: |
737 case KEY_DC: |
738 case 'D' - 'A' + 1: // readline ^D |
738 case 'D' - 'A' + 1: // readline ^D |
739 if (CursorPosition < current_input().length()) |
739 if (m_cursorPosition < current_input().length()) |
740 { |
740 { |
741 mutable_current_input().remove_at (CursorPosition); |
741 mutable_current_input().remove_at (m_cursorPosition); |
742 NeedInputRender = true; |
742 m_needInputRender = true; |
743 } |
743 } |
744 break; |
744 break; |
745 |
745 |
746 case KEY_PPAGE: |
746 case KEY_PPAGE: |
747 OutputScroll += min (g_pageSize, LINES / 2); |
747 m_outputScroll += min (g_pageSize, LINES / 2); |
748 NeedOutputRender = true; |
748 m_needOutputRender = true; |
749 break; |
749 break; |
750 |
750 |
751 case KEY_NPAGE: |
751 case KEY_NPAGE: |
752 OutputScroll -= min (g_pageSize, LINES / 2); |
752 m_outputScroll -= min (g_pageSize, LINES / 2); |
753 NeedOutputRender = true; |
753 m_needOutputRender = true; |
754 break; |
754 break; |
755 |
755 |
756 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
756 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
757 if (CursorPosition > 0) |
757 if (m_cursorPosition > 0) |
758 { |
758 { |
759 yank (0, CursorPosition); |
759 yank (0, m_cursorPosition); |
760 CursorPosition = 0; |
760 m_cursorPosition = 0; |
761 } |
761 } |
762 break; |
762 break; |
763 |
763 |
764 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
764 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
765 yank (CursorPosition, mutable_current_input().length()); |
765 yank (m_cursorPosition, mutable_current_input().length()); |
766 break; |
766 break; |
767 |
767 |
768 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
768 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
769 yank (find_previous_word(), CursorPosition); |
769 yank (find_previous_word(), m_cursorPosition); |
770 break; |
770 break; |
771 |
771 |
772 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
772 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
773 if (not PasteBuffer.is_empty()) |
773 if (not m_pasteBuffer.is_empty()) |
774 { |
774 { |
775 mutable_current_input().insert (CursorPosition, PasteBuffer); |
775 mutable_current_input().insert (m_cursorPosition, m_pasteBuffer); |
776 CursorPosition += PasteBuffer.length(); |
776 m_cursorPosition += m_pasteBuffer.length(); |
777 NeedInputRender = true; |
777 m_needInputRender = true; |
778 } |
778 } |
779 break; |
779 break; |
780 |
780 |
781 case '\t': |
781 case '\t': |
782 { |
782 { |
783 int space = current_input().find (" "); |
783 int space = current_input().find (" "); |
784 |
784 |
785 if (CurrentInputState == INPUTSTATE_NORMAL |
785 if (m_inputState == INPUTSTATE_NORMAL |
786 and CursorPosition > 0 |
786 and m_cursorPosition > 0 |
787 and (space == -1 or space >= CursorPosition)) |
787 and (space == -1 or space >= m_cursorPosition)) |
788 { |
788 { |
789 String start = current_input().mid (0, CursorPosition); |
789 String start = current_input().mid (0, m_cursorPosition); |
790 Session.request_tab_complete (start); |
790 m_session.request_tab_complete (start); |
791 } |
791 } |
792 } |
792 } |
793 break; |
793 break; |
794 |
794 |
795 case '\n': |
795 case '\n': |
796 case '\r': |
796 case '\r': |
797 case KEY_ENTER: |
797 case KEY_ENTER: |
798 switch (CurrentInputState) |
798 switch (m_inputState) |
799 { |
799 { |
800 case INPUTSTATE_CONFIRM_DISCONNECTION: |
800 case INPUTSTATE_CONFIRM_DISCONNECTION: |
801 break; // handled above |
801 break; // handled above |
802 |
802 |
803 case INPUTSTATE_ADDRESS: |
803 case INPUTSTATE_ADDRESS: |
804 try |
804 try |
805 { |
805 { |
806 CurrentAddress = IPAddress::from_string (current_input()); |
806 m_remoteAddress = IPAddress::from_string (current_input()); |
807 } |
807 } |
808 catch (std::exception& e) |
808 catch (std::exception& e) |
809 { |
809 { |
810 print ("%s\n", e.what()); |
810 print ("%s\n", e.what()); |
811 return; |
811 return; |
812 } |
812 } |
813 |
813 |
814 if (CurrentAddress.port == 0) |
814 if (m_remoteAddress.port == 0) |
815 CurrentAddress.port = 10666; |
815 m_remoteAddress.port = 10666; |
816 |
816 |
817 set_input_state (INPUTSTATE_PASSWORD); |
817 set_input_state (INPUTSTATE_PASSWORD); |
818 break; |
818 break; |
819 |
819 |
820 case INPUTSTATE_PASSWORD: |
820 case INPUTSTATE_PASSWORD: |
821 if (CurrentInputState == INPUTSTATE_PASSWORD and not current_input().is_empty()) |
821 if (m_inputState == INPUTSTATE_PASSWORD and not current_input().is_empty()) |
822 { |
822 { |
823 Session.disconnect(); |
823 m_session.disconnect(); |
824 Session.set_password (current_input()); |
824 m_session.set_password (current_input()); |
825 Session.connect (CurrentAddress); |
825 m_session.connect (m_remoteAddress); |
826 set_input_state (INPUTSTATE_NORMAL); |
826 set_input_state (INPUTSTATE_NORMAL); |
827 } |
827 } |
828 break; |
828 break; |
829 |
829 |
830 case INPUTSTATE_NORMAL: |
830 case INPUTSTATE_NORMAL: |
831 if (current_input()[0] == '/') |
831 if (current_input()[0] == '/') |
832 { |
832 { |
833 handle_command(current_input()); |
833 handle_command(current_input()); |
834 flush_input(); |
834 flush_input(); |
835 } |
835 } |
836 else if (Session.send_command (current_input())) |
836 else if (m_session.send_command (current_input())) |
837 { |
837 { |
838 flush_input(); |
838 flush_input(); |
839 } |
839 } |
840 break; |
840 break; |
841 } |
841 } |
842 break; |
842 break; |
843 |
843 |
844 case 'N' - 'A' + 1: // ^N |
844 case 'N' - 'A' + 1: // ^N |
845 if (CurrentInputState == INPUTSTATE_NORMAL) |
845 if (m_inputState == INPUTSTATE_NORMAL) |
846 safe_disconnect ([&](bool){set_input_state (INPUTSTATE_ADDRESS);}); |
846 safe_disconnect ([&](bool){set_input_state (INPUTSTATE_ADDRESS);}); |
847 break; |
847 break; |
848 |
848 |
849 case '\x1b': // Escape |
849 case '\x1b': // Escape |
850 // We may have an alt key coming |
850 // We may have an alt key coming |
855 switch (ch) |
855 switch (ch) |
856 { |
856 { |
857 case 'b': |
857 case 'b': |
858 case 'B': |
858 case 'B': |
859 // readline alt-b - move one word to the left |
859 // readline alt-b - move one word to the left |
860 CursorPosition = find_previous_word(); |
860 m_cursorPosition = find_previous_word(); |
861 NeedInputRender = true; |
861 m_needInputRender = true; |
862 break; |
862 break; |
863 |
863 |
864 case 'f': |
864 case 'f': |
865 case 'F': |
865 case 'F': |
866 // readline alt-f - move one word to the right |
866 // readline alt-f - move one word to the right |
867 CursorPosition = find_next_word(); |
867 m_cursorPosition = find_next_word(); |
868 NeedInputRender = true; |
868 m_needInputRender = true; |
869 break; |
869 break; |
870 |
870 |
871 case 'd': |
871 case 'd': |
872 case 'D': |
872 case 'D': |
873 // readline alt-d - delete from here till next word boundary |
873 // readline alt-d - delete from here till next word boundary |
874 yank (CursorPosition, find_next_word()); |
874 yank (m_cursorPosition, find_next_word()); |
875 break; |
875 break; |
876 |
876 |
877 case KEY_BACKSPACE: // alt+backspace, remove previous word |
877 case KEY_BACKSPACE: // alt+backspace, remove previous word |
878 case '\b': |
878 case '\b': |
879 yank (find_previous_word(), CursorPosition); |
879 yank (find_previous_word(), m_cursorPosition); |
880 break; |
880 break; |
881 } |
881 } |
882 } |
882 } |
883 else |
883 else |
884 { |
884 { |
885 // No alt-key, handle pure escape |
885 // No alt-key, handle pure escape |
886 if (CurrentInputState == INPUTSTATE_PASSWORD) |
886 if (m_inputState == INPUTSTATE_PASSWORD) |
887 set_input_state (INPUTSTATE_ADDRESS); |
887 set_input_state (INPUTSTATE_ADDRESS); |
888 else if (CurrentInputState == INPUTSTATE_ADDRESS) |
888 else if (m_inputState == INPUTSTATE_ADDRESS) |
889 set_input_state (INPUTSTATE_NORMAL); |
889 set_input_state (INPUTSTATE_NORMAL); |
890 } |
890 } |
891 break; |
891 break; |
892 } |
892 } |
893 |
893 |
975 { |
975 { |
976 char ch = message[i]; |
976 char ch = message[i]; |
977 |
977 |
978 if (ch == '\n') |
978 if (ch == '\n') |
979 { |
979 { |
980 OutputLines.last().finalize(); |
980 m_outputLines.last().finalize(); |
981 OutputLines << ColoredLine(); |
981 m_outputLines << ColoredLine(); |
982 continue; |
982 continue; |
983 } |
983 } |
984 |
984 |
985 if (OutputLines.last().length() == 0) |
985 if (m_outputLines.last().length() == 0) |
986 { |
986 { |
987 time_t now; |
987 time_t now; |
988 time (&now); |
988 time (&now); |
989 char timestamp[32]; |
989 char timestamp[32]; |
990 strftime (timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime (&now)); |
990 strftime (timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime (&now)); |
991 |
991 |
992 for (char* cp = timestamp; *cp != '\0'; ++cp) |
992 for (char* cp = timestamp; *cp != '\0'; ++cp) |
993 OutputLines.last().add_char (*cp); |
993 m_outputLines.last().add_char (*cp); |
994 } |
994 } |
995 |
995 |
996 // Remove some lines if there's too many of them. 20,000 should be enough, I hope. |
996 // Remove some lines if there's too many of them. 20,000 should be enough, I hope. |
997 while (OutputLines.size() > 20000) |
997 while (m_outputLines.size() > 20000) |
998 OutputLines.remove_at(0); |
998 m_outputLines.remove_at(0); |
999 |
999 |
1000 OutputLines.last().add_char (ch); |
1000 m_outputLines.last().add_char (ch); |
1001 } |
1001 } |
1002 |
1002 |
1003 NeedOutputRender = true; |
1003 m_needOutputRender = true; |
1004 } |
1004 } |
1005 |
1005 |
1006 // ------------------------------------------------------------------------------------------------- |
1006 // ------------------------------------------------------------------------------------------------- |
1007 // |
1007 // |
1008 void Interface::connect (String address, String password) |
1008 void Interface::connect (String address, String password) |
1009 { |
1009 { |
1010 try |
1010 try |
1011 { |
1011 { |
1012 CurrentAddress = IPAddress::from_string (address); |
1012 m_remoteAddress = IPAddress::from_string (address); |
1013 } |
1013 } |
1014 catch (std::exception& e) |
1014 catch (std::exception& e) |
1015 { |
1015 { |
1016 print ("%s\n", e.what()); |
1016 print ("%s\n", e.what()); |
1017 return; |
1017 return; |
1018 } |
1018 } |
1019 |
1019 |
1020 if (CurrentAddress.port == 0) |
1020 if (m_remoteAddress.port == 0) |
1021 CurrentAddress.port = 10666; |
1021 m_remoteAddress.port = 10666; |
1022 |
1022 |
1023 Session.disconnect(); |
1023 m_session.disconnect(); |
1024 Session.set_password (password); |
1024 m_session.set_password (password); |
1025 Session.connect (CurrentAddress); |
1025 m_session.connect (m_remoteAddress); |
1026 } |
1026 } |
1027 |
1027 |
1028 // ------------------------------------------------------------------------------------------------- |
1028 // ------------------------------------------------------------------------------------------------- |
1029 // |
1029 // |
1030 void Interface::set_player_names (const StringList& names) |
1030 void Interface::set_player_names (const StringList& names) |
1031 { |
1031 { |
1032 PlayerNames = names; |
1032 m_playerNames = names; |
1033 NeedNicklistRender = true; |
1033 m_needNicklistRender = true; |
1034 } |
1034 } |
1035 |
1035 |
1036 // ------------------------------------------------------------------------------------------------- |
1036 // ------------------------------------------------------------------------------------------------- |
1037 // |
1037 // |
1038 void Interface::tab_complete (const String& part, String complete) |
1038 void Interface::tab_complete (const String& part, String complete) |