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