Skip to content

Commit

Permalink
Merge pull request #1799 from freakboy3742/audit-label
Browse files Browse the repository at this point in the history
[widget audit] toga.Label
  • Loading branch information
mhsmith authored Mar 22, 2023
2 parents fa8ae75 + 15c3515 commit a6968e8
Show file tree
Hide file tree
Showing 33 changed files with 276 additions and 180 deletions.
2 changes: 2 additions & 0 deletions android/src/toga_android/libs/android/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
PorterDuffColorFilter = JavaClass("android/graphics/PorterDuffColorFilter")
Rect = JavaClass("android/graphics/Rect")
Typeface = JavaClass("android/graphics/Typeface")

LineBreaker = JavaClass("android/graphics/text/LineBreaker")
3 changes: 3 additions & 0 deletions android/src/toga_android/libs/android/os.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from rubicon.java import JavaClass

Build = JavaClass("android/os/Build")
15 changes: 12 additions & 3 deletions android/src/toga_android/widgets/label.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from travertino.size import at_least

from toga.constants import JUSTIFY
from toga_android.colors import native_color

from ..libs.android.graphics import LineBreaker
from ..libs.android.os import Build
from ..libs.android.view import Gravity, View__MeasureSpec
from ..libs.android.widget import TextView
from .base import Widget, align
Expand All @@ -28,6 +31,9 @@ def create(self):
self.native = TextView(self._native_activity)
self.cache_textview_defaults()

def get_text(self):
return self.native.getText()

def set_text(self, value):
self.native.setText(value)

Expand All @@ -54,12 +60,15 @@ def rehint(self):
self.interface.intrinsic.width = at_least(self.native.getMeasuredWidth())

def set_alignment(self, value):
# Refuse to set alignment if create() has not been called.
if self.native is None:
return
# Refuse to set alignment if widget has no container.
# On Android, calling setGravity() when the widget has no LayoutParams
# results in a NullPointerException.
if not self.native.getLayoutParams():
return

# Justified text wasn't added until Android O (SDK 26)
if value == JUSTIFY and Build.VERSION.SDK_INT >= Build.VERSION_CODES.O:
self.native.setJustificationMode(LineBreaker.JUSTIFICATION_MODE_INTER_WORD)
else:
self.native.setJustificationMode(LineBreaker.JUSTIFICATION_MODE_NONE)
self.native.setGravity(Gravity.CENTER_VERTICAL | align(value))
17 changes: 2 additions & 15 deletions android/tests_backend/widgets/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import asyncio

from java import dynamic_proxy
from pytest import skip

from android.view import ViewTreeObserver
from toga.fonts import SYSTEM
Expand Down Expand Up @@ -48,8 +47,8 @@ def assert_container(self, container):
else:
raise AssertionError(f"cannot find {self.native} in {container_native}")

def assert_alignment_equivalent(self, actual, expected):
assert actual == expected
def assert_alignment(self, expected):
assert self.alignment == expected

def assert_font_family(self, expected):
actual = self.font.family
Expand All @@ -71,18 +70,6 @@ async def redraw(self):
def enabled(self):
return self.native.isEnabled()

@property
def background_color(self):
skip("not implemented: background_color")

@property
def color(self):
skip("not implemented: color")

@property
def hidden(self):
skip("not implemented: hidden")

@property
def width(self):
# Return the value in DP
Expand Down
7 changes: 4 additions & 3 deletions android/tests_backend/widgets/label.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from java import jclass
from pytest import skip

from .base import SimpleProbe
from .properties import toga_color, toga_font
from .properties import toga_alignment, toga_color, toga_font


class LabelProbe(SimpleProbe):
Expand Down Expand Up @@ -30,4 +29,6 @@ def font(self):

@property
def alignment(self):
skip("Alignment probe not implemented")
return toga_alignment(
self.native.getGravity(), self.native.getJustificationMode()
)
20 changes: 20 additions & 0 deletions android/tests_backend/widgets/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
from travertino.fonts import Font

from android.graphics import Color, Typeface
from android.graphics.text import LineBreaker
from android.util import TypedValue
from android.view import Gravity
from toga.colors import TRANSPARENT, rgba
from toga.constants import CENTER, JUSTIFY, LEFT, RIGHT
from toga.fonts import (
BOLD,
ITALIC,
Expand Down Expand Up @@ -62,3 +65,20 @@ def toga_font(typeface, size, resources):
variant=NORMAL,
weight=BOLD if typeface.isBold() else NORMAL,
)


