Commit 9c10bc49 authored by Steven Bennetts's avatar Steven Bennetts Committed by Commit Bot

DisplayInfoProviderChromeOS: Cleanup

Additional cleanup in preparation for moving the ash implementation
to src/ash.

Note: This CL simplifies and clarifies when and how DisplayProperties
are applied.

Bug: 682402
Change-Id: Ic89eefecbbb2c35b018d05861963044b46dad4c3
Reviewed-on: https://chromium-review.googlesource.com/983006
Commit-Queue: Steven Bennetts <stevenjb@chromium.org>
Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarAhmed Fakhry <afakhry@chromium.org>
Reviewed-by: default avatarToni Barzic <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#547454}
parent 2493198a
...@@ -1177,7 +1177,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetNegativeOverscan) { ...@@ -1177,7 +1177,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetNegativeOverscan) {
std::string error = std::string error =
CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info); CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info);
EXPECT_EQ("Negative overscan not allowed.", error); EXPECT_FALSE(error.empty());
EXPECT_EQ("1200,0 300x500", secondary.bounds().ToString()); EXPECT_EQ("1200,0 300x500", secondary.bounds().ToString());
...@@ -1185,8 +1185,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetNegativeOverscan) { ...@@ -1185,8 +1185,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetNegativeOverscan) {
info.overscan->right = -200; info.overscan->right = -200;
error = CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info); error = CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info);
EXPECT_FALSE(error.empty());
EXPECT_EQ("Negative overscan not allowed.", error);
EXPECT_EQ("1200,0 300x500", secondary.bounds().ToString()); EXPECT_EQ("1200,0 300x500", secondary.bounds().ToString());
...@@ -1194,8 +1193,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetNegativeOverscan) { ...@@ -1194,8 +1193,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetNegativeOverscan) {
info.overscan->top = -300; info.overscan->top = -300;
error = CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info); error = CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info);
EXPECT_FALSE(error.empty());
EXPECT_EQ("Negative overscan not allowed.", error);
EXPECT_EQ("1200,0 300x500", secondary.bounds().ToString()); EXPECT_EQ("1200,0 300x500", secondary.bounds().ToString());
...@@ -1203,8 +1201,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetNegativeOverscan) { ...@@ -1203,8 +1201,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetNegativeOverscan) {
info.overscan->top = -1000; info.overscan->top = -1000;
error = CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info); error = CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info);
EXPECT_FALSE(error.empty());
EXPECT_EQ("Negative overscan not allowed.", error);
EXPECT_EQ("1200,0 300x500", secondary.bounds().ToString()); EXPECT_EQ("1200,0 300x500", secondary.bounds().ToString());
...@@ -1231,8 +1228,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetOverscanLargerThanHorizontalBounds) { ...@@ -1231,8 +1228,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetOverscanLargerThanHorizontalBounds) {
std::string error = std::string error =
CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info); CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info);
EXPECT_EQ("Horizontal overscan is more than half of the screen width.", EXPECT_FALSE(error.empty());
error);
} }
TEST_F(DisplayInfoProviderChromeosTest, SetOverscanLargerThanVerticalBounds) { TEST_F(DisplayInfoProviderChromeosTest, SetOverscanLargerThanVerticalBounds) {
...@@ -1249,7 +1245,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetOverscanLargerThanVerticalBounds) { ...@@ -1249,7 +1245,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetOverscanLargerThanVerticalBounds) {
std::string error = std::string error =
CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info); CallSetDisplayUnitInfo(base::Int64ToString(secondary.id()), info);
EXPECT_EQ("Vertical overscan is more than half of the screen height.", error); EXPECT_FALSE(error.empty());
} }
TEST_F(DisplayInfoProviderChromeosTest, SetOverscan) { TEST_F(DisplayInfoProviderChromeosTest, SetOverscan) {
...@@ -1292,7 +1288,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetOverscanForInternal) { ...@@ -1292,7 +1288,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetOverscanForInternal) {
std::string error = std::string error =
CallSetDisplayUnitInfo(base::Int64ToString(internal_display_id), info); CallSetDisplayUnitInfo(base::Int64ToString(internal_display_id), info);
EXPECT_EQ("Overscan changes not allowed for the internal monitor.", error); EXPECT_FALSE(error.empty());
} }
TEST_F(DisplayInfoProviderChromeosTest, DisplayMode) { TEST_F(DisplayInfoProviderChromeosTest, DisplayMode) {
...@@ -1530,7 +1526,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetDisplayZoomFactor) { ...@@ -1530,7 +1526,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetDisplayZoomFactor) {
std::string error = std::string error =
CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[0]), info); CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[0]), info);
EXPECT_EQ(std::string(), error); EXPECT_TRUE(error.empty());
EXPECT_EQ(GetDisplayZoom(display_id_list[0]), final_zoom_factor_1); EXPECT_EQ(GetDisplayZoom(display_id_list[0]), final_zoom_factor_1);
// Display 2 has not been updated yet, so it will still have the old zoom // Display 2 has not been updated yet, so it will still have the old zoom
...@@ -1539,22 +1535,18 @@ TEST_F(DisplayInfoProviderChromeosTest, SetDisplayZoomFactor) { ...@@ -1539,22 +1535,18 @@ TEST_F(DisplayInfoProviderChromeosTest, SetDisplayZoomFactor) {
info.display_zoom_factor = std::make_unique<double>(zoom_factor_2); info.display_zoom_factor = std::make_unique<double>(zoom_factor_2);
error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[1]), info); error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[1]), info);
EXPECT_EQ(std::string(), error); EXPECT_TRUE(error.empty());
// Both displays should now have the correct zoom factor set. // Both displays should now have the correct zoom factor set.
EXPECT_EQ(GetDisplayZoom(display_id_list[0]), final_zoom_factor_1); EXPECT_EQ(GetDisplayZoom(display_id_list[0]), final_zoom_factor_1);
EXPECT_EQ(GetDisplayZoom(display_id_list[1]), final_zoom_factor_2); EXPECT_EQ(GetDisplayZoom(display_id_list[1]), final_zoom_factor_2);
std::string expected_err =
"Zoom value is out of range for display with id: " +
base::Int64ToString(display_id_list[0]);
// This zoom factor when applied to the display with width 1200, will result // This zoom factor when applied to the display with width 1200, will result
// in an effective width greater than 4096, which is out of range. // in an effective width greater than 4096, which is out of range.
float invalid_zoom_factor_1 = 0.285f; float invalid_zoom_factor_1 = 0.285f;
info.display_zoom_factor = std::make_unique<double>(invalid_zoom_factor_1); info.display_zoom_factor = std::make_unique<double>(invalid_zoom_factor_1);
error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[0]), info); error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[0]), info);
EXPECT_EQ(expected_err, error); EXPECT_FALSE(error.empty());
// This zoom factor when applied to the display with width 1200, will result // This zoom factor when applied to the display with width 1200, will result
// in an effective width greater less than 640, which is out of range. // in an effective width greater less than 640, which is out of range.
...@@ -1575,7 +1567,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetDisplayZoomFactor) { ...@@ -1575,7 +1567,7 @@ TEST_F(DisplayInfoProviderChromeosTest, SetDisplayZoomFactor) {
float valid_zoom_factor_1 = 0.8f; float valid_zoom_factor_1 = 0.8f;
info.display_zoom_factor = std::make_unique<double>(valid_zoom_factor_1); info.display_zoom_factor = std::make_unique<double>(valid_zoom_factor_1);
error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[0]), info); error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[0]), info);
EXPECT_EQ(std::string(), error); EXPECT_TRUE(error.empty());
// Results in a logical width of 4200px. This is above the 4096px threshold // Results in a logical width of 4200px. This is above the 4096px threshold
// but is valid because the initial width was 4500px, so logical width of up // but is valid because the initial width was 4500px, so logical width of up
...@@ -1583,17 +1575,17 @@ TEST_F(DisplayInfoProviderChromeosTest, SetDisplayZoomFactor) { ...@@ -1583,17 +1575,17 @@ TEST_F(DisplayInfoProviderChromeosTest, SetDisplayZoomFactor) {
float valid_zoom_factor_2 = 1.07f; float valid_zoom_factor_2 = 1.07f;
info.display_zoom_factor = std::make_unique<double>(valid_zoom_factor_2); info.display_zoom_factor = std::make_unique<double>(valid_zoom_factor_2);
error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[1]), info); error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[1]), info);
EXPECT_EQ(std::string(), error); EXPECT_TRUE(error.empty());
float valid_zoom_factor_3 = 0.5f; float valid_zoom_factor_3 = 0.5f;
info.display_zoom_factor = std::make_unique<double>(valid_zoom_factor_3); info.display_zoom_factor = std::make_unique<double>(valid_zoom_factor_3);
error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[0]), info); error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[0]), info);
EXPECT_EQ(std::string(), error); EXPECT_TRUE(error.empty());
float valid_zoom_factor_4 = 2.f; float valid_zoom_factor_4 = 2.f;
info.display_zoom_factor = std::make_unique<double>(valid_zoom_factor_4); info.display_zoom_factor = std::make_unique<double>(valid_zoom_factor_4);
error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[1]), info); error = CallSetDisplayUnitInfo(base::Int64ToString(display_id_list[1]), info);
EXPECT_EQ(std::string(), error); EXPECT_TRUE(error.empty());
} }
class DisplayInfoProviderChromeosTouchviewTest class DisplayInfoProviderChromeosTouchviewTest
......
...@@ -34,7 +34,9 @@ int RotationToDegrees(display::Display::Rotation rotation) { ...@@ -34,7 +34,9 @@ int RotationToDegrees(display::Display::Rotation rotation) {
} // namespace } // namespace
DisplayInfoProvider::~DisplayInfoProvider() {} DisplayInfoProvider::DisplayInfoProvider() = default;
DisplayInfoProvider::~DisplayInfoProvider() = default;
// static // static
DisplayInfoProvider* DisplayInfoProvider::Get() { DisplayInfoProvider* DisplayInfoProvider::Get() {
...@@ -91,12 +93,12 @@ void DisplayInfoProvider::SetDisplayProperties( ...@@ -91,12 +93,12 @@ void DisplayInfoProvider::SetDisplayProperties(
const std::string& display_id, const std::string& display_id,
const api::system_display::DisplayProperties& properties, const api::system_display::DisplayProperties& properties,
ErrorCallback callback) { ErrorCallback callback) {
LOG(ERROR) << "SetDisplayProperties not implemented"; NOTREACHED() << "SetDisplayProperties not implemented";
} }
void DisplayInfoProvider::SetDisplayLayout(const DisplayLayoutList& layouts, void DisplayInfoProvider::SetDisplayLayout(const DisplayLayoutList& layouts,
ErrorCallback callback) { ErrorCallback callback) {
LOG(ERROR) << "SetDisplayLayout not implemented"; NOTREACHED() << "SetDisplayLayout not implemented";
} }
void DisplayInfoProvider::EnableUnifiedDesktop(bool enable) {} void DisplayInfoProvider::EnableUnifiedDesktop(bool enable) {}
......
...@@ -115,7 +115,7 @@ class DisplayInfoProvider { ...@@ -115,7 +115,7 @@ class DisplayInfoProvider {
ErrorCallback callback); ErrorCallback callback);
protected: protected:
DisplayInfoProvider() = default; DisplayInfoProvider();
// Create a DisplayUnitInfo from a display::Display for implementations of // Create a DisplayUnitInfo from a display::Display for implementations of
// GetAllDisplaysInfo() // GetAllDisplaysInfo()
......
...@@ -221,7 +221,7 @@ namespace system.display { ...@@ -221,7 +221,7 @@ namespace system.display {
// other properties may not apply. This is has no effect if not provided. // other properties may not apply. This is has no effect if not provided.
boolean? isUnified; boolean? isUnified;
// Deprecated. Please use setMirrorMode() instead. // Deprecated. Please use $(ref:setMirrorMode) instead.
// Chrome OS only. If set and not empty, enables mirroring for this display. // Chrome OS only. If set and not empty, enables mirroring for this display.
// Otherwise disables mirroring for this display. This value should indicate // Otherwise disables mirroring for this display. This value should indicate
// the id of the source display to mirror, which must not be the same as the // the id of the source display to mirror, which must not be the same as the
...@@ -229,36 +229,37 @@ namespace system.display { ...@@ -229,36 +229,37 @@ namespace system.display {
DOMString? mirroringSourceId; DOMString? mirroringSourceId;
// If set to true, makes the display primary. No-op if set to false. // If set to true, makes the display primary. No-op if set to false.
// Note: If set, the display is considered primary for all other properties
// (i.e. $(ref:isUnified) may be set and bounds origin may not).
boolean? isPrimary; boolean? isPrimary;
// If set, sets the display's overscan insets to the provided values. Note // If set, sets the display's overscan insets to the provided values. Note
// that overscan values may not be negative or larger than a half of the // that overscan values may not be negative or larger than a half of the
// screen's size. Overscan cannot be changed on the internal monitor. // screen's size. Overscan cannot be changed on the internal monitor.
// It's applied after <code>isPrimary</code> parameter.
Insets? overscan; Insets? overscan;
// If set, updates the display's rotation. // If set, updates the display's rotation.
// Legal values are [0, 90, 180, 270]. The rotation is set clockwise, // Legal values are [0, 90, 180, 270]. The rotation is set clockwise,
// relative to the display's vertical position. // relative to the display's vertical position.
// It's applied after <code>overscan</code> paramter.
long? rotation; long? rotation;
// If set, updates the display's logical bounds origin along x-axis. Applied // If set, updates the display's logical bounds origin along the x-axis.
// together with <code>boundsOriginY</code>, if <code>boundsOriginY</code> // Applied together with $(ref:boundsOriginY). Defaults to the current value
// is set. Note that, when updating the display origin, some constraints // if not set and $(ref:boundsOriginY) is set. Note that when updating the
// will be applied, so the final bounds origin may be different than the one // display origin, some constraints will be applied, so the final bounds
// set. The final bounds can be retrieved using $(ref:getInfo). // origin may be different than the one set. The final bounds can be
// The bounds origin is applied after <code>rotation</code>. // retrieved using $(ref:getInfo). The bounds origin cannot be changed on
// The bounds origin cannot be changed on the primary display. Note that is // the primary display.
// also invalid to set bounds origin values if <code>isPrimary</code> is
// also set (as <code>isPrimary</code> parameter is applied first).
long? boundsOriginX; long? boundsOriginX;
// If set, updates the display's logical bounds origin along y-axis. // If set, updates the display's logical bounds origin along the y-axis.
// See documentation for <code>boundsOriginX</code> parameter. // See documentation for $(ref:boundsOriginX) parameter.
long? boundsOriginY; long? boundsOriginY;
// If set, updates the display mode to the mode matching this value. // If set, updates the display mode to the mode matching this value.
// If other parameters are invalid, this will not be applied. If the
// display mode is invalid, it will not be applied and an error will be
// set, but other properties will still be applied.
DisplayMode? displayMode; DisplayMode? displayMode;
// If set, updates the zoom associated with the display. This zoom performs // If set, updates the zoom associated with the display. This zoom performs
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
#include "base/values.h" #include "base/values.h"
#include "ui/display/display.h" #include "ui/display/display.h"
#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
namespace display { namespace display {
namespace { namespace {
...@@ -329,6 +331,23 @@ void DeIntersectDisplays(int64_t primary_id, ...@@ -329,6 +331,23 @@ void DeIntersectDisplays(int64_t primary_id,
UpdatePlacementList(display_list, placement_list); UpdatePlacementList(display_list, placement_list);
} }
// Checks if the given point is over the radius vector described by its end
// point |vector|. The point is over a vector if it's on its positive (left)
// side. The method sees a point on the same line as the vector as being over
// the vector.
bool IsPointOverRadiusVector(const gfx::Point& point,
const gfx::Point& vector) {
// |point| is left of |vector| if its radius vector's scalar product with a
// vector orthogonal (and facing the positive side) to |vector| is positive.
//
// An orthogonal vector of (a, b) is (b, -a), as the scalar product of these
// two is 0.
// So, (x, y) is over (a, b) if x * b + y * (-a) >= 0, which is equivalent to
// x * b >= y * a.
return static_cast<int64_t>(point.x()) * static_cast<int64_t>(vector.y()) >=
static_cast<int64_t>(point.y()) * static_cast<int64_t>(vector.x());
}
} // namespace } // namespace
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
...@@ -634,6 +653,97 @@ DisplayPlacement DisplayLayout::FindPlacementById(int64_t display_id) const { ...@@ -634,6 +653,97 @@ DisplayPlacement DisplayLayout::FindPlacementById(int64_t display_id) const {
: DisplayPlacement(*iter); : DisplayPlacement(*iter);
} }
// Creates a display::DisplayPlacement value for |rectangle| relative to the
// |reference| rectangle.
// The layout consists of two values:
// - position: Whether the rectangle is positioned left, right, over or under
// the reference.
// - offset: The rectangle's offset from the reference origin along the axis
// opposite the position direction (if the rectangle is left or right along
// y-axis, otherwise along x-axis).
// The rectangle's position is calculated by dividing the space in areas defined
// by the |reference|'s diagonals and finding the area |rectangle|'s center
// point belongs. If the |rectangle| in the calculated layout does not share a
// part of the bounds with the |reference|, the |rectangle| position in set to
// the more suitable neighboring position (e.g. if |rectangle| is completely
// over the |reference| top bound, it will be set to TOP) and the layout is
// recalculated with the new position. This is to handle the case where the
// rectangle shares an edge with the reference, but it's center is not in the
// same area as the reference's edge, e.g.
//
// +---------------------+
// | |
// | REFERENCE |
// | |
// | |
// +---------------------+
// +-------------------------------------------------+
// | RECTANGLE x |
// +-------------------------------------------------+
//
// The rectangle shares an egde with the reference's bottom edge, but it's
// center point is in the left area.
// static
DisplayPlacement DisplayLayout::CreatePlacementForRectangles(
const gfx::Rect& reference,
const gfx::Rect& rectangle) {
// Translate coordinate system so origin is in the reference's top left point
// (so the reference's down-diagonal vector starts in the (0, 0)) and scale it
// up by two (to avoid division when calculating the rectangle's center
// point).
gfx::Point center(2 * (rectangle.x() - reference.x()) + rectangle.width(),
2 * (rectangle.y() - reference.y()) + rectangle.height());
gfx::Point down_diag(2 * reference.width(), 2 * reference.height());
bool is_top_right = IsPointOverRadiusVector(center, down_diag);
// Translate the coordinate system again, so the bottom right point of the
// reference is origin (so the reference's up-diagonal starts at (0, 0)).
// Note that the coordinate system is scaled by 2.
center.Offset(0, -2 * reference.height());
// Choose the vector orientation so the points on the diagonal are considered
// to be left.
gfx::Point up_diag(-2 * reference.width(), 2 * reference.height());
bool is_bottom_right = IsPointOverRadiusVector(center, up_diag);
DisplayPlacement::Position position;
if (is_top_right) {
position =
is_bottom_right ? DisplayPlacement::RIGHT : DisplayPlacement::TOP;
} else {
position =
is_bottom_right ? DisplayPlacement::BOTTOM : DisplayPlacement::LEFT;
}
// If the rectangle with the calculated position would not have common side
// with the reference, try to position it so it shares another edge with the
// reference.
if (is_top_right == is_bottom_right) {
if (rectangle.y() > reference.bottom()) {
// The rectangle is left or right, but completely under the reference.
position = DisplayPlacement::BOTTOM;
} else if (rectangle.bottom() < reference.y()) {
// The rectangle is left or right, but completely over the reference.
position = DisplayPlacement::TOP;
}
} else {
if (rectangle.x() > reference.right()) {
// The rectangle is over or under, but completely right of the reference.
position = DisplayPlacement::RIGHT;
} else if (rectangle.right() < reference.x()) {
// The rectangle is over or under, but completely left of the reference.
position = DisplayPlacement::LEFT;
}
}
int offset = (position == DisplayPlacement::LEFT ||
position == DisplayPlacement::RIGHT)
? rectangle.y()
: rectangle.x();
return DisplayPlacement(position, offset);
}
// static // static
bool DisplayLayout::ApplyDisplayPlacement(const DisplayPlacement& placement, bool DisplayLayout::ApplyDisplayPlacement(const DisplayPlacement& placement,
Displays* display_list, Displays* display_list,
......
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "ui/display/display_export.h" #include "ui/display/display_export.h"
namespace gfx {
class Rect;
}
namespace display { namespace display {
class Display; class Display;
...@@ -123,6 +127,12 @@ class DISPLAY_EXPORT DisplayLayout final { ...@@ -123,6 +127,12 @@ class DISPLAY_EXPORT DisplayLayout final {
// otherwise returns a DisplayPlacement with an invalid display id. // otherwise returns a DisplayPlacement with an invalid display id.
DisplayPlacement FindPlacementById(int64_t display_id) const; DisplayPlacement FindPlacementById(int64_t display_id) const;
// Creates a display::DisplayPlacement value for |rectangle| relative to the
// |reference| rectangle.
static DisplayPlacement CreatePlacementForRectangles(
const gfx::Rect& reference,
const gfx::Rect& rectangle);
private: private:
// Apply the display placement to |display_list|. // Apply the display placement to |display_list|.
// Returns true if the display bounds were updated. // Returns true if the display bounds were updated.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment