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 36 #include "WidgetAttributeText.h" 37 38 #include <ctype.h> 39 #include <stdlib.h> 40 #include <strings.h> 41 42 #include <fs_attr.h> 43 #include <parsedate.h> 44 45 #include <Alert.h> 46 #include <AppFileInfo.h> 47 #include <Catalog.h> 48 #include <DateFormat.h> 49 #include <DateTimeFormat.h> 50 #include <Debug.h> 51 #include <Locale.h> 52 #include <MessageFormat.h> 53 #include <NodeInfo.h> 54 #include <Path.h> 55 #include <TextView.h> 56 #include <Volume.h> 57 #include <VolumeRoster.h> 58 59 #include "Attributes.h" 60 #include "FindPanel.h" 61 #include "FSUndoRedo.h" 62 #include "FSUtils.h" 63 #include "Model.h" 64 #include "OpenWithWindow.h" 65 #include "MimeTypes.h" 66 #include "PoseView.h" 67 #include "SettingsViews.h" 68 #include "Utilities.h" 69 #include "ViewState.h" 70 71 72 #undef B_TRANSLATION_CONTEXT 73 #define B_TRANSLATION_CONTEXT "WidgetAttributeText" 74 75 76 const int32 kGenericReadBufferSize = 1024; 77 78 const char* kSizeFormats[] = { 79 "%.2f %s", 80 "%.1f %s", 81 "%.f %s", 82 "%.f%s", 83 0 84 }; 85 86 87 bool NameAttributeText::sSortFolderNamesFirst = false; 88 bool RealNameAttributeText::sSortFolderNamesFirst = false; 89 90 91 template <class View> 92 float 93 TruncFileSizeBase(BString* outString, int64 value, const View* view, 94 float width) 95 { 96 // ToDo: If slow, replace float divisions with shifts 97 // if fast enough, try fitting more decimal places. 98 99 // ToDo: Update string_for_size() in libshared to be able to 100 // handle this case. 101 102 BString buffer; 103 104 // format file size value 105 if (value == kUnknownSize) { 106 *outString = "-"; 107 return view->StringWidth("-"); 108 } else if (value < kKBSize) { 109 static BMessageFormat format(B_TRANSLATE( 110 "{0, plural, one{# byte} other{# bytes}}")); 111 format.Format(buffer, value); 112 if (view->StringWidth(buffer.String()) > width) 113 buffer.SetToFormat(B_TRANSLATE("%Ld B"), value); 114 } else { 115 const char* suffix; 116 float doubleValue; 117 if (value >= kTBSize) { 118 suffix = B_TRANSLATE("TiB"); 119 doubleValue = (double)value / kTBSize; 120 } else if (value >= kGBSize) { 121 suffix = B_TRANSLATE("GiB"); 122 doubleValue = (double)value / kGBSize; 123 } else if (value >= kMBSize) { 124 suffix = B_TRANSLATE("MiB"); 125 doubleValue = (double)value / kMBSize; 126 } else { 127 ASSERT(value >= kKBSize); 128 suffix = B_TRANSLATE("KiB"); 129 doubleValue = (double)value / kKBSize; 130 } 131 132 for (int32 index = 0; ; index++) { 133 if (kSizeFormats[index] == 0) 134 break; 135 136 buffer.SetToFormat(kSizeFormats[index], doubleValue, suffix); 137 // strip off an insignificant zero so we don't get readings 138 // such as 1.00 139 char* period = 0; 140 for (char* tmp = const_cast<char*>(buffer.String()); *tmp != '\0'; 141 tmp++) { 142 if (*tmp == '.') 143 period = tmp; 144 } 145 if (period && period[1] && period[2] == '0') 146 // move the rest of the string over the insignificant zero 147 for (char* tmp = &period[2]; *tmp; tmp++) 148 *tmp = tmp[1]; 149 150 float resultWidth = view->StringWidth(buffer); 151 if (resultWidth <= width) { 152 *outString = buffer.String(); 153 return resultWidth; 154 } 155 } 156 } 157 158 return TruncStringBase(outString, buffer.String(), buffer.Length(), view, 159 width, (uint32)B_TRUNCATE_END); 160 } 161 162 163 template <class View> 164 float 165 TruncStringBase(BString* outString, const char* inString, int32 length, 166 const View* view, float width, uint32 truncMode = B_TRUNCATE_MIDDLE) 167 { 168 // we are using a template version of this call to make sure 169 // the right StringWidth gets picked up for BView x BPoseView 170 // for max speed and flexibility 171 172 // a standard ellipsis inserting fitting algorithm 173 if (view->StringWidth(inString, length) <= width) 174 *outString = inString; 175 else { 176 const char* source[1]; 177 char* results[1]; 178 179 source[0] = inString; 180 results[0] = outString->LockBuffer(length + 3); 181 182 BFont font; 183 view->GetFont(&font); 184 185 font.GetTruncatedStrings(source, 1, truncMode, width, results); 186 outString->UnlockBuffer(); 187 } 188 189 return view->StringWidth(outString->String(), outString->Length()); 190 } 191 192 193 template <class View> 194 float 195 TruncTimeBase(BString* outString, int64 value, const View* view, float width) 196 { 197 float resultWidth = width + 1; 198 199 time_t timeValue = (time_t)value; 200 201 struct { 202 BDateFormatStyle dateStyle; 203 BTimeFormatStyle timeStyle; 204 } formats[5] = { 205 { B_FULL_DATE_FORMAT, B_MEDIUM_TIME_FORMAT }, 206 { B_LONG_DATE_FORMAT, B_MEDIUM_TIME_FORMAT }, 207 { B_LONG_DATE_FORMAT, B_SHORT_TIME_FORMAT }, 208 { B_MEDIUM_DATE_FORMAT, B_SHORT_TIME_FORMAT }, 209 { B_SHORT_DATE_FORMAT, B_SHORT_TIME_FORMAT }, 210 }; 211 212 BString date; 213 BDateTimeFormat formatter; 214 for (int i = 0; resultWidth > width && i < 5; ++i) { 215 if (formatter.Format(date, timeValue, formats[i].dateStyle, 216 formats[i].timeStyle) == B_OK) { 217 resultWidth = view->StringWidth(date.String(), date.Length()); 218 } 219 } 220 221 if (resultWidth > width 222 && BDateFormat().Format(date, timeValue, 223 B_SHORT_DATE_FORMAT) == B_OK) { 224 resultWidth = view->StringWidth(date.String(), date.Length()); 225 } 226 227 if (resultWidth > width) { 228 // even the shortest format string didn't do it, insert ellipsis 229 resultWidth = TruncStringBase(outString, date.String(), 230 (ssize_t)date.Length(), view, width); 231 } else 232 *outString = date; 233 234 return resultWidth; 235 } 236 237 238 // #pragma mark - WidgetAttributeText base class 239 240 241 WidgetAttributeText* 242 WidgetAttributeText::NewWidgetText(const Model* model, 243 const BColumn* column, const BPoseView* view) 244 { 245 // call this to make the right WidgetAttributeText type for a 246 // given column 247 248 const char* attrName = column->AttrName(); 249 250 if (strcmp(attrName, kAttrPath) == 0) 251 return new PathAttributeText(model, column); 252 253 if (strcmp(attrName, kAttrMIMEType) == 0) 254 return new KindAttributeText(model, column); 255 256 if (strcmp(attrName, kAttrStatName) == 0) 257 return new NameAttributeText(model, column); 258 259 if (strcmp(attrName, kAttrRealName) == 0) 260 return new RealNameAttributeText(model, column); 261 262 if (strcmp(attrName, kAttrStatSize) == 0) 263 return new SizeAttributeText(model, column); 264 265 if (strcmp(attrName, kAttrStatModified) == 0) 266 return new ModificationTimeAttributeText(model, column); 267 268 if (strcmp(attrName, kAttrStatCreated) == 0) 269 return new CreationTimeAttributeText(model, column); 270 271 #ifdef OWNER_GROUP_ATTRIBUTES 272 if (strcmp(attrName, kAttrStatOwner) == 0) 273 return new OwnerAttributeText(model, column); 274 275 if (strcmp(attrName, kAttrStatGroup) == 0) 276 return new GroupAttributeText(model, column); 277 #endif 278 if (strcmp(attrName, kAttrStatMode) == 0) 279 return new ModeAttributeText(model, column); 280 281 if (strcmp(attrName, kAttrOpenWithRelation) == 0) 282 return new OpenWithRelationAttributeText(model, column, view); 283 284 if (strcmp(attrName, kAttrAppVersion) == 0) 285 return new AppShortVersionAttributeText(model, column); 286 287 if (strcmp(attrName, kAttrSystemVersion) == 0) 288 return new SystemShortVersionAttributeText(model, column); 289 290 if (strcmp(attrName, kAttrOriginalPath) == 0) 291 return new OriginalPathAttributeText(model, column); 292 293 if (column->DisplayAs() != NULL) { 294 if (!strncmp(column->DisplayAs(), "checkbox", 8)) 295 return new CheckboxAttributeText(model, column); 296 297 if (!strncmp(column->DisplayAs(), "duration", 8)) 298 return new DurationAttributeText(model, column); 299 300 if (!strncmp(column->DisplayAs(), "rating", 6)) 301 return new RatingAttributeText(model, column); 302 } 303 304 return new GenericAttributeText(model, column); 305 } 306 307 308 WidgetAttributeText::WidgetAttributeText(const Model* model, 309 const BColumn* column) 310 : 311 fModel(const_cast<Model*>(model)), 312 fColumn(column), 313 fOldWidth(-1.0f), 314 fTruncatedWidth(-1.0f), 315 fDirty(true), 316 fValueIsDefined(false) 317 { 318 ASSERT(fColumn != NULL); 319 320 if (fColumn == NULL) 321 return; 322 323 ASSERT(fColumn->Width() > 0); 324 } 325 326 327 WidgetAttributeText::~WidgetAttributeText() 328 { 329 } 330 331 332 const char* 333 WidgetAttributeText::FittingText(const BPoseView* view) 334 { 335 if (fDirty || fColumn->Width() != fOldWidth || CheckSettingsChanged() 336 || !fValueIsDefined) { 337 CheckViewChanged(view); 338 } 339 340 ASSERT(!fDirty); 341 return fText.String(); 342 } 343 344 345 bool 346 WidgetAttributeText::CheckViewChanged(const BPoseView* view) 347 { 348 BString newText; 349 FitValue(&newText, view); 350 if (newText == fText) 351 return false; 352 353 fText = newText; 354 return true; 355 } 356 357 358 bool 359 WidgetAttributeText::CheckSettingsChanged() 360 { 361 return false; 362 } 363 364 365 float 366 WidgetAttributeText::TruncString(BString* outString, const char* inString, 367 int32 length, const BPoseView* view, float width, uint32 truncMode) 368 { 369 return TruncStringBase(outString, inString, length, view, width, truncMode); 370 } 371 372 373 float 374 WidgetAttributeText::TruncFileSize(BString* outString, int64 value, 375 const BPoseView* view, float width) 376 { 377 return TruncFileSizeBase(outString, value, view, width); 378 } 379 380 381 float 382 WidgetAttributeText::TruncTime(BString* outString, int64 value, 383 const BPoseView* view, float width) 384 { 385 return TruncTimeBase(outString, value, view, width); 386 } 387 388 389 float 390 WidgetAttributeText::CurrentWidth() const 391 { 392 return fTruncatedWidth; 393 } 394 395 396 float 397 WidgetAttributeText::Width(const BPoseView* pose) 398 { 399 FittingText(pose); 400 return CurrentWidth(); 401 } 402 403 404 void 405 WidgetAttributeText::SetUpEditing(BTextView*) 406 { 407 ASSERT(fColumn->Editable()); 408 } 409 410 411 bool 412 WidgetAttributeText::CommitEditedText(BTextView*) 413 { 414 // can't do anything here at this point 415 TRESPASS(); 416 return false; 417 } 418 419 420 status_t 421 WidgetAttributeText::AttrAsString(const Model* model, BString* outString, 422 const char* attrName, int32 attrType, float width, BView* view, 423 int64* resultingValue) 424 { 425 int64 value; 426 427 status_t error = model->InitCheck(); 428 if (error != B_OK) 429 return error; 430 431 switch (attrType) { 432 case B_TIME_TYPE: 433 if (strcmp(attrName, kAttrStatModified) == 0) 434 value = model->StatBuf()->st_mtime; 435 else if (strcmp(attrName, kAttrStatCreated) == 0) 436 value = model->StatBuf()->st_crtime; 437 else { 438 TRESPASS(); 439 // not yet supported 440 return B_ERROR; 441 } 442 TruncTimeBase(outString, value, view, width); 443 if (resultingValue) 444 *resultingValue = value; 445 446 return B_OK; 447 448 case B_STRING_TYPE: 449 if (strcmp(attrName, kAttrPath) == 0) { 450 BEntry entry(model->EntryRef()); 451 BPath path; 452 BString tmp; 453 454 if (entry.InitCheck() == B_OK 455 && entry.GetPath(&path) == B_OK) { 456 tmp = path.Path(); 457 TruncateLeaf(&tmp); 458 } else 459 tmp = "-"; 460 461 if (width > 0) { 462 TruncStringBase(outString, tmp.String(), tmp.Length(), view, 463 width); 464 } else 465 *outString = tmp.String(); 466 467 return B_OK; 468 } 469 break; 470 471 case kSizeType: 472 // TruncFileSizeBase(outString, model->StatBuf()->st_size, view, 473 // width); 474 return B_OK; 475 break; 476 477 default: 478 TRESPASS(); 479 // not yet supported 480 return B_ERROR; 481 482 } 483 484 TRESPASS(); 485 return B_ERROR; 486 } 487 488 489 bool 490 WidgetAttributeText::IsEditable() const 491 { 492 return fColumn->Editable() 493 && !BVolume(fModel->StatBuf()->st_dev).IsReadOnly(); 494 } 495 496 497 void 498 WidgetAttributeText::SetDirty(bool value) 499 { 500 fDirty = value; 501 } 502 503 504 // #pragma mark - StringAttributeText 505 506 507 StringAttributeText::StringAttributeText(const Model* model, 508 const BColumn* column) 509 : 510 WidgetAttributeText(model, column), 511 fValueDirty(true) 512 { 513 } 514 515 516 const char* 517 StringAttributeText::ValueAsText(const BPoseView* /*view*/) 518 { 519 if (fValueDirty) 520 ReadValue(&fFullValueText); 521 522 return fFullValueText.String(); 523 } 524 525 526 bool 527 StringAttributeText::CheckAttributeChanged() 528 { 529 BString newString; 530 ReadValue(&newString); 531 532 if (newString == fFullValueText) 533 return false; 534 535 fFullValueText = newString; 536 fDirty = true; // have to redo fitted string 537 return true; 538 } 539 540 541 void 542 StringAttributeText::FitValue(BString* outString, const BPoseView* view) 543 { 544 if (fValueDirty) 545 ReadValue(&fFullValueText); 546 fOldWidth = fColumn->Width(); 547 548 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 549 fFullValueText.Length(), view, fOldWidth); 550 fDirty = false; 551 } 552 553 554 float 555 StringAttributeText::PreferredWidth(const BPoseView* pose) const 556 { 557 return pose->StringWidth(fFullValueText.String()); 558 } 559 560 561 int 562 StringAttributeText::Compare(WidgetAttributeText& attr, BPoseView* view) 563 { 564 StringAttributeText* compareTo = dynamic_cast<StringAttributeText*>(&attr); 565 ThrowOnAssert(compareTo != NULL); 566 567 if (fValueDirty) 568 ReadValue(&fFullValueText); 569 570 return NaturalCompare(fFullValueText.String(), 571 compareTo->ValueAsText(view)); 572 } 573 574 575 bool 576 StringAttributeText::CommitEditedText(BTextView* textView) 577 { 578 ASSERT(fColumn->Editable()); 579 const char* text = textView->Text(); 580 581 if (fFullValueText == text) { 582 // no change 583 return false; 584 } 585 586 if (textView->TextLength() == 0) { 587 // cannot do an empty name 588 return false; 589 } 590 591 // cause re-truncation 592 fDirty = true; 593 594 if (!CommitEditedTextFlavor(textView)) 595 return false; 596 597 // update text and width in this widget 598 fFullValueText = text; 599 600 return true; 601 } 602 603 604 // #pragma mark - ScalarAttributeText 605 606 607 ScalarAttributeText::ScalarAttributeText(const Model* model, 608 const BColumn* column) 609 : 610 WidgetAttributeText(model, column), 611 fValue(0), 612 fValueDirty(true) 613 { 614 } 615 616 617 int64 618 ScalarAttributeText::Value() 619 { 620 if (fValueDirty) 621 fValue = ReadValue(); 622 623 return fValue; 624 } 625 626 627 bool 628 ScalarAttributeText::CheckAttributeChanged() 629 { 630 int64 newValue = ReadValue(); 631 if (newValue == fValue) 632 return false; 633 634 fValue = newValue; 635 fDirty = true; 636 // have to redo fitted string 637 638 return true; 639 } 640 641 642 float 643 ScalarAttributeText::PreferredWidth(const BPoseView* pose) const 644 { 645 BString widthString; 646 widthString << fValue; 647 return pose->StringWidth(widthString.String()); 648 } 649 650 651 int 652 ScalarAttributeText::Compare(WidgetAttributeText& attr, BPoseView*) 653 { 654 ScalarAttributeText* compareTo = dynamic_cast<ScalarAttributeText*>(&attr); 655 ThrowOnAssert(compareTo != NULL); 656 657 if (fValueDirty) 658 fValue = ReadValue(); 659 660 return fValue >= compareTo->Value() 661 ? (fValue == compareTo->Value() ? 0 : 1) : -1; 662 } 663 664 665 // #pragma mark - PathAttributeText 666 667 668 PathAttributeText::PathAttributeText(const Model* model, const BColumn* column) 669 : 670 StringAttributeText(model, column) 671 { 672 } 673 674 675 void 676 PathAttributeText::ReadValue(BString* outString) 677 { 678 // get the path 679 BEntry entry(fModel->EntryRef()); 680 BPath path; 681 682 if (entry.InitCheck() == B_OK && entry.GetPath(&path) == B_OK) { 683 *outString = path.Path(); 684 TruncateLeaf(outString); 685 } else 686 *outString = "-"; 687 688 fValueDirty = false; 689 } 690 691 692 // #pragma mark - OriginalPathAttributeText 693 694 695 OriginalPathAttributeText::OriginalPathAttributeText(const Model* model, 696 const BColumn* column) 697 : 698 StringAttributeText(model, column) 699 { 700 } 701 702 703 void 704 OriginalPathAttributeText::ReadValue(BString* outString) 705 { 706 BEntry entry(fModel->EntryRef()); 707 BPath path; 708 709 // get the original path 710 if (entry.InitCheck() == B_OK && FSGetOriginalPath(&entry, &path) == B_OK) 711 *outString = path.Path(); 712 else 713 *outString = "-"; 714 715 fValueDirty = false; 716 } 717 718 719 // #pragma mark - KindAttributeText 720 721 722 KindAttributeText::KindAttributeText(const Model* model, const BColumn* column) 723 : 724 StringAttributeText(model, column) 725 { 726 } 727 728 729 void 730 KindAttributeText::ReadValue(BString* outString) 731 { 732 BMimeType mime; 733 char desc[B_MIME_TYPE_LENGTH]; 734 735 // get the mime type 736 if (mime.SetType(fModel->MimeType()) != B_OK) 737 *outString = B_TRANSLATE("Unknown"); 738 else if (mime.GetShortDescription(desc) == B_OK) { 739 // get the short mime type description 740 *outString = desc; 741 } else 742 *outString = fModel->MimeType(); 743 744 fValueDirty = false; 745 } 746 747 748 // #pragma mark - NameAttributeText 749 750 751 NameAttributeText::NameAttributeText(const Model* model, 752 const BColumn* column) 753 : 754 StringAttributeText(model, column) 755 { 756 } 757 758 759 int 760 NameAttributeText::Compare(WidgetAttributeText& attr, BPoseView* view) 761 { 762 NameAttributeText* compareTo = dynamic_cast<NameAttributeText*>(&attr); 763 ThrowOnAssert(compareTo != NULL); 764 765 if (fValueDirty) 766 ReadValue(&fFullValueText); 767 768 if (NameAttributeText::sSortFolderNamesFirst) 769 return fModel->CompareFolderNamesFirst(attr.TargetModel()); 770 771 return NaturalCompare(fFullValueText.String(), 772 compareTo->ValueAsText(view)); 773 } 774 775 776 void 777 NameAttributeText::ReadValue(BString* outString) 778 { 779 *outString = fModel->Name(); 780 781 fValueDirty = false; 782 } 783 784 785 void 786 NameAttributeText::FitValue(BString* outString, const BPoseView* view) 787 { 788 if (fValueDirty) 789 ReadValue(&fFullValueText); 790 fOldWidth = fColumn->Width(); 791 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 792 fFullValueText.Length(), view, fOldWidth, B_TRUNCATE_END); 793 fDirty = false; 794 } 795 796 797 void 798 NameAttributeText::SetUpEditing(BTextView* textView) 799 { 800 DisallowFilenameKeys(textView); 801 802 textView->SetMaxBytes(B_FILE_NAME_LENGTH); 803 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 804 } 805 806 807 bool 808 NameAttributeText::CommitEditedTextFlavor(BTextView* textView) 809 { 810 const char* text = textView->Text(); 811 812 BEntry entry(fModel->EntryRef()); 813 if (entry.InitCheck() != B_OK) 814 return false; 815 816 BDirectory parent; 817 if (entry.GetParent(&parent) != B_OK) 818 return false; 819 820 bool removeExisting = false; 821 if (parent.Contains(text)) { 822 BAlert* alert = new BAlert("", 823 B_TRANSLATE("That name is already taken. " 824 "Please type another one."), 825 B_TRANSLATE("Replace other file"), 826 B_TRANSLATE("OK"), 827 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 828 alert->SetShortcut(0, 'r'); 829 if (alert->Go()) 830 return false; 831 832 removeExisting = true; 833 } 834 835 // TODO: 836 // use model-flavor specific virtuals for all of these special 837 // renamings 838 status_t result; 839 if (fModel->IsVolume()) { 840 BVolume volume(fModel->NodeRef()->device); 841 result = volume.InitCheck(); 842 if (result == B_OK) { 843 RenameVolumeUndo undo(volume, text); 844 845 result = volume.SetName(text); 846 if (result != B_OK) 847 undo.Remove(); 848 } 849 } else { 850 if (fModel->IsQuery()) { 851 BModelWriteOpener opener(fModel); 852 ASSERT(fModel->Node()); 853 MoreOptionsStruct::SetQueryTemporary(fModel->Node(), false); 854 } 855 856 RenameUndo undo(entry, text); 857 858 result = entry.Rename(text, removeExisting); 859 if (result != B_OK) 860 undo.Remove(); 861 } 862 863 return result == B_OK; 864 } 865 866 867 void 868 NameAttributeText::SetSortFolderNamesFirst(bool enabled) 869 { 870 NameAttributeText::sSortFolderNamesFirst = enabled; 871 } 872 873 874 bool 875 NameAttributeText::IsEditable() const 876 { 877 return StringAttributeText::IsEditable() 878 && !fModel->HasLocalizedName(); 879 } 880 881 882 // #pragma mark - RealNameAttributeText 883 884 885 RealNameAttributeText::RealNameAttributeText(const Model* model, 886 const BColumn* column) 887 : 888 StringAttributeText(model, column) 889 { 890 } 891 892 893 int 894 RealNameAttributeText::Compare(WidgetAttributeText& attr, BPoseView* view) 895 { 896 RealNameAttributeText* compareTo 897 = dynamic_cast<RealNameAttributeText*>(&attr); 898 ThrowOnAssert(compareTo != NULL); 899 900 if (fValueDirty) 901 ReadValue(&fFullValueText); 902 903 if (RealNameAttributeText::sSortFolderNamesFirst) 904 return fModel->CompareFolderNamesFirst(attr.TargetModel()); 905 906 return NaturalCompare(fFullValueText.String(), 907 compareTo->ValueAsText(view)); 908 } 909 910 911 void 912 RealNameAttributeText::ReadValue(BString* outString) 913 { 914 *outString = fModel->EntryRef()->name; 915 916 fValueDirty = false; 917 } 918 919 920 void 921 RealNameAttributeText::FitValue(BString* outString, const BPoseView* view) 922 { 923 if (fValueDirty) 924 ReadValue(&fFullValueText); 925 fOldWidth = fColumn->Width(); 926 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 927 fFullValueText.Length(), view, fOldWidth, B_TRUNCATE_END); 928 fDirty = false; 929 } 930 931 932 void 933 RealNameAttributeText::SetUpEditing(BTextView* textView) 934 { 935 DisallowFilenameKeys(textView); 936 937 textView->SetMaxBytes(B_FILE_NAME_LENGTH); 938 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 939 } 940 941 942 bool 943 RealNameAttributeText::CommitEditedTextFlavor(BTextView* textView) 944 { 945 const char* text = textView->Text(); 946 947 BEntry entry(fModel->EntryRef()); 948 if (entry.InitCheck() != B_OK) 949 return false; 950 951 BDirectory parent; 952 if (entry.GetParent(&parent) != B_OK) 953 return false; 954 955 bool removeExisting = false; 956 if (parent.Contains(text)) { 957 BAlert* alert = new BAlert("", 958 B_TRANSLATE("That name is already taken. " 959 "Please type another one."), 960 B_TRANSLATE("Replace other file"), 961 B_TRANSLATE("OK"), 962 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 963 964 alert->SetShortcut(0, 'r'); 965 966 if (alert->Go()) 967 return false; 968 969 removeExisting = true; 970 } 971 972 // TODO: 973 // use model-flavor specific virtuals for all of these special 974 // renamings 975 status_t result; 976 if (fModel->IsVolume()) { 977 BVolume volume(fModel->NodeRef()->device); 978 result = volume.InitCheck(); 979 if (result == B_OK) { 980 RenameVolumeUndo undo(volume, text); 981 982 result = volume.SetName(text); 983 if (result != B_OK) 984 undo.Remove(); 985 } 986 } else { 987 if (fModel->IsQuery()) { 988 BModelWriteOpener opener(fModel); 989 ASSERT(fModel->Node()); 990 MoreOptionsStruct::SetQueryTemporary(fModel->Node(), false); 991 } 992 993 RenameUndo undo(entry, text); 994 995 result = entry.Rename(text, removeExisting); 996 if (result != B_OK) 997 undo.Remove(); 998 } 999 1000 return result == B_OK; 1001 } 1002 1003 1004 void 1005 RealNameAttributeText::SetSortFolderNamesFirst(bool enabled) 1006 { 1007 RealNameAttributeText::sSortFolderNamesFirst = enabled; 1008 } 1009 1010 1011 // #pragma mark - owner/group 1012 1013 1014 #ifdef OWNER_GROUP_ATTRIBUTES 1015 OwnerAttributeText::OwnerAttributeText(const Model* model, 1016 const BColumn* column) 1017 : 1018 StringAttributeText(model, column) 1019 { 1020 } 1021 1022 1023 void 1024 OwnerAttributeText::ReadValue(BString* outString) 1025 { 1026 uid_t nodeOwner = fModel->StatBuf()->st_uid; 1027 BString user; 1028 1029 if (nodeOwner == 0) { 1030 if (getenv("USER") != NULL) 1031 user << getenv("USER"); 1032 else 1033 user << "root"; 1034 } else 1035 user << nodeOwner; 1036 *outString = user.String(); 1037 1038 fValueDirty = false; 1039 } 1040 1041 1042 GroupAttributeText::GroupAttributeText(const Model* model, 1043 const BColumn* column) 1044 : 1045 StringAttributeText(model, column) 1046 { 1047 } 1048 1049 1050 void 1051 GroupAttributeText::ReadValue(BString* outString) 1052 { 1053 gid_t nodeGroup = fModel->StatBuf()->st_gid; 1054 BString group; 1055 1056 if (nodeGroup == 0) { 1057 if (getenv("GROUP") != NULL) 1058 group << getenv("GROUP"); 1059 else 1060 group << "0"; 1061 } else 1062 group << nodeGroup; 1063 *outString = group.String(); 1064 1065 fValueDirty = false; 1066 } 1067 #endif // OWNER_GROUP_ATTRIBUTES 1068 1069 1070 // #pragma mark - ModeAttributeText 1071 1072 1073 ModeAttributeText::ModeAttributeText(const Model* model, 1074 const BColumn* column) 1075 : 1076 StringAttributeText(model, column) 1077 { 1078 } 1079 1080 1081 void 1082 ModeAttributeText::ReadValue(BString* outString) 1083 { 1084 mode_t mode = fModel->StatBuf()->st_mode; 1085 mode_t baseMask = 00400; 1086 char buffer[11]; 1087 1088 char* scanner = buffer; 1089 1090 if (S_ISDIR(mode)) 1091 *scanner++ = 'd'; 1092 else if (S_ISLNK(mode)) 1093 *scanner++ = 'l'; 1094 else if (S_ISBLK(mode)) 1095 *scanner++ = 'b'; 1096 else if (S_ISCHR(mode)) 1097 *scanner++ = 'c'; 1098 else 1099 *scanner++ = '-'; 1100 1101 for (int32 index = 0; index < 9; index++) { 1102 *scanner++ = (mode & baseMask) ? "rwx"[index % 3] : '-'; 1103 baseMask >>= 1; 1104 } 1105 1106 *scanner = 0; 1107 *outString = buffer; 1108 1109 fValueDirty = false; 1110 } 1111 1112 1113 // #pragma mark - SizeAttributeText 1114 1115 1116 SizeAttributeText::SizeAttributeText(const Model* model, 1117 const BColumn* column) 1118 : 1119 ScalarAttributeText(model, column) 1120 { 1121 } 1122 1123 1124 int64 1125 SizeAttributeText::ReadValue() 1126 { 1127 fValueDirty = false; 1128 // get the size 1129 1130 if (fModel->IsVolume()) { 1131 BVolume volume(fModel->NodeRef()->device); 1132 1133 return volume.Capacity(); 1134 } 1135 1136 if (fModel->IsDirectory() || fModel->IsQuery() 1137 || fModel->IsQueryTemplate() || fModel->IsSymLink() 1138 || fModel->IsVirtualDirectory()) { 1139 return kUnknownSize; 1140 } 1141 1142 fValueIsDefined = true; 1143 1144 return fModel->StatBuf()->st_size; 1145 } 1146 1147 1148 void 1149 SizeAttributeText::FitValue(BString* outString, const BPoseView* view) 1150 { 1151 if (fValueDirty) 1152 fValue = ReadValue(); 1153 1154 fOldWidth = fColumn->Width(); 1155 fTruncatedWidth = TruncFileSize(outString, fValue, view, fOldWidth); 1156 fDirty = false; 1157 } 1158 1159 1160 float 1161 SizeAttributeText::PreferredWidth(const BPoseView* pose) const 1162 { 1163 if (fValueIsDefined) { 1164 BString widthString; 1165 TruncFileSize(&widthString, fValue, pose, 100000); 1166 return pose->StringWidth(widthString.String()); 1167 } 1168 1169 return pose->StringWidth("-"); 1170 } 1171 1172 1173 // #pragma mark - TimeAttributeText 1174 1175 1176 TimeAttributeText::TimeAttributeText(const Model* model, 1177 const BColumn* column) 1178 : 1179 ScalarAttributeText(model, column), 1180 fLastClockIs24(false), 1181 fLastDateOrder(kDateFormatEnd), 1182 fLastTimeFormatSeparator(kSeparatorsEnd) 1183 { 1184 } 1185 1186 1187 float 1188 TimeAttributeText::PreferredWidth(const BPoseView* pose) const 1189 { 1190 BString widthString; 1191 TruncTimeBase(&widthString, fValue, pose, 100000); 1192 return pose->StringWidth(widthString.String()); 1193 } 1194 1195 1196 void 1197 TimeAttributeText::FitValue(BString* outString, const BPoseView* view) 1198 { 1199 if (fValueDirty) 1200 fValue = ReadValue(); 1201 1202 fOldWidth = fColumn->Width(); 1203 fTruncatedWidth = TruncTime(outString, fValue, view, fOldWidth); 1204 fDirty = false; 1205 } 1206 1207 1208 bool 1209 TimeAttributeText::CheckSettingsChanged(void) 1210 { 1211 // TODO : check against the actual locale settings 1212 return false; 1213 } 1214 1215 1216 // #pragma mark - CreationTimeAttributeText 1217 1218 1219 CreationTimeAttributeText::CreationTimeAttributeText(const Model* model, 1220 const BColumn* column) 1221 : 1222 TimeAttributeText(model, column) 1223 { 1224 } 1225 1226 1227 int64 1228 CreationTimeAttributeText::ReadValue() 1229 { 1230 fValueDirty = false; 1231 fValueIsDefined = true; 1232 return fModel->StatBuf()->st_crtime; 1233 } 1234 1235 1236 // #pragma mark - ModificationTimeAttributeText 1237 1238 1239 ModificationTimeAttributeText::ModificationTimeAttributeText( 1240 const Model* model, const BColumn* column) 1241 : 1242 TimeAttributeText(model, column) 1243 { 1244 } 1245 1246 1247 int64 1248 ModificationTimeAttributeText::ReadValue() 1249 { 1250 fValueDirty = false; 1251 fValueIsDefined = true; 1252 return fModel->StatBuf()->st_mtime; 1253 } 1254 1255 1256 // #pragma mark - GenericAttributeText 1257 1258 1259 GenericAttributeText::GenericAttributeText(const Model* model, 1260 const BColumn* column) 1261 : 1262 StringAttributeText(model, column) 1263 { 1264 } 1265 1266 1267 bool 1268 GenericAttributeText::CheckAttributeChanged() 1269 { 1270 GenericValueStruct tmpValue = fValue; 1271 BString tmpString(fFullValueText); 1272 ReadValue(&fFullValueText); 1273 1274 // fDirty could already be true, in that case we mustn't set it to 1275 // false, even if the attribute text hasn't changed 1276 bool changed = fValue.int64t != tmpValue.int64t 1277 || tmpString != fFullValueText; 1278 if (changed) 1279 fDirty = true; 1280 1281 return fDirty; 1282 } 1283 1284 1285 float 1286 GenericAttributeText::PreferredWidth(const BPoseView* pose) const 1287 { 1288 return pose->StringWidth(fFullValueText.String()); 1289 } 1290 1291 1292 void 1293 GenericAttributeText::ReadValue(BString* outString) 1294 { 1295 BModelOpener opener(const_cast<Model*>(fModel)); 1296 1297 ssize_t length = 0; 1298 fFullValueText = "-"; 1299 fValue.int64t = 0; 1300 fValueIsDefined = false; 1301 fValueDirty = false; 1302 1303 if (!fModel->Node()) 1304 return; 1305 1306 switch (fColumn->AttrType()) { 1307 case B_STRING_TYPE: 1308 { 1309 char buffer[kGenericReadBufferSize]; 1310 length = fModel->Node()->ReadAttr(fColumn->AttrName(), 1311 fColumn->AttrType(), 0, buffer, kGenericReadBufferSize - 1); 1312 1313 if (length > 0) { 1314 buffer[length] = '\0'; 1315 // make sure the buffer is null-terminated even if we 1316 // didn't read the whole attribute in or it wasn't to 1317 // begin with 1318 1319 *outString = buffer; 1320 fValueIsDefined = true; 1321 } 1322 break; 1323 } 1324 1325 case B_SSIZE_T_TYPE: 1326 case B_TIME_TYPE: 1327 case B_OFF_T_TYPE: 1328 case B_FLOAT_TYPE: 1329 case B_BOOL_TYPE: 1330 case B_CHAR_TYPE: 1331 case B_INT8_TYPE: 1332 case B_INT16_TYPE: 1333 case B_INT32_TYPE: 1334 case B_INT64_TYPE: 1335 case B_UINT8_TYPE: 1336 case B_UINT16_TYPE: 1337 case B_UINT32_TYPE: 1338 case B_UINT64_TYPE: 1339 case B_DOUBLE_TYPE: 1340 { 1341 // read in the numerical bit representation and attach it 1342 // with a type, depending on the bytes that could be read 1343 attr_info info; 1344 GenericValueStruct tmp; 1345 if (fModel->Node()->GetAttrInfo(fColumn->AttrName(), &info) 1346 == B_OK) { 1347 if (info.size && info.size <= (off_t)sizeof(int64)) { 1348 length = fModel->Node()->ReadAttr(fColumn->AttrName(), 1349 fColumn->AttrType(), 0, &tmp, (size_t)info.size); 1350 } 1351 1352 // We used tmp as a block of memory, now set the 1353 // correct fValue: 1354 1355 if (length == info.size) { 1356 if (fColumn->AttrType() == B_FLOAT_TYPE 1357 || fColumn->AttrType() == B_DOUBLE_TYPE) { 1358 // filter out special float/double types 1359 switch (info.size) { 1360 case sizeof(float): 1361 fValueIsDefined = true; 1362 fValue.floatt = tmp.floatt; 1363 break; 1364 1365 case sizeof(double): 1366 fValueIsDefined = true; 1367 fValue.doublet = tmp.doublet; 1368 break; 1369 1370 default: 1371 TRESPASS(); 1372 break; 1373 } 1374 } else { 1375 // handle the standard data types 1376 switch (info.size) { 1377 case sizeof(char): 1378 // Takes care of bool too. 1379 fValueIsDefined = true; 1380 fValue.int8t = tmp.int8t; 1381 break; 1382 1383 case sizeof(int16): 1384 fValueIsDefined = true; 1385 fValue.int16t = tmp.int16t; 1386 break; 1387 1388 case sizeof(int32): 1389 // Takes care of time_t too. 1390 fValueIsDefined = true; 1391 fValue.int32t = tmp.int32t; 1392 break; 1393 1394 case sizeof(int64): 1395 // Takes care of off_t too. 1396 fValueIsDefined = true; 1397 fValue.int64t = tmp.int64t; 1398 break; 1399 1400 default: 1401 TRESPASS(); 1402 break; 1403 } 1404 } 1405 } 1406 } 1407 break; 1408 } 1409 } 1410 } 1411 1412 1413 void 1414 GenericAttributeText::FitValue(BString* outString, const BPoseView* view) 1415 { 1416 if (fValueDirty) 1417 ReadValue(&fFullValueText); 1418 1419 fOldWidth = fColumn->Width(); 1420 1421 if (!fValueIsDefined) { 1422 *outString = "-"; 1423 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1424 fFullValueText.Length(), view, fOldWidth); 1425 fDirty = false; 1426 return; 1427 } 1428 1429 char buffer[256]; 1430 1431 switch (fColumn->AttrType()) { 1432 case B_SIZE_T_TYPE: 1433 TruncFileSizeBase(outString, fValue.int32t, view, fOldWidth); 1434 return; 1435 1436 case B_SSIZE_T_TYPE: 1437 if (fValue.int32t > 0) { 1438 TruncFileSizeBase(outString, fValue.int32t, view, fOldWidth); 1439 return; 1440 } 1441 sprintf(buffer, "%s", strerror(fValue.int32t)); 1442 fFullValueText = buffer; 1443 break; 1444 1445 case B_STRING_TYPE: 1446 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1447 fFullValueText.Length(), view, fOldWidth); 1448 fDirty = false; 1449 return; 1450 1451 case B_OFF_T_TYPE: 1452 // As a side effect update the fFullValueText to the string 1453 // representation of value 1454 TruncFileSize(&fFullValueText, fValue.off_tt, view, 100000); 1455 fTruncatedWidth = TruncFileSize(outString, fValue.off_tt, view, 1456 fOldWidth); 1457 fDirty = false; 1458 return; 1459 1460 case B_TIME_TYPE: 1461 // As a side effect update the fFullValueText to the string 1462 // representation of value 1463 TruncTime(&fFullValueText, fValue.time_tt, view, 100000); 1464 fTruncatedWidth = TruncTime(outString, fValue.time_tt, view, 1465 fOldWidth); 1466 fDirty = false; 1467 return; 1468 1469 case B_BOOL_TYPE: 1470 // For now use true/false, would be nice to be able to set 1471 // the value text 1472 1473 sprintf(buffer, "%s", fValue.boolt ? "true" : "false"); 1474 fFullValueText = buffer; 1475 break; 1476 1477 case B_CHAR_TYPE: 1478 // Make sure no non-printable characters are displayed: 1479 if (!isprint(fValue.uint8t)) { 1480 *outString = "-"; 1481 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1482 fFullValueText.Length(), view, fOldWidth); 1483 fDirty = false; 1484 return; 1485 } 1486 1487 sprintf(buffer, "%c", fValue.uint8t); 1488 fFullValueText = buffer; 1489 break; 1490 1491 case B_INT8_TYPE: 1492 sprintf(buffer, "%d", fValue.int8t); 1493 fFullValueText = buffer; 1494 break; 1495 1496 case B_UINT8_TYPE: 1497 sprintf(buffer, "%d", fValue.uint8t); 1498 fFullValueText = buffer; 1499 break; 1500 1501 case B_INT16_TYPE: 1502 sprintf(buffer, "%d", fValue.int16t); 1503 fFullValueText = buffer; 1504 break; 1505 1506 case B_UINT16_TYPE: 1507 sprintf(buffer, "%d", fValue.uint16t); 1508 fFullValueText = buffer; 1509 break; 1510 1511 case B_INT32_TYPE: 1512 sprintf(buffer, "%" B_PRId32, fValue.int32t); 1513 fFullValueText = buffer; 1514 break; 1515 1516 case B_UINT32_TYPE: 1517 sprintf(buffer, "%" B_PRId32, fValue.uint32t); 1518 fFullValueText = buffer; 1519 break; 1520 1521 case B_INT64_TYPE: 1522 sprintf(buffer, "%" B_PRId64, fValue.int64t); 1523 fFullValueText = buffer; 1524 break; 1525 1526 case B_UINT64_TYPE: 1527 sprintf(buffer, "%" B_PRId64, fValue.uint64t); 1528 fFullValueText = buffer; 1529 break; 1530 1531 case B_FLOAT_TYPE: 1532 snprintf(buffer, sizeof(buffer), "%g", fValue.floatt); 1533 fFullValueText = buffer; 1534 break; 1535 1536 case B_DOUBLE_TYPE: 1537 snprintf(buffer, sizeof(buffer), "%g", fValue.doublet); 1538 fFullValueText = buffer; 1539 break; 1540 1541 default: 1542 *outString = "-"; 1543 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1544 fFullValueText.Length(), view, fOldWidth); 1545 fDirty = false; 1546 return; 1547 } 1548 fTruncatedWidth = TruncString(outString, buffer, (ssize_t)strlen(buffer), 1549 view, fOldWidth); 1550 fDirty = false; 1551 } 1552 1553 1554 const char* 1555 GenericAttributeText::ValueAsText(const BPoseView* view) 1556 { 1557 // TODO: redesign this - this is to make sure the value is valid 1558 bool oldDirty = fDirty; 1559 BString outString; 1560 FitValue(&outString, view); 1561 fDirty = oldDirty; 1562 1563 return fFullValueText.String(); 1564 } 1565 1566 1567 int 1568 GenericAttributeText::Compare(WidgetAttributeText& attr, BPoseView*) 1569 { 1570 GenericAttributeText* compareTo 1571 = dynamic_cast<GenericAttributeText*>(&attr); 1572 ThrowOnAssert(compareTo != NULL); 1573 1574 if (fValueDirty) 1575 ReadValue(&fFullValueText); 1576 1577 if (compareTo->fValueDirty) 1578 compareTo->ReadValue(&compareTo->fFullValueText); 1579 1580 // sort undefined values last, regardless of the other value 1581 if (!fValueIsDefined) 1582 return compareTo->fValueIsDefined ? 1 : 0; 1583 1584 if (!compareTo->fValueIsDefined) 1585 return -1; 1586 1587 switch (fColumn->AttrType()) { 1588 case B_STRING_TYPE: 1589 return fFullValueText.ICompare(compareTo->fFullValueText); 1590 1591 case B_CHAR_TYPE: 1592 { 1593 char vStr[2] = { static_cast<char>(fValue.uint8t), 0 }; 1594 char cStr[2] = { static_cast<char>(compareTo->fValue.uint8t), 0}; 1595 1596 BString valueStr(vStr); 1597 BString compareToStr(cStr); 1598 1599 return valueStr.ICompare(compareToStr); 1600 } 1601 1602 case B_FLOAT_TYPE: 1603 return fValue.floatt >= compareTo->fValue.floatt ? 1604 (fValue.floatt == compareTo->fValue.floatt ? 0 : 1) : -1; 1605 1606 case B_DOUBLE_TYPE: 1607 return fValue.doublet >= compareTo->fValue.doublet ? 1608 (fValue.doublet == compareTo->fValue.doublet ? 0 : 1) : -1; 1609 1610 case B_BOOL_TYPE: 1611 return fValue.boolt >= compareTo->fValue.boolt ? 1612 (fValue.boolt == compareTo->fValue.boolt ? 0 : 1) : -1; 1613 1614 case B_UINT8_TYPE: 1615 return fValue.uint8t >= compareTo->fValue.uint8t ? 1616 (fValue.uint8t == compareTo->fValue.uint8t ? 0 : 1) : -1; 1617 1618 case B_INT8_TYPE: 1619 return fValue.int8t >= compareTo->fValue.int8t ? 1620 (fValue.int8t == compareTo->fValue.int8t ? 0 : 1) : -1; 1621 1622 case B_UINT16_TYPE: 1623 return fValue.uint16t >= compareTo->fValue.uint16t ? 1624 (fValue.uint16t == compareTo->fValue.uint16t ? 0 : 1) : -1; 1625 1626 case B_INT16_TYPE: 1627 return fValue.int16t >= compareTo->fValue.int16t ? 1628 (fValue.int16t == compareTo->fValue.int16t ? 0 : 1) : -1; 1629 1630 case B_UINT32_TYPE: 1631 return fValue.uint32t >= compareTo->fValue.uint32t ? 1632 (fValue.uint32t == compareTo->fValue.uint32t ? 0 : 1) : -1; 1633 1634 case B_TIME_TYPE: 1635 // time_t typedef'd to a long, i.e. a int32 1636 case B_INT32_TYPE: 1637 return fValue.int32t >= compareTo->fValue.int32t ? 1638 (fValue.int32t == compareTo->fValue.int32t ? 0 : 1) : -1; 1639 1640 case B_OFF_T_TYPE: 1641 // off_t typedef'd to a long long, i.e. a int64 1642 case B_INT64_TYPE: 1643 return fValue.int64t >= compareTo->fValue.int64t ? 1644 (fValue.int64t == compareTo->fValue.int64t ? 0 : 1) : -1; 1645 1646 case B_UINT64_TYPE: 1647 default: 1648 return fValue.uint64t >= compareTo->fValue.uint64t ? 1649 (fValue.uint64t == compareTo->fValue.uint64t ? 0 : 1) : -1; 1650 } 1651 1652 return 0; 1653 } 1654 1655 1656 bool 1657 GenericAttributeText::CommitEditedText(BTextView* textView) 1658 { 1659 ASSERT(fColumn->Editable()); 1660 const char* text = textView->Text(); 1661 1662 if (fFullValueText == text) 1663 // no change 1664 return false; 1665 1666 if (!CommitEditedTextFlavor(textView)) 1667 return false; 1668 1669 // update text and width in this widget 1670 fFullValueText = text; 1671 // cause re-truncation 1672 fDirty = true; 1673 fValueDirty = true; 1674 1675 return true; 1676 } 1677 1678 1679 void 1680 GenericAttributeText::SetUpEditing(BTextView* textView) 1681 { 1682 textView->SetMaxBytes(kGenericReadBufferSize - 1); 1683 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 1684 } 1685 1686 1687 bool 1688 GenericAttributeText::CommitEditedTextFlavor(BTextView* textView) 1689 { 1690 BNode node(fModel->EntryRef()); 1691 1692 if (node.InitCheck() != B_OK) 1693 return false; 1694 1695 uint32 type = fColumn->AttrType(); 1696 1697 if (type != B_STRING_TYPE 1698 && type != B_UINT64_TYPE 1699 && type != B_UINT32_TYPE 1700 && type != B_UINT16_TYPE 1701 && type != B_UINT8_TYPE 1702 && type != B_INT64_TYPE 1703 && type != B_INT32_TYPE 1704 && type != B_INT16_TYPE 1705 && type != B_INT8_TYPE 1706 && type != B_OFF_T_TYPE 1707 && type != B_TIME_TYPE 1708 && type != B_FLOAT_TYPE 1709 && type != B_DOUBLE_TYPE 1710 && type != B_CHAR_TYPE 1711 && type != B_BOOL_TYPE) { 1712 BAlert* alert = new BAlert("", 1713 B_TRANSLATE("Sorry, you cannot edit that attribute."), 1714 B_TRANSLATE("Cancel"), 1715 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1716 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1717 alert->Go(); 1718 return false; 1719 } 1720 1721 const char* columnName = fColumn->AttrName(); 1722 ssize_t size = 0; 1723 1724 switch (type) { 1725 case B_STRING_TYPE: 1726 size = fModel->WriteAttr(columnName, type, 0, textView->Text(), 1727 (size_t)(textView->TextLength() + 1)); 1728 break; 1729 1730 case B_BOOL_TYPE: 1731 { 1732 bool value = strncasecmp(textView->Text(), "0", 1) != 0 1733 && strncasecmp(textView->Text(), "off", 2) != 0 1734 && strncasecmp(textView->Text(), "no", 3) != 0 1735 && strncasecmp(textView->Text(), "false", 4) != 0 1736 && strlen(textView->Text()) != 0; 1737 1738 size = fModel->WriteAttr(columnName, type, 0, &value, sizeof(bool)); 1739 break; 1740 } 1741 1742 case B_CHAR_TYPE: 1743 { 1744 char ch; 1745 sscanf(textView->Text(), "%c", &ch); 1746 //Check if we read the start of a multi-byte glyph: 1747 if (!isprint(ch)) { 1748 BAlert* alert = new BAlert("", 1749 B_TRANSLATE("Sorry, the 'Character' " 1750 "attribute cannot store a multi-byte glyph."), 1751 B_TRANSLATE("Cancel"), 1752 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1753 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1754 alert->Go(); 1755 return false; 1756 } 1757 1758 size = fModel->WriteAttr(columnName, type, 0, &ch, sizeof(char)); 1759 break; 1760 } 1761 1762 case B_FLOAT_TYPE: 1763 { 1764 float floatVal; 1765 1766 if (sscanf(textView->Text(), "%f", &floatVal) == 1) { 1767 fValueIsDefined = true; 1768 fValue.floatt = floatVal; 1769 size = fModel->WriteAttr(columnName, type, 0, &floatVal, 1770 sizeof(float)); 1771 } else { 1772 // If the value was already defined, it's on disk. 1773 // Otherwise not. 1774 return fValueIsDefined; 1775 } 1776 break; 1777 } 1778 1779 case B_DOUBLE_TYPE: 1780 { 1781 double doubleVal; 1782 1783 if (sscanf(textView->Text(), "%lf", &doubleVal) == 1) { 1784 fValueIsDefined = true; 1785 fValue.doublet = doubleVal; 1786 size = fModel->WriteAttr(columnName, type, 0, &doubleVal, 1787 sizeof(double)); 1788 } else { 1789 // If the value was already defined, it's on disk. 1790 // Otherwise not. 1791 return fValueIsDefined; 1792 } 1793 break; 1794 } 1795 1796 case B_TIME_TYPE: 1797 case B_OFF_T_TYPE: 1798 case B_UINT64_TYPE: 1799 case B_UINT32_TYPE: 1800 case B_UINT16_TYPE: 1801 case B_UINT8_TYPE: 1802 case B_INT64_TYPE: 1803 case B_INT32_TYPE: 1804 case B_INT16_TYPE: 1805 case B_INT8_TYPE: 1806 { 1807 GenericValueStruct tmp; 1808 size_t scalarSize = 0; 1809 1810 switch (type) { 1811 case B_TIME_TYPE: 1812 tmp.time_tt = parsedate(textView->Text(), time(0)); 1813 scalarSize = sizeof(time_t); 1814 break; 1815 1816 // do some size independent conversion on builtin types 1817 case B_OFF_T_TYPE: 1818 tmp.off_tt = StringToScalar(textView->Text()); 1819 scalarSize = sizeof(off_t); 1820 break; 1821 1822 case B_UINT64_TYPE: 1823 case B_INT64_TYPE: 1824 tmp.int64t = StringToScalar(textView->Text()); 1825 scalarSize = sizeof(int64); 1826 break; 1827 1828 case B_UINT32_TYPE: 1829 case B_INT32_TYPE: 1830 tmp.int32t = (int32)StringToScalar(textView->Text()); 1831 scalarSize = sizeof(int32); 1832 break; 1833 1834 case B_UINT16_TYPE: 1835 case B_INT16_TYPE: 1836 tmp.int16t = (int16)StringToScalar(textView->Text()); 1837 scalarSize = sizeof(int16); 1838 break; 1839 1840 case B_UINT8_TYPE: 1841 case B_INT8_TYPE: 1842 tmp.int8t = (int8)StringToScalar(textView->Text()); 1843 scalarSize = sizeof(int8); 1844 break; 1845 1846 default: 1847 TRESPASS(); 1848 } 1849 1850 size = fModel->WriteAttr(columnName, type, 0, &tmp, scalarSize); 1851 break; 1852 } 1853 } 1854 1855 if (size < 0) { 1856 BAlert* alert = new BAlert("", 1857 B_TRANSLATE("There was an error writing the attribute."), 1858 B_TRANSLATE("Cancel"), 1859 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1860 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1861 alert->Go(); 1862 1863 fValueIsDefined = false; 1864 return false; 1865 } 1866 1867 fValueIsDefined = true; 1868 return true; 1869 } 1870 1871 1872 // #pragma mark - DurationAttributeText (display as: duration) 1873 1874 1875 DurationAttributeText::DurationAttributeText(const Model* model, 1876 const BColumn* column) 1877 : 1878 GenericAttributeText(model, column) 1879 { 1880 } 1881 1882 1883 // TODO: support editing! 1884 1885 1886 void 1887 DurationAttributeText::FitValue(BString* outString, const BPoseView* view) 1888 { 1889 if (fValueDirty) 1890 ReadValue(&fFullValueText); 1891 1892 fOldWidth = fColumn->Width(); 1893 fDirty = false; 1894 1895 if (!fValueIsDefined) { 1896 *outString = "-"; 1897 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1898 fFullValueText.Length(), view, fOldWidth); 1899 return; 1900 } 1901 1902 int64 time = 0; 1903 1904 switch (fColumn->AttrType()) { 1905 case B_TIME_TYPE: 1906 time = fValue.time_tt * 1000000LL; 1907 break; 1908 1909 case B_INT8_TYPE: 1910 time = fValue.int8t * 1000000LL; 1911 break; 1912 1913 case B_INT16_TYPE: 1914 time = fValue.int16t * 1000000LL; 1915 break; 1916 1917 case B_INT32_TYPE: 1918 time = fValue.int32t * 1000000LL; 1919 break; 1920 1921 case B_INT64_TYPE: 1922 time = fValue.int64t; 1923 break; 1924 } 1925 1926 // TODO: ignores micro seconds for now 1927 int32 seconds = time / 1000000LL; 1928 1929 bool negative = seconds < 0; 1930 if (negative) 1931 seconds = -seconds; 1932 1933 int32 hours = seconds / 3600; 1934 seconds -= hours * 3600; 1935 int32 minutes = seconds / 60; 1936 seconds = seconds % 60; 1937 1938 char buffer[256]; 1939 if (hours > 0) { 1940 snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32 ":%02" 1941 B_PRId32, negative ? "-" : "", hours, minutes, seconds); 1942 } else { 1943 snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32, 1944 negative ? "-" : "", minutes, seconds); 1945 } 1946 1947 fFullValueText = buffer; 1948 1949 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1950 fFullValueText.Length(), view, fOldWidth); 1951 } 1952 1953 1954 // #pragma mark - CheckboxAttributeText (display as: checkbox) 1955 1956 1957 CheckboxAttributeText::CheckboxAttributeText(const Model* model, 1958 const BColumn* column) 1959 : 1960 GenericAttributeText(model, column), 1961 fOnChar("✖"), 1962 fOffChar("-") 1963 { 1964 // TODO: better have common data in the column object! 1965 if (const char* separator = strchr(column->DisplayAs(), ':')) { 1966 BString chars(separator + 1); 1967 int32 length; 1968 const char* c = chars.CharAt(0, &length); 1969 fOnChar.SetTo(c, length); 1970 if (c[length]) { 1971 c = chars.CharAt(1, &length); 1972 fOffChar.SetTo(c, length); 1973 } 1974 } 1975 } 1976 1977 1978 void 1979 CheckboxAttributeText::SetUpEditing(BTextView* view) 1980 { 1981 // TODO: support editing for real! 1982 BString outString; 1983 GenericAttributeText::FitValue(&outString, NULL); 1984 GenericAttributeText::SetUpEditing(view); 1985 } 1986 1987 1988 void 1989 CheckboxAttributeText::FitValue(BString* outString, const BPoseView* view) 1990 { 1991 if (fValueDirty) 1992 ReadValue(&fFullValueText); 1993 1994 fOldWidth = fColumn->Width(); 1995 fDirty = false; 1996 1997 if (!fValueIsDefined) { 1998 *outString = fOffChar; 1999 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 2000 fFullValueText.Length(), view, fOldWidth); 2001 return; 2002 } 2003 2004 bool checked = false; 2005 2006 switch (fColumn->AttrType()) { 2007 case B_BOOL_TYPE: 2008 checked = fValue.boolt; 2009 break; 2010 2011 case B_INT8_TYPE: 2012 case B_UINT8_TYPE: 2013 checked = fValue.int8t != 0; 2014 break; 2015 2016 case B_INT16_TYPE: 2017 case B_UINT16_TYPE: 2018 checked = fValue.int16t != 0; 2019 break; 2020 2021 case B_INT32_TYPE: 2022 case B_UINT32_TYPE: 2023 checked = fValue.int32t != 0; 2024 break; 2025 } 2026 2027 fFullValueText = checked ? fOnChar : fOffChar; 2028 2029 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 2030 fFullValueText.Length(), view, fOldWidth); 2031 } 2032 2033 2034 // #pragma mark - RatingAttributeText (display as: rating) 2035 2036 2037 RatingAttributeText::RatingAttributeText(const Model* model, 2038 const BColumn* column) 2039 : 2040 GenericAttributeText(model, column), 2041 fCount(5), 2042 fMax(10) 2043 { 2044 // TODO: support different star counts/max via specifier 2045 } 2046 2047 2048 void 2049 RatingAttributeText::SetUpEditing(BTextView* view) 2050 { 2051 // TODO: support editing for real! 2052 BString outString; 2053 GenericAttributeText::FitValue(&outString, NULL); 2054 GenericAttributeText::SetUpEditing(view); 2055 } 2056 2057 2058 void 2059 RatingAttributeText::FitValue(BString* ratingString, const BPoseView* view) 2060 { 2061 if (fValueDirty) 2062 ReadValue(&fFullValueText); 2063 2064 fOldWidth = fColumn->Width(); 2065 fDirty = false; 2066 2067 int64 rating; 2068 if (fValueIsDefined) { 2069 switch (fColumn->AttrType()) { 2070 case B_INT8_TYPE: 2071 rating = fValue.int8t; 2072 break; 2073 2074 case B_INT16_TYPE: 2075 rating = fValue.int16t; 2076 break; 2077 2078 case B_INT32_TYPE: 2079 rating = fValue.int32t; 2080 break; 2081 2082 default: 2083 rating = 0; 2084 break; 2085 } 2086 } else 2087 rating = 0; 2088 2089 if (rating > fMax) 2090 rating = fMax; 2091 2092 if (rating < 0) 2093 rating = 0; 2094 2095 int32 steps = fMax / fCount; 2096 fFullValueText = ""; 2097 2098 for (int32 i = 0; i < fCount; i++) { 2099 int64 n = i * steps; 2100 if (rating > n) 2101 fFullValueText += "★"; 2102 else 2103 fFullValueText += "☆"; 2104 } 2105 2106 fTruncatedWidth = TruncString(ratingString, fFullValueText.String(), 2107 fFullValueText.Length(), view, fOldWidth); 2108 } 2109 2110 2111 // #pragma mark - OpenWithRelationAttributeText 2112 2113 2114 OpenWithRelationAttributeText::OpenWithRelationAttributeText(const Model* model, 2115 const BColumn* column, const BPoseView* view) 2116 : 2117 ScalarAttributeText(model, column), 2118 fPoseView(view) 2119 { 2120 } 2121 2122 2123 int64 2124 OpenWithRelationAttributeText::ReadValue() 2125 { 2126 fValueDirty = false; 2127 2128 const OpenWithPoseView* view 2129 = dynamic_cast<const OpenWithPoseView*>(fPoseView); 2130 if (view != NULL) { 2131 fValue = view->OpenWithRelation(fModel); 2132 fValueIsDefined = true; 2133 } 2134 2135 return fValue; 2136 } 2137 2138 2139 float 2140 OpenWithRelationAttributeText::PreferredWidth(const BPoseView* pose) const 2141 { 2142 BString widthString; 2143 TruncString(&widthString, fRelationText.String(), fRelationText.Length(), 2144 pose, 500, B_TRUNCATE_END); 2145 return pose->StringWidth(widthString.String()); 2146 } 2147 2148 2149 void 2150 OpenWithRelationAttributeText::FitValue(BString* outString, 2151 const BPoseView* view) 2152 { 2153 if (fValueDirty) 2154 ReadValue(); 2155 2156 ASSERT(view == fPoseView); 2157 const OpenWithPoseView* launchWithView 2158 = dynamic_cast<const OpenWithPoseView*>(view); 2159 if (launchWithView != NULL) 2160 launchWithView->OpenWithRelationDescription(fModel, &fRelationText); 2161 2162 fOldWidth = fColumn->Width(); 2163 fTruncatedWidth = TruncString(outString, fRelationText.String(), 2164 fRelationText.Length(), view, fOldWidth, B_TRUNCATE_END); 2165 fDirty = false; 2166 } 2167 2168 2169 // #pragma mark - VersionAttributeText 2170 2171 2172 VersionAttributeText::VersionAttributeText(const Model* model, 2173 const BColumn* column, bool app) 2174 : 2175 StringAttributeText(model, column), 2176 fAppVersion(app) 2177 { 2178 } 2179 2180 2181 void 2182 VersionAttributeText::ReadValue(BString* outString) 2183 { 2184 fValueDirty = false; 2185 2186 BModelOpener opener(fModel); 2187 BFile* file = dynamic_cast<BFile*>(fModel->Node()); 2188 if (file != NULL) { 2189 BAppFileInfo info(file); 2190 version_info version; 2191 if (info.InitCheck() == B_OK 2192 && info.GetVersionInfo(&version, fAppVersion 2193 ? B_APP_VERSION_KIND : B_SYSTEM_VERSION_KIND) == B_OK) { 2194 *outString = version.short_info; 2195 return; 2196 } 2197 } 2198 2199 *outString = "-"; 2200 } 2201