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