1 /* 2 * Copyright 2012-2014, Adrien Destugues, pulkomandy@gmail.com 3 * Distributed under the terms of the MIT licence. 4 */ 5 6 7 #include "TermView.h" 8 9 #include <stdio.h> 10 11 #include <Entry.h> 12 #include <File.h> 13 #include <Font.h> 14 #include <Layout.h> 15 #include <ScrollBar.h> 16 17 #include "SerialApp.h" 18 19 20 struct ScrollBufferItem { 21 int cols; 22 VTermScreenCell cells[]; 23 }; 24 25 26 TermView::TermView() 27 : 28 BView("TermView", B_WILL_DRAW | B_FRAME_EVENTS) 29 { 30 _Init(); 31 } 32 33 34 TermView::TermView(BRect r) 35 : 36 BView(r, "TermView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS) 37 { 38 _Init(); 39 } 40 41 42 TermView::~TermView() 43 { 44 vterm_free(fTerm); 45 } 46 47 48 void 49 TermView::AttachedToWindow() 50 { 51 BView::AttachedToWindow(); 52 MakeFocus(); 53 } 54 55 56 void 57 TermView::Draw(BRect updateRect) 58 { 59 VTermRect updatedChars = _PixelsToGlyphs(updateRect); 60 61 VTermPos pos; 62 font_height height; 63 GetFontHeight(&height); 64 65 VTermPos cursorPos; 66 vterm_state_get_cursorpos(vterm_obtain_state(fTerm), &cursorPos); 67 68 for (pos.row = updatedChars.start_row; pos.row <= updatedChars.end_row; 69 pos.row++) { 70 int x = updatedChars.start_col * fFontWidth + kBorderSpacing; 71 int y = pos.row * fFontHeight + (int)ceil(height.ascent) 72 + kBorderSpacing; 73 MovePenTo(x, y); 74 75 for (pos.col = updatedChars.start_col; 76 pos.col <= updatedChars.end_col;) { 77 VTermScreenCell cell; 78 79 _GetCell(pos, cell); 80 81 rgb_color foreground, background; 82 foreground.red = cell.fg.red; 83 foreground.green = cell.fg.green; 84 foreground.blue = cell.fg.blue; 85 foreground.alpha = 255; 86 background.red = cell.bg.red; 87 background.green = cell.bg.green; 88 background.blue = cell.bg.blue; 89 background.alpha = 255; 90 91 // Draw the cursor by swapping foreground and background colors 92 if ((pos.col == cursorPos.col && pos.row == cursorPos.row)) { 93 SetLowColor(foreground); 94 SetViewColor(foreground); 95 SetHighColor(background); 96 } else { 97 SetLowColor(background); 98 SetViewColor(background); 99 SetHighColor(foreground); 100 } 101 102 FillRect(BRect(x, y - ceil(height.ascent) + 1, 103 x + cell.width * fFontWidth - 1, 104 y + ceil(height.descent) + ceil(height.leading)), 105 B_SOLID_LOW); 106 107 BFont font = be_fixed_font; 108 if (cell.attrs.bold) 109 font.SetFace(B_BOLD_FACE); 110 if (cell.attrs.underline) 111 font.SetFace(B_UNDERSCORE_FACE); 112 if (cell.attrs.italic) 113 font.SetFace(B_ITALIC_FACE); 114 if (cell.attrs.blink) // FIXME make it actually blink 115 font.SetFace(B_OUTLINED_FACE); 116 if (cell.attrs.reverse) 117 font.SetFace(B_NEGATIVE_FACE); 118 if (cell.attrs.strike) 119 font.SetFace(B_STRIKEOUT_FACE); 120 121 // TODO handle "font" (alternate fonts), dwl and dhl (double size) 122 123 SetFont(&font); 124 125 if (cell.chars[0] == 0) { 126 DrawString(" "); 127 pos.col ++; 128 x += fFontWidth; 129 } else { 130 char buffer[VTERM_MAX_CHARS_PER_CELL]; 131 wcstombs(buffer, (wchar_t*)cell.chars, 132 VTERM_MAX_CHARS_PER_CELL); 133 134 DrawString(buffer); 135 x += (int)ceil(StringWidth(buffer)); 136 pos.col += cell.width; 137 } 138 } 139 } 140 } 141 142 143 void 144 TermView::FrameResized(float width, float height) 145 { 146 VTermRect newSize = _PixelsToGlyphs(BRect(0, 0, width - 2 * kBorderSpacing, 147 height - 2 * kBorderSpacing)); 148 vterm_set_size(fTerm, newSize.end_row, newSize.end_col); 149 } 150 151 152 void 153 TermView::GetPreferredSize(float* width, float* height) 154 { 155 if (width != NULL) 156 *width = kDefaultWidth * fFontWidth + 2 * kBorderSpacing - 1; 157 if (height != NULL) 158 *height = kDefaultHeight * fFontHeight + 2 * kBorderSpacing - 1; 159 } 160 161 162 void 163 TermView::KeyDown(const char* bytes, int32 numBytes) 164 { 165 // Translate some keys to more usual VT100 escape codes 166 switch (bytes[0]) { 167 case B_UP_ARROW: 168 numBytes = 3; 169 bytes = "\x1B[A"; 170 break; 171 case B_DOWN_ARROW: 172 numBytes = 3; 173 bytes = "\x1B[B"; 174 break; 175 case B_RIGHT_ARROW: 176 numBytes = 3; 177 bytes = "\x1B[C"; 178 break; 179 case B_LEFT_ARROW: 180 numBytes = 3; 181 bytes = "\x1B[D"; 182 break; 183 case B_BACKSPACE: 184 numBytes = 1; 185 bytes = "\x7F"; 186 break; 187 } 188 189 // Send the bytes to the serial port 190 BMessage* keyEvent = new BMessage(kMsgDataWrite); 191 keyEvent->AddData("data", B_RAW_TYPE, bytes, numBytes); 192 be_app_messenger.SendMessage(keyEvent); 193 } 194 195 196 void 197 TermView::MessageReceived(BMessage* message) 198 { 199 switch(message->what) 200 { 201 case 'DATA': 202 { 203 entry_ref ref; 204 if (message->FindRef("refs", &ref) == B_OK) 205 { 206 // The user just dropped a file on us 207 // TODO send it by XMODEM or so 208 } 209 break; 210 } 211 default: 212 BView::MessageReceived(message); 213 } 214 } 215 216 217 void 218 TermView::PushBytes(const char* bytes, size_t length) 219 { 220 vterm_push_bytes(fTerm, bytes, length); 221 } 222 223 224 // #pragma mark - 225 226 227 void 228 TermView::_Init() 229 { 230 SetFont(be_fixed_font); 231 232 font_height height; 233 GetFontHeight(&height); 234 fFontHeight = (int)(ceilf(height.ascent) + ceilf(height.descent) 235 + ceilf(height.leading)); 236 fFontWidth = (int)be_fixed_font->StringWidth("X"); 237 fTerm = vterm_new(kDefaultHeight, kDefaultWidth); 238 239 fTermScreen = vterm_obtain_screen(fTerm); 240 vterm_screen_set_callbacks(fTermScreen, &sScreenCallbacks, this); 241 vterm_screen_reset(fTermScreen, 1); 242 243 vterm_parser_set_utf8(fTerm, 1); 244 245 VTermScreenCell cell; 246 VTermPos firstPos; 247 firstPos.row = 0; 248 firstPos.col = 0; 249 _GetCell(firstPos, cell); 250 251 rgb_color background; 252 background.red = cell.bg.red; 253 background.green = cell.bg.green; 254 background.blue = cell.bg.blue; 255 background.alpha = 255; 256 257 SetViewColor(background); 258 } 259 260 261 VTermRect 262 TermView::_PixelsToGlyphs(BRect pixels) const 263 { 264 pixels.OffsetBy(-kBorderSpacing, -kBorderSpacing); 265 266 VTermRect rect; 267 rect.start_col = (int)floor(pixels.left / fFontWidth); 268 rect.end_col = (int)ceil(pixels.right / fFontWidth); 269 rect.start_row = (int)floor(pixels.top / fFontHeight); 270 rect.end_row = (int)ceil(pixels.bottom / fFontHeight); 271 #if 0 272 printf( 273 "TOP %d ch < %f px\n" 274 "BTM %d ch < %f px\n" 275 "LFT %d ch < %f px\n" 276 "RGH %d ch < %f px\n", 277 rect.start_row, pixels.top, 278 rect.end_row, pixels.bottom, 279 rect.start_col, pixels.left, 280 rect.end_col, pixels.right 281 ); 282 #endif 283 return rect; 284 } 285 286 287 BRect TermView::_GlyphsToPixels(const VTermRect& glyphs) const 288 { 289 BRect rect; 290 rect.top = glyphs.start_row * fFontHeight; 291 rect.bottom = glyphs.end_row * fFontHeight; 292 rect.left = glyphs.start_col * fFontWidth; 293 rect.right = glyphs.end_col * fFontWidth; 294 295 rect.OffsetBy(kBorderSpacing, kBorderSpacing); 296 #if 0 297 printf( 298 "TOP %d ch > %f px (%f)\n" 299 "BTM %d ch > %f px\n" 300 "LFT %d ch > %f px (%f)\n" 301 "RGH %d ch > %f px\n", 302 glyphs.start_row, rect.top, fFontHeight, 303 glyphs.end_row, rect.bottom, 304 glyphs.start_col, rect.left, fFontWidth, 305 glyphs.end_col, rect.right 306 ); 307 #endif 308 return rect; 309 } 310 311 312 BRect 313 TermView::_GlyphsToPixels(const int width, const int height) const 314 { 315 VTermRect rect; 316 rect.start_row = 0; 317 rect.start_col = 0; 318 rect.end_row = height; 319 rect.end_col = width; 320 return _GlyphsToPixels(rect); 321 } 322 323 324 void 325 TermView::_GetCell(VTermPos pos, VTermScreenCell& cell) 326 { 327 // First handle cells from the normal screen 328 if (vterm_screen_get_cell(fTermScreen, pos, &cell) != 0) 329 return; 330 331 // Try the scroll-back buffer 332 if (pos.row < 0 && pos.col >= 0) { 333 int offset = - pos.row - 1; 334 ScrollBufferItem* line 335 = (ScrollBufferItem*)fScrollBuffer.ItemAt(offset); 336 if (line != NULL && pos.col < line->cols) { 337 cell = line->cells[pos.col]; 338 return; 339 } 340 } 341 342 // All cells outside the used terminal area are drawn with the same 343 // background color as the top-left one. 344 // TODO should they use the attributes of the closest neighbor instead? 345 VTermPos firstPos; 346 firstPos.row = 0; 347 firstPos.col = 0; 348 vterm_screen_get_cell(fTermScreen, firstPos, &cell); 349 cell.chars[0] = 0; 350 cell.width = 1; 351 } 352 353 354 void 355 TermView::_Damage(VTermRect rect) 356 { 357 Invalidate(_GlyphsToPixels(rect)); 358 } 359 360 361 void 362 TermView::_MoveCursor(VTermPos pos, VTermPos oldPos, int visible) 363 { 364 VTermRect r; 365 r.start_row = pos.row; 366 r.start_col = pos.col; 367 r.end_col = pos.col + 1; 368 r.end_row = pos.row + 1; 369 Invalidate(_GlyphsToPixels(r)); 370 371 r.start_row = oldPos.row; 372 r.start_col = oldPos.col; 373 r.end_col = oldPos.col + 1; 374 r.end_row = oldPos.row + 1; 375 Invalidate(_GlyphsToPixels(r)); 376 } 377 378 379 void 380 TermView::_PushLine(int cols, const VTermScreenCell* cells) 381 { 382 ScrollBufferItem* item = (ScrollBufferItem*)malloc(sizeof(int) 383 + cols * sizeof(VTermScreenCell)); 384 item->cols = cols; 385 memcpy(item->cells, cells, cols * sizeof(VTermScreenCell)); 386 387 fScrollBuffer.AddItem(item, 0); 388 389 free(fScrollBuffer.RemoveItem(kScrollBackSize)); 390 391 int availableRows, availableCols; 392 vterm_get_size(fTerm, &availableRows, &availableCols); 393 394 VTermRect dirty; 395 dirty.start_col = 0; 396 dirty.end_col = availableCols; 397 dirty.end_row = 0; 398 dirty.start_row = -fScrollBuffer.CountItems(); 399 // FIXME we should rather use CopyRect if possible, and only invalidate the 400 // newly exposed area here. 401 Invalidate(_GlyphsToPixels(dirty)); 402 403 BScrollBar* scrollBar = ScrollBar(B_VERTICAL); 404 if (scrollBar != NULL) { 405 float range = (fScrollBuffer.CountItems() + availableRows) 406 * fFontHeight; 407 scrollBar->SetRange(availableRows * fFontHeight - range, 0.0f); 408 // TODO we need to adjust this in FrameResized, as availableRows can 409 // change 410 scrollBar->SetProportion(availableRows * fFontHeight / range); 411 scrollBar->SetSteps(fFontHeight, fFontHeight * 3); 412 } 413 } 414 415 416 /* static */ int 417 TermView::_Damage(VTermRect rect, void* user) 418 { 419 TermView* view = (TermView*)user; 420 view->_Damage(rect); 421 422 return 0; 423 } 424 425 426 /* static */ int 427 TermView::_MoveCursor(VTermPos pos, VTermPos oldPos, int visible, void* user) 428 { 429 TermView* view = (TermView*)user; 430 view->_MoveCursor(pos, oldPos, visible); 431 432 return 0; 433 } 434 435 436 /* static */ int 437 TermView::_PushLine(int cols, const VTermScreenCell* cells, void* user) 438 { 439 TermView* view = (TermView*)user; 440 view->_PushLine(cols, cells); 441 442 return 0; 443 } 444 445 446 const 447 VTermScreenCallbacks TermView::sScreenCallbacks = { 448 &TermView::_Damage, 449 /*.moverect =*/ NULL, 450 &TermView::_MoveCursor, 451 /*.settermprop =*/ NULL, 452 /*.setmousefunc =*/ NULL, 453 /*.bell =*/ NULL, 454 /*.resize =*/ NULL, 455 &TermView::_PushLine, 456 /*.sb_popline =*/ NULL, 457 }; 458