Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

bookmark_manager_gtk.cc

// 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 "chrome/browser/gtk/bookmark_manager_gtk.h"

#include <gdk/gdkkeysyms.h>
#include <vector>

#include "app/gtk_dnd_util.h"
#include "app/l10n_util.h"
#include "app/resource_bundle.h"
#include "base/path_service.h"
#include "base/string16.h"
#include "base/thread.h"
#include "chrome/browser/bookmarks/bookmark_html_writer.h"
#include "chrome/browser/bookmarks/bookmark_manager.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/bookmarks/bookmark_table_model.h"
#include "chrome/browser/bookmarks/bookmark_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/gtk/bookmark_tree_model.h"
#include "chrome/browser/gtk/bookmark_utils_gtk.h"
#include "chrome/browser/gtk/gtk_util.h"
#include "chrome/browser/gtk/menu_gtk.h"
#include "chrome/browser/importer/importer.h"
#include "chrome/browser/importer/importer_data_types.h"
#include "chrome/browser/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/sync/sync_ui_util.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "grit/app_resources.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "grit/theme_resources.h"

namespace {

// Number of bookmarks shown in recently bookmarked.
const int kRecentlyBookmarkedCount = 50;

// IDs for the recently added and search nodes. These values assume that node
// IDs will be strictly non-negative, which is an implementation detail of
// BookmarkModel, so this is sort of a hack.
const int64 kRecentID = -1;
const int64 kSearchID = -2;

// Padding between "Search:" and the entry field, in pixels.
const int kSearchPadding = 5;

// Time between a user action in the search box and when we perform the search.
const int kSearchDelayMS = 200;

// The default width of a column in the right tree view. Since we set the
// columns to ellipsize, if we don't explicitly set a width they will be
// wide enough to display only '...'. This will be overridden if the user
// resizes the column.
const int kDefaultColumnWidth = 200;

// The destination targets that the right tree view accepts for dragging.
const int kDestTargetList[] = { gtk_dnd_util::CHROME_BOOKMARK_ITEM, -1 };

// We only have one manager open at a time.
BookmarkManagerGtk* manager = NULL;

// Observer installed on the importer. When done importing the newly created
// folder is selected in the bookmark manager.
// This class is taken almost directly from BookmarkManagerView and should be
// kept in sync with it.
class ImportObserverImpl : public ImportObserver {
 public:
  explicit ImportObserverImpl(Profile* profile) : profile_(profile) {
    BookmarkModel* model = profile->GetBookmarkModel();
    initial_other_count_ = model->other_node()->GetChildCount();
  }

  virtual void ImportCanceled() {
    delete this;
  }

  virtual void ImportComplete() {
    // We aren't needed anymore.
    MessageLoop::current()->DeleteSoon(FROM_HERE, this);

    if (!manager || manager->profile() != profile_)
      return;

    BookmarkModel* model = profile_->GetBookmarkModel();
    int other_count = model->other_node()->GetChildCount();
    if (other_count == initial_other_count_ + 1) {
      const BookmarkNode* imported_node =
          model->other_node()->GetChild(initial_other_count_);
      manager->SelectInTree(imported_node, true);
    }
  }

 private:
  Profile* profile_;
  // Number of children in the other bookmarks folder at the time we were
  // created.
  int initial_other_count_;

  DISALLOW_COPY_AND_ASSIGN(ImportObserverImpl);
};

void SetMenuBarStyle() {
  static bool style_was_set = false;

  if (style_was_set)
    return;
  style_was_set = true;

  gtk_rc_parse_string(
      "style \"chrome-bm-menubar\" {"
      "  GtkMenuBar::shadow-type = GTK_SHADOW_NONE"
      "}"
      "widget \"*chrome-bm-menubar\" style \"chrome-bm-menubar\"");
}

bool CursorIsOverSelection(GtkTreeView* tree_view) {
  bool rv = false;
  gint x, y;
  gtk_widget_get_pointer(GTK_WIDGET(tree_view), &x, &y);
  gint bx, by;
  gtk_tree_view_convert_widget_to_bin_window_coords(tree_view, x, y, &bx, &by);
  GtkTreePath* path;
  if (gtk_tree_view_get_path_at_pos(tree_view, bx, by, &path,
                                    NULL, NULL, NULL)) {
    if (gtk_tree_selection_path_is_selected(
        gtk_tree_view_get_selection(tree_view), path)) {
      rv = true;
    }

    gtk_tree_path_free(path);
  }

  return rv;
}

}  // namespace

// BookmarkManager -------------------------------------------------------------

void BookmarkManager::SelectInTree(Profile* profile, const BookmarkNode* node) {
  if (manager && manager->profile() == profile)
    manager->SelectInTree(node, false);
}

void BookmarkManager::Show(Profile* profile) {
  BookmarkManagerGtk::Show(profile);
}

// BookmarkManagerGtk, public --------------------------------------------------

void BookmarkManagerGtk::SelectInTree(const BookmarkNode* node, bool expand) {
  if (expand)
    DCHECK(node->is_folder());

  // Expand the left tree view to |node| if |node| is a folder, or to the parent
  // folder of |node| if it is a URL.
  GtkTreeIter iter = { 0, };
  int64 id = node->is_folder() ? node->id() : node->GetParent()->id();
  if (RecursiveFind(GTK_TREE_MODEL(left_store_), &iter, id)) {
    GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(left_store_),
                                                &iter);
    gtk_tree_view_expand_to_path(GTK_TREE_VIEW(left_tree_view_), path);
    gtk_tree_selection_select_path(left_selection(), path);
    if (expand)
      gtk_tree_view_expand_row(GTK_TREE_VIEW(left_tree_view_), path, true);

    gtk_tree_path_free(path);
  }

  if (node->is_url()) {
    GtkTreeIter iter;
    bool found = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(right_store_),
                                               &iter);
    while (found) {
      if (node->id() == GetRowIDAt(GTK_TREE_MODEL(right_store_), &iter)) {
        gtk_tree_selection_select_iter(right_selection(), &iter);
        break;
      }

      found = gtk_tree_model_iter_next(GTK_TREE_MODEL(right_store_), &iter);
    }

    DCHECK(found);
  }
}

// static
void BookmarkManagerGtk::Show(Profile* profile) {
  if (!profile->GetBookmarkModel())
    return;
  if (!manager)
    manager = new BookmarkManagerGtk(profile);
  else
    gtk_window_present(GTK_WINDOW(manager->window_));
}

void BookmarkManagerGtk::BookmarkManagerGtk::Loaded(BookmarkModel* model) {
  BuildLeftStore();
  BuildRightStore();
  g_signal_connect(left_selection(), "changed",
                   G_CALLBACK(OnLeftSelectionChanged), this);
  ResetOrganizeMenu(false);
}

void BookmarkManagerGtk::BookmarkModelBeingDeleted(BookmarkModel* model) {
  gtk_widget_destroy(window_);
}

void BookmarkManagerGtk::BookmarkNodeMoved(BookmarkModel* model,
                                           const BookmarkNode* old_parent,
                                           int old_index,
                                           const BookmarkNode* new_parent,
                                           int new_index) {
  BookmarkNodeRemoved(model, old_parent, old_index,
                      new_parent->GetChild(new_index));
  BookmarkNodeAdded(model, new_parent, new_index);
}

