summaryrefslogtreecommitdiffstats
path: root/views/widget/tooltip_manager_gtk.cc
blob: 9a4316bfb7678946e9e58265b9663095258df3ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// 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/widget/tooltip_manager_gtk.h"

#include "app/gfx/font.h"
#include "base/logging.h"
#include "base/string_util.h"
#include "views/focus/focus_manager.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);
  PangoFontDescription* pfd = style->font_desc;
  gfx::Font* font = new gfx::Font(gfx::Font::CreateFont(pfd));
  pango_font_description_free(pfd);

  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;
}

// Callback from gtk_container_foreach. If |*label_p| is NULL and |widget| is
// a GtkLabel, |*label_p| is set to |widget|. Used to find the first GtkLabel
// in a container.
static void LabelLocatorCallback(GtkWidget* widget,
                                 gpointer label_p) {
  GtkWidget** label = static_cast<GtkWidget**>(label_p);
  if (!*label && GTK_IS_LABEL(widget))
    *label = widget;
}

// By default GtkTooltip wraps at a longish string. We want more control over
// that wrapping. The only way to do that is dig out the label and set
// gtk_label_set_max_width_chars, which is what this code does. I also tried
// setting a custom widget on the tooltip, but there is a bug in Gtk that
// triggers continually hiding/showing the widget in that case.
static void AdjustLabel(GtkTooltip* tooltip) {
  static const char kAdjustedLabelPropertyValue[] = "_adjusted_label_";
  gpointer adjusted_value = g_object_get_data(G_OBJECT(tooltip),
                                              kAdjustedLabelPropertyValue);
  if (adjusted_value)
    return;

  adjusted_value = reinterpret_cast<gpointer>(1);
  g_object_set_data(G_OBJECT(tooltip), kAdjustedLabelPropertyValue,
                    adjusted_value);

  GtkWidget* parent;
  {
    // Create a label so that we can get the parent. The Tooltip ends up taking
    // ownership of the label and deleting it.
    GtkWidget* label = gtk_label_new("");
    gtk_tooltip_set_custom(tooltip, label);
    parent = gtk_widget_get_parent(label);
    gtk_tooltip_set_custom(tooltip, NULL);
  }
  if (parent) {
    // We found the parent, find the first label, which is where the tooltip
    // text ends up going.
    GtkLabel* real_label = NULL;
    gtk_container_foreach(GTK_CONTAINER(parent), LabelLocatorCallback,
                          static_cast<gpointer>(&real_label));
    if (real_label) {
      // For some reason I'm occasionally seeing a crash in trying to get font
      // metrics. Explicitly setting the font avoids this.
      PangoFontDescription* pfd =
          gfx::Font::PangoFontFromGfxFont(gfx::Font());
      gtk_widget_modify_font(GTK_WIDGET(real_label), pfd);
      pango_font_description_free(pfd);
      gtk_label_set_max_width_chars(GTK_LABEL(real_label), 3000);
    }
  }
}

TooltipManagerGtk::TooltipManagerGtk(WidgetGtk* widget)
    : widget_(widget),
      keyboard_view_(NULL) {
}

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.x(), view_loc.y(), &text))
    return false;

  AdjustLabel(tooltip);

  // 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.x(), vis_bounds.y());
  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());
  gtk_tooltip_set_text(tooltip, WideToUTF8(text).c_str());

  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(0, 0, &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