1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/gtk/task_manager_gtk.h"
7 #include <gdk/gdkkeysyms.h>
14 #include "base/auto_reset.h"
15 #include "base/command_line.h"
16 #include "base/logging.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/prefs/scoped_user_pref_update.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/defaults.h"
22 #include "chrome/browser/memory_purger.h"
23 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
24 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
25 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
26 #include "chrome/browser/ui/gtk/gtk_tree.h"
27 #include "chrome/browser/ui/gtk/gtk_util.h"
28 #include "chrome/browser/ui/gtk/menu_gtk.h"
29 #include "chrome/browser/ui/host_desktop.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/common/pref_names.h"
32 #include "grit/chromium_strings.h"
33 #include "grit/ui_resources.h"
34 #include "third_party/skia/include/core/SkBitmap.h"
35 #include "ui/base/gtk/gtk_hig_constants.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/models/simple_menu_model.h"
38 #include "ui/base/resource/resource_bundle.h"
39 #include "ui/gfx/gtk_util.h"
40 #include "ui/gfx/image/image.h"
44 // The task manager window default size.
45 const int kDefaultWidth = 460;
46 const int kDefaultHeight = 270;
48 // The resource id for the 'End process' button.
49 const gint kTaskManagerResponseKill = 1;
51 // The resource id for the 'Stats for nerds' link button.
52 const gint kTaskManagerAboutMemoryLink = 2;
54 // The resource id for the 'Purge Memory' button
55 const gint kTaskManagerPurgeMemory = 3;
57 enum TaskManagerColumn {
60 kTaskManagerProfileName,
61 kTaskManagerSharedMem,
62 kTaskManagerPrivateMem,
65 kTaskManagerProcessID,
66 kTaskManagerJavaScriptMemory,
67 kTaskManagerWebCoreImageCache,
68 kTaskManagerWebCoreScriptsCache,
69 kTaskManagerWebCoreCssCache,
70 kTaskManagerVideoMemory,
72 kTaskManagerSqliteMemoryUsed,
73 kTaskManagerGoatsTeleported,
74 kTaskManagerColumnCount,
77 const TaskManagerColumn kTaskManagerLastVisibleColumn =
78 kTaskManagerGoatsTeleported;
80 TaskManagerColumn TaskManagerResourceIDToColumnID(int id) {
82 case IDS_TASK_MANAGER_TASK_COLUMN:
83 return kTaskManagerTask;
84 case IDS_TASK_MANAGER_PROFILE_NAME_COLUMN:
85 return kTaskManagerProfileName;
86 case IDS_TASK_MANAGER_SHARED_MEM_COLUMN:
87 return kTaskManagerSharedMem;
88 case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN:
89 return kTaskManagerPrivateMem;
90 case IDS_TASK_MANAGER_CPU_COLUMN:
91 return kTaskManagerCPU;
92 case IDS_TASK_MANAGER_NET_COLUMN:
93 return kTaskManagerNetwork;
94 case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:
95 return kTaskManagerProcessID;
96 case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN:
97 return kTaskManagerJavaScriptMemory;
98 case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN:
99 return kTaskManagerWebCoreImageCache;
100 case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN:
101 return kTaskManagerWebCoreScriptsCache;
102 case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN:
103 return kTaskManagerWebCoreCssCache;
104 case IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN:
105 return kTaskManagerVideoMemory;
106 case IDS_TASK_MANAGER_FPS_COLUMN:
107 return kTaskManagerFPS;
108 case IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN:
109 return kTaskManagerSqliteMemoryUsed;
110 case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN:
111 return kTaskManagerGoatsTeleported;
114 return static_cast<TaskManagerColumn>(-1);
118 int TaskManagerColumnIDToResourceID(int id) {
120 case kTaskManagerTask:
121 return IDS_TASK_MANAGER_TASK_COLUMN;
122 case kTaskManagerProfileName:
123 return IDS_TASK_MANAGER_PROFILE_NAME_COLUMN;
124 case kTaskManagerSharedMem:
125 return IDS_TASK_MANAGER_SHARED_MEM_COLUMN;
126 case kTaskManagerPrivateMem:
127 return IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN;
128 case kTaskManagerCPU:
129 return IDS_TASK_MANAGER_CPU_COLUMN;
130 case kTaskManagerNetwork:
131 return IDS_TASK_MANAGER_NET_COLUMN;
132 case kTaskManagerProcessID:
133 return IDS_TASK_MANAGER_PROCESS_ID_COLUMN;
134 case kTaskManagerJavaScriptMemory:
135 return IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN;
136 case kTaskManagerWebCoreImageCache:
137 return IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN;
138 case kTaskManagerWebCoreScriptsCache:
139 return IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN;
140 case kTaskManagerWebCoreCssCache:
141 return IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN;
142 case kTaskManagerVideoMemory:
143 return IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN;
144 case kTaskManagerFPS:
145 return IDS_TASK_MANAGER_FPS_COLUMN;
146 case kTaskManagerSqliteMemoryUsed:
147 return IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN;
148 case kTaskManagerGoatsTeleported:
149 return IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN;
156 // Should be used for all gtk_tree_view functions that require a column index on
159 // We need colid - 1 because the gtk_tree_view function is asking for the
160 // column index, not the column id, and both kTaskManagerIcon and
161 // kTaskManagerTask are in the same column index, so all column IDs are off by
163 int TreeViewColumnIndexFromID(TaskManagerColumn colid) {
167 // Shows or hides a treeview column.
168 void TreeViewColumnSetVisible(GtkWidget* treeview, TaskManagerColumn colid,
170 GtkTreeViewColumn* column = gtk_tree_view_get_column(
171 GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
172 gtk_tree_view_column_set_visible(column, visible);
175 bool TreeViewColumnIsVisible(GtkWidget* treeview, TaskManagerColumn colid) {
176 GtkTreeViewColumn* column = gtk_tree_view_get_column(
177 GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
178 return gtk_tree_view_column_get_visible(column);
181 // The task column is special because it has an icon and it gets special
182 // treatment with respect to resizing the columns.
183 void TreeViewInsertTaskColumn(GtkWidget* treeview, int resid) {
184 int colid = TaskManagerResourceIDToColumnID(resid);
185 GtkTreeViewColumn* column = gtk_tree_view_column_new();
186 gtk_tree_view_column_set_title(column,
187 l10n_util::GetStringUTF8(resid).c_str());
188 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(treeview), colid);
189 GtkCellRenderer* image_renderer = gtk_cell_renderer_pixbuf_new();
190 gtk_tree_view_column_pack_start(column, image_renderer, FALSE);
191 gtk_tree_view_column_add_attribute(column, image_renderer,
192 "pixbuf", kTaskManagerIcon);
193 GtkCellRenderer* text_renderer = gtk_cell_renderer_text_new();
194 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
195 gtk_tree_view_column_add_attribute(column, text_renderer, "markup", colid);
196 gtk_tree_view_column_set_resizable(column, TRUE);
197 // This is temporary: we'll turn expanding off after getting the size.
198 gtk_tree_view_column_set_expand(column, TRUE);
199 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
200 gtk_tree_view_column_set_sort_column_id(column, colid);
203 // Inserts a column with a column id of |colid| and |name|.
204 void TreeViewInsertColumnWithName(GtkWidget* treeview,
205 TaskManagerColumn colid, const char* name) {
206 GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
207 gtk_tree_view_insert_column_with_attributes(
208 GTK_TREE_VIEW(treeview), -1,
212 GtkTreeViewColumn* column = gtk_tree_view_get_column(
213 GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
214 gtk_tree_view_column_set_resizable(column, TRUE);
215 gtk_tree_view_column_set_sort_column_id(column, colid);
218 // Loads the column name from |resid| and uses the corresponding
219 // TaskManagerColumn value as the column id to insert into the treeview.
220 void TreeViewInsertColumn(GtkWidget* treeview, int resid) {
221 TreeViewInsertColumnWithName(treeview, TaskManagerResourceIDToColumnID(resid),
222 l10n_util::GetStringUTF8(resid).c_str());
225 // Set the current width of the column without forcing a fixed or maximum
226 // width as gtk_tree_view_column_set_[fixed|maximum]_width() would. This would
227 // basically be gtk_tree_view_column_set_width() except that there is no such
228 // function. It turns out that other applications have done similar hacks to do
229 // the same thing - search the web for that nonexistent function name! :)
230 void TreeViewColumnSetWidth(GtkTreeViewColumn* column, gint width) {
231 column->width = width;
232 column->resized_width = width;
233 column->use_resized_width = TRUE;
234 // Needed for use_resized_width to be effective.
235 gtk_widget_queue_resize(column->tree_view);
240 class TaskManagerGtk::ContextMenuController
241 : public ui::SimpleMenuModel::Delegate {
243 explicit ContextMenuController(TaskManagerGtk* task_manager)
244 : task_manager_(task_manager) {
245 menu_model_.reset(new ui::SimpleMenuModel(this));
246 for (int i = kTaskManagerTask; i <= kTaskManagerLastVisibleColumn; i++) {
247 menu_model_->AddCheckItemWithStringId(
248 i, TaskManagerColumnIDToResourceID(i));
250 menu_.reset(new MenuGtk(NULL, menu_model_.get()));
253 virtual ~ContextMenuController() {}
255 void RunMenu(const gfx::Point& point, guint32 event_time) {
256 menu_->PopupAsContext(point, event_time);
260 task_manager_ = NULL;
265 // ui::SimpleMenuModel::Delegate implementation:
266 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
273 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
277 TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
278 return TreeViewColumnIsVisible(task_manager_->treeview_, colid);
281 virtual bool GetAcceleratorForCommandId(
283 ui::Accelerator* accelerator) OVERRIDE {
287 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
291 TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
292 bool visible = !TreeViewColumnIsVisible(task_manager_->treeview_, colid);
293 TreeViewColumnSetVisible(task_manager_->treeview_, colid, visible);
296 // The model and view for the right click context menu.
297 scoped_ptr<ui::SimpleMenuModel> menu_model_;
298 scoped_ptr<MenuGtk> menu_;
300 // The TaskManager the context menu was brought up for. Set to NULL when the
302 TaskManagerGtk* task_manager_;
304 DISALLOW_COPY_AND_ASSIGN(ContextMenuController);
307 TaskManagerGtk::TaskManagerGtk()
308 : task_manager_(TaskManager::GetInstance()),
309 model_(TaskManager::GetInstance()->model()),
314 ignore_selection_changed_(false) {
319 TaskManagerGtk* TaskManagerGtk::instance_ = NULL;
321 TaskManagerGtk::~TaskManagerGtk() {
322 model_->RemoveObserver(this);
323 task_manager_->OnWindowClosed();
325 gtk_accel_group_disconnect_key(accel_group_, GDK_w, GDK_CONTROL_MASK);
326 gtk_window_remove_accel_group(GTK_WINDOW(dialog_), accel_group_);
327 g_object_unref(accel_group_);
330 // Disconnect the destroy signal so it doesn't delete |this|.
331 g_signal_handler_disconnect(G_OBJECT(dialog_), destroy_handler_id_);
332 gtk_widget_destroy(dialog_);
335 ////////////////////////////////////////////////////////////////////////////////
336 // TaskManagerGtk, TaskManagerModelObserver implementation:
338 void TaskManagerGtk::OnModelChanged() {
342 void TaskManagerGtk::OnItemsChanged(int start, int length) {
344 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
346 NOTREACHED() << "Can't get child " << start <<
347 " from GTK_TREE_MODEL(process_list_)";
350 for (int i = start; i < start + length; i++) {
351 SetRowDataFromModel(i, &iter);
352 if (i != start + length - 1) {
353 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(process_list_), &iter)) {
354 NOTREACHED() << "Can't get next GtkTreeIter object from process_list_ "
355 "iterator at position " << i;
361 void TaskManagerGtk::OnItemsAdded(int start, int length) {
362 base::AutoReset<bool> autoreset(&ignore_selection_changed_, true);
366 gtk_list_store_prepend(process_list_, &iter);
367 } else if (start >= process_count_) {
368 gtk_list_store_append(process_list_, &iter);
371 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &sibling,
373 gtk_list_store_insert_before(process_list_, &iter, &sibling);
376 SetRowDataFromModel(start, &iter);
378 for (int i = start + 1; i < start + length; i++) {
379 gtk_list_store_insert_after(process_list_, &iter, &iter);
380 SetRowDataFromModel(i, &iter);
383 process_count_ += length;
386 void TaskManagerGtk::OnItemsRemoved(int start, int length) {
388 base::AutoReset<bool> autoreset(&ignore_selection_changed_, true);
391 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
394 for (int i = 0; i < length; i++) {
395 // |iter| is moved to the next valid node when the current node is
397 gtk_list_store_remove(process_list_, &iter);
400 process_count_ -= length;
403 // It is possible that we have removed the current selection; run selection
404 // changed to detect that case.
405 OnSelectionChanged(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview_)));
408 ////////////////////////////////////////////////////////////////////////////////
409 // TaskManagerGtk, public:
411 void TaskManagerGtk::Close() {
412 // Blow away our dialog - this will cause TaskManagerGtk to free itself.
413 gtk_widget_destroy(dialog_);
418 void TaskManagerGtk::Show() {
420 // If there's a Task manager window open already, just activate it.
421 gtk_util::PresentWindow(instance_->dialog_, 0);
423 instance_ = new TaskManagerGtk();
424 instance_->model_->StartUpdating();
428 ////////////////////////////////////////////////////////////////////////////////
429 // TaskManagerGtk, private:
431 void TaskManagerGtk::Init() {
432 dialog_ = gtk_dialog_new_with_buttons(
433 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_TITLE).c_str(),
434 // Task Manager window is shared between all browsers.
436 GTK_DIALOG_NO_SEPARATOR,
439 // Allow browser windows to go in front of the task manager dialog in
441 gtk_window_set_type_hint(GTK_WINDOW(dialog_), GDK_WINDOW_TYPE_HINT_NORMAL);
443 if (CommandLine::ForCurrentProcess()->HasSwitch(
444 switches::kPurgeMemoryButton)) {
445 gtk_dialog_add_button(GTK_DIALOG(dialog_),
446 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_PURGE_MEMORY).c_str(),
447 kTaskManagerPurgeMemory);
450 if (browser_defaults::kShowCancelButtonInTaskManager) {
451 gtk_dialog_add_button(GTK_DIALOG(dialog_),
452 l10n_util::GetStringUTF8(IDS_CLOSE).c_str(),
453 GTK_RESPONSE_DELETE_EVENT);
456 gtk_dialog_add_button(GTK_DIALOG(dialog_),
457 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_KILL).c_str(),
458 kTaskManagerResponseKill);
460 // The response button should not be sensitive when the dialog is first opened
461 // because the selection is initially empty.
462 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
463 kTaskManagerResponseKill, FALSE);
465 GtkWidget* link = gtk_chrome_link_button_new(
466 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_ABOUT_MEMORY_LINK).c_str());
467 gtk_dialog_add_action_widget(GTK_DIALOG(dialog_), link,
468 kTaskManagerAboutMemoryLink);
470 // Setting the link widget to secondary positions the button on the left side
471 // of the action area (vice versa for RTL layout).
472 GtkWidget* action_area = gtk_dialog_get_action_area(GTK_DIALOG(dialog_));
473 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(action_area), link, TRUE);
475 ConnectAccelerators();
477 GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_));
478 gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
480 destroy_handler_id_ = g_signal_connect(dialog_, "destroy",
481 G_CALLBACK(OnDestroyThunk), this);
482 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
483 g_signal_connect(dialog_, "button-press-event",
484 G_CALLBACK(OnButtonEventThunk), this);
485 gtk_widget_add_events(dialog_,
486 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
488 // Wrap the treeview widget in a scrolled window in order to have a frame.
489 GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL);
490 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
491 GTK_SHADOW_ETCHED_IN);
492 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
493 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
495 gtk_container_add(GTK_CONTAINER(content_area), scrolled);
497 CreateTaskManagerTreeview();
498 gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview_), TRUE);
499 g_signal_connect(treeview_, "row-activated",
500 G_CALLBACK(OnRowActivatedThunk), this);
501 g_signal_connect(treeview_, "button-press-event",
502 G_CALLBACK(OnButtonEventThunk), this);
504 // |selection| is owned by |treeview_|.
505 GtkTreeSelection* selection = gtk_tree_view_get_selection(
506 GTK_TREE_VIEW(treeview_));
507 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
508 g_signal_connect(selection, "changed",
509 G_CALLBACK(OnSelectionChangedThunk), this);
511 gtk_container_add(GTK_CONTAINER(scrolled), treeview_);
513 SetInitialDialogSize();
514 gtk_util::ShowDialog(dialog_);
516 // If the model already has resources, we need to add them before we start
518 if (model_->ResourceCount() > 0)
519 OnItemsAdded(0, model_->ResourceCount());
521 model_->AddObserver(this);
524 void TaskManagerGtk::SetInitialDialogSize() {
525 // Hook up to the realize event so we can size the task column to the
526 // size of the leftover space after packing the other columns.
527 g_signal_connect(treeview_, "realize",
528 G_CALLBACK(OnTreeViewRealizeThunk), this);
529 // If we previously saved the dialog's bounds, use them.
530 if (g_browser_process->local_state()) {
531 const DictionaryValue* placement_pref =
532 g_browser_process->local_state()->GetDictionary(
533 prefs::kTaskManagerWindowPlacement);
534 int top = 0, left = 0, bottom = 1, right = 1;
535 if (placement_pref &&
536 placement_pref->GetInteger("top", &top) &&
537 placement_pref->GetInteger("left", &left) &&
538 placement_pref->GetInteger("bottom", &bottom) &&
539 placement_pref->GetInteger("right", &right)) {
540 gtk_window_resize(GTK_WINDOW(dialog_),
541 std::max(1, right - left),
542 std::max(1, bottom - top));
547 // Otherwise, just set a default size (GTK will override this if it's not
548 // large enough to hold the window's contents).
549 gtk_window_set_default_size(
550 GTK_WINDOW(dialog_), kDefaultWidth, kDefaultHeight);
553 void TaskManagerGtk::ConnectAccelerators() {
554 accel_group_ = gtk_accel_group_new();
555 gtk_window_add_accel_group(GTK_WINDOW(dialog_), accel_group_);
557 gtk_accel_group_connect(accel_group_,
558 GDK_w, GDK_CONTROL_MASK, GtkAccelFlags(0),
559 g_cclosure_new(G_CALLBACK(OnGtkAcceleratorThunk),
563 void TaskManagerGtk::CreateTaskManagerTreeview() {
564 process_list_ = gtk_list_store_new(kTaskManagerColumnCount,
565 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
566 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
567 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
568 G_TYPE_STRING, G_TYPE_STRING);
570 // Support sorting on all columns.
571 process_list_sort_ = gtk_tree_model_sort_new_with_model(
572 GTK_TREE_MODEL(process_list_));
573 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
575 ComparePage, this, NULL);
576 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
578 CompareProfileName, this, NULL);
579 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
580 kTaskManagerSharedMem,
581 CompareSharedMemory, this, NULL);
582 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
583 kTaskManagerPrivateMem,
584 ComparePrivateMemory, this, NULL);
585 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
586 kTaskManagerJavaScriptMemory,
587 CompareV8Memory, this, NULL);
588 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
590 CompareCPU, this, NULL);
591 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
593 CompareNetwork, this, NULL);
594 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
595 kTaskManagerProcessID,
596 CompareProcessID, this, NULL);
597 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
598 kTaskManagerWebCoreImageCache,
599 CompareWebCoreImageCache, this, NULL);
600 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
601 kTaskManagerWebCoreScriptsCache,
602 CompareWebCoreScriptsCache, this, NULL);
603 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
604 kTaskManagerWebCoreCssCache,
605 CompareWebCoreCssCache, this, NULL);
606 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
607 kTaskManagerVideoMemory,
608 CompareVideoMemory, this, NULL);
609 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
611 CompareFPS, this, NULL);
612 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
613 kTaskManagerSqliteMemoryUsed,
614 CompareSqliteMemoryUsed, this, NULL);
615 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
616 kTaskManagerGoatsTeleported,
617 CompareGoatsTeleported, this, NULL);
618 treeview_ = gtk_tree_view_new_with_model(process_list_sort_);
620 // Insert all the columns.
621 TreeViewInsertTaskColumn(treeview_, IDS_TASK_MANAGER_TASK_COLUMN);
622 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROFILE_NAME_COLUMN);
623 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SHARED_MEM_COLUMN);
624 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN);
625 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_CPU_COLUMN);
626 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_NET_COLUMN);
627 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROCESS_ID_COLUMN);
628 TreeViewInsertColumn(treeview_,
629 IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN);
630 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN);
631 TreeViewInsertColumn(treeview_,
632 IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN);
633 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN);
634 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN);
635 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_FPS_COLUMN);
636 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN);
637 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN);
639 // Hide some columns by default.
640 TreeViewColumnSetVisible(treeview_, kTaskManagerProfileName, false);
641 TreeViewColumnSetVisible(treeview_, kTaskManagerSharedMem, false);
642 TreeViewColumnSetVisible(treeview_, kTaskManagerJavaScriptMemory, false);
643 TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreImageCache, false);
644 TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreScriptsCache, false);
645 TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreCssCache, false);
646 TreeViewColumnSetVisible(treeview_, kTaskManagerVideoMemory, false);
647 TreeViewColumnSetVisible(treeview_, kTaskManagerSqliteMemoryUsed, false);
648 TreeViewColumnSetVisible(treeview_, kTaskManagerGoatsTeleported, false);
650 g_object_unref(process_list_);
651 g_object_unref(process_list_sort_);
654 std::string TaskManagerGtk::GetModelText(int row, int col_id) {
655 return UTF16ToUTF8(model_->GetResourceById(row, col_id));
658 GdkPixbuf* TaskManagerGtk::GetModelIcon(int row) {
659 SkBitmap icon = *model_->GetResourceIcon(row).bitmap();
660 if (icon.pixelRef() ==
661 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
662 IDR_DEFAULT_FAVICON).AsBitmap().pixelRef()) {
663 return static_cast<GdkPixbuf*>(g_object_ref(
664 GtkThemeService::GetDefaultFavicon(true).ToGdkPixbuf()));
667 return gfx::GdkPixbufFromSkBitmap(icon);
670 void TaskManagerGtk::SetRowDataFromModel(int row, GtkTreeIter* iter) {
671 GdkPixbuf* icon = GetModelIcon(row);
672 std::string task = GetModelText(row, IDS_TASK_MANAGER_TASK_COLUMN);
673 std::string profile_name =
674 GetModelText(row, IDS_TASK_MANAGER_PROFILE_NAME_COLUMN);
675 gchar* task_markup = g_markup_escape_text(task.c_str(), task.length());
676 std::string shared_mem =
677 GetModelText(row, IDS_TASK_MANAGER_SHARED_MEM_COLUMN);
678 std::string priv_mem = GetModelText(row, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN);
679 std::string cpu = GetModelText(row, IDS_TASK_MANAGER_CPU_COLUMN);
680 std::string net = GetModelText(row, IDS_TASK_MANAGER_NET_COLUMN);
681 std::string procid = GetModelText(row, IDS_TASK_MANAGER_PROCESS_ID_COLUMN);
683 // Querying the renderer metrics is slow as it has to do IPC, so only do it
684 // when the columns are visible.
685 std::string javascript_memory;
686 if (TreeViewColumnIsVisible(treeview_, kTaskManagerJavaScriptMemory)) {
688 GetModelText(row, IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN);
690 std::string wk_img_cache;
691 if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreImageCache)) {
693 GetModelText(row, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN);
695 std::string wk_scripts_cache;
696 if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreScriptsCache)) {
698 GetModelText(row, IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN);
700 std::string wk_css_cache;
701 if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreCssCache)) {
703 GetModelText(row, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN);
705 std::string video_memory;
706 if (TreeViewColumnIsVisible(treeview_, kTaskManagerVideoMemory))
707 video_memory = GetModelText(row, IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN);
709 if (TreeViewColumnIsVisible(treeview_, kTaskManagerFPS))
710 fps = GetModelText(row, IDS_TASK_MANAGER_FPS_COLUMN);
711 std::string sqlite_memory;
712 if (TreeViewColumnIsVisible(treeview_, kTaskManagerSqliteMemoryUsed)) {
714 GetModelText(row, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN);
718 GetModelText(row, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN);
720 gtk_list_store_set(process_list_, iter,
721 kTaskManagerIcon, icon,
722 kTaskManagerTask, task_markup,
723 kTaskManagerProfileName, profile_name.c_str(),
724 kTaskManagerSharedMem, shared_mem.c_str(),
725 kTaskManagerPrivateMem, priv_mem.c_str(),
726 kTaskManagerCPU, cpu.c_str(),
727 kTaskManagerNetwork, net.c_str(),
728 kTaskManagerProcessID, procid.c_str(),
729 kTaskManagerJavaScriptMemory, javascript_memory.c_str(),
730 kTaskManagerWebCoreImageCache, wk_img_cache.c_str(),
731 kTaskManagerWebCoreScriptsCache, wk_scripts_cache.c_str(),
732 kTaskManagerWebCoreCssCache, wk_css_cache.c_str(),
733 kTaskManagerVideoMemory, video_memory.c_str(),
734 kTaskManagerFPS, fps.c_str(),
735 kTaskManagerSqliteMemoryUsed, sqlite_memory.c_str(),
736 kTaskManagerGoatsTeleported, goats.c_str(),
738 g_object_unref(icon);
742 void TaskManagerGtk::KillSelectedProcesses() {
743 GtkTreeSelection* selection = gtk_tree_view_get_selection(
744 GTK_TREE_VIEW(treeview_));
747 GList* paths = gtk_tree_selection_get_selected_rows(selection, &model);
748 for (GList* item = paths; item; item = item->next) {
749 GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path(
750 GTK_TREE_MODEL_SORT(process_list_sort_),
751 reinterpret_cast<GtkTreePath*>(item->data));
752 int row = gtk_tree::GetRowNumForPath(path);
753 gtk_tree_path_free(path);
754 task_manager_->KillProcess(row);
756 g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
760 void TaskManagerGtk::ShowContextMenu(const gfx::Point& point,
761 guint32 event_time) {
762 if (!menu_controller_.get())
763 menu_controller_.reset(new ContextMenuController(this));
765 menu_controller_->RunMenu(point, event_time);
768 void TaskManagerGtk::OnLinkActivated() {
769 task_manager_->OpenAboutMemory(chrome::HOST_DESKTOP_TYPE_NATIVE);
772 gint TaskManagerGtk::CompareImpl(GtkTreeModel* model, GtkTreeIter* a,
773 GtkTreeIter* b, int id) {
774 int row1 = gtk_tree::GetRowNumForIter(model, b);
775 int row2 = gtk_tree::GetRowNumForIter(model, a);
777 // Otherwise, make sure grouped resources are shown together.
778 TaskManagerModel::GroupRange group_range1 =
779 model_->GetGroupRangeForResource(row1);
780 TaskManagerModel::GroupRange group_range2 =
781 model_->GetGroupRangeForResource(row2);
783 if (group_range1 == group_range2) {
784 // Sort within groups.
785 // We want the first-in-group row at the top, whether we are sorting up or
787 GtkSortType sort_type;
788 gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(process_list_sort_),
790 if (row1 == group_range1.first)
791 return sort_type == GTK_SORT_ASCENDING ? -1 : 1;
792 if (row2 == group_range2.first)
793 return sort_type == GTK_SORT_ASCENDING ? 1 : -1;
795 return model_->CompareValues(row1, row2, id);
797 // Sort between groups.
798 // Compare by the first-in-group rows so that the groups will stay together.
799 return model_->CompareValues(group_range1.first, group_range2.first, id);
803 void TaskManagerGtk::OnDestroy(GtkWidget* dialog) {
808 void TaskManagerGtk::OnResponse(GtkWidget* dialog, int response_id) {
809 if (response_id == GTK_RESPONSE_DELETE_EVENT) {
810 // Store the dialog's size so we can restore it the next time it's opened.
811 if (g_browser_process->local_state()) {
812 gfx::Rect dialog_bounds = gtk_util::GetDialogBounds(GTK_WIDGET(dialog));
814 DictionaryPrefUpdate update(g_browser_process->local_state(),
815 prefs::kTaskManagerWindowPlacement);
816 DictionaryValue* placement_pref = update.Get();
817 // Note that we store left/top for consistency with Windows, but that we
818 // *don't* restore them.
819 placement_pref->SetInteger("left", dialog_bounds.x());
820 placement_pref->SetInteger("top", dialog_bounds.y());
821 placement_pref->SetInteger("right", dialog_bounds.right());
822 placement_pref->SetInteger("bottom", dialog_bounds.bottom());
823 placement_pref->SetBoolean("maximized", false);
828 } else if (response_id == kTaskManagerResponseKill) {
829 KillSelectedProcesses();
830 } else if (response_id == kTaskManagerAboutMemoryLink) {
832 } else if (response_id == kTaskManagerPurgeMemory) {
833 MemoryPurger::PurgeAll();
837 void TaskManagerGtk::OnTreeViewRealize(GtkTreeView* treeview) {
838 // Five columns show by default: the task column, the memory column, the CPU
839 // column, the network column, and the FPS column. Initially we set the task
840 // tolumn to take all the extra space, with the other columns being sized to
841 // fit the column names. Here we turn off the expand property of the first
842 // column (to make the table behave sanely when the user resizes it), and set
843 // the effective sizes of all five default columns to the automatically chosen
844 // sizes before any rows are added. This causes them to stay at those sizes
845 // even if the data would overflow, preventing a horizontal scroll bar from
846 // appearing due to the row data.
847 static const TaskManagerColumn dfl_columns[] = {kTaskManagerPrivateMem,
851 GtkTreeViewColumn* column = NULL;
853 for (size_t i = 0; i < arraysize(dfl_columns); ++i) {
854 column = gtk_tree_view_get_column(treeview,
855 TreeViewColumnIndexFromID(dfl_columns[i]));
856 width = gtk_tree_view_column_get_width(column);
857 TreeViewColumnSetWidth(column, width);
859 // Do the task column separately since it's a little different.
860 column = gtk_tree_view_get_column(treeview,
861 TreeViewColumnIndexFromID(kTaskManagerTask));
862 width = gtk_tree_view_column_get_width(column);
863 // Turn expanding back off to make resizing columns behave sanely.
864 gtk_tree_view_column_set_expand(column, FALSE);
865 TreeViewColumnSetWidth(column, width);
868 void TaskManagerGtk::OnSelectionChanged(GtkTreeSelection* selection) {
869 if (ignore_selection_changed_)
871 base::AutoReset<bool> autoreset(&ignore_selection_changed_, true);
873 // The set of groups that should be selected.
874 std::set<TaskManagerModel::GroupRange> ranges;
875 bool selection_contains_browser_process = false;
878 GList* paths = gtk_tree_selection_get_selected_rows(selection, &model);
879 for (GList* item = paths; item; item = item->next) {
880 GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path(
881 GTK_TREE_MODEL_SORT(process_list_sort_),
882 reinterpret_cast<GtkTreePath*>(item->data));
883 int row = gtk_tree::GetRowNumForPath(path);
884 gtk_tree_path_free(path);
885 if (task_manager_->IsBrowserProcess(row))
886 selection_contains_browser_process = true;
887 ranges.insert(model_->GetGroupRangeForResource(row));
889 g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
892 for (std::set<TaskManagerModel::GroupRange>::iterator iter = ranges.begin();
893 iter != ranges.end(); ++iter) {
894 for (int i = 0; i < iter->second; ++i) {
895 GtkTreePath* child_path = gtk_tree_path_new_from_indices(iter->first + i,
897 GtkTreePath* sort_path = gtk_tree_model_sort_convert_child_path_to_path(
898 GTK_TREE_MODEL_SORT(process_list_sort_), child_path);
899 gtk_tree_selection_select_path(selection, sort_path);
900 gtk_tree_path_free(child_path);
901 gtk_tree_path_free(sort_path);
905 bool sensitive = (paths != NULL) && !selection_contains_browser_process;
906 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
907 kTaskManagerResponseKill, sensitive);
910 void TaskManagerGtk::OnRowActivated(GtkWidget* widget,
912 GtkTreeViewColumn* column) {
913 GtkTreePath* child_path = gtk_tree_model_sort_convert_path_to_child_path(
914 GTK_TREE_MODEL_SORT(process_list_sort_), path);
915 int row = gtk_tree::GetRowNumForPath(child_path);
916 gtk_tree_path_free(child_path);
917 task_manager_->ActivateProcess(row);
920 gboolean TaskManagerGtk::OnButtonEvent(GtkWidget* widget,
921 GdkEventButton* event) {
922 // GTK does menu on mouse-up while views does menu on mouse-down,
923 // so this function can be called from either signal.
924 if (event->button == 3) {
925 ShowContextMenu(gfx::Point(event->x_root, event->y_root),
933 gboolean TaskManagerGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
934 GObject* acceleratable,
936 GdkModifierType modifier) {
937 if (keyval == GDK_w && modifier == GDK_CONTROL_MASK) {
938 // The GTK_RESPONSE_DELETE_EVENT response must be sent before the widget
939 // is destroyed. The deleted object will receive gtk signals otherwise.
940 gtk_dialog_response(GTK_DIALOG(dialog_), GTK_RESPONSE_DELETE_EVENT);
948 // Declared in browser_dialogs.h.
949 void ShowTaskManager(Browser* browser) {
950 TaskManagerGtk::Show();
953 } // namespace chrome