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 798 fOldWidth = fColumn->Width(); 799 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 800 fFullValueText.Length(), view, fOldWidth, B_TRUNCATE_MIDDLE); 801 fDirty = false; 802 } 803 804 805 void 806 NameAttributeText::SetUpEditing(BTextView* textView) 807 { 808 DisallowFilenameKeys(textView); 809 810 textView->SetMaxBytes(B_FILE_NAME_LENGTH); 811 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 812 } 813 814 815 bool 816 NameAttributeText::CommitEditedTextFlavor(BTextView* textView) 817 { 818 const char* text = textView->Text(); 819 820 BEntry entry(fModel->EntryRef()); 821 if (entry.InitCheck() != B_OK) 822 return false; 823 824 BDirectory parent; 825 if (entry.GetParent(&parent) != B_OK) 826 return false; 827 828 bool removeExisting = false; 829 if (parent.Contains(text)) { 830 BAlert* alert = new BAlert("", 831 B_TRANSLATE("That name is already taken. " 832 "Please type another one."), 833 B_TRANSLATE("Replace other file"), 834 B_TRANSLATE("OK"), 835 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 836 alert->SetShortcut(0, 'r'); 837 if (alert->Go()) 838 return false; 839 840 removeExisting = true; 841 } 842 843 // TODO: 844 // use model-flavor specific virtuals for all of these special 845 // renamings 846 status_t result; 847 if (fModel->IsVolume()) { 848 BVolume volume(fModel->NodeRef()->device); 849 result = volume.InitCheck(); 850 if (result == B_OK) { 851 RenameVolumeUndo undo(volume, text); 852 853 result = volume.SetName(text); 854 if (result != B_OK) 855 undo.Remove(); 856 } 857 } else { 858 if (fModel->IsQuery()) { 859 BModelWriteOpener opener(fModel); 860 ASSERT(fModel->Node()); 861 MoreOptionsStruct::SetQueryTemporary(fModel->Node(), false); 862 } 863 864 RenameUndo undo(entry, text); 865 866 result = entry.Rename(text, removeExisting); 867 if (result != B_OK) 868 undo.Remove(); 869 } 870 871 return result == B_OK; 872 } 873 874 875 void 876 NameAttributeText::SetSortFolderNamesFirst(bool enabled) 877 { 878 NameAttributeText::sSortFolderNamesFirst = enabled; 879 } 880 881 882 bool 883 NameAttributeText::IsEditable() const 884 { 885 return StringAttributeText::IsEditable() 886 && !fModel->HasLocalizedName(); 887 } 888 889 890 // #pragma mark - RealNameAttributeText 891 892 893 RealNameAttributeText::RealNameAttributeText(const Model* model, 894 const BColumn* column) 895 : 896 StringAttributeText(model, column) 897 { 898 } 899 900 901 int 902 RealNameAttributeText::Compare(WidgetAttributeText& attr, BPoseView* view) 903 { 904 RealNameAttributeText* compareTo 905 = dynamic_cast<RealNameAttributeText*>(&attr); 906 ThrowOnAssert(compareTo != NULL); 907 908 if (fValueDirty) 909 ReadValue(&fFullValueText); 910 911 if (RealNameAttributeText::sSortFolderNamesFirst) 912 return fModel->CompareFolderNamesFirst(attr.TargetModel()); 913 914 return NaturalCompare(fFullValueText.String(), 915 compareTo->ValueAsText(view)); 916 } 917 918 919 void 920 RealNameAttributeText::ReadValue(BString* outString) 921 { 922 *outString = fModel->EntryRef()->name; 923 924 fValueDirty = false; 925 } 926 927 928 void 929 RealNameAttributeText::FitValue(BString* outString, const BPoseView* view) 930 { 931 if (fValueDirty) 932 ReadValue(&fFullValueText); 933 934 fOldWidth = fColumn->Width(); 935 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 936 fFullValueText.Length(), view, fOldWidth, B_TRUNCATE_MIDDLE); 937 fDirty = false; 938 } 939 940 941 void 942 RealNameAttributeText::SetUpEditing(BTextView* textView) 943 { 944 DisallowFilenameKeys(textView); 945 946 textView->SetMaxBytes(B_FILE_NAME_LENGTH); 947 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 948 } 949 950 951 bool 952 RealNameAttributeText::CommitEditedTextFlavor(BTextView* textView) 953 { 954 const char* text = textView->Text(); 955 956 BEntry entry(fModel->EntryRef()); 957 if (entry.InitCheck() != B_OK) 958 return false; 959 960 BDirectory parent; 961 if (entry.GetParent(&parent) != B_OK) 962 return false; 963 964 bool removeExisting = false; 965 if (parent.Contains(text)) { 966 BAlert* alert = new BAlert("", 967 B_TRANSLATE("That name is already taken. " 968 "Please type another one."), 969 B_TRANSLATE("Replace other file"), 970 B_TRANSLATE("OK"), 971 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 972 973 alert->SetShortcut(0, 'r'); 974 975 if (alert->Go()) 976 return false; 977 978 removeExisting = true; 979 } 980 981 // TODO: 982 // use model-flavor specific virtuals for all of these special 983 // renamings 984 status_t result; 985 if (fModel->IsVolume()) { 986 BVolume volume(fModel->NodeRef()->device); 987 result = volume.InitCheck(); 988 if (result == B_OK) { 989 RenameVolumeUndo undo(volume, text); 990 991 result = volume.SetName(text); 992 if (result != B_OK) 993 undo.Remove(); 994 } 995 } else { 996 if (fModel->IsQuery()) { 997 BModelWriteOpener opener(fModel); 998 ASSERT(fModel->Node()); 999 MoreOptionsStruct::SetQueryTemporary(fModel->Node(), false); 1000 } 1001 1002 RenameUndo undo(entry, text); 1003 1004 result = entry.Rename(text, removeExisting); 1005 if (result != B_OK) 1006 undo.Remove(); 1007 } 1008 1009 return result == B_OK; 1010 } 1011 1012 1013 void 1014 RealNameAttributeText::SetSortFolderNamesFirst(bool enabled) 1015 { 1016 RealNameAttributeText::sSortFolderNamesFirst = enabled; 1017 } 1018 1019 1020 // #pragma mark - owner/group 1021 1022 1023 #ifdef OWNER_GROUP_ATTRIBUTES 1024 OwnerAttributeText::OwnerAttributeText(const Model* model, 1025 const BColumn* column) 1026 : 1027 StringAttributeText(model, column) 1028 { 1029 } 1030 1031 1032 void 1033 OwnerAttributeText::ReadValue(BString* outString) 1034 { 1035 uid_t nodeOwner = fModel->StatBuf()->st_uid; 1036 BString user; 1037 1038 if (nodeOwner == 0) { 1039 if (getenv("USER") != NULL) 1040 user << getenv("USER"); 1041 else 1042 user << "root"; 1043 } else 1044 user << nodeOwner; 1045 *outString = user.String(); 1046 1047 fValueDirty = false; 1048 } 1049 1050 1051 GroupAttributeText::GroupAttributeText(const Model* model, 1052 const BColumn* column) 1053 : 1054 StringAttributeText(model, column) 1055 { 1056 } 1057 1058 1059 void 1060 GroupAttributeText::ReadValue(BString* outString) 1061 { 1062 gid_t nodeGroup = fModel->StatBuf()->st_gid; 1063 BString group; 1064 1065 if (nodeGroup == 0) { 1066 if (getenv("GROUP") != NULL) 1067 group << getenv("GROUP"); 1068 else 1069 group << "0"; 1070 } else 1071 group << nodeGroup; 1072 *outString = group.String(); 1073 1074 fValueDirty = false; 1075 } 1076 #endif // OWNER_GROUP_ATTRIBUTES 1077 1078 1079 // #pragma mark - ModeAttributeText 1080 1081 1082 ModeAttributeText::ModeAttributeText(const Model* model, 1083 const BColumn* column) 1084 : 1085 StringAttributeText(model, column) 1086 { 1087 } 1088 1089 1090 void 1091 ModeAttributeText::ReadValue(BString* outString) 1092 { 1093 mode_t mode = fModel->StatBuf()->st_mode; 1094 mode_t baseMask = 00400; 1095 char buffer[11]; 1096 1097 char* scanner = buffer; 1098 1099 if (S_ISDIR(mode)) 1100 *scanner++ = 'd'; 1101 else if (S_ISLNK(mode)) 1102 *scanner++ = 'l'; 1103 else if (S_ISBLK(mode)) 1104 *scanner++ = 'b'; 1105 else if (S_ISCHR(mode)) 1106 *scanner++ = 'c'; 1107 else 1108 *scanner++ = '-'; 1109 1110 for (int32 index = 0; index < 9; index++) { 1111 *scanner++ = (mode & baseMask) ? "rwx"[index % 3] : '-'; 1112 baseMask >>= 1; 1113 } 1114 1115 *scanner = 0; 1116 *outString = buffer; 1117 1118 fValueDirty = false; 1119 } 1120 1121 1122 // #pragma mark - SizeAttributeText 1123 1124 1125 SizeAttributeText::SizeAttributeText(const Model* model, 1126 const BColumn* column) 1127 : 1128 ScalarAttributeText(model, column) 1129 { 1130 } 1131 1132 1133 int64 1134 SizeAttributeText::ReadValue() 1135 { 1136 fValueDirty = false; 1137 // get the size 1138 1139 if (fModel->IsVolume()) { 1140 BVolume volume(fModel->NodeRef()->device); 1141 1142 return volume.Capacity(); 1143 } 1144 1145 if (fModel->IsDirectory() || fModel->IsQuery() 1146 || fModel->IsQueryTemplate() || fModel->IsSymLink() 1147 || fModel->IsVirtualDirectory()) { 1148 return kUnknownSize; 1149 } 1150 1151 fValueIsDefined = true; 1152 1153 return fModel->StatBuf()->st_size; 1154 } 1155 1156 1157 void 1158 SizeAttributeText::FitValue(BString* outString, const BPoseView* view) 1159 { 1160 if (fValueDirty) 1161 fValue = ReadValue(); 1162 1163 fOldWidth = fColumn->Width(); 1164 fTruncatedWidth = TruncFileSize(outString, fValue, view, fOldWidth); 1165 fDirty = false; 1166 } 1167 1168 1169 float 1170 SizeAttributeText::PreferredWidth(const BPoseView* pose) const 1171 { 1172 if (fValueIsDefined) { 1173 BString widthString; 1174 TruncFileSize(&widthString, fValue, pose, 100000); 1175 return pose->StringWidth(widthString.String()); 1176 } 1177 1178 return pose->StringWidth("-"); 1179 } 1180 1181 1182 // #pragma mark - TimeAttributeText 1183 1184 1185 TimeAttributeText::TimeAttributeText(const Model* model, 1186 const BColumn* column) 1187 : 1188 ScalarAttributeText(model, column), 1189 fLastClockIs24(false), 1190 fLastDateOrder(kDateFormatEnd), 1191 fLastTimeFormatSeparator(kSeparatorsEnd) 1192 { 1193 } 1194 1195 1196 float 1197 TimeAttributeText::PreferredWidth(const BPoseView* pose) const 1198 { 1199 BString widthString; 1200 TruncTimeBase(&widthString, fValue, pose, 100000); 1201 return pose->StringWidth(widthString.String()); 1202 } 1203 1204 1205 void 1206 TimeAttributeText::FitValue(BString* outString, const BPoseView* view) 1207 { 1208 if (fValueDirty) 1209 fValue = ReadValue(); 1210 1211 fOldWidth = fColumn->Width(); 1212 fTruncatedWidth = TruncTime(outString, fValue, view, fOldWidth); 1213 fDirty = false; 1214 } 1215 1216 1217 bool 1218 TimeAttributeText::CheckSettingsChanged(void) 1219 { 1220 // TODO : check against the actual locale settings 1221 return false; 1222 } 1223 1224 1225 // #pragma mark - CreationTimeAttributeText 1226 1227 1228 CreationTimeAttributeText::CreationTimeAttributeText(const Model* model, 1229 const BColumn* column) 1230 : 1231 TimeAttributeText(model, column) 1232 { 1233 } 1234 1235 1236 int64 1237 CreationTimeAttributeText::ReadValue() 1238 { 1239 fValueDirty = false; 1240 fValueIsDefined = true; 1241 return fModel->StatBuf()->st_crtime; 1242 } 1243 1244 1245 // #pragma mark - ModificationTimeAttributeText 1246 1247 1248 ModificationTimeAttributeText::ModificationTimeAttributeText( 1249 const Model* model, const BColumn* column) 1250 : 1251 TimeAttributeText(model, column) 1252 { 1253 } 1254 1255 1256 int64 1257 ModificationTimeAttributeText::ReadValue() 1258 { 1259 fValueDirty = false; 1260 fValueIsDefined = true; 1261 return fModel->StatBuf()->st_mtime; 1262 } 1263 1264 1265 // #pragma mark - GenericAttributeText 1266 1267 1268 GenericAttributeText::GenericAttributeText(const Model* model, 1269 const BColumn* column) 1270 : 1271 StringAttributeText(model, column) 1272 { 1273 } 1274 1275 1276 bool 1277 GenericAttributeText::CheckAttributeChanged() 1278 { 1279 GenericValueStruct tmpValue = fValue; 1280 BString tmpString(fFullValueText); 1281 ReadValue(&fFullValueText); 1282 1283 // fDirty could already be true, in that case we mustn't set it to 1284 // false, even if the attribute text hasn't changed 1285 bool changed = fValue.int64t != tmpValue.int64t 1286 || tmpString != fFullValueText; 1287 if (changed) 1288 fDirty = true; 1289 1290 return fDirty; 1291 } 1292 1293 1294 float 1295 GenericAttributeText::PreferredWidth(const BPoseView* pose) const 1296 { 1297 return pose->StringWidth(fFullValueText.String()); 1298 } 1299 1300 1301 void 1302 GenericAttributeText::ReadValue(BString* outString) 1303 { 1304 BModelOpener opener(const_cast<Model*>(fModel)); 1305 1306 ssize_t length = 0; 1307 fFullValueText = "-"; 1308 fValue.int64t = 0; 1309 fValueIsDefined = false; 1310 fValueDirty = false; 1311 1312 if (!fModel->Node()) 1313 return; 1314 1315 switch (fColumn->AttrType()) { 1316 case B_STRING_TYPE: 1317 { 1318 char buffer[kGenericReadBufferSize]; 1319 length = fModel->Node()->ReadAttr(fColumn->AttrName(), 1320 fColumn->AttrType(), 0, buffer, kGenericReadBufferSize - 1); 1321 1322 if (length > 0) { 1323 buffer[length] = '\0'; 1324 // make sure the buffer is null-terminated even if we 1325 // didn't read the whole attribute in or it wasn't to 1326 // begin with 1327 1328 *outString = buffer; 1329 fValueIsDefined = true; 1330 } 1331 break; 1332 } 1333 1334 case B_SSIZE_T_TYPE: 1335 case B_TIME_TYPE: 1336 case B_OFF_T_TYPE: 1337 case B_FLOAT_TYPE: 1338 case B_BOOL_TYPE: 1339 case B_CHAR_TYPE: 1340 case B_INT8_TYPE: 1341 case B_INT16_TYPE: 1342 case B_INT32_TYPE: 1343 case B_INT64_TYPE: 1344 case B_UINT8_TYPE: 1345 case B_UINT16_TYPE: 1346 case B_UINT32_TYPE: 1347 case B_UINT64_TYPE: 1348 case B_DOUBLE_TYPE: 1349 { 1350 // read in the numerical bit representation and attach it 1351 // with a type, depending on the bytes that could be read 1352 attr_info info; 1353 GenericValueStruct tmp; 1354 if (fModel->Node()->GetAttrInfo(fColumn->AttrName(), &info) 1355 == B_OK) { 1356 if (info.size && info.size <= (off_t)sizeof(int64)) { 1357 length = fModel->Node()->ReadAttr(fColumn->AttrName(), 1358 fColumn->AttrType(), 0, &tmp, (size_t)info.size); 1359 } 1360 1361 // We used tmp as a block of memory, now set the 1362 // correct fValue: 1363 1364 if (length == info.size) { 1365 if (fColumn->AttrType() == B_FLOAT_TYPE 1366 || fColumn->AttrType() == B_DOUBLE_TYPE) { 1367 // filter out special float/double types 1368 switch (info.size) { 1369 case sizeof(float): 1370 fValueIsDefined = true; 1371 fValue.floatt = tmp.floatt; 1372 break; 1373 1374 case sizeof(double): 1375 fValueIsDefined = true; 1376 fValue.doublet = tmp.doublet; 1377 break; 1378 1379 default: 1380 TRESPASS(); 1381 break; 1382 } 1383 } else { 1384 // handle the standard data types 1385 switch (info.size) { 1386 case sizeof(char): 1387 // Takes care of bool too. 1388 fValueIsDefined = true; 1389 fValue.int8t = tmp.int8t; 1390 break; 1391 1392 case sizeof(int16): 1393 fValueIsDefined = true; 1394 fValue.int16t = tmp.int16t; 1395 break; 1396 1397 case sizeof(int32): 1398 // Takes care of time_t too. 1399 fValueIsDefined = true; 1400 fValue.int32t = tmp.int32t; 1401 break; 1402 1403 case sizeof(int64): 1404 // Takes care of off_t too. 1405 fValueIsDefined = true; 1406 fValue.int64t = tmp.int64t; 1407 break; 1408 1409 default: 1410 TRESPASS(); 1411 break; 1412 } 1413 } 1414 } 1415 } 1416 break; 1417 } 1418 } 1419 } 1420 1421 1422 void 1423 GenericAttributeText::FitValue(BString* outString, const BPoseView* view) 1424 { 1425 if (fValueDirty) 1426 ReadValue(&fFullValueText); 1427 1428 fOldWidth = fColumn->Width(); 1429 1430 if (!fValueIsDefined) { 1431 *outString = "-"; 1432 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1433 fFullValueText.Length(), view, fOldWidth); 1434 fDirty = false; 1435 return; 1436 } 1437 1438 char buffer[256]; 1439 1440 switch (fColumn->AttrType()) { 1441 case B_SIZE_T_TYPE: 1442 TruncFileSizeBase(outString, fValue.int32t, view, fOldWidth); 1443 return; 1444 1445 case B_SSIZE_T_TYPE: 1446 if (fValue.int32t > 0) { 1447 TruncFileSizeBase(outString, fValue.int32t, view, fOldWidth); 1448 return; 1449 } 1450 sprintf(buffer, "%s", strerror(fValue.int32t)); 1451 fFullValueText = buffer; 1452 break; 1453 1454 case B_STRING_TYPE: 1455 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1456 fFullValueText.Length(), view, fOldWidth); 1457 fDirty = false; 1458 return; 1459 1460 case B_OFF_T_TYPE: 1461 // As a side effect update the fFullValueText to the string 1462 // representation of value 1463 TruncFileSize(&fFullValueText, fValue.off_tt, view, 100000); 1464 fTruncatedWidth = TruncFileSize(outString, fValue.off_tt, view, 1465 fOldWidth); 1466 fDirty = false; 1467 return; 1468 1469 case B_TIME_TYPE: 1470 // As a side effect update the fFullValueText to the string 1471 // representation of value 1472 TruncTime(&fFullValueText, fValue.time_tt, view, 100000); 1473 fTruncatedWidth = TruncTime(outString, fValue.time_tt, view, 1474 fOldWidth); 1475 fDirty = false; 1476 return; 1477 1478 case B_BOOL_TYPE: 1479 // For now use true/false, would be nice to be able to set 1480 // the value text 1481 1482 sprintf(buffer, "%s", fValue.boolt ? "true" : "false"); 1483 fFullValueText = buffer; 1484 break; 1485 1486 case B_CHAR_TYPE: 1487 // Make sure no non-printable characters are displayed: 1488 if (!isprint(fValue.uint8t)) { 1489 *outString = "-"; 1490 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1491 fFullValueText.Length(), view, fOldWidth); 1492 fDirty = false; 1493 return; 1494 } 1495 1496 sprintf(buffer, "%c", fValue.uint8t); 1497 fFullValueText = buffer; 1498 break; 1499 1500 case B_INT8_TYPE: 1501 sprintf(buffer, "%d", fValue.int8t); 1502 fFullValueText = buffer; 1503 break; 1504 1505 case B_UINT8_TYPE: 1506 sprintf(buffer, "%d", fValue.uint8t); 1507 fFullValueText = buffer; 1508 break; 1509 1510 case B_INT16_TYPE: 1511 sprintf(buffer, "%d", fValue.int16t); 1512 fFullValueText = buffer; 1513 break; 1514 1515 case B_UINT16_TYPE: 1516 sprintf(buffer, "%d", fValue.uint16t); 1517 fFullValueText = buffer; 1518 break; 1519 1520 case B_INT32_TYPE: 1521 sprintf(buffer, "%" B_PRId32, fValue.int32t); 1522 fFullValueText = buffer; 1523 break; 1524 1525 case B_UINT32_TYPE: 1526 sprintf(buffer, "%" B_PRId32, fValue.uint32t); 1527 fFullValueText = buffer; 1528 break; 1529 1530 case B_INT64_TYPE: 1531 sprintf(buffer, "%" B_PRId64, fValue.int64t); 1532 fFullValueText = buffer; 1533 break; 1534 1535 case B_UINT64_TYPE: 1536 sprintf(buffer, "%" B_PRId64, fValue.uint64t); 1537 fFullValueText = buffer; 1538 break; 1539 1540 case B_FLOAT_TYPE: 1541 snprintf(buffer, sizeof(buffer), "%g", fValue.floatt); 1542 fFullValueText = buffer; 1543 break; 1544 1545 case B_DOUBLE_TYPE: 1546 snprintf(buffer, sizeof(buffer), "%g", fValue.doublet); 1547 fFullValueText = buffer; 1548 break; 1549 1550 default: 1551 *outString = "-"; 1552 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1553 fFullValueText.Length(), view, fOldWidth); 1554 fDirty = false; 1555 return; 1556 } 1557 fTruncatedWidth = TruncString(outString, buffer, (ssize_t)strlen(buffer), 1558 view, fOldWidth); 1559 fDirty = false; 1560 } 1561 1562 1563 const char* 1564 GenericAttributeText::ValueAsText(const BPoseView* view) 1565 { 1566 // TODO: redesign this - this is to make sure the value is valid 1567 bool oldDirty = fDirty; 1568 BString outString; 1569 FitValue(&outString, view); 1570 fDirty = oldDirty; 1571 1572 return fFullValueText.String(); 1573 } 1574 1575 1576 int 1577 GenericAttributeText::Compare(WidgetAttributeText& attr, BPoseView*) 1578 { 1579 GenericAttributeText* compareTo 1580 = dynamic_cast<GenericAttributeText*>(&attr); 1581 ThrowOnAssert(compareTo != NULL); 1582 1583 if (fValueDirty) 1584 ReadValue(&fFullValueText); 1585 1586 if (compareTo->fValueDirty) 1587 compareTo->ReadValue(&compareTo->fFullValueText); 1588 1589 // sort undefined values last, regardless of the other value 1590 if (!fValueIsDefined) 1591 return compareTo->fValueIsDefined ? 1 : 0; 1592 1593 if (!compareTo->fValueIsDefined) 1594 return -1; 1595 1596 switch (fColumn->AttrType()) { 1597 case B_STRING_TYPE: 1598 return fFullValueText.ICompare(compareTo->fFullValueText); 1599 1600 case B_CHAR_TYPE: 1601 { 1602 char vStr[2] = { static_cast<char>(fValue.uint8t), 0 }; 1603 char cStr[2] = { static_cast<char>(compareTo->fValue.uint8t), 0}; 1604 1605 BString valueStr(vStr); 1606 BString compareToStr(cStr); 1607 1608 return valueStr.ICompare(compareToStr); 1609 } 1610 1611 case B_FLOAT_TYPE: 1612 return fValue.floatt >= compareTo->fValue.floatt ? 1613 (fValue.floatt == compareTo->fValue.floatt ? 0 : 1) : -1; 1614 1615 case B_DOUBLE_TYPE: 1616 return fValue.doublet >= compareTo->fValue.doublet ? 1617 (fValue.doublet == compareTo->fValue.doublet ? 0 : 1) : -1; 1618 1619 case B_BOOL_TYPE: 1620 return fValue.boolt >= compareTo->fValue.boolt ? 1621 (fValue.boolt == compareTo->fValue.boolt ? 0 : 1) : -1; 1622 1623 case B_UINT8_TYPE: 1624 return fValue.uint8t >= compareTo->fValue.uint8t ? 1625 (fValue.uint8t == compareTo->fValue.uint8t ? 0 : 1) : -1; 1626 1627 case B_INT8_TYPE: 1628 return fValue.int8t >= compareTo->fValue.int8t ? 1629 (fValue.int8t == compareTo->fValue.int8t ? 0 : 1) : -1; 1630 1631 case B_UINT16_TYPE: 1632 return fValue.uint16t >= compareTo->fValue.uint16t ? 1633 (fValue.uint16t == compareTo->fValue.uint16t ? 0 : 1) : -1; 1634 1635 case B_INT16_TYPE: 1636 return fValue.int16t >= compareTo->fValue.int16t ? 1637 (fValue.int16t == compareTo->fValue.int16t ? 0 : 1) : -1; 1638 1639 case B_UINT32_TYPE: 1640 return fValue.uint32t >= compareTo->fValue.uint32t ? 1641 (fValue.uint32t == compareTo->fValue.uint32t ? 0 : 1) : -1; 1642 1643 case B_TIME_TYPE: 1644 // time_t typedef'd to a long, i.e. a int32 1645 case B_INT32_TYPE: 1646 return fValue.int32t >= compareTo->fValue.int32t ? 1647 (fValue.int32t == compareTo->fValue.int32t ? 0 : 1) : -1; 1648 1649 case B_OFF_T_TYPE: 1650 // off_t typedef'd to a long long, i.e. a int64 1651 case B_INT64_TYPE: 1652 return fValue.int64t >= compareTo->fValue.int64t ? 1653 (fValue.int64t == compareTo->fValue.int64t ? 0 : 1) : -1; 1654 1655 case B_UINT64_TYPE: 1656 default: 1657 return fValue.uint64t >= compareTo->fValue.uint64t ? 1658 (fValue.uint64t == compareTo->fValue.uint64t ? 0 : 1) : -1; 1659 } 1660 1661 return 0; 1662 } 1663 1664 1665 bool 1666 GenericAttributeText::CommitEditedText(BTextView* textView) 1667 { 1668 ASSERT(fColumn->Editable()); 1669 const char* text = textView->Text(); 1670 1671 if (fFullValueText == text) 1672 // no change 1673 return false; 1674 1675 if (!CommitEditedTextFlavor(textView)) 1676 return false; 1677 1678 // update text and width in this widget 1679 fFullValueText = text; 1680 // cause re-truncation 1681 fDirty = true; 1682 fValueDirty = true; 1683 1684 return true; 1685 } 1686 1687 1688 void 1689 GenericAttributeText::SetUpEditing(BTextView* textView) 1690 { 1691 textView->SetMaxBytes(kGenericReadBufferSize - 1); 1692 textView->SetText(fFullValueText.String(), fFullValueText.Length()); 1693 } 1694 1695 1696 bool 1697 GenericAttributeText::CommitEditedTextFlavor(BTextView* textView) 1698 { 1699 BNode node(fModel->EntryRef()); 1700 1701 if (node.InitCheck() != B_OK) 1702 return false; 1703 1704 uint32 type = fColumn->AttrType(); 1705 1706 if (type != B_STRING_TYPE 1707 && type != B_UINT64_TYPE 1708 && type != B_UINT32_TYPE 1709 && type != B_UINT16_TYPE 1710 && type != B_UINT8_TYPE 1711 && type != B_INT64_TYPE 1712 && type != B_INT32_TYPE 1713 && type != B_INT16_TYPE 1714 && type != B_INT8_TYPE 1715 && type != B_OFF_T_TYPE 1716 && type != B_TIME_TYPE 1717 && type != B_FLOAT_TYPE 1718 && type != B_DOUBLE_TYPE 1719 && type != B_CHAR_TYPE 1720 && type != B_BOOL_TYPE) { 1721 BAlert* alert = new BAlert("", 1722 B_TRANSLATE("Sorry, you cannot edit that attribute."), 1723 B_TRANSLATE("Cancel"), 1724 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1725 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1726 alert->Go(); 1727 return false; 1728 } 1729 1730 const char* columnName = fColumn->AttrName(); 1731 ssize_t size = 0; 1732 1733 switch (type) { 1734 case B_STRING_TYPE: 1735 size = fModel->WriteAttr(columnName, type, 0, textView->Text(), 1736 (size_t)textView->TextLength() + 1); 1737 break; 1738 1739 case B_BOOL_TYPE: 1740 { 1741 bool value = strncasecmp(textView->Text(), "0", 1) != 0 1742 && strncasecmp(textView->Text(), "off", 2) != 0 1743 && strncasecmp(textView->Text(), "no", 3) != 0 1744 && strncasecmp(textView->Text(), "false", 4) != 0 1745 && strlen(textView->Text()) != 0; 1746 1747 size = fModel->WriteAttr(columnName, type, 0, &value, sizeof(bool)); 1748 break; 1749 } 1750 1751 case B_CHAR_TYPE: 1752 { 1753 char ch; 1754 sscanf(textView->Text(), "%c", &ch); 1755 //Check if we read the start of a multi-byte glyph: 1756 if (!isprint(ch)) { 1757 BAlert* alert = new BAlert("", 1758 B_TRANSLATE("Sorry, the 'Character' " 1759 "attribute cannot store a multi-byte glyph."), 1760 B_TRANSLATE("Cancel"), 1761 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1762 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1763 alert->Go(); 1764 return false; 1765 } 1766 1767 size = fModel->WriteAttr(columnName, type, 0, &ch, sizeof(char)); 1768 break; 1769 } 1770 1771 case B_FLOAT_TYPE: 1772 { 1773 float floatVal; 1774 1775 if (sscanf(textView->Text(), "%f", &floatVal) == 1) { 1776 fValueIsDefined = true; 1777 fValue.floatt = floatVal; 1778 size = fModel->WriteAttr(columnName, type, 0, &floatVal, 1779 sizeof(float)); 1780 } else { 1781 // If the value was already defined, it's on disk. 1782 // Otherwise not. 1783 return fValueIsDefined; 1784 } 1785 break; 1786 } 1787 1788 case B_DOUBLE_TYPE: 1789 { 1790 double doubleVal; 1791 1792 if (sscanf(textView->Text(), "%lf", &doubleVal) == 1) { 1793 fValueIsDefined = true; 1794 fValue.doublet = doubleVal; 1795 size = fModel->WriteAttr(columnName, type, 0, &doubleVal, 1796 sizeof(double)); 1797 } else { 1798 // If the value was already defined, it's on disk. 1799 // Otherwise not. 1800 return fValueIsDefined; 1801 } 1802 break; 1803 } 1804 1805 case B_TIME_TYPE: 1806 case B_OFF_T_TYPE: 1807 case B_UINT64_TYPE: 1808 case B_UINT32_TYPE: 1809 case B_UINT16_TYPE: 1810 case B_UINT8_TYPE: 1811 case B_INT64_TYPE: 1812 case B_INT32_TYPE: 1813 case B_INT16_TYPE: 1814 case B_INT8_TYPE: 1815 { 1816 GenericValueStruct tmp; 1817 size_t scalarSize = 0; 1818 1819 switch (type) { 1820 case B_TIME_TYPE: 1821 tmp.time_tt = parsedate(textView->Text(), time(0)); 1822 scalarSize = sizeof(time_t); 1823 break; 1824 1825 // do some size independent conversion on builtin types 1826 case B_OFF_T_TYPE: 1827 tmp.off_tt = StringToScalar(textView->Text()); 1828 scalarSize = sizeof(off_t); 1829 break; 1830 1831 case B_UINT64_TYPE: 1832 case B_INT64_TYPE: 1833 tmp.int64t = StringToScalar(textView->Text()); 1834 scalarSize = sizeof(int64); 1835 break; 1836 1837 case B_UINT32_TYPE: 1838 case B_INT32_TYPE: 1839 tmp.int32t = (int32)StringToScalar(textView->Text()); 1840 scalarSize = sizeof(int32); 1841 break; 1842 1843 case B_UINT16_TYPE: 1844 case B_INT16_TYPE: 1845 tmp.int16t = (int16)StringToScalar(textView->Text()); 1846 scalarSize = sizeof(int16); 1847 break; 1848 1849 case B_UINT8_TYPE: 1850 case B_INT8_TYPE: 1851 tmp.int8t = (int8)StringToScalar(textView->Text()); 1852 scalarSize = sizeof(int8); 1853 break; 1854 1855 default: 1856 TRESPASS(); 1857 } 1858 1859 size = fModel->WriteAttr(columnName, type, 0, &tmp, scalarSize); 1860 break; 1861 } 1862 } 1863 1864 if (size < 0) { 1865 BAlert* alert = new BAlert("", 1866 B_TRANSLATE("There was an error writing the attribute."), 1867 B_TRANSLATE("Cancel"), 1868 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1869 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1870 alert->Go(); 1871 1872 fValueIsDefined = false; 1873 return false; 1874 } 1875 1876 fValueIsDefined = true; 1877 return true; 1878 } 1879 1880 1881 // #pragma mark - DurationAttributeText (display as: duration) 1882 1883 1884 DurationAttributeText::DurationAttributeText(const Model* model, 1885 const BColumn* column) 1886 : 1887 GenericAttributeText(model, column) 1888 { 1889 } 1890 1891 1892 // TODO: support editing! 1893 1894 1895 void 1896 DurationAttributeText::FitValue(BString* outString, const BPoseView* view) 1897 { 1898 if (fValueDirty) 1899 ReadValue(&fFullValueText); 1900 1901 fOldWidth = fColumn->Width(); 1902 fDirty = false; 1903 1904 if (!fValueIsDefined) { 1905 *outString = "-"; 1906 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1907 fFullValueText.Length(), view, fOldWidth); 1908 return; 1909 } 1910 1911 int64 time = 0; 1912 1913 switch (fColumn->AttrType()) { 1914 case B_TIME_TYPE: 1915 time = fValue.time_tt * 1000000LL; 1916 break; 1917 1918 case B_INT8_TYPE: 1919 time = fValue.int8t * 1000000LL; 1920 break; 1921 1922 case B_INT16_TYPE: 1923 time = fValue.int16t * 1000000LL; 1924 break; 1925 1926 case B_INT32_TYPE: 1927 time = fValue.int32t * 1000000LL; 1928 break; 1929 1930 case B_INT64_TYPE: 1931 time = fValue.int64t; 1932 break; 1933 } 1934 1935 // TODO: ignores micro seconds for now 1936 int32 seconds = time / 1000000LL; 1937 1938 bool negative = seconds < 0; 1939 if (negative) 1940 seconds = -seconds; 1941 1942 int32 hours = seconds / 3600; 1943 seconds -= hours * 3600; 1944 int32 minutes = seconds / 60; 1945 seconds = seconds % 60; 1946 1947 char buffer[256]; 1948 if (hours > 0) { 1949 snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32 ":%02" 1950 B_PRId32, negative ? "-" : "", hours, minutes, seconds); 1951 } else { 1952 snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32, 1953 negative ? "-" : "", minutes, seconds); 1954 } 1955 1956 fFullValueText = buffer; 1957 1958 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 1959 fFullValueText.Length(), view, fOldWidth); 1960 } 1961 1962 1963 // #pragma mark - CheckboxAttributeText (display as: checkbox) 1964 1965 1966 CheckboxAttributeText::CheckboxAttributeText(const Model* model, 1967 const BColumn* column) 1968 : 1969 GenericAttributeText(model, column), 1970 fOnChar("✖"), 1971 fOffChar("-") 1972 { 1973 // TODO: better have common data in the column object! 1974 if (const char* separator = strchr(column->DisplayAs(), ':')) { 1975 BString chars(separator + 1); 1976 int32 length; 1977 const char* c = chars.CharAt(0, &length); 1978 fOnChar.SetTo(c, length); 1979 if (c[length]) { 1980 c = chars.CharAt(1, &length); 1981 fOffChar.SetTo(c, length); 1982 } 1983 } 1984 } 1985 1986 1987 void 1988 CheckboxAttributeText::SetUpEditing(BTextView* view) 1989 { 1990 // TODO: support editing for real! 1991 BString outString; 1992 GenericAttributeText::FitValue(&outString, NULL); 1993 GenericAttributeText::SetUpEditing(view); 1994 } 1995 1996 1997 void 1998 CheckboxAttributeText::FitValue(BString* outString, const BPoseView* view) 1999 { 2000 if (fValueDirty) 2001 ReadValue(&fFullValueText); 2002 2003 fOldWidth = fColumn->Width(); 2004 fDirty = false; 2005 2006 if (!fValueIsDefined) { 2007 *outString = fOffChar; 2008 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 2009 fFullValueText.Length(), view, fOldWidth); 2010 return; 2011 } 2012 2013 bool checked = false; 2014 2015 switch (fColumn->AttrType()) { 2016 case B_BOOL_TYPE: 2017 checked = fValue.boolt; 2018 break; 2019 2020 case B_INT8_TYPE: 2021 case B_UINT8_TYPE: 2022 checked = fValue.int8t != 0; 2023 break; 2024 2025 case B_INT16_TYPE: 2026 case B_UINT16_TYPE: 2027 checked = fValue.int16t != 0; 2028 break; 2029 2030 case B_INT32_TYPE: 2031 case B_UINT32_TYPE: 2032 checked = fValue.int32t != 0; 2033 break; 2034 } 2035 2036 fFullValueText = checked ? fOnChar : fOffChar; 2037 2038 fTruncatedWidth = TruncString(outString, fFullValueText.String(), 2039 fFullValueText.Length(), view, fOldWidth); 2040 } 2041 2042 2043 // #pragma mark - RatingAttributeText (display as: rating) 2044 2045 2046 RatingAttributeText::RatingAttributeText(const Model* model, 2047 const BColumn* column) 2048 : 2049 GenericAttributeText(model, column), 2050 fCount(5), 2051 fMax(10) 2052 { 2053 // TODO: support different star counts/max via specifier 2054 } 2055 2056 2057 void 2058 RatingAttributeText::SetUpEditing(BTextView* view) 2059 { 2060 // TODO: support editing for real! 2061 BString outString; 2062 GenericAttributeText::FitValue(&outString, NULL); 2063 GenericAttributeText::SetUpEditing(view); 2064 } 2065 2066 2067 void 2068 RatingAttributeText::FitValue(BString* ratingString, const BPoseView* view) 2069 { 2070 if (fValueDirty) 2071 ReadValue(&fFullValueText); 2072 2073 fOldWidth = fColumn->Width(); 2074 fDirty = false; 2075 2076 int64 rating; 2077 if (fValueIsDefined) { 2078 switch (fColumn->AttrType()) { 2079 case B_INT8_TYPE: 2080 rating = fValue.int8t; 2081 break; 2082 2083 case B_INT16_TYPE: 2084 rating = fValue.int16t; 2085 break; 2086 2087 case B_INT32_TYPE: 2088 rating = fValue.int32t; 2089 break; 2090 2091 default: 2092 rating = 0; 2093 break; 2094 } 2095 } else 2096 rating = 0; 2097 2098 if (rating > fMax) 2099 rating = fMax; 2100 2101 if (rating < 0) 2102 rating = 0; 2103 2104 int32 steps = fMax / fCount; 2105 fFullValueText = ""; 2106 2107 for (int32 i = 0; i < fCount; i++) { 2108 int64 n = i * steps; 2109 if (rating > n) 2110 fFullValueText += "★"; 2111 else 2112 fFullValueText += "☆"; 2113 } 2114 2115 fTruncatedWidth = TruncString(ratingString, fFullValueText.String(), 2116 fFullValueText.Length(), view, fOldWidth); 2117 } 2118 2119 2120 // #pragma mark - OpenWithRelationAttributeText 2121 2122 2123 OpenWithRelationAttributeText::OpenWithRelationAttributeText(const Model* model, 2124 const BColumn* column, const BPoseView* view) 2125 : 2126 ScalarAttributeText(model, column), 2127 fPoseView(view) 2128 { 2129 } 2130 2131 2132 int64 2133 OpenWithRelationAttributeText::ReadValue() 2134 { 2135 fValueDirty = false; 2136 2137 const OpenWithPoseView* view 2138 = dynamic_cast<const OpenWithPoseView*>(fPoseView); 2139 if (view != NULL) { 2140 fValue = view->OpenWithRelation(fModel); 2141 fValueIsDefined = true; 2142 } 2143 2144 return fValue; 2145 } 2146 2147 2148 float 2149 OpenWithRelationAttributeText::PreferredWidth(const BPoseView* pose) const 2150 { 2151 BString widthString; 2152 TruncString(&widthString, fRelationText.String(), fRelationText.Length(), 2153 pose, 500, B_TRUNCATE_END); 2154 return pose->StringWidth(widthString.String()); 2155 } 2156 2157 2158 void 2159 OpenWithRelationAttributeText::FitValue(BString* outString, 2160 const BPoseView* view) 2161 { 2162 if (fValueDirty) 2163 ReadValue(); 2164 2165 ASSERT(view == fPoseView); 2166 const OpenWithPoseView* launchWithView 2167 = dynamic_cast<const OpenWithPoseView*>(view); 2168 if (launchWithView != NULL) 2169 launchWithView->OpenWithRelationDescription(fModel, &fRelationText); 2170 2171 fOldWidth = fColumn->Width(); 2172 fTruncatedWidth = TruncString(outString, fRelationText.String(), 2173 fRelationText.Length(), view, fOldWidth, B_TRUNCATE_END); 2174 fDirty = false; 2175 } 2176 2177 2178 // #pragma mark - VersionAttributeText 2179 2180 2181 VersionAttributeText::VersionAttributeText(const Model* model, 2182 const BColumn* column, bool app) 2183 : 2184 StringAttributeText(model, column), 2185 fAppVersion(app) 2186 { 2187 } 2188 2189 2190 void 2191 VersionAttributeText::ReadValue(BString* outString) 2192 { 2193 fValueDirty = false; 2194 2195 BModelOpener opener(fModel); 2196 BFile* file = dynamic_cast<BFile*>(fModel->Node()); 2197 if (file != NULL) { 2198 BAppFileInfo info(file); 2199 version_info version; 2200 if (info.InitCheck() == B_OK 2201 && info.GetVersionInfo(&version, fAppVersion 2202 ? B_APP_VERSION_KIND : B_SYSTEM_VERSION_KIND) == B_OK) { 2203 *outString = version.short_info; 2204 return; 2205 } 2206 } 2207 2208 *outString = "-"; 2209 } 2210