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 result.left = poseLoc.x + (B_LARGE_ICON - textWidth) / 2; 174 else 175 // MINI_ICON_MODE rect calc 176 result.left = poseLoc.x + B_MINI_ICON + kMiniIconSeparator; 177 178 result.right = result.left + textWidth; 179 result.bottom = poseLoc.y + view->IconPoseHeight(); 180 181 } 182 result.top = result.bottom - view->FontHeight(); 183 184 return result; 185 } 186 187 188 BRect 189 BTextWidget::CalcRect(BPoint poseLoc, const BColumn *column, 190 const BPoseView *view) 191 { 192 return CalcRectCommon(poseLoc, column, view, fText->Width(view)); 193 } 194 195 196 BRect 197 BTextWidget::CalcOldRect(BPoint poseLoc, const BColumn *column, 198 const BPoseView *view) 199 { 200 return CalcRectCommon(poseLoc, column, view, fText->CurrentWidth()); 201 } 202 203 204 BRect 205 BTextWidget::CalcClickRect(BPoint poseLoc, const BColumn *column, 206 const BPoseView* view) 207 { 208 BRect result = CalcRect(poseLoc, column, view); 209 if (result.Width() < kWidthMargin) { 210 // if resulting rect too narrow, make it a bit wider 211 // for comfortable clicking 212 if (column && column->Width() < kWidthMargin) 213 result.right = result.left + column->Width(); 214 else 215 result.right = result.left + kWidthMargin; 216 } 217 return result; 218 } 219 220 221 void 222 BTextWidget::MouseUp(BRect bounds, BPoseView *view, BPose *pose, BPoint, 223 bool delayedEdit) 224 { 225 // wait until a double click time to see if we are double clicking 226 // or selecting widget for editing 227 // start editing early if mouse left widget or modifier down 228 229 if (!IsEditable()) 230 return; 231 232 if (delayedEdit) { 233 bigtime_t doubleClickTime; 234 get_click_speed(&doubleClickTime); 235 doubleClickTime += system_time(); 236 237 while (system_time() < doubleClickTime) { 238 // loop for double-click time and watch the mouse and keyboard 239 240 BPoint point; 241 uint32 buttons; 242 view->GetMouse(&point, &buttons, false); 243 if (buttons) 244 // if mouse button goes down then a double click, exit 245 // without editing 246 return; 247 248 if (!bounds.Contains(point)) 249 // mouse has moved outside of text widget so go into edit mode 250 break; 251 252 if (modifiers() & (B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY | B_MENU_KEY)) 253 // watch the keyboard (ignoring standard locking keys) 254 break; 255 256 snooze(100000); 257 } 258 } 259 260 StartEdit(bounds, view, pose); 261 } 262 263 264 static filter_result 265 TextViewFilter(BMessage *message, BHandler **, BMessageFilter *filter) 266 { 267 uchar key; 268 if (message->FindInt8("byte", (int8 *)&key) != B_OK) 269 return B_DISPATCH_MESSAGE; 270 271 BPoseView *poseView = dynamic_cast<BContainerWindow*>(filter->Looper())-> 272 PoseView(); 273 274 if (key == B_RETURN || key == B_ESCAPE) { 275 poseView->CommitActivePose(key == B_RETURN); 276 return B_SKIP_MESSAGE; 277 } 278 279 if (key == B_TAB) { 280 if (poseView->ActivePose()) { 281 if (message->FindInt32("modifiers") & B_SHIFT_KEY) 282 poseView->ActivePose()->EditPreviousWidget(poseView); 283 else 284 poseView->ActivePose()->EditNextWidget(poseView); 285 } 286 287 return B_SKIP_MESSAGE; 288 } 289 290 // the BTextView doesn't respect window borders when resizing itself; 291 // we try to work-around this "bug" here. 292 293 // find the text editing view 294 BView *scrollView = poseView->FindView("BorderView"); 295 if (scrollView != NULL) { 296 BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView")); 297 if (textView != NULL) { 298 BRect rect = scrollView->Frame(); 299 300 if (rect.right + 3 > poseView->Bounds().right 301 || rect.left - 3 < 0) 302 textView->MakeResizable(true, NULL); 303 } 304 } 305 306 return B_DISPATCH_MESSAGE; 307 } 308 309 310 void 311 BTextWidget::StartEdit(BRect bounds, BPoseView *view, BPose *pose) 312 { 313 if (!IsEditable()) 314 return; 315 316 // don't allow editing of the trash directory name 317 BEntry entry(pose->TargetModel()->EntryRef()); 318 if (entry.InitCheck() == B_OK && FSIsTrashDir(&entry)) 319 return; 320 321 // don't allow editing of the "Disks" icon name 322 if (pose->TargetModel()->IsRoot()) 323 return; 324 325 if (!ConfirmChangeIfWellKnownDirectory(&entry, "rename")) 326 return; 327 328 // get bounds with full text length 329 BRect rect(bounds); 330 BRect textRect(bounds); 331 rect.OffsetBy(-2, -1); 332 rect.right += 1; 333 334 BFont font; 335 view->GetFont(&font); 336 BTextView *textView = new BTextView(rect, "WidgetTextView", textRect, &font, 0, 337 B_FOLLOW_ALL, B_WILL_DRAW); 338 339 textView->SetWordWrap(false); 340 DisallowMetaKeys(textView); 341 fText->SetUpEditing(textView); 342 343 textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewFilter)); 344 345 rect.right = rect.left + textView->LineWidth() + 3; 346 // center new width, if necessary 347 if (view->ViewMode() == kIconMode 348 || view->ViewMode() == kListMode && fAlignment == B_ALIGN_CENTER) 349 rect.OffsetBy(bounds.Width() / 2 - rect.Width() / 2, 0); 350 351 rect.bottom = rect.top + textView->LineHeight() + 1; 352 textRect = rect.OffsetToCopy(2, 1); 353 textRect.right -= 3; 354 textRect.bottom--; 355 textView->SetTextRect(textRect); 356 357 textRect = view->Bounds(); 358 bool hitBorder = false; 359 if (rect.left < 1) 360 rect.left = 1, hitBorder = true; 361 if (rect.right > textRect.right) 362 rect.right = textRect.right - 2, hitBorder = true; 363 364 textView->MoveTo(rect.LeftTop()); 365 textView->ResizeTo(rect.Width(), rect.Height()); 366 367 BScrollView *scrollView = new BScrollView("BorderView", textView, 0, 0, false, 368 false, B_PLAIN_BORDER); 369 view->AddChild(scrollView); 370 371 // configure text view 372 switch (view->ViewMode()) { 373 case kIconMode: 374 textView->SetAlignment(B_ALIGN_CENTER); 375 break; 376 377 case kMiniIconMode: 378 textView->SetAlignment(B_ALIGN_LEFT); 379 break; 380 381 case kListMode: 382 textView->SetAlignment(fAlignment); 383 break; 384 } 385 textView->MakeResizable(true, hitBorder ? NULL : scrollView); 386 387 view->SetActivePose(pose); // tell view about pose 388 SetActive(true); // for widget 389 390 textView->SelectAll(); 391 textView->MakeFocus(); 392 393 // make this text widget invisible while we edit it 394 SetVisible(false); 395 396 ASSERT(view->Window()); // how can I not have a Window here??? 397 398 if (view->Window()) 399 // force immediate redraw so TextView appears instantly 400 view->Window()->UpdateIfNeeded(); 401 } 402 403 404 void 405 BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView *view, 406 BPose *pose, int32 poseIndex) 407 { 408 // find the text editing view 409 BView *scrollView = view->FindView("BorderView"); 410 ASSERT(scrollView); 411 if (!scrollView) 412 return; 413 414 BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView")); 415 ASSERT(textView); 416 if (!textView) 417 return; 418 419 BColumn *column = view->ColumnFor(fAttrHash); 420 ASSERT(column); 421 if (!column) 422 return; 423 424 if (saveChanges && fText->CommitEditedText(textView)) { 425 // we have an actual change, re-sort 426 view->CheckPoseSortOrder(pose, poseIndex); 427 } 428 429 // make text widget visible again 430 SetVisible(true); 431 view->Invalidate(ColumnRect(poseLoc, column, view)); 432 433 // force immediate redraw so TEView disappears 434 scrollView->RemoveSelf(); 435 delete scrollView; 436 437 ASSERT(view->Window()); 438 view->Window()->UpdateIfNeeded(); 439 view->MakeFocus(); 440 441 SetActive(false); 442 } 443 444 445 void 446 BTextWidget::CheckAndUpdate(BPoint loc, const BColumn *column, BPoseView *view) 447 { 448 BRect oldRect; 449 if (view->ViewMode() != kListMode) 450 oldRect = CalcOldRect(loc, column, view); 451 452 if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view)) { 453 BRect invalRect(ColumnRect(loc, column, view)); 454 if (view->ViewMode() != kListMode) 455 invalRect = invalRect | oldRect; 456 view->Invalidate(invalRect); 457 } 458 } 459 460 461 void 462 BTextWidget::SelectAll(BPoseView *view) 463 { 464 BTextView *text = dynamic_cast<BTextView *>(view->FindView("WidgetTextView")); 465 if (text) 466 text->SelectAll(); 467 } 468 469 470 void 471 BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView *view, 472 BView *drawView, bool selected, uint32 clipboardMode, BPoint offset, bool direct) 473 { 474 if (direct) { 475 // erase area we're going to draw in 476 if (view->EraseWidgetTextBackground() || selected) { 477 drawView->SetDrawingMode(B_OP_COPY); 478 eraseRect.OffsetBy(offset); 479 drawView->FillRect(eraseRect, B_SOLID_LOW); 480 } else 481 drawView->SetDrawingMode(B_OP_OVER); 482 483 // set high color 484 rgb_color highColor; 485 if (view->IsDesktopWindow()) { 486 if (selected) 487 highColor = kWhite; 488 else 489 highColor = view->DeskTextColor(); 490 } else if (selected && view->Window()->IsActive() && !view->EraseWidgetTextBackground()) { 491 highColor = kWhite; 492 } else 493 highColor = kBlack; 494 495 if (clipboardMode == kMoveSelectionTo && !selected) { 496 view->SetDrawingMode(B_OP_ALPHA); 497 view->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 498 highColor.alpha = 64; 499 } 500 drawView->SetHighColor(highColor); 501 } 502 503 BPoint loc; 504 textRect.OffsetBy(offset); 505 506 loc.y = textRect.bottom - view->FontInfo().descent; 507 loc.x = textRect.left + 1; 508 509 drawView->MovePenTo(loc); 510 drawView->DrawString(fText->FittingText(view)); 511 512 if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) { 513 // ToDo: 514 // this should be exported to the WidgetAttribute class, probably 515 // by having a per widget kind style 516 if (direct) 517 drawView->SetHighColor(125, 125, 125); 518 519 textRect.right = textRect.left + fText->Width(view); 520 // only underline text part 521 drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(), 522 B_MIXED_COLORS); 523 } 524 } 525