39 |
39 |
40 static const int PAGE_SIZE = 10; |
40 static const int PAGE_SIZE = 10; |
41 |
41 |
42 // ------------------------------------------------------------------------------------------------- |
42 // ------------------------------------------------------------------------------------------------- |
43 // |
43 // |
44 chtype Interface::color_pair (Color fg, Color bg) |
44 chtype Interface::getColorPair (Color fg, Color bg) |
45 { |
45 { |
46 if (fg == DEFAULT && bg == DEFAULT) |
46 if (fg == DEFAULT && bg == DEFAULT) |
47 return 0; |
47 return 0; |
48 else |
48 else |
49 return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg)); |
49 return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg)); |
50 } |
50 } |
51 |
51 |
52 // ------------------------------------------------------------------------------------------------- |
52 // ------------------------------------------------------------------------------------------------- |
53 // |
53 // |
54 const String& Interface::current_input() |
54 const String& Interface::getCurrentInput() |
55 { |
55 { |
56 return m_inputHistory[m_inputCursor]; |
56 return m_inputHistory[m_inputCursor]; |
57 } |
57 } |
58 |
58 |
59 // ------------------------------------------------------------------------------------------------- |
59 // ------------------------------------------------------------------------------------------------- |
60 // |
60 // |
61 // Makes current_input() the lastmost input (so that we won't modify history) |
61 // Makes current_input() the lastmost input (so that we won't modify history) |
62 // |
62 // |
63 void Interface::detach_input() |
63 void Interface::detachInput() |
64 { |
64 { |
65 if (m_inputCursor > 0) |
65 if (m_inputCursor > 0) |
66 { |
66 { |
67 m_inputHistory[0] = current_input(); |
67 m_inputHistory[0] = getCurrentInput(); |
68 m_inputCursor = 0; |
68 m_inputCursor = 0; |
69 } |
69 } |
70 } |
70 } |
71 |
71 |
72 // ------------------------------------------------------------------------------------------------- |
72 // ------------------------------------------------------------------------------------------------- |
73 // A version of current_input() that allows changing the contents of it. |
73 // A version of current_input() that allows changing the contents of it. |
74 // |
74 // |
75 String& Interface::mutable_current_input() |
75 String& Interface::getEditableInput() |
76 { |
76 { |
77 detach_input(); |
77 detachInput(); |
78 return m_inputHistory[m_inputCursor]; |
78 return m_inputHistory[m_inputCursor]; |
79 } |
79 } |
80 |
80 |
81 // ------------------------------------------------------------------------------------------------- |
81 // ------------------------------------------------------------------------------------------------- |
82 // |
82 // |
83 void Interface::move_input_cursor (int delta) |
83 void Interface::moveInputCursor (int delta) |
84 { |
84 { |
85 // No input history when inputting addresses or passwords |
85 // No input history when inputting addresses or passwords |
86 if (m_inputState != INPUTSTATE_NORMAL) |
86 if (m_inputState != INPUTSTATE_NORMAL) |
87 { |
87 { |
88 m_inputCursor = 0; |
88 m_inputCursor = 0; |
92 int oldcursor = m_inputCursor; |
92 int oldcursor = m_inputCursor; |
93 m_inputCursor = clamp (m_inputCursor + delta, 0, m_inputHistory.size() - 1); |
93 m_inputCursor = clamp (m_inputCursor + delta, 0, m_inputHistory.size() - 1); |
94 |
94 |
95 if (m_inputCursor != oldcursor) |
95 if (m_inputCursor != oldcursor) |
96 { |
96 { |
97 m_cursorPosition = current_input().length(); |
97 m_cursorPosition = getCurrentInput().length(); |
98 m_needInputRender = true; |
98 m_needInputRender = true; |
99 } |
99 } |
100 } |
100 } |
101 |
101 |
102 // ------------------------------------------------------------------------------------------------- |
102 // ------------------------------------------------------------------------------------------------- |
103 // |
103 // |
104 String Interface::prompt_string() |
104 String Interface::getPromptString() |
105 { |
105 { |
106 String prompt; |
106 String prompt; |
107 |
107 |
108 switch (m_inputState) |
108 switch (m_inputState) |
109 { |
109 { |
116 return prompt; |
116 return prompt; |
117 } |
117 } |
118 |
118 |
119 // ------------------------------------------------------------------------------------------------- |
119 // ------------------------------------------------------------------------------------------------- |
120 // |
120 // |
121 void Interface::set_input_state (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 m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) |
126 { |
126 { |
127 m_inputCursor = 0; |
127 m_inputCursor = 0; |
128 mutable_current_input().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 (m_remoteAddress.host != 0) |
135 mutable_current_input() = m_remoteAddress.to_string (IPAddress::WITH_PORT); |
135 getEditableInput() = m_remoteAddress.to_string (IPAddress::WITH_PORT); |
136 break; |
136 break; |
137 |
137 |
138 default: |
138 default: |
139 break; |
139 break; |
140 } |
140 } |
167 m_inputHistory.clear(); |
167 m_inputHistory.clear(); |
168 m_inputHistory << ""; |
168 m_inputHistory << ""; |
169 m_outputLines.clear(); |
169 m_outputLines.clear(); |
170 m_outputLines << ColoredLine(); |
170 m_outputLines << ColoredLine(); |
171 m_session.set_interface (this); |
171 m_session.set_interface (this); |
172 reset_title(); |
172 resetTitle(); |
173 |
173 |
174 if (::has_colors()) |
174 if (::has_colors()) |
175 { |
175 { |
176 ::start_color(); |
176 ::start_color(); |
177 bool hasDefaultColors = (::use_default_colors() == OK); |
177 bool hasDefaultColors = (::use_default_colors() == OK); |
199 m_needRefresh = false; |
199 m_needRefresh = false; |
200 } |
200 } |
201 |
201 |
202 // ------------------------------------------------------------------------------------------------- |
202 // ------------------------------------------------------------------------------------------------- |
203 // |
203 // |
204 void Interface::render_titlebar() |
204 void Interface::renderTitlebar() |
205 { |
205 { |
206 if (m_title.length() <= COLS) |
206 if (m_title.length() <= COLS) |
207 { |
207 { |
208 chtype pair = color_pair (WHITE, BLUE); |
208 chtype pair = getColorPair (WHITE, BLUE); |
209 int startx = (COLS - m_title.length()) / 2; |
209 int startx = (COLS - m_title.length()) / 2; |
210 int endx = startx + m_title.length(); |
210 int endx = startx + m_title.length(); |
211 attron (pair); |
211 attron (pair); |
212 mvprintw (0, startx, "%s", m_title.chars()); |
212 mvprintw (0, startx, "%s", m_title.chars()); |
213 mvhline (0, 0, ' ', startx); |
213 mvhline (0, 0, ' ', startx); |
221 // ------------------------------------------------------------------------------------------------- |
221 // ------------------------------------------------------------------------------------------------- |
222 // |
222 // |
223 void Interface::setTitle (const String& title) |
223 void Interface::setTitle (const String& title) |
224 { |
224 { |
225 m_title = title; |
225 m_title = title; |
226 render_titlebar(); |
226 renderTitlebar(); |
227 } |
227 } |
228 |
228 |
229 // ------------------------------------------------------------------------------------------------- |
229 // ------------------------------------------------------------------------------------------------- |
230 // |
230 // |
231 void Interface::safe_disconnect (std::function<void(bool)> afterwards) |
231 void Interface::safeDisconnect (std::function<void(bool)> afterwards) |
232 { |
232 { |
233 if (m_session.is_active()) |
233 if (m_session.is_active()) |
234 { |
234 { |
235 m_disconnectCallback = afterwards; |
235 m_disconnectCallback = afterwards; |
236 set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION); |
236 setInputState (INPUTSTATE_CONFIRM_DISCONNECTION); |
237 } |
237 } |
238 else |
238 else |
239 afterwards(false); |
239 afterwards(false); |
240 } |
240 } |
241 |
241 |
242 // ------------------------------------------------------------------------------------------------- |
242 // ------------------------------------------------------------------------------------------------- |
243 // |
243 // |
244 int Interface::nicklist_width() |
244 int Interface::nicklistWidth() |
245 { |
245 { |
246 // Allocate at least 12 characters, at most 24 characters, for the nicklist. If we cannot |
246 // Allocate at least 12 characters, at most 24 characters, for the nicklist. If we cannot |
247 // afford that (o_O) then we probably shouldn't draw the nicklist at all I think. |
247 // afford that (o_O) then we probably shouldn't draw the nicklist at all I think. |
248 int nicklistWidth = COLS / 4; |
248 int nicklistWidth = COLS / 4; |
249 |
249 |
255 |
255 |
256 // ------------------------------------------------------------------------------------------------- |
256 // ------------------------------------------------------------------------------------------------- |
257 // Renders the given colored line onto the screen. Will wrap if allowWrap is true. Returns the |
257 // Renders the given colored line onto the screen. Will wrap if allowWrap is true. Returns the |
258 // 'y' value for the next line. |
258 // 'y' value for the next line. |
259 // |
259 // |
260 int Interface::render_colorline (int y, int x0, int width, const ColoredLine& line, bool allowWrap) |
260 int Interface::renderColorline (int y, int x0, int width, const ColoredLine& line, bool allowWrap) |
261 { |
261 { |
262 int x = x0; |
262 int x = x0; |
263 |
263 |
264 for (int byte : line.data()) |
264 for (int byte : line.data()) |
265 { |
265 { |
297 return y + 1; |
297 return y + 1; |
298 } |
298 } |
299 |
299 |
300 // ------------------------------------------------------------------------------------------------- |
300 // ------------------------------------------------------------------------------------------------- |
301 // |
301 // |
302 void Interface::render_output() |
302 void Interface::renderOutput() |
303 { |
303 { |
304 if (m_outputLines.size() == 1) |
304 if (m_outputLines.size() == 1) |
305 return; |
305 return; |
306 |
306 |
307 m_outputScroll = clamp (m_outputScroll, 0, m_outputLines.size() - 1); |
307 m_outputScroll = clamp (m_outputScroll, 0, m_outputLines.size() - 1); |
308 |
308 |
309 int height = LINES - 3; |
309 int height = LINES - 3; |
310 int width = COLS - nicklist_width(); |
310 int width = COLS - nicklistWidth(); |
311 int printOffset = 0; |
311 int printOffset = 0; |
312 int end = m_outputLines.size() - 1 - m_outputScroll; |
312 int end = m_outputLines.size() - 1 - m_outputScroll; |
313 int start = end; |
313 int start = end; |
314 int usedHeight = 0; |
314 int usedHeight = 0; |
315 int y = 1; |
315 int y = 1; |
365 |
365 |
366 // Print the lines |
366 // Print the lines |
367 y += printOffset; |
367 y += printOffset; |
368 |
368 |
369 for (int i : range(start, end)) |
369 for (int i : range(start, end)) |
370 y = render_colorline (y, 0, width, m_outputLines[i], true); |
370 y = renderColorline (y, 0, width, m_outputLines[i], true); |
371 |
371 |
372 m_needOutputRender = false; |
372 m_needOutputRender = false; |
373 m_needRefresh = true; |
373 m_needRefresh = true; |
374 } |
374 } |
375 |
375 |
376 // ------------------------------------------------------------------------------------------------- |
376 // ------------------------------------------------------------------------------------------------- |
377 // |
377 // |
378 void Interface::render_nicklist() |
378 void Interface::renderNicklist() |
379 { |
379 { |
380 int width = nicklist_width(); |
380 int width = nicklistWidth(); |
381 int height = LINES- 3; |
381 int height = LINES- 3; |
382 int y = 1; |
382 int y = 1; |
383 int x = COLS - width; |
383 int x = COLS - width; |
384 |
384 |
385 if (width > 0) |
385 if (width > 0) |
388 for (int i : range(height)) |
388 for (int i : range(height)) |
389 { |
389 { |
390 mvhline (y, x, ' ', width); |
390 mvhline (y, x, ' ', width); |
391 |
391 |
392 if (i < m_playerNames.size()) |
392 if (i < m_playerNames.size()) |
393 render_colorline (y, x, width, m_playerNames[i], false); |
393 renderColorline (y, x, width, m_playerNames[i], false); |
394 |
394 |
395 y++; |
395 y++; |
396 } |
396 } |
397 |
397 |
398 m_needNicklistRender = false; |
398 m_needNicklistRender = false; |
399 m_needRefresh = true; |
399 m_needRefresh = true; |
400 } |
400 } |
401 |
401 |
402 // ------------------------------------------------------------------------------------------------- |
402 // ------------------------------------------------------------------------------------------------- |
403 // |
403 // |
404 void Interface::render_input() |
404 void Interface::renderInput() |
405 { |
405 { |
406 chtype promptColor = color_pair (WHITE, BLUE); |
406 chtype promptColor = getColorPair (WHITE, BLUE); |
407 |
407 |
408 // If we're asking the user if they want to disconnect, we don't render any input strings, |
408 // If we're asking the user if they want to disconnect, we don't render any input strings, |
409 // just the confirmation message. |
409 // just the confirmation message. |
410 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
410 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
411 { |
411 { |
415 attroff (promptColor); |
415 attroff (promptColor); |
416 m_needRefresh = true; |
416 m_needRefresh = true; |
417 return; |
417 return; |
418 } |
418 } |
419 |
419 |
420 String prompt = prompt_string(); |
420 String prompt = getPromptString(); |
421 int displayLength = COLS - prompt.length() - 2; |
421 int displayLength = COLS - prompt.length() - 2; |
422 String displayString = current_input(); |
422 String displayString = getCurrentInput(); |
423 int y = LINES - 2; |
423 int y = LINES - 2; |
424 |
424 |
425 // If we're inputting a password, replace it with asterisks |
425 // If we're inputting a password, replace it with asterisks |
426 if (m_inputState == INPUTSTATE_PASSWORD) |
426 if (m_inputState == INPUTSTATE_PASSWORD) |
427 { |
427 { |
460 m_needInputRender = false; |
460 m_needInputRender = false; |
461 } |
461 } |
462 |
462 |
463 // ------------------------------------------------------------------------------------------------- |
463 // ------------------------------------------------------------------------------------------------- |
464 // |
464 // |
465 void Interface::render_statusbar() |
465 void Interface::renderStatusBar() |
466 { |
466 { |
467 chtype color = color_pair (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.chars()); |
472 attroff (color); |
472 attroff (color); |
529 // ------------------------------------------------------------------------------------------------- |
529 // ------------------------------------------------------------------------------------------------- |
530 // |
530 // |
531 void Interface::renderFull() |
531 void Interface::renderFull() |
532 { |
532 { |
533 updateStatusBar(); |
533 updateStatusBar(); |
534 render_titlebar(); |
534 renderTitlebar(); |
535 render_output(); |
535 renderOutput(); |
536 render_statusbar(); |
536 renderStatusBar(); |
537 render_input(); |
537 renderInput(); |
538 render_nicklist(); |
538 renderNicklist(); |
539 } |
539 } |
540 |
540 |
541 // ------------------------------------------------------------------------------------------------- |
541 // ------------------------------------------------------------------------------------------------- |
542 // |
542 // |
543 void Interface::position_cursor() |
543 void Interface::positionCursor() |
544 { |
544 { |
545 // This is only relevant if the input string is being drawn |
545 // This is only relevant if the input string is being drawn |
546 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
546 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
547 return; |
547 return; |
548 |
548 |
549 int y = LINES - 2; |
549 int y = LINES - 2; |
550 |
550 |
551 if (m_cursorCharacter.ch != '\0') |
551 if (m_cursorCharacter.ch != '\0') |
552 mvprintw (y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); |
552 mvprintw (y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); |
553 else |
553 else |
554 mvprintw (y, prompt_string().length(), " "); |
554 mvprintw (y, getPromptString().length(), " "); |
555 } |
555 } |
556 |
556 |
557 // ------------------------------------------------------------------------------------------------- |
557 // ------------------------------------------------------------------------------------------------- |
558 // |
558 // |
559 int Interface::find_previous_word() |
559 int Interface::findPreviousWord() |
560 { |
560 { |
561 const String& input = current_input(); |
561 const String& input = getCurrentInput(); |
562 int pos = m_cursorPosition; |
562 int pos = m_cursorPosition; |
563 |
563 |
564 // Move past whitespace |
564 // Move past whitespace |
565 while (pos > 0 and isspace (input[pos - 1])) |
565 while (pos > 0 and isspace (input[pos - 1])) |
566 pos--; |
566 pos--; |
572 return pos; |
572 return pos; |
573 } |
573 } |
574 |
574 |
575 // ------------------------------------------------------------------------------------------------- |
575 // ------------------------------------------------------------------------------------------------- |
576 // |
576 // |
577 int Interface::find_next_word() |
577 int Interface::findNextWord() |
578 { |
578 { |
579 const String& input = current_input(); |
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 < input.length() and isspace (input[pos])) |
584 pos++; |
584 pos++; |
598 return; |
598 return; |
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 = mutable_current_input(); |
603 String& input = getEditableInput(); |
604 m_pasteBuffer = input.mid (a, b); |
604 m_pasteBuffer = input.mid (a, b); |
605 input.remove (a, b - a); |
605 input.remove (a, b - a); |
606 m_needInputRender = true; |
606 m_needInputRender = true; |
607 } |
607 } |
608 |
608 |
628 { |
628 { |
629 m_session.disconnect(); |
629 m_session.disconnect(); |
630 m_disconnectCallback(true); |
630 m_disconnectCallback(true); |
631 } |
631 } |
632 else if (ch == 'n' or ch == 'N') |
632 else if (ch == 'n' or ch == 'N') |
633 set_input_state (INPUTSTATE_NORMAL); |
633 setInputState (INPUTSTATE_NORMAL); |
634 |
634 |
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 mutable_current_input().insert (m_cursorPosition++, char (ch)); |
640 getEditableInput().insert (m_cursorPosition++, char (ch)); |
641 m_needInputRender = true; |
641 m_needInputRender = true; |
642 } |
642 } |
643 else switch (ch) |
643 else switch (ch) |
644 { |
644 { |
645 case 'Q' - 'A' + 1: // ^Q |
645 case 'Q' - 'A' + 1: // ^Q |
647 { |
647 { |
648 case INPUTSTATE_CONFIRM_DISCONNECTION: |
648 case INPUTSTATE_CONFIRM_DISCONNECTION: |
649 break; |
649 break; |
650 |
650 |
651 case INPUTSTATE_NORMAL: |
651 case INPUTSTATE_NORMAL: |
652 safe_disconnect ([&](bool hadsession) |
652 safeDisconnect ([&](bool hadsession) |
653 { |
653 { |
654 if (hadsession) |
654 if (hadsession) |
655 { |
655 { |
656 set_input_state (INPUTSTATE_NORMAL); |
656 setInputState (INPUTSTATE_NORMAL); |
657 } |
657 } |
658 else |
658 else |
659 { |
659 { |
660 endwin(); |
660 endwin(); |
661 throw Exitception(); |
661 throw Exitception(); |
662 } |
662 } |
663 }); |
663 }); |
664 break; |
664 break; |
665 |
665 |
666 case INPUTSTATE_PASSWORD: |
666 case INPUTSTATE_PASSWORD: |
667 set_input_state (INPUTSTATE_ADDRESS); |
667 setInputState (INPUTSTATE_ADDRESS); |
668 break; |
668 break; |
669 |
669 |
670 case INPUTSTATE_ADDRESS: |
670 case INPUTSTATE_ADDRESS: |
671 set_input_state (INPUTSTATE_NORMAL); |
671 setInputState (INPUTSTATE_NORMAL); |
672 } |
672 } |
673 break; |
673 break; |
674 |
674 |
675 case KEY_LEFT: |
675 case KEY_LEFT: |
676 case 'B' - 'A' + 1: // readline ^B |
676 case 'B' - 'A' + 1: // readline ^B |
681 } |
681 } |
682 break; |
682 break; |
683 |
683 |
684 case KEY_RIGHT: |
684 case KEY_RIGHT: |
685 case 'F' - 'A' + 1: // readline ^F |
685 case 'F' - 'A' + 1: // readline ^F |
686 if (m_cursorPosition < current_input().length()) |
686 if (m_cursorPosition < getCurrentInput().length()) |
687 { |
687 { |
688 m_cursorPosition++; |
688 m_cursorPosition++; |
689 m_needInputRender = true; |
689 m_needInputRender = true; |
690 } |
690 } |
691 break; |
691 break; |
692 |
692 |
693 case KEY_DOWN: |
693 case KEY_DOWN: |
694 case KEY_UP: |
694 case KEY_UP: |
695 move_input_cursor (ch == KEY_DOWN ? -1 : 1); |
695 moveInputCursor (ch == KEY_DOWN ? -1 : 1); |
696 break; |
696 break; |
697 |
697 |
698 case KEY_HOME: |
698 case KEY_HOME: |
699 case 'A' - 'A' + 1: // readline ^A |
699 case 'A' - 'A' + 1: // readline ^A |
700 if (m_cursorPosition != 0) |
700 if (m_cursorPosition != 0) |
704 } |
704 } |
705 break; |
705 break; |
706 |
706 |
707 case KEY_END: |
707 case KEY_END: |
708 case 'E' - 'A' + 1: // readline ^E |
708 case 'E' - 'A' + 1: // readline ^E |
709 if (m_cursorPosition != current_input().length()) |
709 if (m_cursorPosition != getCurrentInput().length()) |
710 { |
710 { |
711 m_cursorPosition = current_input().length(); |
711 m_cursorPosition = getCurrentInput().length(); |
712 m_needInputRender = true; |
712 m_needInputRender = true; |
713 } |
713 } |
714 break; |
714 break; |
715 |
715 |
716 case KEY_BACKSPACE: |
716 case KEY_BACKSPACE: |
717 case '\b': |
717 case '\b': |
718 if (m_cursorPosition > 0) |
718 if (m_cursorPosition > 0) |
719 { |
719 { |
720 mutable_current_input().remove_at (--m_cursorPosition); |
720 getEditableInput().remove_at (--m_cursorPosition); |
721 m_needInputRender = true; |
721 m_needInputRender = true; |
722 } |
722 } |
723 break; |
723 break; |
724 |
724 |
725 case KEY_DC: |
725 case KEY_DC: |
726 case 'D' - 'A' + 1: // readline ^D |
726 case 'D' - 'A' + 1: // readline ^D |
727 if (m_cursorPosition < current_input().length()) |
727 if (m_cursorPosition < getCurrentInput().length()) |
728 { |
728 { |
729 mutable_current_input().remove_at (m_cursorPosition); |
729 getEditableInput().remove_at (m_cursorPosition); |
730 m_needInputRender = true; |
730 m_needInputRender = true; |
731 } |
731 } |
732 break; |
732 break; |
733 |
733 |
734 case KEY_PPAGE: |
734 case KEY_PPAGE: |
748 m_cursorPosition = 0; |
748 m_cursorPosition = 0; |
749 } |
749 } |
750 break; |
750 break; |
751 |
751 |
752 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
752 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
753 yank (m_cursorPosition, mutable_current_input().length()); |
753 yank (m_cursorPosition, getEditableInput().length()); |
754 break; |
754 break; |
755 |
755 |
756 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
756 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
757 yank (find_previous_word(), m_cursorPosition); |
757 yank (findPreviousWord(), m_cursorPosition); |
758 break; |
758 break; |
759 |
759 |
760 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
760 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
761 if (not m_pasteBuffer.is_empty()) |
761 if (not m_pasteBuffer.is_empty()) |
762 { |
762 { |
763 mutable_current_input().insert (m_cursorPosition, m_pasteBuffer); |
763 getEditableInput().insert (m_cursorPosition, m_pasteBuffer); |
764 m_cursorPosition += m_pasteBuffer.length(); |
764 m_cursorPosition += m_pasteBuffer.length(); |
765 m_needInputRender = true; |
765 m_needInputRender = true; |
766 } |
766 } |
767 break; |
767 break; |
768 |
768 |
769 case '\t': |
769 case '\t': |
770 { |
770 { |
771 int space = current_input().find (" "); |
771 int space = getCurrentInput().find (" "); |
772 |
772 |
773 if (m_inputState == INPUTSTATE_NORMAL |
773 if (m_inputState == INPUTSTATE_NORMAL |
774 and m_cursorPosition > 0 |
774 and m_cursorPosition > 0 |
775 and (space == -1 or space >= m_cursorPosition)) |
775 and (space == -1 or space >= m_cursorPosition)) |
776 { |
776 { |
777 String start = current_input().mid (0, m_cursorPosition); |
777 String start = getCurrentInput().mid (0, m_cursorPosition); |
778 m_session.request_tab_complete (start); |
778 m_session.request_tab_complete (start); |
779 } |
779 } |
780 } |
780 } |
781 break; |
781 break; |
782 |
782 |
789 break; // handled above |
789 break; // handled above |
790 |
790 |
791 case INPUTSTATE_ADDRESS: |
791 case INPUTSTATE_ADDRESS: |
792 try |
792 try |
793 { |
793 { |
794 m_remoteAddress = IPAddress::from_string (current_input()); |
794 m_remoteAddress = IPAddress::from_string (getCurrentInput()); |
795 } |
795 } |
796 catch (std::exception& e) |
796 catch (std::exception& e) |
797 { |
797 { |
798 print ("%s\n", e.what()); |
798 print ("%s\n", e.what()); |
799 return; |
799 return; |
800 } |
800 } |
801 |
801 |
802 if (m_remoteAddress.port == 0) |
802 if (m_remoteAddress.port == 0) |
803 m_remoteAddress.port = 10666; |
803 m_remoteAddress.port = 10666; |
804 |
804 |
805 set_input_state (INPUTSTATE_PASSWORD); |
805 setInputState (INPUTSTATE_PASSWORD); |
806 break; |
806 break; |
807 |
807 |
808 case INPUTSTATE_PASSWORD: |
808 case INPUTSTATE_PASSWORD: |
809 if (m_inputState == INPUTSTATE_PASSWORD and not current_input().is_empty()) |
809 if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().is_empty()) |
810 { |
810 { |
811 m_session.disconnect(); |
811 m_session.disconnect(); |
812 m_session.set_password (current_input()); |
812 m_session.set_password (getCurrentInput()); |
813 m_session.connect (m_remoteAddress); |
813 m_session.connect (m_remoteAddress); |
814 set_input_state (INPUTSTATE_NORMAL); |
814 setInputState (INPUTSTATE_NORMAL); |
815 } |
815 } |
816 break; |
816 break; |
817 |
817 |
818 case INPUTSTATE_NORMAL: |
818 case INPUTSTATE_NORMAL: |
819 if (current_input()[0] == '/') |
819 if (getCurrentInput()[0] == '/') |
820 { |
820 { |
821 handleCommand(current_input()); |
821 handleCommand(getCurrentInput()); |
822 flush_input(); |
822 flushInput(); |
823 } |
823 } |
824 else if (m_session.send_command (current_input())) |
824 else if (m_session.send_command (getCurrentInput())) |
825 { |
825 { |
826 flush_input(); |
826 flushInput(); |
827 } |
827 } |
828 break; |
828 break; |
829 } |
829 } |
830 break; |
830 break; |
831 |
831 |
832 case 'N' - 'A' + 1: // ^N |
832 case 'N' - 'A' + 1: // ^N |
833 if (m_inputState == INPUTSTATE_NORMAL) |
833 if (m_inputState == INPUTSTATE_NORMAL) |
834 safe_disconnect ([&](bool){set_input_state (INPUTSTATE_ADDRESS);}); |
834 safeDisconnect ([&](bool){setInputState (INPUTSTATE_ADDRESS);}); |
835 break; |
835 break; |
836 |
836 |
837 case '\x1b': // Escape |
837 case '\x1b': // Escape |
838 // We may have an alt key coming |
838 // We may have an alt key coming |
839 ch = ::getch(); |
839 ch = ::getch(); |
843 switch (ch) |
843 switch (ch) |
844 { |
844 { |
845 case 'b': |
845 case 'b': |
846 case 'B': |
846 case 'B': |
847 // readline alt-b - move one word to the left |
847 // readline alt-b - move one word to the left |
848 m_cursorPosition = find_previous_word(); |
848 m_cursorPosition = findPreviousWord(); |
849 m_needInputRender = true; |
849 m_needInputRender = true; |
850 break; |
850 break; |
851 |
851 |
852 case 'f': |
852 case 'f': |
853 case 'F': |
853 case 'F': |
854 // readline alt-f - move one word to the right |
854 // readline alt-f - move one word to the right |
855 m_cursorPosition = find_next_word(); |
855 m_cursorPosition = findNextWord(); |
856 m_needInputRender = true; |
856 m_needInputRender = true; |
857 break; |
857 break; |
858 |
858 |
859 case 'd': |
859 case 'd': |
860 case 'D': |
860 case 'D': |
861 // readline alt-d - delete from here till next word boundary |
861 // readline alt-d - delete from here till next word boundary |
862 yank (m_cursorPosition, find_next_word()); |
862 yank (m_cursorPosition, findNextWord()); |
863 break; |
863 break; |
864 |
864 |
865 case KEY_BACKSPACE: // alt+backspace, remove previous word |
865 case KEY_BACKSPACE: // alt+backspace, remove previous word |
866 case '\b': |
866 case '\b': |
867 yank (find_previous_word(), m_cursorPosition); |
867 yank (findPreviousWord(), m_cursorPosition); |
868 break; |
868 break; |
869 } |
869 } |
870 } |
870 } |
871 else |
871 else |
872 { |
872 { |
873 // No alt-key, handle pure escape |
873 // No alt-key, handle pure escape |
874 if (m_inputState == INPUTSTATE_PASSWORD) |
874 if (m_inputState == INPUTSTATE_PASSWORD) |
875 set_input_state (INPUTSTATE_ADDRESS); |
875 setInputState (INPUTSTATE_ADDRESS); |
876 else if (m_inputState == INPUTSTATE_ADDRESS) |
876 else if (m_inputState == INPUTSTATE_ADDRESS) |
877 set_input_state (INPUTSTATE_NORMAL); |
877 setInputState (INPUTSTATE_NORMAL); |
878 } |
878 } |
879 break; |
879 break; |
880 } |
880 } |
881 |
881 |
882 render(); |
882 render(); |
884 |
884 |
885 // ------------------------------------------------------------------------------------------------- |
885 // ------------------------------------------------------------------------------------------------- |
886 // |
886 // |
887 void Interface::render() |
887 void Interface::render() |
888 { |
888 { |
889 if (m_needStatusBarRender) render_statusbar(); |
889 if (m_needStatusBarRender) renderStatusBar(); |
890 if (m_needInputRender) render_input(); |
890 if (m_needInputRender) renderInput(); |
891 if (m_needOutputRender) render_output(); |
891 if (m_needOutputRender) renderOutput(); |
892 if (m_needNicklistRender) render_nicklist(); |
892 if (m_needNicklistRender) renderNicklist(); |
893 |
893 |
894 if (m_needRefresh) |
894 if (m_needRefresh) |
895 { |
895 { |
896 position_cursor(); |
896 positionCursor(); |
897 refresh(); |
897 refresh(); |
898 m_needRefresh = false; |
898 m_needRefresh = false; |
899 } |
899 } |
900 } |
900 } |
901 |
901 |
903 // |
903 // |
904 void Interface::vprint (const char* fmtstr, va_list args) |
904 void Interface::vprint (const char* fmtstr, va_list args) |
905 { |
905 { |
906 String message; |
906 String message; |
907 message.vsprintf (fmtstr, args); |
907 message.vsprintf (fmtstr, args); |
908 print_to_console (message); |
908 printToConsole (message); |
909 } |
909 } |
910 |
910 |
911 // ------------------------------------------------------------------------------------------------- |
911 // ------------------------------------------------------------------------------------------------- |
912 // |
912 // |
913 void __cdecl Interface::printText (const char* fmtstr, ...) |
913 void __cdecl Interface::printText (const char* fmtstr, ...) |
922 // |
922 // |
923 void __cdecl Interface::print (const char* fmtstr, ...) |
923 void __cdecl Interface::print (const char* fmtstr, ...) |
924 { |
924 { |
925 va_list args; |
925 va_list args; |
926 va_start (args, fmtstr); |
926 va_start (args, fmtstr); |
927 print_to_console (TEXTCOLOR_BrightBlue); |
927 printToConsole (TEXTCOLOR_BrightBlue); |
928 vprint (fmtstr, args); |
928 vprint (fmtstr, args); |
929 va_end (args); |
929 va_end (args); |
930 } |
930 } |
931 |
931 |
932 // ------------------------------------------------------------------------------------------------- |
932 // ------------------------------------------------------------------------------------------------- |
933 // |
933 // |
934 void __cdecl Interface::printWarning (const char* fmtstr, ...) |
934 void __cdecl Interface::printWarning (const char* fmtstr, ...) |
935 { |
935 { |
936 va_list args; |
936 va_list args; |
937 va_start (args, fmtstr); |
937 va_start (args, fmtstr); |
938 print_to_console (TEXTCOLOR_BrightYellow "-!- "); |
938 printToConsole (TEXTCOLOR_BrightYellow "-!- "); |
939 vprint (fmtstr, args); |
939 vprint (fmtstr, args); |
940 va_end (args); |
940 va_end (args); |
941 } |
941 } |
942 |
942 |
943 // ------------------------------------------------------------------------------------------------- |
943 // ------------------------------------------------------------------------------------------------- |
944 // |
944 // |
945 void __cdecl Interface::printError (const char* fmtstr, ...) |
945 void __cdecl Interface::printError (const char* fmtstr, ...) |
946 { |
946 { |
947 va_list args; |
947 va_list args; |
948 va_start (args, fmtstr); |
948 va_start (args, fmtstr); |
949 print_to_console (TEXTCOLOR_BrightRed "!!! "); |
949 printToConsole (TEXTCOLOR_BrightRed "!!! "); |
950 vprint (fmtstr, args); |
950 vprint (fmtstr, args); |
951 va_end (args); |
951 va_end (args); |
952 } |
952 } |
953 |
953 |
954 // ------------------------------------------------------------------------------------------------- |
954 // ------------------------------------------------------------------------------------------------- |
955 // |
955 // |
956 void Interface::print_to_console (String message) |
956 void Interface::printToConsole (String message) |
957 { |
957 { |
958 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". |
958 // 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. |
959 // Let's correct that on our end and hope this won't cause conflicts. |
960 message.replace ("\\c", "\x1C"); |
960 message.replace ("\\c", "\x1C"); |
961 |
961 |
1030 |
1030 |
1031 // ------------------------------------------------------------------------------------------------- |
1031 // ------------------------------------------------------------------------------------------------- |
1032 // |
1032 // |
1033 void Interface::tabComplete (const String& part, String complete) |
1033 void Interface::tabComplete (const String& part, String complete) |
1034 { |
1034 { |
1035 String& input = mutable_current_input(); |
1035 String& input = getEditableInput(); |
1036 |
1036 |
1037 if (input.starts_with (part)) |
1037 if (input.starts_with (part)) |
1038 { |
1038 { |
1039 if (input[part.length()] != ' ') |
1039 if (input[part.length()] != ' ') |
1040 complete += ' '; |
1040 complete += ' '; |
1101 // ------------------------------------------------------------------------------------------------- |
1101 // ------------------------------------------------------------------------------------------------- |
1102 // |
1102 // |
1103 void Interface::disconnected() |
1103 void Interface::disconnected() |
1104 { |
1104 { |
1105 print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).chars()); |
1105 print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).chars()); |
1106 reset_title(); |
1106 resetTitle(); |
1107 renderFull(); |
1107 renderFull(); |
1108 } |
1108 } |
1109 |
1109 |
1110 // ------------------------------------------------------------------------------------------------- |
1110 // ------------------------------------------------------------------------------------------------- |
1111 // |
1111 // |
1112 void Interface::reset_title() |
1112 void Interface::resetTitle() |
1113 { |
1113 { |
1114 m_title.sprintf ("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1114 m_title.sprintf ("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1115 } |
1115 } |
1116 |
1116 |
1117 // ------------------------------------------------------------------------------------------------- |
1117 // ------------------------------------------------------------------------------------------------- |
1118 // |
1118 // |
1119 void Interface::flush_input() |
1119 void Interface::flushInput() |
1120 { |
1120 { |
1121 m_inputHistory.insert (0, ""); |
1121 m_inputHistory.insert (0, ""); |
1122 m_inputCursor = 0; |
1122 m_inputCursor = 0; |
1123 m_needInputRender = true; |
1123 m_needInputRender = true; |
1124 } |
1124 } |