Chromium Code Reviews| OLD | NEW | 
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon.h" | 5 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon.h" | 
| 6 | 6 | 
| 7 #include <dlfcn.h> | 7 #include <dlfcn.h> | 
| 8 #include <gtk/gtk.h> | 8 #include <gtk/gtk.h> | 
| 9 | 9 | 
| 10 #include "base/bind.h" | 10 #include "base/bind.h" | 
| 11 #include "base/environment.h" | 11 #include "base/environment.h" | 
| 12 #include "base/files/file_util.h" | 12 #include "base/files/file_util.h" | 
| 13 #include "base/md5.h" | 13 #include "base/md5.h" | 
| 14 #include "base/memory/ref_counted_memory.h" | 14 #include "base/memory/ref_counted_memory.h" | 
| 15 #include "base/nix/xdg_util.h" | 15 #include "base/nix/xdg_util.h" | 
| 16 #include "base/strings/stringprintf.h" | 16 #include "base/strings/stringprintf.h" | 
| 17 #include "base/strings/utf_string_conversions.h" | 17 #include "base/strings/utf_string_conversions.h" | 
| 18 #include "base/threading/sequenced_worker_pool.h" | 18 #include "base/threading/sequenced_worker_pool.h" | 
| 19 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h" | 19 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h" | 
| 20 #include "content/public/browser/browser_thread.h" | 20 #include "content/public/browser/browser_thread.h" | 
| 21 #include "third_party/skia/include/core/SkBitmap.h" | |
| 22 #include "third_party/skia/include/core/SkCanvas.h" | |
| 21 #include "ui/base/models/menu_model.h" | 23 #include "ui/base/models/menu_model.h" | 
| 24 #include "ui/gfx/codec/png_codec.h" | |
| 22 #include "ui/gfx/image/image.h" | 25 #include "ui/gfx/image/image.h" | 
| 23 #include "ui/gfx/image/image_skia.h" | 26 #include "ui/gfx/image/image_skia.h" | 
| 24 | 27 | 
| 25 namespace { | 28 namespace { | 
| 26 | 29 | 
| 27 typedef enum { | 30 typedef enum { | 
| 28 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, | 31 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, | 
| 29 APP_INDICATOR_CATEGORY_COMMUNICATIONS, | 32 APP_INDICATOR_CATEGORY_COMMUNICATIONS, | 
| 30 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES, | 33 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES, | 
| 31 APP_INDICATOR_CATEGORY_HARDWARE, | 34 APP_INDICATOR_CATEGORY_HARDWARE, | 
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 127 | 130 | 
| 128 app_indicator_set_icon_full = | 131 app_indicator_set_icon_full = | 
| 129 reinterpret_cast<app_indicator_set_icon_full_func>( | 132 reinterpret_cast<app_indicator_set_icon_full_func>( | 
| 130 dlsym(indicator_lib, "app_indicator_set_icon_full")); | 133 dlsym(indicator_lib, "app_indicator_set_icon_full")); | 
| 131 | 134 | 
| 132 app_indicator_set_icon_theme_path = | 135 app_indicator_set_icon_theme_path = | 
| 133 reinterpret_cast<app_indicator_set_icon_theme_path_func>( | 136 reinterpret_cast<app_indicator_set_icon_theme_path_func>( | 
| 134 dlsym(indicator_lib, "app_indicator_set_icon_theme_path")); | 137 dlsym(indicator_lib, "app_indicator_set_icon_theme_path")); | 
| 135 } | 138 } | 
| 136 | 139 | 
| 137 // Returns whether a temporary directory should be created for each app | 140 // Writes |bitmap| to a file at |path|. Returns true if successful. | 
| 138 // indicator image. | 141 bool WriteFile(const base::FilePath& path, const SkBitmap& bitmap) { | 
| 139 bool ShouldCreateTempDirectoryPerImage(bool using_kde4) { | 142 std::vector<unsigned char> png_data; | 
| 140 // Create a new temporary directory for each image on Unity since using a | 143 if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data)) | 
| 141 // single temporary directory seems to have issues when changing icons in | 144 return false; | 
| 142 // quick succession. | 145 int bytes_written = base::WriteFile( | 
| 143 return !using_kde4; | 146 path, reinterpret_cast<char*>(&png_data[0]), png_data.size()); | 
| 144 } | 147 return (bytes_written == static_cast<int>(png_data.size())); | 
| 145 | |
| 146 // Returns the subdirectory of |temp_dir| in which the app indicator image | |
| 147 // should be saved. | |
| 148 base::FilePath GetImageDirectoryPath(bool using_kde4, | |
| 149 const base::FilePath& temp_dir) { | |
| 150 // On KDE4, an image located in a directory ending with | |
| 151 // "icons/hicolor/16x16/apps" can be used as the app indicator image because | |
| 152 // "/usr/share/icons/hicolor/16x16/apps" exists. | |
| 153 return using_kde4 ? | |
| 154 temp_dir.AppendASCII("icons").AppendASCII("hicolor").AppendASCII("16x16"). | |
| 155 AppendASCII("apps") : | |
| 156 temp_dir; | |
| 157 } | |
| 158 | |
| 159 std::string GetImageFileNameForKDE4( | |
| 160 const scoped_refptr<base::RefCountedMemory>& png_data) { | |
| 161 // On KDE4, the name of the image file for each different looking bitmap must | |
| 162 // be unique. It must also be unique across runs of Chrome. | |
| 163 base::MD5Digest digest; | |
| 164 base::MD5Sum(png_data->front_as<char>(), png_data->size(), &digest); | |
| 165 return base::StringPrintf("chrome_app_indicator_%s.png", | |
| 166 base::MD5DigestToBase16(digest).c_str()); | |
| 167 } | |
| 168 | |
| 169 std::string GetImageFileNameForNonKDE4(int icon_change_count, | |
| 170 const std::string& id) { | |
| 171 return base::StringPrintf("%s_%d.png", id.c_str(), icon_change_count); | |
| 172 } | |
| 173 | |
| 174 // Returns the "icon theme path" given the file path of the app indicator image. | |
| 175 std::string GetIconThemePath(bool using_kde4, | |
| 176 const base::FilePath& image_path) { | |
| 177 return using_kde4 ? | |
| 178 image_path.DirName().DirName().DirName().DirName().value() : | |
| 179 image_path.DirName().value(); | |
| 180 } | |
| 181 | |
| 182 base::FilePath CreateTempImageFile(bool using_kde4, | |
| 183 gfx::ImageSkia* image_ptr, | |
| 184 int icon_change_count, | |
| 185 std::string id, | |
| 186 const base::FilePath& previous_file_path) { | |
| 187 scoped_ptr<gfx::ImageSkia> image(image_ptr); | |
| 188 | |
| 189 scoped_refptr<base::RefCountedMemory> png_data = | |
| 190 gfx::Image(*image.get()).As1xPNGBytes(); | |
| 191 if (png_data->size() == 0) { | |
| 192 // If the bitmap could not be encoded to PNG format, skip it. | |
| 193 LOG(WARNING) << "Could not encode icon"; | |
| 194 return base::FilePath(); | |
| 195 } | |
| 196 | |
| 197 base::FilePath new_file_path; | |
| 198 if (previous_file_path.empty() || | |
| 199 ShouldCreateTempDirectoryPerImage(using_kde4)) { | |
| 200 base::FilePath tmp_dir; | |
| 201 if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &tmp_dir)) | |
| 202 return base::FilePath(); | |
| 203 new_file_path = GetImageDirectoryPath(using_kde4, tmp_dir); | |
| 204 if (new_file_path != tmp_dir) { | |
| 205 if (!base::CreateDirectory(new_file_path)) | |
| 206 return base::FilePath(); | |
| 207 } | |
| 208 } else { | |
| 209 new_file_path = previous_file_path.DirName(); | |
| 210 } | |
| 211 | |
| 212 new_file_path = new_file_path.Append(using_kde4 ? | |
| 213 GetImageFileNameForKDE4(png_data) : | |
| 214 GetImageFileNameForNonKDE4(icon_change_count, id)); | |
| 215 | |
| 216 int bytes_written = | |
| 217 base::WriteFile(new_file_path, | |
| 218 png_data->front_as<char>(), png_data->size()); | |
| 219 | |
| 220 if (bytes_written != static_cast<int>(png_data->size())) | |
| 221 return base::FilePath(); | |
| 222 return new_file_path; | |
| 223 } | 148 } | 
| 224 | 149 | 
| 225 void DeleteTempDirectory(const base::FilePath& dir_path) { | 150 void DeleteTempDirectory(const base::FilePath& dir_path) { | 
| 226 if (dir_path.empty()) | 151 if (dir_path.empty()) | 
| 227 return; | 152 return; | 
| 228 base::DeleteFile(dir_path, true); | 153 base::DeleteFile(dir_path, true); | 
| 229 } | 154 } | 
| 230 | 155 | 
| 231 } // namespace | 156 } // namespace | 
| 232 | 157 | 
| (...skipping 15 matching lines...) Expand all Loading... | |
| 248 EnsureMethodsLoaded(); | 173 EnsureMethodsLoaded(); | 
| 249 tool_tip_ = base::UTF16ToUTF8(tool_tip); | 174 tool_tip_ = base::UTF16ToUTF8(tool_tip); | 
| 250 SetImage(image); | 175 SetImage(image); | 
| 251 } | 176 } | 
| 252 AppIndicatorIcon::~AppIndicatorIcon() { | 177 AppIndicatorIcon::~AppIndicatorIcon() { | 
| 253 if (icon_) { | 178 if (icon_) { | 
| 254 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE); | 179 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE); | 
| 255 g_object_unref(icon_); | 180 g_object_unref(icon_); | 
| 256 content::BrowserThread::GetBlockingPool()->PostTask( | 181 content::BrowserThread::GetBlockingPool()->PostTask( | 
| 257 FROM_HERE, | 182 FROM_HERE, | 
| 258 base::Bind(&DeleteTempDirectory, icon_file_path_.DirName())); | 183 base::Bind(&DeleteTempDirectory, temp_dir_.DirName())); | 
| 259 } | 184 } | 
| 260 } | 185 } | 
| 261 | 186 | 
| 262 // static | 187 // static | 
| 263 bool AppIndicatorIcon::CouldOpen() { | 188 bool AppIndicatorIcon::CouldOpen() { | 
| 264 EnsureMethodsLoaded(); | 189 EnsureMethodsLoaded(); | 
| 265 return g_opened; | 190 return g_opened; | 
| 266 } | 191 } | 
| 267 | 192 | 
| 268 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) { | 193 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) { | 
| 269 if (!g_opened) | 194 if (!g_opened) | 
| 270 return; | 195 return; | 
| 271 | 196 | 
| 272 ++icon_change_count_; | 197 ++icon_change_count_; | 
| 273 | 198 | 
| 274 // We create a deep copy of the image since it may have been freed by the time | 199 // Copy the bitmap because it may be freed by the time it's accessed in | 
| 275 // it's accessed in the other thread. | 200 // another thread. | 
| 276 scoped_ptr<gfx::ImageSkia> safe_image(image.DeepCopy()); | 201 SkBitmap safe_bitmap = *image.bitmap(); | 
| 277 base::PostTaskAndReplyWithResult( | 202 | 
| 203 scoped_refptr<base::TaskRunner> task_runner = | |
| 278 content::BrowserThread::GetBlockingPool() | 204 content::BrowserThread::GetBlockingPool() | 
| 279 ->GetTaskRunnerWithShutdownBehavior( | 205 ->GetTaskRunnerWithShutdownBehavior( | 
| 280 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN).get(), | 206 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); | 
| 281 FROM_HERE, | 207 if (using_kde4_) { | 
| 282 base::Bind(&CreateTempImageFile, | 208 base::PostTaskAndReplyWithResult( | 
| 283 using_kde4_, | 209 task_runner.get(), FROM_HERE, | 
| 284 safe_image.release(), | 210 base::Bind(AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread, | 
| 285 icon_change_count_, | 211 safe_bitmap, temp_dir_), | 
| 286 id_, | 212 base::Bind(&AppIndicatorIcon::SetImageFromFile, | 
| 287 icon_file_path_), | 213 weak_factory_.GetWeakPtr())); | 
| 288 base::Bind(&AppIndicatorIcon::SetImageFromFile, | 214 } else { | 
| 289 weak_factory_.GetWeakPtr())); | 215 base::PostTaskAndReplyWithResult( | 
| 216 task_runner.get(), FROM_HERE, | |
| 217 base::Bind(AppIndicatorIcon::WriteUnityTempImageOnWorkerThread, | |
| 218 safe_bitmap, icon_change_count_, id_), | |
| 219 base::Bind(&AppIndicatorIcon::SetImageFromFile, | |
| 220 weak_factory_.GetWeakPtr())); | |
| 221 } | |
| 290 } | 222 } | 
| 291 | 223 | 
| 292 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) { | 224 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) { | 
| 293 DCHECK(!tool_tip_.empty()); | 225 DCHECK(!tool_tip_.empty()); | 
| 294 tool_tip_ = base::UTF16ToUTF8(tool_tip); | 226 tool_tip_ = base::UTF16ToUTF8(tool_tip); | 
| 295 UpdateClickActionReplacementMenuItem(); | 227 UpdateClickActionReplacementMenuItem(); | 
| 296 } | 228 } | 
| 297 | 229 | 
| 298 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) { | 230 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) { | 
| 299 if (!g_opened) | 231 if (!g_opened) | 
| 300 return; | 232 return; | 
| 301 | 233 | 
| 302 menu_model_ = model; | 234 menu_model_ = model; | 
| 303 | 235 | 
| 304 // The icon is created asynchronously so it might not exist when the menu is | 236 // The icon is created asynchronously so it might not exist when the menu is | 
| 305 // set. | 237 // set. | 
| 306 if (icon_) | 238 if (icon_) | 
| 307 SetMenu(); | 239 SetMenu(); | 
| 308 } | 240 } | 
| 309 | 241 | 
| 310 void AppIndicatorIcon::RefreshPlatformContextMenu() { | 242 void AppIndicatorIcon::RefreshPlatformContextMenu() { | 
| 311 menu_->Refresh(); | 243 menu_->Refresh(); | 
| 312 } | 244 } | 
| 313 | 245 | 
| 314 void AppIndicatorIcon::SetImageFromFile(const base::FilePath& icon_file_path) { | 246 // static | 
| 247 AppIndicatorIcon::SetImageFromFileParams | |
| 248 AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread( | |
| 249 const SkBitmap& bitmap, | |
| 250 const base::FilePath& existing_temp_dir) { | |
| 251 base::FilePath temp_dir = existing_temp_dir; | |
| 252 if (temp_dir.empty() && | |
| 253 !base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) { | |
| 254 LOG(WARNING) << "Could not create temporary directory"; | |
| 255 return SetImageFromFileParams(); | |
| 256 } | |
| 257 | |
| 258 base::FilePath icon_theme_path = temp_dir.AppendASCII("icons"); | |
| 259 | |
| 260 // On KDE4, the name of the image file for each different looking bitmap must | |
| 261 // be unique. It must also be unique across runs of Chrome. | |
| 262 std::vector<unsigned char> bitmap_png_data; | |
| 263 if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &bitmap_png_data)) { | |
| 264 LOG(WARNING) << "Could not encode icon"; | |
| 265 return SetImageFromFileParams(); | |
| 266 } | |
| 267 base::MD5Digest digest; | |
| 268 base::MD5Sum(reinterpret_cast<char*>(&bitmap_png_data[0]), | |
| 269 bitmap_png_data.size(), &digest); | |
| 270 std::string icon_name = base::StringPrintf( | |
| 271 "chrome_app_indicator2_%s", base::MD5DigestToBase16(digest).c_str()); | |
| 
 
