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
66static 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
77class _InfoTextField
78{
79
80public:					// *** ctor/dtor
81
82						_InfoTextField(
83							BString label,
84							BString text,
85							InfoView *parent);
86
87						~_InfoTextField();
88
89public:					// *** operations
90
91	void				drawField(
92							BPoint position);
93
94	void				updateLineWrapping(
95							bool *wrappingChanged = 0,
96							bool *heightChanged = 0);
97
98public:					// *** accessors
99
100	float				getHeight() const;
101
102	float				getWidth() const;
103
104	bool				isWrapped() const;
105
106private:				// *** static internal methods
107
108	static bool			canEndLine(
109							const char c);
110
111	static bool			mustEndLine(
112							const char c);
113
114private:				// *** 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
129const BRect InfoView::M_DEFAULT_FRAME	= BRect(0.0, 0.0, 250.0, 100.0);
130const float InfoView::M_H_MARGIN		= 5.0;
131const float InfoView::M_V_MARGIN		= 5.0;
132
133// -------------------------------------------------------- //
134// *** ctor/dtor (public)
135// -------------------------------------------------------- //
136
137InfoView::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
156InfoView::~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
183void 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
230void 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
287void 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
337void
338InfoView::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
367void 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
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
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
419void _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
445void _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
554float
555_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
577float
578_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
587bool
588_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
600bool _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
613bool _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