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