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 BEntry entry(pose->TargetModel()->EntryRef()); 322 if (entry.InitCheck() == B_OK && FSIsTrashDir(&entry)) 323 return; 324 325 // don't allow editing of the "Disks" icon name 326 if (pose->TargetModel()->IsRoot()) 327 return; 328 329 if (!ConfirmChangeIfWellKnownDirectory(&entry, "rename")) 330 return; 331 332 // get bounds with full text length 333 BRect rect(bounds); 334 BRect textRect(bounds); 335 rect.OffsetBy(-2, -1); 336 rect.right += 1; 337 338 BFont font; 339 view->GetFont(&font); 340 BTextView *textView = new BTextView(rect, "WidgetTextView", textRect, &font, 0, 341 B_FOLLOW_ALL, B_WILL_DRAW); 342 343 textView->SetWordWrap(false); 344 DisallowMetaKeys(textView); 345 fText->SetUpEditing(textView); 346 347 textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewFilter)); 348 349 rect.right = rect.left + textView->LineWidth() + 3; 350 // center new width, if necessary 351 if (view->ViewMode() == kIconMode 352 || view->ViewMode() == kListMode && fAlignment == B_ALIGN_CENTER) { 353 rect.OffsetBy(bounds.Width() / 2 - rect.Width() / 2, 0); 354 } 355 356 rect.bottom = rect.top + textView->LineHeight() + 1; 357 textRect = rect.OffsetToCopy(2, 1); 358 textRect.right -= 3; 359 textRect.bottom--; 360 textView->SetTextRect(textRect); 361 362 textRect = view->Bounds(); 363 bool hitBorder = false; 364 if (rect.left < 1) 365 rect.left = 1, hitBorder = true; 366 if (rect.right > textRect.right) 367 rect.right = textRect.right - 2, hitBorder = true; 368 369 textView->MoveTo(rect.LeftTop()); 370 textView->ResizeTo(rect.Width(), rect.Height()); 371 372 BScrollView *scrollView = new BScrollView("BorderView", textView, 0, 0, false, 373 false, B_PLAIN_BORDER); 374 view->AddChild(scrollView); 375 376 // configure text view 377 switch (view->ViewMode()) { 378 case kIconMode: 379 textView->SetAlignment(B_ALIGN_CENTER); 380 break; 381 382 case kMiniIconMode: 383 textView->SetAlignment(B_ALIGN_LEFT); 384 break; 385 386 case kListMode: 387 textView->SetAlignment(fAlignment); 388 break; 389 } 390 textView->MakeResizable(true, hitBorder ? NULL : scrollView); 391 392 view->SetActivePose(pose); // tell view about pose 393 SetActive(true); // for widget 394 395 textView->SelectAll(); 396 textView->MakeFocus(); 397 398 // make this text widget invisible while we edit it 399 SetVisible(false); 400 401 ASSERT(view->Window()); // how can I not have a Window here??? 402 403 if (view->Window()) 404 // force immediate redraw so TextView appears instantly 405 view->Window()->UpdateIfNeeded(); 406 } 407 408 409 void 410 BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView *view, 411 BPose *pose, int32 poseIndex) 412 { 413 // find the text editing view 414 BView *scrollView = view->FindView("BorderView"); 415 ASSERT(scrollView); 416 if (!scrollView) 417 return; 418 419 BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView")); 420 ASSERT(textView); 421 if (!textView) 422 return; 423 424 BColumn *column = view->ColumnFor(fAttrHash); 425 ASSERT(column); 426 if (!column) 427 return; 428 429 if (saveChanges && fText->CommitEditedText(textView)) { 430 // we have an actual change, re-sort 431 view->CheckPoseSortOrder(pose, poseIndex); 432 } 433 434 // make text widget visible again 435 SetVisible(true); 436 view->Invalidate(ColumnRect(poseLoc, column, view)); 437 438 // force immediate redraw so TEView disappears 439 scrollView->RemoveSelf(); 440 delete scrollView; 441 442 ASSERT(view->Window()); 443 view->Window()->UpdateIfNeeded(); 444 view->MakeFocus(); 445 446 SetActive(false); 447 } 448 449 450 void 451 BTextWidget::CheckAndUpdate(BPoint loc, const BColumn *column, BPoseView *view) 452 { 453 BRect oldRect; 454 if (view->ViewMode() != kListMode) 455 oldRect = CalcOldRect(loc, column, view); 456 457 if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view)) { 458 BRect invalRect(ColumnRect(loc, column, view)); 459 if (view->ViewMode() != kListMode) 460 invalRect = invalRect | oldRect; 461 view->Invalidate(invalRect); 462 } 463 } 464 465 466 void 467 BTextWidget::SelectAll(BPoseView *view) 468 { 469 BTextView *text = dynamic_cast<BTextView *>(view->FindView("WidgetTextView")); 470 if (text) 471 text->SelectAll(); 472 } 473 474 475 void 476 BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView *view, 477 BView *drawView, bool selected, uint32 clipboardMode, BPoint offset, bool direct) 478 { 479 textRect.OffsetBy(offset); 480 481 if (direct) { 482 #ifdef __HAIKU__ 483 // draw selection box if selected 484 if (selected) { 485 #else 486 // erase area we're going to draw in 487 // NOTE: WidgetTextOutline() is reused for 488 // erasing background on R5 here 489 if (view->WidgetTextOutline() || selected) { 490 #endif 491 drawView->SetDrawingMode(B_OP_COPY); 492 eraseRect.OffsetBy(offset); 493 // drawView->FillRect(eraseRect, B_SOLID_LOW); 494 drawView->FillRect(textRect, B_SOLID_LOW); 495 } else 496 drawView->SetDrawingMode(B_OP_OVER); 497 498 // set high color 499 rgb_color highColor; 500 if (view->IsDesktopWindow()) { 501 if (selected) 502 highColor = kWhite; 503 else 504 highColor = view->DeskTextColor(); 505 } else if (selected && view->Window()->IsActive()) { 506 highColor = kWhite; 507 } else 508 highColor = kBlack; 509 510 if (clipboardMode == kMoveSelectionTo && !selected) { 511 drawView->SetDrawingMode(B_OP_ALPHA); 512 drawView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 513 highColor.alpha = 64; 514 } 515 drawView->SetHighColor(highColor); 516 } 517 518 BPoint loc; 519 loc.y = textRect.bottom - view->FontInfo().descent; 520 loc.x = textRect.left + 1; 521 522 const char* fittingText = fText->FittingText(view); 523 524 #ifdef __HAIKU__ 525 // TODO: Comparing view and drawView here to avoid rendering 526 // the text outline when producing a drag bitmap. The check is 527 // not fully correct, since an offscreen view is also used in some 528 // other rare cases (something to do with columns). But for now, this 529 // fixes the broken drag bitmaps when dragging icons from the Desktop. 530 if (!selected && view == drawView && view->WidgetTextOutline()) { 531 // draw a halo around the text by using the "false bold" 532 // feature for text rendering. Either black or white is used for 533 // the glow (whatever acts as contrast) with a some alpha value, 534 drawView->SetDrawingMode(B_OP_ALPHA); 535 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 536 537 BFont font; 538 drawView->GetFont(&font); 539 540 rgb_color textColor = drawView->HighColor(); 541 if (textColor.red + textColor.green + textColor.blue < 128 * 3) { 542 // dark text on light outline 543 rgb_color glowColor = kWhite; 544 545 font.SetFalseBoldWidth(2.0); 546 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 547 glowColor.alpha = 30; 548 drawView->SetHighColor(glowColor); 549 550 drawView->DrawString(fittingText, loc); 551 552 font.SetFalseBoldWidth(1.0); 553 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 554 glowColor.alpha = 65; 555 drawView->SetHighColor(glowColor); 556 557 drawView->DrawString(fittingText, loc); 558 559 font.SetFalseBoldWidth(0.0); 560 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 561 } else { 562 // light text on dark outline 563 rgb_color outlineColor = kBlack; 564 565 font.SetFalseBoldWidth(1.0); 566 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 567 outlineColor.alpha = 30; 568 drawView->SetHighColor(outlineColor); 569 570 drawView->DrawString(fittingText, loc); 571 572 font.SetFalseBoldWidth(0.0); 573 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 574 575 outlineColor.alpha = 200; 576 drawView->SetHighColor(outlineColor); 577 578 drawView->DrawString(fittingText, loc + BPoint(1, 1)); 579 } 580 581 drawView->SetDrawingMode(B_OP_OVER); 582 drawView->SetHighColor(textColor); 583 } 584 #endif // __HAIKU__ 585 586 drawView->DrawString(fittingText, loc); 587 588 if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) { 589 // ToDo: 590 // this should be exported to the WidgetAttribute class, probably 591 // by having a per widget kind style 592 if (direct) { 593 rgb_color underlineColor = drawView->HighColor(); 594 underlineColor.alpha = 180; 595 drawView->SetHighColor(underlineColor); 596 drawView->SetDrawingMode(B_OP_ALPHA); 597 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 598 } 599 600 textRect.right = textRect.left + fText->Width(view); 601 // only underline text part 602 drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(), 603 B_MIXED_COLORS); 604 } 605 } 606