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