- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / task_manager_gtk.cc
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.
4
5 #include "chrome/browser/ui/gtk/task_manager_gtk.h"
6
7 #include <gdk/gdkkeysyms.h>
8
9 #include <algorithm>
10 #include <set>
11 #include <utility>
12 #include <vector>
13
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"
41
42 namespace {
43
44 // The task manager window default size.
45 const int kDefaultWidth = 460;
46 const int kDefaultHeight = 270;
47
48 // The resource id for the 'End process' button.
49 const gint kTaskManagerResponseKill = 1;
50
51 // The resource id for the 'Stats for nerds' link button.
52 const gint kTaskManagerAboutMemoryLink = 2;
53
54 // The resource id for the 'Purge Memory' button
55 const gint kTaskManagerPurgeMemory = 3;
56
57 enum TaskManagerColumn {
58   kTaskManagerIcon,
59   kTaskManagerTask,
60   kTaskManagerProfileName,
61   kTaskManagerSharedMem,
62   kTaskManagerPrivateMem,
63   kTaskManagerCPU,
64   kTaskManagerNetwork,
65   kTaskManagerProcessID,
66   kTaskManagerJavaScriptMemory,
67   kTaskManagerWebCoreImageCache,
68   kTaskManagerWebCoreScriptsCache,
69   kTaskManagerWebCoreCssCache,
70   kTaskManagerVideoMemory,
71   kTaskManagerFPS,
72   kTaskManagerSqliteMemoryUsed,
73   kTaskManagerGoatsTeleported,
74   kTaskManagerColumnCount,
75 };
76
77 const TaskManagerColumn kTaskManagerLastVisibleColumn =
78     kTaskManagerGoatsTeleported;
79
80 TaskManagerColumn TaskManagerResourceIDToColumnID(int id) {
81   switch (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;
112     default:
113       NOTREACHED();
114       return static_cast<TaskManagerColumn>(-1);
115   }
116 }
117
118 int TaskManagerColumnIDToResourceID(int id) {
119   switch (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;
150     default:
151       NOTREACHED();
152       return -1;
153   }
154 }
155
156 // Should be used for all gtk_tree_view functions that require a column index on
157 // input.
158 //
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
162 // one.
163 int TreeViewColumnIndexFromID(TaskManagerColumn colid) {
164   return colid - 1;
165 }
166
167 // Shows or hides a treeview column.
168 void TreeViewColumnSetVisible(GtkWidget* treeview, TaskManagerColumn colid,
169                               bool visible) {
170   GtkTreeViewColumn* column = gtk_tree_view_get_column(
171       GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid));
172   gtk_tree_view_column_set_visible(column, visible);
173 }
174
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);
179 }
180
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);
201 }
202
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,
209       name, renderer,
210       "text", colid,
211       NULL);
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);
216 }
217
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());
223 }
224
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);
236 }
237
238 }  // namespace
239
240 class TaskManagerGtk::ContextMenuController
241     : public ui::SimpleMenuModel::Delegate {
242  public:
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));
249     }
250     menu_.reset(new MenuGtk(NULL, menu_model_.get()));
251   }
252
253   virtual ~ContextMenuController() {}
254
255   void RunMenu(const gfx::Point& point, guint32 event_time) {
256     menu_->PopupAsContext(point, event_time);
257   }
258
259   void Cancel() {
260     task_manager_ = NULL;
261     menu_->Cancel();
262   }
263
264  private:
265   // ui::SimpleMenuModel::Delegate implementation:
266   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
267     if (!task_manager_)
268       return false;
269
270     return true;
271   }
272
273   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
274     if (!task_manager_)
275       return false;
276
277     TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
278     return TreeViewColumnIsVisible(task_manager_->treeview_, colid);
279   }
280
281   virtual bool GetAcceleratorForCommandId(
282       int command_id,
283       ui::Accelerator* accelerator) OVERRIDE {
284     return false;
285   }
286
287   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
288     if (!task_manager_)
289       return;
290
291     TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id);
292     bool visible = !TreeViewColumnIsVisible(task_manager_->treeview_, colid);
293     TreeViewColumnSetVisible(task_manager_->treeview_, colid, visible);
294   }
295
296   // The model and view for the right click context menu.
297   scoped_ptr<ui::SimpleMenuModel> menu_model_;
298   scoped_ptr<MenuGtk> menu_;
299
300   // The TaskManager the context menu was brought up for. Set to NULL when the
301   // menu is canceled.
302   TaskManagerGtk* task_manager_;
303
304   DISALLOW_COPY_AND_ASSIGN(ContextMenuController);
305 };
306
307 TaskManagerGtk::TaskManagerGtk()
308   : task_manager_(TaskManager::GetInstance()),
309     model_(TaskManager::GetInstance()->model()),
310     dialog_(NULL),
311     treeview_(NULL),
312     process_list_(NULL),
313     process_count_(0),
314     ignore_selection_changed_(false) {
315   Init();
316 }
317
318 // static
319 TaskManagerGtk* TaskManagerGtk::instance_ = NULL;
320
321 TaskManagerGtk::~TaskManagerGtk() {
322   model_->RemoveObserver(this);
323   task_manager_->OnWindowClosed();
324
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_);
328   accel_group_ = NULL;
329
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_);
333 }
334
335 ////////////////////////////////////////////////////////////////////////////////
336 // TaskManagerGtk, TaskManagerModelObserver implementation:
337
338 void TaskManagerGtk::OnModelChanged() {
339   // Nothing to do.
340 }
341
342 void TaskManagerGtk::OnItemsChanged(int start, int length) {
343   GtkTreeIter iter;
344   if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
345                                      NULL, start)) {
346     NOTREACHED() << "Can't get child " << start <<
347         " from GTK_TREE_MODEL(process_list_)";
348   }
349
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;
356       }
357     }
358   }
359 }
360
361 void TaskManagerGtk::OnItemsAdded(int start, int length) {
362   base::AutoReset<bool> autoreset(&ignore_selection_changed_, true);
363
364   GtkTreeIter iter;
365   if (start == 0) {
366     gtk_list_store_prepend(process_list_, &iter);
367   } else if (start >= process_count_) {
368     gtk_list_store_append(process_list_, &iter);
369   } else {
370     GtkTreeIter sibling;
371     gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &sibling,
372                                   NULL, start);
373     gtk_list_store_insert_before(process_list_, &iter, &sibling);
374   }
375
376   SetRowDataFromModel(start, &iter);
377
378   for (int i = start + 1; i < start + length; i++) {
379     gtk_list_store_insert_after(process_list_, &iter, &iter);
380     SetRowDataFromModel(i, &iter);
381   }
382
383   process_count_ += length;
384 }
385
386 void TaskManagerGtk::OnItemsRemoved(int start, int length) {
387   {
388     base::AutoReset<bool> autoreset(&ignore_selection_changed_, true);
389
390     GtkTreeIter iter;
391     gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter,
392                                   NULL, start);
393
394     for (int i = 0; i < length; i++) {
395       // |iter| is moved to the next valid node when the current node is
396       // removed.
397       gtk_list_store_remove(process_list_, &iter);
398     }
399
400     process_count_ -= length;
401   }
402
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_)));
406 }
407
408 ////////////////////////////////////////////////////////////////////////////////
409 // TaskManagerGtk, public:
410
411 void TaskManagerGtk::Close() {
412   // Blow away our dialog - this will cause TaskManagerGtk to free itself.
413   gtk_widget_destroy(dialog_);
414   DCHECK(!instance_);
415 }
416
417 // static
418 void TaskManagerGtk::Show() {
419   if (instance_) {
420     // If there's a Task manager window open already, just activate it.
421     gtk_util::PresentWindow(instance_->dialog_, 0);
422   } else {
423     instance_ = new TaskManagerGtk();
424     instance_->model_->StartUpdating();
425   }
426 }
427
428 ////////////////////////////////////////////////////////////////////////////////
429 // TaskManagerGtk, private:
430
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.
435       NULL,
436       GTK_DIALOG_NO_SEPARATOR,
437       NULL);
438
439   // Allow browser windows to go in front of the task manager dialog in
440   // metacity.
441   gtk_window_set_type_hint(GTK_WINDOW(dialog_), GDK_WINDOW_TYPE_HINT_NORMAL);
442
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);
448   }
449
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);
454   }
455
456   gtk_dialog_add_button(GTK_DIALOG(dialog_),
457       l10n_util::GetStringUTF8(IDS_TASK_MANAGER_KILL).c_str(),
458       kTaskManagerResponseKill);
459
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);
464
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);
469
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);
474
475   ConnectAccelerators();
476
477   GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_));
478   gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
479
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);
487
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);
494
495   gtk_container_add(GTK_CONTAINER(content_area), scrolled);
496
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);
503
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);
510
511   gtk_container_add(GTK_CONTAINER(scrolled), treeview_);
512
513   SetInitialDialogSize();
514   gtk_util::ShowDialog(dialog_);
515
516   // If the model already has resources, we need to add them before we start
517   // observing events.
518   if (model_->ResourceCount() > 0)
519     OnItemsAdded(0, model_->ResourceCount());
520
521   model_->AddObserver(this);
522 }
523
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));
543       return;
544     }
545   }
546
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);
551 }
552
553 void TaskManagerGtk::ConnectAccelerators() {
554   accel_group_ = gtk_accel_group_new();
555   gtk_window_add_accel_group(GTK_WINDOW(dialog_), accel_group_);
556
557   gtk_accel_group_connect(accel_group_,
558                           GDK_w, GDK_CONTROL_MASK, GtkAccelFlags(0),
559                           g_cclosure_new(G_CALLBACK(OnGtkAcceleratorThunk),
560                                          this, NULL));
561 }
562
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);
569
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_),
574                                   kTaskManagerTask,
575                                   ComparePage, this, NULL);
576   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
577                                   kTaskManagerTask,
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_),
589                                   kTaskManagerCPU,
590                                   CompareCPU, this, NULL);
591   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_),
592                                   kTaskManagerNetwork,
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_),
610                                   kTaskManagerFPS,
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_);
619
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);
638
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);
649
650   g_object_unref(process_list_);
651   g_object_unref(process_list_sort_);
652 }
653
654 std::string TaskManagerGtk::GetModelText(int row, int col_id) {
655   return UTF16ToUTF8(model_->GetResourceById(row, col_id));
656 }
657
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()));
665   }
666
667   return gfx::GdkPixbufFromSkBitmap(icon);
668 }
669
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);
682
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)) {
687     javascript_memory =
688         GetModelText(row, IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN);
689   }
690   std::string wk_img_cache;
691   if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreImageCache)) {
692     wk_img_cache =
693         GetModelText(row, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN);
694   }
695   std::string wk_scripts_cache;
696   if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreScriptsCache)) {
697     wk_scripts_cache =
698         GetModelText(row, IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN);
699   }
700   std::string wk_css_cache;
701   if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreCssCache)) {
702     wk_css_cache =
703         GetModelText(row, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN);
704   }
705   std::string video_memory;
706   if (TreeViewColumnIsVisible(treeview_, kTaskManagerVideoMemory))
707     video_memory = GetModelText(row, IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN);
708   std::string fps;
709   if (TreeViewColumnIsVisible(treeview_, kTaskManagerFPS))
710     fps = GetModelText(row, IDS_TASK_MANAGER_FPS_COLUMN);
711   std::string sqlite_memory;
712   if (TreeViewColumnIsVisible(treeview_, kTaskManagerSqliteMemoryUsed)) {
713     sqlite_memory =
714         GetModelText(row, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN);
715   }
716
717   std::string goats =
718       GetModelText(row, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN);
719
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(),
737                      -1);
738   g_object_unref(icon);
739   g_free(task_markup);
740 }
741
742 void TaskManagerGtk::KillSelectedProcesses() {
743   GtkTreeSelection* selection = gtk_tree_view_get_selection(
744       GTK_TREE_VIEW(treeview_));
745
746   GtkTreeModel* model;
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);
755   }
756   g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
757   g_list_free(paths);
758 }
759
760 void TaskManagerGtk::ShowContextMenu(const gfx::Point& point,
761                                      guint32 event_time) {
762   if (!menu_controller_.get())
763     menu_controller_.reset(new ContextMenuController(this));
764
765   menu_controller_->RunMenu(point, event_time);
766 }
767
768 void TaskManagerGtk::OnLinkActivated() {
769   task_manager_->OpenAboutMemory(chrome::HOST_DESKTOP_TYPE_NATIVE);
770 }
771
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);
776
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);
782
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
786     // down.
787     GtkSortType sort_type;
788     gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(process_list_sort_),
789                                          NULL, &sort_type);
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;
794
795     return model_->CompareValues(row1, row2, id);
796   } else {
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);
800   }
801 }
802
803 void TaskManagerGtk::OnDestroy(GtkWidget* dialog) {
804   instance_ = NULL;
805   delete this;
806 }
807
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));
813
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);
824     }
825
826     instance_ = NULL;
827     delete this;
828   } else if (response_id == kTaskManagerResponseKill) {
829     KillSelectedProcesses();
830   } else if (response_id == kTaskManagerAboutMemoryLink) {
831     OnLinkActivated();
832   } else if (response_id == kTaskManagerPurgeMemory) {
833     MemoryPurger::PurgeAll();
834   }
835 }
836
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,
848                                                   kTaskManagerCPU,
849                                                   kTaskManagerNetwork,
850                                                   kTaskManagerFPS};
851   GtkTreeViewColumn* column = NULL;
852   gint width;
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);
858   }
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);
866 }
867
868 void TaskManagerGtk::OnSelectionChanged(GtkTreeSelection* selection) {
869   if (ignore_selection_changed_)
870     return;
871   base::AutoReset<bool> autoreset(&ignore_selection_changed_, true);
872
873   // The set of groups that should be selected.
874   std::set<TaskManagerModel::GroupRange> ranges;
875   bool selection_contains_browser_process = false;
876
877   GtkTreeModel* model;
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));
888   }
889   g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL);
890   g_list_free(paths);
891
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,
896                                                                -1);
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);
902     }
903   }
904
905   bool sensitive = (paths != NULL) && !selection_contains_browser_process;
906   gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
907                                     kTaskManagerResponseKill, sensitive);
908 }
909
910 void TaskManagerGtk::OnRowActivated(GtkWidget* widget,
911                                     GtkTreePath* path,
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);
918 }
919
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),
926                     event->time);
927     return TRUE;
928   }
929
930   return FALSE;
931 }
932
933 gboolean TaskManagerGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
934                                           GObject* acceleratable,
935                                           guint keyval,
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);
941   }
942
943   return TRUE;
944 }
945
946 namespace chrome {
947
948 // Declared in browser_dialogs.h.
949 void ShowTaskManager(Browser* browser) {
950   TaskManagerGtk::Show();
951 }
952
953 }  // namespace chrome