def toga_alignment(gravity, justification_mode):
horizontal_gravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK
if (
justification_mode == LineBreaker.JUSTIFICATION_MODE_INTER_WORD
and horizontal_gravity == Gravity.LEFT
):
return JUSTIFY
elif justification_mode == LineBreaker.JUSTIFICATION_MODE_NONE:
return {
Gravity.LEFT: LEFT,
Gravity.RIGHT: RIGHT,
Gravity.CENTER_HORIZONTAL: CENTER,
}[horizontal_gravity]
else:
raise ValueError(f"unknown combination: {gravity=}, {justification_mode=}")
1 change: 1 addition & 0 deletions changes/1501.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
iOS now supports newlines in Labels.
1 change: 1 addition & 0 deletions changes/1799.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Label widget now has 100% test coverage, and complete API documentation.
5 changes: 4 additions & 1 deletion cocoa/src/toga_cocoa/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ def set_color(self, value):
def set_font(self, font):
self.native.font = font._impl.native

def get_text(self):
return str(self.native.stringValue)

def set_text(self, value):
self.native.stringValue = self.interface._text
self.native.stringValue = value

def rehint(self):
# Width & height of a label is known and fixed.
Expand Down
4 changes: 2 additions & 2 deletions cocoa/tests_backend/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def assert_container(self, container):
else:
raise ValueError(f"cannot find {self.native} in {container_native}")

def assert_alignment_equivalent(self, actual, expected):
assert actual == expected
def assert_alignment(self, expected):
assert self.alignment == expected

