6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
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:
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.
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.
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.
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.
36 #include "TextWidget.h"
44 #include <Directory.h>
45 #include <MessageFilter.h>
46 #include <ScrollView.h>
51 #include "Attributes.h"
52 #include "ContainerWindow.h"
56 #include "Utilities.h"
59 #undef B_TRANSLATION_CONTEXT
60 #define B_TRANSLATION_CONTEXT "TextWidget"
63 const float kWidthMargin
= 20;
66 // #pragma mark - BTextWidget
69 BTextWidget::BTextWidget(Model
* model
, BColumn
* column
, BPoseView
* view
)
71 fText(WidgetAttributeText::NewWidgetText(model
, column
, view
)),
72 fAttrHash(column
->AttrHash()),
73 fAlignment(column
->Alignment()),
74 fEditable(column
->Editable()),
77 fSymLink(model
->IsSymLink()),
83 BTextWidget::~BTextWidget()
85 if (fLastClickedTime
!= 0)
86 fParams
.poseView
->SetTextWidgetToCheck(NULL
, this);
93 BTextWidget::Compare(const BTextWidget
& with
, BPoseView
* view
) const
95 return fText
->Compare(*with
.fText
, view
);
100 BTextWidget::Text(const BPoseView
* view
) const
102 StringAttributeText
* textAttribute
103 = dynamic_cast<StringAttributeText
*>(fText
);
104 if (textAttribute
== NULL
)
107 return textAttribute
->ValueAsText(view
);
112 BTextWidget::TextWidth(const BPoseView
* pose
) const
114 return fText
->Width(pose
);
119 BTextWidget::PreferredWidth(const BPoseView
* pose
) const
121 return fText
->PreferredWidth(pose
) + 1;
126 BTextWidget::ColumnRect(BPoint poseLoc
, const BColumn
* column
,
127 const BPoseView
* view
)
129 if (view
->ViewMode() != kListMode
) {
130 // ColumnRect only makes sense in list view, return
131 // CalcRect otherwise
132 return CalcRect(poseLoc
, column
, view
);
135 result
.left
= column
->Offset() + poseLoc
.x
;
136 result
.right
= result
.left
+ column
->Width();
137 result
.bottom
= poseLoc
.y
+ view
->ListElemHeight() - 1;
138 result
.top
= result
.bottom
- view
->FontHeight();
144 BTextWidget::CalcRectCommon(BPoint poseLoc
, const BColumn
* column
,
145 const BPoseView
* view
, float textWidth
)
148 if (view
->ViewMode() == kListMode
) {
149 poseLoc
.x
+= column
->Offset();
151 switch (fAlignment
) {
153 result
.left
= poseLoc
.x
;
154 result
.right
= result
.left
+ textWidth
+ 1;
158 result
.left
= poseLoc
.x
+ (column
->Width() / 2)
163 result
.right
= result
.left
+ textWidth
+ 1;
167 result
.right
= poseLoc
.x
+ column
->Width();
168 result
.left
= result
.right
- textWidth
- 1;
178 result
.bottom
= poseLoc
.y
179 + roundf((view
->ListElemHeight() + view
->FontHeight()) / 2);
181 if (view
->ViewMode() == kIconMode
) {
182 // large/scaled icon mode
183 result
.left
= poseLoc
.x
+ (view
->IconSizeInt() - textWidth
) / 2;
186 result
.left
= poseLoc
.x
+ B_MINI_ICON
+ kMiniIconSeparator
;
189 result
.right
= result
.left
+ textWidth
;
190 result
.bottom
= poseLoc
.y
+ view
->IconPoseHeight();
192 result
.top
= result
.bottom
- view
->FontHeight();
199 BTextWidget::CalcRect(BPoint poseLoc
, const BColumn
* column
,
200 const BPoseView
* view
)
202 return CalcRectCommon(poseLoc
, column
, view
, fText
->Width(view
));
207 BTextWidget::CalcOldRect(BPoint poseLoc
, const BColumn
* column
,
208 const BPoseView
* view
)
210 return CalcRectCommon(poseLoc
, column
, view
, fText
->CurrentWidth());
215 BTextWidget::CalcClickRect(BPoint poseLoc
, const BColumn
* column
,
216 const BPoseView
* view
)
218 BRect result
= CalcRect(poseLoc
, column
, view
);
219 if (result
.Width() < kWidthMargin
) {
220 // if resulting rect too narrow, make it a bit wider
221 // for comfortable clicking
222 if (column
!= NULL
&& column
->Width() < kWidthMargin
)
223 result
.right
= result
.left
+ column
->Width();
225 result
.right
= result
.left
+ kWidthMargin
;
233 BTextWidget::CheckExpiration()
235 if (IsEditable() && fParams
.pose
->IsSelected() && fLastClickedTime
) {
236 bigtime_t doubleClickSpeed
;
237 get_click_speed(&doubleClickSpeed
);
239 bigtime_t delta
= system_time() - fLastClickedTime
;
241 if (delta
> doubleClickSpeed
) {
242 // at least 'doubleClickSpeed' microseconds ellapsed and no click
243 // was registered since.
244 fLastClickedTime
= 0;
245 StartEdit(fParams
.bounds
, fParams
.poseView
, fParams
.pose
);
248 fLastClickedTime
= 0;
249 fParams
.poseView
->SetTextWidgetToCheck(NULL
);
255 BTextWidget::CancelWait()
257 fLastClickedTime
= 0;
258 fParams
.poseView
->SetTextWidgetToCheck(NULL
);
263 BTextWidget::MouseUp(BRect bounds
, BPoseView
* view
, BPose
* pose
, BPoint
)
265 // Register the time of that click. The PoseView, through its Pulse()
266 // will allow us to StartEdit() if no other click have been registered since
269 // TODO: re-enable modifiers, one should be enough
270 view
->SetTextWidgetToCheck(NULL
);
271 if (IsEditable() && pose
->IsSelected()) {
272 bigtime_t doubleClickSpeed
;
273 get_click_speed(&doubleClickSpeed
);
275 if (fLastClickedTime
== 0) {
276 fLastClickedTime
= system_time();
277 if (fLastClickedTime
- doubleClickSpeed
< pose
->SelectionTime())
278 fLastClickedTime
= 0;
280 fLastClickedTime
= 0;
282 if (fLastClickedTime
== 0)
285 view
->SetTextWidgetToCheck(this);
288 fParams
.bounds
= bounds
;
289 fParams
.poseView
= view
;
291 fLastClickedTime
= 0;
296 TextViewFilter(BMessage
* message
, BHandler
**, BMessageFilter
* filter
)
299 if (message
->FindInt8("byte", (int8
*)&key
) != B_OK
)
300 return B_DISPATCH_MESSAGE
;
302 ThrowOnAssert(filter
!= NULL
);
304 BContainerWindow
* window
= dynamic_cast<BContainerWindow
*>(
306 ThrowOnAssert(window
!= NULL
);
308 BPoseView
* poseView
= window
->PoseView();
309 ThrowOnAssert(poseView
!= NULL
);
311 if (key
== B_RETURN
|| key
== B_ESCAPE
) {
312 poseView
->CommitActivePose(key
== B_RETURN
);
313 return B_SKIP_MESSAGE
;
317 if (poseView
->ActivePose()) {
318 if (message
->FindInt32("modifiers") & B_SHIFT_KEY
)
319 poseView
->ActivePose()->EditPreviousWidget(poseView
);
321 poseView
->ActivePose()->EditNextWidget(poseView
);
324 return B_SKIP_MESSAGE
;
327 // the BTextView doesn't respect window borders when resizing itself;
328 // we try to work-around this "bug" here.
330 // find the text editing view
331 BView
* scrollView
= poseView
->FindView("BorderView");
332 if (scrollView
!= NULL
) {
333 BTextView
* textView
= dynamic_cast<BTextView
*>(
334 scrollView
->FindView("WidgetTextView"));
335 if (textView
!= NULL
) {
336 BRect textRect
= textView
->TextRect();
337 BRect rect
= scrollView
->Frame();
339 if (rect
.right
+ 5 > poseView
->Bounds().right
340 || rect
.left
- 5 < 0)
341 textView
->MakeResizable(true, NULL
);
343 if (textRect
.Width() + 10 < rect
.Width()) {
344 textView
->MakeResizable(true, scrollView
);
345 // make sure no empty white space stays on the right
346 textView
->ScrollToOffset(0);
351 return B_DISPATCH_MESSAGE
;
356 BTextWidget::StartEdit(BRect bounds
, BPoseView
* view
, BPose
* pose
)
358 view
->SetTextWidgetToCheck(NULL
, this);
359 if (!IsEditable() || IsActive())
362 BEntry
entry(pose
->TargetModel()->EntryRef());
363 if (entry
.InitCheck() == B_OK
364 && !ConfirmChangeIfWellKnownDirectory(&entry
, kRename
)) {
368 // get bounds with full text length
370 BRect
textRect(bounds
);
371 rect
.OffsetBy(-2, -1);
375 view
->GetFont(&font
);
376 BTextView
* textView
= new BTextView(rect
, "WidgetTextView", textRect
,
377 &font
, 0, B_FOLLOW_ALL
, B_WILL_DRAW
);
379 textView
->SetWordWrap(false);
380 DisallowMetaKeys(textView
);
381 fText
->SetUpEditing(textView
);
383 textView
->AddFilter(new BMessageFilter(B_KEY_DOWN
, TextViewFilter
));
385 rect
.right
= rect
.left
+ textView
->LineWidth() + 3;
386 // center new width, if necessary
387 if (view
->ViewMode() == kIconMode
388 || (view
->ViewMode() == kListMode
&& fAlignment
== B_ALIGN_CENTER
)) {
389 rect
.OffsetBy(bounds
.Width() / 2 - rect
.Width() / 2, 0);
392 rect
.bottom
= rect
.top
+ textView
->LineHeight() + 1;
393 textRect
= rect
.OffsetToCopy(2, 1);
396 textView
->SetTextRect(textRect
);
398 BPoint origin
= view
->LeftTop();
399 textRect
= view
->Bounds();
401 bool hitBorder
= false;
402 if (rect
.left
<= origin
.x
)
403 rect
.left
= origin
.x
+ 1, hitBorder
= true;
404 if (rect
.right
>= textRect
.right
)
405 rect
.right
= textRect
.right
- 1, hitBorder
= true;
407 textView
->MoveTo(rect
.LeftTop());
408 textView
->ResizeTo(rect
.Width(), rect
.Height());
410 BScrollView
* scrollView
= new BScrollView("BorderView", textView
, 0, 0,
411 false, false, B_PLAIN_BORDER
);
412 view
->AddChild(scrollView
);
414 // configure text view
415 switch (view
->ViewMode()) {
417 textView
->SetAlignment(B_ALIGN_CENTER
);
421 textView
->SetAlignment(B_ALIGN_LEFT
);
425 textView
->SetAlignment(fAlignment
);
428 textView
->MakeResizable(true, hitBorder
? NULL
: scrollView
);
430 view
->SetActivePose(pose
);
431 // tell view about pose
435 textView
->SelectAll();
436 textView
->MakeFocus();
438 // make this text widget invisible while we edit it
441 ASSERT(view
->Window() != NULL
);
442 // how can I not have a Window here???
444 if (view
->Window()) {
445 // force immediate redraw so TextView appears instantly
446 view
->Window()->UpdateIfNeeded();
452 BTextWidget::StopEdit(bool saveChanges
, BPoint poseLoc
, BPoseView
* view
,
453 BPose
* pose
, int32 poseIndex
)
455 // find the text editing view
456 BView
* scrollView
= view
->FindView("BorderView");
457 ASSERT(scrollView
!= NULL
);
458 if (scrollView
== NULL
)
461 BTextView
* textView
= dynamic_cast<BTextView
*>(
462 scrollView
->FindView("WidgetTextView"));
463 ASSERT(textView
!= NULL
);
464 if (textView
== NULL
)
467 BColumn
* column
= view
->ColumnFor(fAttrHash
);
468 ASSERT(column
!= NULL
);
472 if (saveChanges
&& fText
->CommitEditedText(textView
)) {
473 // we have an actual change, re-sort
474 view
->CheckPoseSortOrder(pose
, poseIndex
);
477 // make text widget visible again
479 view
->Invalidate(ColumnRect(poseLoc
, column
, view
));
481 // force immediate redraw so TEView disappears
482 scrollView
->RemoveSelf();
485 ASSERT(view
->Window() != NULL
);
486 view
->Window()->UpdateIfNeeded();
494 BTextWidget::CheckAndUpdate(BPoint loc
, const BColumn
* column
,
495 BPoseView
* view
, bool visible
)
498 if (view
->ViewMode() != kListMode
)
499 oldRect
= CalcOldRect(loc
, column
, view
);
501 if (fText
->CheckAttributeChanged() && fText
->CheckViewChanged(view
)
503 BRect
invalRect(ColumnRect(loc
, column
, view
));
504 if (view
->ViewMode() != kListMode
)
505 invalRect
= invalRect
| oldRect
;
506 view
->Invalidate(invalRect
);
512 BTextWidget::SelectAll(BPoseView
* view
)
514 BTextView
* text
= dynamic_cast<BTextView
*>(
515 view
->FindView("WidgetTextView"));
522 BTextWidget::Draw(BRect eraseRect
, BRect textRect
, float, BPoseView
* view
,
523 BView
* drawView
, bool selected
, uint32 clipboardMode
, BPoint offset
,
526 textRect
.OffsetBy(offset
);
529 // draw selection box if selected
531 drawView
->SetDrawingMode(B_OP_COPY
);
532 // eraseRect.OffsetBy(offset);
533 // drawView->FillRect(eraseRect, B_SOLID_LOW);
534 drawView
->FillRect(textRect
, B_SOLID_LOW
);
536 drawView
->SetDrawingMode(B_OP_OVER
);
540 if (view
->IsDesktopWindow()) {
542 highColor
= ui_color(B_DOCUMENT_BACKGROUND_COLOR
);
544 highColor
= view
->DeskTextColor();
545 } else if (selected
&& view
->Window()->IsActive()) {
546 highColor
= ui_color(B_DOCUMENT_BACKGROUND_COLOR
);
550 if (clipboardMode
== kMoveSelectionTo
&& !selected
) {
551 drawView
->SetDrawingMode(B_OP_ALPHA
);
552 drawView
->SetBlendingMode(B_PIXEL_ALPHA
, B_ALPHA_OVERLAY
);
553 highColor
.alpha
= 64;
555 drawView
->SetHighColor(highColor
);
559 loc
.y
= textRect
.bottom
- view
->FontInfo().descent
;
560 loc
.x
= textRect
.left
+ 1;
562 const char* fittingText
= fText
->FittingText(view
);
564 // TODO: Comparing view and drawView here to avoid rendering
565 // the text outline when producing a drag bitmap. The check is
566 // not fully correct, since an offscreen view is also used in some
567 // other rare cases (something to do with columns). But for now, this
568 // fixes the broken drag bitmaps when dragging icons from the Desktop.
569 if (!selected
&& view
== drawView
&& view
->WidgetTextOutline()) {
570 // draw a halo around the text by using the "false bold"
571 // feature for text rendering. Either black or white is used for
572 // the glow (whatever acts as contrast) with a some alpha value,
573 drawView
->SetDrawingMode(B_OP_ALPHA
);
574 drawView
->SetBlendingMode(B_CONSTANT_ALPHA
, B_ALPHA_OVERLAY
);
577 drawView
->GetFont(&font
);
579 rgb_color textColor
= ui_color(B_PANEL_TEXT_COLOR
);
580 if (view
->IsDesktopWindow())
581 textColor
= view
->DeskTextColor();
583 if (textColor
.Brightness() < 100) {
584 // dark text on light outline
585 rgb_color glowColor
= ui_color(B_SHINE_COLOR
);
587 font
.SetFalseBoldWidth(2.0);
588 drawView
->SetFont(&font
, B_FONT_FALSE_BOLD_WIDTH
);
589 glowColor
.alpha
= 30;
590 drawView
->SetHighColor(glowColor
);
592 drawView
->DrawString(fittingText
, loc
);
594 font
.SetFalseBoldWidth(1.0);
595 drawView
->SetFont(&font
, B_FONT_FALSE_BOLD_WIDTH
);
596 glowColor
.alpha
= 65;
597 drawView
->SetHighColor(glowColor
);
599 drawView
->DrawString(fittingText
, loc
);
601 font
.SetFalseBoldWidth(0.0);
602 drawView
->SetFont(&font
, B_FONT_FALSE_BOLD_WIDTH
);
604 // light text on dark outline
605 rgb_color outlineColor
= kBlack
;
607 font
.SetFalseBoldWidth(1.0);
608 drawView
->SetFont(&font
, B_FONT_FALSE_BOLD_WIDTH
);
609 outlineColor
.alpha
= 30;
610 drawView
->SetHighColor(outlineColor
);
612 drawView
->DrawString(fittingText
, loc
);
614 font
.SetFalseBoldWidth(0.0);
615 drawView
->SetFont(&font
, B_FONT_FALSE_BOLD_WIDTH
);
617 outlineColor
.alpha
= 200;
618 drawView
->SetHighColor(outlineColor
);
620 drawView
->DrawString(fittingText
, loc
+ BPoint(1, 1));
623 drawView
->SetDrawingMode(B_OP_OVER
);
624 drawView
->SetHighColor(textColor
);
627 drawView
->DrawString(fittingText
, loc
);
629 if (fSymLink
&& (fAttrHash
== view
->FirstColumn()->AttrHash())) {
631 // this should be exported to the WidgetAttribute class, probably
632 // by having a per widget kind style
634 rgb_color underlineColor
= drawView
->HighColor();
635 underlineColor
.alpha
= 180;
636 drawView
->SetHighColor(underlineColor
);
637 drawView
->SetDrawingMode(B_OP_ALPHA
);
638 drawView
->SetBlendingMode(B_CONSTANT_ALPHA
, B_ALPHA_OVERLAY
);
641 textRect
.right
= textRect
.left
+ fText
->Width(view
);
642 // only underline text part
643 drawView
->StrokeLine(textRect
.LeftBottom(), textRect
.RightBottom(),