void BookmarkManagerGtk::BookmarkNodeAdded(BookmarkModel* model,
                                           const BookmarkNode* parent,
                                           int index) {
  const BookmarkNode* node = parent->GetChild(index);
  if (node->is_folder()) {
    GtkTreeIter iter = { 0, };
    if (RecursiveFind(GTK_TREE_MODEL(left_store_), &iter, parent->id()))
      bookmark_utils::AddToTreeStoreAt(node, 0, left_store_, NULL, &iter);
  }
}

void BookmarkManagerGtk::BookmarkNodeRemoved(BookmarkModel* model,
                                             const BookmarkNode* parent,
                                             int old_index,
                                             const BookmarkNode* node) {
  if (node->is_folder()) {
    GtkTreeIter iter = { 0, };
    if (RecursiveFind(GTK_TREE_MODEL(left_store_), &iter, node->id())) {
      // If we are deleting the currently selected folder, set the selection to
      // its parent.
      if (gtk_tree_selection_iter_is_selected(left_selection(), &iter)) {
        GtkTreeIter parent;
        gtk_tree_model_iter_parent(GTK_TREE_MODEL(left_store_), &parent, &iter);
        gtk_tree_selection_select_iter(left_selection(), &parent);
      }

      gtk_tree_store_remove(left_store_, &iter);
    }
  }
}

void BookmarkManagerGtk::BookmarkNodeChanged(BookmarkModel* model,
                                             const BookmarkNode* node) {
  if (node->is_folder()) {
    GtkTreeIter iter = { 0, };
    if (RecursiveFind(GTK_TREE_MODEL(left_store_), &iter, node->id())) {
      gtk_tree_store_set(left_store_, &iter,
                         bookmark_utils::FOLDER_NAME,
                         WideToUTF8(node->GetTitle()).c_str(),
                         bookmark_utils::ITEM_ID, node->id(),
                         -1);
    }
  }
}

void BookmarkManagerGtk::BookmarkNodeChildrenReordered(
    BookmarkModel* model, const BookmarkNode* node) {
  // TODO(estade): reorder in the left tree view.
}

void BookmarkManagerGtk::BookmarkNodeFavIconLoaded(BookmarkModel* model,
                                                   const BookmarkNode* node) {
  // I don't think we have anything to do, as we should never get this for a
  // folder node and we handle it via OnItemsChanged for any URL node.
}

void BookmarkManagerGtk::OnModelChanged() {
  ResetRightStoreModel();
}

void BookmarkManagerGtk::SetColumnValues(int row, GtkTreeIter* iter) {
  // TODO(estade): building the path could be optimized out when we aren't
  // showing the path column.
  const BookmarkNode* node = right_tree_model_->GetNodeForRow(row);
  GdkPixbuf* pixbuf = bookmark_utils::GetPixbufForNode(node, model_, true);
  std::wstring title =
      right_tree_model_->GetText(row, IDS_BOOKMARK_TABLE_TITLE);
  std::wstring url = right_tree_model_->GetText(row, IDS_BOOKMARK_TABLE_URL);
  std::wstring path = right_tree_model_->GetText(row, IDS_BOOKMARK_TABLE_PATH);
  gtk_list_store_set(right_store_, iter,
                     RIGHT_PANE_PIXBUF, pixbuf,
                     RIGHT_PANE_TITLE, WideToUTF8(title).c_str(),
                     RIGHT_PANE_URL, WideToUTF8(url).c_str(),
                     RIGHT_PANE_PATH, WideToUTF8(path).c_str(),
                     RIGHT_PANE_ID, node->id(), -1);
  g_object_unref(pixbuf);
}

// BookmarkManagerGtk, private -------------------------------------------------

BookmarkManagerGtk::BookmarkManagerGtk(Profile* profile)
    : profile_(profile),
      model_(profile->GetBookmarkModel()),
      organize_is_for_left_(true),
      sync_status_menu_(NULL),
      sync_service_(NULL),
      sync_relogin_required_(false),
      search_factory_(this),
      select_file_dialog_(SelectFileDialog::Create(this)),
      delaying_mousedown_(false),
      sending_delayed_mousedown_(false),
      ignore_rightclicks_(false) {
  InitWidgets();
  ConnectAccelerators();

  model_->AddObserver(this);
  if (model_->IsLoaded())
    Loaded(model_);

  if (profile_->GetProfileSyncService()) {
    sync_service_ = profile_->GetProfileSyncService();
    sync_service_->AddObserver(this);
    UpdateSyncStatus();
  }

  gtk_widget_show_all(window_);
}

BookmarkManagerGtk::~BookmarkManagerGtk() {
  g_browser_process->local_state()->SetInteger(
      prefs::kBookmarkManagerSplitLocation,
      gtk_paned_get_position(GTK_PANED(paned_)));
  SaveColumnConfiguration();
  model_->RemoveObserver(this);

  if (sync_service_)
    sync_service_->RemoveObserver(this);

  gtk_accel_group_disconnect_key(accel_group_, GDK_w, GDK_CONTROL_MASK);
  gtk_window_remove_accel_group(GTK_WINDOW(window_), accel_group_);
  g_object_unref(accel_group_);
}

void BookmarkManagerGtk::InitWidgets() {
  window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window_),
      l10n_util::GetStringUTF8(IDS_BOOKMARK_MANAGER_TITLE).c_str());
  g_signal_connect(
      window_, "configure-event", G_CALLBACK(OnWindowConfiguredThunk), this);
  g_signal_connect(
      window_, "destroy", G_CALLBACK(OnWindowDestroyedThunk), this);
  g_signal_connect(
      window_, "window-state-event", G_CALLBACK(OnWindowStateChangedThunk),
      this);

  // Add this window to its own unique window group to allow for
  // window-to-parent modality.
  gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(window_));
  g_object_unref(gtk_window_get_group(GTK_WINDOW(window_)));

  SetInitialWindowSize();

  gint x = 0, y = 0, width = 1, height = 1;
  gtk_window_get_position(GTK_WINDOW(window_), &x, &y);
  gtk_window_get_size(GTK_WINDOW(window_), &width, &height);
  window_bounds_.SetRect(x, y, width, height);

  // Build the organize and tools menus.
  organize_ = gtk_menu_item_new_with_label(
      l10n_util::GetStringUTF8(IDS_BOOKMARK_MANAGER_ORGANIZE_MENU).c_str());

  GtkWidget* import_item = gtk_menu_item_new_with_mnemonic(
      gtk_util::ConvertAcceleratorsFromWindowsStyle(
          l10n_util::GetStringUTF8(IDS_BOOKMARK_MANAGER_IMPORT_MENU)).c_str());
  g_signal_connect(import_item, "activate",
                   G_CALLBACK(OnImportItemActivatedThunk), this);

  GtkWidget* export_item = gtk_menu_item_new_with_mnemonic(
      gtk_util::ConvertAcceleratorsFromWindowsStyle(
          l10n_util::GetStringUTF8(IDS_BOOKMARK_MANAGER_EXPORT_MENU)).c_str());
  g_signal_connect(export_item, "activate",
                   G_CALLBACK(OnExportItemActivatedThunk), this);

  GtkWidget* tools_menu = gtk_menu_new();
  gtk_menu_shell_append(GTK_MENU_SHELL(tools_menu), import_item);
  gtk_menu_shell_append(GTK_MENU_SHELL(tools_menu), export_item);

  GtkWidget* tools = gtk_menu_item_new_with_label(
      l10n_util::GetStringUTF8(IDS_BOOKMARK_MANAGER_TOOLS_MENU).c_str());
  gtk_menu_item_set_submenu(GTK_MENU_ITEM(tools), tools_menu);

  // Build the sync status menu item.
  sync_status_menu_ = gtk_menu_item_new_with_label("");
  g_signal_connect(sync_status_menu_, "activate",
                   G_CALLBACK(OnSyncStatusMenuActivatedThunk), this);

  GtkWidget* menu_bar = gtk_menu_bar_new();
  gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), organize_);
  gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), tools);
  gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), sync_status_menu_);
  SetMenuBarStyle();
  gtk_widget_set_name(menu_bar, "chrome-bm-menubar");

  GtkWidget* search_label = gtk_label_new(
      l10n_util::GetStringUTF8(IDS_BOOKMARK_MANAGER_SEARCH_TITLE).c_str());
  search_entry_ = gtk_entry_new();
  g_signal_connect(search_entry_, "changed",
                   G_CALLBACK(OnSearchTextChangedThunk), this);

  GtkWidget* hbox = gtk_hbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(hbox), menu_bar, FALSE, FALSE, 0);
  gtk_box_pack_end(GTK_BOX(hbox), search_entry_, FALSE, FALSE, 0);
  gtk_box_pack_end(GTK_BOX(hbox), search_label, FALSE, FALSE, kSearchPadding);

  GtkWidget* left_pane = MakeLeftPane();
  GtkWidget* right_pane = MakeRightPane();

  paned_ = gtk_hpaned_new();
  gtk_paned_pack1(GTK_PANED(paned_), left_pane, FALSE, FALSE);
  gtk_paned_pack2(GTK_PANED(paned_), right_pane, TRUE, FALSE);

  // Set the initial position of the pane divider.
  int split_x = g_browser_process->local_state()->GetInteger(
      prefs::kBookmarkManagerSplitLocation);
  if (split_x == -1) {
    split_x = width / 3;
  } else {
    int min_split_size = width / 8;
    // Make sure the user can see both the tree/table.
    split_x = std::min(width - min_split_size,
                       std::max(min_split_size, split_x));
  }
  gtk_paned_set_position(GTK_PANED(paned_), split_x);

  GtkWidget* vbox = gtk_vbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), paned_, TRUE, TRUE, 0);
  gtk_container_add(GTK_CONTAINER(window_), vbox);
}

