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 return kUnknownSize; 1109 1110 fValueIsDefined = true; 1111 1112 return fModel->StatBuf()->st_size; 1113 } 1114 1115 1116 void 1117 SizeAttributeText::FitValue(BString* result, const BPoseView* view) 1118 { 1119 if (fValueDirty) 1120 fValue = ReadValue(); 1121 fOldWidth = fColumn->Width(); 1122 fTruncatedWidth = TruncFileSize(result, fValue, view, fOldWidth); 1123 fDirty = false; 1124 } 1125 1126 1127 float 1128 SizeAttributeText::PreferredWidth(const BPoseView* pose) const 1129 { 1130 if (fValueIsDefined) { 1131 BString widthString; 1132 TruncFileSize(&widthString, fValue, pose, 100000); 1133 return pose->StringWidth(widthString.String()); 1134 } 1135 return pose->StringWidth("-"); 1136 } 1137 1138 1139 // #pragma mark - time related 1140 1141 1142 TimeAttributeText::TimeAttributeText(const Model* model, 1143 const BColumn* column) 1144 : 1145 ScalarAttributeText(model, column) 1146 { 1147 } 1148 1149 1150 float 1151 TimeAttributeText::PreferredWidth(const BPoseView* pose) const 1152 { 1153 BString widthString; 1154 TruncTimeBase(&widthString, fValue, pose, 100000); 1155 return pose->StringWidth(widthString.String()); 1156 } 1157 1158 1159 void 1160 TimeAttributeText::FitValue(BString* result, const BPoseView* view) 1161 { 1162 if (fValueDirty) 1163 fValue = ReadValue(); 1164 fOldWidth = fColumn->Width(); 1165 fTruncatedWidth = TruncTime(result, fValue, view, fOldWidth); 1166 fDirty = false; 1167 } 1168 1169 1170 bool 1171 TimeAttributeText::CheckSettingsChanged(void) 1172 { 1173 // TODO : check against the actual locale settings 1174 return false; 1175 } 1176 1177 1178 CreationTimeAttributeText::CreationTimeAttributeText(const Model* model, 1179 const BColumn* column) 1180 : 1181 TimeAttributeText(model, column) 1182 { 1183 } 1184 1185 1186 int64 1187 CreationTimeAttributeText::ReadValue() 1188 { 1189 fValueDirty = false; 1190 fValueIsDefined = true; 1191 return fModel->StatBuf()->st_crtime; 1192 } 1193 1194 1195 ModificationTimeAttributeText::ModificationTimeAttributeText( 1196 const Model* model, const BColumn* column) 1197 : 1198 TimeAttributeText(model, column) 1199 { 1200 } 1201 1202 1203 int64 1204 ModificationTimeAttributeText::ReadValue() 1205 { 1206 fValueDirty = false; 1207 fValueIsDefined = true; 1208 return fModel->StatBuf()->st_mtime; 1209 } 1210 1211 1212 // #pragma mark - 1213 1214 1215 GenericAttributeText::GenericAttributeText(const Model* model, 1216 const BColumn* column) 1217 : 1218 StringAttributeText(model, column) 1219 { 1220 } 1221 1222 1223 bool 1224 GenericAttributeText::CheckAttributeChanged() 1225 { 1226 GenericValueStruct tmpValue = fValue; 1227 BString tmpString(fFullValueText); 1228 ReadValue(&fFullValueText); 1229 1230 // fDirty could already be true, in that case we mustn't set it to 1231 // false, even if the attribute text hasn't changed 1232 bool changed = fValue.int64t != tmpValue.int64t 1233 || tmpString != fFullValueText; 1234 if (changed) 1235 fDirty = true; 1236 1237 return fDirty; 1238 } 1239 1240 1241 float 1242 GenericAttributeText::PreferredWidth(const BPoseView* pose) const 1243 { 1244 return pose->StringWidth(fFullValueText.String()); 1245 } 1246 1247 1248 void 1249 GenericAttributeText::ReadValue(BString* result) 1250 { 1251 BModelOpener opener(const_cast<Model*>(fModel)); 1252 1253 ssize_t length = 0; 1254 fFullValueText = "-"; 1255 fValue.int64t = 0; 1256 fValueIsDefined = false; 1257 fValueDirty = false; 1258 1259 if (!fModel->Node()) 1260 return; 1261 1262 switch (fColumn->AttrType()) { 1263 case B_STRING_TYPE: 1264 { 1265 char buffer[kGenericReadBufferSize]; 1266 length = fModel->Node()->ReadAttr(fColumn->AttrName(), 1267 fColumn->AttrType(), 0, buffer, kGenericReadBufferSize - 1); 1268 1269 if (length > 0) { 1270 buffer[length] = '\0'; 1271 // make sure the buffer is null-terminated even if we 1272 // didn't read the whole attribute in or it wasn't to 1273 // begin with 1274 1275 *result = buffer; 1276 fValueIsDefined = true; 1277 } 1278 break; 1279 } 1280 1281 case B_SSIZE_T_TYPE: 1282 case B_TIME_TYPE: 1283 case B_OFF_T_TYPE: 1284 case B_FLOAT_TYPE: 1285 case B_BOOL_TYPE: 1286 case B_CHAR_TYPE: 1287 case B_INT8_TYPE: 1288 case B_INT16_TYPE: 1289 case B_INT32_TYPE: 1290 case B_INT64_TYPE: 1291 case B_UINT8_TYPE: 1292 case B_UINT16_TYPE: 1293 case B_UINT32_TYPE: 1294 case B_UINT64_TYPE: 1295 case B_DOUBLE_TYPE: 1296 { 1297 // read in the numerical bit representation and attach it 1298 // with a type, depending on the bytes that could be read 1299 attr_info info; 1300 GenericValueStruct tmp; 1301 if (fModel->Node()->GetAttrInfo(fColumn->AttrName(), &info) 1302 == B_OK) { 1303 if (info.size && info.size <= (off_t)sizeof(int64)) { 1304 length = fModel->Node()->ReadAttr(fColumn->AttrName(), 1305 fColumn->AttrType(), 0, &tmp, (size_t)info.size); 1306 } 1307 1308 // We used tmp as a block of memory, now set the 1309 // correct fValue: 1310 1311 if (length == info.size) { 1312 if (fColumn->AttrType() == B_FLOAT_TYPE 1313 || fColumn->AttrType() == B_DOUBLE_TYPE) { 1314 // filter out special float/double types 1315 switch (info.size) { 1316 case sizeof(float): 1317 fValueIsDefined = true; 1318 fValue.floatt = tmp.floatt; 1319 break; 1320 1321 case sizeof(double): 1322 fValueIsDefined = true; 1323 fValue.doublet = tmp.doublet; 1324 break; 1325 1326 default: 1327 TRESPASS(); 1328 } 1329 } else { 1330 // handle the standard data types 1331 switch (info.size) { 1332 case sizeof(char): // Takes care of bool too. 1333 fValueIsDefined = true; 1334 fValue.int8t = tmp.int8t; 1335 break; 1336 1337 case sizeof(int16): 1338 fValueIsDefined = true; 1339 fValue.int16t = tmp.int16t; 1340 break; 1341 1342 case sizeof(int32): // Takes care of time_t too. 1343 fValueIsDefined = true; 1344 fValue.int32t = tmp.int32t; 1345 break; 1346 1347 case sizeof(int64): // Takes care of off_t too. 1348 fValueIsDefined = true; 1349 fValue.int64t = tmp.int64t; 1350 break; 1351 1352 default: 1353 TRESPASS(); 1354 } 1355 } 1356 } 1357 } 1358 break; 1359 } 1360 } 1361 } 1362 1363 1364 void 1365 GenericAttributeText::FitValue(BString* result, const BPoseView* view) 1366 { 1367 if (fValueDirty) 1368 ReadValue(&fFullValueText); 1369 1370 fOldWidth = fColumn->Width(); 1371 1372 if (!fValueIsDefined) { 1373 *result = "-"; 1374 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1375 fFullValueText.Length(), view, fOldWidth); 1376 fDirty = false; 1377 return; 1378 } 1379 1380 char buffer[256]; 1381 1382 switch (fColumn->AttrType()) { 1383 case B_SIZE_T_TYPE: 1384 TruncFileSizeBase(result, fValue.int32t, view, fOldWidth); 1385 return; 1386 1387 case B_SSIZE_T_TYPE: 1388 if (fValue.int32t > 0) { 1389 TruncFileSizeBase(result, fValue.int32t, view, fOldWidth); 1390 return; 1391 } 1392 sprintf(buffer, "%s", strerror(fValue.int32t)); 1393 fFullValueText = buffer; 1394 break; 1395 1396 case B_STRING_TYPE: 1397 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1398 fFullValueText.Length(), view, fOldWidth); 1399 fDirty = false; 1400 return; 1401 1402 case B_OFF_T_TYPE: 1403 // As a side effect update the fFullValueText to the string 1404 // representation of value 1405 TruncFileSize(&fFullValueText, fValue.off_tt, view, 100000); 1406 fTruncatedWidth = TruncFileSize(result, fValue.off_tt, view, 1407 fOldWidth); 1408 fDirty = false; 1409 return; 1410 1411 case B_TIME_TYPE: 1412 // As a side effect update the fFullValueText to the string 1413 // representation of value 1414 TruncTime(&fFullValueText, fValue.time_tt, view, 100000); 1415 fTruncatedWidth = TruncTime(result, fValue.time_tt, view, 1416 fOldWidth); 1417 fDirty = false; 1418 return; 1419 1420 case B_BOOL_TYPE: 1421 // For now use true/false, would be nice to be able to set 1422 // the value text 1423 1424 sprintf(buffer, "%s", fValue.boolt ? "true" : "false"); 1425 fFullValueText = buffer; 1426 break; 1427 1428 case B_CHAR_TYPE: 1429 // Make sure no non-printable characters are displayed: 1430 if (!isprint(fValue.uint8t)) { 1431 *result = "-"; 1432 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1433 fFullValueText.Length(), view, fOldWidth); 1434 fDirty = false; 1435 return; 1436 } 1437 1438 sprintf(buffer, "%c", fValue.uint8t); 1439 fFullValueText = buffer; 1440 break; 1441 1442 case B_INT8_TYPE: 1443 sprintf(buffer, "%d", fValue.int8t); 1444 fFullValueText = buffer; 1445 break; 1446 1447 case B_UINT8_TYPE: 1448 sprintf(buffer, "%d", fValue.uint8t); 1449 fFullValueText = buffer; 1450 break; 1451 1452 case B_INT16_TYPE: 1453 sprintf(buffer, "%d", fValue.int16t); 1454 fFullValueText = buffer; 1455 break; 1456 1457 case B_UINT16_TYPE: 1458 sprintf(buffer, "%d", fValue.uint16t); 1459 fFullValueText = buffer; 1460 break; 1461 1462 case B_INT32_TYPE: 1463 sprintf(buffer, "%" B_PRId32, fValue.int32t); 1464 fFullValueText = buffer; 1465 break; 1466 1467 case B_UINT32_TYPE: 1468 sprintf(buffer, "%" B_PRId32, fValue.uint32t); 1469 fFullValueText = buffer; 1470 break; 1471 1472 case B_INT64_TYPE: 1473 sprintf(buffer, "%" B_PRId64, fValue.int64t); 1474 fFullValueText = buffer; 1475 break; 1476 1477 case B_UINT64_TYPE: 1478 sprintf(buffer, "%" B_PRId64, fValue.uint64t); 1479 fFullValueText = buffer; 1480 break; 1481 1482 case B_FLOAT_TYPE: 1483 snprintf(buffer, sizeof(buffer), "%g", fValue.floatt); 1484 fFullValueText = buffer; 1485 break; 1486 1487 case B_DOUBLE_TYPE: 1488 snprintf(buffer, sizeof(buffer), "%g", fValue.doublet); 1489 fFullValueText = buffer; 1490 break; 1491 1492 default: 1493 *result = "-"; 1494 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1495 fFullValueText.Length(), view, fOldWidth); 1496 fDirty = false; 1497 return; 1498 } 1499 fTruncatedWidth = TruncString(result, buffer, (ssize_t)strlen(buffer), view, 1500 fOldWidth); 1501 fDirty = false; 1502 } 1503 1504 1505 const char* 1506 GenericAttributeText::ValueAsText(const BPoseView* view) 1507 { 1508 // TODO: redesign this - this is to make sure the value is valid 1509 bool oldDirty = fDirty; 1510 BString result; 1511 FitValue(&result, view); 1512 fDirty = oldDirty; 1513 1514 return fFullValueText.String(); 1515 } 1516 1517 1518 int 1519 GenericAttributeText::Compare(WidgetAttributeText& attr, BPoseView*) 1520 { 1521 GenericAttributeText* compareTo 1522 = dynamic_cast<GenericAttributeText*>(&attr); 1523 ASSERT(compareTo); 1524 1525 if (fValueDirty) 1526 ReadValue(&fFullValueText); 1527 if (compareTo->fValueDirty) 1528 compareTo->ReadValue(&compareTo->fFullValueText); 1529 1530 // Sort undefined values last, regardless of the other value: 1531 if (!fValueIsDefined) 1532 return compareTo->fValueIsDefined ? 1 : 0; 1533 if (!compareTo->fValueIsDefined) 1534 return -1; 1535 1536 switch (fColumn->AttrType()) { 1537 case B_STRING_TYPE: 1538 return fFullValueText.ICompare(compareTo->fFullValueText); 1539 1540 case B_CHAR_TYPE: 1541 { 1542 char vStr[2] = { static_cast<char>(fValue.uint8t), 0 }; 1543 char cStr[2] = { static_cast<char>(compareTo->fValue.uint8t), 0}; 1544 1545 BString valueStr(vStr); 1546 BString compareToStr(cStr); 1547 1548 return valueStr.ICompare(compareToStr); 1549 } 1550 1551 case B_FLOAT_TYPE: 1552 return fValue.floatt >= compareTo->fValue.floatt ? 1553 (fValue.floatt == compareTo->fValue.floatt ? 0 : 1) : -1; 1554 1555 case B_DOUBLE_TYPE: 1556 return fValue.doublet >= compareTo->fValue.doublet ? 1557 (fValue.doublet == compareTo->fValue.doublet ? 0 : 1) : -1; 1558 1559 case B_BOOL_TYPE: 1560 return fValue.boolt >= compareTo->fValue.boolt ? 1561 (fValue.boolt == compareTo->fValue.boolt ? 0 : 1) : -1; 1562 1563 case B_UINT8_TYPE: 1564 return fValue.uint8t >= compareTo->fValue.uint8t ? 1565 (fValue.uint8t == compareTo->fValue.uint8t ? 0 : 1) : -1; 1566 1567 case B_INT8_TYPE: 1568 return fValue.int8t >= compareTo->fValue.int8t ? 1569 (fValue.int8t == compareTo->fValue.int8t ? 0 : 1) : -1; 1570 1571 case B_UINT16_TYPE: 1572 return fValue.uint16t >= compareTo->fValue.uint16t ? 1573 (fValue.uint16t == compareTo->fValue.uint16t ? 0 : 1) : -1; 1574 1575 case B_INT16_TYPE: 1576 return fValue.int16t >= compareTo->fValue.int16t ? 1577 (fValue.int16t == compareTo->fValue.int16t ? 0 : 1) : -1; 1578 1579 case B_UINT32_TYPE: 1580 return fValue.uint32t >= compareTo->fValue.uint32t ? 1581 (fValue.uint32t == compareTo->fValue.uint32t ? 0 : 1) : -1; 1582 1583 case B_TIME_TYPE: 1584 // time_t typedef'd to a long, i.e. a int32 1585 case B_INT32_TYPE: 1586 return fValue.int32t >= compareTo->fValue.int32t ? 1587 (fValue.int32t == compareTo->fValue.int32t ? 0 : 1) : -1; 1588 1589 case B_OFF_T_TYPE: 1590 // off_t typedef'd to a long long, i.e. a int64 1591 case B_INT64_TYPE: 1592 return fValue.int64t >= compareTo->fValue.int64t ? 1593 (fValue.int64t == compareTo->fValue.int64t ? 0 : 1) : -1; 1594 1595 case B_UINT64_TYPE: 1596 default: 1597 return fValue.uint64t >= compareTo->fValue.uint64t ? 1598 (fValue.uint64t == compareTo->fValue.uint64t ? 0 : 1) : -1; 1599 } 1600 return 0; 1601 } 1602 1603 1604 bool 1605 GenericAttributeText::CommitEditedText(BTextView* textView) 1606 { 1607 ASSERT(fColumn->Editable()); 1608 const char* text = textView->Text(); 1609 1610 if (fFullValueText == text) 1611 // no change 1612 return false; 1613 1614 if (!CommitEditedTextFlavor(textView)) 1615 return false; 1616 1617 // update text and width in this widget 1618 fFullValueText = text; 1619 // cause re-truncation 1620 fDirty = true; 1621 fValueDirty = true; 1622 1623 return true; 1624 } 1625 1626 1627 void 1628 GenericAttributeText::SetUpEditing(BTextView* textView) 1629 { 1630 textView->SetMaxBytes(kGenericReadBufferSize - 1); 1631 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 1632 } 1633 1634 1635 bool 1636 GenericAttributeText::CommitEditedTextFlavor(BTextView* textView) 1637 { 1638 BNode node(fModel->EntryRef()); 1639 1640 if (node.InitCheck() != B_OK) 1641 return false; 1642 1643 uint32 type = fColumn->AttrType(); 1644 1645 if (type != B_STRING_TYPE 1646 && type != B_UINT64_TYPE 1647 && type != B_UINT32_TYPE 1648 && type != B_UINT16_TYPE 1649 && type != B_UINT8_TYPE 1650 && type != B_INT64_TYPE 1651 && type != B_INT32_TYPE 1652 && type != B_INT16_TYPE 1653 && type != B_INT8_TYPE 1654 && type != B_OFF_T_TYPE 1655 && type != B_TIME_TYPE 1656 && type != B_FLOAT_TYPE 1657 && type != B_DOUBLE_TYPE 1658 && type != B_CHAR_TYPE 1659 && type != B_BOOL_TYPE) { 1660 BAlert* alert = new BAlert("", 1661 B_TRANSLATE("Sorry, you cannot edit that attribute."), 1662 B_TRANSLATE("Cancel"), 1663 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1664 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1665 alert->Go(); 1666 return false; 1667 } 1668 1669 const char* columnName = fColumn->AttrName(); 1670 ssize_t size = 0; 1671 1672 switch (type) { 1673 case B_STRING_TYPE: 1674 size = fModel->WriteAttr(columnName, type, 0, textView->Text(), 1675 (size_t)(textView->TextLength() + 1)); 1676 break; 1677 1678 case B_BOOL_TYPE: 1679 { 1680 bool value = strncasecmp(textView->Text(), "0", 1) != 0 1681 && strncasecmp(textView->Text(), "off", 2) != 0 1682 && strncasecmp(textView->Text(), "no", 3) != 0 1683 && strncasecmp(textView->Text(), "false", 4) != 0 1684 && strlen(textView->Text()) != 0; 1685 1686 size = fModel->WriteAttr(columnName, type, 0, &value, sizeof(bool)); 1687 break; 1688 } 1689 1690 case B_CHAR_TYPE: 1691 { 1692 char ch; 1693 sscanf(textView->Text(), "%c", &ch); 1694 //Check if we read the start of a multi-byte glyph: 1695 if (!isprint(ch)) { 1696 BAlert* alert = new BAlert("", 1697 B_TRANSLATE("Sorry, the 'Character' " 1698 "attribute cannot store a multi-byte glyph."), 1699 B_TRANSLATE("Cancel"), 1700 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1701 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1702 alert->Go(); 1703 return false; 1704 } 1705 1706 size = fModel->WriteAttr(columnName, type, 0, &ch, sizeof(char)); 1707 break; 1708 } 1709 1710 case B_FLOAT_TYPE: 1711 { 1712 float floatVal; 1713 1714 if (sscanf(textView->Text(), "%f", &floatVal) == 1) { 1715 fValueIsDefined = true; 1716 fValue.floatt = floatVal; 1717 size = fModel->WriteAttr(columnName, type, 0, &floatVal, 1718 sizeof(float)); 1719 } else { 1720 // If the value was already defined, it's on disk. 1721 // Otherwise not. 1722 return fValueIsDefined; 1723 } 1724 break; 1725 } 1726 1727 case B_DOUBLE_TYPE: 1728 { 1729 double doubleVal; 1730 1731 if (sscanf(textView->Text(), "%lf", &doubleVal) == 1) { 1732 fValueIsDefined = true; 1733 fValue.doublet = doubleVal; 1734 size = fModel->WriteAttr(columnName, type, 0, &doubleVal, 1735 sizeof(double)); 1736 } else { 1737 // If the value was already defined, it's on disk. 1738 // Otherwise not. 1739 return fValueIsDefined; 1740 } 1741 break; 1742 } 1743 1744 case B_TIME_TYPE: 1745 case B_OFF_T_TYPE: 1746 case B_UINT64_TYPE: 1747 case B_UINT32_TYPE: 1748 case B_UINT16_TYPE: 1749 case B_UINT8_TYPE: 1750 case B_INT64_TYPE: 1751 case B_INT32_TYPE: 1752 case B_INT16_TYPE: 1753 case B_INT8_TYPE: 1754 { 1755 GenericValueStruct tmp; 1756 size_t scalarSize = 0; 1757 1758 switch (type) { 1759 case B_TIME_TYPE: 1760 tmp.time_tt = parsedate(textView->Text(), time(0)); 1761 scalarSize = sizeof(time_t); 1762 break; 1763 1764 // do some size independent conversion on builtin types 1765 case B_OFF_T_TYPE: 1766 tmp.off_tt = StringToScalar(textView->Text()); 1767 scalarSize = sizeof(off_t); 1768 break; 1769 1770 case B_UINT64_TYPE: 1771 case B_INT64_TYPE: 1772 tmp.int64t = StringToScalar(textView->Text()); 1773 scalarSize = sizeof(int64); 1774 break; 1775 1776 case B_UINT32_TYPE: 1777 case B_INT32_TYPE: 1778 tmp.int32t = (int32)StringToScalar(textView->Text()); 1779 scalarSize = sizeof(int32); 1780 break; 1781 1782 case B_UINT16_TYPE: 1783 case B_INT16_TYPE: 1784 tmp.int16t = (int16)StringToScalar(textView->Text()); 1785 scalarSize = sizeof(int16); 1786 break; 1787 1788 case B_UINT8_TYPE: 1789 case B_INT8_TYPE: 1790 tmp.int8t = (int8)StringToScalar(textView->Text()); 1791 scalarSize = sizeof(int8); 1792 break; 1793 1794 default: 1795 TRESPASS(); 1796 1797 } 1798 1799 size = fModel->WriteAttr(columnName, type, 0, &tmp, scalarSize); 1800 break; 1801 } 1802 } 1803 1804 if (size < 0) { 1805 BAlert* alert = new BAlert("", 1806 B_TRANSLATE("There was an error writing the attribute."), 1807 B_TRANSLATE("Cancel"), 1808 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1809 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1810 alert->Go(); 1811 1812 fValueIsDefined = false; 1813 return false; 1814 } 1815 1816 fValueIsDefined = true; 1817 return true; 1818 } 1819 1820 1821 // #pragma mark - display as: duration 1822 1823 1824 DurationAttributeText::DurationAttributeText(const Model* model, 1825 const BColumn* column) 1826 : 1827 GenericAttributeText(model, column) 1828 { 1829 } 1830 1831 1832 // TODO: support editing! 1833 1834 1835 void 1836 DurationAttributeText::FitValue(BString* result, const BPoseView* view) 1837 { 1838 if (fValueDirty) 1839 ReadValue(&fFullValueText); 1840 1841 fOldWidth = fColumn->Width(); 1842 fDirty = false; 1843 1844 if (!fValueIsDefined) { 1845 *result = "-"; 1846 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1847 fFullValueText.Length(), view, fOldWidth); 1848 return; 1849 } 1850 1851 int64 time = 0; 1852 1853 switch (fColumn->AttrType()) { 1854 case B_TIME_TYPE: 1855 time = fValue.time_tt * 1000000LL; 1856 break; 1857 1858 case B_INT8_TYPE: 1859 time = fValue.int8t * 1000000LL; 1860 break; 1861 1862 case B_INT16_TYPE: 1863 time = fValue.int16t * 1000000LL; 1864 break; 1865 1866 case B_INT32_TYPE: 1867 time = fValue.int32t * 1000000LL; 1868 break; 1869 1870 case B_INT64_TYPE: 1871 time = fValue.int64t; 1872 break; 1873 } 1874 1875 // TODO: ignores micro seconds for now 1876 int32 seconds = time / 1000000LL; 1877 1878 bool negative = seconds < 0; 1879 if (negative) 1880 seconds = -seconds; 1881 1882 int32 hours = seconds / 3600; 1883 seconds -= hours * 3600; 1884 int32 minutes = seconds / 60; 1885 seconds = seconds % 60; 1886 1887 char buffer[256]; 1888 if (hours > 0) { 1889 snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32 ":%02" 1890 B_PRId32, negative ? "-" : "", hours, minutes, seconds); 1891 } else { 1892 snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32, 1893 negative ? "-" : "", minutes, seconds); 1894 } 1895 1896 fFullValueText = buffer; 1897 1898 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1899 fFullValueText.Length(), view, fOldWidth); 1900 } 1901 1902 1903 // #pragma mark - display as: checkbox 1904 1905 1906 CheckboxAttributeText::CheckboxAttributeText(const Model* model, 1907 const BColumn* column) 1908 : 1909 GenericAttributeText(model, column), 1910 fOnChar("✖"), 1911 fOffChar("-") 1912 { 1913 // TODO: better have common data in the column object! 1914 if (const char* separator = strchr(column->DisplayAs(), ':')) { 1915 BString chars(separator + 1); 1916 int32 length; 1917 const char* c = chars.CharAt(0, &length); 1918 fOnChar.SetTo(c, length); 1919 if (c[length]) { 1920 c = chars.CharAt(1, &length); 1921 fOffChar.SetTo(c, length); 1922 } 1923 } 1924 } 1925 1926 1927 void 1928 CheckboxAttributeText::SetUpEditing(BTextView* view) 1929 { 1930 // TODO: support editing for real! 1931 BString result; 1932 GenericAttributeText::FitValue(&result, NULL); 1933 GenericAttributeText::SetUpEditing(view); 1934 } 1935 1936 1937 void 1938 CheckboxAttributeText::FitValue(BString* result, const BPoseView* view) 1939 { 1940 if (fValueDirty) 1941 ReadValue(&fFullValueText); 1942 1943 fOldWidth = fColumn->Width(); 1944 fDirty = false; 1945 1946 if (!fValueIsDefined) { 1947 *result = fOffChar; 1948 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1949 fFullValueText.Length(), view, fOldWidth); 1950 return; 1951 } 1952 1953 bool checked = false; 1954 1955 switch (fColumn->AttrType()) { 1956 case B_BOOL_TYPE: 1957 checked = fValue.boolt; 1958 break; 1959 1960 case B_INT8_TYPE: 1961 case B_UINT8_TYPE: 1962 checked = fValue.int8t != 0; 1963 break; 1964 1965 case B_INT16_TYPE: 1966 case B_UINT16_TYPE: 1967 checked = fValue.int16t != 0; 1968 break; 1969 1970 case B_INT32_TYPE: 1971 case B_UINT32_TYPE: 1972 checked = fValue.int32t != 0; 1973 break; 1974 } 1975 1976 fFullValueText = checked ? fOnChar : fOffChar; 1977 1978 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1979 fFullValueText.Length(), view, fOldWidth); 1980 } 1981 1982 1983 // #pragma mark - display as: rating 1984 1985 1986 RatingAttributeText::RatingAttributeText(const Model* model, 1987 const BColumn* column) 1988 : 1989 GenericAttributeText(model, column), 1990 fCount(5), 1991 fMax(10) 1992 { 1993 // TODO: support different star counts/max via specifier 1994 } 1995 1996 1997 void 1998 RatingAttributeText::SetUpEditing(BTextView* view) 1999 { 2000 // TODO: support editing for real! 2001 BString result; 2002 GenericAttributeText::FitValue(&result, NULL); 2003 GenericAttributeText::SetUpEditing(view); 2004 } 2005 2006 2007 void 2008 RatingAttributeText::FitValue(BString* result, const BPoseView* view) 2009 { 2010 if (fValueDirty) 2011 ReadValue(&fFullValueText); 2012 2013 fOldWidth = fColumn->Width(); 2014 fDirty = false; 2015 2016 int64 rating = 0; 2017 2018 if (fValueIsDefined) { 2019 switch (fColumn->AttrType()) { 2020 case B_INT8_TYPE: 2021 rating = fValue.int8t; 2022 break; 2023 2024 case B_INT16_TYPE: 2025 rating = fValue.int16t; 2026 break; 2027 2028 case B_INT32_TYPE: 2029 rating = fValue.int32t; 2030 break; 2031 } 2032 } 2033 2034 if (rating > fMax) 2035 rating = fMax; 2036 if (rating < 0) 2037 rating = 0; 2038 2039 int32 steps = fMax / fCount; 2040 fFullValueText = ""; 2041 2042 for (int32 i = 0; i < fCount; i++) { 2043 if (rating > i * steps) 2044 fFullValueText += "★"; 2045 else 2046 fFullValueText += "☆"; 2047 } 2048 2049 fTruncatedWidth = TruncString(result, fFullValueText.String(), 2050 fFullValueText.Length(), view, fOldWidth); 2051 } 2052 2053 2054 // #pragma mark - 2055 2056 2057 OpenWithRelationAttributeText::OpenWithRelationAttributeText(const Model* model, 2058 const BColumn* column, const BPoseView* view) 2059 : 2060 ScalarAttributeText(model, column), 2061 fPoseView(view) 2062 { 2063 } 2064 2065 2066 int64 2067 OpenWithRelationAttributeText::ReadValue() 2068 { 2069 fValueDirty = false; 2070 2071 const OpenWithPoseView* view 2072 = dynamic_cast<const OpenWithPoseView*>(fPoseView); 2073 if (view != NULL) { 2074 fValue = view->OpenWithRelation(fModel); 2075 fValueIsDefined = true; 2076 } 2077 2078 return fValue; 2079 } 2080 2081 2082 float 2083 OpenWithRelationAttributeText::PreferredWidth(const BPoseView* pose) const 2084 { 2085 BString widthString; 2086 TruncString(&widthString, fRelationText.String(), fRelationText.Length(), 2087 pose, 500, B_TRUNCATE_END); 2088 return pose->StringWidth(widthString.String()); 2089 } 2090 2091 2092 void 2093 OpenWithRelationAttributeText::FitValue(BString* result, const BPoseView* view) 2094 { 2095 if (fValueDirty) 2096 ReadValue(); 2097 2098 ASSERT(view == fPoseView); 2099 const OpenWithPoseView* launchWithView 2100 = dynamic_cast<const OpenWithPoseView*>(view); 2101 2102 if (launchWithView) 2103 launchWithView->OpenWithRelationDescription(fModel, &fRelationText); 2104 2105 fOldWidth = fColumn->Width(); 2106 fTruncatedWidth = TruncString(result, fRelationText.String(), 2107 fRelationText.Length(), view, fOldWidth, B_TRUNCATE_END); 2108 fDirty = false; 2109 } 2110 2111 2112 VersionAttributeText::VersionAttributeText(const Model* model, 2113 const BColumn* column, bool app) 2114 : 2115 StringAttributeText(model, column), 2116 fAppVersion(app) 2117 { 2118 } 2119 2120 2121 void 2122 VersionAttributeText::ReadValue(BString* result) 2123 { 2124 fValueDirty = false; 2125 2126 BModelOpener opener(fModel); 2127 BFile* file = dynamic_cast<BFile*>(fModel->Node()); 2128 if (file) { 2129 BAppFileInfo info(file); 2130 version_info version; 2131 if (info.InitCheck() == B_OK 2132 && info.GetVersionInfo(&version, fAppVersion 2133 ? B_APP_VERSION_KIND : B_SYSTEM_VERSION_KIND) == B_OK) { 2134 *result = version.short_info; 2135 return; 2136 } 2137 } 2138 *result = "-"; 2139 } 2140