// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "views/window/window_gtk.h"

#include "app/gfx/path.h"
#include "app/l10n_util.h"
#include "base/gfx/rect.h"
#include "views/screen.h"
#include "views/widget/root_view.h"
#include "views/window/custom_frame_view.h"
#include "views/window/hit_test.h"
#include "views/window/non_client_view.h"
#include "views/window/window_delegate.h"

namespace {

// Converts a Windows-style hit test result code into a GDK window edge.
GdkWindowEdge HitTestCodeToGDKWindowEdge(int hittest_code) {
  switch (hittest_code) {
    case HTBOTTOM:
      return GDK_WINDOW_EDGE_SOUTH;
    case HTBOTTOMLEFT:
      return GDK_WINDOW_EDGE_SOUTH_WEST;
    case HTBOTTOMRIGHT:
    case HTGROWBOX:
      return GDK_WINDOW_EDGE_SOUTH_EAST;
    case HTLEFT:
      return GDK_WINDOW_EDGE_WEST;
    case HTRIGHT:
      return GDK_WINDOW_EDGE_EAST;
    case HTTOP:
      return GDK_WINDOW_EDGE_NORTH;
    case HTTOPLEFT:
      return GDK_WINDOW_EDGE_NORTH_WEST;
    case HTTOPRIGHT:
      return GDK_WINDOW_EDGE_NORTH_EAST;
    default:
      NOTREACHED();
      break;
  }
  // Default to something defaultish.
  return HitTestCodeToGDKWindowEdge(HTGROWBOX);
}

// Converts a Windows-style hit test result code into a GDK cursor type.
GdkCursorType HitTestCodeToGdkCursorType(int hittest_code) {
  switch (hittest_code) {
    case HTBOTTOM:
      return GDK_BOTTOM_SIDE;
    case HTBOTTOMLEFT:
      return GDK_BOTTOM_LEFT_CORNER;
    case HTBOTTOMRIGHT:
    case HTGROWBOX:
      return GDK_BOTTOM_RIGHT_CORNER;
    case HTLEFT:
      return GDK_LEFT_SIDE;
    case HTRIGHT:
      return GDK_RIGHT_SIDE;
    case HTTOP:
      return GDK_TOP_SIDE;
    case HTTOPLEFT:
      return GDK_TOP_LEFT_CORNER;
    case HTTOPRIGHT:
      return GDK_TOP_RIGHT_CORNER;
    default:
      break;
  }
  // Default to something defaultish.
  return GDK_LEFT_PTR;
}

}  // namespace

