-
-
Notifications
You must be signed in to change notification settings - Fork 671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add window states API #2473
base: main
Are you sure you want to change the base?
Add window states API #2473
Changes from 175 commits
d82c615
96ef3ed
4657567
00491c8
41c1dd7
60d5e43
f497a1d
d34cb2f
4d790b2
18b0cbc
618d647
b7eb117
cadc4cc
bf3511a
d61a644
5b58045
c9a1307
ab9b778
0a90a12
73721b3
c809698
06b7f2a
11fea69
938504c
be87113
9aefd30
13d2ff3
176e879
1ba73b8
d4bfdaa
d993b80
38bf5c3
b710eaf
b461295
0c7900b
97a6c66
ba28194
c7320ce
aed5251
a76bf9b
f0d5d80
e416f20
1fa8cde
6350b5f
d091955
5c2a53b
564c5ed
e36e596
174d66b
17564a8
51cea93
feb78a8
4632738
8286b86
16d5758
5b59b14
85dd202
552c0fc
56edec1
971b8da
934b6b1
1e6bd76
426e8b9
ccef113
4d83d19
067e988
3c84a80
a276f22
78cdd3b
b9e87c5
06d5a9e
b0c37fe
09ee428
1bd1f39
2b6d347
8bba100
6b3e883
74685d1
25c420f
83dcab4
ab0ed45
4fa4af6
71f4314
a308ea7
42642ee
6b513c7
ccc8f11
56f32a7
5da8231
4c6070d
cfa255a
87383f3
c3f32c7
4b2fdbb
b592ee2
a5891e8
e8ad898
8684038
1b53c16
3cebc76
d2510e0
4485cda
d0dcb35
f429307
6aedeea
d7228ba
600e4c9
3689314
1a6b4ad
2885d1c
cd9e00b
57fd1e3
7835fe8
1c0a969
cf23ce9
e6f4dcc
30e5968
c623254
f2bd750
11444e3
0771349
c9b7b90
3441cb3
62dcd7d
4f36f28
ea8058a
11c3ec3
0bd68c4
edef651
3c8b0f1
e915d72
fb6e657
b810d65
1e59da1
9dba56b
56cc124
2d29072
c2e88e5
279dfda
73bf7aa
af6a292
5a0352f
ef68572
ca48ba2
cd8eba2
e5eff2b
26eb01c
3a6aaf7
78b5ddf
e0f5bf0
54cfaab
8b8685e
d599730
91b70da
b38ff03
2cf1839
aa120e9
3661110
c928ac1
f9c18f5
ace13ce
ef7b3dc
bc19c7f
c2235f7
b0ead86
7bbf330
0f7fe4f
7308e11
8f858be
9f95554
295cd59
5bd410e
00a742e
4ef9c50
a1613be
138d0e3
450efad
51d3590
3de8eef
77e2b7e
7db80d2
aaee1f9
903860b
a6df392
f9f1375
ad427ab
56de33d
59012cd
5a66725
bb22a9f
f6a851d
35bbb8e
8bddf0e
03dd538
2af1509
8b56d66
7cd1489
598021f
596acc4
d3f90ba
945caf5
116eec3
71aec38
2559fa0
d822ff0
14b64b7
d8c8422
bb513d3
eb2d9a5
abfa707
8f7c954
379e002
e3f31ff
383e027
c986e3d
5f365c6
2961507
9ca14ed
b6edc2a
0f74ad9
589e3c7
621c723
655a146
4f2f2a5
c29ab39
0d67322
07e82f9
8be4d8d
1692a0d
f8ee69d
b82b6d0
247847e
1b624ad
5560e3d
a8fed19
5f0c1a2
136cdcf
8fcdbd6
ef584b1
7f28348
6e0c091
f535cd6
c175b6a
bc1f902
e8864d5
0f0103c
4162e4b
eb51730
b25b902
8df6c4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -10,6 +10,7 @@ | |||||||||||||||||||||||||||||||||||||||
from java import dynamic_proxy | ||||||||||||||||||||||||||||||||||||||||
from java.io import ByteArrayOutputStream | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
from toga.constants import WindowState | ||||||||||||||||||||||||||||||||||||||||
from toga.types import Position, Size | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
from .container import Container | ||||||||||||||||||||||||||||||||||||||||
|
@@ -31,11 +32,17 @@ def onGlobalLayout(self): | |||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
class Window(Container): | ||||||||||||||||||||||||||||||||||||||||
# ActionBar is always hidden on Window. | ||||||||||||||||||||||||||||||||||||||||
_actionbar_shown_by_default = False | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def __init__(self, interface, title, position, size): | ||||||||||||||||||||||||||||||||||||||||
super().__init__() | ||||||||||||||||||||||||||||||||||||||||
self.interface = interface | ||||||||||||||||||||||||||||||||||||||||
self.interface._impl = self | ||||||||||||||||||||||||||||||||||||||||
self._initial_title = title | ||||||||||||||||||||||||||||||||||||||||
# Use a shadow variable since the presence of ActionBar is not | ||||||||||||||||||||||||||||||||||||||||
# a reliable indicator for confirmation of presentation mode. | ||||||||||||||||||||||||||||||||||||||||
self._in_presentation_mode = False | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
###################################################################### | ||||||||||||||||||||||||||||||||||||||||
# Window properties | ||||||||||||||||||||||||||||||||||||||||
|
@@ -137,8 +144,73 @@ def get_visible(self): | |||||||||||||||||||||||||||||||||||||||
# Window state | ||||||||||||||||||||||||||||||||||||||||
###################################################################### | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def set_full_screen(self, is_full_screen): | ||||||||||||||||||||||||||||||||||||||||
self.interface.factory.not_implemented("Window.set_full_screen()") | ||||||||||||||||||||||||||||||||||||||||
def get_window_state(self): | ||||||||||||||||||||||||||||||||||||||||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
# window.state is called in _close(), which itself sometimes | ||||||||||||||||||||||||||||||||||||||||
# is called during early stages of app startup, during which | ||||||||||||||||||||||||||||||||||||||||
# the app attribute may not exist. In such cases, return NORMAL. | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree I was wrong. It should have been worded as "during the setup of a new test after completion of a previous test". if I modify
Then on running the testbed on Android:
Every test after
Stack dump of the test failure:
This indicates that, the conftest is causing the expected call to toga/testbed/tests/conftest.py Lines 60 to 78 in d7228ba
This cleanup behavior is expected, but I am a bit surprised that But, since |
||||||||||||||||||||||||||||||||||||||||
if getattr(self, "app", None) is None: | ||||||||||||||||||||||||||||||||||||||||
return WindowState.NORMAL | ||||||||||||||||||||||||||||||||||||||||
decor_view = self.app.native.getWindow().getDecorView() | ||||||||||||||||||||||||||||||||||||||||
system_ui_flags = decor_view.getSystemUiVisibility() | ||||||||||||||||||||||||||||||||||||||||
if system_ui_flags & ( | ||||||||||||||||||||||||||||||||||||||||
decor_view.SYSTEM_UI_FLAG_FULLSCREEN | ||||||||||||||||||||||||||||||||||||||||
| decor_view.SYSTEM_UI_FLAG_HIDE_NAVIGATION | ||||||||||||||||||||||||||||||||||||||||
| decor_view.SYSTEM_UI_FLAG_IMMERSIVE | ||||||||||||||||||||||||||||||||||||||||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||||||||||||||||
if self._in_presentation_mode: | ||||||||||||||||||||||||||||||||||||||||
return WindowState.PRESENTATION | ||||||||||||||||||||||||||||||||||||||||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||
return WindowState.FULLSCREEN | ||||||||||||||||||||||||||||||||||||||||
return WindowState.NORMAL | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def set_window_state(self, state): | ||||||||||||||||||||||||||||||||||||||||
current_state = self.get_window_state() | ||||||||||||||||||||||||||||||||||||||||
if current_state == state: | ||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like this should be an optimisation at the core level - it's something that will be common to every implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can't be done at the core level because we can't reliably determine the current state. Backends like Cocoa and GTK use non-blocking state APIs, meaning the state might be in in-between transition when checked. As a result, we could end up comparing against an outdated state. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure - but that means there are 2 possible interpretations of "window state" - the "current state", and the "state we're transitioning to". On blocking APIs, the two are always the same; on non-blocking, they might be different. This is easy enough to accomodate with a boolean argument to For the purpose of the public API, I'd argue you always want the "state we're transitioning to" - as that is the answer that will be consistent across platforms, and will ensure that:
will always return the expected result. However, it would warrant a note on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have modified There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In which case - is the check right here still needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, not really, since the Android API is blocking in nature and the internal API It is only required on the non-blocking API backends like cocoa, as the internal API So, I have removed it from the Android backend. EDIT: On further testing, this check acts as a termination condition for some cases when |
||||||||||||||||||||||||||||||||||||||||
decor_view = self.app.native.getWindow().getDecorView() | ||||||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||||||
current_state != WindowState.NORMAL | ||||||||||||||||||||||||||||||||||||||||
and state != WindowState.NORMAL | ||||||||||||||||||||||||||||||||||||||||
and (getattr(self, "_pending_window_state_transition", None) is None) | ||||||||||||||||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||||||||||||||||
# Set Window state to NORMAL before changing to other states as some | ||||||||||||||||||||||||||||||||||||||||
# states block changing window state without first exiting them or | ||||||||||||||||||||||||||||||||||||||||
# can even cause rendering glitches. | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok... but this block will result in calling |
||||||||||||||||||||||||||||||||||||||||
self._pending_window_state_transition = state | ||||||||||||||||||||||||||||||||||||||||
self.set_window_state(WindowState.NORMAL) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
elif state in {WindowState.FULLSCREEN, WindowState.PRESENTATION}: | ||||||||||||||||||||||||||||||||||||||||
decor_view.setSystemUiVisibility( | ||||||||||||||||||||||||||||||||||||||||
decor_view.SYSTEM_UI_FLAG_FULLSCREEN | ||||||||||||||||||||||||||||||||||||||||
| decor_view.SYSTEM_UI_FLAG_HIDE_NAVIGATION | ||||||||||||||||||||||||||||||||||||||||
| decor_view.SYSTEM_UI_FLAG_IMMERSIVE | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
if state == WindowState.PRESENTATION: | ||||||||||||||||||||||||||||||||||||||||
# Marking this as no branch, since the testbed can't create a simple | ||||||||||||||||||||||||||||||||||||||||
# window, so we can't test the other branch. | ||||||||||||||||||||||||||||||||||||||||
if self._actionbar_shown_by_default: # pragma: no branch | ||||||||||||||||||||||||||||||||||||||||
self.app.native.getSupportActionBar().hide() | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this sort of functionality not be better served by having a "show/hide action bar" method that is a no-op on Window, and implemented on MainWindow? That removes the need for a bunch of private attribute flags. |
||||||||||||||||||||||||||||||||||||||||
self._in_presentation_mode = True | ||||||||||||||||||||||||||||||||||||||||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||
# On Android Maximized state is same as the Normal state | ||||||||||||||||||||||||||||||||||||||||
if state in {WindowState.NORMAL, WindowState.MAXIMIZED}: | ||||||||||||||||||||||||||||||||||||||||
if current_state in { | ||||||||||||||||||||||||||||||||||||||||
WindowState.FULLSCREEN, | ||||||||||||||||||||||||||||||||||||||||
WindowState.PRESENTATION, | ||||||||||||||||||||||||||||||||||||||||
}: | ||||||||||||||||||||||||||||||||||||||||
decor_view.setSystemUiVisibility(0) | ||||||||||||||||||||||||||||||||||||||||
if current_state == WindowState.PRESENTATION: | ||||||||||||||||||||||||||||||||||||||||
# Marking this as no branch, since the testbed can't create a simple | ||||||||||||||||||||||||||||||||||||||||
# window, so we can't test the other branch. | ||||||||||||||||||||||||||||||||||||||||
if self._actionbar_shown_by_default: # pragma: no branch | ||||||||||||||||||||||||||||||||||||||||
self.app.native.getSupportActionBar().show() | ||||||||||||||||||||||||||||||||||||||||
self._in_presentation_mode = False | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# Complete any pending window state transition. | ||||||||||||||||||||||||||||||||||||||||
if getattr(self, "_pending_window_state_transition", None) is not None: | ||||||||||||||||||||||||||||||||||||||||
self.set_window_state(self._pending_window_state_transition) | ||||||||||||||||||||||||||||||||||||||||
del self._pending_window_state_transition | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole method is, quite literally, a state machine - and I'm having a great deal of difficulty understanding the state transitions. In general, a state machine should be structured as :
and with that structure, you should be able to clearly exhaust all possible current and future states. |
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
###################################################################### | ||||||||||||||||||||||||||||||||||||||||
# Window capabilities | ||||||||||||||||||||||||||||||||||||||||
|
@@ -160,6 +232,9 @@ def get_image_data(self): | |||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
class MainWindow(Window): | ||||||||||||||||||||||||||||||||||||||||
# ActionBar is always hidden on MainWindow. | ||||||||||||||||||||||||||||||||||||||||
_actionbar_shown_by_default = True | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def configure_titlebar(self): | ||||||||||||||||||||||||||||||||||||||||
# Display the titlebar on a MainWindow. | ||||||||||||||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
from androidx.appcompat import R as appcompat_R | ||
|
||
from toga.constants import WindowState | ||
|
||
from .dialogs import DialogsMixin | ||
from .probe import BaseProbe | ||
|
||
|
@@ -20,6 +22,22 @@ def content_size(self): | |
self.root_view.getHeight() / self.scale_factor, | ||
) | ||
|
||
def get_window_state(self): | ||
decor_view = self.native.getWindow().getDecorView() | ||
system_ui_flags = decor_view.getSystemUiVisibility() | ||
if system_ui_flags & ( | ||
decor_view.SYSTEM_UI_FLAG_FULLSCREEN | ||
| decor_view.SYSTEM_UI_FLAG_HIDE_NAVIGATION | ||
| decor_view.SYSTEM_UI_FLAG_IMMERSIVE | ||
): | ||
if self.window._impl._in_presentation_mode: | ||
current_state = WindowState.PRESENTATION | ||
else: | ||
current_state = WindowState.FULLSCREEN | ||
else: | ||
current_state = WindowState.NORMAL | ||
return current_state | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure this probe add anything. It's an literal reproduction of parts of the "real" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is still current. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have removed it. |
||
|
||
def _native_menu(self): | ||
return self.native.findViewById(appcompat_R.id.action_bar).getMenu() | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Toga apps can now detect and set their window states including maximized, minimized, normal, full screen and presentation states. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"Full screen mode" on an app has been renamed "Presentation mode" to avoid the ambiguity with "full screen mode" on a window. The ``toga.App.enter_full_screen`` and ``toga.App.exit_full_screen`` APIs have been renamed ``toga.App.enter_presentation_mode`` and ``toga.App.exit_presentation_mode``, respectively. ``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
import toga | ||
from toga.app import overridden | ||
from toga.command import Command, Separator | ||
from toga.constants import WindowState | ||
from toga.handlers import NativeHandler, simple_handler | ||
|
||
from .keys import cocoa_key | ||
|
@@ -491,33 +492,47 @@ def set_current_window(self, window): | |
window._impl.native.makeKeyAndOrderFront(window._impl.native) | ||
|
||
###################################################################### | ||
# Full screen control | ||
# Presentation mode controls | ||
###################################################################### | ||
|
||
def enter_full_screen(self, windows): | ||
def enter_presentation_mode(self, screen_window_dict): | ||
opts = NSMutableDictionary.alloc().init() | ||
opts.setObject( | ||
NSNumber.numberWithBool(True), forKey="NSFullScreenModeAllScreens" | ||
) | ||
|
||
for window, screen in zip(windows, NSScreen.screens): | ||
window.content._impl.native.enterFullScreenMode(screen, withOptions=opts) | ||
# Going full screen causes the window content to be re-homed | ||
# in a NSFullScreenWindow; teach the new parent window | ||
# about its Toga representations. | ||
for screen, window in screen_window_dict.items(): | ||
window.content._impl.native.enterFullScreenMode( | ||
screen._impl.native, withOptions=opts | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above - we try not to use the "all on one lines but indented" when there's more than one argument. Add a comma to the end of the argument list and black-format to 1 argument per line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, Thanks. |
||
) | ||
# Going presentation mode causes the window content to be re-homed | ||
# in a NSFullScreenWindow; teach the new parent window about its | ||
# Toga representations. | ||
window.content._impl.native.window._impl = window._impl | ||
window.content._impl.native.window.interface = window | ||
window.content.refresh() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like I've asked this before, but this ticket has enough history that it's not easy to find a relevant comment... Why is this logic in App, and not part of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. During the design discussion, we had discussed to keep presentation mode on the app in cocoa. But looking at it now, moving the code to window makes more sense. So, I'll move it. |
||
|
||
def exit_full_screen(self, windows): | ||
# Process any pending window state. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this logic being done here, rather than as part of |
||
if ( | ||
window._impl._pending_state_transition | ||
and window._impl._pending_state_transition != WindowState.PRESENTATION | ||
): | ||
window._impl._apply_state(WindowState.NORMAL) | ||
else: | ||
window._impl._pending_state_transition = None | ||
|
||
def exit_presentation_mode(self): | ||
opts = NSMutableDictionary.alloc().init() | ||
opts.setObject( | ||
NSNumber.numberWithBool(True), forKey="NSFullScreenModeAllScreens" | ||
) | ||
|
||
for window in windows: | ||
window.content._impl.native.exitFullScreenModeWithOptions(opts) | ||
window.content.refresh() | ||
for window in self.interface.windows: | ||
if window.state == WindowState.PRESENTATION: | ||
window.content._impl.native.exitFullScreenModeWithOptions(opts) | ||
window.content.refresh() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see how this mechanically exit presentation mode, but I don't see how this causes a change in the value of window.state. The Also - as above, it's not clear why this isn't part of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have moved it to |
||
|
||
# Process any pending window state. | ||
window._impl._apply_state(window._impl._pending_state_transition) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again - why here, instead of as part of |
||
|
||
|
||
class DocumentApp(App): # pragma: no cover | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this variable in aid of? It doesn't appear to be used anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have removed it.