1/*******************************************************************************
2/
3/	File:			ColumnTypes.h
4/
5/   Description:    Experimental classes that implement particular column/field
6/					data types for use in BColumnListView.
7/
8/	Copyright 2000+, Be Incorporated, All Rights Reserved
9/
10*******************************************************************************/
11
12#include "ColumnTypes.h"
13
14#include <View.h>
15
16#include <parsedate.h>
17#include <stdio.h>
18
19
20#define kTEXT_MARGIN	8
21
22
23const int64 kKB_SIZE = 1024;
24const int64 kMB_SIZE = 1048576;
25const int64 kGB_SIZE = 1073741824;
26const int64 kTB_SIZE = kGB_SIZE * kKB_SIZE;
27
28const char* kSIZE_FORMATS[] = {
29	"%.2f %s",
30	"%.1f %s",
31	"%.f %s",
32	"%.f%s",
33	0
34};
35
36
37BTitledColumn::BTitledColumn(const char* title, float width, float minWidth,
38	float maxWidth, alignment align)
39	:
40	BColumn(width, minWidth, maxWidth, align),
41	fTitle(title)
42{
43	font_height fh;
44
45	be_plain_font->GetHeight(&fh);
46	fFontHeight = fh.descent + fh.leading;
47}
48
49
50void
51BTitledColumn::DrawTitle(BRect rect, BView* parent)
52{
53	float width = rect.Width() - (2 * kTEXT_MARGIN);
54	BString out_string(fTitle);
55
56	parent->TruncateString(&out_string, B_TRUNCATE_END, width + 2);
57	DrawString(out_string.String(), parent, rect);
58}
59
60
61void
62BTitledColumn::GetColumnName(BString* into) const
63{
64	*into = fTitle;
65}
66
67
68void
69BTitledColumn::DrawString(const char* string, BView* parent, BRect rect)
70{
71	float width = rect.Width() - (2 * kTEXT_MARGIN);
72	float y;
73	BFont font;
74	font_height	finfo;
75
76	parent->GetFont(&font);
77	font.GetHeight(&finfo);
78	y = rect.top + finfo.ascent
79		+ (rect.Height() - ceilf(finfo.ascent + finfo.descent)) / 2.0f;
80
81	switch (Alignment()) {
82		default:
83		case B_ALIGN_LEFT:
84			parent->MovePenTo(rect.left + kTEXT_MARGIN, y);
85			break;
86
87		case B_ALIGN_CENTER:
88			parent->MovePenTo(rect.left + kTEXT_MARGIN
89				+ ((width - font.StringWidth(string)) / 2), y);
90			break;
91
92		case B_ALIGN_RIGHT:
93			parent->MovePenTo(rect.right - kTEXT_MARGIN
94				- font.StringWidth(string), y);
95			break;
96	}
97
98	parent->DrawString(string);
99}
100
101
102void
103BTitledColumn::SetTitle(const char* title)
104{
105	fTitle.SetTo(title);
106}
107
108
109void
110BTitledColumn::Title(BString* forTitle) const
111{
112	if (forTitle)
113		forTitle->SetTo(fTitle.String());
114}
115
116
117float
118BTitledColumn::FontHeight() const
119{
120	return fFontHeight;
121}
122
123
124float
125BTitledColumn::GetPreferredWidth(BField *_field, BView* parent) const
126{
127	return parent->StringWidth(fTitle.String()) + 2 * kTEXT_MARGIN;
128}
129
130
131// #pragma mark - BStringField
132
133
134BStringField::BStringField(const char* string)
135	:
136	fWidth(0),
137	fString(string),
138	fClippedString(string)
139{
140}
141
142
143void
144BStringField::SetString(const char* val)
145{
146	fString = val;
147	fClippedString = "";
148	fWidth = 0;
149}
150
151
152const char*
153BStringField::String() const
154{
155	return fString.String();
156}
157
158
159void
160BStringField::SetWidth(float width)
161{
162	fWidth = width;
163}
164
165
166float
167BStringField::Width()
168{
169	return fWidth;
170}
171
172
173void
174BStringField::SetClippedString(const char* val)
175{
176	fClippedString = val;
177}
178
179
180bool
181BStringField::HasClippedString() const
182{
183	return !fClippedString.IsEmpty();
184}
185
186
187const char*
188BStringField::ClippedString()
189{
190	return fClippedString.String();
191}
192
193
194// #pragma mark - BStringColumn
195
196
197BStringColumn::BStringColumn(const char* title, float width, float minWidth,
198	float maxWidth, uint32 truncate, alignment align)
199	:
200	BTitledColumn(title, width, minWidth, maxWidth, align),
201	fTruncate(truncate)
202{
203}
204
205
206void
207BStringColumn::DrawField(BField* _field, BRect rect, BView* parent)
208{
209	float width = rect.Width() - (2 * kTEXT_MARGIN);
210	BStringField* field = static_cast<BStringField*>(_field);
211	float fieldWidth = field->Width();
212	bool updateNeeded = width != fieldWidth;
213
214	if (updateNeeded) {
215		BString out_string(field->String());
216		float preferredWidth = parent->StringWidth(out_string.String());
217		if (width < preferredWidth) {
218			parent->TruncateString(&out_string, fTruncate, width + 2);
219			field->SetClippedString(out_string.String());
220		} else
221			field->SetClippedString("");
222		field->SetWidth(width);
223	}
224
225	DrawString(field->HasClippedString()
226		? field->ClippedString()
227		: field->String(), parent, rect);
228}
229
230
231float
232BStringColumn::GetPreferredWidth(BField *_field, BView* parent) const
233{
234	BStringField* field = static_cast<BStringField*>(_field);
235	return parent->StringWidth(field->String()) + 2 * kTEXT_MARGIN;
236}
237
238
239int
240BStringColumn::CompareFields(BField* field1, BField* field2)
241{
242	return ICompare(((BStringField*)field1)->String(),
243		(((BStringField*)field2)->String()));
244}
245
246
247bool
248BStringColumn::AcceptsField(const BField *field) const
249{
250	return static_cast<bool>(dynamic_cast<const BStringField*>(field));
251}
252
253
254// #pragma mark - BDateField
255
256
257BDateField::BDateField(time_t* time)
258	:
259	fTime(*localtime(time)),
260	fUnixTime(*time),
261	fSeconds(0),
262	fClippedString(""),
263	fWidth(0)
264{
265	fSeconds = mktime(&fTime);
266}
267
268
269void
270BDateField::SetWidth(float width)
271{
272	fWidth = width;
273}
274
275
276float
277BDateField::Width()
278{
279	return fWidth;
280}
281
282
283void
284BDateField::SetClippedString(const char* string)
285{
286	fClippedString = string;
287}
288
289
290const char*
291BDateField::ClippedString()
292{
293	return fClippedString.String();
294}
295
296
297time_t
298BDateField::Seconds()
299{
300	return fSeconds;
301}
302
303
304time_t
305BDateField::UnixTime()
306{
307	return fUnixTime;
308}
309
310
311// #pragma mark - BDateColumn
312
313
314BDateColumn::BDateColumn(const char* title, float width, float minWidth,
315	float maxWidth, alignment align)
316	:
317	BTitledColumn(title, width, minWidth, maxWidth, align),
318	fTitle(title)
319{
320}
321
322
323const char *kTIME_FORMATS[] = {
324	"%A, %B %d %Y, %I:%M:%S %p",	// Monday, July 09 1997, 05:08:15 PM
325	"%a, %b %d %Y, %I:%M:%S %p",	// Mon, Jul 09 1997, 05:08:15 PM
326	"%a, %b %d %Y, %I:%M %p",		// Mon, Jul 09 1997, 05:08 PM
327	"%b %d %Y, %I:%M %p",			// Jul 09 1997, 05:08 PM
328	"%m/%d/%y, %I:%M %p",			// 07/09/97, 05:08 PM
329	"%m/%d/%y",						// 07/09/97
330	NULL
331};
332
333
334void
335BDateColumn::DrawField(BField* _field, BRect rect, BView* parent)
336{
337	float width = rect.Width() - (2 * kTEXT_MARGIN);
338	BDateField* field = (BDateField*)_field;
339
340	if (field->Width() != rect.Width()) {
341		char dateString[256];
342		time_t currentTime = field->UnixTime();
343		tm time_data;
344		BFont font;
345
346		parent->GetFont(&font);
347		localtime_r(&currentTime, &time_data);
348
349		for (int32 index = 0; ; index++) {
350			if (!kTIME_FORMATS[index])
351				break;
352
353			strftime(dateString, 256, kTIME_FORMATS[index], &time_data);
354			if (font.StringWidth(dateString) <= width)
355				break;
356		}
357
358		if (font.StringWidth(dateString) > width) {
359			BString out_string(dateString);
360
361			parent->TruncateString(&out_string, B_TRUNCATE_MIDDLE, width + 2);
362			strcpy(dateString, out_string.String());
363		}
364		field->SetClippedString(dateString);
365		field->SetWidth(width);
366	}
367
368	DrawString(field->ClippedString(), parent, rect);
369}
370
371
372int
373BDateColumn::CompareFields(BField* field1, BField* field2)
374{
375	return((BDateField*)field1)->Seconds() - ((BDateField*)field2)->Seconds();
376}
377
378
379// #pragma mark - BSizeField
380
381
382BSizeField::BSizeField(off_t size)
383	:
384	fSize(size)
385{
386}
387
388
389void
390BSizeField::SetSize(off_t size)
391{
392	fSize = size;
393}
394
395
396off_t
397BSizeField::Size()
398{
399	return fSize;
400}
401
402
403// #pragma mark - BSizeColumn
404
405
406BSizeColumn::BSizeColumn(const char* title, float width, float minWidth,
407	float maxWidth, alignment align)
408	:
409	BTitledColumn(title, width, minWidth, maxWidth, align)
410{
411}
412
413
414void
415BSizeColumn::DrawField(BField* _field, BRect rect, BView* parent)
416{
417	char str[256];
418	float width = rect.Width() - (2 * kTEXT_MARGIN);
419	BFont font;
420	BString string;
421	off_t size = ((BSizeField*)_field)->Size();
422
423	parent->GetFont(&font);
424	if (size < kKB_SIZE) {
425		sprintf(str, "%" B_PRId64 " bytes", size);
426		if (font.StringWidth(str) > width)
427			sprintf(str, "%" B_PRId64 " B", size);
428	} else {
429		const char*	suffix;
430		float float_value;
431		if (size >= kTB_SIZE) {
432			suffix = "TB";
433			float_value = (float)size / kTB_SIZE;
434		} else if (size >= kGB_SIZE) {
435			suffix = "GB";
436			float_value = (float)size / kGB_SIZE;
437		} else if (size >= kMB_SIZE) {
438			suffix = "MB";
439			float_value = (float)size / kMB_SIZE;
440		} else {
441			suffix = "KB";
442			float_value = (float)size / kKB_SIZE;
443		}
444
445		for (int32 index = 0; ; index++) {
446			if (!kSIZE_FORMATS[index])
447				break;
448
449			sprintf(str, kSIZE_FORMATS[index], float_value, suffix);
450			// strip off an insignificant zero so we don't get readings
451			// such as 1.00
452			char *period = 0;
453			char *tmp (NULL);
454			for (tmp = str; *tmp; tmp++) {
455				if (*tmp == '.')
456					period = tmp;
457			}
458			if (period && period[1] && period[2] == '0') {
459				// move the rest of the string over the insignificant zero
460				for (tmp = &period[2]; *tmp; tmp++)
461					*tmp = tmp[1];
462			}
463			if (font.StringWidth(str) <= width)
464				break;
465		}
466	}
467
468	string = str;
469	parent->TruncateString(&string, B_TRUNCATE_MIDDLE, width + 2);
470	DrawString(string.String(), parent, rect);
471}
472
473
474int
475BSizeColumn::CompareFields(BField* field1, BField* field2)
476{
477	off_t diff = ((BSizeField*)field1)->Size() - ((BSizeField*)field2)->Size();
478	if (diff > 0)
479		return 1;
480	if (diff < 0)
481		return -1;
482	return 0;
483}
484
485
486// #pragma mark - BIntegerField
487
488
489BIntegerField::BIntegerField(int32 number)
490	:
491	fInteger(number)
492{
493}
494
495
496void
497BIntegerField::SetValue(int32 value)
498{
499	fInteger = value;
500}
501
502
503int32
504BIntegerField::Value()
505{
506	return fInteger;
507}
508
509
510// #pragma mark - BIntegerColumn
511
512
513BIntegerColumn::BIntegerColumn(const char* title, float width, float minWidth,
514	float maxWidth, alignment align)
515	:
516	BTitledColumn(title, width, minWidth, maxWidth, align)
517{
518}
519
520
521void
522BIntegerColumn::DrawField(BField *field, BRect rect, BView* parent)
523{
524	char formatted[256];
525	float width = rect.Width() - (2 * kTEXT_MARGIN);
526	BString string;
527
528	sprintf(formatted, "%d", (int)((BIntegerField*)field)->Value());
529
530	string = formatted;
531	parent->TruncateString(&string, B_TRUNCATE_MIDDLE, width + 2);
532	DrawString(string.String(), parent, rect);
533}
534
535
536int
537BIntegerColumn::CompareFields(BField *field1, BField *field2)
538{
539	return (((BIntegerField*)field1)->Value() - ((BIntegerField*)field2)->Value());
540}
541
542
543// #pragma mark - GraphColumn
544
545
546GraphColumn::GraphColumn(const char* name, float width, float minWidth,
547	float maxWidth, alignment align)
548	:
549	BIntegerColumn(name, width, minWidth, maxWidth, align)
550{
551}
552
553
554void
555GraphColumn::DrawField(BField* field, BRect rect, BView* parent)
556{
557	int number = ((BIntegerField*)field)->Value();
558
559	if (number > 100)
560		number = 100;
561	else if (number < 0)
562		number = 0;
563
564	BRect graphRect(rect);
565	graphRect.InsetBy(5, 3);
566	parent->StrokeRect(graphRect);
567	if (number > 0) {
568		graphRect.InsetBy(1, 1);
569		float value = graphRect.Width() * (float)number / 100;
570		graphRect.right = graphRect.left + value;
571		parent->SetHighColor(0, 0, 190);
572		parent->FillRect(graphRect);
573	}
574
575	parent->SetDrawingMode(B_OP_INVERT);
576	parent->SetHighColor(128, 128, 128);
577	char numberString[256];
578	sprintf(numberString, "%d%%", number);
579
580	float width = be_plain_font->StringWidth(numberString);
581	parent->MovePenTo(rect.left + rect.Width() / 2 - width / 2, rect.bottom - FontHeight());
582	parent->DrawString(numberString);
583}
584
585
586// #pragma mark - BBitmapField
587
588
589BBitmapField::BBitmapField(BBitmap* bitmap)
590	:
591	fBitmap(bitmap)
592{
593}
594
595
596const BBitmap*
597BBitmapField::Bitmap()
598{
599	return fBitmap;
600}
601
602
603void
604BBitmapField::SetBitmap(BBitmap* bitmap)
605{
606	fBitmap = bitmap;
607}
608
609
610// #pragma mark - BBitmapColumn
611
612
613BBitmapColumn::BBitmapColumn(const char* title, float width, float minWidth,
614	float maxWidth, alignment align)
615	:
616	BTitledColumn(title, width, minWidth, maxWidth, align)
617{
618}
619
620
621void
622BBitmapColumn::DrawField(BField* field, BRect rect, BView* parent)
623{
624	BBitmapField* bitmapField = static_cast<BBitmapField*>(field);
625	const BBitmap* bitmap = bitmapField->Bitmap();
626
627	if (bitmap != NULL) {
628		float x = 0.0;
629		BRect r = bitmap->Bounds();
630		float y = rect.top + ((rect.Height() - r.Height()) / 2);
631
632		switch (Alignment()) {
633			default:
634			case B_ALIGN_LEFT:
635				x = rect.left + kTEXT_MARGIN;
636				break;
637
638			case B_ALIGN_CENTER:
639				x = rect.left + ((rect.Width() - r.Width()) / 2);
640				break;
641
642			case B_ALIGN_RIGHT:
643				x = rect.right - kTEXT_MARGIN - r.Width();
644				break;
645		}
646		// setup drawing mode according to bitmap color space,
647		// restore previous mode after drawing
648		drawing_mode oldMode = parent->DrawingMode();
649		if (bitmap->ColorSpace() == B_RGBA32
650			|| bitmap->ColorSpace() == B_RGBA32_BIG) {
651			parent->SetDrawingMode(B_OP_ALPHA);
652			parent->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
653		} else {
654			parent->SetDrawingMode(B_OP_OVER);
655		}
656
657		parent->DrawBitmap(bitmap, BPoint(x, y));
658
659		parent->SetDrawingMode(oldMode);
660	}
661}
662
663
664int
665BBitmapColumn::CompareFields(BField* /*field1*/, BField* /*field2*/)
666{
667	// Comparing bitmaps doesn't really make sense...
668	return 0;
669}
670
671
672bool
673BBitmapColumn::AcceptsField(const BField *field) const
674{
675	return static_cast<bool>(dynamic_cast<const BBitmapField*>(field));
676}
677