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