Commit 2ddf37ba authored by skuhne@chromium.org's avatar skuhne@chromium.org

This CL is adding a minimize function for clicks on launcher items if only a...

This CL is adding a minimize function for clicks on launcher items if only a single item (already active) is associated with it. This is done to avoid that the user clicks and nothing happens (and people expect it to do that). The feature is guarded by a flag.

If the minimize action is not wanted, we use a bouncing animation which gives the user at least some feedback that he has done something.

BUG=231663
TEST=unittest & visual

Review URL: https://chromiumcodereview.appspot.com/14328031

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@195362 0039d316-1c4b-4281-b951-d872f2087c98
parent f95cc396
...@@ -6898,6 +6898,12 @@ Keep your key file in a safe place. You will need it to create new versions of y ...@@ -6898,6 +6898,12 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_FLAGS_SHOW_LAUNCHER_ALIGNMENT_MENU_DESCRIPTION" desc="Description for the flag to show a menu that lets you change the alignment of the launcher."> <message name="IDS_FLAGS_SHOW_LAUNCHER_ALIGNMENT_MENU_DESCRIPTION" desc="Description for the flag to show a menu that lets you change the alignment of the launcher.">
Enables a menu that allows changing the side the launcher is aligned to. Enables a menu that allows changing the side the launcher is aligned to.
</message> </message>
<message name="IDS_FLAGS_DISABLE_MINIMIZE_ON_SECOND_LAUNCHER_ITEM_CLICK_NAME" desc="Name for the flag which allows to minimize a window upon launcher item click under certain conditions.">
Disallow launcher to minimize-on-click
</message>
<message name="IDS_FLAGS_DISABLE_MINIMIZE_ON_SECOND_LAUNCHER_ITEM_CLICK_DESCRIPTION" desc="Description for the flag which allows to minimize a window upon launcher item click under certain conditions.">
Disallow the launcher to minimize a window if a launcher item gets clicked which has only a single, already active, window associated with it.
</message>
<message name="IDS_FLAGS_SHOW_TOUCH_HUD_NAME" desc="Name for the flag to show a heads-up display for tracking touch-points."> <message name="IDS_FLAGS_SHOW_TOUCH_HUD_NAME" desc="Name for the flag to show a heads-up display for tracking touch-points.">
Show HUD for touch points Show HUD for touch points
......
...@@ -971,6 +971,13 @@ const Experiment kExperiments[] = { ...@@ -971,6 +971,13 @@ const Experiment kExperiments[] = {
kOsAll, kOsAll,
SINGLE_VALUE_TYPE(switches::kShowLauncherAlignmentMenu) SINGLE_VALUE_TYPE(switches::kShowLauncherAlignmentMenu)
}, },
{
"disable-minimize-on-second-launcher-item-click",
IDS_FLAGS_DISABLE_MINIMIZE_ON_SECOND_LAUNCHER_ITEM_CLICK_NAME,
IDS_FLAGS_DISABLE_MINIMIZE_ON_SECOND_LAUNCHER_ITEM_CLICK_DESCRIPTION,
kOsAll,
SINGLE_VALUE_TYPE(switches::kDisableMinimizeOnSecondLauncherItemClick)
},
{ {
"show-touch-hud", "show-touch-hud",
IDS_FLAGS_SHOW_TOUCH_HUD_NAME, IDS_FLAGS_SHOW_TOUCH_HUD_NAME,
......
...@@ -88,9 +88,13 @@ void AppShortcutLauncherItemController::Activate() { ...@@ -88,9 +88,13 @@ void AppShortcutLauncherItemController::Activate() {
TabStripModel* tab_strip = browser->tab_strip_model(); TabStripModel* tab_strip = browser->tab_strip_model();
int index = tab_strip->GetIndexOfWebContents(content); int index = tab_strip->GetIndexOfWebContents(content);
DCHECK_NE(TabStripModel::kNoTab, index); DCHECK_NE(TabStripModel::kNoTab, index);
int old_index = tab_strip->active_index();
if (index != old_index)
tab_strip->ActivateTabAt(index, false); tab_strip->ActivateTabAt(index, false);
browser->window()->Show(); app_controller_->ActivateWindowOrMinimizeIfActive(
ash::wm::ActivateWindow(browser->window()->GetNativeWindow()); browser->window(),
index == old_index && GetRunningApplications().size() == 1);
} }
void AppShortcutLauncherItemController::Close() { void AppShortcutLauncherItemController::Close() {
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "chrome/browser/extensions/app_icon_loader.h" #include "chrome/browser/extensions/app_icon_loader.h"
#include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_prefs.h"
class BaseWindow;
class BrowserLauncherItemControllerTest; class BrowserLauncherItemControllerTest;
class LauncherItemController; class LauncherItemController;
class Profile; class Profile;
...@@ -256,6 +257,10 @@ class ChromeLauncherController ...@@ -256,6 +257,10 @@ class ChromeLauncherController
virtual const extensions::Extension* GetExtensionForAppID( virtual const extensions::Extension* GetExtensionForAppID(
const std::string& app_id) const = 0; const std::string& app_id) const = 0;
// Activates a |window|. If |allow_minimize| is true and the system allows
// it, the the window will get minimized instead.
virtual void ActivateWindowOrMinimizeIfActive(BaseWindow* window,
bool allow_minimize) = 0;
// ash::LauncherDelegate overrides: // ash::LauncherDelegate overrides:
virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE = 0; virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE = 0;
virtual void ItemClicked(const ash::LauncherItem& item, virtual void ItemClicked(const ash::LauncherItem& item,
......
...@@ -70,6 +70,7 @@ ...@@ -70,6 +70,7 @@
#include "ui/aura/root_window.h" #include "ui/aura/root_window.h"
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "ui/views/corewm/window_animations.h"
using extensions::Extension; using extensions::Extension;
using content::WebContents; using content::WebContents;
...@@ -873,6 +874,23 @@ const Extension* ChromeLauncherControllerPerApp::GetExtensionForAppID( ...@@ -873,6 +874,23 @@ const Extension* ChromeLauncherControllerPerApp::GetExtensionForAppID(
return profile_->GetExtensionService()->GetInstalledExtension(app_id); return profile_->GetExtensionService()->GetInstalledExtension(app_id);
} }
void ChromeLauncherControllerPerApp::ActivateWindowOrMinimizeIfActive(
BaseWindow* window,
bool allow_minimize) {
if (window->IsActive() && allow_minimize) {
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableMinimizeOnSecondLauncherItemClick)) {
AnimateWindow(window->GetNativeWindow(),
views::corewm::WINDOW_ANIMATION_TYPE_BOUNCE);
} else {
window->Minimize();
}
} else {
window->Show();
window->Activate();
}
}
void ChromeLauncherControllerPerApp::OnBrowserShortcutClicked( void ChromeLauncherControllerPerApp::OnBrowserShortcutClicked(
int event_flags) { int event_flags) {
if (event_flags & ui::EF_CONTROL_DOWN) { if (event_flags & ui::EF_CONTROL_DOWN) {
...@@ -888,9 +906,8 @@ void ChromeLauncherControllerPerApp::OnBrowserShortcutClicked( ...@@ -888,9 +906,8 @@ void ChromeLauncherControllerPerApp::OnBrowserShortcutClicked(
return; return;
} }
aura::Window* window = last_browser->window()->GetNativeWindow(); ActivateWindowOrMinimizeIfActive(last_browser->window(),
window->Show(); GetBrowserApplicationList(0).size() == 2);
ash::wm::ActivateWindow(window);
} }
void ChromeLauncherControllerPerApp::ItemClicked(const ash::LauncherItem& item, void ChromeLauncherControllerPerApp::ItemClicked(const ash::LauncherItem& item,
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "ui/aura/window_observer.h" #include "ui/aura/window_observer.h"
class AppSyncUIState; class AppSyncUIState;
class BaseWindow;
class Browser; class Browser;
class BrowserLauncherItemControllerTest; class BrowserLauncherItemControllerTest;
class ExtensionEnableFlow; class ExtensionEnableFlow;
...@@ -252,6 +253,11 @@ class ChromeLauncherControllerPerApp ...@@ -252,6 +253,11 @@ class ChromeLauncherControllerPerApp
virtual const extensions::Extension* GetExtensionForAppID( virtual const extensions::Extension* GetExtensionForAppID(
const std::string& app_id) const OVERRIDE; const std::string& app_id) const OVERRIDE;
// Activates a |window|. If |allow_minimize| is true and the system allows
// it, the the window will get minimized instead.
virtual void ActivateWindowOrMinimizeIfActive(BaseWindow* window,
bool allow_minimize) OVERRIDE;
// ash::LauncherDelegate overrides: // ash::LauncherDelegate overrides:
virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE; virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE;
virtual void ItemClicked(const ash::LauncherItem& item, virtual void ItemClicked(const ash::LauncherItem& item,
......
...@@ -254,6 +254,28 @@ class LauncherPerAppAppBrowserTestNoDefaultBrowser ...@@ -254,6 +254,28 @@ class LauncherPerAppAppBrowserTestNoDefaultBrowser
DISALLOW_COPY_AND_ASSIGN(LauncherPerAppAppBrowserTestNoDefaultBrowser); DISALLOW_COPY_AND_ASSIGN(LauncherPerAppAppBrowserTestNoDefaultBrowser);
}; };
// Since the default for minimizing on click might change, I added both classes
// to either get the minimize on click or not.
class LauncherPerAppAppBrowserNoMinimizeOnClick
: public LauncherPlatformPerAppAppBrowserTest {
protected:
LauncherPerAppAppBrowserNoMinimizeOnClick() {}
virtual ~LauncherPerAppAppBrowserNoMinimizeOnClick() {}
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
LauncherPlatformPerAppAppBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(
switches::kDisableMinimizeOnSecondLauncherItemClick);
}
private:
DISALLOW_COPY_AND_ASSIGN(LauncherPerAppAppBrowserNoMinimizeOnClick);
};
typedef LauncherPlatformPerAppAppBrowserTest
LauncherPerAppAppBrowserMinimizeOnClick;
// Test that we can launch a platform app and get a running item. // Test that we can launch a platform app and get a running item.
IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, LaunchUnpinned) { IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, LaunchUnpinned) {
int item_count = launcher_model()->item_count(); int item_count = launcher_model()->item_count();
...@@ -552,7 +574,8 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, WindowActivation) { ...@@ -552,7 +574,8 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, WindowActivation) {
} }
// Confirm that Click behavior for app windows is correnct. // Confirm that Click behavior for app windows is correnct.
IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, AppClickBehavior) { IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserNoMinimizeOnClick,
AppClickBehavior) {
// Launch a platform app and create a window for it. // Launch a platform app and create a window for it.
const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); const Extension* extension1 = LoadAndLaunchPlatformApp("launch");
ShellWindow* window1 = CreateShellWindow(extension1); ShellWindow* window1 = CreateShellWindow(extension1);
...@@ -588,6 +611,64 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, AppClickBehavior) { ...@@ -588,6 +611,64 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, AppClickBehavior) {
EXPECT_TRUE(window1->GetBaseWindow()->IsMaximized()); EXPECT_TRUE(window1->GetBaseWindow()->IsMaximized());
} }
// Confirm the minimizing click behavior for apps.
IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserMinimizeOnClick,
PackagedAppClickBehaviorInMinimizeMode) {
// Launch one platform app and create a window for it.
const Extension* extension1 = LoadAndLaunchPlatformApp("launch");
ShellWindow* window1 = CreateShellWindow(extension1);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
// Confirm that a controller item was created and is the correct state.
const ash::LauncherItem& item1 = GetLastLauncherItem();
LauncherItemController* item1_controller = GetItemController(item1.id);
EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type);
EXPECT_EQ(ash::STATUS_ACTIVE, item1.status);
EXPECT_EQ(LauncherItemController::TYPE_APP, item1_controller->type());
// Since it is already active, clicking it should minimize.
TestEvent click_event(ui::ET_MOUSE_PRESSED);
item1_controller->Clicked(click_event);
EXPECT_FALSE(window1->GetNativeWindow()->IsVisible());
EXPECT_FALSE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window1->GetBaseWindow()->IsMinimized());
EXPECT_EQ(ash::STATUS_RUNNING, item1.status);
// Clicking the item again should activate the window again.
item1_controller->Clicked(click_event);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
EXPECT_EQ(ash::STATUS_ACTIVE, item1.status);
// Maximizing a window should preserve state after minimize + click.
window1->GetBaseWindow()->Maximize();
window1->GetBaseWindow()->Minimize();
item1_controller->Clicked(click_event);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
EXPECT_TRUE(window1->GetBaseWindow()->IsMaximized());
window1->GetBaseWindow()->Restore();
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
EXPECT_FALSE(window1->GetBaseWindow()->IsMaximized());
// Creating a second window of the same type should change the behavior so
// that a click does not change the activation state.
ShellWindow* window1a = CreateShellWindow(extension1);
EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1a->GetBaseWindow()->IsActive());
// The first click does nothing.
item1_controller->Clicked(click_event);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
EXPECT_FALSE(window1a->GetBaseWindow()->IsActive());
// The second neither.
item1_controller->Clicked(click_event);
EXPECT_TRUE(window1->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible());
EXPECT_TRUE(window1->GetBaseWindow()->IsActive());
EXPECT_FALSE(window1a->GetBaseWindow()->IsActive());
}
// Confirm that click behavior for app panels is correct. // Confirm that click behavior for app panels is correct.
IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest,
AppPanelClickBehavior) { AppPanelClickBehavior) {
......
...@@ -853,6 +853,13 @@ const Extension* ChromeLauncherControllerPerBrowser::GetExtensionForAppID( ...@@ -853,6 +853,13 @@ const Extension* ChromeLauncherControllerPerBrowser::GetExtensionForAppID(
return profile_->GetExtensionService()->GetInstalledExtension(app_id); return profile_->GetExtensionService()->GetInstalledExtension(app_id);
} }
void ChromeLauncherControllerPerBrowser::ActivateWindowOrMinimizeIfActive(
BaseWindow* window,
bool allow_minimize) {
window->Show();
window->Activate();
}
void ChromeLauncherControllerPerBrowser::OnBrowserShortcutClicked( void ChromeLauncherControllerPerBrowser::OnBrowserShortcutClicked(
int event_flags) { int event_flags) {
if (event_flags & ui::EF_CONTROL_DOWN) { if (event_flags & ui::EF_CONTROL_DOWN) {
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "ui/aura/window_observer.h" #include "ui/aura/window_observer.h"
class AppSyncUIState; class AppSyncUIState;
class BaseWindow;
class Browser; class Browser;
class BrowserLauncherItemControllerTest; class BrowserLauncherItemControllerTest;
class ExtensionEnableFlow; class ExtensionEnableFlow;
...@@ -241,6 +242,11 @@ class ChromeLauncherControllerPerBrowser ...@@ -241,6 +242,11 @@ class ChromeLauncherControllerPerBrowser
virtual const extensions::Extension* GetExtensionForAppID( virtual const extensions::Extension* GetExtensionForAppID(
const std::string& app_id) const OVERRIDE; const std::string& app_id) const OVERRIDE;
// Activates a |window|. If |allow_minimize| is true and the system allows
// it, the the window will get minimized instead.
virtual void ActivateWindowOrMinimizeIfActive(BaseWindow* window,
bool allow_minimize) OVERRIDE;
// ash::LauncherDelegate overrides: // ash::LauncherDelegate overrides:
virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE; virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE;
virtual void ItemClicked(const ash::LauncherItem& item, virtual void ItemClicked(const ash::LauncherItem& item,
......
...@@ -144,18 +144,18 @@ void ShellWindowLauncherItemController::Clicked(const ui::Event& event) { ...@@ -144,18 +144,18 @@ void ShellWindowLauncherItemController::Clicked(const ui::Event& event) {
// activate it. // activate it.
if (ash::wm::MoveWindowToEventRoot(panel->GetNativeWindow(), event)) { if (ash::wm::MoveWindowToEventRoot(panel->GetNativeWindow(), event)) {
if (!panel->GetBaseWindow()->IsActive()) if (!panel->GetBaseWindow()->IsActive())
ShowAndActivate(panel); ShowAndActivateOrMinimize(panel);
} else { } else {
if (panel->GetBaseWindow()->IsActive()) if (panel->GetBaseWindow()->IsActive())
panel->GetBaseWindow()->Minimize(); panel->GetBaseWindow()->Minimize();
else else
ShowAndActivate(panel); ShowAndActivateOrMinimize(panel);
} }
} else if (launcher_controller()->GetPerAppInterface() || } else if (launcher_controller()->GetPerAppInterface() ||
shell_windows_.size() == 1) { shell_windows_.size() == 1) {
ShellWindow* window_to_show = last_active_shell_window_ ? ShellWindow* window_to_show = last_active_shell_window_ ?
last_active_shell_window_ : shell_windows_.front(); last_active_shell_window_ : shell_windows_.front();
ShowAndActivate(window_to_show); ShowAndActivateOrMinimize(window_to_show);
} else { } else {
// TODO(stevenjb): Deprecate // TODO(stevenjb): Deprecate
if (!last_active_shell_window_ || if (!last_active_shell_window_ ||
...@@ -169,7 +169,7 @@ void ShellWindowLauncherItemController::Clicked(const ui::Event& event) { ...@@ -169,7 +169,7 @@ void ShellWindowLauncherItemController::Clicked(const ui::Event& event) {
} }
} }
if (last_active_shell_window_) if (last_active_shell_window_)
ShowAndActivate(last_active_shell_window_); ShowAndActivateOrMinimize(last_active_shell_window_);
} }
} }
...@@ -178,7 +178,7 @@ void ShellWindowLauncherItemController::ActivateIndexedApp(size_t index) { ...@@ -178,7 +178,7 @@ void ShellWindowLauncherItemController::ActivateIndexedApp(size_t index) {
return; return;
ShellWindowList::iterator it = shell_windows_.begin(); ShellWindowList::iterator it = shell_windows_.begin();
std::advance(it, index); std::advance(it, index);
ShowAndActivate(*it); ShowAndActivateOrMinimize(*it);
} }
ChromeLauncherAppMenuItems ChromeLauncherAppMenuItems
...@@ -219,9 +219,10 @@ void ShellWindowLauncherItemController::OnWindowPropertyChanged( ...@@ -219,9 +219,10 @@ void ShellWindowLauncherItemController::OnWindowPropertyChanged(
} }
} }
void ShellWindowLauncherItemController::ShowAndActivate( void ShellWindowLauncherItemController::ShowAndActivateOrMinimize(
ShellWindow* shell_window) { ShellWindow* shell_window) {
// Always activate windows when shown from the launcher. // Either show or minimize windows when shown from the launcher.
shell_window->GetBaseWindow()->Show(); launcher_controller()->ActivateWindowOrMinimizeIfActive(
shell_window->GetBaseWindow()->Activate(); shell_window->GetBaseWindow(),
GetApplicationList().size() == 2);
} }
...@@ -80,7 +80,7 @@ class ShellWindowLauncherItemController : public LauncherItemController, ...@@ -80,7 +80,7 @@ class ShellWindowLauncherItemController : public LauncherItemController,
private: private:
typedef std::list<ShellWindow*> ShellWindowList; typedef std::list<ShellWindow*> ShellWindowList;
void ShowAndActivate(ShellWindow* shell_window); void ShowAndActivateOrMinimize(ShellWindow* shell_window);
// List of associated shell windows // List of associated shell windows
ShellWindowList shell_windows_; ShellWindowList shell_windows_;
......
...@@ -338,6 +338,11 @@ const char kDisableIPPooling[] = "disable-ip-pooling"; ...@@ -338,6 +338,11 @@ const char kDisableIPPooling[] = "disable-ip-pooling";
const char kDisableLocalOnlyInstantExtendedAPI[] = const char kDisableLocalOnlyInstantExtendedAPI[] =
"disable-local-only-instant-extended-api"; "disable-local-only-instant-extended-api";
// Disable the behavior that the second click on a launcher item (the click when
// the item is already active) minimizes the item.
const char kDisableMinimizeOnSecondLauncherItemClick[] =
"disable-minimize-on-second-launcher-item-click";
// Disables the native Autofill UI, which is part of the browser process rather // Disables the native Autofill UI, which is part of the browser process rather
// than part of the renderer process. http://crbug.com/51644 // than part of the renderer process. http://crbug.com/51644
const char kDisableNativeAutofillUi[] = "disable-new-autofill-ui"; const char kDisableNativeAutofillUi[] = "disable-new-autofill-ui";
......
...@@ -103,6 +103,7 @@ extern const char kDisableInstantExtendedAPI[]; ...@@ -103,6 +103,7 @@ extern const char kDisableInstantExtendedAPI[];
extern const char kDisableIPv6[]; extern const char kDisableIPv6[];
extern const char kDisableIPPooling[]; extern const char kDisableIPPooling[];
extern const char kDisableLocalOnlyInstantExtendedAPI[]; extern const char kDisableLocalOnlyInstantExtendedAPI[];
extern const char kDisableMinimizeOnSecondLauncherItemClick[];
extern const char kDisableNativeAutofillUi[]; extern const char kDisableNativeAutofillUi[];
extern const char kDisableNTPOtherSessionsMenu[]; extern const char kDisableNTPOtherSessionsMenu[];
extern const char kDisablePopupBlocking[]; extern const char kDisablePopupBlocking[];
......
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