void BookmarkManagerGtk::ConnectAccelerators() {
  accel_group_ = gtk_accel_group_new();
  gtk_window_add_accel_group(GTK_WINDOW(window_), accel_group_);

  gtk_accel_group_connect(accel_group_,
                          GDK_w, GDK_CONTROL_MASK, GtkAccelFlags(0),
                          g_cclosure_new(G_CALLBACK(OnGtkAccelerator),
                                         this, NULL));
}

GtkWidget* BookmarkManagerGtk::MakeLeftPane() {
  left_store_ = bookmark_utils::MakeFolderTreeStore();
  left_tree_view_ = bookmark_utils::MakeTreeViewForStore(left_store_);

  // When a row is collapsed that contained the selected node, we want to select
  // it.
  g_signal_connect(left_tree_view_, "row-collapsed",
                   G_CALLBACK(OnLeftTreeViewRowCollapsedThunk), this);
  g_signal_connect(left_tree_view_, "focus-in-event",
                   G_CALLBACK(OnLeftTreeViewFocusInThunk), this);
  g_signal_connect(left_tree_view_, "button-press-event",
                   G_CALLBACK(OnTreeViewButtonPressThunk), this);
  g_signal_connect(left_tree_view_, "button-release-event",
                   G_CALLBACK(OnTreeViewButtonReleaseThunk), this);
  g_signal_connect(left_tree_view_, "key-press-event",
                   G_CALLBACK(OnTreeViewKeyPressThunk), this);

  GtkCellRenderer* cell_renderer_text = bookmark_utils::GetCellRendererText(
      GTK_TREE_VIEW(left_tree_view_));
  g_signal_connect(cell_renderer_text, "edited",
                   G_CALLBACK(OnFolderNameEdited), this);

  // The left side is only a drag destination (not a source).
  gtk_drag_dest_set(left_tree_view_, GTK_DEST_DEFAULT_DROP,
                    NULL, 0, GDK_ACTION_MOVE);
  gtk_dnd_util::SetDestTargetList(left_tree_view_, kDestTargetList);

  g_signal_connect(left_tree_view_, "drag-data-received",
                   G_CALLBACK(OnLeftTreeViewDragReceivedThunk), this);
  g_signal_connect(left_tree_view_, "drag-motion",
                   G_CALLBACK(OnLeftTreeViewDragMotionThunk), this);

  GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
                                      GTK_SHADOW_ETCHED_IN);
  gtk_container_add(GTK_CONTAINER(scrolled), left_tree_view_);

  return scrolled;
}

GtkWidget* BookmarkManagerGtk::MakeRightPane() {
  right_store_ = gtk_list_store_new(RIGHT_PANE_NUM,
      GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
      G_TYPE_INT64);
  right_tree_adapter_.reset(new gtk_tree::TableAdapter(this, right_store_,
                                                       NULL));

  title_column_ = gtk_tree_view_column_new();
  gtk_tree_view_column_set_title(title_column_,
      l10n_util::GetStringUTF8(IDS_BOOKMARK_TABLE_TITLE).c_str());
  GtkCellRenderer* image_renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_column_pack_start(title_column_, image_renderer, FALSE);
  gtk_tree_view_column_add_attribute(title_column_, image_renderer,
                                     "pixbuf", RIGHT_PANE_PIXBUF);
  GtkCellRenderer* text_renderer = gtk_cell_renderer_text_new();
  g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
  gtk_tree_view_column_pack_start(title_column_, text_renderer, TRUE);
  gtk_tree_view_column_add_attribute(title_column_, text_renderer,
                                     "text", RIGHT_PANE_TITLE);

  url_column_ = gtk_tree_view_column_new_with_attributes(
      l10n_util::GetStringUTF8(IDS_BOOKMARK_TABLE_URL).c_str(),
      text_renderer, "text", RIGHT_PANE_URL, NULL);

  path_column_ = gtk_tree_view_column_new_with_attributes(
      l10n_util::GetStringUTF8(IDS_BOOKMARK_TABLE_PATH).c_str(),
      text_renderer, "text", RIGHT_PANE_PATH, NULL);

  right_tree_view_ = gtk_tree_view_new_with_model(GTK_TREE_MODEL(right_store_));
  // Let |tree_view| own the store.
  g_object_unref(right_store_);
  gtk_tree_view_append_column(GTK_TREE_VIEW(right_tree_view_), title_column_);
  gtk_tree_view_append_column(GTK_TREE_VIEW(right_tree_view_), url_column_);
  gtk_tree_view_append_column(GTK_TREE_VIEW(right_tree_view_), path_column_);
  gtk_tree_selection_set_mode(right_selection(), GTK_SELECTION_MULTIPLE);

  g_signal_connect(right_tree_view_, "row-activated",
                   G_CALLBACK(OnRightTreeViewRowActivatedThunk), this);
  g_signal_connect(right_selection(), "changed",
                   G_CALLBACK(OnRightSelectionChanged), this);
  g_signal_connect(right_tree_view_, "focus-in-event",
                   G_CALLBACK(OnRightTreeViewFocusInThunk), this);
  g_signal_connect(right_tree_view_, "button-press-event",
                   G_CALLBACK(OnRightTreeViewButtonPressThunk), this);
  g_signal_connect(right_tree_view_, "motion-notify-event",
                   G_CALLBACK(OnRightTreeViewMotionThunk), this);
  // This handler just controls showing the context menu.
  g_signal_connect(right_tree_view_, "button-press-event",
                   G_CALLBACK(OnTreeViewButtonPressThunk), this);
  g_signal_connect(right_tree_view_, "button-release-event",
                   G_CALLBACK(OnTreeViewButtonReleaseThunk), this);
  g_signal_connect(right_tree_view_, "key-press-event",
                   G_CALLBACK(OnTreeViewKeyPressThunk), this);

  // GDK_ACTION_MOVE is necessary to reorder bookmarks within the
  // right tree.  COPY and LINK are necessary for drags to the
  // Gnome desktop (nautilus).
  gtk_drag_source_set(right_tree_view_, GDK_BUTTON1_MASK, NULL, 0,
      static_cast<GdkDragAction>(GDK_ACTION_MOVE | GDK_ACTION_COPY |
                                 GDK_ACTION_LINK));
  gtk_dnd_util::SetSourceTargetListFromCodeMask(
      right_tree_view_, bookmark_utils::GetCodeMask(false));

  // We connect to drag dest signals, but we don't actually enable the widget
  // as a drag destination unless it corresponds to the contents of a folder.
  // See BuildRightStore().
  g_signal_connect(right_tree_view_, "drag-data-get",
                   G_CALLBACK(OnRightTreeViewDragGetThunk), this);
  g_signal_connect(right_tree_view_, "drag-data-received",
                   G_CALLBACK(OnRightTreeViewDragReceivedThunk), this);
  g_signal_connect(right_tree_view_, "drag-motion",
                   G_CALLBACK(OnRightTreeViewDragMotionThunk), this);
  g_signal_connect(right_tree_view_, "drag-begin",
                   G_CALLBACK(OnRightTreeViewDragBeginThunk), this);

  GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
                                      GTK_SHADOW_ETCHED_IN);
  gtk_container_add(GTK_CONTAINER(scrolled), right_tree_view_);

  return scrolled;
}

