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(new _InfoTextField(label, text, this)); 364 } 365 366 // -------------------------------------------------------- // 367 // *** internal class: _InfoTextField 368 // 369 // *** ctor/dtor 370 // -------------------------------------------------------- // 371 372 _InfoTextField::_InfoTextField( 373 BString label, 374 BString text, 375 InfoView *parent) 376 : m_label(label), 377 m_text(text), 378 m_textLines(0), 379 m_parent(parent) { 380 D_ALLOC(("_InfoTextField::_InfoTextField()\n")); 381 382 if (m_label != "") { 383 m_label << ": "; 384 } 385 m_textLines = new BList(); 386 } 387 388 _InfoTextField::~_InfoTextField() { 389 D_ALLOC(("_InfoTextField::~_InfoTextField()\n")); 390 391 // delete every line 392 if (m_textLines) { 393 while (m_textLines->CountItems() > 0) { 394 BString *line = static_cast<BString *>(m_textLines->RemoveItem((int32)0)); 395 if (line) { 396 delete line; 397 } 398 } 399 delete m_textLines; 400 m_textLines = 0; 401 } 402 } 403 404 // -------------------------------------------------------- // 405 // *** internal class: _InfoTextField 406 // 407 // *** operations (public) 408 // -------------------------------------------------------- // 409 410 void _InfoTextField::drawField( 411 BPoint position) { 412 D_METHOD(("_InfoTextField::drawField()\n")); 413 414 float sideBarWidth = m_parent->getSideBarWidth(); 415 416 // Draw label 417 BPoint p = position; 418 p.x += sideBarWidth - be_plain_font->StringWidth(m_label.String()); 419 m_parent->SetHighColor(M_DARK_GRAY_COLOR); 420 m_parent->SetDrawingMode(B_OP_OVER); 421 m_parent->SetFont(be_plain_font); 422 m_parent->DrawString(m_label.String(), p); 423 424 // Draw text 425 font_height fh; 426 be_plain_font->GetHeight(&fh); 427 p.x = position.x + sideBarWidth;// + InfoView::M_H_MARGIN; 428 m_parent->SetHighColor(M_BLACK_COLOR); 429 for (int32 i = 0; i < m_textLines->CountItems(); i++) { 430 BString *line = static_cast<BString *>(m_textLines->ItemAt(i)); 431 m_parent->DrawString(line->String(), p); 432 p.y += fh.ascent + fh.descent + fh.leading; 433 } 434 } 435 436 void _InfoTextField::updateLineWrapping( 437 bool *wrappingChanged, 438 bool *heightChanged) 439 { 440 D_METHOD(("_InfoTextField::updateLineWrapping()\n")); 441 442 // clear the current list of lines but remember their number and 443 // the number of characters per line (to know if something changed) 444 int32 numLines = m_textLines->CountItems(); 445 int32* numChars = new int32[numLines]; 446 array_delete<int32> _d(numChars); 447 448 for (int32 i = 0; i < numLines; i++) 449 { 450 BString *line = static_cast<BString *>(m_textLines->ItemAt(i)); 451 numChars[i] = line->CountChars(); 452 delete line; 453 } 454 m_textLines->MakeEmpty(); 455 456 // calculate the maximum width for a line 457 float maxWidth = m_parent->Bounds().Width(); 458 maxWidth -= m_parent->getSideBarWidth() + 3 * InfoView::M_H_MARGIN + B_LARGE_ICON; 459 if (maxWidth <= be_plain_font->StringWidth("M")) 460 { 461 return; 462 } 463 464 // iterate through the text and split into new lines as 465 // necessary 466 BString *currentLine = new BString(m_text); 467 while (currentLine && (currentLine->CountChars() > 0)) 468 { 469 int32 lastBreak = 0; 470 for (int32 i = 0; i < currentLine->CountChars(); i++) 471 { 472 if (canEndLine(currentLine->ByteAt(i))) 473 { 474 lastBreak = i + 1; 475 if (mustEndLine(currentLine->ByteAt(i))) 476 { 477 BString *newLine = new BString(); 478 currentLine->Remove(i, 1); 479 currentLine->MoveInto(*newLine, i, 480 currentLine->CountChars() - i); 481 m_textLines->AddItem(currentLine); 482 currentLine = newLine; 483 break; 484 } 485 } 486 else 487 { 488 if (i == currentLine->CountChars() - 1) // the last char in the text 489 { 490 m_textLines->AddItem(currentLine); 491 currentLine = 0; 492 break; 493 } 494 else 495 { 496 BString buffer; 497 currentLine->CopyInto(buffer, 0, i); 498 if (be_plain_font->StringWidth(buffer.String()) > maxWidth) 499 { 500 if (lastBreak < 1) 501 { 502 lastBreak = i - 1; 503 } 504 BString *newLine = new BString(); 505 currentLine->MoveInto(*newLine, lastBreak, 506 currentLine->CountChars() - lastBreak); 507 m_textLines->AddItem(currentLine); 508 currentLine = newLine; 509 break; 510 } 511 } 512 } 513 } 514 } 515 516 // report changes in the fields total height (i.e. if the number 517 // of lines changed) 518 if (heightChanged && (numLines != m_textLines->CountItems())) 519 { 520 *heightChanged = true; 521 } 522 523 // report changes in the wrapping (e.g. if a word slipped into the 524 // next line) 525 else if (wrappingChanged) 526 { 527 for (int32 i = 0; i < m_textLines->CountItems(); i++) 528 { 529 BString *line = static_cast<BString *>(m_textLines->ItemAt(i)); 530 if (line->CountChars() != numChars[i]) 531 { 532 *wrappingChanged = true; 533 break; 534 } 535 } 536 } 537 } 538 539 // -------------------------------------------------------- // 540 // *** internal class: _InfoTextField 541 // 542 // *** accessors (public) 543 // -------------------------------------------------------- // 544 545 float 546 _InfoTextField::getHeight() const { 547 D_ACCESS(("_InfoTextField::getHeight()\n")); 548 549 font_height fh; 550 be_plain_font->GetHeight(&fh); 551 552 // calculate the width for an empty line (separator) 553 if (m_textLines->CountItems() == 0) 554 { 555 return fh.ascent + fh.descent + fh.leading; 556 } 557 558 // calculate the total height of the field by counting the 559 else 560 { 561 float height = fh.ascent + fh.descent + fh.leading; 562 height *= m_textLines->CountItems(); 563 height += fh.leading; 564 return height; 565 } 566 } 567 568 float 569 _InfoTextField::getWidth() const { 570 D_ACCESS(("_InfoTextField::getWidth()\n")); 571 572 float width = be_plain_font->StringWidth(m_text.String()); 573 width += m_parent->getSideBarWidth(); 574 575 return width; 576 } 577 578 bool 579 _InfoTextField::isWrapped() const { 580 D_ACCESS(("_InfoTextField::isWrapped()\n")); 581 582 return (m_textLines->CountItems() > 1); 583 } 584 585 // -------------------------------------------------------- // 586 // *** internal class: _InfoTextField 587 // 588 // *** static internal methods (private) 589 // -------------------------------------------------------- // 590 591 bool _InfoTextField::canEndLine( 592 const char c) 593 { 594 D_METHOD(("_InfoTextField::canEndLine()\n")); 595 596 if ((c == B_SPACE) || (c == B_TAB) || (c == B_ENTER) 597 || ( c == '-')) 598 { 599 return true; 600 } 601 return false; 602 } 603 604 bool _InfoTextField::mustEndLine( 605 const char c) 606 { 607 D_METHOD(("_InfoTextField::mustEndLine()\n")); 608 609 if (c == B_ENTER) 610 { 611 return true; 612 } 613 return false; 614 } 615 616 // END -- InfoView.cpp -- 617