-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPlaceholderTextView.cpp
More file actions
204 lines (170 loc) · 4.95 KB
/
Copy pathPlaceholderTextView.cpp
File metadata and controls
204 lines (170 loc) · 4.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
* PlaceholderTextView.cpp - BTextView with placeholder text support
* Distributed under the terms of the MIT License.
*/
#include "PlaceholderTextView.h"
#include "SearchBarView.h"
#include <Window.h>
PlaceholderTextView::PlaceholderTextView(const char* name, uint32 flags)
:
BTextView(name, flags),
fPlaceholder(),
fTarget(),
fModificationMessage(NULL),
fInvokeMessage(NULL)
{
}
PlaceholderTextView::~PlaceholderTextView()
{
delete fModificationMessage;
delete fInvokeMessage;
}
void
PlaceholderTextView::Draw(BRect updateRect)
{
BTextView::Draw(updateRect);
_DrawPlaceholder();
}
void
PlaceholderTextView::MakeFocus(bool focus)
{
BTextView::MakeFocus(focus);
// Redraw to show/hide placeholder text. Also invalidate parent
// (SearchTextControl) so it redraws the focus ring around us.
Invalidate();
if (Parent())
Parent()->Invalidate();
}
void
PlaceholderTextView::KeyDown(const char* bytes, int32 numBytes)
{
if (numBytes == 1 && bytes[0] == B_ESCAPE) {
// Trigger the clear action on our parent SearchTextControl
SearchTextControl* control =
dynamic_cast<SearchTextControl*>(Parent());
if (control != NULL) {
control->TriggerClear();
// The clear action commits the empty state, so sync our
// tracker to avoid a redundant invoke on the next Tab.
fLastCommittedText.SetTo("");
}
return;
}
if (numBytes == 1 && bytes[0] == B_TAB) {
// Navigate to the next/previous tab stop within our SearchBarView
// ancestor instead of inserting a tab character.
// Commit the current field first (same as pressing Enter) so the
// search executes before focus moves away — but only if the text
// has actually changed since the last commit, otherwise the email
// list reloads unnecessarily and steals focus from the target widget.
BString currentText = Text();
if (currentText != fLastCommittedText
&& fInvokeMessage != NULL && fTarget.IsValid()) {
fLastCommittedText = currentText;
BMessage message(*fInvokeMessage);
fTarget.SendMessage(&message);
}
// Check for Shift modifier (reverse tab).
// Read from the window's current B_KEY_DOWN message rather than
// polling ::modifiers(), which can miss a fast Shift release —
// especially after the invoke message send above adds latency.
int32 mods = 0;
BWindow* window = Window();
if (window != NULL) {
BMessage* msg = window->CurrentMessage();
if (msg != NULL)
msg->FindInt32("modifiers", &mods);
}
bool forward = (mods & B_SHIFT_KEY) == 0;
// Walk up to the SearchBarView ancestor and use its tab order
BView* ancestor = Parent();
while (ancestor != NULL
&& strcmp(ancestor->Name(), "search_bar") != 0)
ancestor = ancestor->Parent();
SearchBarView* searchBar = dynamic_cast<SearchBarView*>(ancestor);
if (searchBar != NULL)
searchBar->FocusNextTabStop(this, forward);
return;
}
if (numBytes == 1 && bytes[0] == B_ENTER) {
// Send invoke message on Enter
if (fInvokeMessage != NULL && fTarget.IsValid()) {
fLastCommittedText = Text();
BMessage message(*fInvokeMessage);
fTarget.SendMessage(&message);
}
return; // Don't insert newline
}
BTextView::KeyDown(bytes, numBytes);
}
void
PlaceholderTextView::InsertText(const char* text, int32 length,
int32 offset, const text_run_array* runs)
{
BTextView::InsertText(text, length, offset, runs);
_SendModificationMessage();
Invalidate(); // Redraw for placeholder
}
void
PlaceholderTextView::DeleteText(int32 fromOffset, int32 toOffset)
{
BTextView::DeleteText(fromOffset, toOffset);
_SendModificationMessage();
Invalidate(); // Redraw for placeholder
}
void
PlaceholderTextView::SetPlaceholder(const char* placeholder)
{
fPlaceholder = placeholder;
Invalidate();
}
void
PlaceholderTextView::SetTarget(BMessenger messenger)
{
fTarget = messenger;
}
void
PlaceholderTextView::SetModificationMessage(BMessage* message)
{
delete fModificationMessage;
fModificationMessage = message;
}
void
PlaceholderTextView::SetInvokeMessage(BMessage* message)
{
delete fInvokeMessage;
fInvokeMessage = message;
}
void
PlaceholderTextView::_DrawPlaceholder()
{
// Only draw placeholder when empty
if (TextLength() > 0 || fPlaceholder.IsEmpty())
return;
// Get text rect and font metrics
BRect textRect = TextRect();
font_height fh;
GetFontHeight(&fh);
// If text rect looks wrong (too narrow), use bounds instead
// (can happen before layout is fully complete)
if (textRect.Width() < 10) {
textRect = Bounds();
textRect.InsetBy(2, 1);
}
// Calculate baseline position
float y = textRect.top + fh.ascent;
// Use disabled text color for placeholder
rgb_color viewColor = ViewColor();
rgb_color placeholderColor = tint_color(viewColor, B_DISABLED_LABEL_TINT);
// Draw placeholder text
SetHighColor(placeholderColor);
DrawString(fPlaceholder.String(), BPoint(textRect.left, y));
}
void
PlaceholderTextView::_SendModificationMessage()
{
if (fModificationMessage != NULL && fTarget.IsValid()) {
BMessage message(*fModificationMessage);
fTarget.SendMessage(&message);
}
}