// static
BookmarkManagerGtk* BookmarkManagerGtk::GetCurrentManager() {
  return manager;
}

void BookmarkManagerGtk::SetInitialWindowSize() {
  // If we previously saved the window's bounds, use them.
  if (g_browser_process->local_state()) {
    const DictionaryValue* placement_pref =
        g_browser_process->local_state()->GetDictionary(
            prefs::kBookmarkManagerPlacement);
    int top = 0, left = 0, bottom = 1, right = 1;
    if (placement_pref &&
        placement_pref->GetInteger(L"top", &top) &&
        placement_pref->GetInteger(L"left", &left) &&
        placement_pref->GetInteger(L"bottom", &bottom) &&
        placement_pref->GetInteger(L"right", &right)) {
      int width = std::max(1, right - left);
      int height = std::max(1, bottom - top);
      gtk_window_resize(GTK_WINDOW(window_), width, height);
      return;
    }
  }

  // Otherwise, just set a default size (GTK will override this if it's not
  // large enough to hold the window's contents).
  gtk_widget_realize(window_);
  gtk_util::SetWindowSizeFromResources(
      GTK_WINDOW(window_),
      IDS_BOOKMARK_MANAGER_DIALOG_WIDTH_CHARS,
      IDS_BOOKMARK_MANAGER_DIALOG_HEIGHT_LINES,
      true);
}

void BookmarkManagerGtk::ResetOrganizeMenu(bool left) {
  organize_is_for_left_ = left;
  const BookmarkNode* parent = GetFolder();
  std::vector<const BookmarkNode*> nodes;
  if (!left)
    nodes = GetRightSelection();
  else if (parent)
    nodes.push_back(parent);

  // Whether the "recently added" or "search" options are selected on the left.
  bool is_other = !parent;

  // We DeleteSoon on the old one to give any reference holders (e.g.
  // the event that caused this reset) a chance to release their refs.
  MenuGtk* old_menu = organize_menu_.release();
  if (old_menu)
    MessageLoop::current()->DeleteSoon(FROM_HERE, old_menu);

  BookmarkContextMenuController* old_controller =
      organize_menu_controller_.release();
  if (old_controller)
    MessageLoop::current()->DeleteSoon(FROM_HERE, old_controller);

  organize_menu_controller_.reset(
      new BookmarkContextMenuController(GTK_WINDOW(window_), this, profile_,
        NULL, parent, nodes, is_other ?
        BookmarkContextMenuController::BOOKMARK_MANAGER_ORGANIZE_MENU_OTHER :
        BookmarkContextMenuController::BOOKMARK_MANAGER_ORGANIZE_MENU));
  organize_menu_.reset(
      new MenuGtk(NULL, organize_menu_controller_->menu_model()));
  gtk_menu_item_set_submenu(GTK_MENU_ITEM(organize_),
                            organize_menu_->widget());

  // Also rebuild the context menu.
  old_controller = context_menu_controller_.release();
  if (old_controller)
    MessageLoop::current()->DeleteSoon(FROM_HERE, old_controller);
  context_menu_controller_.reset(
      new BookmarkContextMenuController(GTK_WINDOW(window_), this, profile_,
        NULL, parent, nodes, is_other ?
        BookmarkContextMenuController::BOOKMARK_MANAGER_TABLE_OTHER :
        BookmarkContextMenuController::BOOKMARK_MANAGER_TABLE));
  context_menu_.reset(
      new MenuGtk(NULL, context_menu_controller_->menu_model()));
}

void BookmarkManagerGtk::BuildLeftStore() {
  GtkTreeIter select_iter;
  bookmark_utils::AddToTreeStore(model_,
      model_->GetBookmarkBarNode()->id(), left_store_, &select_iter);
  gtk_tree_selection_select_iter(left_selection(), &select_iter);

  // TODO(estade): is there a decent stock icon we can use here?
  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
  gtk_tree_store_append(left_store_, &select_iter, NULL);
  gtk_tree_store_set(left_store_, &select_iter,
      bookmark_utils::FOLDER_ICON,
      rb.GetPixbufNamed(IDR_BOOKMARK_MANAGER_RECENT_ICON),
      bookmark_utils::FOLDER_NAME,
      l10n_util::GetStringUTF8(
          IDS_BOOKMARK_TREE_RECENTLY_BOOKMARKED_NODE_TITLE).c_str(),
      bookmark_utils::ITEM_ID, kRecentID,
      bookmark_utils::IS_EDITABLE, FALSE,
      -1);

  GdkPixbuf* search_icon = gtk_widget_render_icon(
      window_, GTK_STOCK_FIND, GTK_ICON_SIZE_MENU, NULL);
  gtk_tree_store_append(left_store_, &select_iter, NULL);
  gtk_tree_store_set(left_store_, &select_iter,
      bookmark_utils::FOLDER_ICON,
      search_icon,
      bookmark_utils::FOLDER_NAME,
      l10n_util::GetStringUTF8(
          IDS_BOOKMARK_TREE_SEARCH_NODE_TITLE).c_str(),
      bookmark_utils::ITEM_ID, kSearchID,
      bookmark_utils::IS_EDITABLE, FALSE,
      -1);
  g_object_unref(search_icon);
}

void BookmarkManagerGtk::BuildRightStore() {
  right_tree_adapter_->OnModelChanged();
}

