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

keyword_editor_view.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/keyword_editor_view.h"

#include <string>

#include "app/l10n_util.h"
#include "base/gtk_util.h"
#include "base/message_loop.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/gtk/accessible_widget_helper_gtk.h"
#include "chrome/browser/gtk/edit_search_engine_dialog.h"
#include "chrome/browser/gtk/gtk_tree.h"
#include "chrome/browser/gtk/gtk_util.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/search_engines/keyword_editor_controller.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_model.h"
#include "chrome/browser/search_engines/template_url_table_model.h"
#include "gfx/gtk_util.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"

namespace {

// How many rows should be added to an index into the |table_model_| to get the
// corresponding row in |list_store_|
const int kFirstGroupRowOffset = 2;
const int kSecondGroupRowOffset = 5;

KeywordEditorView* instance_ = NULL;

}  // namespace

// static
void KeywordEditorView::Show(Profile* profile) {
  DCHECK(profile);
  // If this panel is opened from an Incognito window, closing that window can
  // leave this with a stale pointer. Use the original profile instead.
  // See http://crbug.com/23359.
  profile = profile->GetOriginalProfile();
  if (!profile->GetTemplateURLModel())
    return;

  // If there's already an existing editor window, activate it.
  if (instance_) {
    gtk_util::PresentWindow(instance_->dialog_, 0);
  } else {
    instance_ = new KeywordEditorView(profile);
    gtk_util::ShowDialogWithLocalizedSize(instance_->dialog_,
        IDS_SEARCHENGINES_DIALOG_WIDTH_CHARS,
        IDS_SEARCHENGINES_DIALOG_HEIGHT_LINES,
        true);
  }
}

void KeywordEditorView::OnEditedKeyword(const TemplateURL* template_url,
                                        const std::wstring& title,
                                        const std::wstring& keyword,
                                        const std::string& url) {
  if (template_url) {
    controller_->ModifyTemplateURL(template_url, title, keyword, url);

    // Force the make default button to update.
    EnableControls();
  } else {
    SelectModelRow(controller_->AddTemplateURL(title, keyword, url));
  }
}

KeywordEditorView::~KeywordEditorView() {
  controller_->url_model()->RemoveObserver(this);
}

KeywordEditorView::KeywordEditorView(Profile* profile)
    : profile_(profile),
      controller_(new KeywordEditorController(profile)),
      table_model_(controller_->table_model()) {
  Init();
}

