| 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 |