xref: /haiku/src/apps/cortex/InfoView/InfoView.cpp (revision a0795c6fe30e25338049a952326c61deb7a343b6)
1 // InfoView.cpp
2 
3 #include "InfoView.h"
4 #include "cortex_ui.h"
5 
6 #include "array_delete.h"
7 
8 // Interface Kit
9 #include <Bitmap.h>
10 #include <Region.h>
11 #include <ScrollBar.h>
12 #include <StringView.h>
13 #include <TextView.h>
14 #include <Window.h>
15 // Storage Kit
16 #include <Mime.h>
17 // Support Kit
18 #include <List.h>
19 
20 __USE_CORTEX_NAMESPACE
21 
22 #include <Debug.h>
23 #define D_ALLOC(X) //PRINT (x)			// ctor/dtor
24 #define D_HOOK(X) //PRINT (x)			// BView impl.
25 #define D_ACCESS(X) //PRINT (x)			// Accessors
26 #define D_METHOD(x) //PRINT (x)
27 
28 // -------------------------------------------------------- //
29 // *** internal class: _InfoTextField
30 //
31 // * PURPOSE:
32 //   store the label & text for each field, and provide methods
33 //   for linewrapping and drawing
34 //
35 // -------------------------------------------------------- //
36 
37 class _InfoTextField
38 {
39 
40 public:					// *** ctor/dtor
41 
42 						_InfoTextField(
43 							BString label,
44 							BString text,
45 							InfoView *parent);
46 
47 						~_InfoTextField();
48 
49 public:					// *** operations
50 
51 	void				drawField(
52 							BPoint position);
53 
54 	void				updateLineWrapping(
55 							bool *wrappingChanged = 0,
56 							bool *heightChanged = 0);
57 
58 public:					// *** accessors
59 
60 	float				getHeight() const;
61 
62 	float				getWidth() const;
63 
64 	bool				isWrapped() const;
65 
66 private:				// *** static internal methods
67 
68 	static bool			canEndLine(
69 							const char c);
70 
71 	static bool			mustEndLine(
72 							const char c);
73 
74 private:				// *** data members
75 
76 	BString				m_label;
77 
78 	BString				m_text;
79 
80 	BList			   *m_textLines;
81 
82 	InfoView		   *m_parent;
83 };
84 
85 // -------------------------------------------------------- //
86 // *** static member init
87 // -------------------------------------------------------- //
88 
89 const BRect InfoView::M_DEFAULT_FRAME	= BRect(0.0, 0.0, 250.0, 100.0);
90 const float InfoView::M_H_MARGIN		= 5.0;
91 const float InfoView::M_V_MARGIN		= 5.0;
92 
93 // -------------------------------------------------------- //
94 // *** ctor/dtor (public)
95 // -------------------------------------------------------- //
96 
97 InfoView::InfoView(
98 	BString title,
99 	BString subTitle,
100 	BBitmap *icon)
101 	: BView(M_DEFAULT_FRAME, "InfoView", B_FOLLOW_ALL_SIDES,
102 			B_WILL_DRAW | B_FRAME_EVENTS),
103 	  m_title(title),
104 	  m_subTitle(subTitle),
105 	  m_icon(0),
106 	  m_fields(0) {
107 	D_ALLOC(("InfoView::InfoView()\n"));
108 
109 	if (icon) {
110 		m_icon = new BBitmap(icon);
111 	}
112 	m_fields = new BList();
113 	SetViewColor(B_TRANSPARENT_COLOR);
114 }
115 
116 InfoView::~InfoView() {
117 	D_ALLOC(("InfoView::~InfoView()\n"));
118 
119 	// delete all the fields
120 	if (m_fields) {
121 		while (m_fields->CountItems() > 0) {
122 			_InfoTextField *field = static_cast<_InfoTextField *>
123 									(m_fields->RemoveItem((int32)0));
124 			if (field) {
125 				delete field;
126 			}
127 		}
128 		delete m_fields;
129 		m_fields = 0;
130 	}
131 
132 	// delete the icon bitmap
133 	if (m_icon) {
134 		delete m_icon;
135 		m_icon = 0;
136 	}
137 }
138 
139 // -------------------------------------------------------- //
140 // *** BView implementation
141 // -------------------------------------------------------- //
142 
143 void InfoView::AttachedToWindow() {
144 	D_HOOK(("InfoView::AttachedToWindow()\n"));
145 
146 	// adjust the windows title
147 	BString title = m_title;
148 	title << " info";
149 	Window()->SetTitle(title.String());
150 
151 	// calculate the area occupied by title, subtitle and icon
152 	font_height fh;
153 	be_bold_font->GetHeight(&fh);
154 	float titleHeight = fh.leading + fh.descent;
155 	titleHeight += M_V_MARGIN * 2.0 + B_LARGE_ICON / 2.0;
156 	be_plain_font->GetHeight(&fh);
157 	titleHeight += fh.leading + fh.ascent + fh.descent;
158 	BFont font(be_bold_font);
159 	float titleWidth = font.StringWidth(title.String());
160 	titleWidth += M_H_MARGIN + B_LARGE_ICON + B_LARGE_ICON / 2.0;
161 
162 	float width, height;
163 	GetPreferredSize(&width, &height);
164 	Window()->ResizeTo(width + B_V_SCROLL_BAR_WIDTH, height);
165 	ResizeBy(- B_V_SCROLL_BAR_WIDTH, 0.0);
166 
167 	// add scroll bar
168 	BRect scrollRect = Window()->Bounds();
169 	scrollRect.left = scrollRect.right - B_V_SCROLL_BAR_WIDTH + 1.0;
170 	scrollRect.top -= 1.0;
171 	scrollRect.right += 1.0;
172 	scrollRect.bottom -= B_H_SCROLL_BAR_HEIGHT - 1.0;
173 	Window()->AddChild(new BScrollBar(scrollRect, "ScrollBar", this,
174 									  0.0, 0.0, B_VERTICAL));
175 	ScrollBar(B_VERTICAL)->SetRange(0.0, 0.0);
176 	be_plain_font->GetHeight(&fh);
177 	float step = fh.ascent + fh.descent + fh.leading + M_V_MARGIN;
178 	ScrollBar(B_VERTICAL)->SetSteps(step, step * 5);
179 
180 	// set window size limits
181 	float minWidth, maxWidth, minHeight, maxHeight;
182 	Window()->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
183 	Window()->SetSizeLimits(titleWidth + B_V_SCROLL_BAR_WIDTH, maxWidth,
184 							titleHeight + B_H_SCROLL_BAR_HEIGHT, maxHeight);
185 
186 	// cache the bounds rect for proper redraw later on...
187 	m_oldFrame = Bounds();
188 }
189 
190 void InfoView::Draw(
191 	BRect updateRect) {
192 	D_HOOK(("InfoView::Draw()\n"));
193 
194 	// Draw side bar
195 	SetDrawingMode(B_OP_COPY);
196 	BRect r = Bounds();
197 	r.right = B_LARGE_ICON - 1.0;
198 	SetLowColor(M_LIGHT_BLUE_COLOR);
199 	FillRect(r, B_SOLID_LOW);
200 	SetHighColor(M_DARK_BLUE_COLOR);
201 	r.right += 1.0;
202 	StrokeLine(r.RightTop(), r.RightBottom(), B_SOLID_HIGH);
203 
204 	// Draw background
205 	BRegion region;
206 	region.Include(updateRect);
207 	region.Exclude(r);
208 	SetLowColor(M_GRAY_COLOR);
209 	FillRegion(&region, B_SOLID_LOW);
210 
211 	// Draw title
212 	SetDrawingMode(B_OP_OVER);
213 	font_height fh;
214 	be_bold_font->GetHeight(&fh);
215 	SetFont(be_bold_font);
216 	BPoint p(M_H_MARGIN + B_LARGE_ICON + B_LARGE_ICON / 2.0,
217 			 M_V_MARGIN * 2.0 + fh.ascent);
218 	SetHighColor(M_BLACK_COLOR);
219 	DrawString(m_title.String(), p);
220 
221 	// Draw sub-title
222 	p.y += fh.descent;
223 	be_plain_font->GetHeight(&fh);
224 	SetFont(be_plain_font);
225 	p.y += fh.ascent + fh.leading;
226 	SetHighColor(M_DARK_GRAY_COLOR);
227 	DrawString(m_subTitle.String(), p);
228 
229 	// Draw icon
230 	p.y = 2 * M_V_MARGIN;
231 	if (m_icon) {
232 		p.x = B_LARGE_ICON / 2.0;
233 		DrawBitmapAsync(m_icon, p);
234 	}
235 
236 	// Draw fields
237 	be_plain_font->GetHeight(&fh);
238 	p.x = B_LARGE_ICON;
239 	p.y += B_LARGE_ICON + 2 * M_V_MARGIN + fh.ascent + 2 * fh.leading;
240 	for (int32 i = 0; i < m_fields->CountItems(); i++) {
241 		_InfoTextField *field = static_cast<_InfoTextField *>(m_fields->ItemAt(i));
242 		field->drawField(p);
243 		p.y += field->getHeight() + M_V_MARGIN;
244 	}
245 }
246 
247 void InfoView::FrameResized(
248 	float width,
249 	float height) {
250 	D_HOOK(("InfoView::FrameResized()\n"));
251 
252 	BRect newFrame = BRect(0.0, 0.0, width, height);
253 
254 	// update the each lines' line-wrapping and redraw as necessary
255 	font_height fh;
256 	BPoint p;
257 	be_plain_font->GetHeight(&fh);
258 	p.x = B_LARGE_ICON;
259 	p.y += B_LARGE_ICON + M_V_MARGIN * 2.0 + fh.ascent + fh.leading * 2.0;
260 	bool heightChanged = false;
261 	for (int32 i = 0; i < m_fields->CountItems(); i++) {
262 		bool wrappingChanged = false;
263 		_InfoTextField *field = static_cast<_InfoTextField *>(m_fields->ItemAt(i));
264 		field->updateLineWrapping(&wrappingChanged,
265 								  heightChanged ? 0 : &heightChanged);
266 		float fieldHeight = field->getHeight() + M_V_MARGIN;
267 		if (heightChanged) {
268 			Invalidate(BRect(p.x, p.y, width, p.y + fieldHeight));
269 		}
270 		else if (wrappingChanged) {
271 			Invalidate(BRect(p.x + m_sideBarWidth, p.y, width, p.y + fieldHeight));
272 		}
273 		p.y += fieldHeight;
274 	}
275 
276 	// clean up the rest of the view
277 	BRect updateRect;
278 	updateRect.left = B_LARGE_ICON;
279 	updateRect.top = p.y < (m_oldFrame.bottom - M_V_MARGIN - 15.0) ?
280 					 p.y - 15.0 : m_oldFrame.bottom - M_V_MARGIN - 15.0;
281 	updateRect.right = width - M_H_MARGIN;
282 	updateRect.bottom = m_oldFrame.bottom < newFrame.bottom ?
283 						newFrame.bottom : m_oldFrame.bottom;
284 	Invalidate(updateRect);
285 
286 	if (p.y > height) {
287 		ScrollBar(B_VERTICAL)->SetRange(0.0, ceil(p.y - height));
288 	}
289 	else {
290 		ScrollBar(B_VERTICAL)->SetRange(0.0, 0.0);
291 	}
292 
293 	// cache the new frame rect for the next time
294 	m_oldFrame = newFrame;
295 }
296 
297 void
298 InfoView::GetPreferredSize(
299 	float *width,
300 	float *height) {
301 	D_HOOK(("InfoView::GetPreferredSize()\n"));
302 
303 	*width = 0;
304 	*height = 0;
305 
306 	// calculate the height needed to display everything, avoiding line wrapping
307 	font_height fh;
308 	be_plain_font->GetHeight(&fh);
309 	for (int32 i = 0; i < m_fields->CountItems(); i++) {
310 		_InfoTextField *field = static_cast<_InfoTextField *>(m_fields->ItemAt(i));
311 		*height += fh.ascent + fh.descent + fh.leading + M_V_MARGIN;
312 		float tfw = field->getWidth();
313 		if (tfw > *width) {
314 			*width = tfw;
315 		}
316 	}
317 
318 	*width += B_LARGE_ICON + 2 * M_H_MARGIN;
319 	*height += B_LARGE_ICON + 2 * M_V_MARGIN + fh.ascent + 2 * fh.leading;
320 	*height += B_H_SCROLL_BAR_HEIGHT;
321 }
322 
323 // -------------------------------------------------------- //
324 // *** operations (protected)
325 // -------------------------------------------------------- //
326 
327 void InfoView::addField(
328 	BString label,
329 	BString text) {
330 	D_METHOD(("InfoView::addField()\n"));
331 
332 	m_fields->AddItem(reinterpret_cast<void *>
333 					  (new _InfoTextField(label, text, this)));
334 }
335 
336 // -------------------------------------------------------- //
337 // *** internal class: _InfoTextField
338 //
339 // *** ctor/dtor
340 // -------------------------------------------------------- //
341 
342 _InfoTextField::_InfoTextField(
343 	BString label,
344 	BString text,
345 	InfoView *parent)
346 	: m_label(label),
347 	  m_text(text),
348 	  m_textLines(0),
349 	  m_parent(parent) {
350 	D_ALLOC(("_InfoTextField::_InfoTextField()\n"));
351 
352 	if (m_label != "") {
353 		m_label << ":  ";
354 	}
355 	m_textLines = new BList();
356 }
357 
358 _InfoTextField::~_InfoTextField() {
359 	D_ALLOC(("_InfoTextField::~_InfoTextField()\n"));
360 
361 	// delete every line
362 	if (m_textLines) {
363 		while (m_textLines->CountItems() > 0) {
364 			BString *line = static_cast<BString *>(m_textLines->RemoveItem((int32)0));
365 			if (line) {
366 				delete line;
367 			}
368 		}
369 		delete m_textLines;
370 		m_textLines = 0;
371 	}
372 }
373 
374 // -------------------------------------------------------- //
375 // *** internal class: _InfoTextField
376 //
377 // *** operations (public)
378 // -------------------------------------------------------- //
379 
380 void _InfoTextField::drawField(
381 	BPoint position) {
382 	D_METHOD(("_InfoTextField::drawField()\n"));
383 
384 	float sideBarWidth = m_parent->getSideBarWidth();
385 
386 	// Draw label
387 	BPoint p = position;
388 	p.x += sideBarWidth - be_plain_font->StringWidth(m_label.String());
389 	m_parent->SetHighColor(M_DARK_GRAY_COLOR);
390 	m_parent->SetDrawingMode(B_OP_OVER);
391 	m_parent->SetFont(be_plain_font);
392 	m_parent->DrawString(m_label.String(), p);
393 
394 	// Draw text
395 	font_height fh;
396 	be_plain_font->GetHeight(&fh);
397 	p.x = position.x + sideBarWidth;// + InfoView::M_H_MARGIN;
398 	m_parent->SetHighColor(M_BLACK_COLOR);
399 	for (int32 i = 0; i < m_textLines->CountItems(); i++) {
400 		BString *line = static_cast<BString *>(m_textLines->ItemAt(i));
401 		m_parent->DrawString(line->String(), p);
402 		p.y += fh.ascent + fh.descent + fh.leading;
403 	}
404 }
405 
406 void _InfoTextField::updateLineWrapping(
407 	bool *wrappingChanged,
408 	bool *heightChanged)
409 {
410 	D_METHOD(("_InfoTextField::updateLineWrapping()\n"));
411 
412 	// clear the current list of lines but remember their number and
413 	// the number of characters per line (to know if something changed)
414 	int32 numLines = m_textLines->CountItems();
415 	int32* numChars = new int32[numLines];
416 	array_delete<int32> _d(numChars);
417 
418 	for (int32 i = 0; i < numLines; i++)
419 	{
420 		BString *line = static_cast<BString *>(m_textLines->ItemAt(i));
421 		numChars[i] = line->CountChars();
422 		delete line;
423 	}
424 	m_textLines->MakeEmpty();
425 
426 	// calculate the maximum width for a line
427 	float maxWidth = m_parent->Bounds().Width();
428 	maxWidth -= m_parent->getSideBarWidth() + 3 * InfoView::M_H_MARGIN + B_LARGE_ICON;
429 	if (maxWidth <= be_plain_font->StringWidth("M"))
430 	{
431 		return;
432 	}
433 
434 	// iterate through the text and split into new lines as
435 	// necessary
436 	BString *currentLine = new BString(m_text);
437 	while (currentLine && (currentLine->CountChars() > 0))
438 	{
439 		int32 lastBreak = 0;
440 		for (int32 i = 0; i < currentLine->CountChars(); i++)
441 		{
442 			if (canEndLine(currentLine->ByteAt(i)))
443 			{
444 				lastBreak = i + 1;
445 				if (mustEndLine(currentLine->ByteAt(i)))
446 				{
447 					BString *newLine = new BString();
448 					currentLine->Remove(i, 1);
449 					currentLine->MoveInto(*newLine, i,
450 										  currentLine->CountChars() - i);
451 					m_textLines->AddItem(reinterpret_cast<void *>(currentLine));
452 					currentLine = newLine;
453 					break;
454 				}
455 			}
456 			else
457 			{
458 				if (i == currentLine->CountChars() - 1) // the last char in the text
459 				{
460 					m_textLines->AddItem(reinterpret_cast<void *>(currentLine));
461 					currentLine = 0;
462 					break;
463 				}
464 				else
465 				{
466 					BString buffer;
467 					currentLine->CopyInto(buffer, 0, i);
468 					if (be_plain_font->StringWidth(buffer.String()) > maxWidth)
469 					{
470 						if (lastBreak < 1)
471 						{
472 							lastBreak = i - 1;
473 						}
474 						BString *newLine = new BString();
475 						currentLine->MoveInto(*newLine, lastBreak,
476 											  currentLine->CountChars() - lastBreak);
477 						m_textLines->AddItem(reinterpret_cast<void *>(currentLine));
478 						currentLine = newLine;
479 						break;
480 					}
481 				}
482 			}
483 		}
484 	}
485 
486 	// report changes in the fields total height (i.e. if the number
487 	// of lines changed)
488 	if (heightChanged && (numLines != m_textLines->CountItems()))
489 	{
490 		*heightChanged = true;
491 	}
492 
493 	// report changes in the wrapping (e.g. if a word slipped into the
494 	// next line)
495 	else if (wrappingChanged)
496 	{
497 		for (int32 i = 0; i < m_textLines->CountItems(); i++)
498 		{
499 			BString *line = static_cast<BString *>(m_textLines->ItemAt(i));
500 			if (line->CountChars() != numChars[i])
501 			{
502 				*wrappingChanged = true;
503 				break;
504 			}
505 		}
506 	}
507 }
508 
509 // -------------------------------------------------------- //
510 // *** internal class: _InfoTextField
511 //
512 // *** accessors (public)
513 // -------------------------------------------------------- //
514 
515 float
516 _InfoTextField::getHeight() const {
517 	D_ACCESS(("_InfoTextField::getHeight()\n"));
518 
519 	font_height fh;
520 	be_plain_font->GetHeight(&fh);
521 
522 	// calculate the width for an empty line (separator)
523 	if (m_textLines->CountItems() == 0)
524 	{
525 		return fh.ascent + fh.descent + fh.leading;
526 	}
527 
528 	// calculate the total height of the field by counting the
529 	else
530 	{
531 		float height = fh.ascent + fh.descent + fh.leading;
532 		height *= m_textLines->CountItems();
533 		height += fh.leading;
534 		return height;
535 	}
536 }
537 
538 float
539 _InfoTextField::getWidth() const {
540 	D_ACCESS(("_InfoTextField::getWidth()\n"));
541 
542 	float width = be_plain_font->StringWidth(m_text.String());
543 	width += m_parent->getSideBarWidth();
544 
545 	return width;
546 }
547 
548 bool
549 _InfoTextField::isWrapped() const {
550 	D_ACCESS(("_InfoTextField::isWrapped()\n"));
551 
552 	return (m_textLines->CountItems() > 1);
553 }
554 
555 // -------------------------------------------------------- //
556 // *** internal class: _InfoTextField
557 //
558 // *** static internal methods (private)
559 // -------------------------------------------------------- //
560 
561 bool _InfoTextField::canEndLine(
562 	const char c)
563 {
564 	D_METHOD(("_InfoTextField::canEndLine()\n"));
565 
566 	if ((c == B_SPACE) || (c == B_TAB) || (c == B_ENTER)
567 	 || ( c == '-'))
568 	{
569 		return true;
570 	}
571 	return false;
572 }
573 
574 bool _InfoTextField::mustEndLine(
575 	const char c)
576 {
577 	D_METHOD(("_InfoTextField::mustEndLine()\n"));
578 
579 	if (c == B_ENTER)
580 	{
581 		return true;
582 	}
583 	return false;
584 }
585 
586 // END -- InfoView.cpp --
587