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