void KeywordEditorView::Init() {
  std::string dialog_name =
      l10n_util::GetStringUTF8(IDS_SEARCH_ENGINES_EDITOR_WINDOW_TITLE);
  dialog_ = gtk_dialog_new_with_buttons(
      dialog_name.c_str(),
      NULL,
      // Non-modal.
      GTK_DIALOG_NO_SEPARATOR,
      GTK_STOCK_CLOSE,
      GTK_RESPONSE_CLOSE,
      NULL);

  accessible_widget_helper_.reset(new AccessibleWidgetHelper(
      dialog_, profile_));
  accessible_widget_helper_->SendOpenWindowNotification(dialog_name);

  gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox),
                      gtk_util::kContentAreaSpacing);

  GtkWidget* hbox = gtk_hbox_new(FALSE, gtk_util::kControlSpacing);
  gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog_)->vbox), hbox);

  GtkWidget* scroll_window = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window),
                                 GTK_POLICY_AUTOMATIC,
                                 GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll_window),
                                      GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(hbox), scroll_window, TRUE, TRUE, 0);

  list_store_ = gtk_list_store_new(COL_COUNT,
                                   GDK_TYPE_PIXBUF,
                                   G_TYPE_STRING,
                                   G_TYPE_STRING,
                                   G_TYPE_BOOLEAN,
                                   G_TYPE_BOOLEAN,
                                   G_TYPE_INT,
                                   G_TYPE_BOOLEAN);
  tree_ = gtk_tree_view_new_with_model(GTK_TREE_MODEL(list_store_));
  g_object_unref(list_store_);
  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_), TRUE);
  gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(tree_),
                                       OnCheckRowIsSeparator,
                                       NULL, NULL);
  g_signal_connect(tree_, "row-activated",
                   G_CALLBACK(OnRowActivated), this);
  gtk_container_add(GTK_CONTAINER(scroll_window), tree_);

  GtkTreeViewColumn* title_column = gtk_tree_view_column_new();
  GtkCellRenderer* pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_column_pack_start(title_column, pixbuf_renderer, FALSE);
  gtk_tree_view_column_add_attribute(title_column, pixbuf_renderer, "pixbuf",
                                     COL_FAVICON);
  GtkCellRenderer* title_renderer = gtk_cell_renderer_text_new();
  gtk_tree_view_column_pack_start(title_column, title_renderer, TRUE);
  gtk_tree_view_column_add_attribute(title_column, title_renderer, "text",
                                     COL_TITLE);
  gtk_tree_view_column_add_attribute(title_column, title_renderer, "weight",
                                     COL_WEIGHT);
  gtk_tree_view_column_add_attribute(title_column, title_renderer, "weight-set",
                                     COL_WEIGHT_SET);
  gtk_tree_view_column_set_title(
      title_column, l10n_util::GetStringUTF8(
          IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN).c_str());
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree_), title_column);

  GtkTreeViewColumn* keyword_column = gtk_tree_view_column_new_with_attributes(
      l10n_util::GetStringUTF8(
          IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN).c_str(),
      gtk_cell_renderer_text_new(),
      "text", COL_KEYWORD,
      NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree_), keyword_column);

  selection_ = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_));
  gtk_tree_selection_set_mode(selection_, GTK_SELECTION_SINGLE);
  gtk_tree_selection_set_select_function(selection_, OnSelectionFilter,
                                         NULL, NULL);
  g_signal_connect(selection_, "changed",
                   G_CALLBACK(OnSelectionChanged), this);

  GtkWidget* button_box = gtk_vbox_new(FALSE, gtk_util::kControlSpacing);
  gtk_box_pack_start(GTK_BOX(hbox), button_box, FALSE, FALSE, 0);

  add_button_ = gtk_button_new_with_mnemonic(
      gtk_util::ConvertAcceleratorsFromWindowsStyle(
          l10n_util::GetStringUTF8(
              IDS_SEARCH_ENGINES_EDITOR_NEW_BUTTON)).c_str());
  g_signal_connect(add_button_, "clicked",
                   G_CALLBACK(OnAddButtonClicked), this);
  gtk_box_pack_start(GTK_BOX(button_box), add_button_, FALSE, FALSE, 0);

  edit_button_ = gtk_button_new_with_label(
      l10n_util::GetStringUTF8(IDS_SEARCH_ENGINES_EDITOR_EDIT_BUTTON).c_str());
  g_signal_connect(edit_button_, "clicked",
                   G_CALLBACK(OnEditButtonClicked), this);
  gtk_box_pack_start(GTK_BOX(button_box), edit_button_, FALSE, FALSE, 0);

  remove_button_ = gtk_button_new_with_mnemonic(
      gtk_util::ConvertAcceleratorsFromWindowsStyle(
          l10n_util::GetStringUTF8(
              IDS_SEARCH_ENGINES_EDITOR_REMOVE_BUTTON)).c_str());
  g_signal_connect(remove_button_, "clicked",
                   G_CALLBACK(OnRemoveButtonClicked), this);
  gtk_box_pack_start(GTK_BOX(button_box), remove_button_, FALSE, FALSE, 0);

  make_default_button_ = gtk_button_new_with_label(
      l10n_util::GetStringUTF8(
          IDS_SEARCH_ENGINES_EDITOR_MAKE_DEFAULT_BUTTON).c_str());
  g_signal_connect(make_default_button_, "clicked",
                   G_CALLBACK(OnMakeDefaultButtonClicked), this);
  gtk_box_pack_start(GTK_BOX(button_box), make_default_button_, FALSE, FALSE,
                     0);

  controller_->url_model()->AddObserver(this);
  table_model_->SetObserver(this);
  table_model_->Reload();

  EnableControls();

  g_signal_connect(dialog_, "response", G_CALLBACK(OnResponse), this);
  g_signal_connect(dialog_, "destroy", G_CALLBACK(OnWindowDestroy), this);
}

void KeywordEditorView::EnableControls() {
  bool can_edit = false;
  bool can_make_default = false;
  bool can_remove = false;
  int model_row = GetSelectedModelRow();
  if (model_row != -1) {
    can_edit = true;
    const TemplateURL* selected_url = controller_->GetTemplateURL(model_row);
    can_make_default = controller_->CanMakeDefault(selected_url);
    can_remove = controller_->CanRemove(selected_url);
  }
  gtk_widget_set_sensitive(add_button_, controller_->loaded());
  gtk_widget_set_sensitive(edit_button_, can_edit);
  gtk_widget_set_sensitive(remove_button_, can_remove);
  gtk_widget_set_sensitive(make_default_button_, can_make_default);
}

