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