void BookmarkManagerGtk::ResetRightStoreModel() {
  const BookmarkNode* node = GetFolder();

  if (node) {
    SaveColumnConfiguration();
    gtk_tree_view_column_set_visible(path_column_, FALSE);
    SizeColumns();

    right_tree_model_.reset(
        BookmarkTableModel::CreateBookmarkTableModelForFolder(model_, node));

    gtk_drag_dest_set(right_tree_view_, GTK_DEST_DEFAULT_ALL, NULL, 0,
                      GDK_ACTION_MOVE);
    gtk_dnd_util::SetDestTargetList(right_tree_view_, kDestTargetList);
  } else {
    SaveColumnConfiguration();
    gtk_tree_view_column_set_visible(path_column_, TRUE);
    SizeColumns();

    int id = GetSelectedRowID();
    if (kRecentID == id) {
      right_tree_model_.reset(
          BookmarkTableModel::CreateRecentlyBookmarkedModel(model_));
    } else {  // kSearchID == id
      search_factory_.RevokeAll();

      std::wstring search_text(
          UTF8ToWide(gtk_entry_get_text(GTK_ENTRY(search_entry_))));
      std::wstring languages =
          profile_->GetPrefs()->GetString(prefs::kAcceptLanguages);
      right_tree_model_.reset(
          BookmarkTableModel::CreateSearchTableModel(model_, search_text,
                                                     languages));
    }

    gtk_drag_dest_unset(right_tree_view_);
  }

  right_tree_adapter_->SetModel(right_tree_model_.get());
}

int64 BookmarkManagerGtk::GetRowIDAt(GtkTreeModel* model, GtkTreeIter* iter) {
  bool left = model == GTK_TREE_MODEL(left_store_);
  GValue value = { 0, };
  if (left)
    gtk_tree_model_get_value(model, iter, bookmark_utils::ITEM_ID, &value);
  else
    gtk_tree_model_get_value(model, iter, RIGHT_PANE_ID, &value);
  int64 id = g_value_get_int64(&value);
  g_value_unset(&value);
  return id;
}

const BookmarkNode* BookmarkManagerGtk::GetNodeAt(GtkTreeModel* model,
                                                  GtkTreeIter* iter) {
  int64 id = GetRowIDAt(model, iter);
  if (id > 0)
    return model_->GetNodeByID(id);
  else
    return NULL;
}

const BookmarkNode* BookmarkManagerGtk::GetFolder() {
  GtkTreeModel* model;
  GtkTreeIter iter;
  if (!gtk_tree_selection_get_selected(left_selection(), &model, &iter))
    return NULL;
  return GetNodeAt(model, &iter);
}

int BookmarkManagerGtk::GetSelectedRowID() {
  GtkTreeModel* model;
  GtkTreeIter iter;
  gtk_tree_selection_get_selected(left_selection(), &model, &iter);
  return GetRowIDAt(model, &iter);
}

std::vector<const BookmarkNode*> BookmarkManagerGtk::GetRightSelection() {
  GtkTreeModel* model;
  GList* paths = gtk_tree_selection_get_selected_rows(right_selection(),
                                                      &model);
  std::vector<const BookmarkNode*> nodes;
  for (GList* item = paths; item; item = item->next) {
    GtkTreeIter iter;
    gtk_tree_model_get_iter(model, &iter,
                            reinterpret_cast<GtkTreePath*>(item->data));
    nodes.push_back(GetNodeAt(model, &iter));
  }
  g_list_free(paths);

  return nodes;
}

void BookmarkManagerGtk::SizeColumn(GtkTreeViewColumn* column,
                                    const wchar_t* prefname) {
  gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
  gtk_tree_view_column_set_resizable(column, TRUE);

  PrefService* prefs = profile_->GetPrefs();
  if (!prefs)
    return;

  int width = prefs->GetInteger(prefname);
  if (width <= 0)
    width = kDefaultColumnWidth;
  gtk_tree_view_column_set_fixed_width(column, width);
}

void BookmarkManagerGtk::SizeColumns() {
  if (gtk_tree_view_column_get_visible(path_column_)) {
    SizeColumn(title_column_, prefs::kBookmarkTableNameWidth2);
    SizeColumn(url_column_, prefs::kBookmarkTableURLWidth2);
    SizeColumn(path_column_, prefs::kBookmarkTablePathWidth);
  } else {
    SizeColumn(title_column_, prefs::kBookmarkTableNameWidth1);
    SizeColumn(url_column_, prefs::kBookmarkTableURLWidth1);
  }
}

void BookmarkManagerGtk::SaveColumnConfiguration() {
  PrefService* prefs = profile_->GetPrefs();
  if (!prefs)
    return;

  if (gtk_tree_view_column_get_visible(path_column_)) {
    prefs->SetInteger(prefs::kBookmarkTableNameWidth2,
                      gtk_tree_view_column_get_width(title_column_));
    prefs->SetInteger(prefs::kBookmarkTableURLWidth2,
                      gtk_tree_view_column_get_width(url_column_));
    prefs->SetInteger(prefs::kBookmarkTablePathWidth,
                      gtk_tree_view_column_get_width(path_column_));
  } else {
    prefs->SetInteger(prefs::kBookmarkTableNameWidth1,
                      gtk_tree_view_column_get_width(title_column_));
    prefs->SetInteger(prefs::kBookmarkTableURLWidth1,
                      gtk_tree_view_column_get_width(url_column_));
  }
}

void BookmarkManagerGtk::SendDelayedMousedown() {
  sending_delayed_mousedown_ = true;
  gtk_propagate_event(right_tree_view_,
                      reinterpret_cast<GdkEvent*>(&mousedown_event_));
  sending_delayed_mousedown_ = false;
  delaying_mousedown_ = false;
}

bool BookmarkManagerGtk::RecursiveFind(GtkTreeModel* model, GtkTreeIter* iter,
                                       int64 target) {
  GValue value = { 0, };
  bool left = model == GTK_TREE_MODEL(left_store_);
  if (left) {
    if (iter->stamp == 0)
      gtk_tree_model_get_iter_first(GTK_TREE_MODEL(left_store_), iter);
    gtk_tree_model_get_value(model, iter, bookmark_utils::ITEM_ID, &value);
  } else {
    if (iter->stamp == 0)
      gtk_tree_model_get_iter_first(GTK_TREE_MODEL(right_store_), iter);
    gtk_tree_model_get_value(model, iter, RIGHT_PANE_ID, &value);
  }

  int64 id = g_value_get_int64(&value);
  g_value_unset(&value);

  if (id == target) {
    return true;
  }

  GtkTreeIter child;
  // Check the first child.
  if (gtk_tree_model_iter_children(model, &child, iter)) {
    if (RecursiveFind(model, &child, target)) {
      *iter = child;
      return true;
    }
  }

  // Check siblings.
  while (gtk_tree_model_iter_next(model, iter)) {
    if (RecursiveFind(model, iter, target))
      return true;
  }

  return false;
}

void BookmarkManagerGtk::PerformSearch() {
  bool search_selected = GetSelectedRowID() == kSearchID;

  // If the search node is not selected, we'll select it to force a search.
  if (!search_selected) {
    int index =
        gtk_tree_model_iter_n_children(GTK_TREE_MODEL(left_store_), NULL) - 1;
    GtkTreeIter iter;
    gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(left_store_), &iter, NULL,
                                  index);
    gtk_tree_selection_select_iter(left_selection(), &iter);
  } else {
    BuildRightStore();
  }
}

void BookmarkManagerGtk::OnSearchTextChanged(GtkWidget* widget) {
  search_factory_.RevokeAll();
  MessageLoop::current()->PostDelayedTask(FROM_HERE,
      search_factory_.NewRunnableMethod(&BookmarkManagerGtk::PerformSearch),
      kSearchDelayMS);
}

