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