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