// static
void BookmarkManagerGtk::OnLeftSelectionChanged(GtkTreeSelection* selection,
                                                BookmarkManagerGtk* bm) {
  // If the selection is (newly) empty, then make the right tree view take
  // over the organize menu.
  if (gtk_tree_selection_count_selected_rows(selection) == 0) {
    bm->ResetOrganizeMenu(false);
    return;
  }

  bm->ResetOrganizeMenu(true);
  bm->BuildRightStore();
}

// static
void BookmarkManagerGtk::OnRightSelectionChanged(GtkTreeSelection* selection,
                                                 BookmarkManagerGtk* bm) {
  // If the selection is (newly) empty, then make the left tree view take
  // over the organize menu.
  if (gtk_tree_selection_count_selected_rows(selection) == 0) {
    bm->ResetOrganizeMenu(true);
    return;
  }

  bm->ResetOrganizeMenu(false);
}

void BookmarkManagerGtk::OnLeftTreeViewDragReceived(
    GtkWidget* tree_view,
    GdkDragContext* context,
    gint x,
    gint y,
    GtkSelectionData* selection_data,
    guint target_type,
    guint time) {
  gboolean get_nodes_success = FALSE;
  gboolean delete_selection_data = FALSE;

  std::vector<const BookmarkNode*> nodes =
      bookmark_utils::GetNodesFromSelection(context, selection_data,
                                            target_type,
                                            profile_,
                                            &delete_selection_data,
                                            &get_nodes_success);

  if (nodes.empty() || !get_nodes_success) {
    gtk_drag_finish(context, FALSE, delete_selection_data, time);
    return;
  }

  GtkTreePath* path;
  GtkTreeViewDropPosition pos;
  gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y,
                                    &path, &pos);
  if (!path) {
    gtk_drag_finish(context, FALSE, delete_selection_data, time);
    return;
  }

  GtkTreeIter iter;
  gtk_tree_model_get_iter(GTK_TREE_MODEL(left_store_), &iter, path);
  const BookmarkNode* folder = GetNodeAt(GTK_TREE_MODEL(left_store_), &iter);
  gboolean dnd_success = FALSE;

  if (folder) {
    for (std::vector<const BookmarkNode*>::iterator it = nodes.begin();
         it != nodes.end(); ++it) {
      // Don't try to drop a node into one of its descendants.
      if (!folder->HasAncestor(*it)) {
        model_->Move(*it, folder, folder->GetChildCount());
        dnd_success = TRUE;
      }
    }
  }

  gtk_tree_path_free(path);
  gtk_drag_finish(context, dnd_success, delete_selection_data && dnd_success,
                  time);
}

gboolean BookmarkManagerGtk::OnLeftTreeViewDragMotion(
    GtkWidget* tree_view,
    GdkDragContext* context,
    gint x,
    gint y,
    guint time) {
  GtkTreePath* path;
  GtkTreeViewDropPosition pos;
  gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y,
                                    &path, &pos);

  if (path) {
    // Don't accept drops over the "Search" or "Recently added" folders.
    GtkTreeIter iter;
    GtkTreeModel* model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view));
    gtk_tree_model_get_iter(model, &iter, path);
    if (GetNodeAt(model, &iter) == NULL)
      return FALSE;

    // Only allow INTO.
    if (pos == GTK_TREE_VIEW_DROP_BEFORE)
      pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
    else if (pos == GTK_TREE_VIEW_DROP_AFTER)
      pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER;
    gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(tree_view), path, pos);
  } else {
    return FALSE;
  }

  gdk_drag_status(context, GDK_ACTION_MOVE, time);
  gtk_tree_path_free(path);
  return TRUE;
}

void BookmarkManagerGtk::OnLeftTreeViewRowCollapsed(
    GtkWidget* tree_view,
    GtkTreeIter* iter,
    GtkTreePath* path) {
  // If a selection still exists, do nothing.
  if (gtk_tree_selection_get_selected(left_selection(), NULL, NULL))
    return;

  gtk_tree_selection_select_path(left_selection(), path);
}

void BookmarkManagerGtk::OnRightTreeViewDragGet(
    GtkWidget* tree_view,
    GdkDragContext* context,
    GtkSelectionData* selection_data,
    guint target_type,
    guint time) {
  // No selection, do nothing. This shouldn't get hit, but if it does an early
  // return avoids a crash.
  if (gtk_tree_selection_count_selected_rows(right_selection()) == 0) {
    NOTREACHED();
    return;
  }

  bookmark_utils::WriteBookmarksToSelection(GetRightSelection(),
                                            selection_data,
                                            target_type,
                                            profile_);
}

void BookmarkManagerGtk::OnRightTreeViewDragReceived(
    GtkWidget* tree_view,
    GdkDragContext* context,
    gint x,
    gint y,
    GtkSelectionData* selection_data,
    guint target_type,
    guint time) {
  gboolean dnd_success = FALSE;
  gboolean delete_selection_data = FALSE;

  std::vector<const BookmarkNode*> nodes =
      bookmark_utils::GetNodesFromSelection(context, selection_data,
                                            target_type,
                                            profile_,
                                            &delete_selection_data,
                                            &dnd_success);

  if (nodes.empty()) {
    gtk_drag_finish(context, dnd_success, delete_selection_data, time);
    return;
  }

  GtkTreePath* path;
  GtkTreeViewDropPosition pos;
  gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y,
                                    &path, &pos);

  bool drop_before = pos == GTK_TREE_VIEW_DROP_BEFORE;
  bool drop_after = pos == GTK_TREE_VIEW_DROP_AFTER;

  // The parent folder and index therein to drop the nodes.
  const BookmarkNode* parent = NULL;
  int idx = -1;

  // |path| will be null when we are looking at an empty folder.
  if (!drop_before && !drop_after && path) {
    GtkTreeIter iter;
    GtkTreeModel* model = GTK_TREE_MODEL(right_store_);
    gtk_tree_model_get_iter(model, &iter, path);
    const BookmarkNode* node = GetNodeAt(model, &iter);
    if (node && node->is_folder()) {
      parent = node;
      idx = parent->GetChildCount();
    } else {
      drop_before = pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
      drop_after = pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER;
    }
  }

  if (drop_before || drop_after || !path) {
    if (path && drop_after)
      gtk_tree_path_next(path);
    // We will get a null path when the drop is below the lowest row.
    parent = GetFolder();
    idx = !path ? parent->GetChildCount() : gtk_tree_path_get_indices(path)[0];
  }

  for (std::vector<const BookmarkNode*>::iterator it = nodes.begin();
       it != nodes.end(); ++it) {
    // Don't try to drop a node into one of its descendants.
    if (!parent->HasAncestor(*it)) {
      model_->Move(*it, parent, idx);
      idx = parent->IndexOfChild(*it) + 1;
    }
  }

  gtk_tree_path_free(path);
  gtk_drag_finish(context, dnd_success, delete_selection_data, time);
}

void BookmarkManagerGtk::OnRightTreeViewDragBegin(
    GtkWidget* tree_view,
    GdkDragContext* drag_context) {
  gtk_drag_set_icon_stock(drag_context, GTK_STOCK_DND, 0, 0);
}

