1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "WidgetAttributeText.h" 37 38 #include <ctype.h> 39 #include <stdlib.h> 40 #include <string.h> 41 42 #include <fs_attr.h> 43 #include <parsedate.h> 44 45 #include <Alert.h> 46 #include <AppFileInfo.h> 47 #include <Catalog.h> 48 #include <Debug.h> 49 #include <Locale.h> 50 #include <NodeInfo.h> 51 #include <Path.h> 52 #include <TextView.h> 53 #include <Volume.h> 54 #include <VolumeRoster.h> 55 56 #include "Attributes.h" 57 #include "FindPanel.h" 58 #include "FSUndoRedo.h" 59 #include "FSUtils.h" 60 #include "Model.h" 61 #include "OpenWithWindow.h" 62 #include "MimeTypes.h" 63 #include "PoseView.h" 64 #include "SettingsViews.h" 65 #include "Utilities.h" 66 #include "ViewState.h" 67 68 69 #undef B_TRANSLATE_CONTEXT 70 #define B_TRANSLATE_CONTEXT "WidgetAttributeText" 71 72 73 const int32 kGenericReadBufferSize = 1024; 74 75 const char* kSizeFormats[] = { 76 "%.2f %s", 77 "%.1f %s", 78 "%.f %s", 79 "%.f%s", 80 0 81 }; 82 83 84 bool NameAttributeText::sSortFolderNamesFirst = false; 85 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, width, 152 (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 && entry.GetPath(&path) == B_OK) { 424 tmp = path.Path(); 425 TruncateLeaf(&tmp); 426 } else 427 tmp = "-"; 428 429 if (width > 0) { 430 TruncStringBase(result, tmp.String(), tmp.Length(), view, 431 width); 432 } else 433 *result = tmp.String(); 434 435 return B_OK; 436 } 437 break; 438 439 case kSizeType: 440 // TruncFileSizeBase(result, model->StatBuf()->st_size, view, width); 441 return B_OK; 442 break; 443 444 default: 445 TRESPASS(); 446 // not yet supported 447 return B_ERROR; 448 449 } 450 451 TRESPASS(); 452 return B_ERROR; 453 } 454 455 456 bool 457 WidgetAttributeText::IsEditable() const 458 { 459 return fColumn->Editable() 460 && !BVolume(fModel->StatBuf()->st_dev).IsReadOnly(); 461 } 462 463 464 void 465 WidgetAttributeText::SetDirty(bool value) 466 { 467 fDirty = value; 468 } 469 470 471 // #pragma mark - 472 473 474 StringAttributeText::StringAttributeText(const Model* model, 475 const BColumn* column) 476 : 477 WidgetAttributeText(model, column), 478 fValueDirty(true) 479 { 480 } 481 482 483 const char* 484 StringAttributeText::ValueAsText(const BPoseView* /*view*/) 485 { 486 if (fValueDirty) 487 ReadValue(&fFullValueText); 488 489 return fFullValueText.String(); 490 } 491 492 493 bool 494 StringAttributeText::CheckAttributeChanged() 495 { 496 BString newString; 497 ReadValue(&newString); 498 499 if (newString == fFullValueText) 500 return false; 501 502 fFullValueText = newString; 503 fDirty = true; // have to redo fitted string 504 return true; 505 } 506 507 508 void 509 StringAttributeText::FitValue(BString* result, const BPoseView* view) 510 { 511 if (fValueDirty) 512 ReadValue(&fFullValueText); 513 fOldWidth = fColumn->Width(); 514 515 fTruncatedWidth = TruncString(result, fFullValueText.String(), 516 fFullValueText.Length(), view, fOldWidth); 517 fDirty = false; 518 } 519 520 521 float 522 StringAttributeText::PreferredWidth(const BPoseView* pose) const 523 { 524 return pose->StringWidth(fFullValueText.String()); 525 } 526 527 528 int 529 StringAttributeText::Compare(WidgetAttributeText& attr, BPoseView* view) 530 { 531 StringAttributeText* compareTo 532 = dynamic_cast<StringAttributeText*>(&attr); 533 ASSERT(compareTo); 534 535 if (fValueDirty) 536 ReadValue(&fFullValueText); 537 538 return NaturalCompare(fFullValueText.String(), 539 compareTo->ValueAsText(view)); 540 } 541 542 543 bool 544 StringAttributeText::CommitEditedText(BTextView* textView) 545 { 546 ASSERT(fColumn->Editable()); 547 const char* text = textView->Text(); 548 549 if (fFullValueText == text) { 550 // no change 551 return false; 552 } 553 554 if (textView->TextLength() == 0) { 555 // cannot do an empty name 556 return false; 557 } 558 559 // cause re-truncation 560 fDirty = true; 561 562 if (!CommitEditedTextFlavor(textView)) 563 return false; 564 565 // update text and width in this widget 566 fFullValueText = text; 567 568 return true; 569 } 570 571 572 // #pragma mark - 573 574 575 ScalarAttributeText::ScalarAttributeText(const Model* model, 576 const BColumn* column) 577 : 578 WidgetAttributeText(model, column), 579 fValueDirty(true) 580 { 581 } 582 583 584 int64 585 ScalarAttributeText::Value() 586 { 587 if (fValueDirty) 588 fValue = ReadValue(); 589 return fValue; 590 } 591 592 593 bool 594 ScalarAttributeText::CheckAttributeChanged() 595 { 596 int64 newValue = ReadValue(); 597 if (newValue == fValue) 598 return false; 599 600 fValue = newValue; 601 fDirty = true; // have to redo fitted string 602 return true; 603 } 604 605 606 float 607 ScalarAttributeText::PreferredWidth(const BPoseView* pose) const 608 { 609 BString widthString; 610 widthString << fValue; 611 return pose->StringWidth(widthString.String()); 612 } 613 614 615 int 616 ScalarAttributeText::Compare(WidgetAttributeText& attr, BPoseView*) 617 { 618 ScalarAttributeText* compareTo 619 = dynamic_cast<ScalarAttributeText*>(&attr); 620 ASSERT(compareTo); 621 // make sure we're not comparing apples and oranges 622 623 if (fValueDirty) 624 fValue = ReadValue(); 625 626 return fValue >= compareTo->Value() 627 ? (fValue == compareTo->Value() ? 0 : 1) : -1; 628 } 629 630 631 // #pragma mark - 632 633 634 PathAttributeText::PathAttributeText(const Model* model, const BColumn* column) 635 : 636 StringAttributeText(model, column) 637 { 638 } 639 640 641 void 642 PathAttributeText::ReadValue(BString* result) 643 { 644 // get the path 645 BEntry entry(fModel->EntryRef()); 646 BPath path; 647 648 if (entry.InitCheck() == B_OK && entry.GetPath(&path) == B_OK) { 649 *result = path.Path(); 650 TruncateLeaf(result); 651 } else 652 *result = "-"; 653 fValueDirty = false; 654 } 655 656 657 // #pragma mark - 658 659 660 OriginalPathAttributeText::OriginalPathAttributeText(const Model* model, 661 const BColumn* column) 662 : 663 StringAttributeText(model, column) 664 { 665 } 666 667 668 void 669 OriginalPathAttributeText::ReadValue(BString* result) 670 { 671 BEntry entry(fModel->EntryRef()); 672 BPath path; 673 674 // get the original path 675 if (entry.InitCheck() == B_OK && FSGetOriginalPath(&entry, &path) == B_OK) 676 *result = path.Path(); 677 else 678 *result = "-"; 679 fValueDirty = false; 680 } 681 682 683 // #pragma mark - 684 685 686 KindAttributeText::KindAttributeText(const Model* model, const BColumn* column) 687 : 688 StringAttributeText(model, column) 689 { 690 } 691 692 693 void 694 KindAttributeText::ReadValue(BString* result) 695 { 696 BMimeType mime; 697 char desc[B_MIME_TYPE_LENGTH]; 698 699 // get the mime type 700 if (mime.SetType(fModel->MimeType()) != B_OK) 701 *result = B_TRANSLATE("Unknown"); 702 // get the short mime type description 703 else if (mime.GetShortDescription(desc) == B_OK) 704 *result = desc; 705 else 706 *result = fModel->MimeType(); 707 fValueDirty = false; 708 } 709 710 711 // #pragma mark - 712 713 714 NameAttributeText::NameAttributeText(const Model* model, const BColumn* column) 715 : 716 StringAttributeText(model, column) 717 { 718 } 719 720 721 int 722 NameAttributeText::Compare(WidgetAttributeText& attr, BPoseView* view) 723 { 724 NameAttributeText* compareTo = dynamic_cast<NameAttributeText*>(&attr); 725 726 ASSERT(compareTo); 727 728 if (fValueDirty) 729 ReadValue(&fFullValueText); 730 731 if (NameAttributeText::sSortFolderNamesFirst) 732 return fModel->CompareFolderNamesFirst(attr.TargetModel()); 733 734 return NaturalCompare(fFullValueText.String(), 735 compareTo->ValueAsText(view)); 736 } 737 738 739 void 740 NameAttributeText::ReadValue(BString* result) 741 { 742 *result = fModel->Name(); 743 744 fValueDirty = false; 745 } 746 747 748 void 749 NameAttributeText::FitValue(BString* result, const BPoseView* view) 750 { 751 if (fValueDirty) 752 ReadValue(&fFullValueText); 753 fOldWidth = fColumn->Width(); 754 fTruncatedWidth = TruncString(result, fFullValueText.String(), 755 fFullValueText.Length(), view, fOldWidth, B_TRUNCATE_END); 756 fDirty = false; 757 } 758 759 760 void 761 NameAttributeText::SetUpEditing(BTextView* textView) 762 { 763 DisallowFilenameKeys(textView); 764 765 textView->SetMaxBytes(B_FILE_NAME_LENGTH); 766 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 767 } 768 769 770 bool 771 NameAttributeText::CommitEditedTextFlavor(BTextView* textView) 772 { 773 const char* text = textView->Text(); 774 775 BEntry entry(fModel->EntryRef()); 776 if (entry.InitCheck() != B_OK) 777 return false; 778 779 BDirectory parent; 780 if (entry.GetParent(&parent) != B_OK) 781 return false; 782 783 bool removeExisting = false; 784 if (parent.Contains(text)) { 785 BAlert* alert = new BAlert("", 786 B_TRANSLATE("That name is already taken. " 787 "Please type another one."), 788 B_TRANSLATE("Replace other file"), 789 B_TRANSLATE("OK"), 790 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 791 792 alert->SetShortcut(0, 'r'); 793 794 if (alert->Go()) 795 return false; 796 797 removeExisting = true; 798 } 799 800 // TODO: 801 // use model-flavor specific virtuals for all of these special 802 // renamings 803 status_t result; 804 if (fModel->IsVolume()) { 805 BVolume volume(fModel->NodeRef()->device); 806 result = volume.InitCheck(); 807 if (result == B_OK) { 808 RenameVolumeUndo undo(volume, text); 809 810 result = volume.SetName(text); 811 if (result != B_OK) 812 undo.Remove(); 813 } 814 } else { 815 if (fModel->IsQuery()) { 816 BModelWriteOpener opener(fModel); 817 ASSERT(fModel->Node()); 818 MoreOptionsStruct::SetQueryTemporary(fModel->Node(), false); 819 } 820 821 RenameUndo undo(entry, text); 822 823 result = entry.Rename(text, removeExisting); 824 if (result != B_OK) 825 undo.Remove(); 826 } 827 828 return result == B_OK; 829 } 830 831 832 void 833 NameAttributeText::SetSortFolderNamesFirst(bool enabled) 834 { 835 NameAttributeText::sSortFolderNamesFirst = enabled; 836 } 837 838 839 bool 840 NameAttributeText::IsEditable() const 841 { 842 return StringAttributeText::IsEditable() 843 && !fModel->HasLocalizedName(); 844 } 845 846 847 // #pragma mark - 848 849 850 RealNameAttributeText::RealNameAttributeText(const Model* model, 851 const BColumn* column) 852 : 853 StringAttributeText(model, column) 854 { 855 } 856 857 858 int 859 RealNameAttributeText::Compare(WidgetAttributeText& attr, BPoseView* view) 860 { 861 RealNameAttributeText* compareTo 862 = dynamic_cast<RealNameAttributeText*>(&attr); 863 864 ASSERT(compareTo); 865 866 if (fValueDirty) 867 ReadValue(&fFullValueText); 868 869 if (RealNameAttributeText::sSortFolderNamesFirst) 870 return fModel->CompareFolderNamesFirst(attr.TargetModel()); 871 872 return NaturalCompare(fFullValueText.String(), 873 compareTo->ValueAsText(view)); 874 } 875 876 877 void 878 RealNameAttributeText::ReadValue(BString* result) 879 { 880 *result = fModel->EntryRef()->name; 881 882 fValueDirty = false; 883 } 884 885 886 void 887 RealNameAttributeText::FitValue(BString* result, const BPoseView* view) 888 { 889 if (fValueDirty) 890 ReadValue(&fFullValueText); 891 fOldWidth = fColumn->Width(); 892 fTruncatedWidth = TruncString(result, fFullValueText.String(), 893 fFullValueText.Length(), view, fOldWidth, B_TRUNCATE_END); 894 fDirty = false; 895 } 896 897 898 void 899 RealNameAttributeText::SetUpEditing(BTextView* textView) 900 { 901 DisallowFilenameKeys(textView); 902 903 textView->SetMaxBytes(B_FILE_NAME_LENGTH); 904 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 905 } 906 907 908 bool 909 RealNameAttributeText::CommitEditedTextFlavor(BTextView* textView) 910 { 911 const char* text = textView->Text(); 912 913 BEntry entry(fModel->EntryRef()); 914 if (entry.InitCheck() != B_OK) 915 return false; 916 917 BDirectory parent; 918 if (entry.GetParent(&parent) != B_OK) 919 return false; 920 921 bool removeExisting = false; 922 if (parent.Contains(text)) { 923 BAlert* alert = new BAlert("", 924 B_TRANSLATE("That name is already taken. " 925 "Please type another one."), 926 B_TRANSLATE("Replace other file"), 927 B_TRANSLATE("OK"), 928 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 929 930 alert->SetShortcut(0, 'r'); 931 932 if (alert->Go()) 933 return false; 934 935 removeExisting = true; 936 } 937 938 // TODO: 939 // use model-flavor specific virtuals for all of these special 940 // renamings 941 status_t result; 942 if (fModel->IsVolume()) { 943 BVolume volume(fModel->NodeRef()->device); 944 result = volume.InitCheck(); 945 if (result == B_OK) { 946 RenameVolumeUndo undo(volume, text); 947 948 result = volume.SetName(text); 949 if (result != B_OK) 950 undo.Remove(); 951 } 952 } else { 953 if (fModel->IsQuery()) { 954 BModelWriteOpener opener(fModel); 955 ASSERT(fModel->Node()); 956 MoreOptionsStruct::SetQueryTemporary(fModel->Node(), false); 957 } 958 959 RenameUndo undo(entry, text); 960 961 result = entry.Rename(text, removeExisting); 962 if (result != B_OK) 963 undo.Remove(); 964 } 965 966 return result == B_OK; 967 } 968 969 970 void 971 RealNameAttributeText::SetSortFolderNamesFirst(bool enabled) 972 { 973 RealNameAttributeText::sSortFolderNamesFirst = enabled; 974 } 975 976 977 // #pragma mark - owner/group 978 979 980 #ifdef OWNER_GROUP_ATTRIBUTES 981 982 983 OwnerAttributeText::OwnerAttributeText(const Model* model, 984 const BColumn* column) 985 : 986 StringAttributeText(model, column) 987 { 988 } 989 990 991 void 992 OwnerAttributeText::ReadValue(BString* result) 993 { 994 uid_t nodeOwner = fModel->StatBuf()->st_uid; 995 BString user; 996 997 if (nodeOwner == 0) { 998 if (getenv("USER") != NULL) 999 user << getenv("USER"); 1000 else 1001 user << "root"; 1002 } else 1003 user << nodeOwner; 1004 *result = user.String(); 1005 1006 fValueDirty = false; 1007 } 1008 1009 1010 GroupAttributeText::GroupAttributeText(const Model* model, 1011 const BColumn* column) 1012 : 1013 StringAttributeText(model, column) 1014 { 1015 } 1016 1017 1018 void 1019 GroupAttributeText::ReadValue(BString* result) 1020 { 1021 gid_t nodeGroup = fModel->StatBuf()->st_gid; 1022 BString group; 1023 1024 if (nodeGroup == 0) { 1025 if (getenv("GROUP") != NULL) 1026 group << getenv("GROUP"); 1027 else 1028 group << "0"; 1029 } else 1030 group << nodeGroup; 1031 *result = group.String(); 1032 1033 fValueDirty = false; 1034 } 1035 1036 1037 #endif // OWNER_GROUP_ATTRIBUTES 1038 1039 1040 ModeAttributeText::ModeAttributeText(const Model* model, const BColumn* column) 1041 : 1042 StringAttributeText(model, column) 1043 { 1044 } 1045 1046 1047 void 1048 ModeAttributeText::ReadValue(BString* result) 1049 { 1050 mode_t mode = fModel->StatBuf()->st_mode; 1051 mode_t baseMask = 00400; 1052 char buffer[11]; 1053 1054 char* scanner = buffer; 1055 1056 if (S_ISDIR(mode)) 1057 *scanner++ = 'd'; 1058 else if (S_ISLNK(mode)) 1059 *scanner++ = 'l'; 1060 else if (S_ISBLK(mode)) 1061 *scanner++ = 'b'; 1062 else if (S_ISCHR(mode)) 1063 *scanner++ = 'c'; 1064 else 1065 *scanner++ = '-'; 1066 1067 for (int32 index = 0; index < 9; index++) { 1068 *scanner++ = (mode & baseMask) ? "rwx"[index % 3] : '-'; 1069 baseMask >>= 1; 1070 } 1071 1072 *scanner = 0; 1073 *result = buffer; 1074 1075 fValueDirty = false; 1076 } 1077 1078 1079 // #pragma mark - 1080 1081 1082 SizeAttributeText::SizeAttributeText(const Model* model, const BColumn* column) 1083 : 1084 ScalarAttributeText(model, column) 1085 { 1086 } 1087 1088 1089 int64 1090 SizeAttributeText::ReadValue() 1091 { 1092 fValueDirty = false; 1093 // get the size 1094 1095 if (fModel->IsVolume()) { 1096 BVolume volume(fModel->NodeRef()->device); 1097 1098 return volume.Capacity(); 1099 } 1100 1101 if (fModel->IsDirectory() || fModel->IsQuery() 1102 || fModel->IsQueryTemplate() || fModel->IsSymLink()) 1103 return kUnknownSize; 1104 1105 fValueIsDefined = true; 1106 1107 return fModel->StatBuf()->st_size; 1108 } 1109 1110 1111 void 1112 SizeAttributeText::FitValue(BString* result, const BPoseView* view) 1113 { 1114 if (fValueDirty) 1115 fValue = ReadValue(); 1116 fOldWidth = fColumn->Width(); 1117 fTruncatedWidth = TruncFileSize(result, fValue, view, fOldWidth); 1118 fDirty = false; 1119 } 1120 1121 1122 float 1123 SizeAttributeText::PreferredWidth(const BPoseView* pose) const 1124 { 1125 if (fValueIsDefined) { 1126 BString widthString; 1127 TruncFileSize(&widthString, fValue, pose, 100000); 1128 return pose->StringWidth(widthString.String()); 1129 } 1130 return pose->StringWidth("-"); 1131 } 1132 1133 1134 // #pragma mark - time related 1135 1136 1137 TimeAttributeText::TimeAttributeText(const Model* model, const BColumn* column) 1138 : 1139 ScalarAttributeText(model, column) 1140 { 1141 } 1142 1143 1144 float 1145 TimeAttributeText::PreferredWidth(const BPoseView* pose) const 1146 { 1147 BString widthString; 1148 TruncTimeBase(&widthString, fValue, pose, 100000); 1149 return pose->StringWidth(widthString.String()); 1150 } 1151 1152 1153 void 1154 TimeAttributeText::FitValue(BString* result, const BPoseView* view) 1155 { 1156 if (fValueDirty) 1157 fValue = ReadValue(); 1158 fOldWidth = fColumn->Width(); 1159 fTruncatedWidth = TruncTime(result, fValue, view, fOldWidth); 1160 fDirty = false; 1161 } 1162 1163 1164 bool 1165 TimeAttributeText::CheckSettingsChanged(void) 1166 { 1167 // TODO : check against the actual locale settings 1168 return false; 1169 } 1170 1171 1172 CreationTimeAttributeText::CreationTimeAttributeText(const Model* model, 1173 const BColumn* column) 1174 : 1175 TimeAttributeText(model, column) 1176 { 1177 } 1178 1179 1180 int64 1181 CreationTimeAttributeText::ReadValue() 1182 { 1183 fValueDirty = false; 1184 fValueIsDefined = true; 1185 return fModel->StatBuf()->st_crtime; 1186 } 1187 1188 1189 ModificationTimeAttributeText::ModificationTimeAttributeText(const Model* model, 1190 const BColumn* column) 1191 : 1192 TimeAttributeText(model, column) 1193 { 1194 } 1195 1196 1197 int64 1198 ModificationTimeAttributeText::ReadValue() 1199 { 1200 fValueDirty = false; 1201 fValueIsDefined = true; 1202 return fModel->StatBuf()->st_mtime; 1203 } 1204 1205 1206 // #pragma mark - 1207 1208 1209 GenericAttributeText::GenericAttributeText(const Model* model, 1210 const BColumn* column) 1211 : 1212 StringAttributeText(model, column) 1213 { 1214 } 1215 1216 1217 bool 1218 GenericAttributeText::CheckAttributeChanged() 1219 { 1220 GenericValueStruct tmpValue = fValue; 1221 BString tmpString(fFullValueText); 1222 ReadValue(&fFullValueText); 1223 1224 // fDirty could already be true, in that case we mustn't set it to 1225 // false, even if the attribute text hasn't changed 1226 bool changed = fValue.int64t != tmpValue.int64t 1227 || tmpString != fFullValueText; 1228 if (changed) 1229 fDirty = true; 1230 1231 return fDirty; 1232 } 1233 1234 1235 float 1236 GenericAttributeText::PreferredWidth(const BPoseView* pose) const 1237 { 1238 return pose->StringWidth(fFullValueText.String()); 1239 } 1240 1241 1242 void 1243 GenericAttributeText::ReadValue(BString* result) 1244 { 1245 BModelOpener opener(const_cast<Model*>(fModel)); 1246 1247 ssize_t length = 0; 1248 fFullValueText = "-"; 1249 fValue.int64t = 0; 1250 fValueIsDefined = false; 1251 fValueDirty = false; 1252 1253 if (!fModel->Node()) 1254 return; 1255 1256 switch (fColumn->AttrType()) { 1257 case B_STRING_TYPE: 1258 { 1259 char buffer[kGenericReadBufferSize]; 1260 length = fModel->Node()->ReadAttr(fColumn->AttrName(), 1261 fColumn->AttrType(), 0, buffer, kGenericReadBufferSize - 1); 1262 1263 if (length > 0) { 1264 buffer[length] = '\0'; 1265 // make sure the buffer is null-terminated even if we 1266 // didn't read the whole attribute in or it wasn't to 1267 // begin with 1268 1269 *result = buffer; 1270 fValueIsDefined = true; 1271 } 1272 break; 1273 } 1274 1275 case B_SSIZE_T_TYPE: 1276 case B_TIME_TYPE: 1277 case B_OFF_T_TYPE: 1278 case B_FLOAT_TYPE: 1279 case B_BOOL_TYPE: 1280 case B_CHAR_TYPE: 1281 case B_INT8_TYPE: 1282 case B_INT16_TYPE: 1283 case B_INT32_TYPE: 1284 case B_INT64_TYPE: 1285 case B_UINT8_TYPE: 1286 case B_UINT16_TYPE: 1287 case B_UINT32_TYPE: 1288 case B_UINT64_TYPE: 1289 case B_DOUBLE_TYPE: 1290 { 1291 // read in the numerical bit representation and attach it 1292 // with a type, depending on the bytes that could be read 1293 attr_info info; 1294 GenericValueStruct tmp; 1295 if (fModel->Node()->GetAttrInfo(fColumn->AttrName(), &info) == B_OK) { 1296 if (info.size && info.size <= sizeof(int64)) { 1297 length = fModel->Node()->ReadAttr(fColumn->AttrName(), 1298 fColumn->AttrType(), 0, &tmp, (size_t)info.size); 1299 } 1300 1301 // We used tmp as a block of memory, now set the correct fValue: 1302 1303 if (length == info.size) { 1304 if (fColumn->AttrType() == B_FLOAT_TYPE 1305 || fColumn->AttrType() == B_DOUBLE_TYPE) { 1306 // filter out special float/double types 1307 switch (info.size) { 1308 case sizeof(float): 1309 fValueIsDefined = true; 1310 fValue.floatt = tmp.floatt; 1311 break; 1312 1313 case sizeof(double): 1314 fValueIsDefined = true; 1315 fValue.doublet = tmp.doublet; 1316 break; 1317 1318 default: 1319 TRESPASS(); 1320 } 1321 } else { 1322 // handle the standard data types 1323 switch (info.size) { 1324 case sizeof(char): // Takes care of bool, too. 1325 fValueIsDefined = true; 1326 fValue.int8t = tmp.int8t; 1327 break; 1328 1329 case sizeof(int16): 1330 fValueIsDefined = true; 1331 fValue.int16t = tmp.int16t; 1332 break; 1333 1334 case sizeof(int32): // Takes care of time_t, too. 1335 fValueIsDefined = true; 1336 fValue.int32t = tmp.int32t; 1337 break; 1338 1339 case sizeof(int64): // Taked care of off_t, too. 1340 fValueIsDefined = true; 1341 fValue.int64t = tmp.int64t; 1342 break; 1343 1344 default: 1345 TRESPASS(); 1346 } 1347 } 1348 } 1349 } 1350 break; 1351 } 1352 } 1353 } 1354 1355 1356 void 1357 GenericAttributeText::FitValue(BString* result, const BPoseView* view) 1358 { 1359 if (fValueDirty) 1360 ReadValue(&fFullValueText); 1361 1362 fOldWidth = fColumn->Width(); 1363 1364 if (!fValueIsDefined) { 1365 *result = "-"; 1366 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1367 fFullValueText.Length(), view, fOldWidth); 1368 fDirty = false; 1369 return; 1370 } 1371 1372 char buffer[256]; 1373 1374 switch (fColumn->AttrType()) { 1375 case B_SIZE_T_TYPE: 1376 TruncFileSizeBase(result, fValue.int32t, view, fOldWidth); 1377 return; 1378 1379 case B_SSIZE_T_TYPE: 1380 if (fValue.int32t > 0) { 1381 TruncFileSizeBase(result, fValue.int32t, view, fOldWidth); 1382 return; 1383 } 1384 sprintf(buffer, "%s", strerror(fValue.int32t)); 1385 fFullValueText = buffer; 1386 break; 1387 1388 case B_STRING_TYPE: 1389 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1390 fFullValueText.Length(), view, fOldWidth); 1391 fDirty = false; 1392 return; 1393 1394 case B_OFF_T_TYPE: 1395 // As a side effect update the fFullValueText to the string 1396 // representation of value 1397 TruncFileSize(&fFullValueText, fValue.off_tt, view, 100000); 1398 fTruncatedWidth = TruncFileSize(result, fValue.off_tt, view, 1399 fOldWidth); 1400 fDirty = false; 1401 return; 1402 1403 case B_TIME_TYPE: 1404 // As a side effect update the fFullValueText to the string 1405 // representation of value 1406 TruncTime(&fFullValueText, fValue.time_tt, view, 100000); 1407 fTruncatedWidth = TruncTime(result, fValue.time_tt, view, 1408 fOldWidth); 1409 fDirty = false; 1410 return; 1411 1412 case B_BOOL_TYPE: 1413 // For now use true/false, would be nice to be able to set 1414 // the value text 1415 1416 sprintf(buffer, "%s", fValue.boolt ? "true" : "false"); 1417 fFullValueText = buffer; 1418 break; 1419 1420 case B_CHAR_TYPE: 1421 // Make sure no non-printable characters are displayed: 1422 if (!isprint(fValue.uint8t)) { 1423 *result = "-"; 1424 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1425 fFullValueText.Length(), view, fOldWidth); 1426 fDirty = false; 1427 return; 1428 } 1429 1430 sprintf(buffer, "%c", fValue.uint8t); 1431 fFullValueText = buffer; 1432 break; 1433 1434 case B_INT8_TYPE: 1435 sprintf(buffer, "%d", fValue.int8t); 1436 fFullValueText = buffer; 1437 break; 1438 1439 case B_UINT8_TYPE: 1440 sprintf(buffer, "%d", fValue.uint8t); 1441 fFullValueText = buffer; 1442 break; 1443 1444 case B_INT16_TYPE: 1445 sprintf(buffer, "%d", fValue.int16t); 1446 fFullValueText = buffer; 1447 break; 1448 1449 case B_UINT16_TYPE: 1450 sprintf(buffer, "%d", fValue.uint16t); 1451 fFullValueText = buffer; 1452 break; 1453 1454 case B_INT32_TYPE: 1455 sprintf(buffer, "%ld", fValue.int32t); 1456 fFullValueText = buffer; 1457 break; 1458 1459 case B_UINT32_TYPE: 1460 sprintf(buffer, "%ld", fValue.uint32t); 1461 fFullValueText = buffer; 1462 break; 1463 1464 case B_INT64_TYPE: 1465 sprintf(buffer, "%Ld", fValue.int64t); 1466 fFullValueText = buffer; 1467 break; 1468 1469 case B_UINT64_TYPE: 1470 sprintf(buffer, "%Ld", fValue.uint64t); 1471 fFullValueText = buffer; 1472 break; 1473 1474 case B_FLOAT_TYPE: 1475 snprintf(buffer, sizeof(buffer), "%g", fValue.floatt); 1476 fFullValueText = buffer; 1477 break; 1478 1479 case B_DOUBLE_TYPE: 1480 snprintf(buffer, sizeof(buffer), "%g", fValue.doublet); 1481 fFullValueText = buffer; 1482 break; 1483 1484 default: 1485 *result = "-"; 1486 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1487 fFullValueText.Length(), view, fOldWidth); 1488 fDirty = false; 1489 return; 1490 } 1491 fTruncatedWidth = TruncString(result, buffer, (ssize_t)strlen(buffer), view, 1492 fOldWidth); 1493 fDirty = false; 1494 } 1495 1496 1497 const char* 1498 GenericAttributeText::ValueAsText(const BPoseView* view) 1499 { 1500 // TODO: redesign this - this is to make sure the value is valid 1501 bool oldDirty = fDirty; 1502 BString result; 1503 FitValue(&result, view); 1504 fDirty = oldDirty; 1505 1506 return fFullValueText.String(); 1507 } 1508 1509 1510 int 1511 GenericAttributeText::Compare(WidgetAttributeText& attr, BPoseView*) 1512 { 1513 GenericAttributeText* compareTo 1514 = dynamic_cast<GenericAttributeText*>(&attr); 1515 ASSERT(compareTo); 1516 1517 if (fValueDirty) 1518 ReadValue(&fFullValueText); 1519 if (compareTo->fValueDirty) 1520 compareTo->ReadValue(&compareTo->fFullValueText); 1521 1522 // Sort undefined values last, regardless of the other value: 1523 if (!fValueIsDefined) 1524 return compareTo->fValueIsDefined ? 1 : 0; 1525 if (!compareTo->fValueIsDefined) 1526 return -1; 1527 1528 switch (fColumn->AttrType()) { 1529 case B_STRING_TYPE: 1530 return fFullValueText.ICompare(compareTo->fFullValueText); 1531 1532 case B_CHAR_TYPE: 1533 { 1534 char vStr[2] = { static_cast<char>(fValue.uint8t), 0 }; 1535 char cStr[2] = { static_cast<char>(compareTo->fValue.uint8t), 0}; 1536 1537 BString valueStr(vStr); 1538 BString compareToStr(cStr); 1539 1540 return valueStr.ICompare(compareToStr); 1541 } 1542 1543 case B_FLOAT_TYPE: 1544 return fValue.floatt >= compareTo->fValue.floatt ? 1545 (fValue.floatt == compareTo->fValue.floatt ? 0 : 1) : -1; 1546 1547 case B_DOUBLE_TYPE: 1548 return fValue.doublet >= compareTo->fValue.doublet ? 1549 (fValue.doublet == compareTo->fValue.doublet ? 0 : 1) : -1; 1550 1551 case B_BOOL_TYPE: 1552 return fValue.boolt >= compareTo->fValue.boolt ? 1553 (fValue.boolt == compareTo->fValue.boolt ? 0 : 1) : -1; 1554 1555 case B_UINT8_TYPE: 1556 return fValue.uint8t >= compareTo->fValue.uint8t ? 1557 (fValue.uint8t == compareTo->fValue.uint8t ? 0 : 1) : -1; 1558 1559 case B_INT8_TYPE: 1560 return fValue.int8t >= compareTo->fValue.int8t ? 1561 (fValue.int8t == compareTo->fValue.int8t ? 0 : 1) : -1; 1562 1563 case B_UINT16_TYPE: 1564 return fValue.uint16t >= compareTo->fValue.uint16t ? 1565 (fValue.uint16t == compareTo->fValue.uint16t ? 0 : 1) : -1; 1566 1567 case B_INT16_TYPE: 1568 return fValue.int16t >= compareTo->fValue.int16t ? 1569 (fValue.int16t == compareTo->fValue.int16t ? 0 : 1) : -1; 1570 1571 case B_UINT32_TYPE: 1572 return fValue.uint32t >= compareTo->fValue.uint32t ? 1573 (fValue.uint32t == compareTo->fValue.uint32t ? 0 : 1) : -1; 1574 1575 case B_TIME_TYPE: 1576 // time_t typedef'd to a long, i.e. a int32 1577 case B_INT32_TYPE: 1578 return fValue.int32t >= compareTo->fValue.int32t ? 1579 (fValue.int32t == compareTo->fValue.int32t ? 0 : 1) : -1; 1580 1581 case B_OFF_T_TYPE: 1582 // off_t typedef'd to a long long, i.e. a int64 1583 case B_INT64_TYPE: 1584 return fValue.int64t >= compareTo->fValue.int64t ? 1585 (fValue.int64t == compareTo->fValue.int64t ? 0 : 1) : -1; 1586 1587 case B_UINT64_TYPE: 1588 default: 1589 return fValue.uint64t >= compareTo->fValue.uint64t ? 1590 (fValue.uint64t == compareTo->fValue.uint64t ? 0 : 1) : -1; 1591 } 1592 return 0; 1593 } 1594 1595 1596 bool 1597 GenericAttributeText::CommitEditedText(BTextView* textView) 1598 { 1599 ASSERT(fColumn->Editable()); 1600 const char* text = textView->Text(); 1601 1602 if (fFullValueText == text) 1603 // no change 1604 return false; 1605 1606 if (!CommitEditedTextFlavor(textView)) 1607 return false; 1608 1609 // update text and width in this widget 1610 fFullValueText = text; 1611 // cause re-truncation 1612 fDirty = true; 1613 fValueDirty = true; 1614 1615 return true; 1616 } 1617 1618 1619 void 1620 GenericAttributeText::SetUpEditing(BTextView* textView) 1621 { 1622 textView->SetMaxBytes(kGenericReadBufferSize - 1); 1623 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 1624 } 1625 1626 1627 bool 1628 GenericAttributeText::CommitEditedTextFlavor(BTextView* textView) 1629 { 1630 BNode node(fModel->EntryRef()); 1631 1632 if (node.InitCheck() != B_OK) 1633 return false; 1634 1635 uint32 type = fColumn->AttrType(); 1636 1637 if (type != B_STRING_TYPE 1638 && type != B_UINT64_TYPE 1639 && type != B_UINT32_TYPE 1640 && type != B_UINT16_TYPE 1641 && type != B_UINT8_TYPE 1642 && type != B_INT64_TYPE 1643 && type != B_INT32_TYPE 1644 && type != B_INT16_TYPE 1645 && type != B_INT8_TYPE 1646 && type != B_OFF_T_TYPE 1647 && type != B_TIME_TYPE 1648 && type != B_FLOAT_TYPE 1649 && type != B_DOUBLE_TYPE 1650 && type != B_CHAR_TYPE 1651 && type != B_BOOL_TYPE) { 1652 BAlert* alert = new BAlert("", 1653 B_TRANSLATE("Sorry, you cannot edit that attribute."), 1654 B_TRANSLATE("Cancel"), 1655 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1656 alert->SetShortcut(0, B_ESCAPE); 1657 alert->Go(); 1658 return false; 1659 } 1660 1661 const char* columnName = fColumn->AttrName(); 1662 ssize_t size = 0; 1663 1664 switch (type) { 1665 case B_STRING_TYPE: 1666 size = fModel->WriteAttr(columnName, type, 0, textView->Text(), 1667 (size_t)(textView->TextLength() + 1)); 1668 break; 1669 1670 case B_BOOL_TYPE: 1671 { 1672 bool value = strncasecmp(textView->Text(), "0", 1) != 0 1673 && strncasecmp(textView->Text(), "off", 2) != 0 1674 && strncasecmp(textView->Text(), "no", 3) != 0 1675 && strncasecmp(textView->Text(), "false", 4) != 0 1676 && strlen(textView->Text()) != 0; 1677 1678 size = fModel->WriteAttr(columnName, type, 0, &value, sizeof(bool)); 1679 break; 1680 } 1681 1682 case B_CHAR_TYPE: 1683 { 1684 char ch; 1685 sscanf(textView->Text(), "%c", &ch); 1686 //Check if we read the start of a multi-byte glyph: 1687 if (!isprint(ch)) { 1688 BAlert* alert = new BAlert("", 1689 B_TRANSLATE("Sorry, the 'Character' " 1690 "attribute cannot store a multi-byte glyph."), 1691 B_TRANSLATE("Cancel"), 1692 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1693 alert->SetShortcut(0, B_ESCAPE); 1694 alert->Go(); 1695 return false; 1696 } 1697 1698 size = fModel->WriteAttr(columnName, type, 0, &ch, sizeof(char)); 1699 break; 1700 } 1701 1702 case B_FLOAT_TYPE: 1703 { 1704 float floatVal; 1705 1706 if (sscanf(textView->Text(), "%f", &floatVal) == 1) { 1707 fValueIsDefined = true; 1708 fValue.floatt = floatVal; 1709 size = fModel->WriteAttr(columnName, type, 0, &floatVal, 1710 sizeof(float)); 1711 } else { 1712 // If the value was already defined, it's on disk. 1713 // Otherwise not. 1714 return fValueIsDefined; 1715 } 1716 break; 1717 } 1718 1719 case B_DOUBLE_TYPE: 1720 { 1721 double doubleVal; 1722 1723 if (sscanf(textView->Text(), "%lf", &doubleVal) == 1) { 1724 fValueIsDefined = true; 1725 fValue.doublet = doubleVal; 1726 size = fModel->WriteAttr(columnName, type, 0, &doubleVal, 1727 sizeof(double)); 1728 } else { 1729 // If the value was already defined, it's on disk. 1730 // Otherwise not. 1731 return fValueIsDefined; 1732 } 1733 break; 1734 } 1735 1736 case B_TIME_TYPE: 1737 case B_OFF_T_TYPE: 1738 case B_UINT64_TYPE: 1739 case B_UINT32_TYPE: 1740 case B_UINT16_TYPE: 1741 case B_UINT8_TYPE: 1742 case B_INT64_TYPE: 1743 case B_INT32_TYPE: 1744 case B_INT16_TYPE: 1745 case B_INT8_TYPE: 1746 { 1747 GenericValueStruct tmp; 1748 size_t scalarSize = 0; 1749 1750 switch (type) { 1751 case B_TIME_TYPE: 1752 tmp.time_tt = parsedate(textView->Text(), time(0)); 1753 scalarSize = sizeof(time_t); 1754 break; 1755 1756 // do some size independent conversion on builtin types 1757 case B_OFF_T_TYPE: 1758 tmp.off_tt = StringToScalar(textView->Text()); 1759 scalarSize = sizeof(off_t); 1760 break; 1761 1762 case B_UINT64_TYPE: 1763 case B_INT64_TYPE: 1764 tmp.int64t = StringToScalar(textView->Text()); 1765 scalarSize = sizeof(int64); 1766 break; 1767 1768 case B_UINT32_TYPE: 1769 case B_INT32_TYPE: 1770 tmp.int32t = (int32)StringToScalar(textView->Text()); 1771 scalarSize = sizeof(int32); 1772 break; 1773 1774 case B_UINT16_TYPE: 1775 case B_INT16_TYPE: 1776 tmp.int16t = (int16)StringToScalar(textView->Text()); 1777 scalarSize = sizeof(int16); 1778 break; 1779 1780 case B_UINT8_TYPE: 1781 case B_INT8_TYPE: 1782 tmp.int8t = (int8)StringToScalar(textView->Text()); 1783 scalarSize = sizeof(int8); 1784 break; 1785 1786 default: 1787 TRESPASS(); 1788 1789 } 1790 1791 size = fModel->WriteAttr(columnName, type, 0, &tmp, scalarSize); 1792 break; 1793 } 1794 } 1795 1796 if (size < 0) { 1797 BAlert* alert = new BAlert("", 1798 B_TRANSLATE("There was an error writing the attribute."), 1799 B_TRANSLATE("Cancel"), 1800 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1801 alert->SetShortcut(0, B_ESCAPE); 1802 alert->Go(); 1803 1804 fValueIsDefined = false; 1805 return false; 1806 } 1807 1808 fValueIsDefined = true; 1809 return true; 1810 } 1811 1812 1813 // #pragma mark - display as: duration 1814 1815 1816 DurationAttributeText::DurationAttributeText(const Model* model, 1817 const BColumn* column) 1818 : 1819 GenericAttributeText(model, column) 1820 { 1821 } 1822 1823 1824 // TODO: support editing! 1825 1826 1827 void 1828 DurationAttributeText::FitValue(BString* result, const BPoseView* view) 1829 { 1830 if (fValueDirty) 1831 ReadValue(&fFullValueText); 1832 1833 fOldWidth = fColumn->Width(); 1834 fDirty = false; 1835 1836 if (!fValueIsDefined) { 1837 *result = "-"; 1838 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1839 fFullValueText.Length(), view, fOldWidth); 1840 return; 1841 } 1842 1843 int64 time = 0; 1844 1845 switch (fColumn->AttrType()) { 1846 case B_TIME_TYPE: 1847 time = fValue.time_tt * 1000000LL; 1848 break; 1849 1850 case B_INT8_TYPE: 1851 time = fValue.int8t * 1000000LL; 1852 break; 1853 1854 case B_INT16_TYPE: 1855 time = fValue.int16t * 1000000LL; 1856 break; 1857 1858 case B_INT32_TYPE: 1859 time = fValue.int32t * 1000000LL; 1860 break; 1861 1862 case B_INT64_TYPE: 1863 time = fValue.int64t; 1864 break; 1865 } 1866 1867 // TODO: ignores micro seconds for now 1868 int32 seconds = time / 1000000LL; 1869 1870 bool negative = seconds < 0; 1871 if (negative) 1872 seconds = -seconds; 1873 1874 int32 hours = seconds / 3600; 1875 seconds -= hours * 3600; 1876 int32 minutes = seconds / 60; 1877 seconds = seconds % 60; 1878 1879 char buffer[256]; 1880 if (hours > 0) { 1881 snprintf(buffer, sizeof(buffer), "%s%ld:%02ld:%02ld", 1882 negative ? "-" : "", hours, minutes, seconds); 1883 } else { 1884 snprintf(buffer, sizeof(buffer), "%s%ld:%02ld", 1885 negative ? "-" : "", minutes, seconds); 1886 } 1887 1888 fFullValueText = buffer; 1889 1890 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1891 fFullValueText.Length(), view, fOldWidth); 1892 } 1893 1894 1895 // #pragma mark - display as: checkbox 1896 1897 1898 CheckboxAttributeText::CheckboxAttributeText(const Model* model, 1899 const BColumn* column) 1900 : 1901 GenericAttributeText(model, column), 1902 fOnChar("✖"), 1903 fOffChar("-") 1904 { 1905 // TODO: better have common data in the column object! 1906 if (const char* separator = strchr(column->DisplayAs(), ':')) { 1907 BString chars(separator + 1); 1908 int32 length; 1909 const char* c = chars.CharAt(0, &length); 1910 fOnChar.SetTo(c, length); 1911 if (c[length]) { 1912 c = chars.CharAt(1, &length); 1913 fOffChar.SetTo(c, length); 1914 } 1915 } 1916 } 1917 1918 1919 void 1920 CheckboxAttributeText::SetUpEditing(BTextView* view) 1921 { 1922 // TODO: support editing for real! 1923 BString result; 1924 GenericAttributeText::FitValue(&result, NULL); 1925 GenericAttributeText::SetUpEditing(view); 1926 } 1927 1928 1929 void 1930 CheckboxAttributeText::FitValue(BString* result, const BPoseView* view) 1931 { 1932 if (fValueDirty) 1933 ReadValue(&fFullValueText); 1934 1935 fOldWidth = fColumn->Width(); 1936 fDirty = false; 1937 1938 if (!fValueIsDefined) { 1939 *result = fOffChar; 1940 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1941 fFullValueText.Length(), view, fOldWidth); 1942 return; 1943 } 1944 1945 bool checked = false; 1946 1947 switch (fColumn->AttrType()) { 1948 case B_BOOL_TYPE: 1949 checked = fValue.boolt; 1950 break; 1951 1952 case B_INT8_TYPE: 1953 case B_UINT8_TYPE: 1954 checked = fValue.int8t != 0; 1955 break; 1956 1957 case B_INT16_TYPE: 1958 case B_UINT16_TYPE: 1959 checked = fValue.int16t != 0; 1960 break; 1961 1962 case B_INT32_TYPE: 1963 case B_UINT32_TYPE: 1964 checked = fValue.int32t != 0; 1965 break; 1966 } 1967 1968 fFullValueText = checked ? fOnChar : fOffChar; 1969 1970 fTruncatedWidth = TruncString(result, fFullValueText.String(), 1971 fFullValueText.Length(), view, fOldWidth); 1972 } 1973 1974 1975 // #pragma mark - display as: rating 1976 1977 1978 RatingAttributeText::RatingAttributeText(const Model* model, 1979 const BColumn* column) 1980 : 1981 GenericAttributeText(model, column), 1982 fCount(5), 1983 fMax(10) 1984 { 1985 // TODO: support different star counts/max via specifier 1986 } 1987 1988 1989 void 1990 RatingAttributeText::SetUpEditing(BTextView* view) 1991 { 1992 // TODO: support editing for real! 1993 BString result; 1994 GenericAttributeText::FitValue(&result, NULL); 1995 GenericAttributeText::SetUpEditing(view); 1996 } 1997 1998 1999 void 2000 RatingAttributeText::FitValue(BString* result, const BPoseView* view) 2001 { 2002 if (fValueDirty) 2003 ReadValue(&fFullValueText); 2004 2005 fOldWidth = fColumn->Width(); 2006 fDirty = false; 2007 2008 int64 rating = 0; 2009 2010 if (fValueIsDefined) { 2011 switch (fColumn->AttrType()) { 2012 case B_INT8_TYPE: 2013 rating = fValue.int8t; 2014 break; 2015 2016 case B_INT16_TYPE: 2017 rating = fValue.int16t; 2018 break; 2019 2020 case B_INT32_TYPE: 2021 rating = fValue.int32t; 2022 break; 2023 } 2024 } 2025 2026 if (rating > fMax) 2027 rating = fMax; 2028 if (rating < 0) 2029 rating = 0; 2030 2031 int32 steps = fMax / fCount; 2032 fFullValueText = ""; 2033 2034 for (int32 i = 0; i < fCount; i++) { 2035 if (rating > i * steps) 2036 fFullValueText += "★"; 2037 else 2038 fFullValueText += "☆"; 2039 } 2040 2041 fTruncatedWidth = TruncString(result, fFullValueText.String(), 2042 fFullValueText.Length(), view, fOldWidth); 2043 } 2044 2045 2046 // #pragma mark - 2047 2048 2049 OpenWithRelationAttributeText::OpenWithRelationAttributeText(const Model* model, 2050 const BColumn* column, const BPoseView* view) 2051 : 2052 ScalarAttributeText(model, column), 2053 fPoseView(view) 2054 { 2055 } 2056 2057 2058 int64 2059 OpenWithRelationAttributeText::ReadValue() 2060 { 2061 fValueDirty = false; 2062 2063 const OpenWithPoseView* view 2064 = dynamic_cast<const OpenWithPoseView*>(fPoseView); 2065 if (view != NULL) { 2066 fValue = view->OpenWithRelation(fModel); 2067 fValueIsDefined = true; 2068 } 2069 2070 return fValue; 2071 } 2072 2073 2074 float 2075 OpenWithRelationAttributeText::PreferredWidth(const BPoseView* pose) const 2076 { 2077 BString widthString; 2078 TruncString(&widthString, fRelationText.String(), fRelationText.Length(), 2079 pose, 500, B_TRUNCATE_END); 2080 return pose->StringWidth(widthString.String()); 2081 } 2082 2083 2084 void 2085 OpenWithRelationAttributeText::FitValue(BString* result, const BPoseView* view) 2086 { 2087 if (fValueDirty) 2088 ReadValue(); 2089 2090 ASSERT(view == fPoseView); 2091 const OpenWithPoseView* launchWithView 2092 = dynamic_cast<const OpenWithPoseView*>(view); 2093 2094 if (launchWithView) 2095 launchWithView->OpenWithRelationDescription(fModel, &fRelationText); 2096 2097 fOldWidth = fColumn->Width(); 2098 fTruncatedWidth = TruncString(result, fRelationText.String(), 2099 fRelationText.Length(), view, fOldWidth, B_TRUNCATE_END); 2100 fDirty = false; 2101 } 2102 2103 2104 VersionAttributeText::VersionAttributeText(const Model* model, 2105 const BColumn* column, bool app) 2106 : 2107 StringAttributeText(model, column), 2108 fAppVersion(app) 2109 { 2110 } 2111 2112 2113 void 2114 VersionAttributeText::ReadValue(BString* result) 2115 { 2116 fValueDirty = false; 2117 2118 BModelOpener opener(fModel); 2119 BFile* file = dynamic_cast<BFile*>(fModel->Node()); 2120 if (file) { 2121 BAppFileInfo info(file); 2122 version_info version; 2123 if (info.InitCheck() == B_OK 2124 && info.GetVersionInfo(&version, fAppVersion 2125 ? B_APP_VERSION_KIND : B_SYSTEM_VERSION_KIND) == B_OK) { 2126 *result = version.short_info; 2127 return; 2128 } 2129 } 2130 *result = "-"; 2131 } 2132 2133