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