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