Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(262)

Side by Side Diff: chrome/browser/gtk/gtk_theme_provider.cc

Issue 460050: Completely redo how themes are stored on disk and processed at install time. (Closed)
Patch Set: 80 col nits Created 11 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2009 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/gtk/gtk_theme_provider.h" 5 #include "chrome/browser/gtk/gtk_theme_provider.h"
6 6
7 #include <gtk/gtk.h> 7 #include <gtk/gtk.h>
8 8
9 #include "app/gfx/color_utils.h" 9 #include "app/gfx/color_utils.h"
10 #include "app/gfx/gtk_util.h" 10 #include "app/gfx/gtk_util.h"
11 #include "app/gfx/skbitmap_operations.h"
12 #include "base/stl_util-inl.h"
11 #include "chrome/browser/metrics/user_metrics.h" 13 #include "chrome/browser/metrics/user_metrics.h"
12 #include "chrome/browser/profile.h" 14 #include "chrome/browser/profile.h"
13 #include "chrome/browser/gtk/cairo_cached_surface.h" 15 #include "chrome/browser/gtk/cairo_cached_surface.h"
14 #include "chrome/browser/gtk/gtk_chrome_button.h" 16 #include "chrome/browser/gtk/gtk_chrome_button.h"
15 #include "chrome/common/pref_names.h" 17 #include "chrome/common/pref_names.h"
16 #include "chrome/common/pref_service.h" 18 #include "chrome/common/pref_service.h"
17 #include "chrome/common/notification_details.h" 19 #include "chrome/common/notification_details.h"
18 #include "chrome/common/notification_service.h" 20 #include "chrome/common/notification_service.h"
19 #include "chrome/common/notification_source.h" 21 #include "chrome/common/notification_source.h"
20 #include "chrome/common/notification_type.h" 22 #include "chrome/common/notification_type.h"
(...skipping 29 matching lines...) Expand all
50 // calculate the border color in GTK theme mode. 52 // calculate the border color in GTK theme mode.
51 const int kBgWeight = 3; 53 const int kBgWeight = 3;
52 54
53 // Converts a GdkColor to a SkColor. 55 // Converts a GdkColor to a SkColor.
54 SkColor GdkToSkColor(GdkColor* color) { 56 SkColor GdkToSkColor(GdkColor* color) {
55 return SkColorSetRGB(color->red >> 8, 57 return SkColorSetRGB(color->red >> 8,
56 color->green >> 8, 58 color->green >> 8,
57 color->blue >> 8); 59 color->blue >> 8);
58 } 60 }
59 61
62 // A list of images that we provide while in gtk mode.
63 const int kThemeImages[] = {
64 IDR_THEME_TOOLBAR,
65 IDR_THEME_TAB_BACKGROUND,
66 IDR_THEME_TAB_BACKGROUND_INCOGNITO,
67 IDR_THEME_FRAME,
68 IDR_THEME_FRAME_INACTIVE,
69 IDR_THEME_FRAME_INCOGNITO,
70 IDR_THEME_FRAME_INCOGNITO_INACTIVE,
71 };
72
73 bool IsOverridableImage(int id) {
74 for (size_t i = 0; i < arraysize(kThemeImages); ++i) {
75 if (kThemeImages[i] == id)
76 return true;
77 }
78
79 return false;
80 }
81
60 } // namespace 82 } // namespace
61 83
62 GtkWidget* GtkThemeProvider::icon_widget_ = NULL; 84 GtkWidget* GtkThemeProvider::icon_widget_ = NULL;
63 GdkPixbuf* GtkThemeProvider::default_folder_icon_ = NULL; 85 GdkPixbuf* GtkThemeProvider::default_folder_icon_ = NULL;
64 GdkPixbuf* GtkThemeProvider::default_bookmark_icon_ = NULL; 86 GdkPixbuf* GtkThemeProvider::default_bookmark_icon_ = NULL;
65 87
66 // static 88 // static
67 GtkThemeProvider* GtkThemeProvider::GetFrom(Profile* profile) { 89 GtkThemeProvider* GtkThemeProvider::GetFrom(Profile* profile) {
68 return static_cast<GtkThemeProvider*>(profile->GetThemeProvider()); 90 return static_cast<GtkThemeProvider*>(profile->GetThemeProvider());
69 } 91 }
(...skipping 26 matching lines...) Expand all
96 } 118 }
97 } 119 }
98 120
99 void GtkThemeProvider::Init(Profile* profile) { 121 void GtkThemeProvider::Init(Profile* profile) {
100 profile->GetPrefs()->AddPrefObserver(prefs::kUsesSystemTheme, this); 122 profile->GetPrefs()->AddPrefObserver(prefs::kUsesSystemTheme, this);
101 use_gtk_ = profile->GetPrefs()->GetBoolean(prefs::kUsesSystemTheme); 123 use_gtk_ = profile->GetPrefs()->GetBoolean(prefs::kUsesSystemTheme);
102 124
103 BrowserThemeProvider::Init(profile); 125 BrowserThemeProvider::Init(profile);
104 } 126 }
105 127
128 SkBitmap* GtkThemeProvider::GetBitmapNamed(int id) const {
129 if (use_gtk_ && IsOverridableImage(id)) {
130 // Try to get our cached version:
131 ImageCache::const_iterator it = gtk_images_.find(id);
132 if (it != gtk_images_.end())
133 return it->second;
134
135 // We haven't built this image yet:
136 SkBitmap* bitmap = GenerateGtkThemeBitmap(id);
137 gtk_images_[id] = bitmap;
138 return bitmap;
139 }
140
141 return BrowserThemeProvider::GetBitmapNamed(id);
142 }
143
144 SkColor GtkThemeProvider::GetColor(int id) const {
145 if (use_gtk_) {
146 ColorMap::const_iterator it = colors_.find(id);
147 if (it != colors_.end())
148 return it->second;
149 }
150
151 return BrowserThemeProvider::GetColor(id);
152 }
153
154 bool GtkThemeProvider::HasCustomImage(int id) const {
155 if (use_gtk_)
156 return IsOverridableImage(id);
157
158 return BrowserThemeProvider::HasCustomImage(id);
159 }
160
106 void GtkThemeProvider::InitThemesFor(NotificationObserver* observer) { 161 void GtkThemeProvider::InitThemesFor(NotificationObserver* observer) {
107 observer->Observe(NotificationType::BROWSER_THEME_CHANGED, 162 observer->Observe(NotificationType::BROWSER_THEME_CHANGED,
108 Source<ThemeProvider>(this), 163 Source<ThemeProvider>(this),
109 NotificationService::NoDetails()); 164 NotificationService::NoDetails());
110 } 165 }
111 166
112 void GtkThemeProvider::SetTheme(Extension* extension) { 167 void GtkThemeProvider::SetTheme(Extension* extension) {
113 profile()->GetPrefs()->SetBoolean(prefs::kUsesSystemTheme, false); 168 profile()->GetPrefs()->SetBoolean(prefs::kUsesSystemTheme, false);
114 BrowserThemeProvider::SetTheme(extension); 169 BrowserThemeProvider::SetTheme(extension);
115 } 170 }
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after
249 BrowserThemeProvider::NotifyThemeChanged(); 304 BrowserThemeProvider::NotifyThemeChanged();
250 305
251 // Notify all GtkChromeButtons of their new rendering mode: 306 // Notify all GtkChromeButtons of their new rendering mode:
252 for (std::vector<GtkWidget*>::iterator it = chrome_buttons_.begin(); 307 for (std::vector<GtkWidget*>::iterator it = chrome_buttons_.begin();
253 it != chrome_buttons_.end(); ++it) { 308 it != chrome_buttons_.end(); ++it) {
254 gtk_chrome_button_set_use_gtk_rendering( 309 gtk_chrome_button_set_use_gtk_rendering(
255 GTK_CHROME_BUTTON(*it), use_gtk_); 310 GTK_CHROME_BUTTON(*it), use_gtk_);
256 } 311 }
257 } 312 }
258 313
259 SkBitmap* GtkThemeProvider::LoadThemeBitmap(int id) const {
260 if (use_gtk_) {
261 if (id == IDR_THEME_TOOLBAR) {
262 GtkStyle* style = gtk_rc_get_style(fake_window_);
263 GdkColor* color = &style->bg[GTK_STATE_NORMAL];
264 SkBitmap* bitmap = new SkBitmap;
265 bitmap->setConfig(SkBitmap::kARGB_8888_Config,
266 kToolbarImageWidth, kToolbarImageHeight);
267 bitmap->allocPixels();
268 bitmap->eraseRGB(color->red >> 8, color->green >> 8, color->blue >> 8);
269 return bitmap;
270 }
271 if ((id == IDR_THEME_TAB_BACKGROUND) ||
272 (id == IDR_THEME_TAB_BACKGROUND_INCOGNITO))
273 return GenerateTabBackgroundBitmapImpl(id);
274 }
275
276 return BrowserThemeProvider::LoadThemeBitmap(id);
277 }
278
279 void GtkThemeProvider::SaveThemeBitmap(const std::string resource_name,
280 int id) const {
281 if (!use_gtk_) {
282 // Prevent us from writing out our mostly unused resources in gtk theme
283 // mode. Simply preventing us from writing this data out in gtk mode isn't
284 // the best design, but this would probably be a very invasive change on
285 // all three platforms otherwise.
286 BrowserThemeProvider::SaveThemeBitmap(resource_name, id);
287 }
288 }
289
290 void GtkThemeProvider::FreePlatformCaches() { 314 void GtkThemeProvider::FreePlatformCaches() {
291 BrowserThemeProvider::FreePlatformCaches(); 315 BrowserThemeProvider::FreePlatformCaches();
292 FreePerDisplaySurfaces(); 316 FreePerDisplaySurfaces();
317 STLDeleteValues(&gtk_images_);
293 } 318 }
294 319
295 // static 320 // static
296 void GtkThemeProvider::OnStyleSet(GtkWidget* widget, 321 void GtkThemeProvider::OnStyleSet(GtkWidget* widget,
297 GtkStyle* previous_style, 322 GtkStyle* previous_style,
298 GtkThemeProvider* provider) { 323 GtkThemeProvider* provider) {
299 GdkPixbuf* default_folder_icon = default_folder_icon_; 324 GdkPixbuf* default_folder_icon = default_folder_icon_;
300 GdkPixbuf* default_bookmark_icon = default_bookmark_icon_; 325 GdkPixbuf* default_bookmark_icon = default_bookmark_icon_;
301 default_folder_icon_ = NULL; 326 default_folder_icon_ = NULL;
302 default_bookmark_icon_ = NULL; 327 default_bookmark_icon_ = NULL;
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
379 if (hsl_difference <= kMinimumLuminanceDifference) { 404 if (hsl_difference <= kMinimumLuminanceDifference) {
380 // Not enough contrast. Try the text color instead. 405 // Not enough contrast. Try the text color instead.
381 color_utils::HSL label_hsl; 406 color_utils::HSL label_hsl;
382 color_utils::SkColorToHSL(GdkToSkColor(&label_color), &label_hsl); 407 color_utils::SkColorToHSL(GdkToSkColor(&label_color), &label_hsl);
383 double label_difference = fabs(label_hsl.l - toolbar_hsl.l); 408 double label_difference = fabs(label_hsl.l - toolbar_hsl.l);
384 if (label_difference >= kMinimumLuminanceDifference) { 409 if (label_difference >= kMinimumLuminanceDifference) {
385 button_color = label_color; 410 button_color = label_color;
386 } 411 }
387 } 412 }
388 413
389 SetThemeColorFromGtk(kColorFrame, &frame_color); 414 SetThemeTintFromGtk(BrowserThemeProvider::TINT_BUTTONS, &button_color);
415 SetThemeTintFromGtk(BrowserThemeProvider::TINT_FRAME, &frame_color);
416 SetThemeTintFromGtk(BrowserThemeProvider::TINT_FRAME_INCOGNITO, &frame_color);
417 SetThemeTintFromGtk(BrowserThemeProvider::TINT_BACKGROUND_TAB, &frame_color);
418
419 SetThemeColorFromGtk(BrowserThemeProvider::COLOR_FRAME, &frame_color);
420 BuildTintedFrameColor(BrowserThemeProvider::COLOR_FRAME_INACTIVE,
421 BrowserThemeProvider::TINT_FRAME_INACTIVE);
422 BuildTintedFrameColor(BrowserThemeProvider::COLOR_FRAME_INCOGNITO,
423 BrowserThemeProvider::TINT_FRAME_INCOGNITO);
424 BuildTintedFrameColor(BrowserThemeProvider::COLOR_FRAME_INCOGNITO_INACTIVE,
425 BrowserThemeProvider::TINT_FRAME_INCOGNITO_INACTIVE);
426
390 // Skip COLOR_FRAME_INACTIVE and the incognito colors, as they will be 427 // Skip COLOR_FRAME_INACTIVE and the incognito colors, as they will be
391 // autogenerated from tints. 428 // autogenerated from tints. TODO(erg): Still true?
392 SetThemeColorFromGtk(kColorToolbar, &toolbar_color); 429 SetThemeColorFromGtk(BrowserThemeProvider::COLOR_TOOLBAR, &toolbar_color);
393 SetThemeColorFromGtk(kColorTabText, &label_color); 430 SetThemeColorFromGtk(BrowserThemeProvider::COLOR_TAB_TEXT, &label_color);
394 SetThemeColorFromGtk(kColorBookmarkText, &label_color); 431 SetThemeColorFromGtk(BrowserThemeProvider::COLOR_BOOKMARK_TEXT, &label_color);
395 SetThemeColorFromGtk(kColorControlBackground, 432 SetThemeColorFromGtk(BrowserThemeProvider::COLOR_CONTROL_BACKGROUND,
396 &window_style->bg[GTK_STATE_NORMAL]); 433 &window_style->bg[GTK_STATE_NORMAL]);
397 SetThemeColorFromGtk(kColorButtonBackground, 434 SetThemeColorFromGtk(BrowserThemeProvider::COLOR_BUTTON_BACKGROUND,
398 &window_style->bg[GTK_STATE_NORMAL]); 435 &window_style->bg[GTK_STATE_NORMAL]);
399 436
400 SetThemeTintFromGtk(kTintButtons, &button_color,
401 kDefaultTintButtons);
402 SetThemeTintFromGtk(kTintFrame, &frame_color,
403 kDefaultTintFrame);
404 SetThemeTintFromGtk(kTintFrameIncognito,
405 &frame_color,
406 kDefaultTintFrameIncognito);
407 SetThemeTintFromGtk(kTintBackgroundTab,
408 &frame_color,
409 kDefaultTintBackgroundTab);
410
411 // The inactive frame color never occurs naturally in the theme, as it is a 437 // The inactive frame color never occurs naturally in the theme, as it is a
412 // tinted version of |frame_color|. We generate another color based on the 438 // tinted version of |frame_color|. We generate another color based on the
413 // background tab color, with the lightness and saturation moved in the 439 // background tab color, with the lightness and saturation moved in the
414 // opposite direction. (We don't touch the hue, since there should be subtle 440 // opposite direction. (We don't touch the hue, since there should be subtle
415 // hints of the color in the text.) 441 // hints of the color in the text.)
416 color_utils::HSL inactive_tab_text_hsl = GetTint(TINT_BACKGROUND_TAB); 442 color_utils::HSL inactive_tab_text_hsl = tints_[TINT_BACKGROUND_TAB];
417 if (inactive_tab_text_hsl.l < 0.5) 443 if (inactive_tab_text_hsl.l < 0.5)
418 inactive_tab_text_hsl.l = kDarkInactiveLuminance; 444 inactive_tab_text_hsl.l = kDarkInactiveLuminance;
419 else 445 else
420 inactive_tab_text_hsl.l = kLightInactiveLuminance; 446 inactive_tab_text_hsl.l = kLightInactiveLuminance;
421 447
422 if (inactive_tab_text_hsl.s < 0.5) 448 if (inactive_tab_text_hsl.s < 0.5)
423 inactive_tab_text_hsl.s = kHeavyInactiveSaturation; 449 inactive_tab_text_hsl.s = kHeavyInactiveSaturation;
424 else 450 else
425 inactive_tab_text_hsl.s = kLightInactiveSaturation; 451 inactive_tab_text_hsl.s = kLightInactiveSaturation;
426 452
427 SetColor(kColorBackgroundTabText, 453 colors_[BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT] =
428 color_utils::HSLToSkColor(inactive_tab_text_hsl, 255)); 454 color_utils::HSLToSkColor(inactive_tab_text_hsl, 255);
429 455
430 // The inactive color/tint is special: We *must* use the exact insensitive 456 // The inactive color/tint is special: We *must* use the exact insensitive
431 // color for all inactive windows, otherwise we end up neon pink half the 457 // color for all inactive windows, otherwise we end up neon pink half the
432 // time. 458 // time.
433 SetThemeColorFromGtk(kColorFrameInactive, &inactive_frame_color); 459 SetThemeColorFromGtk(BrowserThemeProvider::COLOR_FRAME_INACTIVE,
434 SetThemeTintFromGtk(kTintFrameInactive, &inactive_frame_color, 460 &inactive_frame_color);
435 kExactColor); 461 SetTintToExactColor(BrowserThemeProvider::TINT_FRAME_INACTIVE,
436 SetThemeTintFromGtk(kTintFrameIncognitoInactive, &inactive_frame_color, 462 &inactive_frame_color);
437 kExactColor); 463 SetTintToExactColor(BrowserThemeProvider::TINT_FRAME_INCOGNITO_INACTIVE,
438 464 &inactive_frame_color);
439 force_process_images();
440 GenerateFrameColors();
441 AutoLock lock(themed_image_cache_lock_);
442 GenerateFrameImages();
443 } 465 }
444 466
445 void GtkThemeProvider::SetThemeColorFromGtk(const char* id, GdkColor* color) { 467 void GtkThemeProvider::SetThemeColorFromGtk(int id, GdkColor* color) {
446 SetColor(id, GdkToSkColor(color)); 468 colors_[id] = GdkToSkColor(color);
447 } 469 }
448 470
449 void GtkThemeProvider::SetThemeTintFromGtk( 471 void GtkThemeProvider::SetThemeTintFromGtk(int id, GdkColor* color) {
450 const char* id, 472 color_utils::HSL default_tint = GetDefaultTint(id);
451 GdkColor* color,
452 const color_utils::HSL& default_tint) {
453 color_utils::HSL hsl; 473 color_utils::HSL hsl;
454 color_utils::SkColorToHSL(GdkToSkColor(color), &hsl); 474 color_utils::SkColorToHSL(GdkToSkColor(color), &hsl);
455 475
456 if (default_tint.s != -1) 476 if (default_tint.s != -1)
457 hsl.s = default_tint.s; 477 hsl.s = default_tint.s;
458 478
459 if (default_tint.l != -1) 479 if (default_tint.l != -1)
460 hsl.l = default_tint.l; 480 hsl.l = default_tint.l;
461 SetTint(id, hsl); 481
482 tints_[id] = hsl;
483 }
484
485 void GtkThemeProvider::BuildTintedFrameColor(int color_id, int tint_id) {
486 colors_[color_id] = HSLShift(colors_[BrowserThemeProvider::COLOR_FRAME],
487 tints_[tint_id]);
488 }
489
490 void GtkThemeProvider::SetTintToExactColor(int id, GdkColor* color) {
491 color_utils::HSL hsl;
492 color_utils::SkColorToHSL(GdkToSkColor(color), &hsl);
493 tints_[id] = hsl;
462 } 494 }
463 495
464 void GtkThemeProvider::FreePerDisplaySurfaces() { 496 void GtkThemeProvider::FreePerDisplaySurfaces() {
465 for (PerDisplaySurfaceMap::iterator it = per_display_surfaces_.begin(); 497 for (PerDisplaySurfaceMap::iterator it = per_display_surfaces_.begin();
466 it != per_display_surfaces_.end(); ++it) { 498 it != per_display_surfaces_.end(); ++it) {
467 for (CairoCachedSurfaceMap::iterator jt = it->second.begin(); 499 for (CairoCachedSurfaceMap::iterator jt = it->second.begin();
468 jt != it->second.end(); ++jt) { 500 jt != it->second.end(); ++jt) {
469 delete jt->second; 501 delete jt->second;
470 } 502 }
471 } 503 }
472 per_display_surfaces_.clear(); 504 per_display_surfaces_.clear();
473 } 505 }
474 506
507 SkBitmap* GtkThemeProvider::GenerateGtkThemeBitmap(int id) const {
508 switch (id) {
509 case IDR_THEME_TOOLBAR: {
510 GtkStyle* style = gtk_rc_get_style(fake_window_);
511 GdkColor* color = &style->bg[GTK_STATE_NORMAL];
512 SkBitmap* bitmap = new SkBitmap;
513 bitmap->setConfig(SkBitmap::kARGB_8888_Config,
514 kToolbarImageWidth, kToolbarImageHeight);
515 bitmap->allocPixels();
516 bitmap->eraseRGB(color->red >> 8, color->green >> 8, color->blue >> 8);
517 return bitmap;
518 }
519 case IDR_THEME_TAB_BACKGROUND:
520 return GenerateTabImage(IDR_THEME_FRAME);
521 case IDR_THEME_TAB_BACKGROUND_INCOGNITO:
522 return GenerateTabImage(IDR_THEME_FRAME_INCOGNITO);
523 case IDR_THEME_FRAME:
524 return GenerateFrameImage(BrowserThemeProvider::TINT_FRAME);
525 case IDR_THEME_FRAME_INACTIVE:
526 return GenerateFrameImage(BrowserThemeProvider::TINT_FRAME_INACTIVE);
527 case IDR_THEME_FRAME_INCOGNITO:
528 return GenerateFrameImage(BrowserThemeProvider::TINT_FRAME_INCOGNITO);
529 case IDR_THEME_FRAME_INCOGNITO_INACTIVE: {
530 return GenerateFrameImage(
531 BrowserThemeProvider::TINT_FRAME_INCOGNITO_INACTIVE);
532 }
533 }
534
535 NOTREACHED() << "Invalid bitmap request " << id;
536 return NULL;
537 }
538
539 SkBitmap* GtkThemeProvider::GenerateFrameImage(int tint_id) const {
540 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
541 scoped_ptr<SkBitmap> frame(new SkBitmap(*rb.GetBitmapNamed(IDR_THEME_FRAME)));
542 TintMap::const_iterator it = tints_.find(tint_id);
543 DCHECK(it != tints_.end());
544 return new SkBitmap(SkBitmapOperations::CreateHSLShiftedBitmap(*frame,
545 it->second));
546 }
547
548 SkBitmap* GtkThemeProvider::GenerateTabImage(int base_id) const {
549 SkBitmap* base_image = GetBitmapNamed(base_id);
550 SkBitmap bg_tint = SkBitmapOperations::CreateHSLShiftedBitmap(
551 *base_image, GetTint(BrowserThemeProvider::TINT_BACKGROUND_TAB));
552 return new SkBitmap(SkBitmapOperations::CreateTiledBitmap(
553 bg_tint, 0, 0, bg_tint.width(), bg_tint.height()));
554 }
555
475 void GtkThemeProvider::OnDestroyChromeButton(GtkWidget* button, 556 void GtkThemeProvider::OnDestroyChromeButton(GtkWidget* button,
476 GtkThemeProvider* provider) { 557 GtkThemeProvider* provider) {
477 std::vector<GtkWidget*>::iterator it = 558 std::vector<GtkWidget*>::iterator it =
478 find(provider->chrome_buttons_.begin(), provider->chrome_buttons_.end(), 559 find(provider->chrome_buttons_.begin(), provider->chrome_buttons_.end(),
479 button); 560 button);
480 if (it != provider->chrome_buttons_.end()) 561 if (it != provider->chrome_buttons_.end())
481 provider->chrome_buttons_.erase(it); 562 provider->chrome_buttons_.erase(it);
482 } 563 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698