blob: 0e54ccd662f7e6a8489647a529eed7c37625dfde [file] [log] [blame]
// 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 "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/browser_process.h"
#include "chrome/browser/gtk/bookmark_tree_model.h"
#include "chrome/browser/gtk/bookmark_utils_gtk.h"
#include "chrome/browser/importer/importer.h"
#include "chrome/browser/profile.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/gtk_util.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.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 targets that the right tree view accepts for dragging.
const int kDestTargetList[] = { GtkDndUtil::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),
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_);
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);
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(OnImportItemActivated), 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(OnExportItemActivated), 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);
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);
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(OnLeftTreeViewRowCollapsed), this);
g_signal_connect(left_tree_view_, "focus-in-event",
G_CALLBACK(OnLeftTreeViewFocusIn), this);
g_signal_connect(left_tree_view_, "button-press-event",
G_CALLBACK(OnTreeViewButtonPress), this);
g_signal_connect(left_tree_view_, "button-release-event",
G_CALLBACK(OnTreeViewButtonRelease), this);
g_signal_connect(left_tree_view_, "key-press-event",
G_CALLBACK(OnTreeViewKeyPress), 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);
GtkDndUtil::SetDestTargetList(left_tree_view_, kDestTargetList);
g_signal_connect(left_tree_view_, "drag-data-received",
G_CALLBACK(&OnLeftTreeViewDragReceived), this);
g_signal_connect(left_tree_view_, "drag-motion",
G_CALLBACK(&OnLeftTreeViewDragMotion), 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::ModelAdapter(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(OnRightTreeViewRowActivated), this);
g_signal_connect(right_selection(), "changed",
G_CALLBACK(OnRightSelectionChanged), this);
g_signal_connect(right_tree_view_, "focus-in-event",
G_CALLBACK(OnRightTreeViewFocusIn), this);
g_signal_connect(right_tree_view_, "button-press-event",
G_CALLBACK(OnRightTreeViewButtonPress), this);
g_signal_connect(right_tree_view_, "motion-notify-event",
G_CALLBACK(OnRightTreeViewMotion), this);
// This handler just controls showing the context menu.
g_signal_connect(right_tree_view_, "button-press-event",
G_CALLBACK(OnTreeViewButtonPress), this);
g_signal_connect(right_tree_view_, "button-release-event",
G_CALLBACK(OnTreeViewButtonRelease), this);
g_signal_connect(right_tree_view_, "key-press-event",
G_CALLBACK(OnTreeViewKeyPress), this);
// We don't advertise GDK_ACTION_COPY, but since we don't explicitly do
// any deleting following a succesful move, this should work.
gtk_drag_source_set(right_tree_view_,
GDK_BUTTON1_MASK,
NULL, 0, GDK_ACTION_MOVE);
GtkDndUtil::SetSourceTargetListFromCodeMask(
right_tree_view_, GtkDndUtil::CHROME_BOOKMARK_ITEM);
// 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(&OnRightTreeViewDragGet), this);
g_signal_connect(right_tree_view_, "drag-data-received",
G_CALLBACK(&OnRightTreeViewDragReceived), this);
g_signal_connect(right_tree_view_, "drag-motion",
G_CALLBACK(&OnRightTreeViewDragMotion), this);
g_signal_connect(right_tree_view_, "drag-begin",
G_CALLBACK(&OnRightTreeViewDragBegin), 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_);
int width = 1, height = 1;
gtk_util::GetWidgetSizeFromResources(
window_,
IDS_BOOKMARK_MANAGER_DIALOG_WIDTH_CHARS,
IDS_BOOKMARK_MANAGER_DIALOG_HEIGHT_LINES,
&width, &height);
gtk_window_set_default_size(GTK_WINDOW(window_), width, height);
}
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);
#if defined(TOOLKIT_GTK)
// 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.
BookmarkContextMenu* old_menu = organize_menu_.release();
if (old_menu)
MessageLoop::current()->DeleteSoon(FROM_HERE, old_menu);
organize_menu_.reset(new BookmarkContextMenu(window_, profile_, NULL, NULL,
parent, nodes, BookmarkContextMenu::BOOKMARK_MANAGER_ORGANIZE_MENU,
NULL));
gtk_menu_item_set_submenu(GTK_MENU_ITEM(organize_), organize_menu_->menu());
#else
// GTK+Views should implement this somehow.
NOTIMPLEMENTED();
#endif
}
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);
GtkDndUtil::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;
std::wstring search_text(
UTF8ToWide(gtk_entry_get_text(GTK_ENTRY(search_entry_))));
// 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() {
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);
}
// statuc
void BookmarkManagerGtk::OnLeftTreeViewDragReceived(
GtkWidget* tree_view,
GdkDragContext* context,
gint x,
gint y,
GtkSelectionData* selection_data,
guint target_type,
guint time,
BookmarkManagerGtk* bm) {
gboolean dnd_success = FALSE;
gboolean delete_selection_data = FALSE;
std::vector<const BookmarkNode*> nodes =
bookmark_utils::GetNodesFromSelection(context, selection_data,
target_type,
bm->profile_,
&delete_selection_data,
&dnd_success);
if (nodes.empty()) {
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(bm->left_store_), &iter, path);
const BookmarkNode* folder =
bm->GetNodeAt(GTK_TREE_MODEL(bm->left_store_), &iter);
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))
bm->model_->Move(*it, folder, folder->GetChildCount());
}
gtk_tree_path_free(path);
gtk_drag_finish(context, dnd_success, delete_selection_data, time);
}
// static
gboolean BookmarkManagerGtk::OnLeftTreeViewDragMotion(
GtkWidget* tree_view,
GdkDragContext* context,
gint x,
gint y,
guint time,
BookmarkManagerGtk* bm) {
GtkTreePath* path;
GtkTreeViewDropPosition pos;
gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y,
&path, &pos);
if (path) {
// 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;
}
// static
void BookmarkManagerGtk::OnLeftTreeViewRowCollapsed(
GtkTreeView *tree_view,
GtkTreeIter* iter,
GtkTreePath* path,
BookmarkManagerGtk* bm) {
// If a selection still exists, do nothing.
if (gtk_tree_selection_get_selected(bm->left_selection(), NULL, NULL))
return;
gtk_tree_selection_select_path(bm->left_selection(), path);
}
// static
void BookmarkManagerGtk::OnRightTreeViewDragGet(
GtkWidget* tree_view,
GdkDragContext* context,
GtkSelectionData* selection_data,
guint target_type,
guint time,
BookmarkManagerGtk* bm) {
// 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(bm->right_selection()) == 0) {
NOTREACHED();
return;
}
bookmark_utils::WriteBookmarksToSelection(bm->GetRightSelection(),
selection_data,
target_type,
bm->profile_);
}
// static
void BookmarkManagerGtk::OnRightTreeViewDragReceived(
GtkWidget* tree_view,
GdkDragContext* context,
gint x,
gint y,
GtkSelectionData* selection_data,
guint target_type,
guint time,
BookmarkManagerGtk* bm) {
gboolean dnd_success = FALSE;
gboolean delete_selection_data = FALSE;
std::vector<const BookmarkNode*> nodes =
bookmark_utils::GetNodesFromSelection(context, selection_data,
target_type,
bm->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(bm->right_store_);
gtk_tree_model_get_iter(model, &iter, path);
const BookmarkNode* node = bm->GetNodeAt(model, &iter);
if (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 = bm->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)) {
bm->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);
}
// static
void BookmarkManagerGtk::OnRightTreeViewDragBegin(
GtkWidget* tree_view,
GdkDragContext* drag_context,
BookmarkManagerGtk* bm) {
gtk_drag_set_icon_stock(drag_context, GTK_STOCK_DND, 0, 0);
}
// static
gboolean BookmarkManagerGtk::OnRightTreeViewDragMotion(
GtkWidget* tree_view,
GdkDragContext* context,
gint x,
gint y,
guint time,
BookmarkManagerGtk* bm) {
GtkTreePath* path;
GtkTreeViewDropPosition pos;
gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(tree_view), x, y,
&path, &pos);
const BookmarkNode* parent = bm->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;
}
// static
void BookmarkManagerGtk::OnRightTreeViewRowActivated(
GtkTreeView* tree_view,
GtkTreePath* path,
GtkTreeViewColumn* column,
BookmarkManagerGtk* bm) {
std::vector<const BookmarkNode*> nodes = bm->GetRightSelection();
if (nodes.empty())
return;
if (nodes.size() == 1 && nodes[0]->is_folder()) {
// Double click on a folder descends into the folder.
bm->SelectInTree(nodes[0], false);
return;
}
bookmark_utils::OpenAll(bm->window_, bm->profile_, NULL, nodes, CURRENT_TAB);
}
// static
void BookmarkManagerGtk::OnLeftTreeViewFocusIn(
GtkTreeView* tree_view,
GdkEventFocus* event,
BookmarkManagerGtk* bm) {
if (!bm->organize_is_for_left_)
bm->ResetOrganizeMenu(true);
}
// static
void BookmarkManagerGtk::OnRightTreeViewFocusIn(
GtkTreeView* tree_view,
GdkEventFocus* event,
BookmarkManagerGtk* bm) {
if (bm->organize_is_for_left_)
bm->ResetOrganizeMenu(false);
}
// We do a couple things in this handler.
//
// 1. Ignore left clicks that occur below the lowest row so we don't try to
// start an empty drag, or allow the user to start a drag on the selected
// row by dragging on whitespace. This is the path == NULL return.
// 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.
//
// static
gboolean BookmarkManagerGtk::OnRightTreeViewButtonPress(
GtkWidget* tree_view, GdkEventButton* event, BookmarkManagerGtk* bm) {
// Always let cached mousedown events through.
if (bm->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 (bm->delaying_mousedown_) {
bm->SendDelayedMousedown();
return FALSE;
}
GtkTreePath* path;
gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree_view),
static_cast<gint>(event->x),
static_cast<gint>(event->y),
&path, NULL, NULL, NULL);
if (path == NULL)
return TRUE;
if (gtk_tree_selection_path_is_selected(bm->right_selection(), path)) {
bm->mousedown_event_ = *event;
bm->delaying_mousedown_ = true;
gtk_tree_path_free(path);
return TRUE;
}
gtk_tree_path_free(path);
return FALSE;
}
// static
gboolean BookmarkManagerGtk::OnRightTreeViewMotion(
GtkWidget* tree_view, GdkEventMotion* event, BookmarkManagerGtk* bm) {
// This handler is only used for the multi-drag workaround.
if (!bm->delaying_mousedown_)
return FALSE;
if (gtk_drag_check_threshold(tree_view,
static_cast<gint>(bm->mousedown_event_.x),
static_cast<gint>(bm->mousedown_event_.y),
static_cast<gint>(event->x),
static_cast<gint>(event->y))) {
bm->delaying_mousedown_ = false;
GtkTargetList* targets = GtkDndUtil::GetTargetListFromCodeMask(
GtkDndUtil::CHROME_BOOKMARK_ITEM |
GtkDndUtil::TEXT_URI_LIST);
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;
}
// static
gboolean BookmarkManagerGtk::OnTreeViewButtonPress(
GtkWidget* tree_view, GdkEventButton* button, BookmarkManagerGtk* bm) {
if (button->button != 3)
return FALSE;
#if defined(TOOLKIT_GTK)
if (bm->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))) {
bm->ignore_rightclicks_ = true;
gtk_propagate_event(tree_view, reinterpret_cast<GdkEvent*>(button));
bm->ignore_rightclicks_ = false;
}
bm->organize_menu_->PopupAsContext(button->time);
return TRUE;
#else
// Implement on GTK+views.
NOTIMPLEMENTED();
return FALSE;
#endif
}
// static
gboolean BookmarkManagerGtk::OnTreeViewButtonRelease(
GtkWidget* tree_view, GdkEventButton* button, BookmarkManagerGtk* bm) {
if (bm->delaying_mousedown_ && (tree_view == bm->right_tree_view_))
bm->SendDelayedMousedown();
return FALSE;
}
// static
gboolean BookmarkManagerGtk::OnTreeViewKeyPress(
GtkWidget* tree_view, GdkEventKey* key, BookmarkManagerGtk* bm) {
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 defined(TOOLKIT_GTK)
if (bm->organize_menu_.get() &&
bm->organize_menu_->IsCommandEnabled(command)) {
bm->organize_menu_->ExecuteCommand(command);
return TRUE;
}
#else
NOTIMPLEMENTED();
#endif
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));
}
// static
void BookmarkManagerGtk::OnImportItemActivated(
GtkMenuItem* menuitem, BookmarkManagerGtk* bm) {
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;
bm->select_file_dialog_->SelectFile(
SelectFileDialog::SELECT_OPEN_FILE, string16(),
FilePath(""), &file_type_info, 0,
std::string(), GTK_WINDOW(bm->window_),
reinterpret_cast<void*>(IDS_BOOKMARK_MANAGER_IMPORT_MENU));
}
// static
void BookmarkManagerGtk::OnExportItemActivated(
GtkMenuItem* menuitem, BookmarkManagerGtk* bm) {
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);
bm->select_file_dialog_->SelectFile(
SelectFileDialog::SELECT_SAVEAS_FILE, string16(),
suggested_path.Append("bookmarks.html"), &file_type_info, 0,
"html", GTK_WINDOW(bm->window_),
reinterpret_cast<void*>(IDS_BOOKMARK_MANAGER_EXPORT_MENU));
}
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 = BOOKMARKS_HTML;
profile_info.source_path = path.ToWStringHack();
StartImportingWithUI(GTK_WINDOW(window_), FAVORITES, host,
profile_info, profile_,
new ImportObserverImpl(profile()), false);
} else if (id == IDS_BOOKMARK_MANAGER_EXPORT_MENU) {
if (g_browser_process->io_thread()) {
bookmark_html_writer::WriteBookmarks(
g_browser_process->io_thread()->message_loop(), model_, path);
}
} else {
NOTREACHED();
}
}
// 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;
}