gboolean BookmarkManagerGtk::OnRightTreeViewDragMotion(
    GtkWidget* tree_view,
    GdkDragContext* context,
    gint x,
    gint y,
    guint time) {
  GtkTreePath* path;
  GtkTreeViewDropPosition pos;
  gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y,
                                    &path, &pos);

  const BookmarkNode* parent = GetFolder();
  if (path) {
    int idx =
        gtk_tree_path_get_indices(path)[gtk_tree_path_get_depth(path) - 1];
    // Only allow INTO if the node is a folder.
    if (parent->GetChild(idx)->is_url()) {
      if (pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
        pos = GTK_TREE_VIEW_DROP_BEFORE;
      else if (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
        pos = GTK_TREE_VIEW_DROP_AFTER;
    }
    gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(tree_view), path, pos);
  } else {
    // We allow a drop if the drag is over the bottom of the tree view,
    // but we don't draw any indication.
  }

  gdk_drag_status(context, GDK_ACTION_MOVE, time);
  return TRUE;
}

void BookmarkManagerGtk::OnRightTreeViewRowActivated(
    GtkWidget* tree_view,
    GtkTreePath* path,
    GtkTreeViewColumn* column) {
  std::vector<const BookmarkNode*> nodes = GetRightSelection();
  if (nodes.empty())
    return;
  if (nodes.size() == 1 && nodes[0]->is_folder()) {
    // Double click on a folder descends into the folder.
    SelectInTree(nodes[0], false);
    return;
  }
  bookmark_utils::OpenAll(GTK_WINDOW(window_), profile_, NULL, nodes,
                          CURRENT_TAB);
}

void BookmarkManagerGtk::OnLeftTreeViewFocusIn(GtkWidget* tree_view,
                                               GdkEventFocus* event) {
  if (!organize_is_for_left_)
    ResetOrganizeMenu(true);
}

void BookmarkManagerGtk::OnRightTreeViewFocusIn(GtkWidget* tree_view,
                                                GdkEventFocus* event) {
  if (organize_is_for_left_)
    ResetOrganizeMenu(false);
}

// We do a couple things in this handler.
//
// 1. On left clicks that occur below the lowest row, unselect all selected
// rows. This is not a native GtkTreeView behavior, but it is added by libegg
// and is thus present in Nautilus. This is the path == NULL path.
// 2. Cache left clicks that occur on an already active selection. If the user
// begins a drag, then we will throw away this event and initiate a drag on the
// tree view manually. If the user doesn't begin a drag (e.g. just releases the
// button), send both events to the tree view. This is a workaround for
// http://crbug.com/15240. If we don't do this, when the user tries to drag
// a group of selected rows, the click at the start of the drag will deselect
// all rows except the one the cursor is over.
//
// We return TRUE for when we want to ignore events (i.e., stop the default
// handler from handling them), and FALSE for when we want to continue
// propagation.
gboolean BookmarkManagerGtk::OnRightTreeViewButtonPress(
    GtkWidget* tree_view, GdkEventButton* event) {
  // Always let cached mousedown events through.
  if (sending_delayed_mousedown_)
    return FALSE;

  if (event->button != 1)
    return FALSE;

  // If a user double clicks, we will get two button presses in a row without
  // any intervening mouse up, hence we must flush delayed mousedowns here as
  // well as in the button release handler.
  if (delaying_mousedown_) {
    SendDelayedMousedown();
    return FALSE;
  }

  if (event->window != gtk_tree_view_get_bin_window(GTK_TREE_VIEW(tree_view)))
    return FALSE;

  gint tree_x, tree_y;
  gtk_tree_view_convert_bin_window_to_widget_coords(
      GTK_TREE_VIEW(tree_view),
      static_cast<gint>(event->x),
      static_cast<gint>(event->y),
      &tree_x, &tree_y);

  GtkTreePath* path;
  gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree_view),
                                tree_x, tree_y,
                                &path, NULL, NULL, NULL);

  if (path == NULL) {
    // Checking that the widget already has focus matches libegg behavior.
    if (GTK_WIDGET_HAS_FOCUS(tree_view))
      gtk_tree_selection_unselect_all(right_selection());
    return FALSE;
  }

  if (gtk_tree_selection_path_is_selected(right_selection(), path)) {
    mousedown_event_ = *event;
    delaying_mousedown_ = true;
    gtk_tree_path_free(path);
    return TRUE;
  }

  gtk_tree_path_free(path);
  return FALSE;
}

gboolean BookmarkManagerGtk::OnRightTreeViewMotion(
    GtkWidget* tree_view, GdkEventMotion* event) {
  // Swallow motion events when no row is selected. This prevents the initiation
  // of empty drags.
  if (gtk_tree_selection_count_selected_rows(right_selection()) == 0)
    return TRUE;

  // Otherwise this handler is only used for the multi-drag workaround.
  if (!delaying_mousedown_)
    return FALSE;

  if (gtk_drag_check_threshold(tree_view,
                               static_cast<gint>(mousedown_event_.x),
                               static_cast<gint>(mousedown_event_.y),
                               static_cast<gint>(event->x),
                               static_cast<gint>(event->y))) {
    delaying_mousedown_ = false;
    GtkTargetList* targets = gtk_dnd_util::GetTargetListFromCodeMask(
        bookmark_utils::GetCodeMask(false));
    gtk_drag_begin(tree_view, targets, GDK_ACTION_MOVE,
                   1, reinterpret_cast<GdkEvent*>(event));
    // The drag adds a ref; let it own the list.
    gtk_target_list_unref(targets);
  }

  return FALSE;
}

gboolean BookmarkManagerGtk::OnTreeViewButtonPress(
    GtkWidget* tree_view, GdkEventButton* button) {
  if (button->button != 3)
    return FALSE;

  if (ignore_rightclicks_)
    return FALSE;

  // If the cursor is not hovering over a selected row, let it propagate
  // to the default handler so that a selection change may occur.
  if (!CursorIsOverSelection(GTK_TREE_VIEW(tree_view))) {
    ignore_rightclicks_ = true;
    gtk_propagate_event(tree_view, reinterpret_cast<GdkEvent*>(button));
    ignore_rightclicks_ = false;
  }

  context_menu_->PopupAsContext(button->time);
  return TRUE;
}

gboolean BookmarkManagerGtk::OnTreeViewButtonRelease(
    GtkWidget* tree_view, GdkEventButton* button) {
  if (delaying_mousedown_ && (tree_view == right_tree_view_))
    SendDelayedMousedown();

  return FALSE;
}

gboolean BookmarkManagerGtk::OnTreeViewKeyPress(GtkWidget* tree_view,
                                                GdkEventKey* key) {
  int command = -1;

  if ((key->state & gtk_accelerator_get_default_mod_mask()) ==
      GDK_SHIFT_MASK) {
    if (key->keyval == GDK_Delete)
      command = IDS_CUT;
    else if (key->keyval == GDK_Insert)
      command = IDS_PASTE;
  } else if ((key->state & gtk_accelerator_get_default_mod_mask()) ==
             GDK_CONTROL_MASK) {
    switch (key->keyval) {
      case GDK_c:
      case GDK_Insert:
        command = IDS_COPY;
        break;
      case GDK_x:
        command = IDS_CUT;
        break;
      case GDK_v:
        command = IDS_PASTE;
        break;
      default:
        break;
    }
  } else if (key->keyval == GDK_Delete) {
    command = IDS_BOOKMARK_BAR_REMOVE;
  }

  if (command == -1)
    return FALSE;

  if (organize_menu_controller_.get() &&
      organize_menu_controller_->IsCommandIdEnabled(command)) {
    organize_menu_controller_->ExecuteCommand(command);
    return TRUE;
  }

  return FALSE;
}

