1 /*
2 Open Tracker License
3
4 Terms and Conditions
5
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34
35
36 #include "TextWidget.h"
37
38 #include <string.h>
39 #include <stdlib.h>
40
41 #include <Alert.h>
42 #include <Catalog.h>
43 #include <Clipboard.h>
44 #include <Debug.h>
45 #include <Directory.h>
46 #include <MessageFilter.h>
47 #include <ScrollView.h>
48 #include <TextView.h>
49 #include <Volume.h>
50 #include <Window.h>
51
52 #include "Attributes.h"
53 #include "ContainerWindow.h"
54 #include "Commands.h"
55 #include "FSUtils.h"
56 #include "PoseView.h"
57 #include "Utilities.h"
58
59
60 #undef B_TRANSLATION_CONTEXT
61 #define B_TRANSLATION_CONTEXT "TextWidget"
62
63
64 const float kWidthMargin = 20;
65
66
67 // #pragma mark - BTextWidget
68
69
BTextWidget(Model * model,BColumn * column,BPoseView * view)70 BTextWidget::BTextWidget(Model* model, BColumn* column, BPoseView* view)
71 :
72 fText(WidgetAttributeText::NewWidgetText(model, column, view)),
73 fAttrHash(column->AttrHash()),
74 fAlignment(column->Alignment()),
75 fEditable(column->Editable()),
76 fVisible(true),
77 fActive(false),
78 fSymLink(model->IsSymLink()),
79 fMaxWidth(0),
80 fLastClickedTime(0)
81 {
82 }
83
84
~BTextWidget()85 BTextWidget::~BTextWidget()
86 {
87 if (fLastClickedTime != 0)
88 fParams.poseView->SetTextWidgetToCheck(NULL, this);
89
90 delete fText;
91 }
92
93
94 int
Compare(const BTextWidget & with,BPoseView * view) const95 BTextWidget::Compare(const BTextWidget& with, BPoseView* view) const
96 {
97 return fText->Compare(*with.fText, view);
98 }
99
100
101 const char*
Text(const BPoseView * view) const102 BTextWidget::Text(const BPoseView* view) const
103 {
104 StringAttributeText* textAttribute
105 = dynamic_cast<StringAttributeText*>(fText);
106 if (textAttribute == NULL)
107 return NULL;
108
109 return textAttribute->ValueAsText(view);
110 }
111
112
113 float
TextWidth(const BPoseView * pose) const114 BTextWidget::TextWidth(const BPoseView* pose) const
115 {
116 return fText->Width(pose);
117 }
118
119
120 float
PreferredWidth(const BPoseView * pose) const121 BTextWidget::PreferredWidth(const BPoseView* pose) const
122 {
123 return fText->PreferredWidth(pose);
124 }
125
126
127 BRect
ColumnRect(BPoint poseLoc,const BColumn * column,const BPoseView * view)128 BTextWidget::ColumnRect(BPoint poseLoc, const BColumn* column,
129 const BPoseView* view)
130 {
131 if (view->ViewMode() != kListMode) {
132 // ColumnRect only makes sense in list view, return
133 // CalcRect otherwise
134 return CalcRect(poseLoc, column, view);
135 }
136 BRect result;
137 result.left = column->Offset() + poseLoc.x;
138 result.right = result.left + column->Width();
139 result.bottom = poseLoc.y
140 + roundf((view->ListElemHeight() + ActualFontHeight(view)) / 2);
141 result.top = result.bottom - floorf(ActualFontHeight(view));
142 return result;
143 }
144
145
146 BRect
CalcRectCommon(BPoint poseLoc,const BColumn * column,const BPoseView * view,float textWidth)147 BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn* column,
148 const BPoseView* view, float textWidth)
149 {
150 textWidth -= 1;
151 BRect result;
152 float viewWidth = textWidth;
153
154 if (view->ViewMode() == kListMode) {
155 viewWidth = std::min(column->Width(), textWidth);
156
157 poseLoc.x += column->Offset();
158
159 switch (fAlignment) {
160 case B_ALIGN_LEFT:
161 result.left = poseLoc.x;
162 result.right = result.left + viewWidth;
163 break;
164
165 case B_ALIGN_CENTER:
166 result.left = poseLoc.x
167 + roundf((column->Width() - viewWidth) / 2);
168 if (result.left < 0)
169 result.left = 0;
170
171 result.right = result.left + viewWidth;
172 break;
173
174 case B_ALIGN_RIGHT:
175 result.right = poseLoc.x + column->Width();
176 result.left = result.right - viewWidth;
177 if (result.left < 0)
178 result.left = 0;
179 break;
180
181 default:
182 TRESPASS();
183 break;
184 }
185
186 result.bottom = poseLoc.y
187 + roundf((view->ListElemHeight() + ActualFontHeight(view)) / 2);
188 } else {
189 viewWidth = std::min(view->StringWidth("M") * 30, textWidth);
190 if (view->ViewMode() == kIconMode) {
191 // icon mode
192 result.left = poseLoc.x
193 + roundf((view->IconSizeInt() - viewWidth) / 2);
194 } else {
195 // mini icon mode
196 result.left = poseLoc.x + view->IconSizeInt() + kMiniIconSeparator;
197 }
198 result.bottom = poseLoc.y + view->IconPoseHeight();
199
200 result.right = result.left + viewWidth;
201 }
202
203 result.top = result.bottom - floorf(ActualFontHeight(view));
204
205 return result;
206 }
207
208
209 BRect
CalcRect(BPoint poseLoc,const BColumn * column,const BPoseView * view)210 BTextWidget::CalcRect(BPoint poseLoc, const BColumn* column,
211 const BPoseView* view)
212 {
213 return CalcRectCommon(poseLoc, column, view, fText->Width(view));
214 }
215
216
217 BRect
CalcOldRect(BPoint poseLoc,const BColumn * column,const BPoseView * view)218 BTextWidget::CalcOldRect(BPoint poseLoc, const BColumn* column,
219 const BPoseView* view)
220 {
221 return CalcRectCommon(poseLoc, column, view, fText->CurrentWidth());
222 }
223
224
225 BRect
CalcClickRect(BPoint poseLoc,const BColumn * column,const BPoseView * view)226 BTextWidget::CalcClickRect(BPoint poseLoc, const BColumn* column,
227 const BPoseView* view)
228 {
229 BRect result = CalcRect(poseLoc, column, view);
230 if (result.Width() < kWidthMargin) {
231 // if resulting rect too narrow, make it a bit wider
232 // for comfortable clicking
233 if (column != NULL && column->Width() < kWidthMargin)
234 result.right = result.left + column->Width();
235 else
236 result.right = result.left + kWidthMargin;
237 }
238
239 return result;
240 }
241
242
243 void
CheckExpiration()244 BTextWidget::CheckExpiration()
245 {
246 if (IsEditable() && fParams.pose->IsSelected() && fLastClickedTime) {
247 bigtime_t doubleClickSpeed;
248 get_click_speed(&doubleClickSpeed);
249
250 bigtime_t delta = system_time() - fLastClickedTime;
251
252 if (delta > doubleClickSpeed) {
253 // at least 'doubleClickSpeed' microseconds ellapsed and no click
254 // was registered since.
255 fLastClickedTime = 0;
256 StartEdit(fParams.bounds, fParams.poseView, fParams.pose);
257 }
258 } else {
259 fLastClickedTime = 0;
260 fParams.poseView->SetTextWidgetToCheck(NULL);
261 }
262 }
263
264
265 void
CancelWait()266 BTextWidget::CancelWait()
267 {
268 fLastClickedTime = 0;
269 fParams.poseView->SetTextWidgetToCheck(NULL);
270 }
271
272
273 void
MouseUp(BRect bounds,BPoseView * view,BPose * pose,BPoint)274 BTextWidget::MouseUp(BRect bounds, BPoseView* view, BPose* pose, BPoint)
275 {
276 // Register the time of that click. The PoseView, through its Pulse()
277 // will allow us to StartEdit() if no other click have been registered since
278 // then.
279
280 // TODO: re-enable modifiers, one should be enough
281 view->SetTextWidgetToCheck(NULL);
282 if (IsEditable() && pose->IsSelected()) {
283 bigtime_t doubleClickSpeed;
284 get_click_speed(&doubleClickSpeed);
285
286 if (fLastClickedTime == 0) {
287 fLastClickedTime = system_time();
288 if (fLastClickedTime - doubleClickSpeed < pose->SelectionTime())
289 fLastClickedTime = 0;
290 } else
291 fLastClickedTime = 0;
292
293 if (fLastClickedTime == 0)
294 return;
295
296 view->SetTextWidgetToCheck(this);
297
298 fParams.pose = pose;
299 fParams.bounds = bounds;
300 fParams.poseView = view;
301 } else
302 fLastClickedTime = 0;
303 }
304
305
306 static filter_result
TextViewKeyDownFilter(BMessage * message,BHandler **,BMessageFilter * filter)307 TextViewKeyDownFilter(BMessage* message, BHandler**, BMessageFilter* filter)
308 {
309 uchar key;
310 if (message->FindInt8("byte", (int8*)&key) != B_OK)
311 return B_DISPATCH_MESSAGE;
312
313 ThrowOnAssert(filter != NULL);
314
315 BContainerWindow* window = dynamic_cast<BContainerWindow*>(
316 filter->Looper());
317 ThrowOnAssert(window != NULL);
318
319 BPoseView* view = window->PoseView();
320 ThrowOnAssert(view != NULL);
321
322 if (key == B_RETURN || key == B_ESCAPE) {
323 view->CommitActivePose(key == B_RETURN);
324 return B_SKIP_MESSAGE;
325 }
326
327 if (key == B_TAB) {
328 if (view->ActivePose()) {
329 if (message->FindInt32("modifiers") & B_SHIFT_KEY)
330 view->ActivePose()->EditPreviousWidget(view);
331 else
332 view->ActivePose()->EditNextWidget(view);
333 }
334
335 return B_SKIP_MESSAGE;
336 }
337
338 // the BTextView doesn't respect window borders when resizing itself;
339 // we try to work-around this "bug" here.
340
341 // find the text editing view
342 BView* scrollView = view->FindView("BorderView");
343 if (scrollView != NULL) {
344 BTextView* textView = dynamic_cast<BTextView*>(
345 scrollView->FindView("WidgetTextView"));
346 if (textView != NULL) {
347 ASSERT(view->ActiveTextWidget() != NULL);
348 float maxWidth = view->ActiveTextWidget()->MaxWidth();
349 bool tooWide = textView->TextRect().Width() > maxWidth;
350 textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView);
351 }
352 }
353
354 return B_DISPATCH_MESSAGE;
355 }
356
357
358 static filter_result
TextViewPasteFilter(BMessage * message,BHandler **,BMessageFilter * filter)359 TextViewPasteFilter(BMessage* message, BHandler**, BMessageFilter* filter)
360 {
361 ThrowOnAssert(filter != NULL);
362
363 BContainerWindow* window = dynamic_cast<BContainerWindow*>(
364 filter->Looper());
365 ThrowOnAssert(window != NULL);
366
367 BPoseView* view = window->PoseView();
368 ThrowOnAssert(view != NULL);
369
370 // the BTextView doesn't respect window borders when resizing itself;
371 // we try to work-around this "bug" here.
372
373 // find the text editing view
374 BView* scrollView = view->FindView("BorderView");
375 if (scrollView != NULL) {
376 BTextView* textView = dynamic_cast<BTextView*>(
377 scrollView->FindView("WidgetTextView"));
378 if (textView != NULL) {
379 float textWidth = textView->TextRect().Width();
380
381 // subtract out selected text region width
382 int32 start, finish;
383 textView->GetSelection(&start, &finish);
384 if (start != finish) {
385 BRegion selectedRegion;
386 textView->GetTextRegion(start, finish, &selectedRegion);
387 textWidth -= selectedRegion.Frame().Width();
388 }
389
390 // add pasted text width
391 if (be_clipboard->Lock()) {
392 BMessage* clip = be_clipboard->Data();
393 if (clip != NULL) {
394 const char* text = NULL;
395 ssize_t length = 0;
396
397 if (clip->FindData("text/plain", B_MIME_TYPE,
398 (const void**)&text, &length) == B_OK) {
399 textWidth += textView->StringWidth(text);
400 }
401 }
402
403 be_clipboard->Unlock();
404 }
405
406 // check if pasted text is too wide
407 ASSERT(view->ActiveTextWidget() != NULL);
408 float maxWidth = view->ActiveTextWidget()->MaxWidth();
409 bool tooWide = textWidth > maxWidth;
410
411 if (tooWide) {
412 // resize text view to max width
413
414 // move scroll view if not left aligned
415 float oldWidth = textView->Bounds().Width();
416 float newWidth = maxWidth;
417 float right = oldWidth - newWidth;
418
419 if (textView->Alignment() == B_ALIGN_CENTER)
420 scrollView->MoveBy(roundf(right / 2), 0);
421 else if (textView->Alignment() == B_ALIGN_RIGHT)
422 scrollView->MoveBy(right, 0);
423
424 // resize scroll view
425 float grow = newWidth - oldWidth;
426 scrollView->ResizeBy(grow, 0);
427 }
428
429 textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView);
430 }
431 }
432
433 return B_DISPATCH_MESSAGE;
434 }
435
436
437 void
StartEdit(BRect bounds,BPoseView * view,BPose * pose)438 BTextWidget::StartEdit(BRect bounds, BPoseView* view, BPose* pose)
439 {
440 view->SetTextWidgetToCheck(NULL, this);
441 if (!IsEditable() || IsActive())
442 return;
443
444 view->SetActiveTextWidget(this);
445
446 BRect rect(bounds);
447 rect.OffsetBy(view->ViewMode() == kListMode ? -2 : 0, -2);
448 BTextView* textView = new BTextView(rect, "WidgetTextView", rect,
449 be_plain_font, 0, B_FOLLOW_ALL, B_WILL_DRAW);
450
451 textView->SetWordWrap(false);
452 textView->SetInsets(2, 2, 2, 2);
453 DisallowMetaKeys(textView);
454 fText->SetupEditing(textView);
455
456 textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewKeyDownFilter));
457
458 if (view->SelectedVolumeIsReadOnly()) {
459 textView->MakeEditable(false);
460 textView->MakeSelectable(true);
461 // tint text view background color to indicate not editable
462 textView->SetViewColor(tint_color(textView->ViewColor(),
463 ReadOnlyTint(textView->ViewColor())));
464 } else
465 textView->AddFilter(new BMessageFilter(B_PASTE, TextViewPasteFilter));
466
467 // get full text length
468 rect.right = rect.left + textView->LineWidth();
469 rect.bottom = rect.top + textView->LineHeight() - 1 + 4;
470
471 if (view->ViewMode() == kListMode) {
472 // limit max width to column width in list mode
473 BColumn* column = view->ColumnFor(fAttrHash);
474 ASSERT(column != NULL);
475 fMaxWidth = column->Width();
476 } else {
477 // limit max width to 30em in icon and mini icon mode
478 fMaxWidth = textView->StringWidth("M") * 30;
479
480 if (textView->LineWidth() > fMaxWidth
481 || view->ViewMode() == kMiniIconMode) {
482 // compensate for text going over right inset
483 rect.OffsetBy(-2, 0);
484 }
485 }
486
487 // resize textView
488 textView->MoveTo(rect.LeftTop());
489 textView->ResizeTo(std::min(fMaxWidth, rect.Width()), rect.Height());
490 textView->SetTextRect(rect);
491
492 // set alignment before adding textView so it doesn't redraw
493 switch (view->ViewMode()) {
494 case kIconMode:
495 textView->SetAlignment(B_ALIGN_CENTER);
496 break;
497
498 case kMiniIconMode:
499 textView->SetAlignment(B_ALIGN_LEFT);
500 break;
501
502 case kListMode:
503 textView->SetAlignment(fAlignment);
504 break;
505 }
506
507 BScrollView* scrollView = new BScrollView("BorderView", textView, 0, 0,
508 false, false, B_PLAIN_BORDER);
509 view->AddChild(scrollView);
510
511 bool tooWide = textView->TextRect().Width() > fMaxWidth;
512 textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView);
513
514 view->SetActivePose(pose);
515 // tell view about pose
516 SetActive(true);
517 // for widget
518
519 textView->SelectAll();
520 textView->ScrollToSelection();
521 // scroll to beginning so that text is visible
522 textView->MakeFocus();
523
524 // make this text widget invisible while we edit it
525 SetVisible(false);
526
527 ASSERT(view->Window() != NULL);
528 // how can I not have a Window here???
529
530 if (view->Window()) {
531 // force immediate redraw so TextView appears instantly
532 view->Window()->UpdateIfNeeded();
533 }
534 }
535
536
537 void
StopEdit(bool saveChanges,BPoint poseLoc,BPoseView * view,BPose * pose,int32 poseIndex)538 BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView* view,
539 BPose* pose, int32 poseIndex)
540 {
541 view->SetActiveTextWidget(NULL);
542
543 // find the text editing view
544 BView* scrollView = view->FindView("BorderView");
545 ASSERT(scrollView != NULL);
546 if (scrollView == NULL)
547 return;
548
549 BTextView* textView = dynamic_cast<BTextView*>(
550 scrollView->FindView("WidgetTextView"));
551 ASSERT(textView != NULL);
552 if (textView == NULL)
553 return;
554
555 BColumn* column = view->ColumnFor(fAttrHash);
556 ASSERT(column != NULL);
557 if (column == NULL)
558 return;
559
560 if (saveChanges && fText->CommitEditedText(textView)) {
561 // we have an actual change, re-sort
562 view->CheckPoseSortOrder(pose, poseIndex);
563 }
564
565 // make text widget visible again
566 SetVisible(true);
567 view->Invalidate(ColumnRect(poseLoc, column, view));
568
569 // force immediate redraw so TEView disappears
570 scrollView->RemoveSelf();
571 delete scrollView;
572
573 ASSERT(view->Window() != NULL);
574 view->Window()->UpdateIfNeeded();
575 view->MakeFocus();
576
577 SetActive(false);
578 }
579
580
581 void
CheckAndUpdate(BPoint loc,const BColumn * column,BPoseView * view,bool visible)582 BTextWidget::CheckAndUpdate(BPoint loc, const BColumn* column,
583 BPoseView* view, bool visible)
584 {
585 BRect oldRect;
586 if (view->ViewMode() != kListMode)
587 oldRect = CalcOldRect(loc, column, view);
588
589 if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view)
590 && visible) {
591 BRect invalRect(ColumnRect(loc, column, view));
592 if (view->ViewMode() != kListMode)
593 invalRect = invalRect | oldRect;
594 view->Invalidate(invalRect);
595 }
596 }
597
598
599 void
SelectAll(BPoseView * view)600 BTextWidget::SelectAll(BPoseView* view)
601 {
602 BTextView* text = dynamic_cast<BTextView*>(
603 view->FindView("WidgetTextView"));
604 if (text != NULL)
605 text->SelectAll();
606 }
607
608
609 void
Draw(BRect eraseRect,BRect textRect,float,BPoseView * view,BView * drawView,bool selected,uint32 clipboardMode,BPoint offset,bool direct)610 BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView* view,
611 BView* drawView, bool selected, uint32 clipboardMode, BPoint offset,
612 bool direct)
613 {
614 textRect.OffsetBy(offset);
615
616 // We are only concerned with setting the correct text color.
617
618 // For active views the selection is drawn as inverse text
619 // (background color for the text, solid black for the background).
620 // For inactive windows the text is drawn normally, then the
621 // selection rect is alpha-blended on top. This all happens in
622 // BPose::Draw before and after calling this function.
623
624 if (direct) {
625 // draw selection box if selected
626 if (selected) {
627 drawView->SetDrawingMode(B_OP_COPY);
628 drawView->FillRect(textRect, B_SOLID_LOW);
629 } else
630 drawView->SetDrawingMode(B_OP_OVER);
631
632 // set high color
633 rgb_color highColor;
634 highColor = view->TextColor(selected && view->Window()->IsActive());
635
636 if (clipboardMode == kMoveSelectionTo && !selected) {
637 drawView->SetDrawingMode(B_OP_ALPHA);
638 drawView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
639 highColor.alpha = 64;
640 }
641 drawView->SetHighColor(highColor);
642 } else if (selected && view->Window()->IsActive())
643 drawView->SetHighColor(view->BackColor(true)); // inverse
644 else if (!selected)
645 drawView->SetHighColor(view->TextColor());
646
647 BPoint location;
648 location.y = textRect.bottom - view->FontInfo().descent + 1;
649 location.x = textRect.left;
650
651 const char* fittingText = fText->FittingText(view);
652
653 // TODO: Comparing view and drawView here to avoid rendering
654 // the text outline when producing a drag bitmap. The check is
655 // not fully correct, since an offscreen view is also used in some
656 // other rare cases (something to do with columns). But for now, this
657 // fixes the broken drag bitmaps when dragging icons from the Desktop.
658 if (direct && !selected && view->WidgetTextOutline()) {
659 // draw a halo around the text by using the "false bold"
660 // feature for text rendering. Either black or white is used for
661 // the glow (whatever acts as contrast) with a some alpha value,
662 drawView->SetDrawingMode(B_OP_ALPHA);
663 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
664
665 BFont font;
666 drawView->GetFont(&font);
667
668 rgb_color textColor = view->TextColor();
669 if (textColor.IsDark()) {
670 // dark text on light outline
671 rgb_color glowColor = ui_color(B_SHINE_COLOR);
672
673 font.SetFalseBoldWidth(2.0);
674 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
675 glowColor.alpha = 30;
676 drawView->SetHighColor(glowColor);
677
678 drawView->DrawString(fittingText, location);
679
680 font.SetFalseBoldWidth(1.0);
681 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
682 glowColor.alpha = 65;
683 drawView->SetHighColor(glowColor);
684
685 drawView->DrawString(fittingText, location);
686
687 font.SetFalseBoldWidth(0.0);
688 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
689 } else {
690 // light text on dark outline
691 rgb_color outlineColor = kBlack;
692
693 font.SetFalseBoldWidth(1.0);
694 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
695 outlineColor.alpha = 30;
696 drawView->SetHighColor(outlineColor);
697
698 drawView->DrawString(fittingText, location);
699
700 font.SetFalseBoldWidth(0.0);
701 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
702
703 outlineColor.alpha = 200;
704 drawView->SetHighColor(outlineColor);
705
706 drawView->DrawString(fittingText, location + BPoint(1, 1));
707 }
708
709 drawView->SetDrawingMode(B_OP_OVER);
710 drawView->SetHighColor(textColor);
711 }
712
713 drawView->DrawString(fittingText, location);
714
715 if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) {
716 // TODO:
717 // this should be exported to the WidgetAttribute class, probably
718 // by having a per widget kind style
719 if (direct) {
720 rgb_color underlineColor = drawView->HighColor();
721 underlineColor.alpha = 180;
722 drawView->SetHighColor(underlineColor);
723 drawView->SetDrawingMode(B_OP_ALPHA);
724 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
725 }
726
727 textRect.right = textRect.left + fText->Width(view);
728 // only underline text part
729 drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(),
730 B_MIXED_COLORS);
731 }
732 }
733