namespace views {

WindowGtk::~WindowGtk() {
  ActiveWindowWatcherX::RemoveObserver(this);
}

// static
Window* Window::CreateChromeWindow(gfx::NativeWindow parent,
                                   const gfx::Rect& bounds,
                                   WindowDelegate* window_delegate) {
  WindowGtk* window = new WindowGtk(window_delegate);
  window->GetNonClientView()->SetFrameView(window->CreateFrameViewForWindow());
  window->Init(parent, bounds);
  return window;
}

// static
void Window::CloseAllSecondaryWindows() {
  GList* windows = gtk_window_list_toplevels();
  for (GList* window = windows; window;
       window = g_list_next(window)) {
    Window::CloseSecondaryWidget(
        WidgetGtk::GetViewForNative(GTK_WIDGET(window->data)));
  }
  g_list_free(windows);
}

gfx::Rect WindowGtk::GetBounds() const {
  gfx::Rect bounds;
  WidgetGtk::GetBounds(&bounds, true);
  return bounds;
}

gfx::Rect WindowGtk::GetNormalBounds() const {
  // We currently don't support tiling, so this doesn't matter.
  return GetBounds();
}

void WindowGtk::SetBounds(const gfx::Rect& bounds,
                          gfx::NativeWindow other_window) {
  // TODO: need to deal with other_window.
  WidgetGtk::SetBounds(bounds);
}

void WindowGtk::Show() {
  gtk_widget_show_all(GetNativeView());
}

void WindowGtk::HideWindow() {
  Hide();
}

void WindowGtk::Activate() {
  gtk_window_present(GTK_WINDOW(GetNativeView()));
}

void WindowGtk::Close() {
  if (window_closed_) {
    // Don't do anything if we've already been closed.
    return;
  }

  if (non_client_view_->CanClose()) {
    WidgetGtk::Close();
    window_closed_ = true;
  }
}

void WindowGtk::Maximize() {
  gtk_window_maximize(GetNativeWindow());
}

void WindowGtk::Minimize() {
  gtk_window_iconify(GetNativeWindow());
}

void WindowGtk::Restore() {
  if (IsMaximized())
    gtk_window_unmaximize(GetNativeWindow());
  else if (IsMinimized())
    gtk_window_deiconify(GetNativeWindow());
  else if (IsFullscreen())
    SetFullscreen(false);
}

bool WindowGtk::IsActive() const {
  return is_active_;
}

bool WindowGtk::IsVisible() const {
  return GTK_WIDGET_VISIBLE(GetNativeView());
}

bool WindowGtk::IsMaximized() const {
  return window_state_ & GDK_WINDOW_STATE_MAXIMIZED;
}

bool WindowGtk::IsMinimized() const {
  return window_state_ & GDK_WINDOW_STATE_ICONIFIED;
}

void WindowGtk::SetFullscreen(bool fullscreen) {
  if (fullscreen)
    gtk_window_fullscreen(GetNativeWindow());
  else
    gtk_window_unfullscreen(GetNativeWindow());
}

bool WindowGtk::IsFullscreen() const {
  return window_state_ & GDK_WINDOW_STATE_FULLSCREEN;
}

void WindowGtk::EnableClose(bool enable) {
  gtk_window_set_deletable(GetNativeWindow(), enable);
}

void WindowGtk::DisableInactiveRendering() {
  // TODO(sky): this doesn't make sense as bubbles are popups, which don't
  // trigger a change in active status.
}

void WindowGtk::UpdateWindowTitle() {
  // If the non-client view is rendering its own title, it'll need to relayout
  // now.
  non_client_view_->Layout();

  // Update the native frame's text. We do this regardless of whether or not
  // the native frame is being used, since this also updates the taskbar, etc.
  std::wstring window_title = window_delegate_->GetWindowTitle();
  std::wstring localized_text;
  if (l10n_util::AdjustStringForLocaleDirection(window_title, &localized_text))
    window_title.assign(localized_text);

  gtk_window_set_title(GetNativeWindow(), WideToUTF8(window_title).c_str());
}

void WindowGtk::UpdateWindowIcon() {
  // Doesn't matter for chrome os.
}

void WindowGtk::SetIsAlwaysOnTop(bool always_on_top) {
  gtk_window_set_keep_above(GetNativeWindow(), always_on_top);
}

NonClientFrameView* WindowGtk::CreateFrameViewForWindow() {
  // TODO(erg): Always use a custom frame view? Are there cases where we let
  // the window manager deal with the X11 equivalent of the "non-client" area?
  return new CustomFrameView(this);
}

void WindowGtk::UpdateFrameAfterFrameChange() {
  // We currently don't support different frame types on Gtk, so we don't
  // need to implement this.
  NOTIMPLEMENTED();
}

WindowDelegate* WindowGtk::GetDelegate() const {
  return window_delegate_;
}

NonClientView* WindowGtk::GetNonClientView() const {
  return non_client_view_;
}

ClientView* WindowGtk::GetClientView() const {
  return non_client_view_->client_view();
}

gfx::NativeWindow WindowGtk::GetNativeWindow() const {
  return GTK_WINDOW(GetNativeView());
}

bool WindowGtk::ShouldUseNativeFrame() const {
  return false;
}

void WindowGtk::FrameTypeChanged() {
  // We currently don't support different frame types on Gtk, so we don't
  // need to implement this.
  NOTIMPLEMENTED();
}

////////////////////////////////////////////////////////////////////////////////
// WindowGtk, WidgetGtk overrides:

gboolean WindowGtk::OnButtonPress(GtkWidget* widget, GdkEventButton* event) {
  int hittest_code =
      non_client_view_->NonClientHitTest(gfx::Point(event->x, event->y));
  switch (hittest_code) {
    case HTCAPTION: {
      gfx::Point screen_point(event->x, event->y);
      View::ConvertPointToScreen(GetRootView(), &screen_point);
      gtk_window_begin_move_drag(GetNativeWindow(), event->button,
                                 screen_point.x(), screen_point.y(),
                                 event->time);
      return TRUE;
    }
    case HTBOTTOM:
    case HTBOTTOMLEFT:
    case HTBOTTOMRIGHT:
    case HTGROWBOX:
    case HTLEFT:
    case HTRIGHT:
    case HTTOP:
    case HTTOPLEFT:
    case HTTOPRIGHT: {
      gfx::Point screen_point(event->x, event->y);
      View::ConvertPointToScreen(GetRootView(), &screen_point);
      // TODO(beng): figure out how to get a good minimum size.
      gtk_widget_set_size_request(GetNativeView(), 100, 100);
      gtk_window_begin_resize_drag(GetNativeWindow(),
                                   HitTestCodeToGDKWindowEdge(hittest_code),
                                   event->button, screen_point.x(),
                                   screen_point.y(), event->time);
      return TRUE;
    }
    default:
      // Everything else falls into standard client event handling...
      break;
  }
  return WidgetGtk::OnButtonPress(widget, event);
}

gboolean WindowGtk::OnConfigureEvent(GtkWidget* widget,
                                     GdkEventConfigure* event) {
  SaveWindowPosition();
  return FALSE;
}

gboolean WindowGtk::OnMotionNotify(GtkWidget* widget, GdkEventMotion* event) {
  // Update the cursor for the screen edge.
  int hittest_code =
      non_client_view_->NonClientHitTest(gfx::Point(event->x, event->y));
  GdkCursorType cursor_type = HitTestCodeToGdkCursorType(hittest_code);
  GdkCursor* cursor = gdk_cursor_new(cursor_type);
  gdk_window_set_cursor(widget->window, cursor);
  gdk_cursor_destroy(cursor);

  return WidgetGtk::OnMotionNotify(widget, event);
}

void WindowGtk::OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) {
  WidgetGtk::OnSizeAllocate(widget, allocation);

  // The Window's NonClientView may provide a custom shape for the Window.
  gfx::Path window_mask;
  non_client_view_->GetWindowMask(gfx::Size(allocation->width,
                                            allocation->height),
                                  &window_mask);
  GdkRegion* mask_region = window_mask.CreateGdkRegion();
  gdk_window_shape_combine_region(GetNativeView()->window, mask_region, 0, 0);
  if (mask_region)
    gdk_region_destroy(mask_region);

  SaveWindowPosition();
}

