1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "TextWidget.h" 37 38 #include <string.h> 39 #include <stdlib.h> 40 41 #include <Alert.h> 42 #include <Catalog.h> 43 #include <Debug.h> 44 #include <Directory.h> 45 #include <MessageFilter.h> 46 #include <ScrollView.h> 47 #include <TextView.h> 48 #include <Volume.h> 49 #include <Window.h> 50 51 #include "Attributes.h" 52 #include "ContainerWindow.h" 53 #include "Commands.h" 54 #include "FSUtils.h" 55 #include "PoseView.h" 56 #include "Utilities.h" 57 58 59 #undef B_TRANSLATION_CONTEXT 60 #define B_TRANSLATION_CONTEXT "TextWidget" 61 62 63 const float kWidthMargin = 20; 64 65 66 // #pragma mark - BTextWidget 67 68 69 BTextWidget::BTextWidget(Model* model, BColumn* column, BPoseView* view) 70 : 71 fText(WidgetAttributeText::NewWidgetText(model, column, view)), 72 fAttrHash(column->AttrHash()), 73 fAlignment(column->Alignment()), 74 fEditable(column->Editable()), 75 fVisible(true), 76 fActive(false), 77 fSymLink(model->IsSymLink()), 78 fLastClickedTime(0) 79 { 80 } 81 82 83 BTextWidget::~BTextWidget() 84 { 85 if (fLastClickedTime != 0) 86 fParams.poseView->SetTextWidgetToCheck(NULL, this); 87 88 delete fText; 89 } 90 91 92 int 93 BTextWidget::Compare(const BTextWidget& with, BPoseView* view) const 94 { 95 return fText->Compare(*with.fText, view); 96 } 97 98 99 const char* 100 BTextWidget::Text(const BPoseView* view) const 101 { 102 StringAttributeText* textAttribute 103 = dynamic_cast<StringAttributeText*>(fText); 104 if (textAttribute == NULL) 105 return NULL; 106 107 return textAttribute->ValueAsText(view); 108 } 109 110 111 float 112 BTextWidget::TextWidth(const BPoseView* pose) const 113 { 114 return fText->Width(pose); 115 } 116 117 118 float 119 BTextWidget::PreferredWidth(const BPoseView* pose) const 120 { 121 return fText->PreferredWidth(pose) + 1; 122 } 123 124 125 BRect 126 BTextWidget::ColumnRect(BPoint poseLoc, const BColumn* column, 127 const BPoseView* view) 128 { 129 if (view->ViewMode() != kListMode) { 130 // ColumnRect only makes sense in list view, return 131 // CalcRect otherwise 132 return CalcRect(poseLoc, column, view); 133 } 134 BRect result; 135 result.left = column->Offset() + poseLoc.x; 136 result.right = result.left + column->Width(); 137 result.bottom = poseLoc.y 138 + roundf((view->ListElemHeight() + view->FontHeight()) / 2); 139 result.top = result.bottom - view->FontHeight(); 140 return result; 141 } 142 143 144 BRect 145 BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn* column, 146 const BPoseView* view, float textWidth) 147 { 148 BRect result; 149 if (view->ViewMode() == kListMode) { 150 poseLoc.x += column->Offset(); 151 152 switch (fAlignment) { 153 case B_ALIGN_LEFT: 154 result.left = poseLoc.x; 155 result.right = result.left + textWidth + 1; 156 break; 157 158 case B_ALIGN_CENTER: 159 result.left = poseLoc.x + (column->Width() / 2) 160 - (textWidth / 2); 161 if (result.left < 0) 162 result.left = 0; 163 164 result.right = result.left + textWidth + 1; 165 break; 166 167 case B_ALIGN_RIGHT: 168 result.right = poseLoc.x + column->Width(); 169 result.left = result.right - textWidth - 1; 170 if (result.left < 0) 171 result.left = 0; 172 break; 173 174 default: 175 TRESPASS(); 176 break; 177 } 178 179 result.bottom = poseLoc.y 180 + roundf((view->ListElemHeight() + view->FontHeight()) / 2); 181 } else { 182 if (view->ViewMode() == kIconMode) { 183 // large/scaled icon mode 184 result.left = poseLoc.x + (view->IconSizeInt() - textWidth) / 2; 185 } else { 186 // mini icon mode 187 result.left = poseLoc.x + B_MINI_ICON + kMiniIconSeparator; 188 } 189 190 result.right = result.left + textWidth; 191 result.bottom = poseLoc.y + view->IconPoseHeight(); 192 } 193 result.top = result.bottom - view->FontHeight(); 194 195 return result; 196 } 197 198 199 BRect 200 BTextWidget::CalcRect(BPoint poseLoc, const BColumn* column, 201 const BPoseView* view) 202 { 203 return CalcRectCommon(poseLoc, column, view, fText->Width(view)); 204 } 205 206 207 BRect 208 BTextWidget::CalcOldRect(BPoint poseLoc, const BColumn* column, 209 const BPoseView* view) 210 { 211 return CalcRectCommon(poseLoc, column, view, fText->CurrentWidth()); 212 } 213 214 215 BRect 216 BTextWidget::CalcClickRect(BPoint poseLoc, const BColumn* column, 217 const BPoseView* view) 218 { 219 BRect result = CalcRect(poseLoc, column, view); 220 if (result.Width() < kWidthMargin) { 221 // if resulting rect too narrow, make it a bit wider 222 // for comfortable clicking 223 if (column != NULL && column->Width() < kWidthMargin) 224 result.right = result.left + column->Width(); 225 else 226 result.right = result.left + kWidthMargin; 227 } 228 229 return result; 230 } 231 232 233 void 234 BTextWidget::CheckExpiration() 235 { 236 if (IsEditable() && fParams.pose->IsSelected() && fLastClickedTime) { 237 bigtime_t doubleClickSpeed; 238 get_click_speed(&doubleClickSpeed); 239 240 bigtime_t delta = system_time() - fLastClickedTime; 241 242 if (delta > doubleClickSpeed) { 243 // at least 'doubleClickSpeed' microseconds ellapsed and no click 244 // was registered since. 245 fLastClickedTime = 0; 246 StartEdit(fParams.bounds, fParams.poseView, fParams.pose); 247 } 248 } else { 249 fLastClickedTime = 0; 250 fParams.poseView->SetTextWidgetToCheck(NULL); 251 } 252 } 253 254 255 void 256 BTextWidget::CancelWait() 257 { 258 fLastClickedTime = 0; 259 fParams.poseView->SetTextWidgetToCheck(NULL); 260 } 261 262 263 void 264 BTextWidget::MouseUp(BRect bounds, BPoseView* view, BPose* pose, BPoint) 265 { 266 // Register the time of that click. The PoseView, through its Pulse() 267 // will allow us to StartEdit() if no other click have been registered since 268 // then. 269 270 // TODO: re-enable modifiers, one should be enough 271 view->SetTextWidgetToCheck(NULL); 272 if (IsEditable() && pose->IsSelected()) { 273 bigtime_t doubleClickSpeed; 274 get_click_speed(&doubleClickSpeed); 275 276 if (fLastClickedTime == 0) { 277 fLastClickedTime = system_time(); 278 if (fLastClickedTime - doubleClickSpeed < pose->SelectionTime()) 279 fLastClickedTime = 0; 280 } else 281 fLastClickedTime = 0; 282 283 if (fLastClickedTime == 0) 284 return; 285 286 view->SetTextWidgetToCheck(this); 287 288 fParams.pose = pose; 289 fParams.bounds = bounds; 290 fParams.poseView = view; 291 } else 292 fLastClickedTime = 0; 293 } 294 295 296 static filter_result 297 TextViewFilter(BMessage* message, BHandler**, BMessageFilter* filter) 298 { 299 uchar key; 300 if (message->FindInt8("byte", (int8*)&key) != B_OK) 301 return B_DISPATCH_MESSAGE; 302 303 ThrowOnAssert(filter != NULL); 304 305 BContainerWindow* window = dynamic_cast<BContainerWindow*>( 306 filter->Looper()); 307 ThrowOnAssert(window != NULL); 308 309 BPoseView* poseView = window->PoseView(); 310 ThrowOnAssert(poseView != NULL); 311 312 if (key == B_RETURN || key == B_ESCAPE) { 313 poseView->CommitActivePose(key == B_RETURN); 314 return B_SKIP_MESSAGE; 315 } 316 317 if (key == B_TAB) { 318 if (poseView->ActivePose()) { 319 if (message->FindInt32("modifiers") & B_SHIFT_KEY) 320 poseView->ActivePose()->EditPreviousWidget(poseView); 321 else 322 poseView->ActivePose()->EditNextWidget(poseView); 323 } 324 325 return B_SKIP_MESSAGE; 326 } 327 328 // the BTextView doesn't respect window borders when resizing itself; 329 // we try to work-around this "bug" here. 330 331 // find the text editing view 332 BView* scrollView = poseView->FindView("BorderView"); 333 if (scrollView != NULL) { 334 BTextView* textView = dynamic_cast<BTextView*>( 335 scrollView->FindView("WidgetTextView")); 336 if (textView != NULL) { 337 BRect textRect = textView->TextRect(); 338 BRect rect = scrollView->Frame(); 339 340 if (rect.right + 5 > poseView->Bounds().right 341 || rect.left - 5 < 0) 342 textView->MakeResizable(true, NULL); 343 344 if (textRect.Width() + 10 < rect.Width()) { 345 textView->MakeResizable(true, scrollView); 346 // make sure no empty white space stays on the right 347 textView->ScrollToOffset(0); 348 } 349 } 350 } 351 352 return B_DISPATCH_MESSAGE; 353 } 354 355 356 void 357 BTextWidget::StartEdit(BRect bounds, BPoseView* view, BPose* pose) 358 { 359 view->SetTextWidgetToCheck(NULL, this); 360 if (!IsEditable() || IsActive()) 361 return; 362 363 BEntry entry(pose->TargetModel()->EntryRef()); 364 if (entry.InitCheck() == B_OK 365 && !ConfirmChangeIfWellKnownDirectory(&entry, kRename)) { 366 return; 367 } 368 369 // TODO fix text rect being off by a pixel on some files 370 371 // get bounds with full text length 372 BRect rect(bounds); 373 BRect textRect(bounds); 374 375 // label offset 376 float hOffset = 0; 377 float vOffset = view->ViewMode() == kListMode ? -1 : -2; 378 rect.OffsetBy(hOffset, vOffset); 379 380 BTextView* textView = new BTextView(rect, "WidgetTextView", textRect, 381 be_plain_font, 0, B_FOLLOW_ALL, B_WILL_DRAW); 382 383 textView->SetWordWrap(false); 384 textView->SetInsets(2, 2, 2, 2); 385 DisallowMetaKeys(textView); 386 fText->SetUpEditing(textView); 387 388 textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewFilter)); 389 390 rect.right = rect.left + textView->LineWidth(); 391 rect.bottom = rect.top + textView->LineHeight() - 1; 392 393 // enlarge rect by inset amount 394 rect.InsetBy(-2, -2); 395 396 // undo label offset 397 textRect = rect.OffsetToCopy(-hOffset, -vOffset); 398 399 textView->SetTextRect(textRect); 400 401 BPoint origin = view->LeftTop(); 402 textRect = view->Bounds(); 403 404 bool hitBorder = false; 405 if (rect.left <= origin.x) 406 rect.left = origin.x + 1, hitBorder = true; 407 if (rect.right >= textRect.right) 408 rect.right = textRect.right - 1, hitBorder = true; 409 410 textView->MoveTo(rect.LeftTop()); 411 textView->ResizeTo(rect.Width(), rect.Height()); 412 413 BScrollView* scrollView = new BScrollView("BorderView", textView, 0, 0, 414 false, false, B_PLAIN_BORDER); 415 view->AddChild(scrollView); 416 417 // configure text view 418 switch (view->ViewMode()) { 419 case kIconMode: 420 textView->SetAlignment(B_ALIGN_CENTER); 421 break; 422 423 case kMiniIconMode: 424 textView->SetAlignment(B_ALIGN_LEFT); 425 break; 426 427 case kListMode: 428 textView->SetAlignment(fAlignment); 429 break; 430 } 431 textView->MakeResizable(true, hitBorder ? NULL : scrollView); 432 433 view->SetActivePose(pose); 434 // tell view about pose 435 SetActive(true); 436 // for widget 437 438 textView->SelectAll(); 439 textView->ScrollToSelection(); 440 // scroll to beginning so that text is visible 441 textView->ScrollBy(-1, -2); 442 // scroll in rect to center text 443 textView->MakeFocus(); 444 445 // make this text widget invisible while we edit it 446 SetVisible(false); 447 448 ASSERT(view->Window() != NULL); 449 // how can I not have a Window here??? 450 451 if (view->Window()) { 452 // force immediate redraw so TextView appears instantly 453 view->Window()->UpdateIfNeeded(); 454 } 455 } 456 457 458 void 459 BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView* view, 460 BPose* pose, int32 poseIndex) 461 { 462 // find the text editing view 463 BView* scrollView = view->FindView("BorderView"); 464 ASSERT(scrollView != NULL); 465 if (scrollView == NULL) 466 return; 467 468 BTextView* textView = dynamic_cast<BTextView*>( 469 scrollView->FindView("WidgetTextView")); 470 ASSERT(textView != NULL); 471 if (textView == NULL) 472 return; 473 474 BColumn* column = view->ColumnFor(fAttrHash); 475 ASSERT(column != NULL); 476 if (column == NULL) 477 return; 478 479 if (saveChanges && fText->CommitEditedText(textView)) { 480 // we have an actual change, re-sort 481 view->CheckPoseSortOrder(pose, poseIndex); 482 } 483 484 // make text widget visible again 485 SetVisible(true); 486 view->Invalidate(ColumnRect(poseLoc, column, view)); 487 488 // force immediate redraw so TEView disappears 489 scrollView->RemoveSelf(); 490 delete scrollView; 491 492 ASSERT(view->Window() != NULL); 493 view->Window()->UpdateIfNeeded(); 494 view->MakeFocus(); 495 496 SetActive(false); 497 } 498 499 500 void 501 BTextWidget::CheckAndUpdate(BPoint loc, const BColumn* column, 502 BPoseView* view, bool visible) 503 { 504 BRect oldRect; 505 if (view->ViewMode() != kListMode) 506 oldRect = CalcOldRect(loc, column, view); 507 508 if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view) 509 && visible) { 510 BRect invalRect(ColumnRect(loc, column, view)); 511 if (view->ViewMode() != kListMode) 512 invalRect = invalRect | oldRect; 513 view->Invalidate(invalRect); 514 } 515 } 516 517 518 void 519 BTextWidget::SelectAll(BPoseView* view) 520 { 521 BTextView* text = dynamic_cast<BTextView*>( 522 view->FindView("WidgetTextView")); 523 if (text != NULL) 524 text->SelectAll(); 525 } 526 527 528 void 529 BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView* view, 530 BView* drawView, bool selected, uint32 clipboardMode, BPoint offset, 531 bool direct) 532 { 533 textRect.OffsetBy(offset); 534 535 if (direct) { 536 // draw selection box if selected 537 if (selected) { 538 drawView->SetDrawingMode(B_OP_COPY); 539 // eraseRect.OffsetBy(offset); 540 // drawView->FillRect(eraseRect, B_SOLID_LOW); 541 drawView->FillRect(textRect, B_SOLID_LOW); 542 } else 543 drawView->SetDrawingMode(B_OP_OVER); 544 545 // set high color 546 rgb_color highColor; 547 // for active views, the selection is drawn as inverse text (background color for the text, 548 // solid black for the background). 549 // For inactive windows, the text is drawn normally, then the selection rect is 550 // alpha-blended on top of it. 551 // This all happens in BPose::Draw before and after calling this function, here we are 552 // only concerned with setting the correct color for the text. 553 if (selected && view->Window()->IsActive()) 554 highColor = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 555 else 556 highColor = view->DeskTextColor(); 557 558 if (clipboardMode == kMoveSelectionTo && !selected) { 559 drawView->SetDrawingMode(B_OP_ALPHA); 560 drawView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 561 highColor.alpha = 64; 562 } 563 drawView->SetHighColor(highColor); 564 } 565 566 BPoint loc; 567 loc.y = textRect.bottom - view->FontInfo().descent; 568 loc.x = textRect.left + 1; 569 570 const char* fittingText = fText->FittingText(view); 571 572 // TODO: Comparing view and drawView here to avoid rendering 573 // the text outline when producing a drag bitmap. The check is 574 // not fully correct, since an offscreen view is also used in some 575 // other rare cases (something to do with columns). But for now, this 576 // fixes the broken drag bitmaps when dragging icons from the Desktop. 577 if (!selected && view == drawView && view->WidgetTextOutline()) { 578 // draw a halo around the text by using the "false bold" 579 // feature for text rendering. Either black or white is used for 580 // the glow (whatever acts as contrast) with a some alpha value, 581 drawView->SetDrawingMode(B_OP_ALPHA); 582 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 583 584 BFont font; 585 drawView->GetFont(&font); 586 587 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR); 588 if (view->IsDesktopWindow()) 589 textColor = view->DeskTextColor(); 590 591 if (textColor.Brightness() < 100) { 592 // dark text on light outline 593 rgb_color glowColor = ui_color(B_SHINE_COLOR); 594 595 font.SetFalseBoldWidth(2.0); 596 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 597 glowColor.alpha = 30; 598 drawView->SetHighColor(glowColor); 599 600 drawView->DrawString(fittingText, loc); 601 602 font.SetFalseBoldWidth(1.0); 603 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 604 glowColor.alpha = 65; 605 drawView->SetHighColor(glowColor); 606 607 drawView->DrawString(fittingText, loc); 608 609 font.SetFalseBoldWidth(0.0); 610 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 611 } else { 612 // light text on dark outline 613 rgb_color outlineColor = kBlack; 614 615 font.SetFalseBoldWidth(1.0); 616 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 617 outlineColor.alpha = 30; 618 drawView->SetHighColor(outlineColor); 619 620 drawView->DrawString(fittingText, loc); 621 622 font.SetFalseBoldWidth(0.0); 623 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 624 625 outlineColor.alpha = 200; 626 drawView->SetHighColor(outlineColor); 627 628 drawView->DrawString(fittingText, loc + BPoint(1, 1)); 629 } 630 631 drawView->SetDrawingMode(B_OP_OVER); 632 drawView->SetHighColor(textColor); 633 } 634 635 drawView->DrawString(fittingText, loc); 636 637 if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) { 638 // TODO: 639 // this should be exported to the WidgetAttribute class, probably 640 // by having a per widget kind style 641 if (direct) { 642 rgb_color underlineColor = drawView->HighColor(); 643 underlineColor.alpha = 180; 644 drawView->SetHighColor(underlineColor); 645 drawView->SetDrawingMode(B_OP_ALPHA); 646 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 647 } 648 649 textRect.right = textRect.left + fText->Width(view); 650 // only underline text part 651 drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(), 652 B_MIXED_COLORS); 653 } 654 } 655