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 |