sources/interface.cpp

branch
protocol5
changeset 195
be953e1621d9
parent 160
cf514fa0f1cc
parent 194
0c7e44e1078a
equal deleted inserted replaced
176:060a13878ca0 195:be953e1621d9
1 /* 1 /*
2 Copyright 2014 - 2016 Teemu Piippo 2 Copyright 2014 - 2021 Teemu Piippo
3 All rights reserved. 3 All rights reserved.
4 4
5 Redistribution and use in source and binary forms, with or without 5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions 6 modification, are permitted provided that the following conditions
7 are met: 7 are met:
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;
120 // 120 //
121 void Interface::setInputState(InputState newstate) 121 void Interface::setInputState(InputState newstate)
122 { 122 {
123 // Clear the input row(unless going to or from confirm state) 123 // Clear the input row(unless going to or from confirm state)
124 if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION 124 if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION
125 and m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) 125 and this->m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION)
126 { 126 {
127 m_inputCursor = 0; 127 this->m_inputCursor = 0;
128 getEditableInput().clear(); 128 getEditableInput().clear();
129 } 129 }
130 130
131 switch (newstate) 131 switch (newstate)
132 { 132 {
133 case INPUTSTATE_ADDRESS: 133 case INPUTSTATE_ADDRESS:
134 if (m_remoteAddress.host != 0) 134 if (this->m_remoteAddress.host != 0)
135 getEditableInput() = m_remoteAddress.to_string(IPAddress::WITH_PORT); 135 getEditableInput() = net::ip_address_to_string(this->m_remoteAddress);
136 break; 136 break;
137 137
138 default: 138 default:
139 break; 139 break;
140 } 140 }
141 141
142 m_inputState = newstate; 142 this->m_inputState = newstate;
143 m_needInputRender = true; 143 this->m_needInputRender = true;
144 } 144 }
145 145
146 // ------------------------------------------------------------------------------------------------- 146 // -------------------------------------------------------------------------------------------------
147 // 147 //
148 Interface::Interface() : 148 Interface::Interface() :
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 m_inputHistory.clear(); 167 this->m_inputHistory.clear();
168 m_inputHistory << ""; 168 this->m_inputHistory.push_back("");
169 m_outputLines.clear(); 169 this->m_outputLines.clear();
170 m_outputLines << ColoredLine(); 170 this->m_outputLines.push_back(ColoredLine());
171 m_session.setInterface(this); 171 this->m_session.setInterface(this);
172 resetTitle(); 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);
178 int defaultFg = hasDefaultColors ? -1 : COLOR_WHITE; 178 int defaultFg = hasDefaultColors ? -1 : COLOR_WHITE;
179 int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK; 179 int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK;
180 180
181 // Initialize color pairs 181 // Initialize color pairs
182 for (int i : range<int>(NUM_COLORS)) 182 for (int i = 0; i < NUM_COLORS; i += 1)
183 for (int j : range<int>(NUM_COLORS)) 183 for (int j = 0; j < NUM_COLORS; j += 1)
184 { 184 {
185 int pairnum = 1 + (i * NUM_COLORS + j); 185 int pairnum = 1 + (i * NUM_COLORS + j);
186 int fg =(i == DEFAULT) ? defaultFg : i; 186 int fg =(i == DEFAULT) ? defaultFg : i;
187 int bg =(j == DEFAULT) ? defaultBg : j; 187 int bg =(j == DEFAULT) ? defaultBg : j;
188 188
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 }
299 304
300 // ------------------------------------------------------------------------------------------------- 305 // -------------------------------------------------------------------------------------------------
301 // 306 //
302 void Interface::renderOutput() 307 void Interface::renderOutput()
303 { 308 {
304 if (m_outputLines.size() == 1) 309 if (this->m_outputLines.size() == 1)
305 return; 310 return;
306 311
307 m_outputScroll = clamp(m_outputScroll, 0, m_outputLines.size() - 1); 312 this->m_outputScroll = clamp(this->m_outputScroll, 0, static_cast<signed>(this->m_outputLines.size() - 1));
308 313
309 int height = LINES - 3; 314 int height = LINES - 3;
310 int width = COLS - nicklistWidth(); 315 int width = COLS - nicklistWidth();
311 int printOffset = 0; 316 int printOffset = 0;
312 int end = m_outputLines.size() - 1 - m_outputScroll; 317 int end = this->m_outputLines.size() - 1 - this->m_outputScroll;
313 int start = end; 318 int start = end;
314 int usedHeight = 0; 319 int usedHeight = 0;
315 int y = 1; 320 int y = 1;
316 bool tightFit = false; 321 bool tightFit = false;
317 322
318 // Where to start? 323 // Where to start?
319 while (start > 0) 324 while (start > 0)
320 { 325 {
321 int rows = m_outputLines[start - 1].rows(width); 326 int rows = this->m_outputLines[start - 1].rows(width);
322 327
323 if (usedHeight + rows > height) 328 if (usedHeight + rows > height)
324 { 329 {
325 // This line won't fit anymore. 330 // This line won't fit anymore.
326 tightFit = true; 331 tightFit = true;
332 } 337 }
333 338
334 // See if there's any more rows to use(end may be too small) 339 // See if there's any more rows to use(end may be too small)
335 if (not tightFit) 340 if (not tightFit)
336 { 341 {
337 while (end < m_outputLines.size()) 342 while (end < static_cast<int>(this->m_outputLines.size()))
338 { 343 {
339 int rows = m_outputLines[end].rows(width); 344 int rows = this->m_outputLines[end].rows(width);
340 345
341 if (usedHeight + rows > height) 346 if (usedHeight + rows > height)
342 { 347 {
343 tightFit = true; 348 tightFit = true;
344 break; 349 break;
350 } 355 }
351 356
352 if (start > 0) 357 if (start > 0)
353 printOffset = height - usedHeight; 358 printOffset = height - usedHeight;
354 359
355 m_outputScroll = m_outputLines.size() - 1 - end; 360 this->m_outputScroll = this->m_outputLines.size() - 1 - end;
356 361
357 if (start < 0 or start == end or printOffset >= height) 362 if (start < 0 or start == end or printOffset >= height)
358 return; 363 return;
359 364
360 assert(start <= end and start - end <= height); 365 assert(start <= end and start - end <= height);
361 366
362 // Clear the display 367 // Clear the display
363 for (int i : range(height)) 368 for (int i = 0; i < height; i += 1)
364 mvhline(y + i, 0, ' ', width); 369 mvhline(y + i, 0, ' ', width);
365 370
366 // Print the lines 371 // Print the lines
367 y += printOffset; 372 y += printOffset;
368 373
369 for (int i : range(start, end)) 374 for (int i = start; i < end; i += 1)
370 y = renderColorline(y, 0, width, m_outputLines[i], true); 375 y = renderColorline(y, 0, width, this->m_outputLines[i], true);
371 376
372 m_needOutputRender = false; 377 this->m_needOutputRender = false;
373 m_needRefresh = true; 378 this->m_needRefresh = true;
374 } 379 }
375 380
376 // ------------------------------------------------------------------------------------------------- 381 // -------------------------------------------------------------------------------------------------
377 // 382 //
378 void Interface::renderNicklist() 383 void Interface::renderNicklist()
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 //
541 // ------------------------------------------------------------------------------------------------- 546 // -------------------------------------------------------------------------------------------------
542 // 547 //
543 void Interface::positionCursor() 548 void Interface::positionCursor()
544 { 549 {
545 // This is only relevant if the input string is being drawn 550 // This is only relevant if the input string is being drawn
546 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) 551 if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
547 return; 552 return;
548 553
549 int y = LINES - 2; 554 int y = LINES - 2;
550 555
551 if (m_cursorCharacter.ch != '\0') 556 if (this->m_cursorCharacter.ch != '\0')
552 mvprintw(y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); 557 mvprintw(y, this->m_cursorCharacter.x, "%c", this->m_cursorCharacter.ch);
553 else 558 else
554 mvprintw(y, getPromptString().length(), " "); 559 mvprintw(y, getPromptString().length(), " ");
555 } 560 }
556 561
557 // ------------------------------------------------------------------------------------------------- 562 // -------------------------------------------------------------------------------------------------
558 // 563 //
559 int Interface::findPreviousWord() 564 int Interface::findPreviousWord()
560 { 565 {
561 const String& input = getCurrentInput(); 566 const std::string& input = getCurrentInput();
562 int pos = m_cursorPosition; 567 int pos = this->m_cursorPosition;
563 568
564 // Move past whitespace 569 // Move past whitespace
565 while (pos > 0 and isspace(input[pos - 1])) 570 while (pos > 0 and isspace(input[pos - 1]))
566 pos--; 571 pos--;
567 572
574 579
575 // ------------------------------------------------------------------------------------------------- 580 // -------------------------------------------------------------------------------------------------
576 // 581 //
577 int Interface::findNextWord() 582 int Interface::findNextWord()
578 { 583 {
579 const String& input = getCurrentInput(); 584 const std::string& input = getCurrentInput();
580 int pos = m_cursorPosition; 585 int pos = this->m_cursorPosition;
581 586
582 // Move past current whitespace 587 // Move past current whitespace
583 while (pos < input.length() and isspace(input[pos])) 588 while (pos < static_cast<signed>(input.length()) and isspace(input[pos]))
584 pos++; 589 pos++;
585 590
586 // Move past the word 591 // Move past the word
587 while (input[pos] != '\0' and not isspace(input[pos])) 592 while (input[pos] != '\0' and not isspace(input[pos]))
588 pos++; 593 pos++;
595 void Interface::yank(int a, int b) 600 void Interface::yank(int a, int b)
596 { 601 {
597 if (a >= b) 602 if (a >= b)
598 return; 603 return;
599 604
600 if (m_cursorPosition > a and m_cursorPosition <= b) 605 if (this->m_cursorPosition > a and this->m_cursorPosition <= b)
601 m_cursorPosition = a; 606 this->m_cursorPosition = a;
602 607
603 String& input = getEditableInput(); 608 std::string& input = getEditableInput();
604 m_pasteBuffer = input.mid(a, b); 609 this->m_pasteBuffer = mid(input, a, b);
605 input.remove(a, b - a); 610 input = remove_range(input, a, b - a);
606 m_needInputRender = true; 611 this->m_needInputRender = true;
607 } 612 }
608 613
609 // ------------------------------------------------------------------------------------------------- 614 bool Interface::tryResolveAddress(const std::string &address_string, net::ip_address* target)
610 // 615 {
611 void Interface::handleInput() 616 std::stringstream errors;
617 const std::optional<net::ip_address> address_opt = net::ip_resolve(address_string, errors);
618 if (address_opt.has_value())
619 {
620 *target = address_opt.value();
621 if (target->port == 0)
622 {
623 target->port = 10666;
624 }
625 return true;
626 }
627 else
628 {
629 this->printError("%s\n", errors.str().data());
630 return false;
631 }
632 }
633
634 // -------------------------------------------------------------------------------------------------
635 //
636 void Interface::handleInput(bool* shouldquit)
612 { 637 {
613 int ch = ::getch(); 638 int ch = ::getch();
614 639
615 if (ch < 0) 640 if (ch < 0)
616 return; 641 return;
620 ::clear(); 645 ::clear();
621 renderFull(); 646 renderFull();
622 return; 647 return;
623 } 648 }
624 649
625 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) 650 if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
626 { 651 {
627 if (ch == 'y' or ch == 'Y') 652 if (ch == 'y' or ch == 'Y')
628 { 653 {
629 m_session.disconnect(); 654 this->m_session.disconnect();
630 m_disconnectCallback(true); 655 this->m_disconnectCallback(true);
656 this->updateStatusBar();
631 } 657 }
632 else if (ch == 'n' or ch == 'N') 658 else if (ch == 'n' or ch == 'N')
633 setInputState(INPUTSTATE_NORMAL); 659 setInputState(INPUTSTATE_NORMAL);
634 660
635 return; 661 return;
636 } 662 }
637 663
638 if (ch >= 0x20 and ch <= 0x7E) 664 if (ch >= 0x20 and ch <= 0x7E)
639 { 665 {
640 getEditableInput().insert(m_cursorPosition++, char(ch)); 666 std::string& input = getEditableInput();
641 m_needInputRender = true; 667 input.insert(input.begin() + this->m_cursorPosition, char(ch));
668 this->m_cursorPosition += 1;
669 this->m_needInputRender = true;
642 } 670 }
643 else switch (ch) 671 else switch (ch)
644 { 672 {
645 case 'Q' - 'A' + 1: // ^Q 673 case 'Q' - 'A' + 1: // ^Q
646 switch (m_inputState) 674 switch (this->m_inputState)
647 { 675 {
648 case INPUTSTATE_CONFIRM_DISCONNECTION: 676 case INPUTSTATE_CONFIRM_DISCONNECTION:
649 break; 677 break;
650 678
651 case INPUTSTATE_NORMAL: 679 case INPUTSTATE_NORMAL:
655 { 683 {
656 setInputState(INPUTSTATE_NORMAL); 684 setInputState(INPUTSTATE_NORMAL);
657 } 685 }
658 else 686 else
659 { 687 {
660 endwin(); 688 *shouldquit = true;
661 throw Exitception();
662 } 689 }
663 }); 690 });
664 break; 691 break;
665 692
666 case INPUTSTATE_PASSWORD: 693 case INPUTSTATE_PASSWORD:
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
843 switch (ch) 863 switch (ch)
844 { 864 {
845 case 'b': 865 case 'b':
846 case 'B': 866 case 'B':
847 // readline alt-b - move one word to the left 867 // readline alt-b - move one word to the left
848 m_cursorPosition = findPreviousWord(); 868 this->m_cursorPosition = findPreviousWord();
849 m_needInputRender = true; 869 this->m_needInputRender = true;
850 break; 870 break;
851 871
852 case 'f': 872 case 'f':
853 case 'F': 873 case 'F':
854 // readline alt-f - move one word to the right 874 // readline alt-f - move one word to the right
855 m_cursorPosition = findNextWord(); 875 this->m_cursorPosition = findNextWord();
856 m_needInputRender = true; 876 this->m_needInputRender = true;
857 break; 877 break;
858 878
859 case 'd': 879 case 'd':
860 case 'D': 880 case 'D':
861 // readline alt-d - delete from here till next word boundary 881 // readline alt-d - delete from here till next word boundary
862 yank(m_cursorPosition, findNextWord()); 882 yank(this->m_cursorPosition, findNextWord());
863 break; 883 break;
864 884
865 case KEY_BACKSPACE: // alt+backspace, remove previous word 885 case KEY_BACKSPACE: // alt+backspace, remove previous word
866 case '\b': 886 case '\b':
867 yank(findPreviousWord(), m_cursorPosition); 887 yank(findPreviousWord(), this->m_cursorPosition);
868 break; 888 break;
869 } 889 }
870 } 890 }
871 else 891 else
872 { 892 {
873 // No alt-key, handle pure escape 893 // No alt-key, handle pure escape
874 if (m_inputState == INPUTSTATE_PASSWORD) 894 if (this->m_inputState == INPUTSTATE_PASSWORD)
875 setInputState(INPUTSTATE_ADDRESS); 895 setInputState(INPUTSTATE_ADDRESS);
876 else if (m_inputState == INPUTSTATE_ADDRESS) 896 else if (this->m_inputState == INPUTSTATE_ADDRESS)
877 setInputState(INPUTSTATE_NORMAL); 897 setInputState(INPUTSTATE_NORMAL);
878 } 898 }
879 break; 899 break;
880 } 900 }
881 901
884 904
885 // ------------------------------------------------------------------------------------------------- 905 // -------------------------------------------------------------------------------------------------
886 // 906 //
887 void Interface::render() 907 void Interface::render()
888 { 908 {
889 if (m_needStatusBarRender) renderStatusBar(); 909 if (this->m_needStatusBarRender) renderStatusBar();
890 if (m_needInputRender) renderInput(); 910 if (this->m_needInputRender) renderInput();
891 if (m_needOutputRender) renderOutput(); 911 if (this->m_needOutputRender) renderOutput();
892 if (m_needNicklistRender) renderNicklist(); 912 if (this->m_needNicklistRender) renderNicklist();
893 913
894 if (m_needRefresh) 914 if (this->m_needRefresh)
895 { 915 {
896 positionCursor(); 916 positionCursor();
897 refresh(); 917 refresh();
898 m_needRefresh = false; 918 this->m_needRefresh = false;
899 } 919 }
900 } 920 }
901 921
902 // ------------------------------------------------------------------------------------------------- 922 // -------------------------------------------------------------------------------------------------
903 // 923 //
904 void Interface::vprint(const char* fmtstr, va_list args) 924 void Interface::vprint(const char* fmtstr, va_list args)
905 { 925 {
906 String message; 926 std::string message;
907 message.vsprintf(fmtstr, args); 927 message = vsprintf(fmtstr, args);
908 printToConsole(message); 928 printToConsole(message);
909 } 929 }
910 930
911 // ------------------------------------------------------------------------------------------------- 931 // -------------------------------------------------------------------------------------------------
912 // 932 //
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

mercurial