void KeywordEditorView::SetColumnValues(int model_row, GtkTreeIter* iter) {
  SkBitmap bitmap = table_model_->GetIcon(model_row);
  GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&bitmap);
  gtk_list_store_set(
      list_store_, iter,
      COL_FAVICON, pixbuf,
      // Dunno why, even with COL_WEIGHT_SET to FALSE here, the weight still
      // has an effect.  So we just set it to normal.
      COL_WEIGHT, PANGO_WEIGHT_NORMAL,
      COL_WEIGHT_SET, TRUE,
      COL_TITLE, WideToUTF8(table_model_->GetText(
          model_row, IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN)).c_str(),
      COL_KEYWORD, WideToUTF8(table_model_->GetText(
          model_row, IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN)).c_str(),
      -1);
  g_object_unref(pixbuf);
}

int KeywordEditorView::GetListStoreRowForModelRow(int model_row) const {
  if (model_row < model_second_group_index_)
    return model_row + kFirstGroupRowOffset;
  else
    return model_row + kSecondGroupRowOffset;
}

int KeywordEditorView::GetModelRowForPath(GtkTreePath* path) const {
  gint* indices = gtk_tree_path_get_indices(path);
  if (!indices) {
    NOTREACHED();
    return -1;
  }
  if (indices[0] >= model_second_group_index_ + kSecondGroupRowOffset)
    return indices[0] - kSecondGroupRowOffset;
  return indices[0] - kFirstGroupRowOffset;
}

int KeywordEditorView::GetModelRowForIter(GtkTreeIter* iter) const {
  GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(list_store_),
                                              iter);
  int model_row = GetModelRowForPath(path);
  gtk_tree_path_free(path);
  return model_row;
}

int KeywordEditorView::GetSelectedModelRow() const {
  GtkTreeIter iter;
  if (!gtk_tree_selection_get_selected(selection_, NULL, &iter))
    return -1;
  return GetModelRowForIter(&iter);
}

void KeywordEditorView::SelectModelRow(int model_row) {
  int row = GetListStoreRowForModelRow(model_row);
  gtk_tree::SelectAndFocusRowNum(row, GTK_TREE_VIEW(tree_));
}

void KeywordEditorView::AddNodeToList(int model_row) {
  GtkTreeIter iter;
  int row = GetListStoreRowForModelRow(model_row);
  if (row == 0) {
    gtk_list_store_prepend(list_store_, &iter);
  } else {
    GtkTreeIter sibling;
    gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), &sibling,
                                  NULL, row - 1);
    gtk_list_store_insert_after(list_store_, &iter, &sibling);
  }

  SetColumnValues(model_row, &iter);
}

void KeywordEditorView::OnModelChanged() {
  model_second_group_index_ = table_model_->last_search_engine_index();
  gtk_list_store_clear(list_store_);

  TableModel::Groups groups(table_model_->GetGroups());
  if (groups.size() != 2) {
    NOTREACHED();
    return;
  }

  GtkTreeIter iter;
  // First group title.
  gtk_list_store_append(list_store_, &iter);
  gtk_list_store_set(
      list_store_, &iter,
      COL_WEIGHT, PANGO_WEIGHT_BOLD,
      COL_WEIGHT_SET, TRUE,
      COL_TITLE, WideToUTF8(groups[0].title).c_str(),
      COL_IS_HEADER, TRUE,
      -1);
  // First group separator.
  gtk_list_store_append(list_store_, &iter);
  gtk_list_store_set(
      list_store_, &iter,
      COL_IS_HEADER, TRUE,
      COL_IS_SEPARATOR, TRUE,
      -1);

  // Blank row between groups.
  gtk_list_store_append(list_store_, &iter);
  gtk_list_store_set(
      list_store_, &iter,
      COL_IS_HEADER, TRUE,
      -1);
  // Second group title.
  gtk_list_store_append(list_store_, &iter);
  gtk_list_store_set(
      list_store_, &iter,
      COL_WEIGHT, PANGO_WEIGHT_BOLD,
      COL_WEIGHT_SET, TRUE,
      COL_TITLE, WideToUTF8(groups[1].title).c_str(),
      COL_IS_HEADER, TRUE,
      -1);
  // Second group separator.
  gtk_list_store_append(list_store_, &iter);
  gtk_list_store_set(
      list_store_, &iter,
      COL_IS_HEADER, TRUE,
      COL_IS_SEPARATOR, TRUE,
      -1);

  for (int i = 0; i < table_model_->RowCount(); ++i)
    AddNodeToList(i);
}

void KeywordEditorView::OnItemsChanged(int start, int length) {
  DCHECK(model_second_group_index_ == table_model_->last_search_engine_index());
  GtkTreeIter iter;
  for (int i = 0; i < length; ++i) {
    int row = GetListStoreRowForModelRow(start + i);
    bool rv = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_),
        &iter, NULL, row);
    if (!rv) {
      NOTREACHED();
      return;
    }
    SetColumnValues(start + i, &iter);
    rv = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store_), &iter);
  }
}

