xref: /haiku/src/kits/tracker/WidgetAttributeText.cpp (revision 1978089f7cec856677e46204e992c7273d70b9af)
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 		fValueIsDefined = volume.Capacity() != 0;
999 		return volume.Capacity();
1000 	}
1001 
1002 	if (fModel->IsDirectory() || fModel->IsQuery()
1003 		|| fModel->IsQueryTemplate() || fModel->IsSymLink()
1004 		|| fModel->IsVirtualDirectory()) {
1005 		fValueIsDefined = false;
1006 		return kUnknownSize;
1007 	}
1008 
1009 	fValueIsDefined = true;
1010 
1011 	return fModel->StatBuf()->st_size;
1012 }
1013 
1014 
1015 void
1016 SizeAttributeText::FitValue(BString* outString, const BPoseView* poseView)
1017 {
1018 	if (fValueDirty)
1019 		fValue = ReadValue();
1020 
1021 	fOldWidth = fColumn->Width();
1022 	if (!fValueIsDefined) {
1023 		*outString = "-";
1024 		fTruncatedWidth = poseView->StringWidth("-");
1025 	} else
1026 		fTruncatedWidth = TruncFileSize(outString, fValue, poseView, fOldWidth);
1027 	fDirty = false;
1028 }
1029 
1030 
1031 float
1032 SizeAttributeText::PreferredWidth(const BPoseView* poseView) const
1033 {
1034 	if (!fValueIsDefined)
1035 		return poseView->StringWidth("-");
1036 
1037 	BString widthString;
1038 	TruncFileSize(&widthString, fValue, poseView, 100000);
1039 
1040 	return poseView->StringWidth(widthString.String());
1041 }
1042 
1043 
1044 // #pragma mark - TimeAttributeText
1045 
1046 
1047 TimeAttributeText::TimeAttributeText(const Model* model,
1048 	const BColumn* column)
1049 	:
1050 	ScalarAttributeText(model, column),
1051 	fLastClockIs24(false),
1052 	fLastDateOrder(kDateFormatEnd),
1053 	fLastTimeFormatSeparator(kSeparatorsEnd)
1054 {
1055 }
1056 
1057 
1058 float
1059 TimeAttributeText::PreferredWidth(const BPoseView* pose) const
1060 {
1061 	BString widthString;
1062 	TruncTimeBase(&widthString, fValue, pose, 100000);
1063 	return pose->StringWidth(widthString.String());
1064 }
1065 
1066 
1067 void
1068 TimeAttributeText::FitValue(BString* outString, const BPoseView* view)
1069 {
1070 	if (fValueDirty)
1071 		fValue = ReadValue();
1072 
1073 	fOldWidth = fColumn->Width();
1074 	fTruncatedWidth = TruncTime(outString, fValue, view, fOldWidth);
1075 	fDirty = false;
1076 }
1077 
1078 
1079 bool
1080 TimeAttributeText::CheckSettingsChanged(void)
1081 {
1082 	// TODO : check against the actual locale settings
1083 	return false;
1084 }
1085 
1086 
1087 //	#pragma mark - CreationTimeAttributeText
1088 
1089 
1090 CreationTimeAttributeText::CreationTimeAttributeText(const Model* model,
1091 	const BColumn* column)
1092 	:
1093 	TimeAttributeText(model, column)
1094 {
1095 }
1096 
1097 
1098 int64
1099 CreationTimeAttributeText::ReadValue()
1100 {
1101 	fValueDirty = false;
1102 	fValueIsDefined = true;
1103 	return fModel->StatBuf()->st_crtime;
1104 }
1105 
1106 
1107 //	#pragma mark - ModificationTimeAttributeText
1108 
1109 
1110 ModificationTimeAttributeText::ModificationTimeAttributeText(
1111 	const Model* model, const BColumn* column)
1112 	:
1113 	TimeAttributeText(model, column)
1114 {
1115 }
1116 
1117 
1118 int64
1119 ModificationTimeAttributeText::ReadValue()
1120 {
1121 	fValueDirty = false;
1122 	fValueIsDefined = true;
1123 	return fModel->StatBuf()->st_mtime;
1124 }
1125 
1126 
1127 //	#pragma mark - GenericAttributeText
1128 
1129 
1130 GenericAttributeText::GenericAttributeText(const Model* model,
1131 	const BColumn* column)
1132 	:
1133 	StringAttributeText(model, column)
1134 {
1135 }
1136 
1137 
1138 bool
1139 GenericAttributeText::CheckAttributeChanged()
1140 {
1141 	GenericValueStruct tmpValue = fValue;
1142 	BString tmpString(fFullValueText);
1143 	ReadValue(&fFullValueText);
1144 
1145 	// fDirty could already be true, in that case we mustn't set it to
1146 	// false, even if the attribute text hasn't changed
1147 	bool changed = fValue.int64t != tmpValue.int64t
1148 		|| tmpString != fFullValueText;
1149 	if (changed)
1150 		fDirty = true;
1151 
1152 	return fDirty;
1153 }
1154 
1155 
1156 float
1157 GenericAttributeText::PreferredWidth(const BPoseView* pose) const
1158 {
1159 	return pose->StringWidth(fFullValueText.String());
1160 }
1161 
1162 
1163 void
1164 GenericAttributeText::ReadValue(BString* outString)
1165 {
1166 	BModelOpener opener(const_cast<Model*>(fModel));
1167 
1168 	ssize_t length = 0;
1169 	fFullValueText = "-";
1170 	fValue.int64t = 0;
1171 	fValueIsDefined = false;
1172 	fValueDirty = false;
1173 
1174 	if (!fModel->Node())
1175 		return;
1176 
1177 	switch (fColumn->AttrType()) {
1178 		case B_STRING_TYPE:
1179 		{
1180 			char buffer[kGenericReadBufferSize];
1181 			length = fModel->Node()->ReadAttr(fColumn->AttrName(),
1182 				fColumn->AttrType(), 0, buffer, kGenericReadBufferSize - 1);
1183 
1184 			if (length > 0) {
1185 				buffer[length] = '\0';
1186 				// make sure the buffer is null-terminated even if we
1187 				// didn't read the whole attribute in or it wasn't to
1188 				// begin with
1189 
1190 				*outString = buffer;
1191 				fValueIsDefined = true;
1192 			}
1193 			break;
1194 		}
1195 
1196 		case B_SSIZE_T_TYPE:
1197 		case B_TIME_TYPE:
1198 		case B_OFF_T_TYPE:
1199 		case B_FLOAT_TYPE:
1200 		case B_BOOL_TYPE:
1201 		case B_CHAR_TYPE:
1202 		case B_INT8_TYPE:
1203 		case B_INT16_TYPE:
1204 		case B_INT32_TYPE:
1205 		case B_INT64_TYPE:
1206 		case B_UINT8_TYPE:
1207 		case B_UINT16_TYPE:
1208 		case B_UINT32_TYPE:
1209 		case B_UINT64_TYPE:
1210 		case B_DOUBLE_TYPE:
1211 		{
1212 			// read in the numerical bit representation and attach it
1213 			// with a type, depending on the bytes that could be read
1214 			attr_info info;
1215 			GenericValueStruct tmp;
1216 			if (fModel->Node()->GetAttrInfo(fColumn->AttrName(), &info)
1217 					== B_OK) {
1218 				if (info.size && info.size <= (off_t)sizeof(int64)) {
1219 					length = fModel->Node()->ReadAttr(fColumn->AttrName(),
1220 						fColumn->AttrType(), 0, &tmp, (size_t)info.size);
1221 				}
1222 
1223 				// We used tmp as a block of memory, now set the
1224 				// correct fValue:
1225 
1226 				if (length == info.size) {
1227 					if (fColumn->AttrType() == B_FLOAT_TYPE
1228 						|| fColumn->AttrType() == B_DOUBLE_TYPE) {
1229 						// filter out special float/double types
1230 						switch (info.size) {
1231 							case sizeof(float):
1232 								fValueIsDefined = true;
1233 								fValue.floatt = tmp.floatt;
1234 								break;
1235 
1236 							case sizeof(double):
1237 								fValueIsDefined = true;
1238 								fValue.doublet = tmp.doublet;
1239 								break;
1240 
1241 							default:
1242 								TRESPASS();
1243 								break;
1244 						}
1245 					} else {
1246 						// handle the standard data types
1247 						switch (info.size) {
1248 							case sizeof(char):
1249 								// Takes care of bool too.
1250 								fValueIsDefined = true;
1251 								fValue.int8t = tmp.int8t;
1252 								break;
1253 
1254 							case sizeof(int16):
1255 								fValueIsDefined = true;
1256 								fValue.int16t = tmp.int16t;
1257 								break;
1258 
1259 							case sizeof(int32):
1260 								// Takes care of time_t too.
1261 								fValueIsDefined = true;
1262 								fValue.int32t = tmp.int32t;
1263 								break;
1264 
1265 							case sizeof(int64):
1266 								// Takes care of off_t too.
1267 								fValueIsDefined = true;
1268 								fValue.int64t = tmp.int64t;
1269 								break;
1270 
1271 							default:
1272 								TRESPASS();
1273 								break;
1274 						}
1275 					}
1276 				}
1277 			}
1278 			break;
1279 		}
1280 	}
1281 }
1282 
1283 
1284 void
1285 GenericAttributeText::FitValue(BString* outString, const BPoseView* view)
1286 {
1287 	if (fValueDirty)
1288 		ReadValue(&fFullValueText);
1289 
1290 	fOldWidth = fColumn->Width();
1291 
1292 	if (!fValueIsDefined) {
1293 		*outString = "-";
1294 		fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1295 			fFullValueText.Length(), view, fOldWidth);
1296 		fDirty = false;
1297 		return;
1298 	}
1299 
1300 	char buffer[256];
1301 
1302 	switch (fColumn->AttrType()) {
1303 		case B_SIZE_T_TYPE:
1304 			TruncFileSizeBase(outString, fValue.int32t, view, fOldWidth);
1305 			return;
1306 
1307 		case B_SSIZE_T_TYPE:
1308 			if (fValue.int32t > 0) {
1309 				TruncFileSizeBase(outString, fValue.int32t, view, fOldWidth);
1310 				return;
1311 			}
1312 			sprintf(buffer, "%s", strerror(fValue.int32t));
1313 			fFullValueText = buffer;
1314 			break;
1315 
1316 		case B_STRING_TYPE:
1317 			fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1318 				fFullValueText.Length(), view, fOldWidth);
1319 			fDirty = false;
1320 			return;
1321 
1322 		case B_OFF_T_TYPE:
1323 			// As a side effect update the fFullValueText to the string
1324 			// representation of value
1325 			TruncFileSize(&fFullValueText, fValue.off_tt, view, 100000);
1326 			fTruncatedWidth = TruncFileSize(outString, fValue.off_tt, view,
1327 				fOldWidth);
1328 			fDirty = false;
1329 			return;
1330 
1331 		case B_TIME_TYPE:
1332 			// As a side effect update the fFullValueText to the string
1333 			// representation of value
1334 			TruncTime(&fFullValueText, fValue.time_tt, view, 100000);
1335 			fTruncatedWidth = TruncTime(outString, fValue.time_tt, view,
1336 				fOldWidth);
1337 			fDirty = false;
1338 			return;
1339 
1340 		case B_BOOL_TYPE:
1341 			// For now use true/false, would be nice to be able to set
1342 			// the value text
1343 
1344  			sprintf(buffer, "%s", fValue.boolt ? "true" : "false");
1345 			fFullValueText = buffer;
1346 			break;
1347 
1348 		case B_CHAR_TYPE:
1349 			// Make sure no non-printable characters are displayed:
1350 			if (!isprint(fValue.uint8t)) {
1351 				*outString = "-";
1352 				fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1353 					fFullValueText.Length(), view, fOldWidth);
1354 				fDirty = false;
1355 				return;
1356 			}
1357 
1358 			sprintf(buffer, "%c", fValue.uint8t);
1359 			fFullValueText = buffer;
1360 			break;
1361 
1362 		case B_INT8_TYPE:
1363 			sprintf(buffer, "%d", fValue.int8t);
1364 			fFullValueText = buffer;
1365 			break;
1366 
1367 		case B_UINT8_TYPE:
1368 			sprintf(buffer, "%d", fValue.uint8t);
1369 			fFullValueText = buffer;
1370 			break;
1371 
1372 		case B_INT16_TYPE:
1373 			sprintf(buffer, "%d", fValue.int16t);
1374 			fFullValueText = buffer;
1375 			break;
1376 
1377 		case B_UINT16_TYPE:
1378 			sprintf(buffer, "%d", fValue.uint16t);
1379 			fFullValueText = buffer;
1380 			break;
1381 
1382 		case B_INT32_TYPE:
1383 			sprintf(buffer, "%" B_PRId32, fValue.int32t);
1384 			fFullValueText = buffer;
1385 			break;
1386 
1387 		case B_UINT32_TYPE:
1388 			sprintf(buffer, "%" B_PRId32, fValue.uint32t);
1389 			fFullValueText = buffer;
1390 			break;
1391 
1392 		case B_INT64_TYPE:
1393 			sprintf(buffer, "%" B_PRId64, fValue.int64t);
1394 			fFullValueText = buffer;
1395 			break;
1396 
1397 		case B_UINT64_TYPE:
1398 			sprintf(buffer, "%" B_PRId64, fValue.uint64t);
1399 			fFullValueText = buffer;
1400 			break;
1401 
1402 		case B_FLOAT_TYPE:
1403 			snprintf(buffer, sizeof(buffer), "%g", fValue.floatt);
1404 			fFullValueText = buffer;
1405 			break;
1406 
1407 		case B_DOUBLE_TYPE:
1408 			snprintf(buffer, sizeof(buffer), "%g", fValue.doublet);
1409 			fFullValueText = buffer;
1410 			break;
1411 
1412 		default:
1413 			*outString = "-";
1414 			fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1415 				fFullValueText.Length(), view, fOldWidth);
1416 			fDirty = false;
1417 			return;
1418 	}
1419 	fTruncatedWidth = TruncString(outString, buffer, (ssize_t)strlen(buffer),
1420 		view, fOldWidth);
1421 	fDirty = false;
1422 }
1423 
1424 
1425 const char*
1426 GenericAttributeText::ValueAsText(const BPoseView* view)
1427 {
1428 	// TODO: redesign this - this is to make sure the value is valid
1429 	bool oldDirty = fDirty;
1430 	BString outString;
1431 	FitValue(&outString, view);
1432 	fDirty = oldDirty;
1433 
1434 	return fFullValueText.String();
1435 }
1436 
1437 
1438 int
1439 GenericAttributeText::Compare(WidgetAttributeText& attr, BPoseView*)
1440 {
1441 	GenericAttributeText* compareTo
1442 		= dynamic_cast<GenericAttributeText*>(&attr);
1443 	ThrowOnAssert(compareTo != NULL);
1444 
1445 	if (fValueDirty)
1446 		ReadValue(&fFullValueText);
1447 
1448 	if (compareTo->fValueDirty)
1449 		compareTo->ReadValue(&compareTo->fFullValueText);
1450 
1451 	// sort undefined values last, regardless of the other value
1452 	if (!fValueIsDefined)
1453 		return compareTo->fValueIsDefined ? 1 : 0;
1454 
1455 	if (!compareTo->fValueIsDefined)
1456 		return -1;
1457 
1458 	switch (fColumn->AttrType()) {
1459 		case B_STRING_TYPE:
1460 			return fFullValueText.ICompare(compareTo->fFullValueText);
1461 
1462 		case B_CHAR_TYPE:
1463 		{
1464 			char vStr[2] = { static_cast<char>(fValue.uint8t), 0 };
1465 			char cStr[2] = { static_cast<char>(compareTo->fValue.uint8t), 0};
1466 
1467 			BString valueStr(vStr);
1468 			BString compareToStr(cStr);
1469 
1470 			return valueStr.ICompare(compareToStr);
1471 		}
1472 
1473 		case B_FLOAT_TYPE:
1474 			return fValue.floatt >= compareTo->fValue.floatt ?
1475 				(fValue.floatt == compareTo->fValue.floatt ? 0 : 1) : -1;
1476 
1477 		case B_DOUBLE_TYPE:
1478 			return fValue.doublet >= compareTo->fValue.doublet ?
1479 				(fValue.doublet == compareTo->fValue.doublet ? 0 : 1) : -1;
1480 
1481 		case B_BOOL_TYPE:
1482 			return fValue.boolt >= compareTo->fValue.boolt ?
1483 				(fValue.boolt == compareTo->fValue.boolt ? 0 : 1) : -1;
1484 
1485 		case B_UINT8_TYPE:
1486 			return fValue.uint8t >= compareTo->fValue.uint8t ?
1487 				(fValue.uint8t == compareTo->fValue.uint8t ? 0 : 1) : -1;
1488 
1489 		case B_INT8_TYPE:
1490 			return fValue.int8t >= compareTo->fValue.int8t ?
1491 					(fValue.int8t == compareTo->fValue.int8t ? 0 : 1) : -1;
1492 
1493 		case B_UINT16_TYPE:
1494 			return fValue.uint16t >= compareTo->fValue.uint16t ?
1495 				(fValue.uint16t == compareTo->fValue.uint16t ? 0 : 1) : -1;
1496 
1497 		case B_INT16_TYPE:
1498 			return fValue.int16t >= compareTo->fValue.int16t ?
1499 				(fValue.int16t == compareTo->fValue.int16t ? 0 : 1) : -1;
1500 
1501 		case B_UINT32_TYPE:
1502 			return fValue.uint32t >= compareTo->fValue.uint32t ?
1503 				(fValue.uint32t == compareTo->fValue.uint32t ? 0 : 1) : -1;
1504 
1505 		case B_TIME_TYPE:
1506 			// time_t typedef'd to a long, i.e. a int32
1507 		case B_INT32_TYPE:
1508 			return fValue.int32t >= compareTo->fValue.int32t ?
1509 				(fValue.int32t == compareTo->fValue.int32t ? 0 : 1) : -1;
1510 
1511 		case B_OFF_T_TYPE:
1512 			// off_t typedef'd to a long long, i.e. a int64
1513 		case B_INT64_TYPE:
1514 			return fValue.int64t >= compareTo->fValue.int64t ?
1515 				(fValue.int64t == compareTo->fValue.int64t ? 0 : 1) : -1;
1516 
1517 		case B_UINT64_TYPE:
1518 		default:
1519 			return fValue.uint64t >= compareTo->fValue.uint64t ?
1520 				(fValue.uint64t == compareTo->fValue.uint64t ? 0 : 1) : -1;
1521 	}
1522 
1523 	return 0;
1524 }
1525 
1526 
1527 bool
1528 GenericAttributeText::CommitEditedText(BTextView* textView)
1529 {
1530 	ASSERT(fColumn->Editable());
1531 	const char* text = textView->Text();
1532 
1533 	if (fFullValueText == text)
1534 		// no change
1535 		return false;
1536 
1537 	if (!CommitEditedTextFlavor(textView))
1538 		return false;
1539 
1540 	// update text and width in this widget
1541 	fFullValueText = text;
1542 	// cause re-truncation
1543 	fDirty = true;
1544 	fValueDirty = true;
1545 
1546 	return true;
1547 }
1548 
1549 
1550 void
1551 GenericAttributeText::SetupEditing(BTextView* textView)
1552 {
1553 	textView->SetMaxBytes(kGenericReadBufferSize - 1);
1554 	textView->SetText(fFullValueText.String(), fFullValueText.Length());
1555 }
1556 
1557 
1558 bool
1559 GenericAttributeText::CommitEditedTextFlavor(BTextView* textView)
1560 {
1561 	BNode node(fModel->EntryRef());
1562 
1563 	if (node.InitCheck() != B_OK)
1564 		return false;
1565 
1566 	uint32 type = fColumn->AttrType();
1567 
1568 	if (type != B_STRING_TYPE
1569 		&& type != B_UINT64_TYPE
1570 		&& type != B_UINT32_TYPE
1571 		&& type != B_UINT16_TYPE
1572 		&& type != B_UINT8_TYPE
1573 		&& type != B_INT64_TYPE
1574 		&& type != B_INT32_TYPE
1575 		&& type != B_INT16_TYPE
1576 		&& type != B_INT8_TYPE
1577 		&& type != B_OFF_T_TYPE
1578 		&& type != B_TIME_TYPE
1579 		&& type != B_FLOAT_TYPE
1580 		&& type != B_DOUBLE_TYPE
1581 		&& type != B_CHAR_TYPE
1582 		&& type != B_BOOL_TYPE) {
1583 		BAlert* alert = new BAlert("",
1584 			B_TRANSLATE("Sorry, you cannot edit that attribute."),
1585 			B_TRANSLATE("Cancel"),
1586 			0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1587 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1588 		alert->Go();
1589 		return false;
1590 	}
1591 
1592 	const char* columnName = fColumn->AttrName();
1593 	ssize_t size = 0;
1594 
1595 	switch (type) {
1596 		case B_STRING_TYPE:
1597 			size = fModel->WriteAttr(columnName, type, 0, textView->Text(),
1598 				(size_t)textView->TextLength() + 1);
1599 			break;
1600 
1601 		case B_BOOL_TYPE:
1602 		{
1603 			bool value = strncasecmp(textView->Text(), "0", 1) != 0
1604 				&& strncasecmp(textView->Text(), "off", 2) != 0
1605 				&& strncasecmp(textView->Text(), "no", 3) != 0
1606 				&& strncasecmp(textView->Text(), "false", 4) != 0
1607 				&& strlen(textView->Text()) != 0;
1608 
1609 			size = fModel->WriteAttr(columnName, type, 0, &value, sizeof(bool));
1610 			break;
1611 		}
1612 
1613 		case B_CHAR_TYPE:
1614 		{
1615 			char ch;
1616 			sscanf(textView->Text(), "%c", &ch);
1617 			//Check if we read the start of a multi-byte glyph:
1618 			if (!isprint(ch)) {
1619 				BAlert* alert = new BAlert("",
1620 					B_TRANSLATE("Sorry, the 'Character' "
1621 					"attribute cannot store a multi-byte glyph."),
1622 					B_TRANSLATE("Cancel"),
1623 					0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1624 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1625 				alert->Go();
1626 				return false;
1627 			}
1628 
1629 			size = fModel->WriteAttr(columnName, type, 0, &ch, sizeof(char));
1630 			break;
1631 		}
1632 
1633 		case B_FLOAT_TYPE:
1634 		{
1635 			float floatVal;
1636 
1637 			if (sscanf(textView->Text(), "%f", &floatVal) == 1) {
1638 				fValueIsDefined = true;
1639 				fValue.floatt = floatVal;
1640 				size = fModel->WriteAttr(columnName, type, 0, &floatVal,
1641 					sizeof(float));
1642 			} else {
1643 				// If the value was already defined, it's on disk.
1644 				// Otherwise not.
1645 				return fValueIsDefined;
1646 			}
1647 			break;
1648 		}
1649 
1650 		case B_DOUBLE_TYPE:
1651 		{
1652 			double doubleVal;
1653 
1654 			if (sscanf(textView->Text(), "%lf", &doubleVal) == 1) {
1655 				fValueIsDefined = true;
1656 				fValue.doublet = doubleVal;
1657 				size = fModel->WriteAttr(columnName, type, 0, &doubleVal,
1658 					sizeof(double));
1659 			} else {
1660 				// If the value was already defined, it's on disk.
1661 				// Otherwise not.
1662 				return fValueIsDefined;
1663 			}
1664 			break;
1665 		}
1666 
1667 		case B_TIME_TYPE:
1668 		case B_OFF_T_TYPE:
1669 		case B_UINT64_TYPE:
1670 		case B_UINT32_TYPE:
1671 		case B_UINT16_TYPE:
1672 		case B_UINT8_TYPE:
1673 		case B_INT64_TYPE:
1674 		case B_INT32_TYPE:
1675 		case B_INT16_TYPE:
1676 		case B_INT8_TYPE:
1677 		{
1678 			GenericValueStruct tmp;
1679 			size_t scalarSize = 0;
1680 
1681 			switch (type) {
1682 				case B_TIME_TYPE:
1683 					tmp.time_tt = parsedate(textView->Text(), time(0));
1684 					scalarSize = sizeof(time_t);
1685 					break;
1686 
1687 				// do some size independent conversion on builtin types
1688 				case B_OFF_T_TYPE:
1689 					tmp.off_tt = StringToScalar(textView->Text());
1690 					scalarSize = sizeof(off_t);
1691 					break;
1692 
1693 				case B_UINT64_TYPE:
1694 				case B_INT64_TYPE:
1695 					tmp.int64t = StringToScalar(textView->Text());
1696 					scalarSize = sizeof(int64);
1697 					break;
1698 
1699 				case B_UINT32_TYPE:
1700 				case B_INT32_TYPE:
1701 					tmp.int32t = (int32)StringToScalar(textView->Text());
1702 					scalarSize = sizeof(int32);
1703 					break;
1704 
1705 				case B_UINT16_TYPE:
1706 				case B_INT16_TYPE:
1707 					tmp.int16t = (int16)StringToScalar(textView->Text());
1708 					scalarSize = sizeof(int16);
1709 					break;
1710 
1711 				case B_UINT8_TYPE:
1712 				case B_INT8_TYPE:
1713 					tmp.int8t = (int8)StringToScalar(textView->Text());
1714 					scalarSize = sizeof(int8);
1715 					break;
1716 
1717 				default:
1718 					TRESPASS();
1719 			}
1720 
1721 			size = fModel->WriteAttr(columnName, type, 0, &tmp, scalarSize);
1722 			break;
1723 		}
1724 	}
1725 
1726 	if (size < 0) {
1727 		BAlert* alert = new BAlert("",
1728 			B_TRANSLATE("There was an error writing the attribute."),
1729 			B_TRANSLATE("Cancel"),
1730 			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1731 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1732 		alert->Go();
1733 
1734 		fValueIsDefined = false;
1735 		return false;
1736 	}
1737 
1738 	fValueIsDefined = true;
1739 	return true;
1740 }
1741 
1742 
1743 // #pragma mark - DurationAttributeText (display as: duration)
1744 
1745 
1746 DurationAttributeText::DurationAttributeText(const Model* model,
1747 	const BColumn* column)
1748 	:
1749 	GenericAttributeText(model, column)
1750 {
1751 }
1752 
1753 
1754 // TODO: support editing!
1755 
1756 
1757 void
1758 DurationAttributeText::FitValue(BString* outString, const BPoseView* view)
1759 {
1760 	if (fValueDirty)
1761 		ReadValue(&fFullValueText);
1762 
1763 	fOldWidth = fColumn->Width();
1764 	fDirty = false;
1765 
1766 	if (!fValueIsDefined) {
1767 		*outString = "-";
1768 		fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1769 			fFullValueText.Length(), view, fOldWidth);
1770 		return;
1771 	}
1772 
1773 	int64 time = 0;
1774 
1775 	switch (fColumn->AttrType()) {
1776 		case B_TIME_TYPE:
1777 			time = fValue.time_tt * 1000000LL;
1778 			break;
1779 
1780 		case B_INT8_TYPE:
1781 			time = fValue.int8t * 1000000LL;
1782 			break;
1783 
1784 		case B_INT16_TYPE:
1785 			time = fValue.int16t * 1000000LL;
1786 			break;
1787 
1788 		case B_INT32_TYPE:
1789 			time = fValue.int32t * 1000000LL;
1790 			break;
1791 
1792 		case B_INT64_TYPE:
1793 			time = fValue.int64t;
1794 			break;
1795 	}
1796 
1797 	// TODO: ignores micro seconds for now
1798 	int32 seconds = time / 1000000LL;
1799 
1800 	bool negative = seconds < 0;
1801 	if (negative)
1802 		seconds = -seconds;
1803 
1804 	int32 hours = seconds / 3600;
1805 	seconds -= hours * 3600;
1806 	int32 minutes = seconds / 60;
1807 	seconds = seconds % 60;
1808 
1809 	char buffer[256];
1810 	if (hours > 0) {
1811 		snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32 ":%02"
1812 			B_PRId32, negative ? "-" : "", hours, minutes, seconds);
1813 	} else {
1814 		snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32,
1815 			negative ? "-" : "", minutes, seconds);
1816 	}
1817 
1818 	fFullValueText = buffer;
1819 
1820 	fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1821 		fFullValueText.Length(), view, fOldWidth);
1822 }
1823 
1824 
1825 // #pragma mark - CheckboxAttributeText (display as: checkbox)
1826 
1827 
1828 CheckboxAttributeText::CheckboxAttributeText(const Model* model,
1829 	const BColumn* column)
1830 	:
1831 	GenericAttributeText(model, column),
1832 	fOnChar("✖"),
1833 	fOffChar("-")
1834 {
1835 	// TODO: better have common data in the column object!
1836 	if (const char* separator = strchr(column->DisplayAs(), ':')) {
1837 		BString chars(separator + 1);
1838 		int32 length;
1839 		const char* c = chars.CharAt(0, &length);
1840 		fOnChar.SetTo(c, length);
1841 		if (c[length]) {
1842 			c = chars.CharAt(1, &length);
1843 			fOffChar.SetTo(c, length);
1844 		}
1845 	}
1846 }
1847 
1848 
1849 void
1850 CheckboxAttributeText::SetupEditing(BTextView* view)
1851 {
1852 	// TODO: support editing for real!
1853 	BString outString;
1854 	GenericAttributeText::FitValue(&outString, NULL);
1855 	GenericAttributeText::SetupEditing(view);
1856 }
1857 
1858 
1859 void
1860 CheckboxAttributeText::FitValue(BString* outString, const BPoseView* view)
1861 {
1862 	if (fValueDirty)
1863 		ReadValue(&fFullValueText);
1864 
1865 	fOldWidth = fColumn->Width();
1866 	fDirty = false;
1867 
1868 	if (!fValueIsDefined) {
1869 		*outString = fOffChar;
1870 		fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1871 			fFullValueText.Length(), view, fOldWidth);
1872 		return;
1873 	}
1874 
1875 	bool checked = false;
1876 
1877 	switch (fColumn->AttrType()) {
1878 		case B_BOOL_TYPE:
1879 			checked = fValue.boolt;
1880 			break;
1881 
1882 		case B_INT8_TYPE:
1883 		case B_UINT8_TYPE:
1884 			checked = fValue.int8t != 0;
1885 			break;
1886 
1887 		case B_INT16_TYPE:
1888 		case B_UINT16_TYPE:
1889 			checked = fValue.int16t != 0;
1890 			break;
1891 
1892 		case B_INT32_TYPE:
1893 		case B_UINT32_TYPE:
1894 			checked = fValue.int32t != 0;
1895 			break;
1896 	}
1897 
1898 	fFullValueText = checked ? fOnChar : fOffChar;
1899 
1900 	fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1901 		fFullValueText.Length(), view, fOldWidth);
1902 }
1903 
1904 
1905 // #pragma mark - RatingAttributeText (display as: rating)
1906 
1907 
1908 RatingAttributeText::RatingAttributeText(const Model* model,
1909 	const BColumn* column)
1910 	:
1911 	GenericAttributeText(model, column),
1912 	fCount(5),
1913 	fMax(10)
1914 {
1915 	// TODO: support different star counts/max via specifier
1916 }
1917 
1918 
1919 void
1920 RatingAttributeText::SetupEditing(BTextView* view)
1921 {
1922 	// TODO: support editing for real!
1923 	BString outString;
1924 	GenericAttributeText::FitValue(&outString, NULL);
1925 	GenericAttributeText::SetupEditing(view);
1926 }
1927 
1928 
1929 void
1930 RatingAttributeText::FitValue(BString* ratingString, const BPoseView* view)
1931 {
1932 	if (fValueDirty)
1933 		ReadValue(&fFullValueText);
1934 
1935 	fOldWidth = fColumn->Width();
1936 	fDirty = false;
1937 
1938 	int64 rating;
1939 	if (fValueIsDefined) {
1940 		switch (fColumn->AttrType()) {
1941 			case B_INT8_TYPE:
1942 				rating = fValue.int8t;
1943 				break;
1944 
1945 			case B_INT16_TYPE:
1946 				rating = fValue.int16t;
1947 				break;
1948 
1949 			case B_INT32_TYPE:
1950 				rating = fValue.int32t;
1951 				break;
1952 
1953 			default:
1954 				rating = 0;
1955 				break;
1956 		}
1957 	} else
1958 		rating = 0;
1959 
1960 	if (rating > fMax)
1961 		rating = fMax;
1962 
1963 	if (rating < 0)
1964 		rating = 0;
1965 
1966 	int32 steps = fMax / fCount;
1967 	fFullValueText = "";
1968 
1969 	for (int32 i = 0; i < fCount; i++) {
1970 		int64 n = i * steps;
1971 		if (rating > n + steps / 2)
1972 			fFullValueText += "★";
1973 		else if (rating > n)
1974 			fFullValueText += "⯪";
1975 		else
1976 			fFullValueText += "☆";
1977 	}
1978 
1979 	fTruncatedWidth = TruncString(ratingString, fFullValueText.String(),
1980 		fFullValueText.Length(), view, fOldWidth);
1981 }
1982 
1983 
1984 // #pragma mark - OpenWithRelationAttributeText
1985 
1986 
1987 OpenWithRelationAttributeText::OpenWithRelationAttributeText(const Model* model,
1988 	const BColumn* column, const BPoseView* view)
1989 	:
1990 	ScalarAttributeText(model, column),
1991 	fPoseView(view)
1992 {
1993 }
1994 
1995 
1996 int64
1997 OpenWithRelationAttributeText::ReadValue()
1998 {
1999 	fValueDirty = false;
2000 
2001 	const OpenWithPoseView* view
2002 		= dynamic_cast<const OpenWithPoseView*>(fPoseView);
2003 	if (view != NULL) {
2004 		fValue = view->OpenWithRelation(fModel);
2005 		fValueIsDefined = true;
2006 	}
2007 
2008 	return fValue;
2009 }
2010 
2011 
2012 float
2013 OpenWithRelationAttributeText::PreferredWidth(const BPoseView* pose) const
2014 {
2015 	BString widthString;
2016 	TruncString(&widthString, fRelationText.String(), fRelationText.Length(),
2017 		pose, 500, B_TRUNCATE_END);
2018 	return pose->StringWidth(widthString.String());
2019 }
2020 
2021 
2022 void
2023 OpenWithRelationAttributeText::FitValue(BString* outString,
2024 	const BPoseView* view)
2025 {
2026 	if (fValueDirty)
2027 		ReadValue();
2028 
2029 	ASSERT(view == fPoseView);
2030 	const OpenWithPoseView* launchWithView
2031 		= dynamic_cast<const OpenWithPoseView*>(view);
2032 	if (launchWithView != NULL)
2033 		launchWithView->OpenWithRelationDescription(fModel, &fRelationText);
2034 
2035 	fOldWidth = fColumn->Width();
2036 	fTruncatedWidth = TruncString(outString, fRelationText.String(),
2037 		fRelationText.Length(), view, fOldWidth, B_TRUNCATE_END);
2038 	fDirty = false;
2039 }
2040 
2041 
2042 // #pragma mark - VersionAttributeText
2043 
2044 
2045 VersionAttributeText::VersionAttributeText(const Model* model,
2046 	const BColumn* column, bool app)
2047 	:
2048 	StringAttributeText(model, column),
2049 	fAppVersion(app)
2050 {
2051 }
2052 
2053 
2054 void
2055 VersionAttributeText::ReadValue(BString* outString)
2056 {
2057 	fValueDirty = false;
2058 
2059 	BModelOpener opener(fModel);
2060 	BFile* file = dynamic_cast<BFile*>(fModel->Node());
2061 	if (file != NULL) {
2062 		BAppFileInfo info(file);
2063 		version_info version;
2064 		if (info.InitCheck() == B_OK
2065 			&& info.GetVersionInfo(&version, fAppVersion
2066 				? B_APP_VERSION_KIND : B_SYSTEM_VERSION_KIND) == B_OK) {
2067 			*outString = version.short_info;
2068 			return;
2069 		}
2070 	}
2071 
2072 	*outString = "-";
2073 }
2074