1 // InfoView.cpp 2 3 #include "InfoView.h" 4 #include "cortex_ui.h" 5 6 #include "array_delete.h" 7 8 // Interface Kit 9 #include <Bitmap.h> 10 #include <Region.h> 11 #include <ScrollBar.h> 12 #include <StringView.h> 13 #include <TextView.h> 14 #include <Window.h> 15 // Storage Kit 16 #include <Mime.h> 17 // Support Kit 18 #include <List.h> 19 20 __USE_CORTEX_NAMESPACE 21 22 #include <Debug.h> 23 #define D_ALLOC(X) //PRINT (x) // ctor/dtor 24 #define D_HOOK(X) //PRINT (x) // BView impl. 25 #define D_ACCESS(X) //PRINT (x) // Accessors 26 #define D_METHOD(x) //PRINT (x) 27 28 // -------------------------------------------------------- // 29 // *** internal class: _InfoTextField 30 // 31 // * PURPOSE: 32 // store the label & text for each field, and provide methods 33 // for linewrapping and drawing 34 // 35 // -------------------------------------------------------- // 36 37 class _InfoTextField 38 { 39 40 public: // *** ctor/dtor 41 42 _InfoTextField( 43 BString label, 44 BString text, 45 InfoView *parent); 46 47 ~_InfoTextField(); 48 49 public: // *** operations 50 51 void drawField( 52 BPoint position); 53 54 void updateLineWrapping( 55 bool *wrappingChanged = 0, 56 bool *heightChanged = 0); 57 58 public: // *** accessors 59 60 float getHeight() const; 61 62 float getWidth() const; 63 64 bool isWrapped() const; 65 66 private: // *** static internal methods 67 68 static bool canEndLine( 69 const char c); 70 71 static bool mustEndLine( 72 const char c); 73 74 private: // *** data members 75 76 BString m_label; 77 78 BString m_text; 79 80 BList *m_textLines; 81 82 InfoView *m_parent; 83 }; 84 85 // -------------------------------------------------------- // 86 // *** static member init 87 // -------------------------------------------------------- // 88 89 const BRect InfoView::M_DEFAULT_FRAME = BRect(0.0, 0.0, 250.0, 100.0); 90 const float InfoView::M_H_MARGIN = 5.0; 91 const float InfoView::M_V_MARGIN = 5.0; 92 93 // -------------------------------------------------------- // 94 // *** ctor/dtor (public) 95 // -------------------------------------------------------- // 96 97 InfoView::InfoView( 98 BString title, 99 BString subTitle, 100 BBitmap *icon) 101 : BView(M_DEFAULT_FRAME, "InfoView", B_FOLLOW_ALL_SIDES, 102 B_WILL_DRAW | B_FRAME_EVENTS), 103 m_title(title), 104 m_subTitle(subTitle), 105 m_icon(0), 106 m_fields(0) { 107 D_ALLOC(("InfoView::InfoView()\n")); 108 109 if (icon) { 110 m_icon = new BBitmap(icon); 111 } 112 m_fields = new BList(); 113 SetViewColor(B_TRANSPARENT_COLOR); 114 } 115 116 InfoView::~InfoView() { 117 D_ALLOC(("InfoView::~InfoView()\n")); 118 119 // delete all the fields 120 if (m_fields) { 121 while (m_fields->CountItems() > 0) { 122 _InfoTextField *field = static_cast<_InfoTextField *> 123 (m_fields->RemoveItem((int32)0)); 124 if (field) { 125 delete field; 126 } 127 } 128 delete m_fields; 129 m_fields = 0; 130 } 131 132 // delete the icon bitmap 133 if (m_icon) { 134 delete m_icon; 135 m_icon = 0; 136 } 137 } 138 139 // -------------------------------------------------------- // 140 // *** BView implementation 141 // -------------------------------------------------------- // 142 143 void InfoView::AttachedToWindow() { 144 D_HOOK(("InfoView::AttachedToWindow()\n")); 145 146 // adjust the windows title 147 BString title = m_title; 148 title << " info"; 149 Window()->SetTitle(title.String()); 150 151 // calculate the area occupied by title, subtitle and icon 152 font_height fh; 153 be_bold_font->GetHeight(&fh); 154 float titleHeight = fh.leading + fh.descent; 155 titleHeight += M_V_MARGIN * 2.0 + B_LARGE_ICON / 2.0; 156 be_plain_font->GetHeight(&fh); 157 titleHeight += fh.leading + fh.ascent + fh.descent; 158 BFont font(be_bold_font); 159 float titleWidth = font.StringWidth(title.String()); 160 titleWidth += M_H_MARGIN + B_LARGE_ICON + B_LARGE_ICON / 2.0; 161 162 float width, height; 163 GetPreferredSize(&width, &height); 164 Window()->ResizeTo(width + B_V_SCROLL_BAR_WIDTH, height); 165 ResizeBy(- B_V_SCROLL_BAR_WIDTH, 0.0); 166 167 // add scroll bar 168 BRect scrollRect = Window()->Bounds(); 169 scrollRect.left = scrollRect.right - B_V_SCROLL_BAR_WIDTH + 1.0; 170 scrollRect.top -= 1.0; 171 scrollRect.right += 1.0; 172 scrollRect.bottom -= B_H_SCROLL_BAR_HEIGHT - 1.0; 173 Window()->AddChild(new BScrollBar(scrollRect, "ScrollBar", this, 174 0.0, 0.0, B_VERTICAL)); 175 ScrollBar(B_VERTICAL)->SetRange(0.0, 0.0); 176 be_plain_font->GetHeight(&fh); 177 float step = fh.ascent + fh.descent + fh.leading + M_V_MARGIN; 178 ScrollBar(B_VERTICAL)->SetSteps(step, step * 5); 179 180 // set window size limits 181 float minWidth, maxWidth, minHeight, maxHeight; 182 Window()->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight); 183 Window()->SetSizeLimits(titleWidth + B_V_SCROLL_BAR_WIDTH, maxWidth, 184 titleHeight + B_H_SCROLL_BAR_HEIGHT, maxHeight); 185 186 // cache the bounds rect for proper redraw later on... 187 m_oldFrame = Bounds(); 188 } 189 190 void InfoView::Draw( 191 BRect updateRect) { 192 D_HOOK(("InfoView::Draw()\n")); 193 194 // Draw side bar 195 SetDrawingMode(B_OP_COPY); 196 BRect r = Bounds(); 197 r.right = B_LARGE_ICON - 1.0; 198 SetLowColor(M_LIGHT_BLUE_COLOR); 199 FillRect(r, B_SOLID_LOW); 200 SetHighColor(M_DARK_BLUE_COLOR); 201 r.right += 1.0; 202 StrokeLine(r.RightTop(), r.RightBottom(), B_SOLID_HIGH); 203 204 // Draw background 205 BRegion region; 206 region.Include(updateRect); 207 region.Exclude(r); 208 SetLowColor(M_GRAY_COLOR); 209 FillRegion(®ion, B_SOLID_LOW); 210 211 // Draw title 212 SetDrawingMode(B_OP_OVER); 213 font_height fh; 214 be_bold_font->GetHeight(&fh); 215 SetFont(be_bold_font); 216 BPoint p(M_H_MARGIN + B_LARGE_ICON + B_LARGE_ICON / 2.0, 217 M_V_MARGIN * 2.0 + fh.ascent); 218 SetHighColor(M_BLACK_COLOR); 219 DrawString(m_title.String(), p); 220 221 // Draw sub-title 222 p.y += fh.descent; 223 be_plain_font->GetHeight(&fh); 224 SetFont(be_plain_font); 225 p.y += fh.ascent + fh.leading; 226 SetHighColor(M_DARK_GRAY_COLOR); 227 DrawString(m_subTitle.String(), p); 228 229 // Draw icon 230 p.y = 2 * M_V_MARGIN; 231 if (m_icon) { 232 p.x = B_LARGE_ICON / 2.0; 233 DrawBitmapAsync(m_icon, p); 234 } 235 236 // Draw fields 237 be_plain_font->GetHeight(&fh); 238 p.x = B_LARGE_ICON; 239 p.y += B_LARGE_ICON + 2 * M_V_MARGIN + fh.ascent + 2 * fh.leading; 240 for (int32 i = 0; i < m_fields->CountItems(); i++) { 241 _InfoTextField *field = static_cast<_InfoTextField *>(m_fields->ItemAt(i)); 242 field->drawField(p); 243 p.y += field->getHeight() + M_V_MARGIN; 244 } 245 } 246 247 void InfoView::FrameResized( 248 float width, 249 float height) { 250 D_HOOK(("InfoView::FrameResized()\n")); 251 252 BRect newFrame = BRect(0.0, 0.0, width, height); 253 254 // update the each lines' line-wrapping and redraw as necessary 255 font_height fh; 256 BPoint p; 257 be_plain_font->GetHeight(&fh); 258 p.x = B_LARGE_ICON; 259 p.y += B_LARGE_ICON + M_V_MARGIN * 2.0 + fh.ascent + fh.leading * 2.0; 260 bool heightChanged = false; 261 for (int32 i = 0; i < m_fields->CountItems(); i++) { 262 bool wrappingChanged = false; 263 _InfoTextField *field = static_cast<_InfoTextField *>(m_fields->ItemAt(i)); 264 field->updateLineWrapping(&wrappingChanged, 265 heightChanged ? 0 : &heightChanged); 266 float fieldHeight = field->getHeight() + M_V_MARGIN; 267 if (heightChanged) { 268 Invalidate(BRect(p.x, p.y, width, p.y + fieldHeight)); 269 } 270 else if (wrappingChanged) { 271 Invalidate(BRect(p.x + m_sideBarWidth, p.y, width, p.y + fieldHeight)); 272 } 273 p.y += fieldHeight; 274 } 275 276 // clean up the rest of the view 277 BRect updateRect; 278 updateRect.left = B_LARGE_ICON; 279 updateRect.top = p.y < (m_oldFrame.bottom - M_V_MARGIN - 15.0) ? 280 p.y - 15.0 : m_oldFrame.bottom - M_V_MARGIN - 15.0; 281 updateRect.right = width - M_H_MARGIN; 282 updateRect.bottom = m_oldFrame.bottom < newFrame.bottom ? 283 newFrame.bottom : m_oldFrame.bottom; 284 Invalidate(updateRect); 285 286 if (p.y > height) { 287 ScrollBar(B_VERTICAL)->SetRange(0.0, ceil(p.y - height)); 288 } 289 else { 290 ScrollBar(B_VERTICAL)->SetRange(0.0, 0.0); 291 } 292 293 // cache the new frame rect for the next time 294 m_oldFrame = newFrame; 295 } 296 297 void 298 InfoView::GetPreferredSize( 299 float *width, 300 float *height) { 301 D_HOOK(("InfoView::GetPreferredSize()\n")); 302 303 *width = 0; 304 *height = 0; 305 306 // calculate the height needed to display everything, avoiding line wrapping 307 font_height fh; 308 be_plain_font->GetHeight(&fh); 309 for (int32 i = 0; i < m_fields->CountItems(); i++) { 310 _InfoTextField *field = static_cast<_InfoTextField *>(m_fields->ItemAt(i)); 311 *height += fh.ascent + fh.descent + fh.leading + M_V_MARGIN; 312 float tfw = field->getWidth(); 313 if (tfw > *width) { 314 *width = tfw; 315 } 316 } 317 318 *width += B_LARGE_ICON + 2 * M_H_MARGIN; 319 *height += B_LARGE_ICON + 2 * M_V_MARGIN + fh.ascent + 2 * fh.leading; 320 *height += B_H_SCROLL_BAR_HEIGHT; 321 } 322 323 // -------------------------------------------------------- // 324 // *** operations (protected) 325 // -------------------------------------------------------- // 326 327 void InfoView::addField( 328 BString label, 329 BString text) { 330 D_METHOD(("InfoView::addField()\n")); 331 332 m_fields->AddItem(reinterpret_cast<void *> 333 (new _InfoTextField(label, text, this))); 334 } 335 336 // -------------------------------------------------------- // 337 // *** internal class: _InfoTextField 338 // 339 // *** ctor/dtor 340 // -------------------------------------------------------- // 341 342 _InfoTextField::_InfoTextField( 343 BString label, 344 BString text, 345 InfoView *parent) 346 : m_label(label), 347 m_text(text), 348 m_textLines(0), 349 m_parent(parent) { 350 D_ALLOC(("_InfoTextField::_InfoTextField()\n")); 351 352 if (m_label != "") { 353 m_label << ": "; 354 } 355 m_textLines = new BList(); 356 } 357 358 _InfoTextField::~_InfoTextField() { 359 D_ALLOC(("_InfoTextField::~_InfoTextField()\n")); 360 361 // delete every line 362 if (m_textLines) { 363 while (m_textLines->CountItems() > 0) { 364 BString *line = static_cast<BString *>(m_textLines->RemoveItem((int32)0)); 365 if (line) { 366 delete line; 367 } 368 } 369 delete m_textLines; 370 m_textLines = 0; 371 } 372 } 373 374 // -------------------------------------------------------- // 375 // *** internal class: _InfoTextField 376 // 377 // *** operations (public) 378 // -------------------------------------------------------- // 379 380 void _InfoTextField::drawField( 381 BPoint position) { 382 D_METHOD(("_InfoTextField::drawField()\n")); 383 384 float sideBarWidth = m_parent->getSideBarWidth(); 385 386 // Draw label 387 BPoint p = position; 388 p.x += sideBarWidth - be_plain_font->StringWidth(m_label.String()); 389 m_parent->SetHighColor(M_DARK_GRAY_COLOR); 390 m_parent->SetDrawingMode(B_OP_OVER); 391 m_parent->SetFont(be_plain_font); 392 m_parent->DrawString(m_label.String(), p); 393 394 // Draw text 395 font_height fh; 396 be_plain_font->GetHeight(&fh); 397 p.x = position.x + sideBarWidth;// + InfoView::M_H_MARGIN; 398 m_parent->SetHighColor(M_BLACK_COLOR); 399 for (int32 i = 0; i < m_textLines->CountItems(); i++) { 400 BString *line = static_cast<BString *>(m_textLines->ItemAt(i)); 401 m_parent->DrawString(line->String(), p); 402 p.y += fh.ascent + fh.descent + fh.leading; 403 } 404 } 405 406 void _InfoTextField::updateLineWrapping( 407 bool *wrappingChanged, 408 bool *heightChanged) 409 { 410 D_METHOD(("_InfoTextField::updateLineWrapping()\n")); 411 412 // clear the current list of lines but remember their number and 413 // the number of characters per line (to know if something changed) 414 int32 numLines = m_textLines->CountItems(); 415 int32* numChars = new int32[numLines]; 416 array_delete<int32> _d(numChars); 417 418 for (int32 i = 0; i < numLines; i++) 419 { 420 BString *line = static_cast<BString *>(m_textLines->ItemAt(i)); 421 numChars[i] = line->CountChars(); 422 delete line; 423 } 424 m_textLines->MakeEmpty(); 425 426 // calculate the maximum width for a line 427 float maxWidth = m_parent->Bounds().Width(); 428 maxWidth -= m_parent->getSideBarWidth() + 3 * InfoView::M_H_MARGIN + B_LARGE_ICON; 429 if (maxWidth <= be_plain_font->StringWidth("M")) 430 { 431 return; 432 } 433 434 // iterate through the text and split into new lines as 435 // necessary 436 BString *currentLine = new BString(m_text); 437 while (currentLine && (currentLine->CountChars() > 0)) 438 { 439 int32 lastBreak = 0; 440 for (int32 i = 0; i < currentLine->CountChars(); i++) 441 { 442 if (canEndLine(currentLine->ByteAt(i))) 443 { 444 lastBreak = i + 1; 445 if (mustEndLine(currentLine->ByteAt(i))) 446 { 447 BString *newLine = new BString(); 448 currentLine->Remove(i, 1); 449 currentLine->MoveInto(*newLine, i, 450 currentLine->CountChars() - i); 451 m_textLines->AddItem(reinterpret_cast<void *>(currentLine)); 452 currentLine = newLine; 453 break; 454 } 455 } 456 else 457 { 458 if (i == currentLine->CountChars() - 1) // the last char in the text 459 { 460 m_textLines->AddItem(reinterpret_cast<void *>(currentLine)); 461 currentLine = 0; 462 break; 463 } 464 else 465 { 466 BString buffer; 467 currentLine->CopyInto(buffer, 0, i); 468 if (be_plain_font->StringWidth(buffer.String()) > maxWidth) 469 { 470 if (lastBreak < 1) 471 { 472 lastBreak = i - 1; 473 } 474 BString *newLine = new BString(); 475 currentLine->MoveInto(*newLine, lastBreak, 476 currentLine->CountChars() - lastBreak); 477 m_textLines->AddItem(reinterpret_cast<void *>(currentLine)); 478 currentLine = newLine; 479 break; 480 } 481 } 482 } 483 } 484 } 485 486 // report changes in the fields total height (i.e. if the number 487 // of lines changed) 488 if (heightChanged && (numLines != m_textLines->CountItems())) 489 { 490 *heightChanged = true; 491 } 492 493 // report changes in the wrapping (e.g. if a word slipped into the 494 // next line) 495 else if (wrappingChanged) 496 { 497 for (int32 i = 0; i < m_textLines->CountItems(); i++) 498 { 499 BString *line = static_cast<BString *>(m_textLines->ItemAt(i)); 500 if (line->CountChars() != numChars[i]) 501 { 502 *wrappingChanged = true; 503 break; 504 } 505 } 506 } 507 } 508 509 // -------------------------------------------------------- // 510 // *** internal class: _InfoTextField 511 // 512 // *** accessors (public) 513 // -------------------------------------------------------- // 514 515 float 516 _InfoTextField::getHeight() const { 517 D_ACCESS(("_InfoTextField::getHeight()\n")); 518 519 font_height fh; 520 be_plain_font->GetHeight(&fh); 521 522 // calculate the width for an empty line (separator) 523 if (m_textLines->CountItems() == 0) 524 { 525 return fh.ascent + fh.descent + fh.leading; 526 } 527 528 // calculate the total height of the field by counting the 529 else 530 { 531 float height = fh.ascent + fh.descent + fh.leading; 532 height *= m_textLines->CountItems(); 533 height += fh.leading; 534 return height; 535 } 536 } 537 538 float 539 _InfoTextField::getWidth() const { 540 D_ACCESS(("_InfoTextField::getWidth()\n")); 541 542 float width = be_plain_font->StringWidth(m_text.String()); 543 width += m_parent->getSideBarWidth(); 544 545 return width; 546 } 547 548 bool 549 _InfoTextField::isWrapped() const { 550 D_ACCESS(("_InfoTextField::isWrapped()\n")); 551 552 return (m_textLines->CountItems() > 1); 553 } 554 555 // -------------------------------------------------------- // 556 // *** internal class: _InfoTextField 557 // 558 // *** static internal methods (private) 559 // -------------------------------------------------------- // 560 561 bool _InfoTextField::canEndLine( 562 const char c) 563 { 564 D_METHOD(("_InfoTextField::canEndLine()\n")); 565 566 if ((c == B_SPACE) || (c == B_TAB) || (c == B_ENTER) 567 || ( c == '-')) 568 { 569 return true; 570 } 571 return false; 572 } 573 574 bool _InfoTextField::mustEndLine( 575 const char c) 576 { 577 D_METHOD(("_InfoTextField::mustEndLine()\n")); 578 579 if (c == B_ENTER) 580 { 581 return true; 582 } 583 return false; 584 } 585 586 // END -- InfoView.cpp -- 587