// static
void BookmarkManagerGtk::OnFolderNameEdited(GtkCellRendererText* render,
    gchar* path, gchar* new_folder_name, BookmarkManagerGtk* bm) {
  // A folder named was edited in place.  Sync the change to the bookmark
  // model.
  GtkTreeIter iter;
  GtkTreePath* tree_path = gtk_tree_path_new_from_string(path);
  gboolean rv = gtk_tree_model_get_iter(GTK_TREE_MODEL(bm->left_store_),
                                        &iter, tree_path);
  DCHECK(rv);
  bm->model_->SetTitle(bm->GetNodeAt(GTK_TREE_MODEL(bm->left_store_), &iter),
                       UTF8ToWide(new_folder_name));
}

void BookmarkManagerGtk::OnImportItemActivated(GtkWidget* menuitem) {
  SelectFileDialog::FileTypeInfo file_type_info;
  file_type_info.extensions.resize(1);
  file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
  file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("htm"));
  file_type_info.include_all_files = true;
  select_file_dialog_->SelectFile(
      SelectFileDialog::SELECT_OPEN_FILE, string16(),
      FilePath(""), &file_type_info, 0,
      std::string(), GTK_WINDOW(window_),
      reinterpret_cast<void*>(IDS_BOOKMARK_MANAGER_IMPORT_MENU));
}

void BookmarkManagerGtk::OnExportItemActivated(GtkWidget* menuitem) {
  SelectFileDialog::FileTypeInfo file_type_info;
  file_type_info.extensions.resize(1);
  file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
  file_type_info.include_all_files = true;
  // TODO(estade): If a user exports a bookmark file then we will remember the
  // download location. If the user subsequently downloads a file, we will
  // suggest this cached download location. This is bad! We ought to remember
  // save locations differently for different user tasks.
  FilePath suggested_path;
  PathService::Get(chrome::DIR_USER_DATA, &suggested_path);
  select_file_dialog_->SelectFile(
      SelectFileDialog::SELECT_SAVEAS_FILE, string16(),
      suggested_path.Append("bookmarks.html"), &file_type_info, 0,
      "html", GTK_WINDOW(window_),
      reinterpret_cast<void*>(IDS_BOOKMARK_MANAGER_EXPORT_MENU));
}

void BookmarkManagerGtk::OnSyncStatusMenuActivated(GtkWidget* menu_item) {
  if (sync_relogin_required_) {
    DCHECK(sync_service_);
    sync_service_->ShowLoginDialog();
  } else {
    sync_ui_util::OpenSyncMyBookmarksDialog(
        profile_, ProfileSyncService::START_FROM_BOOKMARK_MANAGER);
  }
}

gboolean BookmarkManagerGtk::OnWindowDestroyed(GtkWidget* window) {
  DCHECK_EQ(this, manager);

  if (g_browser_process->local_state()) {
    DictionaryValue* placement_pref =
        g_browser_process->local_state()->GetMutableDictionary(
            prefs::kBookmarkManagerPlacement);
    // Note that we store left/top for consistency with Windows, but that we
    // *don't* restore them.
    placement_pref->SetInteger(L"left", window_bounds_.x());
    placement_pref->SetInteger(L"top", window_bounds_.y());
    placement_pref->SetInteger(L"right", window_bounds_.right());
    placement_pref->SetInteger(L"bottom", window_bounds_.bottom());
    placement_pref->SetBoolean(L"maximized", false);
  }

  delete manager;
  manager = NULL;
  return FALSE;
}

gboolean BookmarkManagerGtk::OnWindowStateChanged(GtkWidget* window,
                                                  GdkEventWindowState* event) {
  DCHECK_EQ(this, manager);
  window_state_ = event->new_window_state;
  return FALSE;
}

gboolean BookmarkManagerGtk::OnWindowConfigured(GtkWidget* window,
                                                GdkEventConfigure* event) {
  DCHECK_EQ(this, manager);

  // Don't save the size if we're in an abnormal state.
  if (!(window_state_ & (GDK_WINDOW_STATE_MAXIMIZED |
                         GDK_WINDOW_STATE_WITHDRAWN |
                         GDK_WINDOW_STATE_FULLSCREEN |
                         GDK_WINDOW_STATE_ICONIFIED))) {
    window_bounds_.SetRect(event->x, event->y, event->width, event->height);
  }

  return FALSE;
}

void BookmarkManagerGtk::FileSelected(const FilePath& path,
                                      int index, void* params) {
  int id = reinterpret_cast<intptr_t>(params);
  if (id == IDS_BOOKMARK_MANAGER_IMPORT_MENU) {
    // ImporterHost is ref counted and will delete itself when done.
    ImporterHost* host = new ImporterHost();
    ProfileInfo profile_info;
    profile_info.browser_type = importer::BOOKMARKS_HTML;
    profile_info.source_path = path.ToWStringHack();
    StartImportingWithUI(GTK_WINDOW(window_), importer::FAVORITES, host,
                         profile_info, profile_,
                         new ImportObserverImpl(profile()), false);
  } else if (id == IDS_BOOKMARK_MANAGER_EXPORT_MENU) {
    bookmark_html_writer::WriteBookmarks(profile(), path, NULL);
  } else {
    NOTREACHED();
  }
}

void BookmarkManagerGtk::OnStateChanged() {
  UpdateSyncStatus();
}

void BookmarkManagerGtk::CloseMenu() {
  organize_menu_->Cancel();
}

// static
gboolean BookmarkManagerGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
    GObject* acceleratable,
    guint keyval,
    GdkModifierType modifier,
    BookmarkManagerGtk* bookmark_manager) {
  modifier = static_cast<GdkModifierType>(
      modifier & gtk_accelerator_get_default_mod_mask());
  // The only accelerator we have registered is ctrl+w, so any other value is a
  // non-fatal error.
  DCHECK_EQ(keyval, static_cast<guint>(GDK_w));
  DCHECK_EQ(modifier, GDK_CONTROL_MASK);

  gtk_widget_destroy(bookmark_manager->window_);

  return TRUE;
}

void BookmarkManagerGtk::UpdateSyncStatus() {
  DCHECK(sync_service_);
  string16 status_label;
  string16 link_label;
  sync_relogin_required_ = sync_ui_util::GetStatusLabels(
      sync_service_, &status_label, &link_label) == sync_ui_util::SYNC_ERROR;

  if (sync_relogin_required_) {
    GtkWidget* sync_status_label = gtk_bin_get_child(
        GTK_BIN(sync_status_menu_));
    gtk_label_set_label(
        GTK_LABEL(sync_status_label),
        l10n_util::GetStringUTF8(IDS_SYNC_BOOKMARK_BAR_ERROR).c_str());
    return;
  }

  if (sync_service_->HasSyncSetupCompleted()) {
      string16 username = sync_service_->GetAuthenticatedUsername();
      status_label = l10n_util::GetStringFUTF16(IDS_SYNC_NTP_SYNCED_TO,
                                                username);
  } else if (sync_service_->SetupInProgress()) {
    status_label = l10n_util::GetStringUTF16(IDS_SYNC_NTP_SETUP_IN_PROGRESS);
  } else {
    status_label = l10n_util::GetStringUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL);
  }
  GtkWidget* sync_status_label = gtk_bin_get_child(GTK_BIN(sync_status_menu_));
  gtk_label_set_label(GTK_LABEL(sync_status_label),
                      UTF16ToUTF8(status_label).c_str());
}

Generated by  Doxygen 1.6.0   Back to index