OLD | NEW |
| (Empty) |
1 // Copyright 2006-2009 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 // | |
16 | |
17 // TODO(omaha): need to handle WM_GETTEXT | |
18 // TODO(omaha): need to handle WM_SIZE | |
19 // TODO(omaha): nice to have transparent mode | |
20 | |
21 #include "omaha/ui/uilib/static_ex.h" | |
22 | |
23 #include <shellapi.h> | |
24 #include <strsafe.h> | |
25 #include "omaha/ui/uilib/node_state.h" | |
26 | |
27 const int StaticEx::kBorderNone = 0; | |
28 const int StaticEx::kBorderLeft = 1; | |
29 const int StaticEx::kBorderTop = 2; | |
30 const int StaticEx::kBorderRight = 4; | |
31 const int StaticEx::kBorderBottom = 8; | |
32 const int StaticEx::kBorderAll = kBorderLeft | kBorderTop | kBorderRight | | |
33 kBorderBottom; | |
34 | |
35 HCURSOR StaticEx::hand_cursor_ = NULL; | |
36 | |
37 StaticEx::StaticEx() | |
38 : margins_(1, 0, 1, 0), // see comment in h file | |
39 background_color_(0xffffff), | |
40 use_background_color_(false), | |
41 ellipsis_(0), | |
42 border_(kBorderNone), | |
43 border_color_(0), | |
44 default_font_(NULL) { | |
45 } | |
46 | |
47 StaticEx::~StaticEx() { | |
48 EraseNodes(); | |
49 EraseLines(&lines_); | |
50 } | |
51 | |
52 void StaticEx::Reset() { | |
53 text_.Empty(); | |
54 EraseNodes(); | |
55 EraseLines(&lines_); | |
56 default_font_ = NULL; | |
57 } | |
58 | |
59 BOOL StaticEx::SubclassWindow(HWND window) { | |
60 Reset(); | |
61 // first get text from exising control | |
62 unsigned length = ::SendMessage(window, WM_GETTEXTLENGTH, 0, 0); | |
63 CString text; | |
64 if (length > 0) { | |
65 TCHAR* buffer = text.GetBufferSetLength(length); | |
66 ::SendMessage(window, | |
67 WM_GETTEXT, | |
68 length + 1, | |
69 reinterpret_cast<LPARAM>(buffer)); | |
70 text.ReleaseBuffer(-1); | |
71 } | |
72 | |
73 // then subclass | |
74 BOOL result = CWindowImpl<StaticEx>::SubclassWindow(window); | |
75 | |
76 // set text back (it will parse it and replace text in subclassed control | |
77 // with readble text) | |
78 if (result && length > 0) { | |
79 SetWindowText(text); | |
80 } | |
81 | |
82 return result; | |
83 } | |
84 | |
85 HWND StaticEx::UnsubclassWindow(BOOL force /*= FALSE*/) { | |
86 Reset(); // clean up an old state | |
87 return CWindowImpl<StaticEx>::UnsubclassWindow(force); | |
88 } | |
89 | |
90 LRESULT StaticEx::OnSetText(UINT msg, WPARAM wparam, LPARAM lparam, | |
91 BOOL& handled) { | |
92 // parse text first, because we will need to get "readable" text | |
93 text_ = reinterpret_cast<const TCHAR*>(lparam); | |
94 ParseText(); | |
95 | |
96 // set readable text to subclassed control, this text will be return by | |
97 // GetWindowText() or WM_GETTEXT. (when GetWindowText is called from another | |
98 // process it doesn't send WM_GETTEXT but reads text directly from contol) | |
99 // so we need to set text to it. | |
100 // Disable redraw, without it calling DefWindowProc would redraw control | |
101 // immediately without sending WM_PAINT message | |
102 SetRedraw(FALSE); | |
103 DefWindowProc(msg, wparam, | |
104 reinterpret_cast<LPARAM>(static_cast<const TCHAR*>(GetReadableText()))); | |
105 SetRedraw(TRUE); | |
106 | |
107 // now invalidate to display new text | |
108 Invalidate(); | |
109 | |
110 handled = TRUE; | |
111 return 1; | |
112 } | |
113 | |
114 LRESULT StaticEx::OnGetText(UINT, WPARAM wparam, LPARAM lparam, BOOL& handled) {
// NOLINT | |
115 if (!lparam) return 0; | |
116 unsigned size = static_cast<unsigned>(wparam); | |
117 TCHAR* buffer = reinterpret_cast<TCHAR*>(lparam); | |
118 | |
119 handled = TRUE; | |
120 unsigned my_size = text_.GetLength(); | |
121 if (my_size < size) { | |
122 StringCchCopy(buffer, size, text_); | |
123 return my_size; | |
124 } | |
125 | |
126 StringCchCopyN(buffer, size, text_, size - 1); | |
127 buffer[size - 1] = 0; | |
128 | |
129 return size - 1; | |
130 } | |
131 | |
132 LRESULT StaticEx::OnGetTextLength(UINT, WPARAM, LPARAM, BOOL& handled) { // NOL
INT | |
133 handled = TRUE; | |
134 return text_.GetLength(); | |
135 } | |
136 | |
137 void StaticEx::set_background_color(COLORREF back_color) { | |
138 background_color_ = back_color; | |
139 use_background_color_ = true; | |
140 Invalidate(); | |
141 } | |
142 | |
143 void StaticEx::set_margins(const RECT& rect) { | |
144 margins_ = rect; | |
145 Invalidate(); | |
146 } | |
147 | |
148 void StaticEx::set_margins(int left, int top, int right, int bottom) { | |
149 margins_.SetRect(left, top, right, bottom); | |
150 Invalidate(); | |
151 } | |
152 | |
153 | |
154 LRESULT StaticEx::OnLButtonUp(UINT, WPARAM, LPARAM lparam, BOOL&) { | |
155 CPoint point(LOWORD(lparam), HIWORD(lparam)); | |
156 | |
157 int height = margins_.top + (border_ & kBorderTop) ? 1 : 0; | |
158 size_t size = lines_.size(); | |
159 for (size_t i = 0; i < size; i++) { | |
160 height += lines_[i]->height(); | |
161 if (point.y < height) { | |
162 CString action; | |
163 if (lines_[i]->IsUrlUnderMouse(point, &action) && !action.IsEmpty()) { | |
164 // Notify the parent window to handle the click. | |
165 LRESULT handled = 0; | |
166 NMSTATICEX notification = {0}; | |
167 notification.header.hwndFrom = m_hWnd; | |
168 notification.header.idFrom = GetDlgCtrlID(); | |
169 notification.header.code = NM_STATICEX; | |
170 notification.action = action; | |
171 | |
172 HWND parent = GetParent(); | |
173 if (parent) { | |
174 handled = ::SendMessage(parent, WM_NOTIFY, notification.header.idFrom, | |
175 reinterpret_cast<LPARAM>(¬ification)); | |
176 ATLASSERT(handled); | |
177 } | |
178 } | |
179 break; | |
180 } | |
181 } | |
182 return 0; | |
183 } | |
184 | |
185 | |
186 LRESULT StaticEx::OnSetCursor(UINT, WPARAM, LPARAM lparam, BOOL& handled) { //
NOLINT | |
187 int hit_test = LOWORD(lparam); | |
188 handled = FALSE; | |
189 if (hit_test != HTCLIENT) { | |
190 return 0; | |
191 } | |
192 | |
193 POINT position; | |
194 if (!GetCursorPos(&position)) | |
195 return 0; | |
196 | |
197 ScreenToClient(&position); | |
198 | |
199 int offset = margins_.top + (border_ & kBorderTop) ? 1 : 0; | |
200 size_t size = lines_.size(); | |
201 for (size_t i = 0; i < size; i++) { | |
202 offset += lines_[i]->height(); | |
203 if (position.y < offset) { | |
204 if (lines_[i]->IsUrlUnderMouse(position, NULL)) { | |
205 ::SetCursor(GetHandCursor()); | |
206 handled = TRUE; | |
207 } | |
208 break; | |
209 } | |
210 } | |
211 | |
212 return 0; | |
213 } | |
214 | |
215 void StaticEx::set_ellipsis(int ellipsis) { | |
216 if (ellipsis == DT_END_ELLIPSIS || | |
217 ellipsis == DT_WORD_ELLIPSIS || | |
218 ellipsis == DT_PATH_ELLIPSIS || | |
219 ellipsis == 0) { | |
220 ellipsis_ = ellipsis; | |
221 if (ellipsis != 0) | |
222 ModifyStyle(0, SS_LEFTNOWORDWRAP); | |
223 Invalidate(); | |
224 } | |
225 } | |
226 | |
227 void StaticEx::set_border(int border) { | |
228 border_ = border; | |
229 Invalidate(); | |
230 } | |
231 | |
232 void StaticEx::set_border_color(COLORREF border_color) { | |
233 border_color_ = border_color; | |
234 Invalidate(); | |
235 } | |
236 | |
237 void StaticEx::ParseText() { | |
238 EraseNodes(); | |
239 EraseLines(&lines_); | |
240 | |
241 if (text_.IsEmpty()) | |
242 return; | |
243 | |
244 if (!default_font_) | |
245 default_font_ = GetFont(); | |
246 | |
247 NodeState node_state(m_hWnd); | |
248 node_state.SetStdFont(default_font_); | |
249 | |
250 const TCHAR* current_string = text_; | |
251 int current_offset = 0; | |
252 bool had_good_tag = true; | |
253 while (*current_string) { | |
254 current_offset = 0; | |
255 | |
256 if (had_good_tag) { | |
257 // if it was a good tag we consumed it and need to start with a new node | |
258 Node* node = new Node(m_hWnd); | |
259 nodes_.push_back(node); | |
260 | |
261 // -1 if there is no Open Bracket "<" | |
262 current_offset = FindOpenBracket(current_string); | |
263 | |
264 if (current_offset < 0) { | |
265 // no tags left, just plain text | |
266 node->AddText(current_string); | |
267 node->set_node_state(node_state); | |
268 break; | |
269 } | |
270 | |
271 if (*current_string != _T('<')) { | |
272 // has some text before the tag | |
273 node->AddText(CString(current_string, current_offset)); | |
274 node->set_node_state(node_state); | |
275 current_string += current_offset; | |
276 continue; | |
277 } | |
278 | |
279 int next_offset = node_state.ConsumeTag(current_string + current_offset); | |
280 | |
281 if (next_offset > 0) { | |
282 // it was a known tag | |
283 had_good_tag = true; | |
284 current_string += current_offset + next_offset; | |
285 } else { | |
286 // unknown tag, will keep looking | |
287 had_good_tag = false; | |
288 node->AddText(CString(current_string, current_offset + 1)); | |
289 node->set_node_state(node_state); | |
290 current_string += current_offset + 1; | |
291 continue; | |
292 } | |
293 } else { | |
294 had_good_tag = true; | |
295 } | |
296 delete nodes_.back(); | |
297 nodes_.pop_back(); | |
298 } | |
299 } | |
300 | |
301 CString StaticEx::GetReadableText() { | |
302 CString text; | |
303 for (size_t i = 0; i < nodes_.size(); i++) { | |
304 text += nodes_[i]->node_text(); | |
305 } | |
306 return text; | |
307 } | |
308 | |
309 void StaticEx::EraseNodes() { | |
310 for (size_t i = 0; i < nodes_.size(); ++i) { | |
311 delete nodes_[i]; | |
312 } | |
313 nodes_.clear(); | |
314 } | |
315 | |
316 void StaticEx::EraseLines(std::vector<StaticLine*>* lines) { | |
317 size_t size = lines->size(); | |
318 for (size_t i = 0; i < size; i++) { | |
319 delete (*lines)[i]; | |
320 } | |
321 lines->clear(); | |
322 } | |
323 | |
324 int StaticEx::FindOpenBracket(const TCHAR* string) { | |
325 const TCHAR* left_bracket = _tcschr(string, _T('<')); | |
326 | |
327 if (left_bracket == NULL) | |
328 return -1; | |
329 | |
330 return static_cast<int>(left_bracket - string); | |
331 } | |
332 | |
333 LRESULT StaticEx::OnPaint(UINT, WPARAM, LPARAM, BOOL&) { | |
334 PAINTSTRUCT paint_struct; | |
335 HDC hdc = BeginPaint(&paint_struct); | |
336 | |
337 CRect client_rect; | |
338 GetClientRect(&client_rect); | |
339 | |
340 CRect working_rect(client_rect); | |
341 | |
342 working_rect.DeflateRect(margins_); | |
343 working_rect.DeflateRect((border_ & kBorderLeft) ? 1 : 0, | |
344 (border_ & kBorderTop) ? 1 : 0, | |
345 (border_ & kBorderRight) ? 1 : 0, | |
346 (border_ & kBorderBottom) ? 1 : 0); | |
347 | |
348 DWORD style = GetStyle(); | |
349 | |
350 EraseLines(&lines_); | |
351 PrePaint(hdc, &lines_, nodes_, working_rect, style, ellipsis_); | |
352 | |
353 if (use_background_color_) { | |
354 FillRect(hdc, &client_rect, CreateSolidBrush(background_color_)); | |
355 } else { | |
356 HBRUSH brush = reinterpret_cast<HBRUSH>(::SendMessage(GetParent(), | |
357 WM_CTLCOLORSTATIC, reinterpret_cast<WPARAM>(hdc), | |
358 reinterpret_cast<LPARAM>(m_hWnd))); | |
359 if (brush) { | |
360 ::FillRect(hdc, &client_rect, brush); | |
361 } | |
362 } | |
363 | |
364 if (border_ != kBorderNone) | |
365 DrawBorder(hdc, client_rect); | |
366 | |
367 Paint(hdc, lines_, working_rect, style, ellipsis_); | |
368 | |
369 EndPaint(&paint_struct); | |
370 return 0; | |
371 } | |
372 | |
373 void StaticEx::PrePaint(HDC hdc, std::vector<StaticLine*>* lines, | |
374 const std::vector<Node*>& nodes, RECT rect, DWORD style, | |
375 int ellipsis) { | |
376 if (nodes.empty()) | |
377 return; | |
378 | |
379 int x = 0; | |
380 int width = rect.right - rect.left; | |
381 StaticLine* line = new StaticLine; | |
382 lines->push_back(line); | |
383 bool done = false; | |
384 size_t size = nodes.size(); | |
385 for (size_t i = 0; i < size; ++i) { | |
386 Node* node = nodes[i]; | |
387 const NodeState& node_state = node->node_state(); | |
388 CString text = node->node_text(); | |
389 int string_len = text.GetLength(); | |
390 | |
391 HFONT font = node_state.GetFont(); | |
392 if (!font) | |
393 return; | |
394 | |
395 HFONT old_font = static_cast<HFONT>(SelectObject(hdc, font)); | |
396 | |
397 TEXTMETRIC text_metrics; | |
398 GetTextMetrics(hdc, &text_metrics); | |
399 | |
400 int height = text_metrics.tmHeight + text_metrics.tmExternalLeading; | |
401 int base_line = text_metrics.tmHeight + text_metrics.tmExternalLeading - | |
402 text_metrics.tmDescent; | |
403 line->AdjustHeight(height); | |
404 line->AdjustBaseLine(base_line); | |
405 | |
406 bool single_line = (style & SS_LEFTNOWORDWRAP) != 0; | |
407 | |
408 int current_pos = 0; | |
409 bool more_left = false; | |
410 while (true) { | |
411 int current_length = string_len - current_pos; | |
412 | |
413 // find LF if any | |
414 int lf_position = text.Find(_T('\n'), current_pos); | |
415 if (lf_position == current_pos) { | |
416 if (single_line) { | |
417 if (ellipsis) | |
418 line->AddEllipses(); | |
419 break; | |
420 } | |
421 line = new StaticLine; | |
422 lines->push_back(line); | |
423 line->AdjustHeight(height); | |
424 line->AdjustBaseLine(base_line); | |
425 x = 0; | |
426 | |
427 current_pos++; | |
428 | |
429 continue; | |
430 } else if (lf_position > 0) { | |
431 current_length = lf_position - current_pos; | |
432 more_left = true; | |
433 } | |
434 | |
435 // check if it will fit in one line | |
436 int fit = 0; | |
437 SIZE string_size; | |
438 GetTextExtentExPoint(hdc, static_cast<const TCHAR*>(text) + current_pos, | |
439 current_length, width - x, &fit, NULL, &string_size); | |
440 | |
441 if (fit < current_length) { | |
442 // string doesn't fit, need to move to the next line | |
443 // find last space | |
444 int fit_saved = fit; | |
445 for (; fit > 0; fit--) { | |
446 if (text.GetAt(current_pos + fit) == _T(' ')) { | |
447 break; | |
448 } | |
449 } | |
450 | |
451 // if a first word of a node doesn't fit and it starts in a first half | |
452 // of control then wrap the word on a last char that fits | |
453 // otherwise move whole node to the next line | |
454 if ((fit <= 0) && (x < width / 2)) | |
455 fit = fit_saved; | |
456 | |
457 if (fit > 0) { | |
458 line->AddNode(node, current_pos, fit, height, base_line, width - x); | |
459 } | |
460 | |
461 if (single_line) { | |
462 if (ellipsis) | |
463 line->AddEllipses(); | |
464 done = true; | |
465 break; | |
466 } | |
467 line = new StaticLine; | |
468 lines->push_back(line); | |
469 line->AdjustHeight(height); | |
470 line->AdjustBaseLine(base_line); | |
471 x = 0; | |
472 | |
473 current_pos += fit; | |
474 // skip spaces | |
475 while (text.GetAt(current_pos) == _T(' ')) | |
476 current_pos++; | |
477 continue; | |
478 } else { | |
479 line->AddNode(node, current_pos, fit, height, base_line, | |
480 string_size.cx); | |
481 } | |
482 | |
483 // done, it fits | |
484 x += string_size.cx; | |
485 if (!more_left) | |
486 break; | |
487 | |
488 current_pos += current_length; | |
489 more_left = false; | |
490 } | |
491 | |
492 if (old_font) | |
493 SelectObject(hdc, old_font); | |
494 | |
495 if (done) | |
496 break; | |
497 } | |
498 } | |
499 | |
500 | |
501 void StaticEx::Paint(HDC hdc, const std::vector<StaticLine*>& lines, RECT rect, | |
502 DWORD style, int ellipsis) { | |
503 if ((style & SS_LEFTNOWORDWRAP) == 0) { | |
504 ellipsis = 0; | |
505 } | |
506 | |
507 size_t size = lines.size(); | |
508 int y = rect.top; | |
509 for (size_t i = 0; i < size; i++) { | |
510 int height = lines[i]->Paint(hdc, rect.left, rect.right, y, style, | |
511 ellipsis); | |
512 y += height; | |
513 } | |
514 } | |
515 | |
516 LRESULT StaticEx::OnEraseBkgnd(UINT /*msg*/, WPARAM /*wparam*/, | |
517 LPARAM /*lparam*/, BOOL& handled) { | |
518 handled = TRUE; | |
519 return 0; | |
520 } | |
521 | |
522 void StaticEx::DrawBorder(HDC hdc, const CRect& rect) { | |
523 HGDIOBJ old_object = SelectObject(hdc, GetStockObject(DC_PEN)); | |
524 SetDCPenColor(hdc, border_color_); | |
525 if (border_ & kBorderLeft) { | |
526 MoveToEx(hdc, rect.left, rect.top, NULL); | |
527 LineTo(hdc, rect.left, rect.bottom); | |
528 } | |
529 if (border_ & kBorderTop) { | |
530 MoveToEx(hdc, rect.left, rect.top, NULL); | |
531 LineTo(hdc, rect.right, rect.top); | |
532 } | |
533 if (border_ & kBorderRight) { | |
534 MoveToEx(hdc, rect.right - 1, rect.top, NULL); | |
535 LineTo(hdc, rect.right - 1, rect.bottom); | |
536 } | |
537 if (border_ & kBorderBottom) { | |
538 MoveToEx(hdc, rect.left, rect.bottom - 1, NULL); | |
539 LineTo(hdc, rect.right, rect.bottom - 1); | |
540 } | |
541 SelectObject(hdc, old_object); | |
542 } | |
543 | |
544 int StaticEx::GetMinimumHeight(int width) { | |
545 HDC device_context = CreateCompatibleDC(NULL); | |
546 | |
547 CRect client_rect; | |
548 if (width <= 0) | |
549 GetClientRect(&client_rect); | |
550 else | |
551 client_rect.SetRect(0, 0, width, 100); // last value is not used | |
552 | |
553 CRect working_rect(client_rect); | |
554 | |
555 working_rect.DeflateRect(margins_); | |
556 working_rect.DeflateRect((border_ & kBorderLeft) ? 1 : 0, | |
557 (border_ & kBorderTop) ? 1 : 0, | |
558 (border_ & kBorderRight) ? 1 : 0, | |
559 (border_ & kBorderBottom) ? 1 : 0); | |
560 | |
561 DWORD style = GetStyle(); | |
562 | |
563 std::vector<StaticLine*> lines; | |
564 PrePaint(device_context, &lines, nodes_, working_rect, style, ellipsis_); | |
565 | |
566 DeleteDC(device_context); | |
567 | |
568 int height = 0; | |
569 for (unsigned i = 0; i < lines.size(); i++) { | |
570 height += lines[i]->height(); | |
571 } | |
572 height += margins_.top + margins_.bottom; | |
573 height += (border_ & kBorderTop) ? 1 : 0; | |
574 height += (border_ & kBorderBottom) ? 1 : 0; | |
575 | |
576 return height; | |
577 } | |
578 | |
579 | |
580 HCURSOR StaticEx::GetHandCursor() { | |
581 if (hand_cursor_ == NULL) { | |
582 // Load cursor resource | |
583 hand_cursor_ = (HCURSOR)LoadCursor(NULL, IDC_HAND); // doesn't work on NT4! | |
584 } | |
585 return hand_cursor_; | |
586 } | |
OLD | NEW |