OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 "content/browser/renderer_host/render_widget_host_view_win.h" | 5 #include "content/browser/renderer_host/render_widget_host_view_win.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
10 #include "base/i18n/rtl.h" | 10 #include "base/i18n/rtl.h" |
(...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
283 result.windowX = result.x; | 283 result.windowX = result.x; |
284 result.windowY = result.y; | 284 result.windowY = result.y; |
285 // Note that we support diagonal scrolling. | 285 // Note that we support diagonal scrolling. |
286 result.deltaX = static_cast<float>(delta.x); | 286 result.deltaX = static_cast<float>(delta.x); |
287 result.wheelTicksX = WHEEL_DELTA; | 287 result.wheelTicksX = WHEEL_DELTA; |
288 result.deltaY = static_cast<float>(delta.y); | 288 result.deltaY = static_cast<float>(delta.y); |
289 result.wheelTicksY = WHEEL_DELTA; | 289 result.wheelTicksY = WHEEL_DELTA; |
290 return result; | 290 return result; |
291 } | 291 } |
292 | 292 |
293 static const int kTouchMask = 0x7; | |
294 | |
295 inline int GetTouchType(const TOUCHINPUT& point) { | |
ananta
2011/11/10 22:31:15
These functions can be moved to the new class?
| |
296 return point.dwFlags & kTouchMask; | |
297 } | |
298 | |
299 inline void SetTouchType(TOUCHINPUT* point, int type) { | |
300 point->dwFlags = (point->dwFlags & kTouchMask) | type; | |
301 } | |
302 | |
293 } // namespace | 303 } // namespace |
294 | 304 |
295 /////////////////////////////////////////////////////////////////////////////// | 305 /////////////////////////////////////////////////////////////////////////////// |
296 // RenderWidgetHostViewWin, public: | 306 // RenderWidgetHostViewWin, public: |
297 | 307 |
298 RenderWidgetHostViewWin::RenderWidgetHostViewWin(RenderWidgetHost* widget) | 308 RenderWidgetHostViewWin::RenderWidgetHostViewWin(RenderWidgetHost* widget) |
299 : render_widget_host_(widget), | 309 : render_widget_host_(widget), |
300 compositor_host_window_(NULL), | 310 compositor_host_window_(NULL), |
301 hide_compositor_window_at_next_paint_(false), | 311 hide_compositor_window_at_next_paint_(false), |
302 track_mouse_leave_(false), | 312 track_mouse_leave_(false), |
303 ime_notification_(false), | 313 ime_notification_(false), |
304 capture_enter_key_(false), | 314 capture_enter_key_(false), |
305 is_hidden_(false), | 315 is_hidden_(false), |
306 about_to_validate_and_paint_(false), | 316 about_to_validate_and_paint_(false), |
307 close_on_deactivate_(false), | 317 close_on_deactivate_(false), |
308 being_destroyed_(false), | 318 being_destroyed_(false), |
309 tooltip_hwnd_(NULL), | 319 tooltip_hwnd_(NULL), |
310 tooltip_showing_(false), | 320 tooltip_showing_(false), |
311 shutdown_factory_(this), | 321 shutdown_factory_(this), |
312 parent_hwnd_(NULL), | 322 parent_hwnd_(NULL), |
313 is_loading_(false), | 323 is_loading_(false), |
314 overlay_color_(0), | 324 overlay_color_(0), |
315 text_input_type_(ui::TEXT_INPUT_TYPE_NONE), | 325 text_input_type_(ui::TEXT_INPUT_TYPE_NONE), |
316 is_fullscreen_(false), | 326 is_fullscreen_(false), |
317 ignore_mouse_movement_(true), | 327 ignore_mouse_movement_(true), |
318 composition_range_(ui::Range::InvalidRange()), | 328 composition_range_(ui::Range::InvalidRange()), |
319 ignore_next_lbutton_message_at_same_location(false), | 329 ignore_next_lbutton_message_at_same_location(false), |
320 last_pointer_down_location_(0) { | 330 last_pointer_down_location_(0), |
331 touch_state_(this) { | |
321 render_widget_host_->SetView(this); | 332 render_widget_host_->SetView(this); |
322 registrar_.Add(this, | 333 registrar_.Add(this, |
323 content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, | 334 content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, |
324 content::NotificationService::AllBrowserContextsAndSources()); | 335 content::NotificationService::AllBrowserContextsAndSources()); |
325 } | 336 } |
326 | 337 |
327 RenderWidgetHostViewWin::~RenderWidgetHostViewWin() { | 338 RenderWidgetHostViewWin::~RenderWidgetHostViewWin() { |
328 UnlockMouse(); | 339 UnlockMouse(); |
329 ResetTooltip(); | 340 ResetTooltip(); |
330 } | 341 } |
(...skipping 541 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
872 | 883 |
873 LRESULT RenderWidgetHostViewWin::OnCreate(CREATESTRUCT* create_struct) { | 884 LRESULT RenderWidgetHostViewWin::OnCreate(CREATESTRUCT* create_struct) { |
874 // Call the WM_INPUTLANGCHANGE message handler to initialize the input locale | 885 // Call the WM_INPUTLANGCHANGE message handler to initialize the input locale |
875 // of a browser process. | 886 // of a browser process. |
876 OnInputLangChange(0, 0); | 887 OnInputLangChange(0, 0); |
877 // Marks that window as supporting mouse-wheel messages rerouting so it is | 888 // Marks that window as supporting mouse-wheel messages rerouting so it is |
878 // scrolled when under the mouse pointer even if inactive. | 889 // scrolled when under the mouse pointer even if inactive. |
879 props_.push_back(ui::SetWindowSupportsRerouteMouseWheel(m_hWnd)); | 890 props_.push_back(ui::SetWindowSupportsRerouteMouseWheel(m_hWnd)); |
880 | 891 |
881 if (base::win::GetVersion() >= base::win::VERSION_WIN7) { | 892 if (base::win::GetVersion() >= base::win::VERSION_WIN7) { |
882 // Single finger panning is consistent with other windows applications. | 893 // Use gestures if touch event switch isn't present or registration fails. |
883 const DWORD gesture_allow = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY | | 894 if (!CommandLine::ForCurrentProcess()->HasSwitch( |
884 GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; | 895 switches::kEnableTouchEvents) || |
885 const DWORD gesture_block = GC_PAN_WITH_GUTTER; | 896 !RegisterTouchWindow(m_hWnd, 0)) { |
886 GESTURECONFIG gc[] = { | 897 // Single finger panning is consistent with other windows applications. |
887 { GID_ZOOM, GC_ZOOM, 0 }, | 898 const DWORD gesture_allow = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY | |
888 { GID_PAN, gesture_allow , gesture_block}, | 899 GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; |
889 { GID_TWOFINGERTAP, GC_TWOFINGERTAP , 0}, | 900 const DWORD gesture_block = GC_PAN_WITH_GUTTER; |
890 { GID_PRESSANDTAP, GC_PRESSANDTAP , 0} | 901 GESTURECONFIG gc[] = { |
891 }; | 902 { GID_ZOOM, GC_ZOOM, 0 }, |
892 if (!SetGestureConfig(m_hWnd, 0, arraysize(gc), gc, sizeof(GESTURECONFIG))) | 903 { GID_PAN, gesture_allow , gesture_block}, |
893 { | 904 { GID_TWOFINGERTAP, GC_TWOFINGERTAP , 0}, |
894 NOTREACHED(); | 905 { GID_PRESSANDTAP, GC_PRESSANDTAP , 0} |
906 }; | |
907 if (!SetGestureConfig(m_hWnd, 0, arraysize(gc), gc, | |
908 sizeof(GESTURECONFIG))) { | |
909 NOTREACHED(); | |
910 } | |
895 } | 911 } |
896 } | 912 } |
897 | 913 |
898 return 0; | 914 return 0; |
899 } | 915 } |
900 | 916 |
901 void RenderWidgetHostViewWin::OnActivate(UINT action, BOOL minimized, | 917 void RenderWidgetHostViewWin::OnActivate(UINT action, BOOL minimized, |
902 HWND window) { | 918 HWND window) { |
903 // If the container is a popup, clicking elsewhere on screen should close the | 919 // If the container is a popup, clicking elsewhere on screen should close the |
904 // popup. | 920 // popup. |
(...skipping 15 matching lines...) Expand all Loading... | |
920 // handler for NPP_DestroyStream relies on. | 936 // handler for NPP_DestroyStream relies on. |
921 // | 937 // |
922 // The fix is to detach plugin windows from web contents when it is going | 938 // The fix is to detach plugin windows from web contents when it is going |
923 // away. This will prevent the plugin windows from getting destroyed | 939 // away. This will prevent the plugin windows from getting destroyed |
924 // automatically. The detached plugin windows will get cleaned up in proper | 940 // automatically. The detached plugin windows will get cleaned up in proper |
925 // sequence as part of the usual cleanup when the plugin instance goes away. | 941 // sequence as part of the usual cleanup when the plugin instance goes away. |
926 EnumChildWindows(m_hWnd, DetachPluginWindowsCallback, NULL); | 942 EnumChildWindows(m_hWnd, DetachPluginWindowsCallback, NULL); |
927 | 943 |
928 props_.reset(); | 944 props_.reset(); |
929 | 945 |
946 if (IsTouchWindow(m_hWnd, NULL)) | |
947 UnregisterTouchWindow(m_hWnd); | |
948 | |
930 CleanupCompositorWindow(); | 949 CleanupCompositorWindow(); |
931 | 950 |
932 ResetTooltip(); | 951 ResetTooltip(); |
933 TrackMouseLeave(false); | 952 TrackMouseLeave(false); |
934 } | 953 } |
935 | 954 |
936 void RenderWidgetHostViewWin::OnPaint(HDC unused_dc) { | 955 void RenderWidgetHostViewWin::OnPaint(HDC unused_dc) { |
937 if (!render_widget_host_) | 956 if (!render_widget_host_) |
938 return; | 957 return; |
939 | 958 |
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1095 return 0; | 1114 return 0; |
1096 } | 1115 } |
1097 | 1116 |
1098 void RenderWidgetHostViewWin::OnSetFocus(HWND window) { | 1117 void RenderWidgetHostViewWin::OnSetFocus(HWND window) { |
1099 if (browser_accessibility_manager_.get()) | 1118 if (browser_accessibility_manager_.get()) |
1100 browser_accessibility_manager_->GotFocus(); | 1119 browser_accessibility_manager_->GotFocus(); |
1101 if (render_widget_host_) { | 1120 if (render_widget_host_) { |
1102 render_widget_host_->GotFocus(); | 1121 render_widget_host_->GotFocus(); |
1103 render_widget_host_->SetActive(true); | 1122 render_widget_host_->SetActive(true); |
1104 } | 1123 } |
1124 if (touch_state_.ReleaseTouchPoints()) | |
1125 render_widget_host_->ForwardTouchEvent(touch_state_.touch_event()); | |
1105 } | 1126 } |
1106 | 1127 |
1107 void RenderWidgetHostViewWin::OnKillFocus(HWND window) { | 1128 void RenderWidgetHostViewWin::OnKillFocus(HWND window) { |
1108 if (render_widget_host_) { | 1129 if (render_widget_host_) { |
1109 render_widget_host_->SetActive(false); | 1130 render_widget_host_->SetActive(false); |
1110 render_widget_host_->Blur(); | 1131 render_widget_host_->Blur(); |
1111 } | 1132 } |
1133 if (touch_state_.ReleaseTouchPoints()) | |
1134 render_widget_host_->ForwardTouchEvent(touch_state_.touch_event()); | |
1112 } | 1135 } |
1113 | 1136 |
1114 void RenderWidgetHostViewWin::OnCaptureChanged(HWND window) { | 1137 void RenderWidgetHostViewWin::OnCaptureChanged(HWND window) { |
1115 if (render_widget_host_) | 1138 if (render_widget_host_) |
1116 render_widget_host_->LostCapture(); | 1139 render_widget_host_->LostCapture(); |
1117 } | 1140 } |
1118 | 1141 |
1119 void RenderWidgetHostViewWin::OnCancelMode() { | 1142 void RenderWidgetHostViewWin::OnCancelMode() { |
1120 if (render_widget_host_) | 1143 if (render_widget_host_) |
1121 render_widget_host_->LostCapture(); | 1144 render_widget_host_->LostCapture(); |
(...skipping 441 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1563 | 1586 |
1564 if (!handled_by_TabContents && render_widget_host_) { | 1587 if (!handled_by_TabContents && render_widget_host_) { |
1565 render_widget_host_->ForwardWheelEvent( | 1588 render_widget_host_->ForwardWheelEvent( |
1566 WebInputEventFactory::mouseWheelEvent(m_hWnd, message, wparam, | 1589 WebInputEventFactory::mouseWheelEvent(m_hWnd, message, wparam, |
1567 lparam)); | 1590 lparam)); |
1568 } | 1591 } |
1569 handled = TRUE; | 1592 handled = TRUE; |
1570 return 0; | 1593 return 0; |
1571 } | 1594 } |
1572 | 1595 |
1596 RenderWidgetHostViewWin::WebTouchState::WebTouchState(const CWindowImpl* window) | |
1597 : window_(window) { } | |
1598 | |
1599 size_t RenderWidgetHostViewWin::WebTouchState::UpdateTouchPoints( | |
1600 TOUCHINPUT* points, size_t count) { | |
1601 // First we reset all touch event state. This involves removing any released | |
1602 // touchpoints and marking the rest as stationary. After that we go through | |
1603 // and alter/add any touchpoints (from the touch input buffer) that we can | |
1604 // coalesce into a single message. The return value is the number of consumed | |
1605 // input message. | |
1606 WebKit::WebTouchPoint* point = touch_event_.touches; | |
1607 WebKit::WebTouchPoint* end = point + touch_event_.touchesLength; | |
1608 while (point < end) { | |
1609 if (point->state == WebKit::WebTouchPoint::StateReleased) { | |
1610 *point = *(--end); | |
1611 --touch_event_.touchesLength; | |
1612 } else { | |
1613 point->state = WebKit::WebTouchPoint::StateStationary; | |
1614 point++; | |
1615 } | |
1616 } | |
1617 touch_event_.changedTouchesLength = 0; | |
1618 | |
1619 // Consume all events of the same type and add them to the changed list. | |
1620 int last_type = 0; | |
1621 for (size_t i = 0; i < count; ++i) { | |
1622 if (points[i].dwID == 0ul) | |
1623 continue; | |
1624 | |
1625 WebKit::WebTouchPoint* point = NULL; | |
1626 for (unsigned j = 0; j < touch_event_.touchesLength; ++j) { | |
1627 if (static_cast<DWORD>(touch_event_.touches[j].id) == points[i].dwID) { | |
1628 point = &touch_event_.touches[j]; | |
1629 break; | |
1630 } | |
1631 } | |
1632 | |
1633 // Use a move instead if we see a down on a point we already have. | |
1634 int type = GetTouchType(points[i]); | |
1635 if (point && type == TOUCHEVENTF_DOWN) | |
1636 SetTouchType(&points[i], TOUCHEVENTF_MOVE); | |
1637 | |
1638 // Stop processing when the event type changes. | |
1639 if (touch_event_.changedTouchesLength && type != last_type) | |
1640 return i; | |
1641 | |
1642 last_type = type; | |
1643 switch (type) { | |
1644 case TOUCHEVENTF_DOWN: { | |
1645 if (!(point = AddTouchPoint(&points[i]))) | |
1646 continue; | |
1647 touch_event_.type = WebKit::WebInputEvent::TouchStart; | |
1648 break; | |
1649 } | |
1650 | |
1651 case TOUCHEVENTF_UP: { | |
1652 if (!point) // Just throw away a stray up. | |
1653 continue; | |
1654 point->state = WebKit::WebTouchPoint::StateReleased; | |
1655 UpdateTouchPoint(point, &points[i]); | |
1656 touch_event_.type = WebKit::WebInputEvent::TouchEnd; | |
1657 break; | |
1658 } | |
1659 | |
1660 case TOUCHEVENTF_MOVE: { | |
1661 if (point) { | |
1662 point->state = WebKit::WebTouchPoint::StateMoved; | |
1663 // Don't update the message if the point didn't really move. | |
1664 if (UpdateTouchPoint(point, &points[i])) | |
1665 continue; | |
1666 touch_event_.type = WebKit::WebInputEvent::TouchMove; | |
1667 } else if (touch_event_.changedTouchesLength) { | |
1668 // Can't add a point if we're already handling move events. | |
1669 return i; | |
1670 } else { | |
1671 // Treat a move with no existing point as a down. | |
1672 if (!(point = AddTouchPoint(&points[i]))) | |
1673 continue; | |
1674 last_type = TOUCHEVENTF_DOWN; | |
1675 SetTouchType(&points[i], TOUCHEVENTF_DOWN); | |
1676 touch_event_.type = WebKit::WebInputEvent::TouchStart; | |
1677 } | |
1678 break; | |
1679 } | |
1680 | |
1681 default: | |
1682 NOTREACHED(); | |
1683 continue; | |
1684 } | |
1685 touch_event_.changedTouches[touch_event_.changedTouchesLength++] = *point; | |
1686 } | |
1687 | |
1688 return count; | |
1689 } | |
1690 | |
1691 bool RenderWidgetHostViewWin::WebTouchState::ReleaseTouchPoints() { | |
1692 if (touch_event_.touchesLength == 0) | |
1693 return false; | |
1694 // Mark every active touchpoint as released. | |
1695 touch_event_.type = WebKit::WebInputEvent::TouchEnd; | |
1696 touch_event_.changedTouchesLength = touch_event_.touchesLength; | |
1697 for (unsigned int i = 0; i < touch_event_.touchesLength; ++i) { | |
1698 touch_event_.touches[i].state = WebKit::WebTouchPoint::StateReleased; | |
1699 touch_event_.changedTouches[i].state = | |
1700 WebKit::WebTouchPoint::StateReleased; | |
1701 } | |
1702 | |
1703 return true; | |
1704 } | |
1705 | |
1706 WebKit::WebTouchPoint* RenderWidgetHostViewWin::WebTouchState::AddTouchPoint( | |
1707 TOUCHINPUT* touch_input) { | |
1708 if (touch_event_.touchesLength >= WebKit::WebTouchEvent::touchesLengthCap) | |
1709 return NULL; | |
1710 WebKit::WebTouchPoint* point = | |
1711 &touch_event_.touches[touch_event_.touchesLength++]; | |
1712 point->state = WebKit::WebTouchPoint::StatePressed; | |
1713 point->id = touch_input->dwID; | |
1714 UpdateTouchPoint(point, touch_input); | |
1715 return point; | |
1716 } | |
1717 | |
1718 bool RenderWidgetHostViewWin::WebTouchState::UpdateTouchPoint( | |
1719 WebKit::WebTouchPoint* touch_point, | |
1720 TOUCHINPUT* touch_input) { | |
1721 CPoint coordinates(TOUCH_COORD_TO_PIXEL(touch_input->x), | |
1722 TOUCH_COORD_TO_PIXEL(touch_input->y)); | |
1723 int radius_x = 1; | |
1724 int radius_y = 1; | |
1725 if (touch_input->dwMask & TOUCHINPUTMASKF_CONTACTAREA) { | |
1726 radius_x = TOUCH_COORD_TO_PIXEL(touch_input->cxContact); | |
1727 radius_y = TOUCH_COORD_TO_PIXEL(touch_input->cyContact); | |
1728 } | |
1729 | |
1730 // Detect and exclude stationary moves. | |
1731 if (GetTouchType(*touch_input) == TOUCHEVENTF_MOVE && | |
1732 touch_point->screenPosition.x == coordinates.x && | |
1733 touch_point->screenPosition.y == coordinates.y && | |
1734 touch_point->radiusX == radius_x && | |
1735 touch_point->radiusY == radius_y) { | |
1736 touch_point->state = WebKit::WebTouchPoint::StateStationary; | |
1737 return true; | |
1738 } | |
1739 | |
1740 touch_point->screenPosition.x = coordinates.x; | |
1741 touch_point->screenPosition.y = coordinates.y; | |
1742 window_->GetParent().ScreenToClient(&coordinates); | |
1743 touch_point->position.x = coordinates.x; | |
1744 touch_point->position.y = coordinates.y; | |
1745 touch_point->radiusX = radius_x; | |
1746 touch_point->radiusY = radius_y; | |
1747 touch_point->force = 0; | |
1748 touch_point->rotationAngle = 0; | |
1749 return false; | |
1750 } | |
1751 | |
1752 LRESULT RenderWidgetHostViewWin::OnTouchEvent(UINT message, WPARAM wparam, | |
1753 LPARAM lparam, BOOL& handled) { | |
1754 // TODO(jschuh): Add support for an arbitrary number of touchpoints. | |
1755 size_t total = std::min(static_cast<int>(LOWORD(wparam)), | |
1756 static_cast<int>(WebKit::WebTouchEvent::touchesLengthCap)); | |
1757 TOUCHINPUT points[WebKit::WebTouchEvent::touchesLengthCap]; | |
1758 | |
1759 if (!total || !GetTouchInputInfo((HTOUCHINPUT)lparam, total, | |
1760 points, sizeof(TOUCHINPUT))) { | |
1761 return 0; | |
1762 } | |
1763 | |
1764 for (size_t start = 0; start < total;) { | |
1765 start += touch_state_.UpdateTouchPoints(points + start, total - start); | |
1766 if (touch_state_.is_changed()) | |
1767 render_widget_host_->ForwardTouchEvent(touch_state_.touch_event()); | |
1768 } | |
1769 | |
1770 CloseTouchInputHandle((HTOUCHINPUT)lparam); | |
1771 | |
1772 return 0; | |
1773 } | |
1774 | |
1573 LRESULT RenderWidgetHostViewWin::OnMouseActivate(UINT message, | 1775 LRESULT RenderWidgetHostViewWin::OnMouseActivate(UINT message, |
1574 WPARAM wparam, | 1776 WPARAM wparam, |
1575 LPARAM lparam, | 1777 LPARAM lparam, |
1576 BOOL& handled) { | 1778 BOOL& handled) { |
1577 if (!render_widget_host_) | 1779 if (!render_widget_host_) |
1578 return MA_NOACTIVATE; | 1780 return MA_NOACTIVATE; |
1579 | 1781 |
1580 if (!IsActivatable()) | 1782 if (!IsActivatable()) |
1581 return MA_NOACTIVATE; | 1783 return MA_NOACTIVATE; |
1582 | 1784 |
(...skipping 642 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2225 | 2427 |
2226 size_t offset = selection_range_.GetMin() - selection_text_offset_; | 2428 size_t offset = selection_range_.GetMin() - selection_text_offset_; |
2227 memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING), | 2429 memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING), |
2228 selection_text_.c_str() + offset, len * sizeof(WCHAR)); | 2430 selection_text_.c_str() + offset, len * sizeof(WCHAR)); |
2229 | 2431 |
2230 // According to Microsft API document, IMR_RECONVERTSTRING and | 2432 // According to Microsft API document, IMR_RECONVERTSTRING and |
2231 // IMR_DOCUMENTFEED should return reconv, but some applications return | 2433 // IMR_DOCUMENTFEED should return reconv, but some applications return |
2232 // need_size. | 2434 // need_size. |
2233 return reinterpret_cast<LRESULT>(reconv); | 2435 return reinterpret_cast<LRESULT>(reconv); |
2234 } | 2436 } |
OLD | NEW |