def assert_font_family(self, expected):
assert self.font.family == {
Expand Down
33 changes: 13 additions & 20 deletions core/src/toga/widgets/label.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import warnings

from .base import Widget


Expand All @@ -9,7 +7,6 @@ def __init__(
text,
id=None,
style=None,
factory=None, # DEPRECATED!
):
"""A text label.
Expand All @@ -19,35 +16,31 @@ def __init__(
:param id: The ID for the widget.
:param style: A style object. If no style is provided, a default style
will be applied to the widget.
:param factory: *Deprecated*
"""
super().__init__(id=id, style=style)

######################################################################
# 2022-09: Backwards compatibility
######################################################################
# factory no longer used
if factory:
warnings.warn("The factory argument is no longer used.", DeprecationWarning)
######################################################################
# End backwards compatibility.
######################################################################

# Create a platform specific implementation of a Label
self._impl = self.factory.Label(interface=self)

self.text = text

@property
def text(self):
"""The text displayed by the label."""
return self._text
"""The text displayed by the label.
``None``, and the Unicode codepoint U+200B (ZERO WIDTH SPACE), will be
interpreted and returned as an empty string. Any other object will be
converted to a string using ``str()``.
"""
return self._impl.get_text()

@text.setter
def text(self, value):
if value is None:
self._text = ""
if value is None or value == "\u200B":
text = ""
else:
self._text = str(value)
self._impl.set_text(value)
text = str(value)

self._impl.set_text(text)
self.refresh()
6 changes: 0 additions & 6 deletions core/tests/test_deprecated_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,6 @@ def test_image_view_created(self):
self.assertEqual(widget._impl.interface, widget)
self.assertNotEqual(widget.factory, self.factory)

def test_label_created(self):
with self.assertWarns(DeprecationWarning):
widget = toga.Label("Test", factory=self.factory)
self.assertEqual(widget._impl.interface, widget)
self.assertNotEqual(widget.factory, self.factory)

def test_multiline_text_input_created(self):
with self.assertWarns(DeprecationWarning):
widget = toga.MultilineTextInput(factory=self.factory)
Expand Down
55 changes: 37 additions & 18 deletions core/tests/widgets/test_label.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,46 @@
import pytest

import toga
from toga_dummy.utils import TestCase
from toga_dummy.utils import (
EventLog,
assert_action_performed,
attribute_value,
)


@pytest.fixture
def label():
return toga.Label("Test Label")


class LabelTests(TestCase):
def setUp(self):
super().setUp()
def test_label_created(label):
"A label can be created."
# Round trip the impl/interface
assert label._impl.interface == label
assert_action_performed(label, "create Label")

self.text = "test text"

self.label = toga.Label(self.text)
@pytest.mark.parametrize(
"value, expected",
[
("New Text", "New Text"),
(12345, "12345"),
(None, ""),
("\u200B", ""),
("Contains\nsome\nnewlines", "Contains\nsome\nnewlines"),
],
)
def test_update_label_text(label, value, expected):
assert label.text == "Test Label"

def test_widget_created(self):
self.assertEqual(self.label._impl.interface, self.label)
self.assertActionPerformed(self.label, "create Label")
# Clear the event log
EventLog.reset()

def test_update_label_text(self):
new_text = "updated text"
self.label.text = new_text
self.assertEqual(self.label.text, new_text)
self.assertValueSet(self.label, "text", new_text)
self.assertActionPerformed(self.label, "refresh")
label.text = value
assert label.text == expected

self.label.text = None
self.assertEqual(self.label.text, "")
# test backend has the right value
assert attribute_value(label, "text") == expected

self.assertValueSet(self.label, "text", "")
# A rehint was performed
assert_action_performed(label, "refresh")
2 changes: 1 addition & 1 deletion docs/reference/api/widgets/button.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ A button has a text label. A handler can be associated with button press events.
# handle event
pass
button = toga.Button('Click me', on_press=my_callback)
button = toga.Button("Click me", on_press=my_callback)
Notes
-----
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/api/widgets/label.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Usage
import toga
label = toga.Label('Hello world')
label = toga.Label("Hello world")
Notes
-----
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/data/widgets_by_platform.csv
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ DatePicker,General Widget,:class:`~toga.widgets.datepicker.DatePicker`,An input
DetailedList,General Widget,:class:`~toga.widgets.detailedlist.DetailedList`,A list of complex content,|b|,|b|,,|b|,|b|,
Divider,General Widget,:class:`~toga.widgets.divider.Divider`,A horizontal or vertical line,|b|,|b|,|b|,,,
ImageView,General Widget,:class:`~toga.widgets.imageview.ImageView`,Image Viewer,|b|,|b|,|b|,|b|,|b|,
Label,General Widget,:class:`~toga.widgets.label.Label`,Text label,|b|,|b|,|b|,|b|,|b|,|b|
Label,General Widget,:class:`~toga.widgets.label.Label`,Text label,|y|,|y|,|y|,|y|,|y|,|b|
MultilineTextInput,General Widget,:class:`~toga.widgets.multilinetextinput.MultilineTextInput`,Multi-line Text Input field,|b|,|b|,|b|,|b|,|b|,
NumberInput,General Widget,:class:`~toga.widgets.numberinput.NumberInput`,Number Input field,|b|,|b|,|b|,|b|,|b|,
PasswordInput,General Widget,:class:`~toga.widgets.passwordinput.PasswordInput`,A text input that hides it’s input,|b|,|b|,|b|,|b|,|b|,
Expand Down
5 changes: 4 additions & 1 deletion dummy/src/toga_dummy/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ def create(self):
def set_alignment(self, value):
self._set_value("alignment", value)

def get_text(self):
return self._get_value("text")

def set_text(self, value):
self._set_value("text", self.interface._text)
self._set_value("text", value)
14 changes: 11 additions & 3 deletions gtk/src/toga_gtk/fonts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
from toga.constants import BOLD, ITALIC, OBLIQUE, SMALL_CAPS, SYSTEM
from toga.constants import (
BOLD,
ITALIC,
OBLIQUE,
SMALL_CAPS,
SYSTEM,
SYSTEM_DEFAULT_FONT_SIZE,
)

from .libs import Pango

Expand Down Expand Up @@ -27,8 +34,9 @@ def __init__(self, interface):

font.set_family(family)

# Set font size
font.set_size(self.interface.size * Pango.SCALE)
# If this is a non-default font size, set the font size
if self.interface.size != SYSTEM_DEFAULT_FONT_SIZE:
font.set_size(self.interface.size * Pango.SCALE)

# Set font style
if self.interface.style == ITALIC:
Expand Down
1 change: 0 additions & 1 deletion gtk/src/toga_gtk/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ def create(self):
self.native.get_style_context().add_class("toga")
self.native.interface = self.interface

self.native.connect("show", lambda event: self.refresh())
self.native.connect("clicked", self.gtk_on_press)

def get_text(self):
Expand Down
Loading

0 comments on commit a6968e8

Please sign in to comment.