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