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