OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "views/controls/textfield/native_textfield_win.h" | 5 #include "views/controls/textfield/native_textfield_win.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "app/clipboard/clipboard.h" | 9 #include "app/clipboard/clipboard.h" |
10 #include "app/clipboard/scoped_clipboard_writer.h" | 10 #include "app/clipboard/scoped_clipboard_writer.h" |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
52 if (count == 0) { | 52 if (count == 0) { |
53 // We need to UpdateWindow() here instead of InvalidateRect() because, as | 53 // We need to UpdateWindow() here instead of InvalidateRect() because, as |
54 // far as I can tell, the edit likes to synchronously erase its background | 54 // far as I can tell, the edit likes to synchronously erase its background |
55 // when unfreezing, thus requiring us to synchronously redraw if we don't | 55 // when unfreezing, thus requiring us to synchronously redraw if we don't |
56 // want flicker. | 56 // want flicker. |
57 edit_->UpdateWindow(); | 57 edit_->UpdateWindow(); |
58 } | 58 } |
59 } | 59 } |
60 } | 60 } |
61 | 61 |
| 62 NativeTextfieldWin::ScopedSuspendUndo::ScopedSuspendUndo( |
| 63 ITextDocument* text_object_model) |
| 64 : text_object_model_(text_object_model) { |
| 65 // Suspend Undo processing. |
| 66 if (text_object_model_) |
| 67 text_object_model_->Undo(tomSuspend, NULL); |
| 68 } |
| 69 |
| 70 NativeTextfieldWin::ScopedSuspendUndo::~ScopedSuspendUndo() { |
| 71 // Resume Undo processing. |
| 72 if (text_object_model_) |
| 73 text_object_model_->Undo(tomResume, NULL); |
| 74 } |
| 75 |
62 /////////////////////////////////////////////////////////////////////////////// | 76 /////////////////////////////////////////////////////////////////////////////// |
63 // NativeTextfieldWin | 77 // NativeTextfieldWin |
64 | 78 |
65 bool NativeTextfieldWin::did_load_library_ = false; | 79 bool NativeTextfieldWin::did_load_library_ = false; |
66 | 80 |
67 NativeTextfieldWin::NativeTextfieldWin(Textfield* textfield) | 81 NativeTextfieldWin::NativeTextfieldWin(Textfield* textfield) |
68 : textfield_(textfield), | 82 : textfield_(textfield), |
69 tracking_double_click_(false), | 83 tracking_double_click_(false), |
70 double_click_time_(0), | 84 double_click_time_(0), |
71 can_discard_mousemove_(false), | 85 can_discard_mousemove_(false), |
(...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
307 ScopedFreeze freeze(this, GetTextObjectModel()); | 321 ScopedFreeze freeze(this, GetTextObjectModel()); |
308 OnBeforePossibleChange(); | 322 OnBeforePossibleChange(); |
309 switch (command_id) { | 323 switch (command_id) { |
310 case IDS_APP_UNDO: Undo(); break; | 324 case IDS_APP_UNDO: Undo(); break; |
311 case IDS_APP_CUT: Cut(); break; | 325 case IDS_APP_CUT: Cut(); break; |
312 case IDS_APP_COPY: Copy(); break; | 326 case IDS_APP_COPY: Copy(); break; |
313 case IDS_APP_PASTE: Paste(); break; | 327 case IDS_APP_PASTE: Paste(); break; |
314 case IDS_APP_SELECT_ALL: SelectAll(); break; | 328 case IDS_APP_SELECT_ALL: SelectAll(); break; |
315 default: NOTREACHED(); break; | 329 default: NOTREACHED(); break; |
316 } | 330 } |
317 OnAfterPossibleChange(); | 331 OnAfterPossibleChange(true); |
318 } | 332 } |
319 | 333 |
320 //////////////////////////////////////////////////////////////////////////////// | 334 //////////////////////////////////////////////////////////////////////////////// |
321 // NativeTextfieldWin, private: | 335 // NativeTextfieldWin, private: |
322 | 336 |
323 void NativeTextfieldWin::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { | 337 void NativeTextfieldWin::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { |
324 HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags); | 338 HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags); |
325 } | 339 } |
326 | 340 |
327 void NativeTextfieldWin::OnContextMenu(HWND window, const POINT& point) { | 341 void NativeTextfieldWin::OnContextMenu(HWND window, const POINT& point) { |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
408 | 422 |
409 const int composition_size = | 423 const int composition_size = |
410 ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); | 424 ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); |
411 if (composition_size >= 0) | 425 if (composition_size >= 0) |
412 ime_composition_length_ = composition_size / sizeof(wchar_t); | 426 ime_composition_length_ = composition_size / sizeof(wchar_t); |
413 | 427 |
414 ImmReleaseContext(m_hWnd, imm_context); | 428 ImmReleaseContext(m_hWnd, imm_context); |
415 } | 429 } |
416 } | 430 } |
417 | 431 |
418 OnAfterPossibleChange(); | 432 // If we allow OnAfterPossibleChange() to redraw the text, it will do this by |
| 433 // setting the edit's text directly, which can cancel the current IME |
| 434 // composition or cause other adverse affects. So we set |should_redraw_text| |
| 435 // to false. |
| 436 OnAfterPossibleChange(false); |
419 return result; | 437 return result; |
420 } | 438 } |
421 | 439 |
422 LRESULT NativeTextfieldWin::OnImeEndComposition(UINT message, | 440 LRESULT NativeTextfieldWin::OnImeEndComposition(UINT message, |
423 WPARAM wparam, | 441 WPARAM wparam, |
424 LPARAM lparam) { | 442 LPARAM lparam) { |
425 // Bug 11863: Korean IMEs send a WM_IME_ENDCOMPOSITION message without | 443 // Bug 11863: Korean IMEs send a WM_IME_ENDCOMPOSITION message without |
426 // sending any WM_IME_COMPOSITION messages when a user deletes all | 444 // sending any WM_IME_COMPOSITION messages when a user deletes all |
427 // composition characters, i.e. a composition string becomes empty. To handle | 445 // composition characters, i.e. a composition string becomes empty. To handle |
428 // this case, we need to update the find results when a composition is | 446 // this case, we need to update the find results when a composition is |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
466 | 484 |
467 case VK_DELETE: | 485 case VK_DELETE: |
468 case 'X': | 486 case 'X': |
469 if ((flags & KF_ALTDOWN) || | 487 if ((flags & KF_ALTDOWN) || |
470 (GetKeyState((key == 'X') ? VK_CONTROL : VK_SHIFT) >= 0)) | 488 (GetKeyState((key == 'X') ? VK_CONTROL : VK_SHIFT) >= 0)) |
471 break; | 489 break; |
472 if (GetKeyState((key == 'X') ? VK_SHIFT : VK_CONTROL) >= 0) { | 490 if (GetKeyState((key == 'X') ? VK_SHIFT : VK_CONTROL) >= 0) { |
473 ScopedFreeze freeze(this, GetTextObjectModel()); | 491 ScopedFreeze freeze(this, GetTextObjectModel()); |
474 OnBeforePossibleChange(); | 492 OnBeforePossibleChange(); |
475 Cut(); | 493 Cut(); |
476 OnAfterPossibleChange(); | 494 OnAfterPossibleChange(true); |
477 } | 495 } |
478 return; | 496 return; |
479 | 497 |
480 case 'C': | 498 case 'C': |
481 if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) | 499 if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) |
482 break; | 500 break; |
483 if (GetKeyState(VK_SHIFT) >= 0) | 501 if (GetKeyState(VK_SHIFT) >= 0) |
484 Copy(); | 502 Copy(); |
485 return; | 503 return; |
486 | 504 |
487 case VK_INSERT: | 505 case VK_INSERT: |
488 // Ignore insert by itself, so we don't turn overtype mode on/off. | 506 // Ignore insert by itself, so we don't turn overtype mode on/off. |
489 if (!(flags & KF_ALTDOWN) && (GetKeyState(VK_SHIFT) >= 0) && | 507 if (!(flags & KF_ALTDOWN) && (GetKeyState(VK_SHIFT) >= 0) && |
490 (GetKeyState(VK_CONTROL) >= 0)) | 508 (GetKeyState(VK_CONTROL) >= 0)) |
491 return; | 509 return; |
492 case 'V': | 510 case 'V': |
493 if ((flags & KF_ALTDOWN) || | 511 if ((flags & KF_ALTDOWN) || |
494 (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) | 512 (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) |
495 break; | 513 break; |
496 if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { | 514 if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { |
497 ScopedFreeze freeze(this, GetTextObjectModel()); | 515 ScopedFreeze freeze(this, GetTextObjectModel()); |
498 OnBeforePossibleChange(); | 516 OnBeforePossibleChange(); |
499 Paste(); | 517 Paste(); |
500 OnAfterPossibleChange(); | 518 OnAfterPossibleChange(true); |
501 } | 519 } |
502 return; | 520 return; |
503 | 521 |
504 case 0xbb: // Ctrl-'='. Triggers subscripting, even in plain text mode. | 522 case 0xbb: // Ctrl-'='. Triggers subscripting, even in plain text mode. |
505 // We don't use VK_OEM_PLUS in case the macro isn't defined. | 523 // We don't use VK_OEM_PLUS in case the macro isn't defined. |
506 // (e.g., we don't have this symbol in embeded environment). | 524 // (e.g., we don't have this symbol in embeded environment). |
507 return; | 525 return; |
508 | 526 |
509 case VK_PROCESSKEY: | 527 case VK_PROCESSKEY: |
510 // This key event is consumed by an IME. | 528 // This key event is consumed by an IME. |
(...skipping 10 matching lines...) Expand all Loading... |
521 void NativeTextfieldWin::OnLButtonDblClk(UINT keys, const CPoint& point) { | 539 void NativeTextfieldWin::OnLButtonDblClk(UINT keys, const CPoint& point) { |
522 // Save the double click info for later triple-click detection. | 540 // Save the double click info for later triple-click detection. |
523 tracking_double_click_ = true; | 541 tracking_double_click_ = true; |
524 double_click_point_ = point; | 542 double_click_point_ = point; |
525 double_click_time_ = GetCurrentMessage()->time; | 543 double_click_time_ = GetCurrentMessage()->time; |
526 | 544 |
527 ScopedFreeze freeze(this, GetTextObjectModel()); | 545 ScopedFreeze freeze(this, GetTextObjectModel()); |
528 OnBeforePossibleChange(); | 546 OnBeforePossibleChange(); |
529 DefWindowProc(WM_LBUTTONDBLCLK, keys, | 547 DefWindowProc(WM_LBUTTONDBLCLK, keys, |
530 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); | 548 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); |
531 OnAfterPossibleChange(); | 549 OnAfterPossibleChange(true); |
532 } | 550 } |
533 | 551 |
534 void NativeTextfieldWin::OnLButtonDown(UINT keys, const CPoint& point) { | 552 void NativeTextfieldWin::OnLButtonDown(UINT keys, const CPoint& point) { |
535 // Check for triple click, then reset tracker. Should be safe to subtract | 553 // Check for triple click, then reset tracker. Should be safe to subtract |
536 // double_click_time_ from the current message's time even if the timer has | 554 // double_click_time_ from the current message's time even if the timer has |
537 // wrapped in between. | 555 // wrapped in between. |
538 const bool is_triple_click = tracking_double_click_ && | 556 const bool is_triple_click = tracking_double_click_ && |
539 win_util::IsDoubleClick(double_click_point_, point, | 557 win_util::IsDoubleClick(double_click_point_, point, |
540 GetCurrentMessage()->time - double_click_time_); | 558 GetCurrentMessage()->time - double_click_time_); |
541 tracking_double_click_ = false; | 559 tracking_double_click_ = false; |
542 | 560 |
543 ScopedFreeze freeze(this, GetTextObjectModel()); | 561 ScopedFreeze freeze(this, GetTextObjectModel()); |
544 OnBeforePossibleChange(); | 562 OnBeforePossibleChange(); |
545 DefWindowProc(WM_LBUTTONDOWN, keys, | 563 DefWindowProc(WM_LBUTTONDOWN, keys, |
546 MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), | 564 MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), |
547 point.y)); | 565 point.y)); |
548 OnAfterPossibleChange(); | 566 OnAfterPossibleChange(true); |
549 } | 567 } |
550 | 568 |
551 void NativeTextfieldWin::OnLButtonUp(UINT keys, const CPoint& point) { | 569 void NativeTextfieldWin::OnLButtonUp(UINT keys, const CPoint& point) { |
552 ScopedFreeze freeze(this, GetTextObjectModel()); | 570 ScopedFreeze freeze(this, GetTextObjectModel()); |
553 OnBeforePossibleChange(); | 571 OnBeforePossibleChange(); |
554 DefWindowProc(WM_LBUTTONUP, keys, | 572 DefWindowProc(WM_LBUTTONUP, keys, |
555 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); | 573 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); |
556 OnAfterPossibleChange(); | 574 OnAfterPossibleChange(true); |
557 } | 575 } |
558 | 576 |
559 void NativeTextfieldWin::OnMouseLeave() { | 577 void NativeTextfieldWin::OnMouseLeave() { |
560 SetContainsMouse(false); | 578 SetContainsMouse(false); |
561 } | 579 } |
562 | 580 |
563 LRESULT NativeTextfieldWin::OnMouseWheel(UINT message, WPARAM w_param, | 581 LRESULT NativeTextfieldWin::OnMouseWheel(UINT message, WPARAM w_param, |
564 LPARAM l_param) { | 582 LPARAM l_param) { |
565 // Reroute the mouse-wheel to the window under the mouse pointer if | 583 // Reroute the mouse-wheel to the window under the mouse pointer if |
566 // applicable. | 584 // applicable. |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
612 // behaves strangely when the cursor is dragged vertically: if the cursor | 630 // behaves strangely when the cursor is dragged vertically: if the cursor |
613 // is in the middle of the text, drags inside the clip rect do nothing, | 631 // is in the middle of the text, drags inside the clip rect do nothing, |
614 // and drags outside the clip rect act as if the cursor jumped to the | 632 // and drags outside the clip rect act as if the cursor jumped to the |
615 // left edge of the text. When the cursor is at the right edge, drags of | 633 // left edge of the text. When the cursor is at the right edge, drags of |
616 // just a few pixels vertically end up selecting the "phantom newline"... | 634 // just a few pixels vertically end up selecting the "phantom newline"... |
617 // sometimes. | 635 // sometimes. |
618 RECT r; | 636 RECT r; |
619 GetRect(&r); | 637 GetRect(&r); |
620 DefWindowProc(WM_MOUSEMOVE, keys, | 638 DefWindowProc(WM_MOUSEMOVE, keys, |
621 MAKELPARAM(point.x, (r.bottom - r.top) / 2)); | 639 MAKELPARAM(point.x, (r.bottom - r.top) / 2)); |
622 OnAfterPossibleChange(); | 640 OnAfterPossibleChange(true); |
623 } | 641 } |
624 } | 642 } |
625 | 643 |
626 int NativeTextfieldWin::OnNCCalcSize(BOOL w_param, LPARAM l_param) { | 644 int NativeTextfieldWin::OnNCCalcSize(BOOL w_param, LPARAM l_param) { |
627 content_insets_.Set(0, 0, 0, 0); | 645 content_insets_.Set(0, 0, 0, 0); |
628 if (textfield_->draw_border()) | 646 if (textfield_->draw_border()) |
629 content_insets_ = CalculateInsets(); | 647 content_insets_ = CalculateInsets(); |
630 if (w_param) { | 648 if (w_param) { |
631 NCCALCSIZE_PARAMS* nc_params = | 649 NCCALCSIZE_PARAMS* nc_params = |
632 reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param); | 650 reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param); |
(...skipping 156 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
789 // layout of the first LTR character in input text. Such keyboard layout | 807 // layout of the first LTR character in input text. Such keyboard layout |
790 // change behavior is surprising and inconsistent with keyboard behavior | 808 // change behavior is surprising and inconsistent with keyboard behavior |
791 // elsewhere, so reset the layout in this case. | 809 // elsewhere, so reset the layout in this case. |
792 HKL layout = GetKeyboardLayout(0); | 810 HKL layout = GetKeyboardLayout(0); |
793 DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); | 811 DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); |
794 ActivateKeyboardLayout(layout, KLF_REORDER); | 812 ActivateKeyboardLayout(layout, KLF_REORDER); |
795 } else { | 813 } else { |
796 DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); | 814 DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); |
797 } | 815 } |
798 | 816 |
799 OnAfterPossibleChange(); | 817 // CRichEditCtrl automatically turns on IMF_AUTOKEYBOARD when the user |
| 818 // inputs an RTL character, making it difficult for the user to control |
| 819 // what language is set as they type. Force this off to make the edit's |
| 820 // behavior more stable. |
| 821 const int lang_options = SendMessage(EM_GETLANGOPTIONS, 0, 0); |
| 822 if (lang_options & IMF_AUTOKEYBOARD) |
| 823 SendMessage(EM_SETLANGOPTIONS, 0, lang_options & ~IMF_AUTOKEYBOARD); |
| 824 |
| 825 OnAfterPossibleChange(true); |
800 } | 826 } |
801 } | 827 } |
802 | 828 |
803 void NativeTextfieldWin::OnBeforePossibleChange() { | 829 void NativeTextfieldWin::OnBeforePossibleChange() { |
804 // Record our state. | 830 // Record our state. |
805 text_before_change_ = GetText(); | 831 text_before_change_ = GetText(); |
806 } | 832 } |
807 | 833 |
808 void NativeTextfieldWin::OnAfterPossibleChange() { | 834 void NativeTextfieldWin::OnAfterPossibleChange(bool should_redraw_text) { |
809 // Prevent the user from selecting the "phantom newline" at the end of the | 835 // Prevent the user from selecting the "phantom newline" at the end of the |
810 // edit. If they try, we just silently move the end of the selection back to | 836 // edit. If they try, we just silently move the end of the selection back to |
811 // the end of the real text. | 837 // the end of the real text. |
812 CHARRANGE new_sel; | 838 CHARRANGE new_sel; |
813 GetSel(new_sel); | 839 GetSel(new_sel); |
814 const int length = GetTextLength(); | 840 const int length = GetTextLength(); |
815 if (new_sel.cpMax > length) { | 841 if (new_sel.cpMax > length) { |
816 new_sel.cpMax = length; | 842 new_sel.cpMax = length; |
817 if (new_sel.cpMin > length) | 843 if (new_sel.cpMin > length) |
818 new_sel.cpMin = length; | 844 new_sel.cpMin = length; |
819 SetSel(new_sel); | 845 SetSel(new_sel); |
820 } | 846 } |
821 | 847 |
822 std::wstring new_text(GetText()); | 848 std::wstring new_text(GetText()); |
823 if (new_text != text_before_change_) { | 849 if (new_text != text_before_change_) { |
824 if (ime_discard_composition_ && ime_composition_start_ >= 0 && | 850 if (ime_discard_composition_ && ime_composition_start_ >= 0 && |
825 ime_composition_length_ > 0) { | 851 ime_composition_length_ > 0) { |
826 // A string retrieved with a GetText() call contains a string being | 852 // A string retrieved with a GetText() call contains a string being |
827 // composed by an IME. We remove the composition string from this search | 853 // composed by an IME. We remove the composition string from this search |
828 // string. | 854 // string. |
829 new_text.erase(ime_composition_start_, ime_composition_length_); | 855 new_text.erase(ime_composition_start_, ime_composition_length_); |
830 ime_composition_start_ = 0; | 856 ime_composition_start_ = 0; |
831 ime_composition_length_ = 0; | 857 ime_composition_length_ = 0; |
832 if (new_text.empty()) | 858 if (new_text.empty()) |
833 return; | 859 return; |
834 } | 860 } |
835 textfield_->SyncText(); | 861 textfield_->SyncText(); |
836 if (textfield_->GetController()) | 862 if (textfield_->GetController()) |
837 textfield_->GetController()->ContentsChanged(textfield_, new_text); | 863 textfield_->GetController()->ContentsChanged(textfield_, new_text); |
| 864 |
| 865 if (should_redraw_text) { |
| 866 CHARRANGE original_sel; |
| 867 GetSel(original_sel); |
| 868 std::wstring text = GetText(); |
| 869 ScopedSuspendUndo suspend_undo(GetTextObjectModel()); |
| 870 |
| 871 SelectAll(); |
| 872 ReplaceSel(reinterpret_cast<LPCTSTR>(text.c_str()), true); |
| 873 SetSel(original_sel); |
| 874 } |
838 } | 875 } |
839 } | 876 } |
840 | 877 |
841 LONG NativeTextfieldWin::ClipXCoordToVisibleText(LONG x, | 878 LONG NativeTextfieldWin::ClipXCoordToVisibleText(LONG x, |
842 bool is_triple_click) const { | 879 bool is_triple_click) const { |
843 // Clip the X coordinate to the left edge of the text. Careful: | 880 // Clip the X coordinate to the left edge of the text. Careful: |
844 // PosFromChar(0) may return a negative X coordinate if the beginning of the | 881 // PosFromChar(0) may return a negative X coordinate if the beginning of the |
845 // text has scrolled off the edit, so don't go past the clip rect's edge. | 882 // text has scrolled off the edit, so don't go past the clip rect's edge. |
846 PARAFORMAT2 pf2; | 883 PARAFORMAT2 pf2; |
847 GetParaFormat(pf2); | 884 GetParaFormat(pf2); |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
944 //////////////////////////////////////////////////////////////////////////////// | 981 //////////////////////////////////////////////////////////////////////////////// |
945 // NativeTextfieldWrapper, public: | 982 // NativeTextfieldWrapper, public: |
946 | 983 |
947 // static | 984 // static |
948 NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper( | 985 NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper( |
949 Textfield* field) { | 986 Textfield* field) { |
950 return new NativeTextfieldWin(field); | 987 return new NativeTextfieldWin(field); |
951 } | 988 } |
952 | 989 |
953 } // namespace views | 990 } // namespace views |
OLD | NEW |