void KeywordEditorView::OnItemsAdded(int start, int length) {
  model_second_group_index_ = table_model_->last_search_engine_index();
  for (int i = 0; i < length; ++i) {
    AddNodeToList(start + i);
  }
}

void KeywordEditorView::OnItemsRemoved(int start, int length) {
  // This is quite likely not correct with removing multiple in one call, but
  // that shouldn't happen since we only can select and modify/remove one at a
  // time.
  DCHECK_EQ(length, 1);
  for (int i = 0; i < length; ++i) {
    int row = GetListStoreRowForModelRow(start + i);
    GtkTreeIter iter;
    if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), &iter,
                                       NULL, row)) {
      NOTREACHED();
      return;
    }
    gtk_list_store_remove(list_store_, &iter);
  }
  model_second_group_index_ = table_model_->last_search_engine_index();
}

void KeywordEditorView::OnTemplateURLModelChanged() {
  EnableControls();
}

// static
void KeywordEditorView::OnWindowDestroy(GtkWidget* widget,
                                        KeywordEditorView* window) {
  instance_ = NULL;
  MessageLoop::current()->DeleteSoon(FROM_HERE, window);
}

// static
void KeywordEditorView::OnResponse(GtkDialog* dialog, int response_id,
                                   KeywordEditorView* window) {
  gtk_widget_destroy(window->dialog_);
}

// static
gboolean KeywordEditorView::OnCheckRowIsSeparator(GtkTreeModel* model,
                                                  GtkTreeIter* iter,
                                                  gpointer user_data) {
  gboolean is_separator;
  gtk_tree_model_get(model, iter, COL_IS_SEPARATOR, &is_separator, -1);
  return is_separator;
}

// static
gboolean KeywordEditorView::OnSelectionFilter(GtkTreeSelection* selection,
                                              GtkTreeModel* model,
                                              GtkTreePath* path,
                                              gboolean path_currently_selected,
                                              gpointer user_data) {
  GtkTreeIter iter;
  if (!gtk_tree_model_get_iter(model, &iter, path)) {
    NOTREACHED();
    return TRUE;
  }
  gboolean is_header;
  gtk_tree_model_get(model, &iter, COL_IS_HEADER, &is_header, -1);
  return !is_header;
}

// static
void KeywordEditorView::OnSelectionChanged(
    GtkTreeSelection* selection, KeywordEditorView* editor) {
  editor->EnableControls();
}

// static
void KeywordEditorView::OnRowActivated(GtkTreeView* tree_view,
                                       GtkTreePath* path,
                                       GtkTreeViewColumn* column,
                                       KeywordEditorView* editor) {
  OnEditButtonClicked(NULL, editor);
}

// static
void KeywordEditorView::OnAddButtonClicked(GtkButton* button,
                                           KeywordEditorView* editor) {
  new EditSearchEngineDialog(
      GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(
          gtk_util::GetDialogWindow(editor->dialog_)))),
      NULL,
      editor,
      editor->profile_);
}

// static
void KeywordEditorView::OnEditButtonClicked(GtkButton* button,
                                            KeywordEditorView* editor) {
  int model_row = editor->GetSelectedModelRow();
  if (model_row == -1)
    return;

  new EditSearchEngineDialog(
      GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(
          gtk_util::GetDialogWindow(editor->dialog_)))),
      editor->controller_->GetTemplateURL(model_row),
      editor,
      editor->profile_);
}

// static
void KeywordEditorView::OnRemoveButtonClicked(GtkButton* button,
                                              KeywordEditorView* editor) {
  int model_row = editor->GetSelectedModelRow();
  if (model_row == -1) {
    NOTREACHED();
    return;
  }
  editor->controller_->RemoveTemplateURL(model_row);
  if (model_row >= editor->table_model_->RowCount())
    model_row = editor->table_model_->RowCount() - 1;
  if (model_row >= 0)
    editor->SelectModelRow(model_row);
}

// static
void KeywordEditorView::OnMakeDefaultButtonClicked(GtkButton* button,
                                                   KeywordEditorView* editor) {
  int model_row = editor->GetSelectedModelRow();
  if (model_row == -1) {
    NOTREACHED();
    return;
  }
  int new_index = editor->controller_->MakeDefaultTemplateURL(model_row);
  if (new_index > 0) {
    editor->SelectModelRow(new_index);
  }
}

Generated by  Doxygen 1.6.0   Back to index