| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "views/controls/table/native_table_gtk.h" | |
| 6 | |
| 7 #include <string> | |
| 8 | |
| 9 #include "base/logging.h" | |
| 10 #include "base/utf_string_conversions.h" | |
| 11 #include "third_party/skia/include/core/SkBitmap.h" | |
| 12 #include "ui/gfx/gtk_util.h" | |
| 13 #include "ui/views/widget/widget.h" | |
| 14 #include "views/controls/table/table_view2.h" | |
| 15 #include "views/controls/table/table_view_observer.h" | |
| 16 | |
| 17 namespace views { | |
| 18 | |
| 19 //////////////////////////////////////////////////////////////////////////////// | |
| 20 // NativeTableGtk, public: | |
| 21 | |
| 22 NativeTableGtk::NativeTableGtk(TableView2* table) | |
| 23 : table_(table), | |
| 24 gtk_model_(NULL), | |
| 25 tree_view_(NULL), | |
| 26 tree_selection_(NULL) { | |
| 27 // Associates the actual GtkWidget with the table so the table is the one | |
| 28 // considered as having the focus (not the wrapper) when the HWND is | |
| 29 // focused directly (with a click for example). | |
| 30 set_focus_view(table); | |
| 31 } | |
| 32 | |
| 33 NativeTableGtk::~NativeTableGtk() { | |
| 34 } | |
| 35 | |
| 36 //////////////////////////////////////////////////////////////////////////////// | |
| 37 // NativeTableGtk, NativeTableWrapper implementation: | |
| 38 | |
| 39 int NativeTableGtk::GetRowCount() const { | |
| 40 if (!tree_view_) | |
| 41 return 0; | |
| 42 | |
| 43 GtkTreeIter iter; | |
| 44 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(gtk_model_), &iter)) | |
| 45 return 0; // Empty tree. | |
| 46 | |
| 47 int count = 1; | |
| 48 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtk_model_), &iter)) | |
| 49 count++; | |
| 50 return count; | |
| 51 } | |
| 52 | |
| 53 View* NativeTableGtk::GetView() { | |
| 54 return this; | |
| 55 } | |
| 56 | |
| 57 void NativeTableGtk::SetFocus() { | |
| 58 // Focus the associated widget. | |
| 59 OnFocus(); | |
| 60 } | |
| 61 | |
| 62 gfx::NativeView NativeTableGtk::GetTestingHandle() const { | |
| 63 // Note that we are returning the tree view, not the scrolled window as | |
| 64 // arguably the tests need to access the tree view. | |
| 65 return GTK_WIDGET(tree_view_); | |
| 66 } | |
| 67 | |
| 68 void NativeTableGtk::InsertColumn(const ui::TableColumn& column, int index) { | |
| 69 NOTIMPLEMENTED(); | |
| 70 } | |
| 71 | |
| 72 void NativeTableGtk::RemoveColumn(int column_index) { | |
| 73 if (!native_view()) | |
| 74 return; | |
| 75 | |
| 76 GtkTreeViewColumn* column = gtk_tree_view_get_column(tree_view_, | |
| 77 column_index); | |
| 78 if (column) { | |
| 79 gtk_tree_view_remove_column(tree_view_, column); | |
| 80 | |
| 81 if (table_->model()->RowCount() > 0) | |
| 82 OnRowsChanged(0, table_->model()->RowCount() - 1); | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 int NativeTableGtk::GetColumnWidth(int column_index) const { | |
| 87 GtkTreeViewColumn* column = gtk_tree_view_get_column(tree_view_, | |
| 88 column_index); | |
| 89 return column ? gtk_tree_view_column_get_width(column) : -1; | |
| 90 } | |
| 91 | |
| 92 void NativeTableGtk::SetColumnWidth(int column_index, int width) { | |
| 93 GtkTreeViewColumn* column = gtk_tree_view_get_column(tree_view_, | |
| 94 column_index); | |
| 95 column->width = width; | |
| 96 column->resized_width = width; | |
| 97 column->use_resized_width = TRUE; | |
| 98 // Needed for use_resized_width to be effective. | |
| 99 gtk_widget_queue_resize(GTK_WIDGET(tree_view_)); | |
| 100 } | |
| 101 | |
| 102 int NativeTableGtk::GetSelectedRowCount() const { | |
| 103 return gtk_tree_selection_count_selected_rows(tree_selection_); | |
| 104 } | |
| 105 | |
| 106 int NativeTableGtk::GetFirstSelectedRow() const { | |
| 107 int result = -1; | |
| 108 GList* selected_rows = | |
| 109 gtk_tree_selection_get_selected_rows(tree_selection_, NULL); | |
| 110 if (g_list_length(selected_rows) > 0) { | |
| 111 GtkTreePath* tree_path = | |
| 112 static_cast<GtkTreePath*>(g_list_first(selected_rows)->data); | |
| 113 gint* indices = gtk_tree_path_get_indices(tree_path); | |
| 114 CHECK(indices); | |
| 115 result = indices[0]; | |
| 116 } | |
| 117 | |
| 118 g_list_foreach(selected_rows, reinterpret_cast<GFunc>(gtk_tree_path_free), | |
| 119 NULL); | |
| 120 g_list_free(selected_rows); | |
| 121 return result; | |
| 122 } | |
| 123 | |
| 124 int NativeTableGtk::GetFirstFocusedRow() const { | |
| 125 NOTIMPLEMENTED(); | |
| 126 return -1; | |
| 127 } | |
| 128 | |
| 129 bool NativeTableGtk::IsRowFocused(int model_row) const { | |
| 130 NOTIMPLEMENTED(); | |
| 131 return false; | |
| 132 } | |
| 133 | |
| 134 void NativeTableGtk::ClearRowFocus() { | |
| 135 NOTIMPLEMENTED(); | |
| 136 } | |
| 137 | |
| 138 void NativeTableGtk::ClearSelection() { | |
| 139 gtk_tree_selection_unselect_all(tree_selection_); | |
| 140 } | |
| 141 | |
| 142 void NativeTableGtk::SetSelectedState(int model_row, bool state) { | |
| 143 GtkTreeIter iter; | |
| 144 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtk_model_), &iter, NULL, | |
| 145 model_row)) { | |
| 146 NOTREACHED(); | |
| 147 return; | |
| 148 } | |
| 149 if (state) | |
| 150 gtk_tree_selection_select_iter(tree_selection_, &iter); | |
| 151 else | |
| 152 gtk_tree_selection_unselect_iter(tree_selection_, &iter); | |
| 153 } | |
| 154 | |
| 155 void NativeTableGtk::SetFocusState(int model_row, bool state) { | |
| 156 NOTIMPLEMENTED(); | |
| 157 } | |
| 158 | |
| 159 bool NativeTableGtk::IsRowSelected(int model_row) const { | |
| 160 GtkTreeIter iter; | |
| 161 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtk_model_), &iter, NULL, | |
| 162 model_row)) { | |
| 163 NOTREACHED(); | |
| 164 return false; | |
| 165 } | |
| 166 return gtk_tree_selection_iter_is_selected(tree_selection_, &iter); | |
| 167 } | |
| 168 | |
| 169 void NativeTableGtk::OnRowsChanged(int start, int length) { | |
| 170 GtkTreeIter iter; | |
| 171 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtk_model_), &iter, NULL, | |
| 172 start)) { | |
| 173 NOTREACHED(); | |
| 174 return; | |
| 175 } | |
| 176 for (int i = start; i < start + length; i++) { | |
| 177 GtkTreePath* tree_path = | |
| 178 gtk_tree_model_get_path(GTK_TREE_MODEL(gtk_model_), &iter); | |
| 179 gtk_tree_model_row_changed(GTK_TREE_MODEL(gtk_model_), tree_path, &iter); | |
| 180 gtk_tree_path_free(tree_path); | |
| 181 SetRowData(i, &iter); | |
| 182 gboolean r = gtk_tree_model_iter_next(GTK_TREE_MODEL(gtk_model_), &iter); | |
| 183 DCHECK(r || i == start + length - 1); // (start + length - 1) might be the | |
| 184 // last item, in which case we won't | |
| 185 // get a next iterator. | |
| 186 } | |
| 187 } | |
| 188 | |
| 189 void NativeTableGtk::OnRowsAdded(int start, int length) { | |
| 190 GtkTreeIter iter; | |
| 191 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtk_model_), &iter, | |
| 192 NULL, start); | |
| 193 for (int i = start; i < start + length; i++) { | |
| 194 gtk_list_store_append(gtk_model_, &iter); | |
| 195 SetRowData(i, &iter); | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 void NativeTableGtk::OnRowsRemoved(int start, int length) { | |
| 200 GtkTreeIter iter; | |
| 201 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtk_model_), &iter, | |
| 202 NULL, start); | |
| 203 for (int i = start; i < start + length; i++) { | |
| 204 gboolean r = gtk_list_store_remove(gtk_model_, &iter); | |
| 205 DCHECK(r || i == start + length - 1); // (start + length - 1) might be the | |
| 206 // last item, in which case we won't | |
| 207 // get a next iterator. | |
| 208 } | |
| 209 } | |
| 210 | |
| 211 gfx::Rect NativeTableGtk::GetBounds() const { | |
| 212 NOTIMPLEMENTED(); | |
| 213 return gfx::Rect(); | |
| 214 } | |
| 215 | |
| 216 void NativeTableGtk::CreateNativeControl() { | |
| 217 if (table_->type() == CHECK_BOX_AND_TEXT) { | |
| 218 // We are not supporting checkbox in tables on Gtk yet, as it is not used | |
| 219 // in Chrome at this point in time | |
| 220 NOTREACHED(); | |
| 221 } | |
| 222 | |
| 223 tree_view_ = GTK_TREE_VIEW(gtk_tree_view_new()); | |
| 224 g_signal_connect(tree_view_, "cursor-changed", | |
| 225 G_CALLBACK(OnCursorChangedThunk), this); | |
| 226 | |
| 227 // The tree view must be wrapped in a scroll-view to be scrollable. | |
| 228 GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL); | |
| 229 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), | |
| 230 GTK_SHADOW_ETCHED_IN); | |
| 231 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), | |
| 232 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); | |
| 233 gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET(tree_view_)); | |
| 234 NativeControlCreated(scrolled); | |
| 235 // native_view() is now available. | |
| 236 | |
| 237 // Set the selection mode, single or multiple. | |
| 238 tree_selection_ = gtk_tree_view_get_selection(tree_view_); | |
| 239 gtk_tree_selection_set_mode( | |
| 240 tree_selection_, table_->single_selection() ? GTK_SELECTION_SINGLE : | |
| 241 GTK_SELECTION_MULTIPLE); | |
| 242 | |
| 243 // Don't make the header clickable until we support sorting. | |
| 244 gtk_tree_view_set_headers_clickable(tree_view_, FALSE); | |
| 245 | |
| 246 // Show grid lines based on the options. | |
| 247 GtkTreeViewGridLines grid_lines = GTK_TREE_VIEW_GRID_LINES_NONE; | |
| 248 if (table_->horizontal_lines() && table_->vertical_lines()) { | |
| 249 grid_lines = GTK_TREE_VIEW_GRID_LINES_BOTH; | |
| 250 } else if (table_->horizontal_lines()) { | |
| 251 grid_lines = GTK_TREE_VIEW_GRID_LINES_HORIZONTAL; | |
| 252 } else if (table_->vertical_lines()) { | |
| 253 grid_lines = GTK_TREE_VIEW_GRID_LINES_VERTICAL; | |
| 254 } | |
| 255 gtk_tree_view_set_grid_lines(tree_view_, grid_lines); | |
| 256 | |
| 257 int gtk_column_index = 0; | |
| 258 size_t column_index = 0; | |
| 259 if (table_->type() == ICON_AND_TEXT) { | |
| 260 InsertIconAndTextColumn(table_->GetVisibleColumnAt(0), 0); | |
| 261 column_index = 1; | |
| 262 gtk_column_index = 2; | |
| 263 } | |
| 264 | |
| 265 for (; column_index < table_->GetVisibleColumnCount(); | |
| 266 ++column_index, gtk_column_index++) { | |
| 267 InsertTextColumn(table_->GetVisibleColumnAt(column_index), | |
| 268 gtk_column_index); | |
| 269 } | |
| 270 | |
| 271 // Now create the model. | |
| 272 int column_count = table_->GetVisibleColumnCount(); | |
| 273 scoped_array<GType> types( | |
| 274 new GType[column_count + 1]); // One extra column for the icon (if any). | |
| 275 for (int i = 0; i < column_count + 1; i++) | |
| 276 types[i] = G_TYPE_STRING; | |
| 277 | |
| 278 if (table_->type() == ICON_AND_TEXT) { | |
| 279 types[0] = GDK_TYPE_PIXBUF; | |
| 280 gtk_model_ = gtk_list_store_newv(column_count + 1, types.get()); | |
| 281 } else { | |
| 282 gtk_model_ = gtk_list_store_newv(column_count, types.get()); | |
| 283 } | |
| 284 | |
| 285 gtk_tree_view_set_model(tree_view_, GTK_TREE_MODEL(gtk_model_)); | |
| 286 g_object_unref(gtk_model_); // Now the tree owns the model. | |
| 287 | |
| 288 // Updates the gtk model with the actual model. | |
| 289 if (table_->model()) | |
| 290 OnRowsAdded(0, table_->model()->RowCount()); | |
| 291 | |
| 292 gtk_widget_show_all(native_view()); | |
| 293 } | |
| 294 | |
| 295 void NativeTableGtk::InsertTextColumn(const ui::TableColumn& column, | |
| 296 int index) { | |
| 297 GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); | |
| 298 gtk_tree_view_insert_column_with_attributes(tree_view_, -1, | |
| 299 UTF16ToUTF8(column.title).c_str(), | |
| 300 renderer, "text", index, NULL); | |
| 301 } | |
| 302 | |
| 303 void NativeTableGtk::InsertIconAndTextColumn(const ui::TableColumn& column, | |
| 304 int index) { | |
| 305 // If necessary we could support more than 1 icon and text column and we could | |
| 306 // make it so it does not have to be the 1st column. | |
| 307 DCHECK_EQ(0, index) << "The icon and text column can only be the first column" | |
| 308 " at this point."; | |
| 309 | |
| 310 GtkTreeViewColumn* gtk_column = gtk_tree_view_column_new(); | |
| 311 gtk_tree_view_column_set_title(gtk_column, UTF16ToUTF8(column.title).c_str()); | |
| 312 GtkCellRenderer* renderer = gtk_cell_renderer_pixbuf_new(); | |
| 313 gtk_tree_view_column_pack_start(gtk_column, renderer, FALSE); | |
| 314 // First we set the icon renderer at index 0. | |
| 315 gtk_tree_view_column_set_attributes(gtk_column, renderer, "pixbuf", 0, NULL); | |
| 316 | |
| 317 renderer = gtk_cell_renderer_text_new(); | |
| 318 gtk_tree_view_column_pack_start(gtk_column, renderer, TRUE); | |
| 319 // Then we set the text renderer at index 1. | |
| 320 gtk_tree_view_column_set_attributes(gtk_column, renderer, "text", 1, NULL); | |
| 321 | |
| 322 gtk_tree_view_append_column(tree_view_, gtk_column); | |
| 323 } | |
| 324 | |
| 325 void NativeTableGtk::SetRowData(int row_index, GtkTreeIter* iter) { | |
| 326 int gtk_column_index = 0; | |
| 327 if (table_->type() == ICON_AND_TEXT) { | |
| 328 GdkPixbuf* icon = GetModelIcon(row_index); | |
| 329 gtk_list_store_set(gtk_model_, iter, 0, icon, -1); | |
| 330 g_object_unref(icon); | |
| 331 gtk_column_index++; | |
| 332 } | |
| 333 for (size_t i = 0; i < table_->GetVisibleColumnCount(); | |
| 334 ++i, ++gtk_column_index) { | |
| 335 std::string text = | |
| 336 UTF16ToUTF8(table_->model()->GetText(row_index, | |
| 337 table_->GetVisibleColumnAt(i).id)); | |
| 338 gtk_list_store_set(gtk_model_, iter, gtk_column_index, text.c_str(), -1); | |
| 339 } | |
| 340 } | |
| 341 | |
| 342 void NativeTableGtk::OnCursorChanged(GtkWidget* widget) { | |
| 343 // Ignore the signal if no row is selected. This can occur when GTK | |
| 344 // first opens a window (i.e. no row is selected but the cursor is set | |
| 345 // to the first row). When a user clicks on a row, the row is selected, | |
| 346 // and then "cursor-changed" signal is emitted, hence the selection | |
| 347 // count will be 1 here. | |
| 348 if (gtk_tree_selection_count_selected_rows(tree_selection_) == 0) { | |
| 349 return; | |
| 350 } | |
| 351 GtkTreePath *tree_path = NULL; | |
| 352 gtk_tree_view_get_cursor(tree_view_, &tree_path, NULL); | |
| 353 if (tree_path) { | |
| 354 const gint* indices = gtk_tree_path_get_indices(tree_path); | |
| 355 CHECK(indices); | |
| 356 table_->SelectRow(indices[0]); | |
| 357 gtk_tree_path_free(tree_path); | |
| 358 } | |
| 359 } | |
| 360 | |
| 361 GdkPixbuf* NativeTableGtk::GetModelIcon(int row) { | |
| 362 SkBitmap icon = table_->model()->GetIcon(row); | |
| 363 return gfx::GdkPixbufFromSkBitmap(&icon); | |
| 364 } | |
| 365 | |
| 366 // static | |
| 367 NativeTableWrapper* NativeTableWrapper::CreateNativeWrapper(TableView2* table) { | |
| 368 return new NativeTableGtk(table); | |
| 369 } | |
| 370 | |
| 371 } // namespace views | |
| OLD | NEW |