xref: /haiku/src/kits/tracker/WidgetAttributeText.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
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 	fOldWidth = fColumn->Width();
798 	fTruncatedWidth = TruncString(outString, fFullValueText.String(),
799 		fFullValueText.Length(), view, fOldWidth, B_TRUNCATE_END);
800 	fDirty = false;
801 }
802 
803 
804 void
805 NameAttributeText::SetUpEditing(BTextView* textView)
806 {
807 	DisallowFilenameKeys(textView);
808 
809 	textView->SetMaxBytes(B_FILE_NAME_LENGTH);
810 	textView->SetText(fFullValueText.String(), fFullValueText.Length());
811 }
812 
813 
814 bool
815 NameAttributeText::CommitEditedTextFlavor(BTextView* textView)
816 {
817 	const char* text = textView->Text();
818 
819 	BEntry entry(fModel->EntryRef());
820 	if (entry.InitCheck() != B_OK)
821 		return false;
822 
823 	BDirectory	parent;
824 	if (entry.GetParent(&parent) != B_OK)
825 		return false;
826 
827 	bool removeExisting = false;
828 	if (parent.Contains(text)) {
829 		BAlert* alert = new BAlert("",
830 			B_TRANSLATE("That name is already taken. "
831 			"Please type another one."),
832 			B_TRANSLATE("Replace other file"),
833 			B_TRANSLATE("OK"),
834 			NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
835 		alert->SetShortcut(0, 'r');
836 		if (alert->Go())
837 			return false;
838 
839 		removeExisting = true;
840 	}
841 
842 	// TODO:
843 	// use model-flavor specific virtuals for all of these special
844 	// renamings
845 	status_t result;
846 	if (fModel->IsVolume()) {
847 		BVolume	volume(fModel->NodeRef()->device);
848 		result = volume.InitCheck();
849 		if (result == B_OK) {
850 			RenameVolumeUndo undo(volume, text);
851 
852 			result = volume.SetName(text);
853 			if (result != B_OK)
854 				undo.Remove();
855 		}
856 	} else {
857 		if (fModel->IsQuery()) {
858 			BModelWriteOpener opener(fModel);
859 			ASSERT(fModel->Node());
860 			MoreOptionsStruct::SetQueryTemporary(fModel->Node(), false);
861 		}
862 
863 		RenameUndo undo(entry, text);
864 
865 		result = entry.Rename(text, removeExisting);
866 		if (result != B_OK)
867 			undo.Remove();
868 	}
869 
870 	return result == B_OK;
871 }
872 
873 
874 void
875 NameAttributeText::SetSortFolderNamesFirst(bool enabled)
876 {
877 	NameAttributeText::sSortFolderNamesFirst = enabled;
878 }
879 
880 
881 bool
882 NameAttributeText::IsEditable() const
883 {
884 	return StringAttributeText::IsEditable()
885 		&& !fModel->HasLocalizedName();
886 }
887 
888 
889 // #pragma mark - RealNameAttributeText
890 
891 
892 RealNameAttributeText::RealNameAttributeText(const Model* model,
893 	const BColumn* column)
894 	:
895 	StringAttributeText(model, column)
896 {
897 }
898 
899 
900 int
901 RealNameAttributeText::Compare(WidgetAttributeText& attr, BPoseView* view)
902 {
903 	RealNameAttributeText* compareTo
904 		= dynamic_cast<RealNameAttributeText*>(&attr);
905 	ThrowOnAssert(compareTo != NULL);
906 
907 	if (fValueDirty)
908 		ReadValue(&fFullValueText);
909 
910 	if (RealNameAttributeText::sSortFolderNamesFirst)
911 		return fModel->CompareFolderNamesFirst(attr.TargetModel());
912 
913 	return NaturalCompare(fFullValueText.String(),
914 		compareTo->ValueAsText(view));
915 }
916 
917 
918 void
919 RealNameAttributeText::ReadValue(BString* outString)
920 {
921 	*outString = fModel->EntryRef()->name;
922 
923 	fValueDirty = false;
924 }
925 
926 
927 void
928 RealNameAttributeText::FitValue(BString* outString, const BPoseView* view)
929 {
930 	if (fValueDirty)
931 		ReadValue(&fFullValueText);
932 	fOldWidth = fColumn->Width();
933 	fTruncatedWidth = TruncString(outString, fFullValueText.String(),
934 		fFullValueText.Length(), view, fOldWidth, B_TRUNCATE_END);
935 	fDirty = false;
936 }
937 
938 
939 void
940 RealNameAttributeText::SetUpEditing(BTextView* textView)
941 {
942 	DisallowFilenameKeys(textView);
943 
944 	textView->SetMaxBytes(B_FILE_NAME_LENGTH);
945 	textView->SetText(fFullValueText.String(), fFullValueText.Length());
946 }
947 
948 
949 bool
950 RealNameAttributeText::CommitEditedTextFlavor(BTextView* textView)
951 {
952 	const char* text = textView->Text();
953 
954 	BEntry entry(fModel->EntryRef());
955 	if (entry.InitCheck() != B_OK)
956 		return false;
957 
958 	BDirectory	parent;
959 	if (entry.GetParent(&parent) != B_OK)
960 		return false;
961 
962 	bool removeExisting = false;
963 	if (parent.Contains(text)) {
964 		BAlert* alert = new BAlert("",
965 			B_TRANSLATE("That name is already taken. "
966 			"Please type another one."),
967 			B_TRANSLATE("Replace other file"),
968 			B_TRANSLATE("OK"),
969 			NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
970 
971 		alert->SetShortcut(0, 'r');
972 
973 		if (alert->Go())
974 			return false;
975 
976 		removeExisting = true;
977 	}
978 
979 	// TODO:
980 	// use model-flavor specific virtuals for all of these special
981 	// renamings
982 	status_t result;
983 	if (fModel->IsVolume()) {
984 		BVolume volume(fModel->NodeRef()->device);
985 		result = volume.InitCheck();
986 		if (result == B_OK) {
987 			RenameVolumeUndo undo(volume, text);
988 
989 			result = volume.SetName(text);
990 			if (result != B_OK)
991 				undo.Remove();
992 		}
993 	} else {
994 		if (fModel->IsQuery()) {
995 			BModelWriteOpener opener(fModel);
996 			ASSERT(fModel->Node());
997 			MoreOptionsStruct::SetQueryTemporary(fModel->Node(), false);
998 		}
999 
1000 		RenameUndo undo(entry, text);
1001 
1002 		result = entry.Rename(text, removeExisting);
1003 		if (result != B_OK)
1004 			undo.Remove();
1005 	}
1006 
1007 	return result == B_OK;
1008 }
1009 
1010 
1011 void
1012 RealNameAttributeText::SetSortFolderNamesFirst(bool enabled)
1013 {
1014 	RealNameAttributeText::sSortFolderNamesFirst = enabled;
1015 }
1016 
1017 
1018 // #pragma mark - owner/group
1019 
1020 
1021 #ifdef OWNER_GROUP_ATTRIBUTES
1022 OwnerAttributeText::OwnerAttributeText(const Model* model,
1023 	const BColumn* column)
1024 	:
1025 	StringAttributeText(model, column)
1026 {
1027 }
1028 
1029 
1030 void
1031 OwnerAttributeText::ReadValue(BString* outString)
1032 {
1033 	uid_t nodeOwner = fModel->StatBuf()->st_uid;
1034 	BString user;
1035 
1036 	if (nodeOwner == 0) {
1037 		if (getenv("USER") != NULL)
1038 			user << getenv("USER");
1039 		else
1040 			user << "root";
1041 	} else
1042 		user << nodeOwner;
1043 	*outString = user.String();
1044 
1045 	fValueDirty = false;
1046 }
1047 
1048 
1049 GroupAttributeText::GroupAttributeText(const Model* model,
1050 	const BColumn* column)
1051 	:
1052 	StringAttributeText(model, column)
1053 {
1054 }
1055 
1056 
1057 void
1058 GroupAttributeText::ReadValue(BString* outString)
1059 {
1060 	gid_t nodeGroup = fModel->StatBuf()->st_gid;
1061 	BString group;
1062 
1063 	if (nodeGroup == 0) {
1064 		if (getenv("GROUP") != NULL)
1065 			group << getenv("GROUP");
1066 		else
1067 			group << "0";
1068 	} else
1069 		group << nodeGroup;
1070 	*outString = group.String();
1071 
1072 	fValueDirty = false;
1073 }
1074 #endif  // OWNER_GROUP_ATTRIBUTES
1075 
1076 
1077 //	#pragma mark - ModeAttributeText
1078 
1079 
1080 ModeAttributeText::ModeAttributeText(const Model* model,
1081 	const BColumn* column)
1082 	:
1083 	StringAttributeText(model, column)
1084 {
1085 }
1086 
1087 
1088 void
1089 ModeAttributeText::ReadValue(BString* outString)
1090 {
1091 	mode_t mode = fModel->StatBuf()->st_mode;
1092 	mode_t baseMask = 00400;
1093 	char buffer[11];
1094 
1095 	char* scanner = buffer;
1096 
1097 	if (S_ISDIR(mode))
1098 		*scanner++ = 'd';
1099 	else if (S_ISLNK(mode))
1100 		*scanner++ = 'l';
1101 	else if (S_ISBLK(mode))
1102 		*scanner++ = 'b';
1103 	else if (S_ISCHR(mode))
1104 		*scanner++ = 'c';
1105 	else
1106 		*scanner++ = '-';
1107 
1108 	for (int32 index = 0; index < 9; index++) {
1109 		*scanner++ = (mode & baseMask) ? "rwx"[index % 3] : '-';
1110 		baseMask >>= 1;
1111 	}
1112 
1113 	*scanner = 0;
1114 	*outString = buffer;
1115 
1116 	fValueDirty = false;
1117 }
1118 
1119 
1120 //	#pragma mark - SizeAttributeText
1121 
1122 
1123 SizeAttributeText::SizeAttributeText(const Model* model,
1124 	const BColumn* column)
1125 	:
1126 	ScalarAttributeText(model, column)
1127 {
1128 }
1129 
1130 
1131 int64
1132 SizeAttributeText::ReadValue()
1133 {
1134 	fValueDirty = false;
1135 	// get the size
1136 
1137 	if (fModel->IsVolume()) {
1138 		BVolume volume(fModel->NodeRef()->device);
1139 
1140 		return volume.Capacity();
1141 	}
1142 
1143 	if (fModel->IsDirectory() || fModel->IsQuery()
1144 		|| fModel->IsQueryTemplate() || fModel->IsSymLink()
1145 		|| fModel->IsVirtualDirectory()) {
1146 		return kUnknownSize;
1147 	}
1148 
1149 	fValueIsDefined = true;
1150 
1151 	return fModel->StatBuf()->st_size;
1152 }
1153 
1154 
1155 void
1156 SizeAttributeText::FitValue(BString* outString, const BPoseView* view)
1157 {
1158 	if (fValueDirty)
1159 		fValue = ReadValue();
1160 
1161 	fOldWidth = fColumn->Width();
1162 	fTruncatedWidth = TruncFileSize(outString, fValue, view, fOldWidth);
1163 	fDirty = false;
1164 }
1165 
1166 
1167 float
1168 SizeAttributeText::PreferredWidth(const BPoseView* pose) const
1169 {
1170 	if (fValueIsDefined) {
1171 		BString widthString;
1172 		TruncFileSize(&widthString, fValue, pose, 100000);
1173 		return pose->StringWidth(widthString.String());
1174 	}
1175 
1176 	return pose->StringWidth("-");
1177 }
1178 
1179 
1180 // #pragma mark - TimeAttributeText
1181 
1182 
1183 TimeAttributeText::TimeAttributeText(const Model* model,
1184 	const BColumn* column)
1185 	:
1186 	ScalarAttributeText(model, column),
1187 	fLastClockIs24(false),
1188 	fLastDateOrder(kDateFormatEnd),
1189 	fLastTimeFormatSeparator(kSeparatorsEnd)
1190 {
1191 }
1192 
1193 
1194 float
1195 TimeAttributeText::PreferredWidth(const BPoseView* pose) const
1196 {
1197 	BString widthString;
1198 	TruncTimeBase(&widthString, fValue, pose, 100000);
1199 	return pose->StringWidth(widthString.String());
1200 }
1201 
1202 
1203 void
1204 TimeAttributeText::FitValue(BString* outString, const BPoseView* view)
1205 {
1206 	if (fValueDirty)
1207 		fValue = ReadValue();
1208 
1209 	fOldWidth = fColumn->Width();
1210 	fTruncatedWidth = TruncTime(outString, fValue, view, fOldWidth);
1211 	fDirty = false;
1212 }
1213 
1214 
1215 bool
1216 TimeAttributeText::CheckSettingsChanged(void)
1217 {
1218 	// TODO : check against the actual locale settings
1219 	return false;
1220 }
1221 
1222 
1223 //	#pragma mark - CreationTimeAttributeText
1224 
1225 
1226 CreationTimeAttributeText::CreationTimeAttributeText(const Model* model,
1227 	const BColumn* column)
1228 	:
1229 	TimeAttributeText(model, column)
1230 {
1231 }
1232 
1233 
1234 int64
1235 CreationTimeAttributeText::ReadValue()
1236 {
1237 	fValueDirty = false;
1238 	fValueIsDefined = true;
1239 	return fModel->StatBuf()->st_crtime;
1240 }
1241 
1242 
1243 //	#pragma mark - ModificationTimeAttributeText
1244 
1245 
1246 ModificationTimeAttributeText::ModificationTimeAttributeText(
1247 	const Model* model, const BColumn* column)
1248 	:
1249 	TimeAttributeText(model, column)
1250 {
1251 }
1252 
1253 
1254 int64
1255 ModificationTimeAttributeText::ReadValue()
1256 {
1257 	fValueDirty = false;
1258 	fValueIsDefined = true;
1259 	return fModel->StatBuf()->st_mtime;
1260 }
1261 
1262 
1263 //	#pragma mark - GenericAttributeText
1264 
1265 
1266 GenericAttributeText::GenericAttributeText(const Model* model,
1267 	const BColumn* column)
1268 	:
1269 	StringAttributeText(model, column)
1270 {
1271 }
1272 
1273 
1274 bool
1275 GenericAttributeText::CheckAttributeChanged()
1276 {
1277 	GenericValueStruct tmpValue = fValue;
1278 	BString tmpString(fFullValueText);
1279 	ReadValue(&fFullValueText);
1280 
1281 	// fDirty could already be true, in that case we mustn't set it to
1282 	// false, even if the attribute text hasn't changed
1283 	bool changed = fValue.int64t != tmpValue.int64t
1284 		|| tmpString != fFullValueText;
1285 	if (changed)
1286 		fDirty = true;
1287 
1288 	return fDirty;
1289 }
1290 
1291 
1292 float
1293 GenericAttributeText::PreferredWidth(const BPoseView* pose) const
1294 {
1295 	return pose->StringWidth(fFullValueText.String());
1296 }
1297 
1298 
1299 void
1300 GenericAttributeText::ReadValue(BString* outString)
1301 {
1302 	BModelOpener opener(const_cast<Model*>(fModel));
1303 
1304 	ssize_t length = 0;
1305 	fFullValueText = "-";
1306 	fValue.int64t = 0;
1307 	fValueIsDefined = false;
1308 	fValueDirty = false;
1309 
1310 	if (!fModel->Node())
1311 		return;
1312 
1313 	switch (fColumn->AttrType()) {
1314 		case B_STRING_TYPE:
1315 		{
1316 			char buffer[kGenericReadBufferSize];
1317 			length = fModel->Node()->ReadAttr(fColumn->AttrName(),
1318 				fColumn->AttrType(), 0, buffer, kGenericReadBufferSize - 1);
1319 
1320 			if (length > 0) {
1321 				buffer[length] = '\0';
1322 				// make sure the buffer is null-terminated even if we
1323 				// didn't read the whole attribute in or it wasn't to
1324 				// begin with
1325 
1326 				*outString = buffer;
1327 				fValueIsDefined = true;
1328 			}
1329 			break;
1330 		}
1331 
1332 		case B_SSIZE_T_TYPE:
1333 		case B_TIME_TYPE:
1334 		case B_OFF_T_TYPE:
1335 		case B_FLOAT_TYPE:
1336 		case B_BOOL_TYPE:
1337 		case B_CHAR_TYPE:
1338 		case B_INT8_TYPE:
1339 		case B_INT16_TYPE:
1340 		case B_INT32_TYPE:
1341 		case B_INT64_TYPE:
1342 		case B_UINT8_TYPE:
1343 		case B_UINT16_TYPE:
1344 		case B_UINT32_TYPE:
1345 		case B_UINT64_TYPE:
1346 		case B_DOUBLE_TYPE:
1347 		{
1348 			// read in the numerical bit representation and attach it
1349 			// with a type, depending on the bytes that could be read
1350 			attr_info info;
1351 			GenericValueStruct tmp;
1352 			if (fModel->Node()->GetAttrInfo(fColumn->AttrName(), &info)
1353 					== B_OK) {
1354 				if (info.size && info.size <= (off_t)sizeof(int64)) {
1355 					length = fModel->Node()->ReadAttr(fColumn->AttrName(),
1356 						fColumn->AttrType(), 0, &tmp, (size_t)info.size);
1357 				}
1358 
1359 				// We used tmp as a block of memory, now set the
1360 				// correct fValue:
1361 
1362 				if (length == info.size) {
1363 					if (fColumn->AttrType() == B_FLOAT_TYPE
1364 						|| fColumn->AttrType() == B_DOUBLE_TYPE) {
1365 						// filter out special float/double types
1366 						switch (info.size) {
1367 							case sizeof(float):
1368 								fValueIsDefined = true;
1369 								fValue.floatt = tmp.floatt;
1370 								break;
1371 
1372 							case sizeof(double):
1373 								fValueIsDefined = true;
1374 								fValue.doublet = tmp.doublet;
1375 								break;
1376 
1377 							default:
1378 								TRESPASS();
1379 								break;
1380 						}
1381 					} else {
1382 						// handle the standard data types
1383 						switch (info.size) {
1384 							case sizeof(char):
1385 								// Takes care of bool too.
1386 								fValueIsDefined = true;
1387 								fValue.int8t = tmp.int8t;
1388 								break;
1389 
1390 							case sizeof(int16):
1391 								fValueIsDefined = true;
1392 								fValue.int16t = tmp.int16t;
1393 								break;
1394 
1395 							case sizeof(int32):
1396 								// Takes care of time_t too.
1397 								fValueIsDefined = true;
1398 								fValue.int32t = tmp.int32t;
1399 								break;
1400 
1401 							case sizeof(int64):
1402 								// Takes care of off_t too.
1403 								fValueIsDefined = true;
1404 								fValue.int64t = tmp.int64t;
1405 								break;
1406 
1407 							default:
1408 								TRESPASS();
1409 								break;
1410 						}
1411 					}
1412 				}
1413 			}
1414 			break;
1415 		}
1416 	}
1417 }
1418 
1419 
1420 void
1421 GenericAttributeText::FitValue(BString* outString, const BPoseView* view)
1422 {
1423 	if (fValueDirty)
1424 		ReadValue(&fFullValueText);
1425 
1426 	fOldWidth = fColumn->Width();
1427 
1428 	if (!fValueIsDefined) {
1429 		*outString = "-";
1430 		fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1431 			fFullValueText.Length(), view, fOldWidth);
1432 		fDirty = false;
1433 		return;
1434 	}
1435 
1436 	char buffer[256];
1437 
1438 	switch (fColumn->AttrType()) {
1439 		case B_SIZE_T_TYPE:
1440 			TruncFileSizeBase(outString, fValue.int32t, view, fOldWidth);
1441 			return;
1442 
1443 		case B_SSIZE_T_TYPE:
1444 			if (fValue.int32t > 0) {
1445 				TruncFileSizeBase(outString, fValue.int32t, view, fOldWidth);
1446 				return;
1447 			}
1448 			sprintf(buffer, "%s", strerror(fValue.int32t));
1449 			fFullValueText = buffer;
1450 			break;
1451 
1452 		case B_STRING_TYPE:
1453 			fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1454 				fFullValueText.Length(), view, fOldWidth);
1455 			fDirty = false;
1456 			return;
1457 
1458 		case B_OFF_T_TYPE:
1459 			// As a side effect update the fFullValueText to the string
1460 			// representation of value
1461 			TruncFileSize(&fFullValueText, fValue.off_tt, view, 100000);
1462 			fTruncatedWidth = TruncFileSize(outString, fValue.off_tt, view,
1463 				fOldWidth);
1464 			fDirty = false;
1465 			return;
1466 
1467 		case B_TIME_TYPE:
1468 			// As a side effect update the fFullValueText to the string
1469 			// representation of value
1470 			TruncTime(&fFullValueText, fValue.time_tt, view, 100000);
1471 			fTruncatedWidth = TruncTime(outString, fValue.time_tt, view,
1472 				fOldWidth);
1473 			fDirty = false;
1474 			return;
1475 
1476 		case B_BOOL_TYPE:
1477 			// For now use true/false, would be nice to be able to set
1478 			// the value text
1479 
1480  			sprintf(buffer, "%s", fValue.boolt ? "true" : "false");
1481 			fFullValueText = buffer;
1482 			break;
1483 
1484 		case B_CHAR_TYPE:
1485 			// Make sure no non-printable characters are displayed:
1486 			if (!isprint(fValue.uint8t)) {
1487 				*outString = "-";
1488 				fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1489 					fFullValueText.Length(), view, fOldWidth);
1490 				fDirty = false;
1491 				return;
1492 			}
1493 
1494 			sprintf(buffer, "%c", fValue.uint8t);
1495 			fFullValueText = buffer;
1496 			break;
1497 
1498 		case B_INT8_TYPE:
1499 			sprintf(buffer, "%d", fValue.int8t);
1500 			fFullValueText = buffer;
1501 			break;
1502 
1503 		case B_UINT8_TYPE:
1504 			sprintf(buffer, "%d", fValue.uint8t);
1505 			fFullValueText = buffer;
1506 			break;
1507 
1508 		case B_INT16_TYPE:
1509 			sprintf(buffer, "%d", fValue.int16t);
1510 			fFullValueText = buffer;
1511 			break;
1512 
1513 		case B_UINT16_TYPE:
1514 			sprintf(buffer, "%d", fValue.uint16t);
1515 			fFullValueText = buffer;
1516 			break;
1517 
1518 		case B_INT32_TYPE:
1519 			sprintf(buffer, "%" B_PRId32, fValue.int32t);
1520 			fFullValueText = buffer;
1521 			break;
1522 
1523 		case B_UINT32_TYPE:
1524 			sprintf(buffer, "%" B_PRId32, fValue.uint32t);
1525 			fFullValueText = buffer;
1526 			break;
1527 
1528 		case B_INT64_TYPE:
1529 			sprintf(buffer, "%" B_PRId64, fValue.int64t);
1530 			fFullValueText = buffer;
1531 			break;
1532 
1533 		case B_UINT64_TYPE:
1534 			sprintf(buffer, "%" B_PRId64, fValue.uint64t);
1535 			fFullValueText = buffer;
1536 			break;
1537 
1538 		case B_FLOAT_TYPE:
1539 			snprintf(buffer, sizeof(buffer), "%g", fValue.floatt);
1540 			fFullValueText = buffer;
1541 			break;
1542 
1543 		case B_DOUBLE_TYPE:
1544 			snprintf(buffer, sizeof(buffer), "%g", fValue.doublet);
1545 			fFullValueText = buffer;
1546 			break;
1547 
1548 		default:
1549 			*outString = "-";
1550 			fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1551 				fFullValueText.Length(), view, fOldWidth);
1552 			fDirty = false;
1553 			return;
1554 	}
1555 	fTruncatedWidth = TruncString(outString, buffer, (ssize_t)strlen(buffer),
1556 		view, fOldWidth);
1557 	fDirty = false;
1558 }
1559 
1560 
1561 const char*
1562 GenericAttributeText::ValueAsText(const BPoseView* view)
1563 {
1564 	// TODO: redesign this - this is to make sure the value is valid
1565 	bool oldDirty = fDirty;
1566 	BString outString;
1567 	FitValue(&outString, view);
1568 	fDirty = oldDirty;
1569 
1570 	return fFullValueText.String();
1571 }
1572 
1573 
1574 int
1575 GenericAttributeText::Compare(WidgetAttributeText& attr, BPoseView*)
1576 {
1577 	GenericAttributeText* compareTo
1578 		= dynamic_cast<GenericAttributeText*>(&attr);
1579 	ThrowOnAssert(compareTo != NULL);
1580 
1581 	if (fValueDirty)
1582 		ReadValue(&fFullValueText);
1583 
1584 	if (compareTo->fValueDirty)
1585 		compareTo->ReadValue(&compareTo->fFullValueText);
1586 
1587 	// sort undefined values last, regardless of the other value
1588 	if (!fValueIsDefined)
1589 		return compareTo->fValueIsDefined ? 1 : 0;
1590 
1591 	if (!compareTo->fValueIsDefined)
1592 		return -1;
1593 
1594 	switch (fColumn->AttrType()) {
1595 		case B_STRING_TYPE:
1596 			return fFullValueText.ICompare(compareTo->fFullValueText);
1597 
1598 		case B_CHAR_TYPE:
1599 		{
1600 			char vStr[2] = { static_cast<char>(fValue.uint8t), 0 };
1601 			char cStr[2] = { static_cast<char>(compareTo->fValue.uint8t), 0};
1602 
1603 			BString valueStr(vStr);
1604 			BString compareToStr(cStr);
1605 
1606 			return valueStr.ICompare(compareToStr);
1607 		}
1608 
1609 		case B_FLOAT_TYPE:
1610 			return fValue.floatt >= compareTo->fValue.floatt ?
1611 				(fValue.floatt == compareTo->fValue.floatt ? 0 : 1) : -1;
1612 
1613 		case B_DOUBLE_TYPE:
1614 			return fValue.doublet >= compareTo->fValue.doublet ?
1615 				(fValue.doublet == compareTo->fValue.doublet ? 0 : 1) : -1;
1616 
1617 		case B_BOOL_TYPE:
1618 			return fValue.boolt >= compareTo->fValue.boolt ?
1619 				(fValue.boolt == compareTo->fValue.boolt ? 0 : 1) : -1;
1620 
1621 		case B_UINT8_TYPE:
1622 			return fValue.uint8t >= compareTo->fValue.uint8t ?
1623 				(fValue.uint8t == compareTo->fValue.uint8t ? 0 : 1) : -1;
1624 
1625 		case B_INT8_TYPE:
1626 			return fValue.int8t >= compareTo->fValue.int8t ?
1627 					(fValue.int8t == compareTo->fValue.int8t ? 0 : 1) : -1;
1628 
1629 		case B_UINT16_TYPE:
1630 			return fValue.uint16t >= compareTo->fValue.uint16t ?
1631 				(fValue.uint16t == compareTo->fValue.uint16t ? 0 : 1) : -1;
1632 
1633 		case B_INT16_TYPE:
1634 			return fValue.int16t >= compareTo->fValue.int16t ?
1635 				(fValue.int16t == compareTo->fValue.int16t ? 0 : 1) : -1;
1636 
1637 		case B_UINT32_TYPE:
1638 			return fValue.uint32t >= compareTo->fValue.uint32t ?
1639 				(fValue.uint32t == compareTo->fValue.uint32t ? 0 : 1) : -1;
1640 
1641 		case B_TIME_TYPE:
1642 			// time_t typedef'd to a long, i.e. a int32
1643 		case B_INT32_TYPE:
1644 			return fValue.int32t >= compareTo->fValue.int32t ?
1645 				(fValue.int32t == compareTo->fValue.int32t ? 0 : 1) : -1;
1646 
1647 		case B_OFF_T_TYPE:
1648 			// off_t typedef'd to a long long, i.e. a int64
1649 		case B_INT64_TYPE:
1650 			return fValue.int64t >= compareTo->fValue.int64t ?
1651 				(fValue.int64t == compareTo->fValue.int64t ? 0 : 1) : -1;
1652 
1653 		case B_UINT64_TYPE:
1654 		default:
1655 			return fValue.uint64t >= compareTo->fValue.uint64t ?
1656 				(fValue.uint64t == compareTo->fValue.uint64t ? 0 : 1) : -1;
1657 	}
1658 
1659 	return 0;
1660 }
1661 
1662 
1663 bool
1664 GenericAttributeText::CommitEditedText(BTextView* textView)
1665 {
1666 	ASSERT(fColumn->Editable());
1667 	const char* text = textView->Text();
1668 
1669 	if (fFullValueText == text)
1670 		// no change
1671 		return false;
1672 
1673 	if (!CommitEditedTextFlavor(textView))
1674 		return false;
1675 
1676 	// update text and width in this widget
1677 	fFullValueText = text;
1678 	// cause re-truncation
1679 	fDirty = true;
1680 	fValueDirty = true;
1681 
1682 	return true;
1683 }
1684 
1685 
1686 void
1687 GenericAttributeText::SetUpEditing(BTextView* textView)
1688 {
1689 	textView->SetMaxBytes(kGenericReadBufferSize - 1);
1690 	textView->SetText(fFullValueText.String(), fFullValueText.Length());
1691 }
1692 
1693 
1694 bool
1695 GenericAttributeText::CommitEditedTextFlavor(BTextView* textView)
1696 {
1697 	BNode node(fModel->EntryRef());
1698 
1699 	if (node.InitCheck() != B_OK)
1700 		return false;
1701 
1702 	uint32 type = fColumn->AttrType();
1703 
1704 	if (type != B_STRING_TYPE
1705 		&& type != B_UINT64_TYPE
1706 		&& type != B_UINT32_TYPE
1707 		&& type != B_UINT16_TYPE
1708 		&& type != B_UINT8_TYPE
1709 		&& type != B_INT64_TYPE
1710 		&& type != B_INT32_TYPE
1711 		&& type != B_INT16_TYPE
1712 		&& type != B_INT8_TYPE
1713 		&& type != B_OFF_T_TYPE
1714 		&& type != B_TIME_TYPE
1715 		&& type != B_FLOAT_TYPE
1716 		&& type != B_DOUBLE_TYPE
1717 		&& type != B_CHAR_TYPE
1718 		&& type != B_BOOL_TYPE) {
1719 		BAlert* alert = new BAlert("",
1720 			B_TRANSLATE("Sorry, you cannot edit that attribute."),
1721 			B_TRANSLATE("Cancel"),
1722 			0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1723 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1724 		alert->Go();
1725 		return false;
1726 	}
1727 
1728 	const char* columnName = fColumn->AttrName();
1729 	ssize_t size = 0;
1730 
1731 	switch (type) {
1732 		case B_STRING_TYPE:
1733 			size = fModel->WriteAttr(columnName, type, 0, textView->Text(),
1734 				(size_t)(textView->TextLength() + 1));
1735 			break;
1736 
1737 		case B_BOOL_TYPE:
1738 		{
1739 			bool value = strncasecmp(textView->Text(), "0", 1) != 0
1740 				&& strncasecmp(textView->Text(), "off", 2) != 0
1741 				&& strncasecmp(textView->Text(), "no", 3) != 0
1742 				&& strncasecmp(textView->Text(), "false", 4) != 0
1743 				&& strlen(textView->Text()) != 0;
1744 
1745 			size = fModel->WriteAttr(columnName, type, 0, &value, sizeof(bool));
1746 			break;
1747 		}
1748 
1749 		case B_CHAR_TYPE:
1750 		{
1751 			char ch;
1752 			sscanf(textView->Text(), "%c", &ch);
1753 			//Check if we read the start of a multi-byte glyph:
1754 			if (!isprint(ch)) {
1755 				BAlert* alert = new BAlert("",
1756 					B_TRANSLATE("Sorry, the 'Character' "
1757 					"attribute cannot store a multi-byte glyph."),
1758 					B_TRANSLATE("Cancel"),
1759 					0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1760 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1761 				alert->Go();
1762 				return false;
1763 			}
1764 
1765 			size = fModel->WriteAttr(columnName, type, 0, &ch, sizeof(char));
1766 			break;
1767 		}
1768 
1769 		case B_FLOAT_TYPE:
1770 		{
1771 			float floatVal;
1772 
1773 			if (sscanf(textView->Text(), "%f", &floatVal) == 1) {
1774 				fValueIsDefined = true;
1775 				fValue.floatt = floatVal;
1776 				size = fModel->WriteAttr(columnName, type, 0, &floatVal,
1777 					sizeof(float));
1778 			} else {
1779 				// If the value was already defined, it's on disk.
1780 				// Otherwise not.
1781 				return fValueIsDefined;
1782 			}
1783 			break;
1784 		}
1785 
1786 		case B_DOUBLE_TYPE:
1787 		{
1788 			double doubleVal;
1789 
1790 			if (sscanf(textView->Text(), "%lf", &doubleVal) == 1) {
1791 				fValueIsDefined = true;
1792 				fValue.doublet = doubleVal;
1793 				size = fModel->WriteAttr(columnName, type, 0, &doubleVal,
1794 					sizeof(double));
1795 			} else {
1796 				// If the value was already defined, it's on disk.
1797 				// Otherwise not.
1798 				return fValueIsDefined;
1799 			}
1800 			break;
1801 		}
1802 
1803 		case B_TIME_TYPE:
1804 		case B_OFF_T_TYPE:
1805 		case B_UINT64_TYPE:
1806 		case B_UINT32_TYPE:
1807 		case B_UINT16_TYPE:
1808 		case B_UINT8_TYPE:
1809 		case B_INT64_TYPE:
1810 		case B_INT32_TYPE:
1811 		case B_INT16_TYPE:
1812 		case B_INT8_TYPE:
1813 		{
1814 			GenericValueStruct tmp;
1815 			size_t scalarSize = 0;
1816 
1817 			switch (type) {
1818 				case B_TIME_TYPE:
1819 					tmp.time_tt = parsedate(textView->Text(), time(0));
1820 					scalarSize = sizeof(time_t);
1821 					break;
1822 
1823 				// do some size independent conversion on builtin types
1824 				case B_OFF_T_TYPE:
1825 					tmp.off_tt = StringToScalar(textView->Text());
1826 					scalarSize = sizeof(off_t);
1827 					break;
1828 
1829 				case B_UINT64_TYPE:
1830 				case B_INT64_TYPE:
1831 					tmp.int64t = StringToScalar(textView->Text());
1832 					scalarSize = sizeof(int64);
1833 					break;
1834 
1835 				case B_UINT32_TYPE:
1836 				case B_INT32_TYPE:
1837 					tmp.int32t = (int32)StringToScalar(textView->Text());
1838 					scalarSize = sizeof(int32);
1839 					break;
1840 
1841 				case B_UINT16_TYPE:
1842 				case B_INT16_TYPE:
1843 					tmp.int16t = (int16)StringToScalar(textView->Text());
1844 					scalarSize = sizeof(int16);
1845 					break;
1846 
1847 				case B_UINT8_TYPE:
1848 				case B_INT8_TYPE:
1849 					tmp.int8t = (int8)StringToScalar(textView->Text());
1850 					scalarSize = sizeof(int8);
1851 					break;
1852 
1853 				default:
1854 					TRESPASS();
1855 			}
1856 
1857 			size = fModel->WriteAttr(columnName, type, 0, &tmp, scalarSize);
1858 			break;
1859 		}
1860 	}
1861 
1862 	if (size < 0) {
1863 		BAlert* alert = new BAlert("",
1864 			B_TRANSLATE("There was an error writing the attribute."),
1865 			B_TRANSLATE("Cancel"),
1866 			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1867 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1868 		alert->Go();
1869 
1870 		fValueIsDefined = false;
1871 		return false;
1872 	}
1873 
1874 	fValueIsDefined = true;
1875 	return true;
1876 }
1877 
1878 
1879 // #pragma mark - DurationAttributeText (display as: duration)
1880 
1881 
1882 DurationAttributeText::DurationAttributeText(const Model* model,
1883 	const BColumn* column)
1884 	:
1885 	GenericAttributeText(model, column)
1886 {
1887 }
1888 
1889 
1890 // TODO: support editing!
1891 
1892 
1893 void
1894 DurationAttributeText::FitValue(BString* outString, const BPoseView* view)
1895 {
1896 	if (fValueDirty)
1897 		ReadValue(&fFullValueText);
1898 
1899 	fOldWidth = fColumn->Width();
1900 	fDirty = false;
1901 
1902 	if (!fValueIsDefined) {
1903 		*outString = "-";
1904 		fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1905 			fFullValueText.Length(), view, fOldWidth);
1906 		return;
1907 	}
1908 
1909 	int64 time = 0;
1910 
1911 	switch (fColumn->AttrType()) {
1912 		case B_TIME_TYPE:
1913 			time = fValue.time_tt * 1000000LL;
1914 			break;
1915 
1916 		case B_INT8_TYPE:
1917 			time = fValue.int8t * 1000000LL;
1918 			break;
1919 
1920 		case B_INT16_TYPE:
1921 			time = fValue.int16t * 1000000LL;
1922 			break;
1923 
1924 		case B_INT32_TYPE:
1925 			time = fValue.int32t * 1000000LL;
1926 			break;
1927 
1928 		case B_INT64_TYPE:
1929 			time = fValue.int64t;
1930 			break;
1931 	}
1932 
1933 	// TODO: ignores micro seconds for now
1934 	int32 seconds = time / 1000000LL;
1935 
1936 	bool negative = seconds < 0;
1937 	if (negative)
1938 		seconds = -seconds;
1939 
1940 	int32 hours = seconds / 3600;
1941 	seconds -= hours * 3600;
1942 	int32 minutes = seconds / 60;
1943 	seconds = seconds % 60;
1944 
1945 	char buffer[256];
1946 	if (hours > 0) {
1947 		snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32 ":%02"
1948 			B_PRId32, negative ? "-" : "", hours, minutes, seconds);
1949 	} else {
1950 		snprintf(buffer, sizeof(buffer), "%s%" B_PRId32 ":%02" B_PRId32,
1951 			negative ? "-" : "", minutes, seconds);
1952 	}
1953 
1954 	fFullValueText = buffer;
1955 
1956 	fTruncatedWidth = TruncString(outString, fFullValueText.String(),
1957 		fFullValueText.Length(), view, fOldWidth);
1958 }
1959 
1960 
1961 // #pragma mark - CheckboxAttributeText (display as: checkbox)
1962 
1963 
1964 CheckboxAttributeText::CheckboxAttributeText(const Model* model,
1965 	const BColumn* column)
1966 	:
1967 	GenericAttributeText(model, column),
1968 	fOnChar("✖"),
1969 	fOffChar("-")
1970 {
1971 	// TODO: better have common data in the column object!
1972 	if (const char* separator = strchr(column->DisplayAs(), ':')) {
1973 		BString chars(separator + 1);
1974 		int32 length;
1975 		const char* c = chars.CharAt(0, &length);
1976 		fOnChar.SetTo(c, length);
1977 		if (c[length]) {
1978 			c = chars.CharAt(1, &length);
1979 			fOffChar.SetTo(c, length);
1980 		}
1981 	}
1982 }
1983 
1984 
1985 void
1986 CheckboxAttributeText::SetUpEditing(BTextView* view)
1987 {
1988 	// TODO: support editing for real!
1989 	BString outString;
1990 	GenericAttributeText::FitValue(&outString, NULL);
1991 	GenericAttributeText::SetUpEditing(view);
1992 }
1993 
1994 
1995 void
1996 CheckboxAttributeText::FitValue(BString* outString, const BPoseView* view)
1997 {
1998 	if (fValueDirty)
1999 		ReadValue(&fFullValueText);
2000 
2001 	fOldWidth = fColumn->Width();
2002 	fDirty = false;
2003 
2004 	if (!fValueIsDefined) {
2005 		*outString = fOffChar;
2006 		fTruncatedWidth = TruncString(outString, fFullValueText.String(),
2007 			fFullValueText.Length(), view, fOldWidth);
2008 		return;
2009 	}
2010 
2011 	bool checked = false;
2012 
2013 	switch (fColumn->AttrType()) {
2014 		case B_BOOL_TYPE:
2015 			checked = fValue.boolt;
2016 			break;
2017 
2018 		case B_INT8_TYPE:
2019 		case B_UINT8_TYPE:
2020 			checked = fValue.int8t != 0;
2021 			break;
2022 
2023 		case B_INT16_TYPE:
2024 		case B_UINT16_TYPE:
2025 			checked = fValue.int16t != 0;
2026 			break;
2027 
2028 		case B_INT32_TYPE:
2029 		case B_UINT32_TYPE:
2030 			checked = fValue.int32t != 0;
2031 			break;
2032 	}
2033 
2034 	fFullValueText = checked ? fOnChar : fOffChar;
2035 
2036 	fTruncatedWidth = TruncString(outString, fFullValueText.String(),
2037 		fFullValueText.Length(), view, fOldWidth);
2038 }
2039 
2040 
2041 // #pragma mark - RatingAttributeText (display as: rating)
2042 
2043 
2044 RatingAttributeText::RatingAttributeText(const Model* model,
2045 	const BColumn* column)
2046 	:
2047 	GenericAttributeText(model, column),
2048 	fCount(5),
2049 	fMax(10)
2050 {
2051 	// TODO: support different star counts/max via specifier
2052 }
2053 
2054 
2055 void
2056 RatingAttributeText::SetUpEditing(BTextView* view)
2057 {
2058 	// TODO: support editing for real!
2059 	BString outString;
2060 	GenericAttributeText::FitValue(&outString, NULL);
2061 	GenericAttributeText::SetUpEditing(view);
2062 }
2063 
2064 
2065 void
2066 RatingAttributeText::FitValue(BString* ratingString, const BPoseView* view)
2067 {
2068 	if (fValueDirty)
2069 		ReadValue(&fFullValueText);
2070 
2071 	fOldWidth = fColumn->Width();
2072 	fDirty = false;
2073 
2074 	int64 rating;
2075 	if (fValueIsDefined) {
2076 		switch (fColumn->AttrType()) {
2077 			case B_INT8_TYPE:
2078 				rating = fValue.int8t;
2079 				break;
2080 
2081 			case B_INT16_TYPE:
2082 				rating = fValue.int16t;
2083 				break;
2084 
2085 			case B_INT32_TYPE:
2086 				rating = fValue.int32t;
2087 				break;
2088 
2089 			default:
2090 				rating = 0;
2091 				break;
2092 		}
2093 	} else
2094 		rating = 0;
2095 
2096 	if (rating > fMax)
2097 		rating = fMax;
2098 
2099 	if (rating < 0)
2100 		rating = 0;
2101 
2102 	int32 steps = fMax / fCount;
2103 	fFullValueText = "";
2104 
2105 	for (int32 i = 0; i < fCount; i++) {
2106 		int64 n = i * steps;
2107 		if (rating > n)
2108 			fFullValueText += "★";
2109 		else
2110 			fFullValueText += "☆";
2111 	}
2112 
2113 	fTruncatedWidth = TruncString(ratingString, fFullValueText.String(),
2114 		fFullValueText.Length(), view, fOldWidth);
2115 }
2116 
2117 
2118 // #pragma mark - OpenWithRelationAttributeText
2119 
2120 
2121 OpenWithRelationAttributeText::OpenWithRelationAttributeText(const Model* model,
2122 	const BColumn* column, const BPoseView* view)
2123 	:
2124 	ScalarAttributeText(model, column),
2125 	fPoseView(view)
2126 {
2127 }
2128 
2129 
2130 int64
2131 OpenWithRelationAttributeText::ReadValue()
2132 {
2133 	fValueDirty = false;
2134 
2135 	const OpenWithPoseView* view
2136 		= dynamic_cast<const OpenWithPoseView*>(fPoseView);
2137 	if (view != NULL) {
2138 		fValue = view->OpenWithRelation(fModel);
2139 		fValueIsDefined = true;
2140 	}
2141 
2142 	return fValue;
2143 }
2144 
2145 
2146 float
2147 OpenWithRelationAttributeText::PreferredWidth(const BPoseView* pose) const
2148 {
2149 	BString widthString;
2150 	TruncString(&widthString, fRelationText.String(), fRelationText.Length(),
2151 		pose, 500, B_TRUNCATE_END);
2152 	return pose->StringWidth(widthString.String());
2153 }
2154 
2155 
2156 void
2157 OpenWithRelationAttributeText::FitValue(BString* outString,
2158 	const BPoseView* view)
2159 {
2160 	if (fValueDirty)
2161 		ReadValue();
2162 
2163 	ASSERT(view == fPoseView);
2164 	const OpenWithPoseView* launchWithView
2165 		= dynamic_cast<const OpenWithPoseView*>(view);
2166 	if (launchWithView != NULL)
2167 		launchWithView->OpenWithRelationDescription(fModel, &fRelationText);
2168 
2169 	fOldWidth = fColumn->Width();
2170 	fTruncatedWidth = TruncString(outString, fRelationText.String(),
2171 		fRelationText.Length(), view, fOldWidth, B_TRUNCATE_END);
2172 	fDirty = false;
2173 }
2174 
2175 
2176 // #pragma mark - VersionAttributeText
2177 
2178 
2179 VersionAttributeText::VersionAttributeText(const Model* model,
2180 	const BColumn* column, bool app)
2181 	:
2182 	StringAttributeText(model, column),
2183 	fAppVersion(app)
2184 {
2185 }
2186 
2187 
2188 void
2189 VersionAttributeText::ReadValue(BString* outString)
2190 {
2191 	fValueDirty = false;
2192 
2193 	BModelOpener opener(fModel);
2194 	BFile* file = dynamic_cast<BFile*>(fModel->Node());
2195 	if (file != NULL) {
2196 		BAppFileInfo info(file);
2197 		version_info version;
2198 		if (info.InitCheck() == B_OK
2199 			&& info.GetVersionInfo(&version, fAppVersion
2200 				? B_APP_VERSION_KIND : B_SYSTEM_VERSION_KIND) == B_OK) {
2201 			*outString = version.short_info;
2202 			return;
2203 		}
2204 	}
2205 
2206 	*outString = "-";
2207 }
2208