display_options_handler.cc revision 010d83a9304c5a91596085d917d248abff47903a
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/webui/options/chromeos/display_options_handler.h"
6
7#include <string>
8
9#include "ash/display/display_configurator_animation.h"
10#include "ash/display/display_controller.h"
11#include "ash/display/display_manager.h"
12#include "ash/display/resolution_notification_controller.h"
13#include "ash/shell.h"
14#include "base/bind.h"
15#include "base/logging.h"
16#include "base/strings/string_number_conversions.h"
17#include "base/strings/stringprintf.h"
18#include "base/values.h"
19#include "chrome/browser/chromeos/display/display_preferences.h"
20#include "content/public/browser/user_metrics.h"
21#include "content/public/browser/web_ui.h"
22#include "grit/ash_strings.h"
23#include "grit/generated_resources.h"
24#include "ui/base/l10n/l10n_util.h"
25#include "ui/gfx/display.h"
26#include "ui/gfx/rect.h"
27#include "ui/gfx/screen.h"
28#include "ui/gfx/size_conversions.h"
29
30using ash::DisplayManager;
31
32namespace chromeos {
33namespace options {
34namespace {
35
36DisplayManager* GetDisplayManager() {
37  return ash::Shell::GetInstance()->display_manager();
38}
39
40int64 GetDisplayId(const base::ListValue* args) {
41  // Assumes the display ID is specified as the first argument.
42  std::string id_value;
43  if (!args->GetString(0, &id_value)) {
44    LOG(ERROR) << "Can't find ID";
45    return gfx::Display::kInvalidDisplayID;
46  }
47
48  int64 display_id = gfx::Display::kInvalidDisplayID;
49  if (!base::StringToInt64(id_value, &display_id)) {
50    LOG(ERROR) << "Invalid display id: " << id_value;
51    return gfx::Display::kInvalidDisplayID;
52  }
53
54  return display_id;
55}
56
57bool CompareResolution(base::Value* display1, base::Value* display2) {
58  base::DictionaryValue* d1 = NULL;
59  base::DictionaryValue* d2 = NULL;
60  CHECK(display1->GetAsDictionary(&d1) && display2->GetAsDictionary(&d2));
61  int width1 = 0, height1 = 0, width2 = 0, height2 = 0;
62  CHECK(d1->GetInteger("width", &width1) && d1->GetInteger("height", &height1));
63  CHECK(d2->GetInteger("width", &width2) && d2->GetInteger("height", &height2));
64  double scale_factor1 = 0, scale_factor2 = 0;
65  if (d1->GetDouble("scaleFactor", &scale_factor1)) {
66    width1 /= scale_factor1;
67    height1 /= scale_factor1;
68  }
69  if (d2->GetDouble("scaleFactor", &scale_factor2)) {
70    width2 /= scale_factor2;
71    height2 /= scale_factor2;
72  }
73
74  if (width1 * height1 == width2 * height2) {
75    if (scale_factor1 != scale_factor2)
76      return scale_factor1 < scale_factor2;
77
78    int refresh_rate1 = 0, refresh_rate2 = 0;
79    CHECK(d1->GetInteger("refreshRate", &refresh_rate1) ==
80          d2->GetInteger("refreshRate", &refresh_rate2));
81    return refresh_rate1 < refresh_rate2;
82  }
83  return width1 * height1 < width2 * height2;
84}
85
86base::string16 GetColorProfileName(ui::ColorCalibrationProfile profile) {
87  switch (profile) {
88    case ui::COLOR_PROFILE_STANDARD:
89      return l10n_util::GetStringUTF16(
90          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE_STANDARD);
91    case ui::COLOR_PROFILE_DYNAMIC:
92      return l10n_util::GetStringUTF16(
93          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE_DYNAMIC);
94    case ui::COLOR_PROFILE_MOVIE:
95      return l10n_util::GetStringUTF16(
96          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE_MOVIE);
97    case ui::COLOR_PROFILE_READING:
98      return l10n_util::GetStringUTF16(
99          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE_READING);
100    case ui::NUM_COLOR_PROFILES:
101      break;
102  }
103
104  NOTREACHED();
105  return base::string16();
106}
107
108scoped_ptr<base::ListValue> GetResolutionsForInternalDisplay(
109    const ash::DisplayInfo& display_info) {
110  scoped_ptr<base::ListValue> js_resolutions(new base::ListValue);
111  const std::vector<float> ui_scales =
112      DisplayManager::GetScalesForDisplay(display_info);
113  gfx::SizeF base_size = display_info.bounds_in_native().size();
114  base_size.Scale(1.0f / display_info.device_scale_factor());
115  if (display_info.rotation() == gfx::Display::ROTATE_90 ||
116      display_info.rotation() == gfx::Display::ROTATE_270) {
117    float tmp = base_size.width();
118    base_size.set_width(base_size.height());
119    base_size.set_height(tmp);
120  }
121
122  for (size_t i = 0; i < ui_scales.size(); ++i) {
123    base::DictionaryValue* resolution_info = new base::DictionaryValue();
124    gfx::SizeF new_size = base_size;
125    new_size.Scale(ui_scales[i]);
126    gfx::Size resolution = gfx::ToFlooredSize(new_size);
127    resolution_info->SetDouble("scale", ui_scales[i]);
128    if (ui_scales[i] == 1.0f)
129      resolution_info->SetBoolean("isBest", true);
130    resolution_info->SetBoolean(
131        "selected", display_info.configured_ui_scale() == ui_scales[i]);
132    resolution_info->SetInteger("width", resolution.width());
133    resolution_info->SetInteger("height", resolution.height());
134    js_resolutions->Append(resolution_info);
135  }
136
137  return js_resolutions.Pass();
138}
139
140scoped_ptr<base::ListValue> GetResolutionsForExternalDisplay(
141    const ash::DisplayInfo& display_info) {
142  scoped_ptr<base::ListValue> js_resolutions(new base::ListValue);
143
144  gfx::Size current_size = display_info.bounds_in_native().size();
145  int largest_index = -1;
146  int largest_area = -1;
147
148  for (size_t i = 0; i < display_info.display_modes().size(); ++i) {
149    base::DictionaryValue* resolution_info = new base::DictionaryValue();
150    const ash::DisplayMode& display_mode = display_info.display_modes()[i];
151    gfx::Size resolution = display_mode.size;
152
153    if (resolution.GetArea() > largest_area) {
154      resolution_info->SetBoolean("isBest", true);
155      largest_area = resolution.GetArea();
156      if (largest_index >= 0) {
157        base::DictionaryValue* prev_largest = NULL;
158        CHECK(js_resolutions->GetDictionary(largest_index, &prev_largest));
159        prev_largest->SetBoolean("isBest", false);
160      }
161      largest_index = i;
162    }
163
164    if (resolution == current_size) {
165      // Right now, the scale factor for unselected resolutions is unknown.
166      // TODO(mukai): Set the scale factor for unselected ones.
167      resolution_info->SetDouble(
168          "scaleFactor", display_info.device_scale_factor());
169      resolution_info->SetBoolean("selected", true);
170    }
171
172    resolution_info->SetInteger("width", resolution.width());
173    resolution_info->SetInteger("height", resolution.height());
174    resolution_info->SetDouble("refreshRate", display_mode.refresh_rate);
175    js_resolutions->Append(resolution_info);
176  }
177
178  return js_resolutions.Pass();
179}
180
181}  // namespace
182
183DisplayOptionsHandler::DisplayOptionsHandler() {
184  ash::Shell::GetInstance()->display_controller()->AddObserver(this);
185}
186
187DisplayOptionsHandler::~DisplayOptionsHandler() {
188  ash::Shell::GetInstance()->display_controller()->RemoveObserver(this);
189}
190
191void DisplayOptionsHandler::GetLocalizedValues(
192    base::DictionaryValue* localized_strings) {
193  DCHECK(localized_strings);
194  RegisterTitle(localized_strings, "displayOptionsPage",
195                IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_TAB_TITLE);
196
197  localized_strings->SetString(
198      "selectedDisplayTitleOptions", l10n_util::GetStringUTF16(
199          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_OPTIONS));
200  localized_strings->SetString(
201      "selectedDisplayTitleResolution", l10n_util::GetStringUTF16(
202          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_RESOLUTION));
203  localized_strings->SetString(
204      "selectedDisplayTitleOrientation", l10n_util::GetStringUTF16(
205          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_ORIENTATION));
206  localized_strings->SetString(
207      "selectedDisplayTitleOverscan", l10n_util::GetStringUTF16(
208          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_OVERSCAN));
209
210  localized_strings->SetString("startMirroring", l10n_util::GetStringUTF16(
211      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_START_MIRRORING));
212  localized_strings->SetString("stopMirroring", l10n_util::GetStringUTF16(
213      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_STOP_MIRRORING));
214  localized_strings->SetString("mirroringDisplay", l10n_util::GetStringUTF16(
215      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_MIRRORING_DISPLAY_NAME));
216  localized_strings->SetString("setPrimary", l10n_util::GetStringUTF16(
217      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_SET_PRIMARY));
218  localized_strings->SetString("annotateBest", l10n_util::GetStringUTF16(
219      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_RESOLUTION_ANNOTATION_BEST));
220  localized_strings->SetString("orientation0", l10n_util::GetStringUTF16(
221      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_STANDARD_ORIENTATION));
222  localized_strings->SetString("orientation90", l10n_util::GetStringUTF16(
223      IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90));
224  localized_strings->SetString("orientation180", l10n_util::GetStringUTF16(
225      IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180));
226  localized_strings->SetString("orientation270", l10n_util::GetStringUTF16(
227      IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270));
228  localized_strings->SetString(
229      "startCalibratingOverscan", l10n_util::GetStringUTF16(
230          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_START_CALIBRATING_OVERSCAN));
231  localized_strings->SetString(
232      "selectedDisplayColorProfile", l10n_util::GetStringUTF16(
233          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE));
234}
235
236void DisplayOptionsHandler::InitializePage() {
237  DCHECK(web_ui());
238}
239
240void DisplayOptionsHandler::RegisterMessages() {
241  web_ui()->RegisterMessageCallback(
242      "getDisplayInfo",
243      base::Bind(&DisplayOptionsHandler::HandleDisplayInfo,
244                 base::Unretained(this)));
245  web_ui()->RegisterMessageCallback(
246      "setMirroring",
247      base::Bind(&DisplayOptionsHandler::HandleMirroring,
248                 base::Unretained(this)));
249  web_ui()->RegisterMessageCallback(
250      "setPrimary",
251      base::Bind(&DisplayOptionsHandler::HandleSetPrimary,
252                 base::Unretained(this)));
253  web_ui()->RegisterMessageCallback(
254      "setDisplayLayout",
255      base::Bind(&DisplayOptionsHandler::HandleDisplayLayout,
256                 base::Unretained(this)));
257  web_ui()->RegisterMessageCallback(
258      "setUIScale",
259      base::Bind(&DisplayOptionsHandler::HandleSetUIScale,
260                 base::Unretained(this)));
261  web_ui()->RegisterMessageCallback(
262      "setResolution",
263      base::Bind(&DisplayOptionsHandler::HandleSetResolution,
264                 base::Unretained(this)));
265  web_ui()->RegisterMessageCallback(
266      "setOrientation",
267      base::Bind(&DisplayOptionsHandler::HandleSetOrientation,
268                 base::Unretained(this)));
269  web_ui()->RegisterMessageCallback(
270      "setColorProfile",
271      base::Bind(&DisplayOptionsHandler::HandleSetColorProfile,
272                 base::Unretained(this)));
273}
274
275void DisplayOptionsHandler::OnDisplayConfigurationChanging() {
276}
277
278void DisplayOptionsHandler::OnDisplayConfigurationChanged() {
279  SendAllDisplayInfo();
280}
281
282void DisplayOptionsHandler::SendAllDisplayInfo() {
283  DisplayManager* display_manager = GetDisplayManager();
284
285  std::vector<gfx::Display> displays;
286  for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
287    displays.push_back(display_manager->GetDisplayAt(i));
288  }
289  SendDisplayInfo(displays);
290}
291
292void DisplayOptionsHandler::SendDisplayInfo(
293    const std::vector<gfx::Display>& displays) {
294  DisplayManager* display_manager = GetDisplayManager();
295  base::FundamentalValue mirroring(display_manager->IsMirrored());
296
297  int64 primary_id = ash::Shell::GetScreen()->GetPrimaryDisplay().id();
298  base::ListValue js_displays;
299  for (size_t i = 0; i < displays.size(); ++i) {
300    const gfx::Display& display = displays[i];
301    const ash::DisplayInfo& display_info =
302        display_manager->GetDisplayInfo(display.id());
303    const gfx::Rect& bounds = display.bounds();
304    base::DictionaryValue* js_display = new base::DictionaryValue();
305    js_display->SetString("id", base::Int64ToString(display.id()));
306    js_display->SetInteger("x", bounds.x());
307    js_display->SetInteger("y", bounds.y());
308    js_display->SetInteger("width", bounds.width());
309    js_display->SetInteger("height", bounds.height());
310    js_display->SetString("name",
311                          display_manager->GetDisplayNameForId(display.id()));
312    js_display->SetBoolean("isPrimary", display.id() == primary_id);
313    js_display->SetBoolean("isInternal", display.IsInternal());
314    js_display->SetInteger("orientation",
315                           static_cast<int>(display_info.rotation()));
316
317    scoped_ptr<base::ListValue> js_resolutions = display.IsInternal() ?
318        GetResolutionsForInternalDisplay(display_info) :
319        GetResolutionsForExternalDisplay(display_info);
320    std::sort(
321        js_resolutions->begin(), js_resolutions->end(), CompareResolution);
322    js_display->Set("resolutions", js_resolutions.release());
323
324    js_display->SetInteger("colorProfile", display_info.color_profile());
325    base::ListValue* available_color_profiles = new base::ListValue();
326    for (size_t i = 0;
327         i < display_info.available_color_profiles().size(); ++i) {
328      base::DictionaryValue* color_profile_dict = new base::DictionaryValue();
329      ui::ColorCalibrationProfile color_profile =
330          display_info.available_color_profiles()[i];
331      color_profile_dict->SetInteger("profileId", color_profile);
332      color_profile_dict->SetString("name", GetColorProfileName(color_profile));
333      available_color_profiles->Append(color_profile_dict);
334    }
335    js_display->Set("availableColorProfiles", available_color_profiles);
336    js_displays.Append(js_display);
337  }
338
339  scoped_ptr<base::Value> layout_value(base::Value::CreateNullValue());
340  scoped_ptr<base::Value> offset_value(base::Value::CreateNullValue());
341  if (display_manager->GetNumDisplays() > 1) {
342    const ash::DisplayLayout layout =
343        display_manager->GetCurrentDisplayLayout();
344    layout_value.reset(new base::FundamentalValue(layout.position));
345    offset_value.reset(new base::FundamentalValue(layout.offset));
346  }
347
348  web_ui()->CallJavascriptFunction(
349      "options.DisplayOptions.setDisplayInfo",
350      mirroring, js_displays, *layout_value.get(), *offset_value.get());
351}
352
353void DisplayOptionsHandler::OnFadeOutForMirroringFinished(bool is_mirroring) {
354  ash::Shell::GetInstance()->display_manager()->SetMirrorMode(is_mirroring);
355  // Not necessary to start fade-in animation. DisplayConfigurator will do that.
356}
357
358void DisplayOptionsHandler::OnFadeOutForDisplayLayoutFinished(
359    int position, int offset) {
360  SetCurrentDisplayLayout(
361      ash::DisplayLayout::FromInts(position, offset));
362  ash::Shell::GetInstance()->display_configurator_animation()->
363      StartFadeInAnimation();
364}
365
366void DisplayOptionsHandler::HandleDisplayInfo(
367    const base::ListValue* unused_args) {
368  SendAllDisplayInfo();
369}
370
371void DisplayOptionsHandler::HandleMirroring(const base::ListValue* args) {
372  DCHECK(!args->empty());
373  content::RecordAction(
374      base::UserMetricsAction("Options_DisplayToggleMirroring"));
375  bool is_mirroring = false;
376  args->GetBoolean(0, &is_mirroring);
377  ash::Shell::GetInstance()->display_configurator_animation()->
378      StartFadeOutAnimation(base::Bind(
379          &DisplayOptionsHandler::OnFadeOutForMirroringFinished,
380          base::Unretained(this),
381          is_mirroring));
382}
383
384void DisplayOptionsHandler::HandleSetPrimary(const base::ListValue* args) {
385  DCHECK(!args->empty());
386  int64 display_id = GetDisplayId(args);
387  if (display_id == gfx::Display::kInvalidDisplayID)
388    return;
389
390  content::RecordAction(base::UserMetricsAction("Options_DisplaySetPrimary"));
391  ash::Shell::GetInstance()->display_controller()->
392      SetPrimaryDisplayId(display_id);
393}
394
395void DisplayOptionsHandler::HandleDisplayLayout(const base::ListValue* args) {
396  double layout = -1;
397  double offset = -1;
398  if (!args->GetDouble(0, &layout) || !args->GetDouble(1, &offset)) {
399    LOG(ERROR) << "Invalid parameter";
400    SendAllDisplayInfo();
401    return;
402  }
403  DCHECK_LE(ash::DisplayLayout::TOP, layout);
404  DCHECK_GE(ash::DisplayLayout::LEFT, layout);
405  content::RecordAction(base::UserMetricsAction("Options_DisplayRearrange"));
406  ash::Shell::GetInstance()->display_configurator_animation()->
407      StartFadeOutAnimation(base::Bind(
408          &DisplayOptionsHandler::OnFadeOutForDisplayLayoutFinished,
409          base::Unretained(this),
410          static_cast<int>(layout),
411          static_cast<int>(offset)));
412}
413
414void DisplayOptionsHandler::HandleSetUIScale(const base::ListValue* args) {
415  DCHECK(!args->empty());
416
417  int64 display_id = GetDisplayId(args);
418  if (display_id == gfx::Display::kInvalidDisplayID)
419    return;
420
421  double ui_scale = 0.0f;
422  if (!args->GetDouble(1, &ui_scale) || ui_scale == 0.0f) {
423    LOG(ERROR) << "Can't find new ui_scale";
424    return;
425  }
426
427  GetDisplayManager()->SetDisplayUIScale(display_id, ui_scale);
428}
429
430void DisplayOptionsHandler::HandleSetResolution(const base::ListValue* args) {
431  DCHECK(!args->empty());
432  int64 display_id = GetDisplayId(args);
433  if (display_id == gfx::Display::kInvalidDisplayID)
434    return;
435
436  content::RecordAction(
437      base::UserMetricsAction("Options_DisplaySetResolution"));
438  double width = 0.0f;
439  double height = 0.0f;
440  if (!args->GetDouble(1, &width) || width == 0.0f) {
441    LOG(ERROR) << "Can't find new width";
442    return;
443  }
444  if (!args->GetDouble(2, &height) || height == 0.0f) {
445    LOG(ERROR) << "Can't find new height";
446    return;
447  }
448
449  const ash::DisplayInfo& display_info =
450      GetDisplayManager()->GetDisplayInfo(display_id);
451  gfx::Size new_resolution = gfx::ToFlooredSize(gfx::SizeF(width, height));
452  gfx::Size old_resolution = display_info.bounds_in_native().size();
453  bool has_new_resolution = false;
454  bool has_old_resolution = false;
455  for (size_t i = 0; i < display_info.display_modes().size(); ++i) {
456    ash::DisplayMode display_mode = display_info.display_modes()[i];
457    if (display_mode.size == new_resolution)
458      has_new_resolution = true;
459    if (display_mode.size == old_resolution)
460      has_old_resolution = true;
461  }
462  if (!has_new_resolution) {
463    LOG(ERROR) << "No new resolution " << new_resolution.ToString()
464               << " is found in the display info " << display_info.ToString();
465    return;
466  }
467  if (!has_old_resolution) {
468    LOG(ERROR) << "No old resolution " << old_resolution.ToString()
469               << " is found in the display info " << display_info.ToString();
470    return;
471  }
472
473  ash::Shell::GetInstance()->resolution_notification_controller()->
474      SetDisplayResolutionAndNotify(
475          display_id, old_resolution, new_resolution,
476          base::Bind(&StoreDisplayPrefs));
477}
478
479void DisplayOptionsHandler::HandleSetOrientation(const base::ListValue* args) {
480  DCHECK(!args->empty());
481
482  int64 display_id = GetDisplayId(args);
483  if (display_id == gfx::Display::kInvalidDisplayID)
484    return;
485
486  std::string rotation_value;
487  gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_0;
488  if (!args->GetString(1, &rotation_value)) {
489    LOG(ERROR) << "Can't find new orientation";
490    return;
491  }
492  if (rotation_value == "90")
493    new_rotation = gfx::Display::ROTATE_90;
494  else if (rotation_value == "180")
495    new_rotation = gfx::Display::ROTATE_180;
496  else if (rotation_value == "270")
497    new_rotation = gfx::Display::ROTATE_270;
498  else if (rotation_value != "0")
499    LOG(ERROR) << "Invalid rotation: " << rotation_value << " Falls back to 0";
500
501  content::RecordAction(
502      base::UserMetricsAction("Options_DisplaySetOrientation"));
503  GetDisplayManager()->SetDisplayRotation(display_id, new_rotation);
504}
505
506void DisplayOptionsHandler::HandleSetColorProfile(const base::ListValue* args) {
507  DCHECK(!args->empty());
508  int64 display_id = GetDisplayId(args);
509  if (display_id == gfx::Display::kInvalidDisplayID)
510    return;
511
512  std::string profile_value;
513  if (!args->GetString(1, &profile_value)) {
514    LOG(ERROR) << "Invalid profile_value";
515    return;
516  }
517
518  int profile_id;
519  if (!base::StringToInt(profile_value, &profile_id)) {
520    LOG(ERROR) << "Invalid profile: " << profile_value;
521    return;
522  }
523
524  if (profile_id < ui::COLOR_PROFILE_STANDARD ||
525      profile_id > ui::COLOR_PROFILE_READING) {
526    LOG(ERROR) << "Invalid profile_id: " << profile_id;
527    return;
528  }
529
530  GetDisplayManager()->SetColorCalibrationProfile(
531      display_id, static_cast<ui::ColorCalibrationProfile>(profile_id));
532  SendAllDisplayInfo();
533}
534
535}  // namespace options
536}  // namespace chromeos
537