gboolean WindowGtk::OnWindowStateEvent(GtkWidget* widget,
                                       GdkEventWindowState* event) {
  window_state_ = event->new_window_state;
  if (!(window_state_ & GDK_WINDOW_STATE_WITHDRAWN))
    SaveWindowPosition();
  return FALSE;
}

void WindowGtk::ActiveWindowChanged(GdkWindow* active_window) {
  if (!GetNativeWindow())
    return;

  bool was_active = IsActive();
  is_active_ = (active_window == GTK_WIDGET(GetNativeWindow())->window);
  if (was_active != IsActive())
    IsActiveChanged();
}

void WindowGtk::IsActiveChanged() {
}

////////////////////////////////////////////////////////////////////////////////
// WindowGtk, protected:

WindowGtk::WindowGtk(WindowDelegate* window_delegate)
    : WidgetGtk(TYPE_WINDOW),
      is_modal_(false),
      window_delegate_(window_delegate),
      non_client_view_(new NonClientView(this)),
      window_state_(GDK_WINDOW_STATE_WITHDRAWN),
      window_closed_(false),
      is_active_(false) {
  is_window_ = true;
  window_delegate_->window_.reset(this);

  ActiveWindowWatcherX::AddObserver(this);
}

void WindowGtk::Init(GtkWindow* parent, const gfx::Rect& bounds) {
  WidgetGtk::Init(NULL, bounds);

  // We call this after initializing our members since our implementations of
  // assorted WidgetWin functions may be called during initialization.
  is_modal_ = window_delegate_->IsModal();
  if (is_modal_)
    gtk_window_set_modal(GetNativeWindow(), true);
  if (parent)
    gtk_window_set_transient_for(GetNativeWindow(), parent);

  g_signal_connect(G_OBJECT(GetNativeWindow()), "configure-event",
                   G_CALLBACK(CallConfigureEvent), this);
  g_signal_connect(G_OBJECT(GetNativeWindow()), "window-state-event",
                   G_CALLBACK(CallWindowStateEvent), this);

  // Create the ClientView, add it to the NonClientView and add the
  // NonClientView to the RootView. This will cause everything to be parented.
  non_client_view_->set_client_view(window_delegate_->CreateClientView(this));
  WidgetGtk::SetContentsView(non_client_view_);

  UpdateWindowTitle();
  SetInitialBounds(parent, bounds);

  // if (!IsAppWindow()) {
  //   notification_registrar_.Add(
  //       this,
  //       NotificationType::ALL_APPWINDOWS_CLOSED,
  //       NotificationService::AllSources());
  // }

  // ResetWindowRegion(false);
}

