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 #include <string.h> 36 #include <stdlib.h> 37 38 #include <Alert.h> 39 #include <Catalog.h> 40 #include <Debug.h> 41 #include <Directory.h> 42 #include <MessageFilter.h> 43 #include <ScrollView.h> 44 #include <TextView.h> 45 #include <Volume.h> 46 #include <Window.h> 47 48 #include "Attributes.h" 49 #include "ContainerWindow.h" 50 #include "Commands.h" 51 #include "FSUtils.h" 52 #include "PoseView.h" 53 #include "TextWidget.h" 54 #include "Utilities.h" 55 #include "WidgetAttributeText.h" 56 57 58 #undef B_TRANSLATE_CONTEXT 59 #define B_TRANSLATE_CONTEXT "TextWidget" 60 61 const float kWidthMargin = 20; 62 63 64 BTextWidget::BTextWidget(Model *model, BColumn *column, BPoseView *view) 65 : 66 fText(WidgetAttributeText::NewWidgetText(model, column, view)), 67 fAttrHash(column->AttrHash()), 68 fAlignment(column->Alignment()), 69 fEditable(column->Editable()), 70 fVisible(true), 71 fActive(false), 72 fSymLink(model->IsSymLink()) 73 { 74 } 75 76 77 BTextWidget::~BTextWidget() 78 { 79 delete fText; 80 } 81 82 83 int 84 BTextWidget::Compare(const BTextWidget &with, BPoseView *view) const 85 { 86 return fText->Compare(*with.fText, view); 87 } 88 89 90 const char * 91 BTextWidget::Text(const BPoseView *view) const 92 { 93 StringAttributeText *textAttribute = dynamic_cast<StringAttributeText *>(fText); 94 if (textAttribute == NULL) 95 return NULL; 96 97 return textAttribute->ValueAsText(view); 98 } 99 100 101 float 102 BTextWidget::TextWidth(const BPoseView *pose) const 103 { 104 return fText->Width(pose); 105 } 106 107 108 float 109 BTextWidget::PreferredWidth(const BPoseView *pose) const 110 { 111 return fText->PreferredWidth(pose) + 1; 112 } 113 114 115 BRect 116 BTextWidget::ColumnRect(BPoint poseLoc, const BColumn *column, 117 const BPoseView *view) 118 { 119 if (view->ViewMode() != kListMode) { 120 // ColumnRect only makes sense in list view, return 121 // CalcRect otherwise 122 return CalcRect(poseLoc, column, view); 123 } 124 BRect result; 125 result.left = column->Offset() + poseLoc.x; 126 result.right = result.left + column->Width(); 127 result.bottom = poseLoc.y + view->ListElemHeight() - 1; 128 result.top = result.bottom - view->FontHeight(); 129 return result; 130 } 131 132 133 BRect 134 BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn *column, 135 const BPoseView *view, float textWidth) 136 { 137 BRect result; 138 if (view->ViewMode() == kListMode) { 139 poseLoc.x += column->Offset(); 140 141 switch (fAlignment) { 142 case B_ALIGN_LEFT: 143 result.left = poseLoc.x; 144 result.right = result.left + textWidth + 1; 145 break; 146 147 case B_ALIGN_CENTER: 148 result.left = poseLoc.x + (column->Width() / 2) - (textWidth / 2); 149 if (result.left < 0) 150 result.left = 0; 151 result.right = result.left + textWidth + 1; 152 break; 153 154 case B_ALIGN_RIGHT: 155 result.right = poseLoc.x + column->Width(); 156 result.left = result.right - textWidth - 1; 157 if (result.left < 0) 158 result.left = 0; 159 break; 160 default: 161 TRESPASS(); 162 } 163 164 result.bottom = poseLoc.y + (view->ListElemHeight() - 1); 165 } else { 166 if (view->ViewMode() == kIconMode) { 167 // large/scaled icon mode 168 result.left = poseLoc.x + (view->IconSizeInt() - textWidth) / 2; 169 } else { 170 // mini icon mode 171 result.left = poseLoc.x + B_MINI_ICON + kMiniIconSeparator; 172 } 173 174 result.right = result.left + textWidth; 175 result.bottom = poseLoc.y + view->IconPoseHeight(); 176 177 } 178 result.top = result.bottom - view->FontHeight(); 179 180 return result; 181 } 182 183 184 BRect 185 BTextWidget::CalcRect(BPoint poseLoc, const BColumn *column, 186 const BPoseView *view) 187 { 188 return CalcRectCommon(poseLoc, column, view, fText->Width(view)); 189 } 190 191 192 BRect 193 BTextWidget::CalcOldRect(BPoint poseLoc, const BColumn *column, 194 const BPoseView *view) 195 { 196 return CalcRectCommon(poseLoc, column, view, fText->CurrentWidth()); 197 } 198 199 200 BRect 201 BTextWidget::CalcClickRect(BPoint poseLoc, const BColumn *column, 202 const BPoseView* view) 203 { 204 BRect result = CalcRect(poseLoc, column, view); 205 if (result.Width() < kWidthMargin) { 206 // if resulting rect too narrow, make it a bit wider 207 // for comfortable clicking 208 if (column && column->Width() < kWidthMargin) 209 result.right = result.left + column->Width(); 210 else 211 result.right = result.left + kWidthMargin; 212 } 213 return result; 214 } 215 216 217 void 218 BTextWidget::MouseUp(BRect bounds, BPoseView *view, BPose *pose, BPoint) 219 { 220 // Start editing without delay if the pose was selected recently and this 221 // click is not the second click of a doubleclick. 222 // If the pose has been selected a long time ago, check again 223 // for a double click (inducing a delay). 224 225 // TODO: re-enable modifiers, one should be enough 226 227 if (IsEditable() && pose->IsSelected()) { 228 bigtime_t delta = system_time() - pose->SelectionTime(); 229 bigtime_t doubleClickSpeed; 230 get_click_speed(&doubleClickSpeed); 231 bigtime_t oldClickSpeed = 2 * doubleClickSpeed; 232 233 // freshly selected and not a double click 234 if (delta > doubleClickSpeed && delta < oldClickSpeed) { 235 StartEdit(bounds, view, pose); 236 return; 237 } 238 239 // TODO: reimplement asynchronous 240 // selected a longer time ago, redo a double click detection 241 if (delta > oldClickSpeed) { 242 // check for double click 243 bigtime_t doubleClickTime = system_time() + doubleClickSpeed; 244 while (system_time() < doubleClickTime) { 245 // loop for double-click time and watch the mouse and keyboard 246 247 BPoint point; 248 uint32 buttons; 249 view->GetMouse(&point, &buttons, false); 250 251 // double click 252 if (buttons) 253 return; 254 255 // mouse moved too far 256 if (!bounds.Contains(point)) 257 return; 258 259 //if (modifiers() & (B_SHIFT_KEY | B_COMMAND_KEY 260 // | B_CONTROL_KEY | B_MENU_KEY)) 261 // // watch the keyboard (ignoring standard locking keys) 262 // break; 263 264 snooze(10000); 265 } 266 StartEdit(bounds, view, pose); 267 } 268 } 269 } 270 271 272 static filter_result 273 TextViewFilter(BMessage *message, BHandler **, BMessageFilter *filter) 274 { 275 uchar key; 276 if (message->FindInt8("byte", (int8 *)&key) != B_OK) 277 return B_DISPATCH_MESSAGE; 278 279 BPoseView *poseView = dynamic_cast<BContainerWindow*>(filter->Looper())-> 280 PoseView(); 281 282 if (key == B_RETURN || key == B_ESCAPE) { 283 poseView->CommitActivePose(key == B_RETURN); 284 return B_SKIP_MESSAGE; 285 } 286 287 if (key == B_TAB) { 288 if (poseView->ActivePose()) { 289 if (message->FindInt32("modifiers") & B_SHIFT_KEY) 290 poseView->ActivePose()->EditPreviousWidget(poseView); 291 else 292 poseView->ActivePose()->EditNextWidget(poseView); 293 } 294 295 return B_SKIP_MESSAGE; 296 } 297 298 // the BTextView doesn't respect window borders when resizing itself; 299 // we try to work-around this "bug" here. 300 301 // find the text editing view 302 BView *scrollView = poseView->FindView("BorderView"); 303 if (scrollView != NULL) { 304 BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView")); 305 if (textView != NULL) { 306 BRect rect = scrollView->Frame(); 307 308 if (rect.right + 3 > poseView->Bounds().right 309 || rect.left - 3 < 0) 310 textView->MakeResizable(true, NULL); 311 } 312 } 313 314 return B_DISPATCH_MESSAGE; 315 } 316 317 318 void 319 BTextWidget::StartEdit(BRect bounds, BPoseView *view, BPose *pose) 320 { 321 if (!IsEditable()) 322 return; 323 324 BEntry entry(pose->TargetModel()->EntryRef()); 325 if (entry.InitCheck() == B_OK 326 && !ConfirmChangeIfWellKnownDirectory(&entry, 327 B_TRANSLATE_COMMENT("rename", 328 "As in 'if you rename this folder...' (en) " 329 "'Wird dieser Ordner umbenannt...' (de)"), 330 B_TRANSLATE_COMMENT("rename", 331 "As in 'to rename this folder...' (en) " 332 "'Um diesen Ordner umzubenennen...' (de)"), 333 B_TRANSLATE_COMMENT("Rename", 334 "Button label, 'Rename' (en), 'Umbenennen' (de)"))) 335 return; 336 337 // get bounds with full text length 338 BRect rect(bounds); 339 BRect textRect(bounds); 340 rect.OffsetBy(-2, -1); 341 rect.right += 1; 342 343 BFont font; 344 view->GetFont(&font); 345 BTextView *textView = new BTextView(rect, "WidgetTextView", textRect, &font, 0, 346 B_FOLLOW_ALL, B_WILL_DRAW); 347 348 textView->SetWordWrap(false); 349 DisallowMetaKeys(textView); 350 fText->SetUpEditing(textView); 351 352 textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewFilter)); 353 354 rect.right = rect.left + textView->LineWidth() + 3; 355 // center new width, if necessary 356 if (view->ViewMode() == kIconMode 357 || (view->ViewMode() == kListMode && fAlignment == B_ALIGN_CENTER)) { 358 rect.OffsetBy(bounds.Width() / 2 - rect.Width() / 2, 0); 359 } 360 361 rect.bottom = rect.top + textView->LineHeight() + 1; 362 textRect = rect.OffsetToCopy(2, 1); 363 textRect.right -= 3; 364 textRect.bottom--; 365 textView->SetTextRect(textRect); 366 367 textRect = view->Bounds(); 368 bool hitBorder = false; 369 if (rect.left < 1) 370 rect.left = 1, hitBorder = true; 371 if (rect.right > textRect.right) 372 rect.right = textRect.right - 2, hitBorder = true; 373 374 textView->MoveTo(rect.LeftTop()); 375 textView->ResizeTo(rect.Width(), rect.Height()); 376 377 BScrollView *scrollView = new BScrollView("BorderView", textView, 0, 0, false, 378 false, B_PLAIN_BORDER); 379 view->AddChild(scrollView); 380 381 // configure text view 382 switch (view->ViewMode()) { 383 case kIconMode: 384 textView->SetAlignment(B_ALIGN_CENTER); 385 break; 386 387 case kMiniIconMode: 388 textView->SetAlignment(B_ALIGN_LEFT); 389 break; 390 391 case kListMode: 392 textView->SetAlignment(fAlignment); 393 break; 394 } 395 textView->MakeResizable(true, hitBorder ? NULL : scrollView); 396 397 view->SetActivePose(pose); // tell view about pose 398 SetActive(true); // for widget 399 400 textView->SelectAll(); 401 textView->MakeFocus(); 402 403 // make this text widget invisible while we edit it 404 SetVisible(false); 405 406 ASSERT(view->Window()); // how can I not have a Window here??? 407 408 if (view->Window()) 409 // force immediate redraw so TextView appears instantly 410 view->Window()->UpdateIfNeeded(); 411 } 412 413 414 void 415 BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView *view, 416 BPose *pose, int32 poseIndex) 417 { 418 // find the text editing view 419 BView *scrollView = view->FindView("BorderView"); 420 ASSERT(scrollView); 421 if (!scrollView) 422 return; 423 424 BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView")); 425 ASSERT(textView); 426 if (!textView) 427 return; 428 429 BColumn *column = view->ColumnFor(fAttrHash); 430 ASSERT(column); 431 if (!column) 432 return; 433 434 if (saveChanges && fText->CommitEditedText(textView)) { 435 // we have an actual change, re-sort 436 view->CheckPoseSortOrder(pose, poseIndex); 437 } 438 439 // make text widget visible again 440 SetVisible(true); 441 view->Invalidate(ColumnRect(poseLoc, column, view)); 442 443 // force immediate redraw so TEView disappears 444 scrollView->RemoveSelf(); 445 delete scrollView; 446 447 ASSERT(view->Window()); 448 view->Window()->UpdateIfNeeded(); 449 view->MakeFocus(); 450 451 SetActive(false); 452 } 453 454 455 void 456 BTextWidget::CheckAndUpdate(BPoint loc, const BColumn *column, BPoseView *view, 457 bool visible) 458 { 459 BRect oldRect; 460 if (view->ViewMode() != kListMode) 461 oldRect = CalcOldRect(loc, column, view); 462 463 if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view) 464 && visible) { 465 BRect invalRect(ColumnRect(loc, column, view)); 466 if (view->ViewMode() != kListMode) 467 invalRect = invalRect | oldRect; 468 view->Invalidate(invalRect); 469 } 470 } 471 472 473 void 474 BTextWidget::SelectAll(BPoseView *view) 475 { 476 BTextView *text = dynamic_cast<BTextView *>(view->FindView("WidgetTextView")); 477 if (text) 478 text->SelectAll(); 479 } 480 481 482 void 483 BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView *view, 484 BView *drawView, bool selected, uint32 clipboardMode, BPoint offset, bool direct) 485 { 486 textRect.OffsetBy(offset); 487 488 if (direct) { 489 #ifdef __HAIKU__ 490 // draw selection box if selected 491 if (selected) { 492 #else 493 // erase area we're going to draw in 494 // NOTE: WidgetTextOutline() is reused for 495 // erasing background on R5 here 496 if (view->WidgetTextOutline() || selected) { 497 #endif 498 drawView->SetDrawingMode(B_OP_COPY); 499 eraseRect.OffsetBy(offset); 500 // drawView->FillRect(eraseRect, B_SOLID_LOW); 501 drawView->FillRect(textRect, B_SOLID_LOW); 502 } else 503 drawView->SetDrawingMode(B_OP_OVER); 504 505 // set high color 506 rgb_color highColor; 507 if (view->IsDesktopWindow()) { 508 if (selected) 509 highColor = kWhite; 510 else 511 highColor = view->DeskTextColor(); 512 } else if (selected && view->Window()->IsActive()) { 513 highColor = kWhite; 514 } else 515 highColor = kBlack; 516 517 if (clipboardMode == kMoveSelectionTo && !selected) { 518 drawView->SetDrawingMode(B_OP_ALPHA); 519 drawView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 520 highColor.alpha = 64; 521 } 522 drawView->SetHighColor(highColor); 523 } 524 525 BPoint loc; 526 loc.y = textRect.bottom - view->FontInfo().descent; 527 loc.x = textRect.left + 1; 528 529 const char* fittingText = fText->FittingText(view); 530 531 #ifdef __HAIKU__ 532 // TODO: Comparing view and drawView here to avoid rendering 533 // the text outline when producing a drag bitmap. The check is 534 // not fully correct, since an offscreen view is also used in some 535 // other rare cases (something to do with columns). But for now, this 536 // fixes the broken drag bitmaps when dragging icons from the Desktop. 537 if (!selected && view == drawView && view->WidgetTextOutline()) { 538 // draw a halo around the text by using the "false bold" 539 // feature for text rendering. Either black or white is used for 540 // the glow (whatever acts as contrast) with a some alpha value, 541 drawView->SetDrawingMode(B_OP_ALPHA); 542 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 543 544 BFont font; 545 drawView->GetFont(&font); 546 547 rgb_color textColor = drawView->HighColor(); 548 if (textColor.red + textColor.green + textColor.blue < 128 * 3) { 549 // dark text on light outline 550 rgb_color glowColor = kWhite; 551 552 font.SetFalseBoldWidth(2.0); 553 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 554 glowColor.alpha = 30; 555 drawView->SetHighColor(glowColor); 556 557 drawView->DrawString(fittingText, loc); 558 559 font.SetFalseBoldWidth(1.0); 560 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 561 glowColor.alpha = 65; 562 drawView->SetHighColor(glowColor); 563 564 drawView->DrawString(fittingText, loc); 565 566 font.SetFalseBoldWidth(0.0); 567 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 568 } else { 569 // light text on dark outline 570 rgb_color outlineColor = kBlack; 571 572 font.SetFalseBoldWidth(1.0); 573 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 574 outlineColor.alpha = 30; 575 drawView->SetHighColor(outlineColor); 576 577 drawView->DrawString(fittingText, loc); 578 579 font.SetFalseBoldWidth(0.0); 580 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 581 582 outlineColor.alpha = 200; 583 drawView->SetHighColor(outlineColor); 584 585 drawView->DrawString(fittingText, loc + BPoint(1, 1)); 586 } 587 588 drawView->SetDrawingMode(B_OP_OVER); 589 drawView->SetHighColor(textColor); 590 } 591 #endif // __HAIKU__ 592 593 drawView->DrawString(fittingText, loc); 594 595 if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) { 596 // ToDo: 597 // this should be exported to the WidgetAttribute class, probably 598 // by having a per widget kind style 599 if (direct) { 600 rgb_color underlineColor = drawView->HighColor(); 601 underlineColor.alpha = 180; 602 drawView->SetHighColor(underlineColor); 603 drawView->SetDrawingMode(B_OP_ALPHA); 604 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 605 } 606 607 textRect.right = textRect.left + fText->Width(view); 608 // only underline text part 609 drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(), 610 B_MIXED_COLORS); 611 } 612 } 613