pkotwicz
2014/11/13 05:02:39
This is "chrome_app_indicator2" on purpose. This i
 
 | |
| 272 | |
| 273 // On KDE4, an image located in a directory ending with | |
| 274 // "icons/hicolor/24x24/apps" can be used as the app indicator image because | |
| 275 // "/usr/share/icons/hicolor/24x24/apps" exists. | |
| 276 base::FilePath image_dir = icon_theme_path.AppendASCII("hicolor") | |
| 277 .AppendASCII("24x24") | |
| 278 .AppendASCII("apps"); | |
| 279 | |
| 280 if (!base::CreateDirectory(image_dir)) | |
| 281 return SetImageFromFileParams(); | |
| 282 | |
| 283 // If |bitmap| is not 24x24, KDE does some really ugly resizing. Pad |bitmap| | |
| 284 // with transparent pixels to make it 24x24. | |
| 285 const int kDesiredSize = 24; | |
| 286 SkBitmap scaled_bitmap; | |
| 287 scaled_bitmap.allocN32Pixels(kDesiredSize, kDesiredSize); | |
| 288 scaled_bitmap.eraseARGB(0, 0, 0, 0); | |
| 289 SkCanvas canvas(scaled_bitmap); | |
| 290 canvas.drawBitmap(bitmap, (kDesiredSize - bitmap.width()) / 2, | |
| 291 (kDesiredSize - bitmap.height()) / 2); | |
| 292 | |
| 293 base::FilePath image_path = image_dir.Append(icon_name + ".png"); | |
| 294 if (!WriteFile(image_path, scaled_bitmap)) | |
| 295 return SetImageFromFileParams(); | |
| 296 | |
| 297 SetImageFromFileParams params; | |
| 298 params.parent_temp_dir = temp_dir; | |
| 299 params.icon_theme_path = icon_theme_path.value(); | |
| 300 params.icon_name = icon_name; | |
| 301 return params; | |
| 302 } | |
| 303 | |
| 304 // static | |
| 305 AppIndicatorIcon::SetImageFromFileParams | |
| 306 AppIndicatorIcon::WriteUnityTempImageOnWorkerThread(const SkBitmap& bitmap, | |
| 307 int icon_change_count, | |
| 308 const std::string& id) { | |
| 309 // Create a new temporary directory for each image on Unity since using a | |
| 310 // single temporary directory seems to have issues when changing icons in | |
| 311 // quick succession. | |
| 312 base::FilePath temp_dir; | |
| 313 if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) { | |
| 314 LOG(WARNING) << "Could not create temporary directory"; | |
| 315 return SetImageFromFileParams(); | |
| 316 } | |
| 317 | |
| 318 std::string icon_name = | |
| 319 base::StringPrintf("%s_%d", id.c_str(), icon_change_count); | |
| 320 base::FilePath image_path = temp_dir.Append(icon_name + ".png"); | |
| 321 SetImageFromFileParams params; | |
| 322 if (WriteFile(image_path, bitmap)) { | |
| 323 params.parent_temp_dir = temp_dir; | |
| 324 params.icon_theme_path = temp_dir.value(); | |
| 325 params.icon_name = icon_name; | |
| 326 } | |
| 327 return params; | |
| 328 } | |
| 329 | |
| 330 void AppIndicatorIcon::SetImageFromFile(const SetImageFromFileParams& params) { | |
| 315 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 331 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
| 316 if (icon_file_path.empty()) | 332 if (params.icon_theme_path.empty()) | 
| 317 return; | 333 return; | 
| 318 | 334 | 
| 319 base::FilePath old_path = icon_file_path_; | |
| 320 icon_file_path_ = icon_file_path; | |
| 321 | |
| 322 std::string icon_name = | |
| 323 icon_file_path_.BaseName().RemoveExtension().value(); | |
| 324 std::string icon_dir = GetIconThemePath(using_kde4_, icon_file_path); | |
| 325 if (!icon_) { | 335 if (!icon_) { | 
| 326 icon_ = | 336 icon_ = | 
| 327 app_indicator_new_with_path(id_.c_str(), | 337 app_indicator_new_with_path(id_.c_str(), | 
| 328 icon_name.c_str(), | 338 params.icon_name.c_str(), | 
| 329 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, | 339 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, | 
| 330 icon_dir.c_str()); | 340 params.icon_theme_path.c_str()); | 
| 331 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE); | 341 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE); | 
| 332 SetMenu(); | 342 SetMenu(); | 
| 333 } else { | 343 } else { | 
| 334 // Currently we are creating a new temp directory every time the icon is | 344 app_indicator_set_icon_theme_path(icon_, params.icon_theme_path.c_str()); | 
| 335 // set. So we need to set the directory each time. | 345 app_indicator_set_icon_full(icon_, params.icon_name.c_str(), "icon"); | 
| 336 app_indicator_set_icon_theme_path(icon_, icon_dir.c_str()); | 346 } | 
| 337 app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon"); | |
| 338 | 347 | 
| 339 if (ShouldCreateTempDirectoryPerImage(using_kde4_)) { | 348 if (temp_dir_ != params.parent_temp_dir) { | 
| 340 // Delete previous icon directory. | 349 content::BrowserThread::GetBlockingPool()->PostTask( | 
| 341 content::BrowserThread::GetBlockingPool()->PostTask( | 350 FROM_HERE, | 
| 342 FROM_HERE, | 351 base::Bind(&DeleteTempDirectory, temp_dir_)); | 
| 343 base::Bind(&DeleteTempDirectory, old_path.DirName())); | 352 temp_dir_ = params.parent_temp_dir; | 
| 344 } | |
| 345 } | 353 } | 
| 346 } | 354 } | 
| 347 | 355 | 
| 348 void AppIndicatorIcon::SetMenu() { | 356 void AppIndicatorIcon::SetMenu() { | 
| 349 menu_.reset(new AppIndicatorIconMenu(menu_model_)); | 357 menu_.reset(new AppIndicatorIconMenu(menu_model_)); | 
| 350 UpdateClickActionReplacementMenuItem(); | 358 UpdateClickActionReplacementMenuItem(); | 
| 351 app_indicator_set_menu(icon_, menu_->GetGtkMenu()); | 359 app_indicator_set_menu(icon_, menu_->GetGtkMenu()); | 
| 352 } | 360 } | 
| 353 | 361 | 
| 354 void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() { | 362 void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() { | 
| (...skipping 10 matching lines...) Expand all Loading... | |
| 365 base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated, | 373 base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated, | 
| 366 base::Unretained(this))); | 374 base::Unretained(this))); | 
| 367 } | 375 } | 
| 368 | 376 | 
| 369 void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() { | 377 void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() { | 
| 370 if (delegate()) | 378 if (delegate()) | 
| 371 delegate()->OnClick(); | 379 delegate()->OnClick(); | 
| 372 } | 380 } | 
| 373 | 381 | 
| 374 } // namespace libgtk2ui | 382 } // namespace libgtk2ui | 
| OLD | NEW |