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