blob: 257f190feeb64c41bffeda6c3ce8ff9237f4e7d4 [file] [log] [blame]
// Copyright 2012 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.
#ifndef CHROME_BROWSER_UI_GTK_OMNIBOX_OMNIBOX_VIEW_GTK_H_
#define CHROME_BROWSER_UI_GTK_OMNIBOX_OMNIBOX_VIEW_GTK_H_
#include <gtk/gtk.h>
#include <algorithm>
#include <string>
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_util.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
#include "chrome/browser/ui/toolbar/toolbar_model.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "ui/base/gtk/gtk_signal.h"
#include "ui/base/gtk/gtk_signal_registrar.h"
#include "ui/base/gtk/owned_widget_gtk.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/rect.h"
class Browser;
class OmniboxPopupView;
class Profile;
namespace gfx {
class Font;
}
class GtkThemeService;
class OmniboxViewGtk : public OmniboxView,
public content::NotificationObserver {
public:
// Modeled like the Windows CHARRANGE. Represent a pair of cursor position
// offsets. Since GtkTextIters are invalid after the buffer is changed, we
// work in character offsets (not bytes).
struct CharRange {
CharRange() : cp_min(0), cp_max(0) { }
CharRange(int n, int x) : cp_min(n), cp_max(x) { }
// Returns the start/end of the selection.
int selection_min() const { return std::min(cp_min, cp_max); }
int selection_max() const { return std::max(cp_min, cp_max); }
// Work in integers to match the gint GTK APIs.
int cp_min; // For a selection: Represents the start.
int cp_max; // For a selection: Represents the end (insert position).
};
// profile parameter is introduced for unittests which can not instantiate
// browser object and pass NULL to the browser parameter.
// In other use case, you should pass browser->profile() object as
// profile parameter.
OmniboxViewGtk(OmniboxEditController* controller,
Browser* browser,
Profile* profile,
CommandUpdater* command_updater,
bool popup_window_mode,
GtkWidget* location_bar);
virtual ~OmniboxViewGtk();
// Initialize, create the underlying widgets, etc.
void Init();
// OmniboxView:
virtual void SaveStateToTab(content::WebContents* tab) OVERRIDE;
virtual void OnTabChanged(const content::WebContents* web_contents) OVERRIDE;
virtual void Update() OVERRIDE;
virtual base::string16 GetText() const OVERRIDE;
virtual void SetWindowTextAndCaretPos(const base::string16& text,
size_t caret_pos,
bool update_popup,
bool notify_text_changed) OVERRIDE;
virtual void SetForcedQuery() OVERRIDE;
virtual bool IsSelectAll() const OVERRIDE;
virtual bool DeleteAtEndPressed() OVERRIDE;
virtual void GetSelectionBounds(
base::string16::size_type* start,
base::string16::size_type* end) const OVERRIDE;
virtual void SelectAll(bool reversed) OVERRIDE;
virtual void UpdatePopup() OVERRIDE;
virtual void SetFocus() OVERRIDE;
virtual void ApplyCaretVisibility() OVERRIDE;
virtual void OnTemporaryTextMaybeChanged(
const base::string16& display_text,
bool save_original_selection,
bool notify_text_changed) OVERRIDE;
virtual bool OnInlineAutocompleteTextMaybeChanged(
const base::string16& display_text, size_t user_text_length) OVERRIDE;
virtual void OnInlineAutocompleteTextCleared() OVERRIDE;
virtual void OnRevertTemporaryText() OVERRIDE;
virtual void OnBeforePossibleChange() OVERRIDE;
virtual bool OnAfterPossibleChange() OVERRIDE;
virtual gfx::NativeView GetNativeView() const OVERRIDE;
virtual gfx::NativeView GetRelativeWindowForPopup() const OVERRIDE;
virtual void SetGrayTextAutocompletion(
const base::string16& suggestion) OVERRIDE;
virtual base::string16 GetGrayTextAutocompletion() const OVERRIDE;
virtual int GetTextWidth() const OVERRIDE;
virtual int GetWidth() const OVERRIDE;
virtual bool IsImeComposing() const OVERRIDE;
// Overridden from content::NotificationObserver:
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
// Sets the colors of the text view according to the theme.
void SetBaseColor();
// Sets the colors of the gray text suggestion view according to the theme.
void UpdateGrayTextViewColors();
// Returns the text view gtk widget. May return NULL if the widget
// has already been destroyed.
GtkWidget* text_view() {
return text_view_;
}
private:
friend class OmniboxViewGtkTest;
CHROMEG_CALLBACK_0(OmniboxViewGtk, void, HandleBeginUserAction,
GtkTextBuffer*);
CHROMEG_CALLBACK_0(OmniboxViewGtk, void, HandleEndUserAction, GtkTextBuffer*);
CHROMEG_CALLBACK_2(OmniboxViewGtk, void, HandleMarkSet, GtkTextBuffer*,
GtkTextIter*, GtkTextMark*);
// As above, but called after the default handler.
CHROMEG_CALLBACK_2(OmniboxViewGtk, void, HandleMarkSetAfter, GtkTextBuffer*,
GtkTextIter*, GtkTextMark*);
CHROMEG_CALLBACK_3(OmniboxViewGtk, void, HandleInsertText, GtkTextBuffer*,
GtkTextIter*, const gchar*, gint);
CHROMEG_CALLBACK_0(OmniboxViewGtk, void, HandleKeymapDirectionChanged,
GdkKeymap*);
CHROMEG_CALLBACK_2(OmniboxViewGtk, void, HandleDeleteRange, GtkTextBuffer*,
GtkTextIter*, GtkTextIter*);
// Unlike above HandleMarkSet and HandleMarkSetAfter, this handler will always
// be connected to the signal.
CHROMEG_CALLBACK_2(OmniboxViewGtk, void, HandleMarkSetAlways, GtkTextBuffer*,
GtkTextIter*, GtkTextMark*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, gboolean, HandleKeyPress, GdkEventKey*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, gboolean, HandleKeyRelease,
GdkEventKey*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, gboolean, HandleViewButtonPress,
GdkEventButton*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, gboolean, HandleViewButtonRelease,
GdkEventButton*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, gboolean, HandleViewFocusIn,
GdkEventFocus*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, gboolean, HandleViewFocusOut,
GdkEventFocus*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, void, HandleViewMoveFocus,
GtkDirectionType);
CHROMEGTK_CALLBACK_3(OmniboxViewGtk, void, HandleViewMoveCursor,
GtkMovementStep, gint, gboolean);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, void, HandleViewSizeRequest,
GtkRequisition*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, void, HandlePopulatePopup, GtkMenu*);
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandlePasteAndGo);
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandleShowURL);
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandleEditSearchEngines);
CHROMEGTK_CALLBACK_6(OmniboxViewGtk, void, HandleDragDataReceived,
GdkDragContext*, gint, gint, GtkSelectionData*,
guint, guint);
CHROMEGTK_CALLBACK_4(OmniboxViewGtk, void, HandleDragDataGet,
GdkDragContext*, GtkSelectionData*, guint, guint);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, void, HandleDragBegin,
GdkDragContext*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, void, HandleDragEnd,
GdkDragContext*);
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandleBackSpace);
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandleCopyClipboard);
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandleCutClipboard);
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandlePasteClipboard);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, gboolean, HandleExposeEvent,
GdkEventExpose*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, void, HandleWidgetDirectionChanged,
GtkTextDirection);
CHROMEGTK_CALLBACK_2(OmniboxViewGtk, void, HandleDeleteFromCursor,
GtkDeleteType, gint);
// We connect to this so we can determine our toplevel window, so we can
// listen to focus change events on it.
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, void, HandleHierarchyChanged,
GtkWidget*);
CHROMEGTK_CALLBACK_1(OmniboxViewGtk, void, HandlePreEditChanged,
const gchar*);
// Undo/redo operations won't trigger "begin-user-action" and
// "end-user-action" signals, so we need to hook into "undo" and "redo"
// signals and call OnBeforePossibleChange()/OnAfterPossibleChange() by
// ourselves.
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandleUndoRedo);
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandleUndoRedoAfter);
CHROMEG_CALLBACK_1(OmniboxViewGtk, void, HandleWindowSetFocus,
GtkWindow*, GtkWidget*);
// Callback function called after context menu is closed.
CHROMEGTK_CALLBACK_0(OmniboxViewGtk, void, HandlePopupMenuDeactivate);
// Callback for the PRIMARY selection clipboard.
static void ClipboardGetSelectionThunk(GtkClipboard* clipboard,
GtkSelectionData* selection_data,
guint info,
gpointer object);
void ClipboardGetSelection(GtkClipboard* clipboard,
GtkSelectionData* selection_data,
guint info);
void HandleCopyOrCutClipboard(bool copy);
// OmniboxView overrides.
virtual int GetOmniboxTextLength() const OVERRIDE;
virtual void EmphasizeURLComponents() OVERRIDE;
// Common implementation for performing a drop on the edit view.
bool OnPerformDropImpl(const base::string16& text);
// Returns the font used in |text_view_|.
gfx::Font GetFont();
// Take control of the PRIMARY selection clipboard with |text|. Use
// |text_buffer_| as the owner, so that this doesn't remove the selection on
// it. This makes use of the above callbacks.
void OwnPrimarySelection(const std::string& text);
// Gets the GTK_TEXT_WINDOW_WIDGET coordinates for |text_view_| that bound the
// given iters.
gfx::Rect WindowBoundsFromIters(GtkTextIter* iter1, GtkTextIter* iter2);
// Actual implementation of SelectAll(), but also provides control over
// whether the PRIMARY selection is set to the selected text (in SelectAll(),
// it isn't, but we want set the selection when the user clicks in the entry).
void SelectAllInternal(bool reversed, bool update_primary_selection);
// Get ready to update |text_buffer_|'s highlighting without making changes to
// the PRIMARY selection. Removes the clipboard from |text_buffer_| and
// blocks the "mark-set" signal handler.
void StartUpdatingHighlightedText();
// Finish updating |text_buffer_|'s highlighting such that future changes will
// automatically update the PRIMARY selection. Undoes
// StartUpdatingHighlightedText()'s changes.
void FinishUpdatingHighlightedText();
// Get the character indices of the current selection. This honors
// direction, cp_max is the insertion point, and cp_min is the bound.
CharRange GetSelection() const;
// Translate from character positions to iterators for the current buffer.
void ItersFromCharRange(const CharRange& range,
GtkTextIter* iter_min,
GtkTextIter* iter_max);
// Returns true if the caret is at the end of the content.
bool IsCaretAtEnd() const;
// Save |selected_text| as the PRIMARY X selection. Unlike
// OwnPrimarySelection(), this won't set an owner or use callbacks.
void SavePrimarySelection(const std::string& selected_text);
// Update the field with |text| and set the selection.
void SetTextAndSelectedRange(const base::string16& text,
const CharRange& range);
// Set the selection to |range|.
void SetSelectedRange(const CharRange& range);
// Adjust the text justification according to the text direction of the widget
// and |text_buffer_|'s content, to make sure the real text justification is
// always in sync with the UI language direction.
void AdjustTextJustification();
// Get the text direction of |text_buffer_|'s content, by searching the first
// character that has a strong direction.
PangoDirection GetContentDirection();
// Returns the selected text.
std::string GetSelectedText() const;
// If the selected text parses as a URL OwnPrimarySelection is invoked.
void UpdatePrimarySelectionIfValidURL();
// Retrieves the first and last iterators in the |text_buffer_|, but excludes
// the anchor holding the |gray_text_view_| widget.
void GetTextBufferBounds(GtkTextIter* start, GtkTextIter* end) const;
// Validates an iterator in the |text_buffer_|, to make sure it doesn't go
// beyond the anchor for holding the |gray_text_view_| widget.
void ValidateTextBufferIter(GtkTextIter* iter) const;
// Adjusts vertical alignment of the |gray_text_view_| in the |text_view_|, to
// make sure they have the same baseline.
void AdjustVerticalAlignmentOfGrayTextView();
// The Browser that contains this omnibox.
Browser* browser_;
// The widget we expose, used for vertically centering the real text edit,
// since the height will change based on the font / font size, etc.
ui::OwnedWidgetGtk alignment_;
// The actual text entry which will be owned by the alignment_. The
// reference will be set to NULL upon destruction to tell if the gtk
// widget tree has been destroyed. This is because gtk destroies child
// widgets if the parent (alignemtn_)'s refcount does not go down to 0.
GtkWidget* text_view_;
GtkTextTagTable* tag_table_;
GtkTextBuffer* text_buffer_;
GtkTextTag* faded_text_tag_;
GtkTextTag* secure_scheme_tag_;
GtkTextTag* security_error_scheme_tag_;
GtkTextTag* normal_text_tag_;
// Objects for the gray suggestion text view.
GtkTextTag* gray_text_anchor_tag_;
// A widget for displaying gray autocompletion text. It'll be attached to a
// child anchor in the |text_buffer_| object.
GtkWidget* gray_text_view_;
// A mark to split the content and the gray text anchor. Wherever the end
// iterator of the text buffer is required, the iterator to this mark should
// be used.
GtkTextMark* gray_text_mark_;
scoped_ptr<OmniboxPopupView> popup_view_;
// When true, the location bar view is read only and also is has a slightly
// different presentation (smaller font size). This is used for popups.
bool popup_window_mode_;
ToolbarModel::SecurityLevel security_level_;
// Selection at the point where the user started using the
// arrows to move around in the popup.
CharRange saved_temporary_selection_;
// Tracking state before and after a possible change.
base::string16 text_before_change_;
CharRange sel_before_change_;
// The most-recently-selected text from the entry that was copied to the
// clipboard. This is updated on-the-fly as the user selects text. This may
// differ from the actual selected text, such as when 'http://' is prefixed to
// the text. It is used in cases where we need to make the PRIMARY selection
// persist even after the user has unhighlighted the text in the view
// (e.g. when they highlight some text and then click to unhighlight it, we
// pass this string to SavePrimarySelection()).
std::string selected_text_;
std::string dragged_text_;
// When we own the X clipboard, this is the text for it.
std::string primary_selection_text_;
// IDs of the signal handlers for "mark-set" on |text_buffer_|.
gulong mark_set_handler_id_;
gulong mark_set_handler_id2_;
// Is the first mouse button currently down? When selection marks get moved,
// we use this to determine if the user was highlighting text with the mouse
// -- if so, we avoid selecting all the text on mouse-up.
bool button_1_pressed_;
// Supplies colors, et cetera.
GtkThemeService* theme_service_;
content::NotificationRegistrar registrar_;
// Indicates if Enter key was pressed.
//
// It's used in the key press handler to detect an Enter key press event
// during sync dispatch of "end-user-action" signal so that an unexpected
// change caused by the event can be ignored in OnAfterPossibleChange().
bool enter_was_pressed_;
// Indicates if Tab key was pressed.
//
// It's only used in the key press handler to detect a Tab key press event
// during sync dispatch of "move-focus" signal.
bool tab_was_pressed_;
// Indicates if Shift key was pressed.
// Used in conjunction with the Tab key to determine if either traversal
// needs to move up the results or if the keyword needs to be cleared.
bool shift_was_pressed_;
// Indicates that user requested to paste clipboard.
// The actual paste clipboard action might be performed later if the
// clipboard is not empty.
bool paste_clipboard_requested_;
// Text to "Paste and go"; set by HandlePopulatePopup() and consumed by
// HandlePasteAndGo().
base::string16 sanitized_text_for_paste_and_go_;
// Indicates if an Enter key press is inserted as text.
// It's used in the key press handler to determine if an Enter key event is
// handled by IME or not.
bool enter_was_inserted_;
// Indicates whether the IME changed the text. It's possible for the IME to
// handle a key event but not change the text contents (e.g., when pressing
// shift+del with no selection).
bool text_changed_;
// Contains the character range that should have a strikethrough (used for
// insecure schemes). If the range is size one or less, no strikethrough
// is needed.
CharRange strikethrough_;
// Indicates if the selected text is suggested text or not. If the selection
// is not suggested text, that means the user manually made the selection.
bool selection_suggested_;
// Was delete pressed?
bool delete_was_pressed_;
// Was the delete key pressed with an empty selection at the end of the edit?
bool delete_at_end_pressed_;
// Indicates if we are handling a key press event.
bool handling_key_press_;
// Indicates if omnibox's content maybe changed by a key press event, so that
// we need to call OnAfterPossibleChange() after handling the event.
// This flag should be set for changes directly caused by a key press event,
// including changes to content text, selection range and pre-edit string.
// Changes caused by function calls like SetUserText() should not affect this
// flag.
bool content_maybe_changed_by_key_press_;
// Set this flag to call UpdatePopup() in lost focus and need to update.
// Because context menu might take the focus, before setting the flag, check
// the focus with model_->has_focus().
bool update_popup_without_focus_;
// On GTK 2.20+ |pre_edit_| and |pre_edit_size_before_change_| will be used.
const bool supports_pre_edit_;
// Stores the text being composed by the input method.
base::string16 pre_edit_;
// Tracking pre-edit state before and after a possible change. We don't need
// to track pre-edit_'s content, as it'll be treated as part of text content.
size_t pre_edit_size_before_change_;
// The view that is going to be focused next. Only valid while handling
// "focus-out" events.
GtkWidget* going_to_focus_;
ui::GtkSignalRegistrar signals_;
DISALLOW_COPY_AND_ASSIGN(OmniboxViewGtk);
};
#endif // CHROME_BROWSER_UI_GTK_OMNIBOX_OMNIBOX_VIEW_GTK_H_