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(®ion, 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