// Copyright (c) 2010 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/widget/tooltip_manager_gtk.h"

#include "base/logging.h"
#include "base/utf_string_conversions.h"
#include "gfx/font.h"
#include "views/focus/focus_manager.h"
#include "views/screen.h"
#include "views/widget/root_view.h"
#include "views/widget/widget_gtk.h"

// WARNING: this implementation is good for a start, but it doesn't give us
// control of tooltip positioning both on mouse events and when showing from
// keyboard. We may need to write our own to give us the control we need.

namespace views {

static gfx::Font* LoadDefaultFont() {
  // Create a tooltip widget and extract the font from it (we have to realize
  // it to make sure the correct font gets set).
  GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
  gtk_widget_set_name(window, "gtk-tooltip");
  GtkWidget* label = gtk_label_new("");
  gtk_widget_show(label);

  gtk_container_add(GTK_CONTAINER(window), label);
  gtk_widget_realize(window);

  GtkStyle* style = gtk_widget_get_style(label);
  gfx::Font* font = new gfx::Font(gfx::Font::CreateFont(style->font_desc));

  gtk_widget_destroy(window);

  return font;
}

// static
int TooltipManager::GetTooltipHeight() {
  // This is only used to position the tooltip, and we don't yet support
  // positioning the tooltip, it isn't worth trying to implement this.
  return 0;
}

// static
gfx::Font TooltipManager::GetDefaultFont() {
  static gfx::Font* font = NULL;
  if (!font)
    font = LoadDefaultFont();

  return *font;
}

// static
const std::wstring& TooltipManager::GetLineSeparator() {
  static std::wstring* line_separator = NULL;
  if (!line_separator)
    line_separator = new std::wstring(L"\n");
  return *line_separator;
}

// static
int TooltipManager::GetMaxWidth(int x, int y) {
  gfx::Rect monitor_bounds =
      Screen::GetMonitorAreaNearestPoint(gfx::Point(x, y));
  // GtkLabel (gtk_label_ensure_layout) forces wrapping at this size. We mirror
  // the size here otherwise tooltips wider than the size used by gtklabel end
  // up with extraneous empty lines.
  return monitor_bounds.width() == 0 ? 800 : (monitor_bounds.width() + 1) / 2;
}

TooltipManagerGtk::TooltipManagerGtk(WidgetGtk* widget)
    : widget_(widget),
      keyboard_view_(NULL),
      tooltip_window_(widget->window_contents()) {
}

bool TooltipManagerGtk::ShowTooltip(int x, int y, bool for_keyboard,
                                    GtkTooltip* tooltip) {
  View* view = NULL;
  gfx::Point view_loc;
  if (keyboard_view_) {
    view = keyboard_view_;
    view_loc.SetPoint(view->width() / 2, view->height() / 2);
  } else if (!for_keyboard) {
    RootView* root_view = widget_->GetRootView();
    view = root_view->GetViewForPoint(gfx::Point(x, y));
    view_loc.SetPoint(x, y);
    View::ConvertPointFromWidget(view, &view_loc);
  } else {
    FocusManager* focus_manager = widget_->GetFocusManager();
    if (focus_manager) {
      view = focus_manager->GetFocusedView();
      if (view)
        view_loc.SetPoint(view->width() / 2, view->height() / 2);
    }
  }

  if (!view)
    return false;

  std::wstring text;
  if (!view->GetTooltipText(view_loc, &text))
    return false;

  // Sets the area of the tooltip. This way if different views in the same
  // widget have tooltips the tooltip doesn't get stuck at the same location.
  gfx::Rect vis_bounds = view->GetVisibleBounds();
  gfx::Point widget_loc(vis_bounds.origin());
  View::ConvertPointToWidget(view, &widget_loc);
  GdkRectangle tip_area = { widget_loc.x(), widget_loc.y(),
                            vis_bounds.width(), vis_bounds.height() };
  gtk_tooltip_set_tip_area(tooltip, &tip_area);

  int max_width, line_count;
  gfx::Point screen_loc(x, y);
  View::ConvertPointToScreen(widget_->GetRootView(), &screen_loc);
  TrimTooltipToFit(&text, &max_width, &line_count, screen_loc.x(),
                   screen_loc.y());
  tooltip_window_.SetTooltipText(text);

  return true;
}

void TooltipManagerGtk::UpdateTooltip() {
  // UpdateTooltip may be invoked after the widget has been destroyed.
  GtkWidget* widget = widget_->GetNativeView();
  if (!widget)
    return;

  GdkDisplay* display = gtk_widget_get_display(widget);
  if (display)
    gtk_tooltip_trigger_tooltip_query(display);
}

void TooltipManagerGtk::TooltipTextChanged(View* view) {
  UpdateTooltip();
}

void TooltipManagerGtk::ShowKeyboardTooltip(View* view) {
  if (view == keyboard_view_)
    return;  // We're already showing the tip for the specified view.

  // We have to hide the current tooltip, then show again.
  HideKeyboardTooltip();

  std::wstring tooltip_text;
  if (!view->GetTooltipText(gfx::Point(), &tooltip_text))
    return;  // The view doesn't have a tooltip, nothing to do.

  keyboard_view_ = view;
  if (!SendShowHelpSignal()) {
    keyboard_view_ = NULL;
    return;
  }
}

void TooltipManagerGtk::HideKeyboardTooltip() {
  if (!keyboard_view_)
    return;

  SendShowHelpSignal();
  keyboard_view_ = NULL;
}

bool TooltipManagerGtk::SendShowHelpSignal() {
  GtkWidget* widget = widget_->window_contents();
  GType itype = G_TYPE_FROM_INSTANCE(G_OBJECT(widget));
  guint signal_id;
  GQuark detail;
  if (!g_signal_parse_name("show_help", itype, &signal_id, &detail, FALSE)) {
    NOTREACHED();
    return false;
  }
  gboolean result;
  g_signal_emit(widget, signal_id, 0, GTK_WIDGET_HELP_TOOLTIP, &result);
  return true;
}

}  // namespace views