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 "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/utf_string_conversions.h" 18 #include "chrome/browser/browser_process.h" 19 #include "chrome/browser/defaults.h" 20 #include "chrome/browser/memory_purger.h" 21 #include "chrome/browser/prefs/pref_service.h" 22 #include "chrome/browser/prefs/scoped_user_pref_update.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_tree.h" 26 #include "chrome/browser/ui/gtk/gtk_util.h" 27 #include "chrome/common/chrome_switches.h" 28 #include "chrome/common/pref_names.h" 29 #include "grit/app_resources.h" 30 #include "grit/chromium_strings.h" 31 #include "third_party/skia/include/core/SkBitmap.h" 32 #include "ui/base/l10n/l10n_util.h" 33 #include "ui/base/models/simple_menu_model.h" 34 #include "ui/base/resource/resource_bundle.h" 35 #include "ui/gfx/gtk_util.h" 36 37 #if defined(TOOLKIT_VIEWS) 38 #include "views/controls/menu/menu_2.h" 39 #else 40 #include "chrome/browser/ui/gtk/menu_gtk.h" 41 #endif 42 43 namespace { 44 45 // The task manager window default size. 46 const int kDefaultWidth = 460; 47 const int kDefaultHeight = 270; 48 49 // The resource id for the 'End process' button. 50 const gint kTaskManagerResponseKill = 1; 51 52 // The resource id for the 'Stats for nerds' link button. 53 const gint kTaskManagerAboutMemoryLink = 2; 54 55 // The resource id for the 'Purge Memory' button 56 const gint kTaskManagerPurgeMemory = 3; 57 58 enum TaskManagerColumn { 59 kTaskManagerIcon, 60 kTaskManagerPage, 61 kTaskManagerSharedMem, 62 kTaskManagerPrivateMem, 63 kTaskManagerCPU, 64 kTaskManagerNetwork, 65 kTaskManagerProcessID, 66 kTaskManagerJavaScriptMemory, 67 kTaskManagerWebCoreImageCache, 68 kTaskManagerWebCoreScriptsCache, 69 kTaskManagerWebCoreCssCache, 70 kTaskManagerSqliteMemoryUsed, 71 kTaskManagerGoatsTeleported, 72 // Columns below this point are not visible in the task manager. 73 kTaskManagerBackgroundColor, 74 kTaskManagerColumnCount, 75 }; 76 77 const TaskManagerColumn kTaskManagerLastVisibleColumn = 78 kTaskManagerGoatsTeleported; 79 80 static const GdkColor kHighlightColor = GDK_COLOR_RGB(0xff, 0xfa, 0xcd); 81 82 TaskManagerColumn TaskManagerResourceIDToColumnID(int id) { 83 switch (id) { 84 case IDS_TASK_MANAGER_PAGE_COLUMN: 85 return kTaskManagerPage; 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_SQLITE_MEMORY_USED_COLUMN: 105 return kTaskManagerSqliteMemoryUsed; 106 case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN: 107 return kTaskManagerGoatsTeleported; 108 default: 109 NOTREACHED(); 110 return static_cast<TaskManagerColumn>(-1); 111 } 112 } 113 114 int TaskManagerColumnIDToResourceID(int id) { 115 switch (id) { 116 case kTaskManagerPage: 117 return IDS_TASK_MANAGER_PAGE_COLUMN; 118 case kTaskManagerSharedMem: 119 return IDS_TASK_MANAGER_SHARED_MEM_COLUMN; 120 case kTaskManagerPrivateMem: 121 return IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN; 122 case kTaskManagerCPU: 123 return IDS_TASK_MANAGER_CPU_COLUMN; 124 case kTaskManagerNetwork: 125 return IDS_TASK_MANAGER_NET_COLUMN; 126 case kTaskManagerProcessID: 127 return IDS_TASK_MANAGER_PROCESS_ID_COLUMN; 128 case kTaskManagerJavaScriptMemory: 129 return IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN; 130 case kTaskManagerWebCoreImageCache: 131 return IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN; 132 case kTaskManagerWebCoreScriptsCache: 133 return IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN; 134 case kTaskManagerWebCoreCssCache: 135 return IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN; 136 case kTaskManagerSqliteMemoryUsed: 137 return IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN; 138 case kTaskManagerGoatsTeleported: 139 return IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN; 140 default: 141 NOTREACHED(); 142 return -1; 143 } 144 } 145 146 // Should be used for all gtk_tree_view functions that require a column index on 147 // input. 148 // 149 // We need colid - 1 because the gtk_tree_view function is asking for the 150 // column index, not the column id, and both kTaskManagerIcon and 151 // kTaskManagerPage are in the same column index, so all column IDs are off by 152 // one. 153 int TreeViewColumnIndexFromID(TaskManagerColumn colid) { 154 return colid - 1; 155 } 156 157 // Shows or hides a treeview column. 158 void TreeViewColumnSetVisible(GtkWidget* treeview, TaskManagerColumn colid, 159 bool visible) { 160 GtkTreeViewColumn* column = gtk_tree_view_get_column( 161 GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid)); 162 gtk_tree_view_column_set_visible(column, visible); 163 } 164 165 bool TreeViewColumnIsVisible(GtkWidget* treeview, TaskManagerColumn colid) { 166 GtkTreeViewColumn* column = gtk_tree_view_get_column( 167 GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid)); 168 return gtk_tree_view_column_get_visible(column); 169 } 170 171 void TreeViewInsertColumnWithPixbuf(GtkWidget* treeview, int resid) { 172 int colid = TaskManagerResourceIDToColumnID(resid); 173 GtkTreeViewColumn* column = gtk_tree_view_column_new(); 174 gtk_tree_view_column_set_title(column, 175 l10n_util::GetStringUTF8(resid).c_str()); 176 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(treeview), colid); 177 GtkCellRenderer* image_renderer = gtk_cell_renderer_pixbuf_new(); 178 gtk_tree_view_column_pack_start(column, image_renderer, FALSE); 179 gtk_tree_view_column_add_attribute(column, image_renderer, 180 "pixbuf", kTaskManagerIcon); 181 gtk_tree_view_column_add_attribute(column, image_renderer, 182 "cell-background-gdk", 183 kTaskManagerBackgroundColor); 184 GtkCellRenderer* text_renderer = gtk_cell_renderer_text_new(); 185 gtk_tree_view_column_pack_start(column, text_renderer, TRUE); 186 gtk_tree_view_column_add_attribute(column, text_renderer, "text", colid); 187 gtk_tree_view_column_add_attribute(column, text_renderer, 188 "cell-background-gdk", 189 kTaskManagerBackgroundColor); 190 gtk_tree_view_column_set_resizable(column, TRUE); 191 // This is temporary: we'll turn expanding off after getting the size. 192 gtk_tree_view_column_set_expand(column, TRUE); 193 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); 194 gtk_tree_view_column_set_sort_column_id(column, colid); 195 } 196 197 // Inserts a column with a column id of |colid| and |name|. 198 void TreeViewInsertColumnWithName(GtkWidget* treeview, 199 TaskManagerColumn colid, const char* name) { 200 GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); 201 gtk_tree_view_insert_column_with_attributes( 202 GTK_TREE_VIEW(treeview), -1, 203 name, renderer, 204 "text", colid, 205 "cell-background-gdk", kTaskManagerBackgroundColor, 206 NULL); 207 GtkTreeViewColumn* column = gtk_tree_view_get_column( 208 GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid)); 209 gtk_tree_view_column_set_resizable(column, TRUE); 210 gtk_tree_view_column_set_sort_column_id(column, colid); 211 } 212 213 // Loads the column name from |resid| and uses the corresponding 214 // TaskManagerColumn value as the column id to insert into the treeview. 215 void TreeViewInsertColumn(GtkWidget* treeview, int resid) { 216 TreeViewInsertColumnWithName(treeview, TaskManagerResourceIDToColumnID(resid), 217 l10n_util::GetStringUTF8(resid).c_str()); 218 } 219 220 // Set the current width of the column without forcing a minimum width as 221 // gtk_tree_view_column_set_fixed_width() would. This would basically be 222 // gtk_tree_view_column_set_width() except that there is no such function. 223 void TreeViewColumnSetWidth(GtkTreeViewColumn* column, gint width) { 224 column->width = width; 225 column->resized_width = width; 226 column->use_resized_width = TRUE; 227 // Needed for use_resized_width to be effective. 228 gtk_widget_queue_resize(column->tree_view); 229 } 230 231 } // namespace 232 233 class TaskManagerGtk::ContextMenuController 234 : public ui::SimpleMenuModel::Delegate { 235 public: 236 explicit ContextMenuController(TaskManagerGtk* task_manager) 237 : task_manager_(task_manager) { 238 menu_model_.reset(new ui::SimpleMenuModel(this)); 239 for (int i = kTaskManagerPage; i <= kTaskManagerLastVisibleColumn; i++) { 240 menu_model_->AddCheckItemWithStringId( 241 i, TaskManagerColumnIDToResourceID(i)); 242 } 243 #if defined(TOOLKIT_VIEWS) 244 menu_.reset(new views::Menu2(menu_model_.get())); 245 #else 246 menu_.reset(new MenuGtk(NULL, menu_model_.get())); 247 #endif 248 } 249 250 virtual ~ContextMenuController() {} 251 252 void RunMenu(const gfx::Point& point, guint32 event_time) { 253 #if defined(TOOLKIT_VIEWS) 254 menu_->RunContextMenuAt(point); 255 #else 256 menu_->PopupAsContext(point, event_time); 257 #endif 258 } 259 260 void Cancel() { 261 task_manager_ = NULL; 262 #if defined(TOOLKIT_VIEWS) 263 menu_->CancelMenu(); 264 #else 265 menu_->Cancel(); 266 #endif 267 } 268 269 private: 270 // ui::SimpleMenuModel::Delegate implementation: 271 virtual bool IsCommandIdEnabled(int command_id) const { 272 if (!task_manager_) 273 return false; 274 275 return true; 276 } 277 278 virtual bool IsCommandIdChecked(int command_id) const { 279 if (!task_manager_) 280 return false; 281 282 TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id); 283 return TreeViewColumnIsVisible(task_manager_->treeview_, colid); 284 } 285 286 virtual bool GetAcceleratorForCommandId( 287 int command_id, 288 ui::Accelerator* accelerator) { 289 return false; 290 } 291 292 virtual void ExecuteCommand(int command_id) { 293 if (!task_manager_) 294 return; 295 296 TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id); 297 bool visible = !TreeViewColumnIsVisible(task_manager_->treeview_, colid); 298 TreeViewColumnSetVisible(task_manager_->treeview_, colid, visible); 299 } 300 301 // The model and view for the right click context menu. 302 scoped_ptr<ui::SimpleMenuModel> menu_model_; 303 #if defined(TOOLKIT_VIEWS) 304 scoped_ptr<views::Menu2> menu_; 305 #else 306 scoped_ptr<MenuGtk> menu_; 307 #endif 308 309 // The TaskManager the context menu was brought up for. Set to NULL when the 310 // menu is canceled. 311 TaskManagerGtk* task_manager_; 312 313 DISALLOW_COPY_AND_ASSIGN(ContextMenuController); 314 }; 315 316 TaskManagerGtk::TaskManagerGtk(bool highlight_background_resources) 317 : task_manager_(TaskManager::GetInstance()), 318 model_(TaskManager::GetInstance()->model()), 319 dialog_(NULL), 320 treeview_(NULL), 321 process_list_(NULL), 322 process_count_(0), 323 ignore_selection_changed_(false), 324 highlight_background_resources_(highlight_background_resources) { 325 Init(); 326 } 327 328 // static 329 TaskManagerGtk* TaskManagerGtk::instance_ = NULL; 330 331 TaskManagerGtk::~TaskManagerGtk() { 332 model_->RemoveObserver(this); 333 task_manager_->OnWindowClosed(); 334 335 gtk_accel_group_disconnect_key(accel_group_, GDK_w, GDK_CONTROL_MASK); 336 gtk_window_remove_accel_group(GTK_WINDOW(dialog_), accel_group_); 337 g_object_unref(accel_group_); 338 accel_group_ = NULL; 339 340 // Disconnect the destroy signal so it doesn't delete |this|. 341 g_signal_handler_disconnect(G_OBJECT(dialog_), destroy_handler_id_); 342 gtk_widget_destroy(dialog_); 343 } 344 345 //////////////////////////////////////////////////////////////////////////////// 346 // TaskManagerGtk, TaskManagerModelObserver implementation: 347 348 void TaskManagerGtk::OnModelChanged() { 349 // Nothing to do. 350 } 351 352 void TaskManagerGtk::OnItemsChanged(int start, int length) { 353 GtkTreeIter iter; 354 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter, 355 NULL, start); 356 357 for (int i = start; i < start + length; i++) { 358 SetRowDataFromModel(i, &iter); 359 gtk_tree_model_iter_next(GTK_TREE_MODEL(process_list_), &iter); 360 } 361 } 362 363 void TaskManagerGtk::OnItemsAdded(int start, int length) { 364 AutoReset<bool> autoreset(&ignore_selection_changed_, true); 365 366 GtkTreeIter iter; 367 if (start == 0) { 368 gtk_list_store_prepend(process_list_, &iter); 369 } else if (start >= process_count_) { 370 gtk_list_store_append(process_list_, &iter); 371 } else { 372 GtkTreeIter sibling; 373 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &sibling, 374 NULL, start); 375 gtk_list_store_insert_before(process_list_, &iter, &sibling); 376 } 377 378 SetRowDataFromModel(start, &iter); 379 380 for (int i = start + 1; i < start + length; i++) { 381 gtk_list_store_insert_after(process_list_, &iter, &iter); 382 SetRowDataFromModel(i, &iter); 383 } 384 385 process_count_ += length; 386 } 387 388 void TaskManagerGtk::OnItemsRemoved(int start, int length) { 389 { 390 AutoReset<bool> autoreset(&ignore_selection_changed_, true); 391 392 GtkTreeIter iter; 393 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter, 394 NULL, start); 395 396 for (int i = 0; i < length; i++) { 397 // |iter| is moved to the next valid node when the current node is 398 // removed. 399 gtk_list_store_remove(process_list_, &iter); 400 } 401 402 process_count_ -= length; 403 } 404 405 // It is possible that we have removed the current selection; run selection 406 // changed to detect that case. 407 OnSelectionChanged(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview_))); 408 } 409 410 //////////////////////////////////////////////////////////////////////////////// 411 // TaskManagerGtk, public: 412 413 void TaskManagerGtk::Close() { 414 // Blow away our dialog - this will cause TaskManagerGtk to free itself. 415 gtk_widget_destroy(dialog_); 416 DCHECK(!instance_); 417 } 418 419 // static 420 void TaskManagerGtk::Show(bool highlight_background_resources) { 421 if (instance_ && 422 instance_->highlight_background_resources_ != 423 highlight_background_resources) { 424 instance_->Close(); 425 DCHECK(!instance_); 426 } 427 428 if (instance_) { 429 // If there's a Task manager window open already, just activate it. 430 gtk_util::PresentWindow(instance_->dialog_, 0); 431 } else { 432 instance_ = new TaskManagerGtk(highlight_background_resources); 433 instance_->model_->StartUpdating(); 434 } 435 } 436 437 //////////////////////////////////////////////////////////////////////////////// 438 // TaskManagerGtk, private: 439 440 void TaskManagerGtk::Init() { 441 dialog_ = gtk_dialog_new_with_buttons( 442 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_TITLE).c_str(), 443 // Task Manager window is shared between all browsers. 444 NULL, 445 GTK_DIALOG_NO_SEPARATOR, 446 NULL); 447 448 // Allow browser windows to go in front of the task manager dialog in 449 // metacity. 450 gtk_window_set_type_hint(GTK_WINDOW(dialog_), GDK_WINDOW_TYPE_HINT_NORMAL); 451 452 if (CommandLine::ForCurrentProcess()->HasSwitch( 453 switches::kPurgeMemoryButton)) { 454 gtk_dialog_add_button(GTK_DIALOG(dialog_), 455 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_PURGE_MEMORY).c_str(), 456 kTaskManagerPurgeMemory); 457 } 458 459 if (browser_defaults::kShowCancelButtonInTaskManager) { 460 gtk_dialog_add_button(GTK_DIALOG(dialog_), 461 l10n_util::GetStringUTF8(IDS_CLOSE).c_str(), 462 GTK_RESPONSE_DELETE_EVENT); 463 } 464 465 gtk_dialog_add_button(GTK_DIALOG(dialog_), 466 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_KILL).c_str(), 467 kTaskManagerResponseKill); 468 469 // The response button should not be sensitive when the dialog is first opened 470 // because the selection is initially empty. 471 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_), 472 kTaskManagerResponseKill, FALSE); 473 474 GtkWidget* link = gtk_chrome_link_button_new( 475 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_ABOUT_MEMORY_LINK).c_str()); 476 gtk_dialog_add_action_widget(GTK_DIALOG(dialog_), link, 477 kTaskManagerAboutMemoryLink); 478 479 // Setting the link widget to secondary positions the button on the left side 480 // of the action area (vice versa for RTL layout). 481 gtk_button_box_set_child_secondary( 482 GTK_BUTTON_BOX(GTK_DIALOG(dialog_)->action_area), link, TRUE); 483 484 ConnectAccelerators(); 485 486 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox), 487 gtk_util::kContentAreaSpacing); 488 489 destroy_handler_id_ = g_signal_connect(dialog_, "destroy", 490 G_CALLBACK(OnDestroyThunk), this); 491 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this); 492 // GTK does menu on mouse-up while views does menu on mouse-down, 493 // so connect to different handlers. 494 #if defined(TOOLKIT_VIEWS) 495 g_signal_connect(dialog_, "button-release-event", 496 G_CALLBACK(OnButtonEventThunk), this); 497 #else 498 g_signal_connect(dialog_, "button-press-event", 499 G_CALLBACK(OnButtonEventThunk), this); 500 #endif 501 gtk_widget_add_events(dialog_, 502 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); 503 504 // Wrap the treeview widget in a scrolled window in order to have a frame. 505 GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL); 506 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), 507 GTK_SHADOW_ETCHED_IN); 508 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), 509 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); 510 511 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog_)->vbox), scrolled); 512 513 CreateTaskManagerTreeview(); 514 gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview_), TRUE); 515 g_signal_connect(treeview_, "row-activated", 516 G_CALLBACK(OnRowActivatedThunk), this); 517 #if defined(TOOLKIT_GTK) 518 g_signal_connect(treeview_, "button-press-event", 519 G_CALLBACK(OnButtonEventThunk), this); 520 #endif 521 522 // |selection| is owned by |treeview_|. 523 GtkTreeSelection* selection = gtk_tree_view_get_selection( 524 GTK_TREE_VIEW(treeview_)); 525 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); 526 g_signal_connect(selection, "changed", 527 G_CALLBACK(OnSelectionChangedThunk), this); 528 529 gtk_container_add(GTK_CONTAINER(scrolled), treeview_); 530 531 SetInitialDialogSize(); 532 gtk_util::ShowDialog(dialog_); 533 534 // If the model already has resources, we need to add them before we start 535 // observing events. 536 if (model_->ResourceCount() > 0) 537 OnItemsAdded(0, model_->ResourceCount()); 538 539 model_->AddObserver(this); 540 } 541 542 void TaskManagerGtk::SetInitialDialogSize() { 543 // Hook up to the realize event so we can size the page column to the 544 // size of the leftover space after packing the other columns. 545 g_signal_connect(treeview_, "realize", 546 G_CALLBACK(OnTreeViewRealizeThunk), this); 547 // If we previously saved the dialog's bounds, use them. 548 if (g_browser_process->local_state()) { 549 const DictionaryValue* placement_pref = 550 g_browser_process->local_state()->GetDictionary( 551 prefs::kTaskManagerWindowPlacement); 552 int top = 0, left = 0, bottom = 1, right = 1; 553 if (placement_pref && 554 placement_pref->GetInteger("top", &top) && 555 placement_pref->GetInteger("left", &left) && 556 placement_pref->GetInteger("bottom", &bottom) && 557 placement_pref->GetInteger("right", &right)) { 558 gtk_window_resize(GTK_WINDOW(dialog_), 559 std::max(1, right - left), 560 std::max(1, bottom - top)); 561 return; 562 } 563 } 564 565 // Otherwise, just set a default size (GTK will override this if it's not 566 // large enough to hold the window's contents). 567 gtk_window_set_default_size( 568 GTK_WINDOW(dialog_), kDefaultWidth, kDefaultHeight); 569 } 570 571 void TaskManagerGtk::ConnectAccelerators() { 572 accel_group_ = gtk_accel_group_new(); 573 gtk_window_add_accel_group(GTK_WINDOW(dialog_), accel_group_); 574 575 gtk_accel_group_connect(accel_group_, 576 GDK_w, GDK_CONTROL_MASK, GtkAccelFlags(0), 577 g_cclosure_new(G_CALLBACK(OnGtkAcceleratorThunk), 578 this, NULL)); 579 } 580 581 void TaskManagerGtk::CreateTaskManagerTreeview() { 582 process_list_ = gtk_list_store_new(kTaskManagerColumnCount, 583 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, 584 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, 585 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, 586 G_TYPE_STRING, GDK_TYPE_COLOR); 587 588 // Support sorting on all columns. 589 process_list_sort_ = gtk_tree_model_sort_new_with_model( 590 GTK_TREE_MODEL(process_list_)); 591 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 592 kTaskManagerPage, 593 ComparePage, this, NULL); 594 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 595 kTaskManagerSharedMem, 596 CompareSharedMemory, this, NULL); 597 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 598 kTaskManagerPrivateMem, 599 ComparePrivateMemory, this, NULL); 600 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 601 kTaskManagerJavaScriptMemory, 602 CompareV8Memory, this, NULL); 603 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 604 kTaskManagerCPU, 605 CompareCPU, this, NULL); 606 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 607 kTaskManagerNetwork, 608 CompareNetwork, this, NULL); 609 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 610 kTaskManagerProcessID, 611 CompareProcessID, this, NULL); 612 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 613 kTaskManagerWebCoreImageCache, 614 CompareWebCoreImageCache, this, NULL); 615 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 616 kTaskManagerWebCoreScriptsCache, 617 CompareWebCoreScriptsCache, this, NULL); 618 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 619 kTaskManagerWebCoreCssCache, 620 CompareWebCoreCssCache, this, NULL); 621 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 622 kTaskManagerSqliteMemoryUsed, 623 CompareSqliteMemoryUsed, this, NULL); 624 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 625 kTaskManagerGoatsTeleported, 626 CompareGoatsTeleported, this, NULL); 627 treeview_ = gtk_tree_view_new_with_model(process_list_sort_); 628 629 // Insert all the columns. 630 TreeViewInsertColumnWithPixbuf(treeview_, IDS_TASK_MANAGER_PAGE_COLUMN); 631 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SHARED_MEM_COLUMN); 632 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN); 633 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_CPU_COLUMN); 634 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_NET_COLUMN); 635 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROCESS_ID_COLUMN); 636 TreeViewInsertColumn(treeview_, 637 IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN); 638 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN); 639 TreeViewInsertColumn(treeview_, 640 IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN); 641 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN); 642 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN); 643 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN); 644 645 // Hide some columns by default. 646 TreeViewColumnSetVisible(treeview_, kTaskManagerSharedMem, false); 647 TreeViewColumnSetVisible(treeview_, kTaskManagerProcessID, false); 648 TreeViewColumnSetVisible(treeview_, kTaskManagerJavaScriptMemory, false); 649 TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreImageCache, false); 650 TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreScriptsCache, false); 651 TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreCssCache, false); 652 TreeViewColumnSetVisible(treeview_, kTaskManagerSqliteMemoryUsed, false); 653 TreeViewColumnSetVisible(treeview_, kTaskManagerGoatsTeleported, false); 654 655 g_object_unref(process_list_); 656 g_object_unref(process_list_sort_); 657 } 658 659 bool IsSharedByGroup(int col_id) { 660 switch (col_id) { 661 case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN: 662 case IDS_TASK_MANAGER_SHARED_MEM_COLUMN: 663 case IDS_TASK_MANAGER_CPU_COLUMN: 664 case IDS_TASK_MANAGER_PROCESS_ID_COLUMN: 665 case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN: 666 case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN: 667 case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN: 668 case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN: 669 return true; 670 default: 671 return false; 672 } 673 } 674 675 std::string TaskManagerGtk::GetModelText(int row, int col_id) { 676 if (IsSharedByGroup(col_id) && !model_->IsResourceFirstInGroup(row)) 677 return std::string(); 678 679 switch (col_id) { 680 case IDS_TASK_MANAGER_PAGE_COLUMN: // Process 681 return UTF16ToUTF8(model_->GetResourceTitle(row)); 682 683 case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN: // Memory 684 return UTF16ToUTF8(model_->GetResourcePrivateMemory(row)); 685 686 case IDS_TASK_MANAGER_SHARED_MEM_COLUMN: // Memory 687 return UTF16ToUTF8(model_->GetResourceSharedMemory(row)); 688 689 case IDS_TASK_MANAGER_CPU_COLUMN: // CPU 690 return UTF16ToUTF8(model_->GetResourceCPUUsage(row)); 691 692 case IDS_TASK_MANAGER_NET_COLUMN: // Net 693 return UTF16ToUTF8(model_->GetResourceNetworkUsage(row)); 694 695 case IDS_TASK_MANAGER_PROCESS_ID_COLUMN: // Process ID 696 return UTF16ToUTF8(model_->GetResourceProcessId(row)); 697 698 case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN: 699 return UTF16ToUTF8(model_->GetResourceV8MemoryAllocatedSize(row)); 700 701 case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN: 702 return UTF16ToUTF8(model_->GetResourceWebCoreImageCacheSize(row)); 703 704 case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN: 705 return UTF16ToUTF8(model_->GetResourceWebCoreScriptsCacheSize(row)); 706 707 case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN: 708 return UTF16ToUTF8(model_->GetResourceWebCoreCSSCacheSize(row)); 709 710 case IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN: 711 return UTF16ToUTF8(model_->GetResourceSqliteMemoryUsed(row)); 712 713 case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN: // Goats Teleported! 714 return UTF16ToUTF8(model_->GetResourceGoatsTeleported(row)); 715 716 default: 717 NOTREACHED(); 718 return std::string(); 719 } 720 } 721 722 GdkPixbuf* TaskManagerGtk::GetModelIcon(int row) { 723 SkBitmap icon = model_->GetResourceIcon(row); 724 if (icon.pixelRef() == 725 ResourceBundle::GetSharedInstance().GetBitmapNamed( 726 IDR_DEFAULT_FAVICON)->pixelRef()) { 727 return static_cast<GdkPixbuf*>(g_object_ref( 728 GtkThemeService::GetDefaultFavicon(true))); 729 } 730 731 return gfx::GdkPixbufFromSkBitmap(&icon); 732 } 733 734 void TaskManagerGtk::SetRowDataFromModel(int row, GtkTreeIter* iter) { 735 GdkPixbuf* icon = GetModelIcon(row); 736 std::string page = GetModelText(row, IDS_TASK_MANAGER_PAGE_COLUMN); 737 std::string shared_mem = GetModelText( 738 row, IDS_TASK_MANAGER_SHARED_MEM_COLUMN); 739 std::string priv_mem = GetModelText(row, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN); 740 std::string cpu = GetModelText(row, IDS_TASK_MANAGER_CPU_COLUMN); 741 std::string net = GetModelText(row, IDS_TASK_MANAGER_NET_COLUMN); 742 std::string procid = GetModelText(row, IDS_TASK_MANAGER_PROCESS_ID_COLUMN); 743 744 // Querying the renderer metrics is slow as it has to do IPC, so only do it 745 // when the columns are visible. 746 std::string javascript_memory; 747 if (TreeViewColumnIsVisible(treeview_, kTaskManagerJavaScriptMemory)) 748 javascript_memory = GetModelText( 749 row, IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN); 750 std::string wk_img_cache; 751 if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreImageCache)) 752 wk_img_cache = GetModelText( 753 row, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN); 754 std::string wk_scripts_cache; 755 if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreScriptsCache)) 756 wk_scripts_cache = GetModelText( 757 row, IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN); 758 std::string wk_css_cache; 759 if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreCssCache)) 760 wk_css_cache = GetModelText( 761 row, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN); 762 std::string sqlite_memory; 763 if (TreeViewColumnIsVisible(treeview_, kTaskManagerSqliteMemoryUsed)) 764 sqlite_memory = GetModelText( 765 row, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN); 766 767 std::string goats = GetModelText( 768 row, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN); 769 770 bool is_background = model_->IsBackgroundResource(row) && 771 highlight_background_resources_; 772 gtk_list_store_set(process_list_, iter, 773 kTaskManagerIcon, icon, 774 kTaskManagerPage, page.c_str(), 775 kTaskManagerSharedMem, shared_mem.c_str(), 776 kTaskManagerPrivateMem, priv_mem.c_str(), 777 kTaskManagerCPU, cpu.c_str(), 778 kTaskManagerNetwork, net.c_str(), 779 kTaskManagerProcessID, procid.c_str(), 780 kTaskManagerJavaScriptMemory, javascript_memory.c_str(), 781 kTaskManagerWebCoreImageCache, wk_img_cache.c_str(), 782 kTaskManagerWebCoreScriptsCache, wk_scripts_cache.c_str(), 783 kTaskManagerWebCoreCssCache, wk_css_cache.c_str(), 784 kTaskManagerSqliteMemoryUsed, sqlite_memory.c_str(), 785 kTaskManagerGoatsTeleported, goats.c_str(), 786 kTaskManagerBackgroundColor, 787 is_background ? &kHighlightColor : NULL, 788 -1); 789 g_object_unref(icon); 790 } 791 792 void TaskManagerGtk::KillSelectedProcesses() { 793 GtkTreeSelection* selection = gtk_tree_view_get_selection( 794 GTK_TREE_VIEW(treeview_)); 795 796 GtkTreeModel* model; 797 GList* paths = gtk_tree_selection_get_selected_rows(selection, &model); 798 for (GList* item = paths; item; item = item->next) { 799 GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path( 800 GTK_TREE_MODEL_SORT(process_list_sort_), 801 reinterpret_cast<GtkTreePath*>(item->data)); 802 int row = gtk_tree::GetRowNumForPath(path); 803 gtk_tree_path_free(path); 804 task_manager_->KillProcess(row); 805 } 806 g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL); 807 g_list_free(paths); 808 } 809 810 void TaskManagerGtk::ShowContextMenu(const gfx::Point& point, 811 guint32 event_time) { 812 if (!menu_controller_.get()) 813 menu_controller_.reset(new ContextMenuController(this)); 814 815 menu_controller_->RunMenu(point, event_time); 816 } 817 818 void TaskManagerGtk::OnLinkActivated() { 819 task_manager_->OpenAboutMemory(); 820 } 821 822 gint TaskManagerGtk::CompareImpl(GtkTreeModel* model, GtkTreeIter* a, 823 GtkTreeIter* b, int id) { 824 int row1 = gtk_tree::GetRowNumForIter(model, b); 825 int row2 = gtk_tree::GetRowNumForIter(model, a); 826 827 // When sorting by non-grouped attributes (e.g., Network), just do a normal 828 // sort. 829 if (!IsSharedByGroup(id)) 830 return model_->CompareValues(row1, row2, id); 831 832 // Otherwise, make sure grouped resources are shown together. 833 std::pair<int, int> group_range1 = model_->GetGroupRangeForResource(row1); 834 std::pair<int, int> group_range2 = model_->GetGroupRangeForResource(row2); 835 836 if (group_range1 == group_range2) { 837 // Sort within groups. 838 // We want the first-in-group row at the top, whether we are sorting up or 839 // down. 840 GtkSortType sort_type; 841 gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(process_list_sort_), 842 NULL, &sort_type); 843 if (row1 == group_range1.first) 844 return sort_type == GTK_SORT_ASCENDING ? -1 : 1; 845 if (row2 == group_range2.first) 846 return sort_type == GTK_SORT_ASCENDING ? 1 : -1; 847 848 return model_->CompareValues(row1, row2, id); 849 } else { 850 // Sort between groups. 851 // Compare by the first-in-group rows so that the groups will stay together. 852 return model_->CompareValues(group_range1.first, group_range2.first, id); 853 } 854 } 855 856 void TaskManagerGtk::OnDestroy(GtkWidget* dialog) { 857 instance_ = NULL; 858 delete this; 859 } 860 861 void TaskManagerGtk::OnResponse(GtkWidget* dialog, int response_id) { 862 if (response_id == GTK_RESPONSE_DELETE_EVENT) { 863 // Store the dialog's size so we can restore it the next time it's opened. 864 if (g_browser_process->local_state()) { 865 gfx::Rect dialog_bounds = gtk_util::GetDialogBounds(GTK_WIDGET(dialog)); 866 867 DictionaryPrefUpdate update(g_browser_process->local_state(), 868 prefs::kTaskManagerWindowPlacement); 869 DictionaryValue* placement_pref = update.Get(); 870 // Note that we store left/top for consistency with Windows, but that we 871 // *don't* restore them. 872 placement_pref->SetInteger("left", dialog_bounds.x()); 873 placement_pref->SetInteger("top", dialog_bounds.y()); 874 placement_pref->SetInteger("right", dialog_bounds.right()); 875 placement_pref->SetInteger("bottom", dialog_bounds.bottom()); 876 placement_pref->SetBoolean("maximized", false); 877 } 878 879 instance_ = NULL; 880 delete this; 881 } else if (response_id == kTaskManagerResponseKill) { 882 KillSelectedProcesses(); 883 } else if (response_id == kTaskManagerAboutMemoryLink) { 884 OnLinkActivated(); 885 } else if (response_id == kTaskManagerPurgeMemory) { 886 MemoryPurger::PurgeAll(); 887 } 888 } 889 890 void TaskManagerGtk::OnTreeViewRealize(GtkTreeView* treeview) { 891 // Four columns show by default: the page column, the memory column, the 892 // CPU column, and the network column. Initially we set the page column to 893 // take all the extra space, with the other columns being sized to fit the 894 // column names. Here we turn off the expand property of the first column 895 // (to make the table behave sanely when the user resizes columns) and set 896 // the effective sizes of all four default columns to the automatically 897 // chosen size before any rows are added. This causes them to stay at that 898 // size even if the data would overflow, preventing a horizontal scroll 899 // bar from appearing due to the row data. 900 const TaskManagerColumn dfl_columns[] = {kTaskManagerNetwork, kTaskManagerCPU, 901 kTaskManagerPrivateMem}; 902 GtkTreeViewColumn* column = NULL; 903 gint width; 904 for (size_t i = 0; i < arraysize(dfl_columns); ++i) { 905 column = gtk_tree_view_get_column(treeview, 906 TreeViewColumnIndexFromID(dfl_columns[i])); 907 width = gtk_tree_view_column_get_width(column); 908 TreeViewColumnSetWidth(column, width); 909 } 910 // Do the page column separately since it's a little different. 911 column = gtk_tree_view_get_column(treeview, 912 TreeViewColumnIndexFromID(kTaskManagerPage)); 913 width = gtk_tree_view_column_get_width(column); 914 // Turn expanding back off to make resizing columns behave sanely. 915 gtk_tree_view_column_set_expand(column, FALSE); 916 TreeViewColumnSetWidth(column, width); 917 } 918 919 void TaskManagerGtk::OnSelectionChanged(GtkTreeSelection* selection) { 920 if (ignore_selection_changed_) 921 return; 922 AutoReset<bool> autoreset(&ignore_selection_changed_, true); 923 924 // The set of groups that should be selected. 925 std::set<std::pair<int, int> > ranges; 926 bool selection_contains_browser_process = false; 927 928 GtkTreeModel* model; 929 GList* paths = gtk_tree_selection_get_selected_rows(selection, &model); 930 for (GList* item = paths; item; item = item->next) { 931 GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path( 932 GTK_TREE_MODEL_SORT(process_list_sort_), 933 reinterpret_cast<GtkTreePath*>(item->data)); 934 int row = gtk_tree::GetRowNumForPath(path); 935 gtk_tree_path_free(path); 936 if (task_manager_->IsBrowserProcess(row)) 937 selection_contains_browser_process = true; 938 ranges.insert(model_->GetGroupRangeForResource(row)); 939 } 940 g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL); 941 g_list_free(paths); 942 943 for (std::set<std::pair<int, int> >::iterator iter = ranges.begin(); 944 iter != ranges.end(); ++iter) { 945 for (int i = 0; i < iter->second; ++i) { 946 GtkTreePath* child_path = gtk_tree_path_new_from_indices(iter->first + i, 947 -1); 948 GtkTreePath* sort_path = gtk_tree_model_sort_convert_child_path_to_path( 949 GTK_TREE_MODEL_SORT(process_list_sort_), child_path); 950 gtk_tree_selection_select_path(selection, sort_path); 951 gtk_tree_path_free(child_path); 952 gtk_tree_path_free(sort_path); 953 } 954 } 955 956 bool sensitive = (paths != NULL) && !selection_contains_browser_process; 957 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_), 958 kTaskManagerResponseKill, sensitive); 959 } 960 961 void TaskManagerGtk::OnRowActivated(GtkWidget* widget, 962 GtkTreePath* path, 963 GtkTreeViewColumn* column) { 964 GtkTreePath* child_path = gtk_tree_model_sort_convert_path_to_child_path( 965 GTK_TREE_MODEL_SORT(process_list_sort_), path); 966 int row = gtk_tree::GetRowNumForPath(child_path); 967 gtk_tree_path_free(child_path); 968 task_manager_->ActivateProcess(row); 969 } 970 971 gboolean TaskManagerGtk::OnButtonEvent(GtkWidget* widget, 972 GdkEventButton* event) { 973 // GTK does menu on mouse-up while views does menu on mouse-down, 974 // so this function can be called from either signal. 975 if (event->button == 3) { 976 ShowContextMenu(gfx::Point(event->x_root, event->y_root), 977 event->time); 978 return TRUE; 979 } 980 981 return FALSE; 982 } 983 984 gboolean TaskManagerGtk::OnGtkAccelerator(GtkAccelGroup* accel_group, 985 GObject* acceleratable, 986 guint keyval, 987 GdkModifierType modifier) { 988 if (keyval == GDK_w && modifier == GDK_CONTROL_MASK) { 989 // The GTK_RESPONSE_DELETE_EVENT response must be sent before the widget 990 // is destroyed. The deleted object will receive gtk signals otherwise. 991 gtk_dialog_response(GTK_DIALOG(dialog_), GTK_RESPONSE_DELETE_EVENT); 992 } 993 994 return TRUE; 995 } 996