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