OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "chrome/browser/ui/views/tabs/tab_drag_controller.h" | 5 #include "chrome/browser/ui/views/tabs/tab_drag_controller.h" |
6 | 6 |
7 #include <math.h> | 7 #include <math.h> |
8 #include <set> | 8 #include <set> |
9 | 9 |
10 #include "base/auto_reset.h" | 10 #include "base/auto_reset.h" |
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
203 #endif | 203 #endif |
204 } | 204 } |
205 | 205 |
206 void SetWindowPositionManaged(gfx::NativeWindow window, bool value) { | 206 void SetWindowPositionManaged(gfx::NativeWindow window, bool value) { |
207 #if defined(USE_ASH) | 207 #if defined(USE_ASH) |
208 ash::wm::GetWindowState(window)->set_window_position_managed(value); | 208 ash::wm::GetWindowState(window)->set_window_position_managed(value); |
209 #endif | 209 #endif |
210 } | 210 } |
211 | 211 |
212 // Returns true if |tab_strip| browser window is docked. | 212 // Returns true if |tab_strip| browser window is docked. |
213 bool IsDocked(const TabStrip* tab_strip) { | 213 bool IsDockedOrSnapped(const TabStrip* tab_strip) { |
214 #if defined(USE_ASH) | 214 #if defined(USE_ASH) |
215 DCHECK(tab_strip); | 215 DCHECK(tab_strip); |
216 return ash::wm::GetWindowState( | 216 ash::wm::WindowState* window_state = |
217 tab_strip->GetWidget()->GetNativeWindow())->IsDocked(); | 217 ash::wm::GetWindowState(tab_strip->GetWidget()->GetNativeWindow()); |
218 return window_state->IsDocked() || | |
219 window_state->window_show_type() == ash::wm::SHOW_TYPE_LEFT_SNAPPED || | |
220 window_state->window_show_type() == ash::wm::SHOW_TYPE_RIGHT_SNAPPED; | |
218 #endif | 221 #endif |
219 return false; | 222 return false; |
220 } | 223 } |
221 | 224 |
222 // Returns true if |bounds| contains the y-coordinate |y|. The y-coordinate | 225 // Returns true if |bounds| contains the y-coordinate |y|. The y-coordinate |
223 // of |bounds| is adjusted by |vertical_adjustment|. | 226 // of |bounds| is adjusted by |vertical_adjustment|. |
224 bool DoesRectContainVerticalPointExpanded( | 227 bool DoesRectContainVerticalPointExpanded( |
225 const gfx::Rect& bounds, | 228 const gfx::Rect& bounds, |
226 int vertical_adjustment, | 229 int vertical_adjustment, |
227 int y) { | 230 int y) { |
(...skipping 311 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
539 { | 542 { |
540 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); | 543 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); |
541 SaveFocus(); | 544 SaveFocus(); |
542 if (!ref) | 545 if (!ref) |
543 return; | 546 return; |
544 } | 547 } |
545 started_drag_ = true; | 548 started_drag_ = true; |
546 Attach(source_tabstrip_, gfx::Point()); | 549 Attach(source_tabstrip_, gfx::Point()); |
547 if (detach_into_browser_ && static_cast<int>(drag_data_.size()) == | 550 if (detach_into_browser_ && static_cast<int>(drag_data_.size()) == |
548 GetModel(source_tabstrip_)->count()) { | 551 GetModel(source_tabstrip_)->count()) { |
552 #if defined(USE_ASH) | |
553 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH && | |
varkha
2013/11/27 19:54:45
This behavior is now ash-only - including ash on W
| |
554 (was_source_maximized_ || was_source_fullscreen_)) { | |
555 // When all tabs in a maximized browser are dragged the browser gets | |
556 // restored during the drag and maximized back when the drag ends. | |
557 views::Widget* widget = GetAttachedBrowserWidget(); | |
558 const int last_tabstrip_width = attached_tabstrip_->tab_area_width(); | |
559 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs(); | |
560 OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds); | |
561 gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source_tabstrip_, | |
562 point_in_screen, | |
563 &drag_bounds)); | |
564 new_bounds.Offset(-widget->GetRestoredBounds().x() + | |
565 point_in_screen.x() - | |
566 mouse_offset_.x(), 0); | |
567 widget->SetVisibilityChangedAnimationsEnabled(false); | |
568 widget->Restore(); | |
569 widget->SetBounds(new_bounds); | |
570 AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width, | |
571 point_in_screen, | |
572 &drag_bounds); | |
573 widget->SetVisibilityChangedAnimationsEnabled(true); | |
574 } | |
575 #endif | |
549 RunMoveLoop(GetWindowOffset(point_in_screen)); | 576 RunMoveLoop(GetWindowOffset(point_in_screen)); |
550 return; | 577 return; |
551 } | 578 } |
552 } | 579 } |
553 | 580 |
554 ContinueDragging(point_in_screen); | 581 ContinueDragging(point_in_screen); |
555 } | 582 } |
556 | 583 |
557 void TabDragController::EndDrag(EndDragReason reason) { | 584 void TabDragController::EndDrag(EndDragReason reason) { |
558 TRACE_EVENT0("views", "TabDragController::EndDrag"); | 585 TRACE_EVENT0("views", "TabDragController::EndDrag"); |
(...skipping 910 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1469 // gesture sequence and the GR's touch events queue to the new window. This | 1496 // gesture sequence and the GR's touch events queue to the new window. This |
1470 // should really be done somewhere in capture change code and or inside the | 1497 // should really be done somewhere in capture change code and or inside the |
1471 // GR. But we currently do not have a consistent way for doing it that would | 1498 // GR. But we currently do not have a consistent way for doing it that would |
1472 // work in all cases. Hence this hack. | 1499 // work in all cases. Hence this hack. |
1473 ui::GestureRecognizer::Get()->TransferEventsTo( | 1500 ui::GestureRecognizer::Get()->TransferEventsTo( |
1474 attached_native_view, | 1501 attached_native_view, |
1475 dragged_widget->GetNativeView()); | 1502 dragged_widget->GetNativeView()); |
1476 #endif | 1503 #endif |
1477 dragged_widget->SetVisibilityChangedAnimationsEnabled(false); | 1504 dragged_widget->SetVisibilityChangedAnimationsEnabled(false); |
1478 Attach(dragged_browser_view->tabstrip(), gfx::Point()); | 1505 Attach(dragged_browser_view->tabstrip(), gfx::Point()); |
1479 attached_tabstrip_->InvalidateLayout(); | 1506 AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width, |
1480 dragged_widget->non_client_view()->Layout(); | 1507 point_in_screen, |
1481 const int dragged_tabstrip_width = attached_tabstrip_->tab_area_width(); | 1508 &drag_bounds); |
1482 | |
1483 // If the new tabstrip is smaller than the old resize the tabs. | |
1484 if (dragged_tabstrip_width < last_tabstrip_width) { | |
1485 const float leading_ratio = | |
1486 drag_bounds.front().x() / static_cast<float>(last_tabstrip_width); | |
1487 drag_bounds = CalculateBoundsForDraggedTabs(); | |
1488 | |
1489 if (drag_bounds.back().right() < dragged_tabstrip_width) { | |
1490 const int delta_x = | |
1491 std::min(static_cast<int>(leading_ratio * dragged_tabstrip_width), | |
1492 dragged_tabstrip_width - | |
1493 (drag_bounds.back().right() - | |
1494 drag_bounds.front().x())); | |
1495 OffsetX(delta_x, &drag_bounds); | |
1496 } | |
1497 | |
1498 // Reposition the restored window such that the tab that was dragged remains | |
1499 // under the mouse cursor. | |
1500 gfx::Point offset( | |
1501 static_cast<int>(drag_bounds[source_tab_index_].width() * | |
1502 offset_to_width_ratio_) + | |
1503 drag_bounds[source_tab_index_].x(), 0); | |
1504 views::View::ConvertPointToWidget(attached_tabstrip_, &offset); | |
1505 gfx::Rect new_bounds = browser->window()->GetBounds(); | |
1506 new_bounds.set_x(point_in_screen.x() - offset.x()); | |
1507 browser->window()->SetBounds(new_bounds); | |
1508 } | |
1509 | |
1510 attached_tabstrip_->SetTabBoundsForDrag(drag_bounds); | |
1511 | |
1512 WindowPositionManagedUpdater updater; | 1509 WindowPositionManagedUpdater updater; |
1513 dragged_widget->AddObserver(&updater); | 1510 dragged_widget->AddObserver(&updater); |
1514 browser->window()->Show(); | 1511 browser->window()->Show(); |
1515 dragged_widget->RemoveObserver(&updater); | 1512 dragged_widget->RemoveObserver(&updater); |
1516 dragged_widget->SetVisibilityChangedAnimationsEnabled(true); | 1513 dragged_widget->SetVisibilityChangedAnimationsEnabled(true); |
1517 // Activate may trigger a focus loss, destroying us. | 1514 // Activate may trigger a focus loss, destroying us. |
1518 { | 1515 { |
1519 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); | 1516 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); |
1520 browser->window()->Activate(); | 1517 browser->window()->Activate(); |
1521 if (!ref) | 1518 if (!ref) |
(...skipping 405 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1927 GetModel(source_tabstrip_)->InsertWebContentsAt( | 1924 GetModel(source_tabstrip_)->InsertWebContentsAt( |
1928 data->source_model_index, data->contents, | 1925 data->source_model_index, data->contents, |
1929 (data->pinned ? TabStripModel::ADD_PINNED : 0)); | 1926 (data->pinned ? TabStripModel::ADD_PINNED : 0)); |
1930 } | 1927 } |
1931 } | 1928 } |
1932 | 1929 |
1933 void TabDragController::CompleteDrag() { | 1930 void TabDragController::CompleteDrag() { |
1934 DCHECK(started_drag_); | 1931 DCHECK(started_drag_); |
1935 | 1932 |
1936 if (attached_tabstrip_) { | 1933 if (attached_tabstrip_) { |
1937 if (is_dragging_new_browser_) { | 1934 if (IsDockedOrSnapped(attached_tabstrip_)) { |
1938 if (IsDocked(attached_tabstrip_)) { | 1935 DCHECK_EQ(host_desktop_type_, chrome::HOST_DESKTOP_TYPE_ASH); |
1939 DCHECK_EQ(host_desktop_type_, chrome::HOST_DESKTOP_TYPE_ASH); | 1936 was_source_maximized_ = false; |
1940 was_source_maximized_ = false; | 1937 was_source_fullscreen_ = false; |
1941 was_source_fullscreen_ = false; | 1938 } |
1942 } | 1939 // If source window was maximized - maximize the new window as well. |
1943 // If source window was maximized - maximize the new window as well. | 1940 if (was_source_maximized_ || was_source_fullscreen_) |
1944 if (was_source_maximized_) | 1941 GetAttachedBrowserWidget()->Maximize(); |
1945 attached_tabstrip_->GetWidget()->Maximize(); | |
1946 #if defined(USE_ASH) | 1942 #if defined(USE_ASH) |
1947 if (was_source_fullscreen_ && | 1943 if (was_source_fullscreen_ && |
1948 host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) { | 1944 host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) { |
1949 // In fullscreen mode it is only possible to get here if the source | 1945 // In fullscreen mode it is only possible to get here if the source |
1950 // was in "immersive fullscreen" mode, so toggle it back on. | 1946 // was in "immersive fullscreen" mode, so toggle it back on. |
1951 ash::accelerators::ToggleFullscreen(); | 1947 ash::accelerators::ToggleFullscreen(); |
1952 } | 1948 } |
1953 #endif | 1949 #endif |
1954 } else { | |
1955 // When dragging results in maximized or fullscreen browser window getting | |
1956 // docked, restore it. | |
1957 if ((was_source_fullscreen_ || was_source_maximized_) && | |
1958 (IsDocked(attached_tabstrip_))) { | |
1959 DCHECK_EQ(host_desktop_type_, chrome::HOST_DESKTOP_TYPE_ASH); | |
1960 attached_tabstrip_->GetWidget()->Restore(); | |
1961 } | |
1962 } | |
1963 attached_tabstrip_->StoppedDraggingTabs( | 1950 attached_tabstrip_->StoppedDraggingTabs( |
1964 GetTabsMatchingDraggedContents(attached_tabstrip_), | 1951 GetTabsMatchingDraggedContents(attached_tabstrip_), |
1965 initial_tab_positions_, | 1952 initial_tab_positions_, |
1966 move_behavior_ == MOVE_VISIBILE_TABS, | 1953 move_behavior_ == MOVE_VISIBILE_TABS, |
1967 true); | 1954 true); |
1968 } else { | 1955 } else { |
1969 if (dock_info_.type() != DockInfo::NONE) { | 1956 if (dock_info_.type() != DockInfo::NONE) { |
1970 switch (dock_info_.type()) { | 1957 switch (dock_info_.type()) { |
1971 case DockInfo::LEFT_OF_WINDOW: | 1958 case DockInfo::LEFT_OF_WINDOW: |
1972 content::RecordAction(UserMetricsAction("DockingWindow_Left")); | 1959 content::RecordAction(UserMetricsAction("DockingWindow_Left")); |
(...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2241 bool TabDragController::AreTabsConsecutive() { | 2228 bool TabDragController::AreTabsConsecutive() { |
2242 for (size_t i = 1; i < drag_data_.size(); ++i) { | 2229 for (size_t i = 1; i < drag_data_.size(); ++i) { |
2243 if (drag_data_[i - 1].source_model_index + 1 != | 2230 if (drag_data_[i - 1].source_model_index + 1 != |
2244 drag_data_[i].source_model_index) { | 2231 drag_data_[i].source_model_index) { |
2245 return false; | 2232 return false; |
2246 } | 2233 } |
2247 } | 2234 } |
2248 return true; | 2235 return true; |
2249 } | 2236 } |
2250 | 2237 |
2251 Browser* TabDragController::CreateBrowserForDrag( | 2238 gfx::Rect TabDragController::CalculateDraggedBrowserBounds( |
2252 TabStrip* source, | 2239 TabStrip* source, |
2253 const gfx::Point& point_in_screen, | 2240 const gfx::Point& point_in_screen, |
2254 gfx::Vector2d* drag_offset, | |
2255 std::vector<gfx::Rect>* drag_bounds) { | 2241 std::vector<gfx::Rect>* drag_bounds) { |
2256 gfx::Point center(0, source->height() / 2); | 2242 gfx::Point center(0, source->height() / 2); |
2257 views::View::ConvertPointToWidget(source, ¢er); | 2243 views::View::ConvertPointToWidget(source, ¢er); |
2258 gfx::Rect new_bounds(source->GetWidget()->GetRestoredBounds()); | 2244 gfx::Rect new_bounds(source->GetWidget()->GetRestoredBounds()); |
2259 if (source->GetWidget()->IsMaximized()) { | 2245 if (source->GetWidget()->IsMaximized()) { |
2260 // If the restore bounds is really small, we don't want to honor it | 2246 // If the restore bounds is really small, we don't want to honor it |
2261 // (dragging a really small window looks wrong), instead make sure the new | 2247 // (dragging a really small window looks wrong), instead make sure the new |
2262 // window is at least 50% the size of the old. | 2248 // window is at least 50% the size of the old. |
2263 const gfx::Size max_size( | 2249 const gfx::Size max_size( |
2264 source->GetWidget()->GetWindowBoundsInScreen().size()); | 2250 source->GetWidget()->GetWindowBoundsInScreen().size()); |
2265 new_bounds.set_width( | 2251 new_bounds.set_width( |
2266 std::max(max_size.width() / 2, new_bounds.width())); | 2252 std::max(max_size.width() / 2, new_bounds.width())); |
2267 new_bounds.set_height( | 2253 new_bounds.set_height( |
2268 std::max(max_size.height() / 2, new_bounds.width())); | 2254 std::max(max_size.height() / 2, new_bounds.height())); |
varkha
2013/11/27 19:54:45
This looked like copy and paste bug. Am I correct
sky
2013/12/02 15:51:39
Ya, looks like a copy/paste error.
| |
2269 } | 2255 } |
2270 new_bounds.set_y(point_in_screen.y() - center.y()); | 2256 new_bounds.set_y(point_in_screen.y() - center.y()); |
2271 switch (GetDetachPosition(point_in_screen)) { | 2257 switch (GetDetachPosition(point_in_screen)) { |
2272 case DETACH_BEFORE: | 2258 case DETACH_BEFORE: |
2273 new_bounds.set_x(point_in_screen.x() - center.x()); | 2259 new_bounds.set_x(point_in_screen.x() - center.x()); |
2274 new_bounds.Offset(-mouse_offset_.x(), 0); | 2260 new_bounds.Offset(-mouse_offset_.x(), 0); |
2275 break; | 2261 break; |
2276 case DETACH_AFTER: { | 2262 case DETACH_AFTER: { |
2277 gfx::Point right_edge(source->width(), 0); | 2263 gfx::Point right_edge(source->width(), 0); |
2278 views::View::ConvertPointToWidget(source, &right_edge); | 2264 views::View::ConvertPointToWidget(source, &right_edge); |
2279 new_bounds.set_x(point_in_screen.x() - right_edge.x()); | 2265 new_bounds.set_x(point_in_screen.x() - right_edge.x()); |
2280 new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0); | 2266 new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0); |
2281 OffsetX(-(*drag_bounds)[0].x(), drag_bounds); | 2267 OffsetX(-(*drag_bounds)[0].x(), drag_bounds); |
2282 break; | 2268 break; |
2283 } | 2269 } |
2284 default: | 2270 default: |
2285 break; // Nothing to do for DETACH_ABOVE_OR_BELOW. | 2271 break; // Nothing to do for DETACH_ABOVE_OR_BELOW. |
2286 } | 2272 } |
2287 | 2273 |
2288 // To account for the extra vertical on restored windows that is absent on | 2274 // To account for the extra vertical on restored windows that is absent on |
2289 // maximized windows, add an additional vertical offset extracted from the tab | 2275 // maximized windows, add an additional vertical offset extracted from the tab |
2290 // strip. | 2276 // strip. |
2291 if (source->GetWidget()->IsMaximized()) | 2277 if (source->GetWidget()->IsMaximized()) |
2292 new_bounds.Offset(0, -source->button_v_offset()); | 2278 new_bounds.Offset(0, -source->button_v_offset()); |
2279 return new_bounds; | |
2280 } | |
2293 | 2281 |
2282 void TabDragController::AdjustBrowserAndTabBoundsForDrag( | |
2283 int last_tabstrip_width, | |
2284 const gfx::Point& point_in_screen, | |
2285 std::vector<gfx::Rect>* drag_bounds) { | |
2286 attached_tabstrip_->InvalidateLayout(); | |
2287 attached_tabstrip_->DoLayout(); | |
2288 const int dragged_tabstrip_width = attached_tabstrip_->tab_area_width(); | |
2289 | |
2290 // If the new tabstrip is smaller than the old resize the tabs. | |
2291 if (dragged_tabstrip_width < last_tabstrip_width) { | |
2292 const float leading_ratio = | |
2293 drag_bounds->front().x() / static_cast<float>(last_tabstrip_width); | |
2294 *drag_bounds = CalculateBoundsForDraggedTabs(); | |
2295 | |
2296 if (drag_bounds->back().right() < dragged_tabstrip_width) { | |
2297 const int delta_x = | |
2298 std::min(static_cast<int>(leading_ratio * dragged_tabstrip_width), | |
2299 dragged_tabstrip_width - | |
2300 (drag_bounds->back().right() - | |
2301 drag_bounds->front().x())); | |
2302 OffsetX(delta_x, drag_bounds); | |
2303 } | |
2304 | |
2305 // Reposition the restored window such that the tab that was dragged remains | |
2306 // under the mouse cursor. | |
2307 gfx::Point offset( | |
2308 static_cast<int>((*drag_bounds)[source_tab_index_].width() * | |
2309 offset_to_width_ratio_) + | |
2310 (*drag_bounds)[source_tab_index_].x(), 0); | |
2311 views::View::ConvertPointToWidget(attached_tabstrip_, &offset); | |
2312 gfx::Rect bounds = GetAttachedBrowserWidget()->GetWindowBoundsInScreen(); | |
2313 bounds.set_x(point_in_screen.x() - offset.x()); | |
2314 GetAttachedBrowserWidget()->SetBounds(bounds); | |
2315 } | |
2316 attached_tabstrip_->SetTabBoundsForDrag(*drag_bounds); | |
2317 } | |
2318 | |
2319 Browser* TabDragController::CreateBrowserForDrag( | |
2320 TabStrip* source, | |
2321 const gfx::Point& point_in_screen, | |
2322 gfx::Vector2d* drag_offset, | |
2323 std::vector<gfx::Rect>* drag_bounds) { | |
2324 gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source, | |
2325 point_in_screen, | |
2326 drag_bounds)); | |
2294 *drag_offset = point_in_screen - new_bounds.origin(); | 2327 *drag_offset = point_in_screen - new_bounds.origin(); |
2295 | 2328 |
2296 Profile* profile = | 2329 Profile* profile = |
2297 Profile::FromBrowserContext(drag_data_[0].contents->GetBrowserContext()); | 2330 Profile::FromBrowserContext(drag_data_[0].contents->GetBrowserContext()); |
2298 Browser::CreateParams create_params(Browser::TYPE_TABBED, | 2331 Browser::CreateParams create_params(Browser::TYPE_TABBED, |
2299 profile, | 2332 profile, |
2300 host_desktop_type_); | 2333 host_desktop_type_); |
2301 create_params.initial_bounds = new_bounds; | 2334 create_params.initial_bounds = new_bounds; |
2302 Browser* browser = new Browser(create_params); | 2335 Browser* browser = new Browser(create_params); |
2303 is_dragging_new_browser_ = true; | 2336 is_dragging_new_browser_ = true; |
(...skipping 29 matching lines...) Expand all Loading... | |
2333 gfx::Vector2d TabDragController::GetWindowOffset( | 2366 gfx::Vector2d TabDragController::GetWindowOffset( |
2334 const gfx::Point& point_in_screen) { | 2367 const gfx::Point& point_in_screen) { |
2335 TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ? | 2368 TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ? |
2336 attached_tabstrip_ : source_tabstrip_; | 2369 attached_tabstrip_ : source_tabstrip_; |
2337 views::View* toplevel_view = owning_tabstrip->GetWidget()->GetContentsView(); | 2370 views::View* toplevel_view = owning_tabstrip->GetWidget()->GetContentsView(); |
2338 | 2371 |
2339 gfx::Point point = point_in_screen; | 2372 gfx::Point point = point_in_screen; |
2340 views::View::ConvertPointFromScreen(toplevel_view, &point); | 2373 views::View::ConvertPointFromScreen(toplevel_view, &point); |
2341 return point.OffsetFromOrigin(); | 2374 return point.OffsetFromOrigin(); |
2342 } | 2375 } |
OLD | NEW |