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 bool delayedEdit) 216 { 217 // wait until a double click time to see if we are double clicking 218 // or selecting widget for editing 219 // start editing early if mouse left widget or modifier down 220 221 if (!IsEditable()) 222 return; 223 224 if (delayedEdit) { 225 bigtime_t doubleClickTime; 226 get_click_speed(&doubleClickTime); 227 doubleClickTime += system_time(); 228 229 while (system_time() < doubleClickTime) { 230 // loop for double-click time and watch the mouse and keyboard 231 232 BPoint point; 233 uint32 buttons; 234 view->GetMouse(&point, &buttons, false); 235 if (buttons) 236 // if mouse button goes down then a double click, exit 237 // without editing 238 return; 239 240 if (!bounds.Contains(point)) 241 // mouse has moved outside of text widget so go into edit mode 242 break; 243 244 if (modifiers() & (B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY | B_MENU_KEY)) 245 // watch the keyboard (ignoring standard locking keys) 246 break; 247 248 snooze(100000); 249 } 250 } 251 252 StartEdit(bounds, view, pose); 253 } 254 255 256 static filter_result 257 TextViewFilter(BMessage *message, BHandler **, BMessageFilter *filter) 258 { 259 uchar key; 260 if (message->FindInt8("byte", (int8 *)&key) != B_OK) 261 return B_DISPATCH_MESSAGE; 262 263 BPoseView *poseView = dynamic_cast<BContainerWindow*>(filter->Looper())-> 264 PoseView(); 265 266 if (key == B_RETURN || key == B_ESCAPE) { 267 poseView->CommitActivePose(key == B_RETURN); 268 return B_SKIP_MESSAGE; 269 } 270 271 if (key == B_TAB) { 272 if (poseView->ActivePose()) { 273 if (message->FindInt32("modifiers") & B_SHIFT_KEY) 274 poseView->ActivePose()->EditPreviousWidget(poseView); 275 else 276 poseView->ActivePose()->EditNextWidget(poseView); 277 } 278 279 return B_SKIP_MESSAGE; 280 } 281 282 // the BTextView doesn't respect window borders when resizing itself; 283 // we try to work-around this "bug" here. 284 285 // find the text editing view 286 BView *scrollView = poseView->FindView("BorderView"); 287 if (scrollView != NULL) { 288 BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView")); 289 if (textView != NULL) { 290 BRect rect = scrollView->Frame(); 291 292 if (rect.right + 3 > poseView->Bounds().right 293 || rect.left - 3 < 0) 294 textView->MakeResizable(true, NULL); 295 } 296 } 297 298 return B_DISPATCH_MESSAGE; 299 } 300 301 302 void 303 BTextWidget::StartEdit(BRect bounds, BPoseView *view, BPose *pose) 304 { 305 if (!IsEditable()) 306 return; 307 308 // don't allow editing of the trash directory name 309 BEntry entry(pose->TargetModel()->EntryRef()); 310 if (entry.InitCheck() == B_OK && FSIsTrashDir(&entry)) 311 return; 312 313 // don't allow editing of the "Disks" icon name 314 if (pose->TargetModel()->IsRoot()) 315 return; 316 317 if (!ConfirmChangeIfWellKnownDirectory(&entry, "rename")) 318 return; 319 320 // get bounds with full text length 321 BRect rect(bounds); 322 BRect textRect(bounds); 323 rect.OffsetBy(-2, -1); 324 rect.right += 1; 325 326 BFont font; 327 view->GetFont(&font); 328 BTextView *textView = new BTextView(rect, "WidgetTextView", textRect, &font, 0, 329 B_FOLLOW_ALL, B_WILL_DRAW); 330 331 textView->SetWordWrap(false); 332 DisallowMetaKeys(textView); 333 fText->SetUpEditing(textView); 334 335 textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewFilter)); 336 337 rect.right = rect.left + textView->LineWidth() + 3; 338 // center new width, if necessary 339 if (view->ViewMode() == kIconMode 340 || view->ViewMode() == kListMode && fAlignment == B_ALIGN_CENTER) { 341 rect.OffsetBy(bounds.Width() / 2 - rect.Width() / 2, 0); 342 } 343 344 rect.bottom = rect.top + textView->LineHeight() + 1; 345 textRect = rect.OffsetToCopy(2, 1); 346 textRect.right -= 3; 347 textRect.bottom--; 348 textView->SetTextRect(textRect); 349 350 textRect = view->Bounds(); 351 bool hitBorder = false; 352 if (rect.left < 1) 353 rect.left = 1, hitBorder = true; 354 if (rect.right > textRect.right) 355 rect.right = textRect.right - 2, hitBorder = true; 356 357 textView->MoveTo(rect.LeftTop()); 358 textView->ResizeTo(rect.Width(), rect.Height()); 359 360 BScrollView *scrollView = new BScrollView("BorderView", textView, 0, 0, false, 361 false, B_PLAIN_BORDER); 362 view->AddChild(scrollView); 363 364 // configure text view 365 switch (view->ViewMode()) { 366 case kIconMode: 367 textView->SetAlignment(B_ALIGN_CENTER); 368 break; 369 370 case kMiniIconMode: 371 textView->SetAlignment(B_ALIGN_LEFT); 372 break; 373 374 case kListMode: 375 textView->SetAlignment(fAlignment); 376 break; 377 } 378 textView->MakeResizable(true, hitBorder ? NULL : scrollView); 379 380 view->SetActivePose(pose); // tell view about pose 381 SetActive(true); // for widget 382 383 textView->SelectAll(); 384 textView->MakeFocus(); 385 386 // make this text widget invisible while we edit it 387 SetVisible(false); 388 389 ASSERT(view->Window()); // how can I not have a Window here??? 390 391 if (view->Window()) 392 // force immediate redraw so TextView appears instantly 393 view->Window()->UpdateIfNeeded(); 394 } 395 396 397 void 398 BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView *view, 399 BPose *pose, int32 poseIndex) 400 { 401 // find the text editing view 402 BView *scrollView = view->FindView("BorderView"); 403 ASSERT(scrollView); 404 if (!scrollView) 405 return; 406 407 BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView")); 408 ASSERT(textView); 409 if (!textView) 410 return; 411 412 BColumn *column = view->ColumnFor(fAttrHash); 413 ASSERT(column); 414 if (!column) 415 return; 416 417 if (saveChanges && fText->CommitEditedText(textView)) { 418 // we have an actual change, re-sort 419 view->CheckPoseSortOrder(pose, poseIndex); 420 } 421 422 // make text widget visible again 423 SetVisible(true); 424 view->Invalidate(ColumnRect(poseLoc, column, view)); 425 426 // force immediate redraw so TEView disappears 427 scrollView->RemoveSelf(); 428 delete scrollView; 429 430 ASSERT(view->Window()); 431 view->Window()->UpdateIfNeeded(); 432 view->MakeFocus(); 433 434 SetActive(false); 435 } 436 437 438 void 439 BTextWidget::CheckAndUpdate(BPoint loc, const BColumn *column, BPoseView *view) 440 { 441 BRect oldRect; 442 if (view->ViewMode() != kListMode) 443 oldRect = CalcOldRect(loc, column, view); 444 445 if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view)) { 446 BRect invalRect(ColumnRect(loc, column, view)); 447 if (view->ViewMode() != kListMode) 448 invalRect = invalRect | oldRect; 449 view->Invalidate(invalRect); 450 } 451 } 452 453 454 void 455 BTextWidget::SelectAll(BPoseView *view) 456 { 457 BTextView *text = dynamic_cast<BTextView *>(view->FindView("WidgetTextView")); 458 if (text) 459 text->SelectAll(); 460 } 461 462 463 void 464 BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView *view, 465 BView *drawView, bool selected, uint32 clipboardMode, BPoint offset, bool direct) 466 { 467 textRect.OffsetBy(offset); 468 469 if (direct) { 470 #ifdef __HAIKU__ 471 // draw selection box if selected 472 if (selected) { 473 #else 474 // erase area we're going to draw in 475 // NOTE: WidgetTextOutline() is reused for 476 // erasing background on R5 here 477 if (view->WidgetTextOutline() || selected) { 478 #endif 479 drawView->SetDrawingMode(B_OP_COPY); 480 eraseRect.OffsetBy(offset); 481 // drawView->FillRect(eraseRect, B_SOLID_LOW); 482 drawView->FillRect(textRect, B_SOLID_LOW); 483 } else 484 drawView->SetDrawingMode(B_OP_OVER); 485 486 // set high color 487 rgb_color highColor; 488 if (view->IsDesktopWindow()) { 489 if (selected) 490 highColor = kWhite; 491 else 492 highColor = view->DeskTextColor(); 493 } else if (selected && view->Window()->IsActive()) { 494 highColor = kWhite; 495 } else 496 highColor = kBlack; 497 498 if (clipboardMode == kMoveSelectionTo && !selected) { 499 drawView->SetDrawingMode(B_OP_ALPHA); 500 drawView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 501 highColor.alpha = 64; 502 } 503 drawView->SetHighColor(highColor); 504 } 505 506 BPoint loc; 507 loc.y = textRect.bottom - view->FontInfo().descent; 508 loc.x = textRect.left + 1; 509 510 const char* fittingText = fText->FittingText(view); 511 512 #ifdef __HAIKU__ 513 // TODO: Comparing view and drawView here to avoid rendering 514 // the text outline when producing a drag bitmap. The check is 515 // not fully correct, since an offscreen view is also used in some 516 // other rare cases (something to do with columns). But for now, this 517 // fixes the broken drag bitmaps when dragging icons from the Desktop. 518 if (!selected && view == drawView && view->WidgetTextOutline()) { 519 // draw a halo around the text by using the "false bold" 520 // feature for text rendering. Either black or white is used for 521 // the glow (whatever acts as contrast) with a some alpha value, 522 drawView->SetDrawingMode(B_OP_ALPHA); 523 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 524 525 BFont font; 526 drawView->GetFont(&font); 527 528 rgb_color textColor = drawView->HighColor(); 529 if (textColor.red + textColor.green + textColor.blue < 128 * 3) { 530 // dark text on light outline 531 rgb_color glowColor = kWhite; 532 533 font.SetFalseBoldWidth(2.0); 534 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 535 glowColor.alpha = 30; 536 drawView->SetHighColor(glowColor); 537 538 drawView->DrawString(fittingText, loc); 539 540 font.SetFalseBoldWidth(1.0); 541 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 542 glowColor.alpha = 65; 543 drawView->SetHighColor(glowColor); 544 545 drawView->DrawString(fittingText, loc); 546 547 font.SetFalseBoldWidth(0.0); 548 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 549 } else { 550 // light text on dark outline 551 rgb_color outlineColor = kBlack; 552 553 font.SetFalseBoldWidth(1.0); 554 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 555 outlineColor.alpha = 30; 556 drawView->SetHighColor(outlineColor); 557 558 drawView->DrawString(fittingText, loc); 559 560 font.SetFalseBoldWidth(0.0); 561 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 562 563 outlineColor.alpha = 200; 564 drawView->SetHighColor(outlineColor); 565 566 drawView->DrawString(fittingText, loc + BPoint(1, 1)); 567 } 568 569 drawView->SetDrawingMode(B_OP_OVER); 570 drawView->SetHighColor(textColor); 571 } 572 #endif // __HAIKU__ 573 574 drawView->DrawString(fittingText, loc); 575 576 if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) { 577 // ToDo: 578 // this should be exported to the WidgetAttribute class, probably 579 // by having a per widget kind style 580 if (direct) { 581 rgb_color underlineColor = drawView->HighColor(); 582 underlineColor.alpha = 180; 583 drawView->SetHighColor(underlineColor); 584 drawView->SetDrawingMode(B_OP_ALPHA); 585 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 586 } 587 588 textRect.right = textRect.left + fText->Width(view); 589 // only underline text part 590 drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(), 591 B_MIXED_COLORS); 592 } 593 } 594