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 void 87 BTextWidget::RecalculateText(const BPoseView *view) 88 { 89 fText->SetDirty(true); 90 fText->CheckViewChanged(view); 91 } 92 93 94 const char * 95 BTextWidget::Text() const 96 { 97 StringAttributeText *textAttribute = dynamic_cast<StringAttributeText *>(fText); 98 99 ASSERT(textAttribute); 100 if (!textAttribute) 101 return ""; 102 103 return textAttribute->Value(); 104 } 105 106 107 float 108 BTextWidget::TextWidth(const BPoseView *pose) const 109 { 110 return fText->Width(pose); 111 } 112 113 114 float 115 BTextWidget::PreferredWidth(const BPoseView *pose) const 116 { 117 return fText->PreferredWidth(pose) + 1; 118 } 119 120 121 BRect 122 BTextWidget::ColumnRect(BPoint poseLoc, const BColumn *column, 123 const BPoseView *view) 124 { 125 if (view->ViewMode() != kListMode) { 126 // ColumnRect only makes sense in list view, return 127 // CalcRect otherwise 128 return CalcRect(poseLoc, column, view); 129 } 130 BRect result; 131 result.left = column->Offset() + poseLoc.x; 132 result.right = result.left + column->Width(); 133 result.bottom = poseLoc.y + view->ListElemHeight() - 1; 134 result.top = result.bottom - view->FontHeight(); 135 return result; 136 } 137 138 139 BRect 140 BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn *column, 141 const BPoseView *view, float textWidth) 142 { 143 BRect result; 144 if (view->ViewMode() == kListMode) { 145 poseLoc.x += column->Offset(); 146 147 switch (fAlignment) { 148 case B_ALIGN_LEFT: 149 result.left = poseLoc.x; 150 result.right = result.left + textWidth + 1; 151 break; 152 153 case B_ALIGN_CENTER: 154 result.left = poseLoc.x + (column->Width() / 2) - (textWidth / 2); 155 if (result.left < 0) 156 result.left = 0; 157 result.right = result.left + textWidth + 1; 158 break; 159 160 case B_ALIGN_RIGHT: 161 result.right = poseLoc.x + column->Width(); 162 result.left = result.right - textWidth - 1; 163 if (result.left < 0) 164 result.left = 0; 165 break; 166 default: 167 TRESPASS(); 168 } 169 170 result.bottom = poseLoc.y + (view->ListElemHeight() - 1); 171 } else { 172 if (view->ViewMode() == kIconMode 173 || view->ViewMode() == kScaleIconMode) { 174 // large/scaled icon mode 175 result.left = poseLoc.x + (view->IconSizeInt() - textWidth) / 2; 176 } else { 177 // mini icon mode 178 result.left = poseLoc.x + B_MINI_ICON + kMiniIconSeparator; 179 } 180 181 result.right = result.left + textWidth; 182 result.bottom = poseLoc.y + view->IconPoseHeight(); 183 184 } 185 result.top = result.bottom - view->FontHeight(); 186 187 return result; 188 } 189 190 191 BRect 192 BTextWidget::CalcRect(BPoint poseLoc, const BColumn *column, 193 const BPoseView *view) 194 { 195 return CalcRectCommon(poseLoc, column, view, fText->Width(view)); 196 } 197 198 199 BRect 200 BTextWidget::CalcOldRect(BPoint poseLoc, const BColumn *column, 201 const BPoseView *view) 202 { 203 return CalcRectCommon(poseLoc, column, view, fText->CurrentWidth()); 204 } 205 206 207 BRect 208 BTextWidget::CalcClickRect(BPoint poseLoc, const BColumn *column, 209 const BPoseView* view) 210 { 211 BRect result = CalcRect(poseLoc, column, view); 212 if (result.Width() < kWidthMargin) { 213 // if resulting rect too narrow, make it a bit wider 214 // for comfortable clicking 215 if (column && column->Width() < kWidthMargin) 216 result.right = result.left + column->Width(); 217 else 218 result.right = result.left + kWidthMargin; 219 } 220 return result; 221 } 222 223 224 void 225 BTextWidget::MouseUp(BRect bounds, BPoseView *view, BPose *pose, BPoint, 226 bool delayedEdit) 227 { 228 // wait until a double click time to see if we are double clicking 229 // or selecting widget for editing 230 // start editing early if mouse left widget or modifier down 231 232 if (!IsEditable()) 233 return; 234 235 if (delayedEdit) { 236 bigtime_t doubleClickTime; 237 get_click_speed(&doubleClickTime); 238 doubleClickTime += system_time(); 239 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 if (buttons) 247 // if mouse button goes down then a double click, exit 248 // without editing 249 return; 250 251 if (!bounds.Contains(point)) 252 // mouse has moved outside of text widget so go into edit mode 253 break; 254 255 if (modifiers() & (B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY | B_MENU_KEY)) 256 // watch the keyboard (ignoring standard locking keys) 257 break; 258 259 snooze(100000); 260 } 261 } 262 263 StartEdit(bounds, view, pose); 264 } 265 266 267 static filter_result 268 TextViewFilter(BMessage *message, BHandler **, BMessageFilter *filter) 269 { 270 uchar key; 271 if (message->FindInt8("byte", (int8 *)&key) != B_OK) 272 return B_DISPATCH_MESSAGE; 273 274 BPoseView *poseView = dynamic_cast<BContainerWindow*>(filter->Looper())-> 275 PoseView(); 276 277 if (key == B_RETURN || key == B_ESCAPE) { 278 poseView->CommitActivePose(key == B_RETURN); 279 return B_SKIP_MESSAGE; 280 } 281 282 if (key == B_TAB) { 283 if (poseView->ActivePose()) { 284 if (message->FindInt32("modifiers") & B_SHIFT_KEY) 285 poseView->ActivePose()->EditPreviousWidget(poseView); 286 else 287 poseView->ActivePose()->EditNextWidget(poseView); 288 } 289 290 return B_SKIP_MESSAGE; 291 } 292 293 // the BTextView doesn't respect window borders when resizing itself; 294 // we try to work-around this "bug" here. 295 296 // find the text editing view 297 BView *scrollView = poseView->FindView("BorderView"); 298 if (scrollView != NULL) { 299 BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView")); 300 if (textView != NULL) { 301 BRect rect = scrollView->Frame(); 302 303 if (rect.right + 3 > poseView->Bounds().right 304 || rect.left - 3 < 0) 305 textView->MakeResizable(true, NULL); 306 } 307 } 308 309 return B_DISPATCH_MESSAGE; 310 } 311 312 313 void 314 BTextWidget::StartEdit(BRect bounds, BPoseView *view, BPose *pose) 315 { 316 if (!IsEditable()) 317 return; 318 319 // don't allow editing of the trash directory name 320 BEntry entry(pose->TargetModel()->EntryRef()); 321 if (entry.InitCheck() == B_OK && FSIsTrashDir(&entry)) 322 return; 323 324 // don't allow editing of the "Disks" icon name 325 if (pose->TargetModel()->IsRoot()) 326 return; 327 328 if (!ConfirmChangeIfWellKnownDirectory(&entry, "rename")) 329 return; 330 331 // get bounds with full text length 332 BRect rect(bounds); 333 BRect textRect(bounds); 334 rect.OffsetBy(-2, -1); 335 rect.right += 1; 336 337 BFont font; 338 view->GetFont(&font); 339 BTextView *textView = new BTextView(rect, "WidgetTextView", textRect, &font, 0, 340 B_FOLLOW_ALL, B_WILL_DRAW); 341 342 textView->SetWordWrap(false); 343 DisallowMetaKeys(textView); 344 fText->SetUpEditing(textView); 345 346 textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewFilter)); 347 348 rect.right = rect.left + textView->LineWidth() + 3; 349 // center new width, if necessary 350 if (view->ViewMode() == kIconMode 351 || view->ViewMode() == kScaleIconMode 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 case kScaleIconMode: 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 { 454 BRect oldRect; 455 if (view->ViewMode() != kListMode) 456 oldRect = CalcOldRect(loc, column, view); 457 458 if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view)) { 459 BRect invalRect(ColumnRect(loc, column, view)); 460 if (view->ViewMode() != kListMode) 461 invalRect = invalRect | oldRect; 462 view->Invalidate(invalRect); 463 } 464 } 465 466 467 void 468 BTextWidget::SelectAll(BPoseView *view) 469 { 470 BTextView *text = dynamic_cast<BTextView *>(view->FindView("WidgetTextView")); 471 if (text) 472 text->SelectAll(); 473 } 474 475 476 void 477 BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView *view, 478 BView *drawView, bool selected, uint32 clipboardMode, BPoint offset, bool direct) 479 { 480 textRect.OffsetBy(offset); 481 482 if (direct) { 483 #ifdef __HAIKU__ 484 // draw selection box if selected 485 if (selected) { 486 #else 487 // erase area we're going to draw in 488 // NOTE: WidgetTextOutline() is reused for 489 // erasing background on R5 here 490 if (view->WidgetTextOutline() || selected) { 491 #endif 492 drawView->SetDrawingMode(B_OP_COPY); 493 eraseRect.OffsetBy(offset); 494 // drawView->FillRect(eraseRect, B_SOLID_LOW); 495 drawView->FillRect(textRect, B_SOLID_LOW); 496 } else 497 drawView->SetDrawingMode(B_OP_OVER); 498 499 // set high color 500 rgb_color highColor; 501 if (view->IsDesktopWindow()) { 502 if (selected) 503 highColor = kWhite; 504 else 505 highColor = view->DeskTextColor(); 506 } else if (selected && view->Window()->IsActive()) { 507 highColor = kWhite; 508 } else 509 highColor = kBlack; 510 511 if (clipboardMode == kMoveSelectionTo && !selected) { 512 drawView->SetDrawingMode(B_OP_ALPHA); 513 drawView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 514 highColor.alpha = 64; 515 } 516 drawView->SetHighColor(highColor); 517 } 518 519 BPoint loc; 520 loc.y = textRect.bottom - view->FontInfo().descent; 521 loc.x = textRect.left + 1; 522 523 const char* fittingText = fText->FittingText(view); 524 525 #ifdef __HAIKU__ 526 if (!selected && view->WidgetTextOutline()) { 527 // draw a halo around the text by using the "false bold" 528 // feature for text rendering. Either black or white is used for 529 // the glow (whatever acts as contrast) with a some alpha value, 530 // two passes are used to achive a blur effect 531 drawView->SetDrawingMode(B_OP_ALPHA); 532 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 533 534 BFont font; 535 drawView->GetFont(&font); 536 // NOTE: commented out first pass for halo, since just a plain 537 // outline looks better IMHO -stippi 538 // font.SetFalseBoldWidth(2.0); 539 // drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 540 rgb_color textColor = drawView->HighColor(); 541 rgb_color glow = textColor.red 542 + textColor.green + textColor.blue > 128 * 3 ? kBlack : kWhite; 543 // glow.alpha = 40; 544 // drawView->SetHighColor(glow); 545 546 // drawView->DrawString(fittingText, loc); 547 548 font.SetFalseBoldWidth(1.0); 549 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 550 glow.alpha = 220; 551 drawView->SetHighColor(glow); 552 553 drawView->DrawString(fittingText, loc); 554 555 font.SetFalseBoldWidth(0.0); 556 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 557 drawView->SetHighColor(textColor); 558 } 559 #endif // __HAIKU__ 560 561 drawView->DrawString(fittingText, loc); 562 563 if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) { 564 // ToDo: 565 // this should be exported to the WidgetAttribute class, probably 566 // by having a per widget kind style 567 if (direct) { 568 rgb_color underlineColor = drawView->HighColor(); 569 underlineColor.alpha = 180; 570 drawView->SetHighColor(underlineColor); 571 drawView->SetDrawingMode(B_OP_ALPHA); 572 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 573 } 574 575 textRect.right = textRect.left + fText->Width(view); 576 // only underline text part 577 drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(), 578 B_MIXED_COLORS); 579 } 580 } 581