tab_drag_controller.h revision 9ab5563a3196760eb381d102cbb2bc0f7abc6a50
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
3// found in the LICENSE file.
4
5#ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_
6#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_
7
8#include <vector>
9
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop/message_loop.h"
12#include "base/timer/timer.h"
13#include "chrome/browser/ui/tabs/dock_info.h"
14#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
15#include "chrome/browser/ui/views/tabs/tab_strip_types.h"
16#include "content/public/browser/notification_observer.h"
17#include "content/public/browser/notification_registrar.h"
18#include "content/public/browser/web_contents_delegate.h"
19#include "ui/base/models/list_selection_model.h"
20#include "ui/gfx/rect.h"
21#include "ui/views/widget/widget_observer.h"
22
23namespace gfx {
24class Screen;
25}
26namespace ui {
27class ListSelectionModel;
28}
29namespace views {
30class View;
31}
32class Browser;
33class DraggedTabView;
34class Tab;
35struct TabRendererData;
36class TabStrip;
37class TabStripModel;
38
39// TabDragController is responsible for managing the tab dragging session. When
40// the user presses the mouse on a tab a new TabDragController is created and
41// Drag() is invoked as the mouse is dragged. If the mouse is dragged far enough
42// TabDragController starts a drag session. The drag session is completed when
43// EndDrag() is invoked (or the TabDragController is destroyed).
44//
45// While dragging within a tab strip TabDragController sets the bounds of the
46// tabs (this is referred to as attached). When the user drags far enough such
47// that the tabs should be moved out of the tab strip two possible things
48// can happen (this state is referred to as detached):
49// . If |detach_into_browser_| is true then a new Browser is created and
50//   RunMoveLoop() is invoked on the Widget to drag the browser around. This is
51//   the default on chromeos and can be enabled on windows with a flag.
52// . If |detach_into_browser_| is false a small representation of the active tab
53//   is created and that is dragged around. This mode does not run a nested
54//   message loop.
55class TabDragController : public content::WebContentsDelegate,
56                          public content::NotificationObserver,
57                          public base::MessageLoopForUI::Observer,
58                          public views::WidgetObserver,
59                          public TabStripModelObserver {
60 public:
61  enum DetachBehavior {
62    DETACHABLE,
63    NOT_DETACHABLE
64  };
65
66  // What should happen as the mouse is dragged within the tabstrip.
67  enum MoveBehavior {
68    // Only the set of visible tabs should change. This is only applicable when
69    // using touch layout.
70    MOVE_VISIBILE_TABS,
71
72    // Typical behavior where tabs are dragged around.
73    REORDER
74  };
75
76  // Indicates the event source that initiated the drag.
77  enum EventSource {
78    EVENT_SOURCE_MOUSE,
79    EVENT_SOURCE_TOUCH,
80  };
81
82  // Amount above or below the tabstrip the user has to drag before detaching.
83  static const int kTouchVerticalDetachMagnetism;
84  static const int kVerticalDetachMagnetism;
85
86  TabDragController();
87  virtual ~TabDragController();
88
89  // Initializes TabDragController to drag the tabs in |tabs| originating
90  // from |source_tabstrip|. |source_tab| is the tab that initiated the drag and
91  // is contained in |tabs|.  |mouse_offset| is the distance of the mouse
92  // pointer from the origin of the first tab in |tabs| and |source_tab_offset|
93  // the offset from |source_tab|. |source_tab_offset| is the horizontal distant
94  // for a horizontal tab strip, and the vertical distance for a vertical tab
95  // strip. |initial_selection_model| is the selection model before the drag
96  // started and is only non-empty if |source_tab| was not initially selected.
97  void Init(TabStrip* source_tabstrip,
98            Tab* source_tab,
99            const std::vector<Tab*>& tabs,
100            const gfx::Point& mouse_offset,
101            int source_tab_offset,
102            const ui::ListSelectionModel& initial_selection_model,
103            DetachBehavior detach_behavior,
104            MoveBehavior move_behavior,
105            EventSource event_source);
106
107  // Returns true if there is a drag underway and the drag is attached to
108  // |tab_strip|.
109  // NOTE: this returns false if the TabDragController is in the process of
110  // finishing the drag.
111  static bool IsAttachedTo(TabStrip* tab_strip);
112
113  // Returns true if there is a drag underway.
114  static bool IsActive();
115
116  // Used to determine whether the tab drag controller detaches dragged tabs
117  // into new browser windows while the drag is in process.
118  static bool ShouldDetachIntoNewBrowser();
119
120  // Sets the move behavior. Has no effect if started_drag() is true.
121  void SetMoveBehavior(MoveBehavior behavior);
122  MoveBehavior move_behavior() const { return move_behavior_; }
123
124  EventSource event_source() const { return event_source_; }
125
126  // See description above fields for details on these.
127  bool active() const { return active_; }
128  const TabStrip* attached_tabstrip() const { return attached_tabstrip_; }
129
130  // Returns true if a drag started.
131  bool started_drag() const { return started_drag_; }
132
133  // Returns true if mutating the TabStripModel.
134  bool is_mutating() const { return is_mutating_; }
135
136  // Returns true if we've detached from a tabstrip and are running a nested
137  // move message loop.
138  bool is_dragging_window() const { return is_dragging_window_; }
139
140  // Invoked to drag to the new location, in screen coordinates.
141  void Drag(const gfx::Point& point_in_screen);
142
143  // Complete the current drag session.
144  void EndDrag(EndDragReason reason);
145
146 private:
147  class DockDisplayer;
148  friend class DockDisplayer;
149
150  typedef std::set<gfx::NativeView> DockWindows;
151
152  // Used to indicate the direction the mouse has moved when attached.
153  static const int kMovedMouseLeft  = 1 << 0;
154  static const int kMovedMouseRight = 1 << 1;
155
156  // Enumeration of the ways a drag session can end.
157  enum EndDragType {
158    // Drag session exited normally: the user released the mouse.
159    NORMAL,
160
161    // The drag session was canceled (alt-tab during drag, escape ...)
162    CANCELED,
163
164    // The tab (NavigationController) was destroyed during the drag.
165    TAB_DESTROYED
166  };
167
168  // Whether Detach() should release capture or not.
169  enum ReleaseCapture {
170    RELEASE_CAPTURE,
171    DONT_RELEASE_CAPTURE,
172  };
173
174  // Specifies what should happen when RunMoveLoop completes.
175  enum EndRunLoopBehavior {
176    // Indicates the drag should end.
177    END_RUN_LOOP_STOP_DRAGGING,
178
179    // Indicates the drag should continue.
180    END_RUN_LOOP_CONTINUE_DRAGGING
181  };
182
183  // Enumeration of the possible positions the detached tab may detach from.
184  enum DetachPosition {
185    DETACH_BEFORE,
186    DETACH_AFTER,
187    DETACH_ABOVE_OR_BELOW
188  };
189
190  // Indicates what should happen after invoking DragBrowserToNewTabStrip().
191  enum DragBrowserResultType {
192    // The caller should return immediately. This return value is used if a
193    // nested message loop was created or we're in a nested message loop and
194    // need to exit it.
195    DRAG_BROWSER_RESULT_STOP,
196
197    // The caller should continue.
198    DRAG_BROWSER_RESULT_CONTINUE,
199  };
200
201  // Stores the date associated with a single tab that is being dragged.
202  struct TabDragData {
203    TabDragData();
204    ~TabDragData();
205
206    // The WebContents being dragged.
207    content::WebContents* contents;
208
209    // The original content::WebContentsDelegate of |contents|, before it was
210    // detached from the browser window. We store this so that we can forward
211    // certain delegate notifications back to it if we can't handle them
212    // locally.
213    content::WebContentsDelegate* original_delegate;
214
215    // This is the index of the tab in |source_tabstrip_| when the drag
216    // began. This is used to restore the previous state if the drag is aborted.
217    int source_model_index;
218
219    // If attached this is the tab in |attached_tabstrip_|.
220    Tab* attached_tab;
221
222    // Is the tab pinned?
223    bool pinned;
224  };
225
226  typedef std::vector<TabDragData> DragData;
227
228  // Sets |drag_data| from |tab|. This also registers for necessary
229  // notifications and resets the delegate of the WebContents.
230  void InitTabDragData(Tab* tab, TabDragData* drag_data);
231
232  // Overridden from content::WebContentsDelegate:
233  virtual content::WebContents* OpenURLFromTab(
234      content::WebContents* source,
235      const content::OpenURLParams& params) OVERRIDE;
236  virtual void NavigationStateChanged(const content::WebContents* source,
237                                      unsigned changed_flags) OVERRIDE;
238  virtual void AddNewContents(content::WebContents* source,
239                              content::WebContents* new_contents,
240                              WindowOpenDisposition disposition,
241                              const gfx::Rect& initial_pos,
242                              bool user_gesture,
243                              bool* was_blocked) OVERRIDE;
244  virtual void LoadingStateChanged(content::WebContents* source) OVERRIDE;
245  virtual bool ShouldSuppressDialogs() OVERRIDE;
246  virtual content::JavaScriptDialogManager*
247      GetJavaScriptDialogManager() OVERRIDE;
248
249  // Overridden from content::NotificationObserver:
250  virtual void Observe(int type,
251                       const content::NotificationSource& source,
252                       const content::NotificationDetails& details) OVERRIDE;
253
254  // Overridden from MessageLoop::Observer:
255  virtual base::EventStatus WillProcessEvent(
256      const base::NativeEvent& event) OVERRIDE;
257  virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE;
258
259  // Overriden from views::WidgetObserver:
260  virtual void OnWidgetBoundsChanged(views::Widget* widget,
261                                     const gfx::Rect& new_bounds) OVERRIDE;
262
263  // Overriden from TabStripModelObserver:
264  virtual void TabStripEmpty() OVERRIDE;
265
266  // Initialize the offset used to calculate the position to create windows
267  // in |GetWindowCreatePoint|. This should only be invoked from |Init|.
268  void InitWindowCreatePoint();
269
270  // Returns the point where a detached window should be created given the
271  // current mouse position |origin|.
272  gfx::Point GetWindowCreatePoint(const gfx::Point& origin) const;
273
274  void UpdateDockInfo(const gfx::Point& point_in_screen);
275
276  // Saves focus in the window that the drag initiated from. Focus will be
277  // restored appropriately if the drag ends within this same window.
278  void SaveFocus();
279
280  // Restore focus to the View that had focus before the drag was started, if
281  // the drag ends within the same Window as it began.
282  void RestoreFocus();
283
284  // Tests whether |point_in_screen| is past a minimum elasticity threshold
285  // required to start a drag.
286  bool CanStartDrag(const gfx::Point& point_in_screen) const;
287
288  // Move the DraggedTabView according to the current mouse screen position,
289  // potentially updating the source and other TabStrips.
290  void ContinueDragging(const gfx::Point& point_in_screen);
291
292  // Transitions dragging from |attached_tabstrip_| to |target_tabstrip|.
293  // |target_tabstrip| is NULL if the mouse is not over a valid tab strip.  See
294  // DragBrowserResultType for details of the return type.
295  DragBrowserResultType DragBrowserToNewTabStrip(
296      TabStrip* target_tabstrip,
297      const gfx::Point& point_in_screen);
298
299  // Handles dragging for a touch tabstrip when the tabs are stacked. Doesn't
300  // actually reorder the tabs in anyway, just changes what's visible.
301  void DragActiveTabStacked(const gfx::Point& point_in_screen);
302
303  // Moves the active tab to the next/previous tab. Used when the next/previous
304  // tab is stacked.
305  void MoveAttachedToNextStackedIndex(const gfx::Point& point_in_screen);
306  void MoveAttachedToPreviousStackedIndex(const gfx::Point& point_in_screen);
307
308  // Handles dragging tabs while the tabs are attached.
309  void MoveAttached(const gfx::Point& point_in_screen);
310
311  // Handles dragging while the tabs are detached.
312  void MoveDetached(const gfx::Point& point_in_screen);
313
314  // If necessary starts the |move_stacked_timer_|. The timer is started if
315  // close enough to an edge with stacked tabs.
316  void StartMoveStackedTimerIfNecessary(
317      const gfx::Point& point_in_screen,
318      int delay_ms);
319
320  // Returns the TabStrip for the specified window, or NULL if one doesn't exist
321  // or isn't compatible.
322  TabStrip* GetTabStripForWindow(gfx::NativeWindow window);
323
324  // Returns the compatible TabStrip to drag to at the specified point (screen
325  // coordinates), or NULL if there is none.
326  TabStrip* GetTargetTabStripForPoint(const gfx::Point& point_in_screen);
327
328  // Returns true if |tabstrip| contains the specified point in screen
329  // coordinates.
330  bool DoesTabStripContain(TabStrip* tabstrip,
331                           const gfx::Point& point_in_screen) const;
332
333  // Returns the DetachPosition given the specified location in screen
334  // coordinates.
335  DetachPosition GetDetachPosition(const gfx::Point& point_in_screen);
336
337  DockInfo GetDockInfoAtPoint(const gfx::Point& point_in_screen);
338
339  // Attach the dragged Tab to the specified TabStrip.
340  void Attach(TabStrip* attached_tabstrip, const gfx::Point& point_in_screen);
341
342  // Detach the dragged Tab from the current TabStrip.
343  void Detach(ReleaseCapture release_capture);
344
345  // Detaches the tabs being dragged, creates a new Browser to contain them and
346  // runs a nested move loop.
347  void DetachIntoNewBrowserAndRunMoveLoop(const gfx::Point& point_in_screen);
348
349  // Runs a nested message loop that handles moving the current
350  // Browser. |drag_offset| is the offset from the window origin and is used in
351  // calculating the location of the window offset from the cursor while
352  // dragging.
353  void RunMoveLoop(const gfx::Vector2d& drag_offset);
354
355  // Determines the index to insert tabs at. |dragged_bounds| is the bounds of
356  // the tabs being dragged, |start| the index of the tab to start looking from
357  // and |delta| the amount to increment (1 or -1).
358  int GetInsertionIndexFrom(const gfx::Rect& dragged_bounds,
359                            int start,
360                            int delta) const;
361
362  // Returns the index where the dragged WebContents should be inserted into
363  // |attached_tabstrip_| given the DraggedTabView's bounds |dragged_bounds| in
364  // coordinates relative to |attached_tabstrip_| and has had the mirroring
365  // transformation applied.
366  // NOTE: this is invoked from |Attach| before the tabs have been inserted.
367  int GetInsertionIndexForDraggedBounds(const gfx::Rect& dragged_bounds) const;
368
369  // Returns true if |dragged_bounds| is close enough to the next stacked tab
370  // so that the active tab should be dragged there.
371  bool ShouldDragToNextStackedTab(const gfx::Rect& dragged_bounds,
372                                  int index) const;
373
374  // Returns true if |dragged_bounds| is close enough to the previous stacked
375  // tab so that the active tab should be dragged there.
376  bool ShouldDragToPreviousStackedTab(const gfx::Rect& dragged_bounds,
377                                      int index) const;
378
379  // Used by GetInsertionIndexForDraggedBounds() when the tabstrip is stacked.
380  int GetInsertionIndexForDraggedBoundsStacked(
381      const gfx::Rect& dragged_bounds) const;
382
383  // Retrieve the bounds of the DraggedTabView relative to the attached
384  // TabStrip. |tab_strip_point| is in the attached TabStrip's coordinate
385  // system.
386  gfx::Rect GetDraggedViewTabStripBounds(const gfx::Point& tab_strip_point);
387
388  // Get the position of the dragged tab view relative to the attached tab
389  // strip with the mirroring transform applied.
390  gfx::Point GetAttachedDragPoint(const gfx::Point& point_in_screen);
391
392  // Finds the Tabs within the specified TabStrip that corresponds to the
393  // WebContents of the dragged tabs. Returns an empty vector if not attached.
394  std::vector<Tab*> GetTabsMatchingDraggedContents(TabStrip* tabstrip);
395
396  // Returns the bounds for the tabs based on the attached tab strip. The
397  // x-coordinate of each tab is offset by |x_offset|.
398  std::vector<gfx::Rect> CalculateBoundsForDraggedTabs(int x_offset);
399
400  // Does the work for EndDrag. If we actually started a drag and |how_end| is
401  // not TAB_DESTROYED then one of EndDrag or RevertDrag is invoked.
402  void EndDragImpl(EndDragType how_end);
403
404  // Reverts a cancelled drag operation.
405  void RevertDrag();
406
407  // Reverts the tab at |drag_index| in |drag_data_|.
408  void RevertDragAt(size_t drag_index);
409
410  // Selects the dragged tabs in |model|. Does nothing if there are no longer
411  // any dragged contents (as happens when a WebContents is deleted out from
412  // under us).
413  void ResetSelection(TabStripModel* model);
414
415  // Finishes a succesful drag operation.
416  void CompleteDrag();
417
418  // Resets the delegates of the WebContents.
419  void ResetDelegates();
420
421  // Create the DraggedTabView.
422  void CreateDraggedView(const std::vector<TabRendererData>& data,
423                         const std::vector<gfx::Rect>& renderer_bounds);
424
425  // Returns the bounds (in screen coordinates) of the specified View.
426  gfx::Rect GetViewScreenBounds(views::View* tabstrip) const;
427
428  // Hides the frame for the window that contains the TabStrip the current
429  // drag session was initiated from.
430  void HideFrame();
431
432  // Closes a hidden frame at the end of a drag session.
433  void CleanUpHiddenFrame();
434
435  void DockDisplayerDestroyed(DockDisplayer* controller);
436
437  void BringWindowUnderPointToFront(const gfx::Point& point_in_screen);
438
439  // Convenience for getting the TabDragData corresponding to the tab the user
440  // started dragging.
441  TabDragData* source_tab_drag_data() {
442    return &(drag_data_[source_tab_index_]);
443  }
444
445  // Convenience for |source_tab_drag_data()->contents|.
446  content::WebContents* source_dragged_contents() {
447    return source_tab_drag_data()->contents;
448  }
449
450  // Returns the Widget of the currently attached TabStrip's BrowserView.
451  views::Widget* GetAttachedBrowserWidget();
452
453  // Returns true if the tabs were originality one after the other in
454  // |source_tabstrip_|.
455  bool AreTabsConsecutive();
456
457  // Creates and returns a new Browser to handle the drag.
458  Browser* CreateBrowserForDrag(TabStrip* source,
459                                const gfx::Point& point_in_screen,
460                                gfx::Vector2d* drag_offset,
461                                std::vector<gfx::Rect>* drag_bounds);
462
463  // Returns the TabStripModel for the specified tabstrip.
464  TabStripModel* GetModel(TabStrip* tabstrip) const;
465
466  // Returns the location of the cursor. This is either the location of the
467  // mouse or the location of the current touch point.
468  gfx::Point GetCursorScreenPoint();
469
470  // Returns the offset from the top left corner of the window to
471  // |point_in_screen|.
472  gfx::Vector2d GetWindowOffset(const gfx::Point& point_in_screen);
473
474  // Returns true if moving the mouse only changes the visible tabs.
475  bool move_only() const {
476    return (move_behavior_ == MOVE_VISIBILE_TABS) != 0;
477  }
478
479  // If true Detaching creates a new browser and enters a nested message loop.
480  bool detach_into_browser_;
481
482  // Handles registering for notifications.
483  content::NotificationRegistrar registrar_;
484
485  EventSource event_source_;
486
487  // The TabStrip the drag originated from.
488  TabStrip* source_tabstrip_;
489
490  // The TabStrip the dragged Tab is currently attached to, or NULL if the
491  // dragged Tab is detached.
492  TabStrip* attached_tabstrip_;
493
494  // The screen that this drag is associated with. Cached, because other UI
495  // elements are NULLd at various points during the lifetime of this object.
496  gfx::Screen* screen_;
497
498  // The desktop type that this drag is associated with. Cached, because other
499  // UI elements are NULLd at various points during the lifetime of this
500  // object.
501  chrome::HostDesktopType host_desktop_type_;
502
503  // The visual representation of the dragged Tab.
504  scoped_ptr<DraggedTabView> view_;
505
506  // The position of the mouse (in screen coordinates) at the start of the drag
507  // operation. This is used to calculate minimum elasticity before a
508  // DraggedTabView is constructed.
509  gfx::Point start_point_in_screen_;
510
511  // This is the offset of the mouse from the top left of the Tab where
512  // dragging begun. This is used to ensure that the dragged view is always
513  // positioned at the correct location during the drag, and to ensure that the
514  // detached window is created at the right location.
515  gfx::Point mouse_offset_;
516
517  // Ratio of the x-coordinate of the |source_tab_offset| to the width of the
518  // tab. Not used for vertical tabs.
519  float offset_to_width_ratio_;
520
521  // A hint to use when positioning new windows created by detaching Tabs. This
522  // is the distance of the mouse from the top left of the dragged tab as if it
523  // were the distance of the mouse from the top left of the first tab in the
524  // attached TabStrip from the top left of the window.
525  gfx::Point window_create_point_;
526
527  // Location of the first tab in the source tabstrip in screen coordinates.
528  // This is used to calculate window_create_point_.
529  gfx::Point first_source_tab_point_;
530
531  // The bounds of the browser window before the last Tab was detached. When
532  // the last Tab is detached, rather than destroying the frame (which would
533  // abort the drag session), the frame is moved off-screen. If the drag is
534  // aborted (e.g. by the user pressing Esc, or capture being lost), the Tab is
535  // attached to the hidden frame and the frame moved back to these bounds.
536  gfx::Rect restore_bounds_;
537
538  // The last view that had focus in the window containing |source_tab_|. This
539  // is saved so that focus can be restored properly when a drag begins and
540  // ends within this same window.
541  views::View* old_focused_view_;
542
543  // The position along the major axis of the mouse cursor in screen coordinates
544  // at the time of the last re-order event.
545  int last_move_screen_loc_;
546
547  DockInfo dock_info_;
548
549  DockWindows dock_windows_;
550
551  std::vector<DockDisplayer*> dock_controllers_;
552
553  // Timer used to bring the window under the cursor to front. If the user
554  // stops moving the mouse for a brief time over a browser window, it is
555  // brought to front.
556  base::OneShotTimer<TabDragController> bring_to_front_timer_;
557
558  // Timer used to move the stacked tabs. See comment aboue
559  // StartMoveStackedTimerIfNecessary().
560  base::OneShotTimer<TabDragController> move_stacked_timer_;
561
562  // Did the mouse move enough that we started a drag?
563  bool started_drag_;
564
565  // Is the drag active?
566  bool active_;
567
568  DragData drag_data_;
569
570  // Index of the source tab in drag_data_.
571  size_t source_tab_index_;
572
573  // True until |MoveAttached| is invoked once.
574  bool initial_move_;
575
576  // The selection model before the drag started. See comment above Init() for
577  // details.
578  ui::ListSelectionModel initial_selection_model_;
579
580  // The selection model of |attached_tabstrip_| before the tabs were attached.
581  ui::ListSelectionModel selection_model_before_attach_;
582
583  // Initial x-coordinates of the tabs when the drag started. Only used for
584  // touch mode.
585  std::vector<int> initial_tab_positions_;
586
587  DetachBehavior detach_behavior_;
588  MoveBehavior move_behavior_;
589
590  // Updated as the mouse is moved when attached. Indicates whether the mouse
591  // has ever moved to the left or right. If the tabs are ever detached this
592  // is set to kMovedMouseRight | kMovedMouseLeft.
593  int mouse_move_direction_;
594
595  // Last location used in screen coordinates.
596  gfx::Point last_point_in_screen_;
597
598  // The following are needed when detaching into a browser
599  // (|detach_into_browser_| is true).
600
601  // See description above getter.
602  bool is_dragging_window_;
603
604  EndRunLoopBehavior end_run_loop_behavior_;
605
606  // If true, we're waiting for a move loop to complete.
607  bool waiting_for_run_loop_to_exit_;
608
609  // The TabStrip to attach to after the move loop completes.
610  TabStrip* tab_strip_to_attach_to_after_exit_;
611
612  // Non-null for the duration of RunMoveLoop.
613  views::Widget* move_loop_widget_;
614
615  // If non-null set to true from destructor.
616  bool* destroyed_;
617
618  // See description above getter.
619  bool is_mutating_;
620
621  DISALLOW_COPY_AND_ASSIGN(TabDragController);
622};
623
624#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_
625