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