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