////////////////////////////////////////////////////////////////////////////////
// WindowGtk, private:

// static
gboolean WindowGtk::CallConfigureEvent(GtkWidget* widget,
                                       GdkEventConfigure* event,
                                       WindowGtk* window_gtk) {
  return window_gtk->OnConfigureEvent(widget, event);
}

// static
gboolean WindowGtk::CallWindowStateEvent(GtkWidget* widget,
                                         GdkEventWindowState* event,
                                         WindowGtk* window_gtk) {
  return window_gtk->OnWindowStateEvent(widget, event);
}

void WindowGtk::SaveWindowPosition() {
  // The delegate may have gone away on us.
  if (!window_delegate_)
    return;

  bool maximized = window_state_ & GDK_WINDOW_STATE_MAXIMIZED;
  window_delegate_->SaveWindowPlacement(GetBounds(), maximized);
}

void WindowGtk::SetInitialBounds(GtkWindow* parent,
                                 const gfx::Rect& create_bounds) {
  gfx::Rect saved_bounds(create_bounds.ToGdkRectangle());
  if (window_delegate_->GetSavedWindowBounds(&saved_bounds)) {
    WidgetGtk::SetBounds(saved_bounds);
  } else {
    if (create_bounds.IsEmpty()) {
      SizeWindowToDefault(parent);
    } else {
      SetBounds(create_bounds, NULL);
    }
  }
}

void WindowGtk::SizeWindowToDefault(GtkWindow* parent) {
  gfx::Rect center_rect;

  if (parent) {
    // We have a parent window, center over it.
    gint parent_x = 0;
    gint parent_y = 0;
    gtk_window_get_position(parent, &parent_x, &parent_y);
    gint parent_w = 0;
    gint parent_h = 0;
    gtk_window_get_size(parent, &parent_w, &parent_h);
    center_rect = gfx::Rect(parent_x, parent_y, parent_w, parent_h);
  } else {
    // We have no parent window, center over the screen.
    center_rect = Screen::GetMonitorWorkAreaNearestWindow(GetNativeWindow());
  }
  gfx::Size size = non_client_view_->GetPreferredSize();
  gfx::Rect bounds(center_rect.x() + (center_rect.width() - size.width()) / 2,
                   center_rect.y() + (center_rect.height() - size.height()) / 2,
                   size.width(), size.height());
  SetBounds(bounds, NULL);
}

}  // namespace views