Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions android/src/toga_android/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ def set_textview_font(textview, font, default_typeface, default_size):
textview.setTextSize(TypedValue.COMPLEX_UNIT_PX, font.size(default=default_size))


def set_alignment(textview, value, vertical_gravity):
# Justified text wasn't added until API level 26.
# We only run the test suite on API 31, so we need to disable branch coverage.
if Build.VERSION.SDK_INT >= 26: # pragma: no branch
textview.setJustificationMode(
Layout.JUSTIFICATION_MODE_INTER_WORD
if value == JUSTIFY
else Layout.JUSTIFICATION_MODE_NONE
)
textview.setGravity(vertical_gravity | android_text_align(value))


class TextViewWidget(Widget):
def cache_textview_defaults(self):
self._default_text_color = self.native.getCurrentTextColor()
Expand All @@ -36,16 +48,7 @@ def set_color(self, value):
self.native.setTextColor(native_color(value))

def set_textview_alignment(self, value, vertical_gravity):
# Justified text wasn't added until API level 26.
# We only run the test suite on API 31, so we need to disable branch coverage.
if Build.VERSION.SDK_INT >= 26: # pragma: no branch
self.native.setJustificationMode(
Layout.JUSTIFICATION_MODE_INTER_WORD
if value == JUSTIFY
else Layout.JUSTIFICATION_MODE_NONE
)

self.native.setGravity(vertical_gravity | android_text_align(value))
set_alignment(self.native, value, vertical_gravity)


class Label(TextViewWidget):
Expand Down
24 changes: 21 additions & 3 deletions android/src/toga_android/widgets/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from android.widget import LinearLayout, ScrollView, TableLayout, TableRow, TextView
from java import dynamic_proxy

from ..colors import native_color
from .base import Widget
from .label import set_textview_font
from .label import set_alignment, set_textview_font


class TogaOnClickListener(dynamic_proxy(View.OnClickListener)):
Expand Down Expand Up @@ -149,18 +150,35 @@ def create_table_row(self, row_index):
)
text_view = TextView(self._native_activity)
text_view.setText(toga_column.text(data_row, missing_value))
text_align = toga_column.text_align(data_row)
color = toga_column.color(data_row)
background_color = toga_column.background_color(data_row)
font = toga_column.font(data_row, self.interface.style.font)

if color is not None:
text_view.setTextColor(native_color(color))
if background_color is not None:
text_view.setBackgroundColor(native_color(background_color))
# font is only None if something is very wrong (eg. can't find system font)
# so can't test
if font is not None:
font_impl = font._impl
else: # pragma: no cover
font_impl = self._font_impl
set_textview_font(
text_view,
self._font_impl,
font_impl,
text_view.getTypeface(),
text_view.getTextSize(),
)
text_view_params = TableRow.LayoutParams(
TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT
)
text_view_params.setMargins(10, 5, 10, 5) # left, top, right, bottom
text_view_params.gravity = Gravity.START
text_view_params.gravity = Gravity.FILL_HORIZONTAL
text_view.setLayoutParams(text_view_params)
if text_align is not None:
set_alignment(text_view, text_align, Gravity.CENTER_VERTICAL)
table_row.addView(text_view)
return table_row

Expand Down
53 changes: 51 additions & 2 deletions android/tests_backend/widgets/table.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import pytest
from android.widget import ScrollView, TableLayout, TextView

from toga_android.colors import native_color
from toga_android.widgets.base import android_text_align

from .base import SimpleProbe

HEADER = "HEADER"
Expand All @@ -11,6 +14,7 @@ class TableProbe(SimpleProbe):
supports_icons = False
supports_keyboard_shortcuts = False
supports_widgets = False
supports_styles = True
column_proportion_tolerance = 35

def __init__(self, widget):
Expand All @@ -30,18 +34,63 @@ def row_count(self):
def column_count(self):
return self._row_view(HEADER).getChildCount()

def assert_cell_content(self, row, col, value=None, icon=None, widget=None):
def assert_cell_content(
self,
row,
col,
value=None,
icon=None,
widget=None,
text_align=None,
color=None,
background_color=None,
font=None,
):
if widget:
pytest.skip("This backend doesn't support widgets in Tables")
else:
assert self._cell_text(row, col) == value
if value is not None:
assert self._cell_text(row, col) == value
assert icon is None
if text_align is not None:
assert self._cell_gravity(row, col) & android_text_align(text_align)
if color is not None:
assert self._cell_color(row, col) == native_color(color)
if background_color is not None:
assert self._cell_background_color(row, col) == native_color(
background_color
)
if font is not None:
assert self._cell_font(row, col) == (
font._impl.typeface(),
font._impl.size(),
)

def _cell_text(self, row, col):
tv = self._row_view(row).getChildAt(col)
assert isinstance(tv, TextView)
return str(tv.getText())

def _cell_color(self, row, col):
tv = self._row_view(row).getChildAt(col)
assert isinstance(tv, TextView)
return tv.getCurrentTextColor()

def _cell_background_color(self, row, col):
tv = self._row_view(row).getChildAt(col)
assert isinstance(tv, TextView)
return tv.getBackground().getColor()

def _cell_gravity(self, row, col):
tv = self._row_view(row).getChildAt(col)
assert isinstance(tv, TextView)
return tv.getGravity()

def _cell_font(self, row, col):
tv = self._row_view(row).getChildAt(col)
assert isinstance(tv, TextView)
return tv.getTypeface(), tv.getTextSize()

def _row_view(self, row):
if row == HEADER:
row = 0
Expand Down
1 change: 1 addition & 0 deletions changes/4258.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The `Table` and `Tree` widgets on Cocoa and Qt, and the `Table` widget on Android, can now specify colors, fonts and text alignment to use for individual cells based on the data contained in the cell by writing an appropriate `Column` subclass.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit for this to read more smoothly:

Suggested change
The `Table` and `Tree` widgets on Cocoa and Qt, and the `Table` widget on Android, can now specify colors, fonts and text alignment to use for individual cells based on the data contained in the cell by writing an appropriate `Column` subclass.
The `Table` and `Tree` widgets on Cocoa and Qt and the `Table` widget on Android can now specify colors, fonts, or text alignment for individual cells based on their data by providing an appropriate `Column` subclass.

26 changes: 26 additions & 0 deletions cocoa/src/toga_cocoa/widgets/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
NSTableView,
NSTableViewAnimation,
NSTableViewColumnAutoresizingStyle,
NSTextAlignment,
)

from ..colors import native_color
from .base import Widget
from .internal.cells import TogaIconView

Expand Down Expand Up @@ -43,6 +45,10 @@ def tableView_viewForTableColumn_row_(self, table, column, row: int):

icon = column.toga_column.icon(data_row)
text = column.toga_column.text(data_row, self.interface.missing_value)
text_align = column.toga_column.text_align(data_row)
color = column.toga_column.color(data_row)
background_color = column.toga_column.background_color(data_row)
font = column.toga_column.font(data_row, self.interface.style.font)

# creates a NSTableCellView from interface-builder template (does not exist)
# or reuses an existing view which is currently not needed for painting
Expand All @@ -60,6 +66,26 @@ def tableView_viewForTableColumn_row_(self, table, column, row: int):
else:
tcv.setImage(None)

if text_align is not None:
tcv.textField.alignment = NSTextAlignment(text_align)
else:
tcv.textField.alignment = self.alignment

if color is not None:
tcv.textField.textColor = native_color(color)
else:
tcv.textField.textColor = None
if background_color is not None:
tcv.drawsBackground = True
tcv.backgroundColor = native_color(background_color)
else:
tcv.textField.drawsBackground = False

# font is only None if something is very wrong (eg. can't find system font)
# so can't test
if font is not None: # pragma: no branch
tcv.textField.font = font._impl.native
Comment on lines +69 to +87
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This piece of code gets thrown around a lot. Is it possible to have a set_textfield_attributes function at the top of this file that accepts all the stylistic attributes, and reuse this in tree.py?


return tcv

@objc_method
Expand Down
26 changes: 26 additions & 0 deletions cocoa/src/toga_cocoa/widgets/tree.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from rubicon.objc import SEL, at, objc_method, objc_property
from travertino.size import at_least

from toga_cocoa.colors import native_color
from toga_cocoa.libs import (
NSBezelBorder,
NSIndexSet,
Expand All @@ -9,6 +10,7 @@
NSTableColumn,
NSTableViewAnimation,
NSTableViewColumnAutoresizingStyle,
NSTextAlignment,
)
from toga_cocoa.widgets.base import Widget
from toga_cocoa.widgets.internal.cells import TogaIconView
Expand Down Expand Up @@ -64,6 +66,10 @@ def outlineView_viewForTableColumn_item_(self, tree, column, item):
return widget._impl.native
icon = column.toga_column.icon(node)
text = column.toga_column.text(node, self.interface.missing_value)
text_align = column.toga_column.text_align(node)
color = column.toga_column.color(node)
background_color = column.toga_column.background_color(node)
font = column.toga_column.font(node, self.interface.style.font)

# creates a NSTableCellView from interface-builder template (does not exist)
# or reuses an existing view which is currently not needed for painting
Expand All @@ -84,6 +90,26 @@ def outlineView_viewForTableColumn_item_(self, tree, column, item):
else:
tcv.setImage(None)

if text_align is not None:
tcv.textField.alignment = NSTextAlignment(text_align)
else:
tcv.textField.alignment = self.alignment

if color is not None:
tcv.textField.textColor = native_color(color)
else:
tcv.textField.textColor = None
if background_color is not None:
tcv.drawsBackground = True
tcv.backgroundColor = native_color(background_color)
else:
tcv.textField.drawsBackground = False

# font is only None if something is very wrong (eg. can't find system font)
# so can't test
if font is not None: # pragma: no branch
tcv.textField.font = font._impl.native

return tcv

# 2023-06-29: Commented out this method because it appears to be a
Expand Down
31 changes: 28 additions & 3 deletions cocoa/tests_backend/widgets/table.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import pytest
from rubicon.objc import NSPoint

from toga_cocoa.colors import native_color
from toga_cocoa.keys import NSEventModifierFlagCommand
from toga_cocoa.libs import NSEventType, NSScrollView, NSTableView
from toga_cocoa.libs import NSEventType, NSScrollView, NSTableView, NSTextAlignment

from .base import SimpleProbe
from .properties import toga_color
Expand All @@ -14,6 +15,7 @@ class TableProbe(SimpleProbe):
supports_keyboard_shortcuts = True
supports_keyboard_boundary_shortcuts = False
supports_widgets = True
supports_styles = True

def __init__(self, widget):
super().__init__(widget)
Expand All @@ -22,7 +24,7 @@ def __init__(self, widget):

@property
def font(self):
pytest.skip("Font changes not implemented for Tree on macOS")
pytest.skip("Font changes not implemented for Table on macOS")

@property
def background_color(self):
Expand All @@ -43,7 +45,18 @@ def row_count(self):
def column_count(self):
return len(self.native_table.tableColumns)

def assert_cell_content(self, row, col, value=None, icon=None, widget=None):
def assert_cell_content(
self,
row,
col,
value=None,
icon=None,
widget=None,
color=None,
text_align=None,
background_color=None,
font=None,
):
view = self.native_table.tableView(
self.native_table,
viewForTableColumn=self.native_table.tableColumns[col],
Expand All @@ -59,6 +72,18 @@ def assert_cell_content(self, row, col, value=None, icon=None, widget=None):
else:
assert view.imageView.image is None

if text_align:
assert view.textField.alignment == NSTextAlignment(text_align)

if color:
assert view.textField.textColor == native_color(color)

if background_color:
assert view.backgroundColor == native_color(background_color)

if font:
assert view.textField.font == font._impl.native

@property
def max_scroll_position(self):
return int(self.native.documentView.bounds.size.height) - int(
Expand Down
29 changes: 27 additions & 2 deletions cocoa/tests_backend/widgets/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from pytest import skip
from rubicon.objc import NSPoint

from toga_cocoa.colors import native_color
from toga_cocoa.keys import NSEventModifierFlagCommand
from toga_cocoa.libs import NSEventType, NSOutlineView, NSScrollView
from toga_cocoa.libs import NSEventType, NSOutlineView, NSScrollView, NSTextAlignment

from .base import SimpleProbe
from .properties import toga_color
Expand All @@ -14,6 +15,7 @@ class TreeProbe(SimpleProbe):
native_class = NSScrollView
supports_keyboard_shortcuts = True
supports_widgets = True
supports_styles = True

def __init__(self, widget):
super().__init__(widget)
Expand Down Expand Up @@ -73,7 +75,18 @@ def child_count(self, row_path=None):
def column_count(self):
return len(self.native_tree.tableColumns)

def assert_cell_content(self, row_path, col, value=None, icon=None, widget=None):
def assert_cell_content(
self,
row_path,
col,
value=None,
icon=None,
widget=None,
text_align=None,
color=None,
background_color=None,
font=None,
):
view = self.native_tree.outlineView(
self.native_tree,
viewForTableColumn=self.native_tree.tableColumns[col],
Expand All @@ -89,6 +102,18 @@ def assert_cell_content(self, row_path, col, value=None, icon=None, widget=None)
else:
assert view.imageView.image is None

if text_align:
assert view.textField.alignment == NSTextAlignment(text_align)

if color:
assert view.textField.textColor == native_color(color)

if background_color:
assert view.backgroundColor == native_color(background_color)

if font:
assert view.textField.font == font._impl.native

@property
def max_scroll_position(self):
return int(self.native.documentView.bounds.size.height) - int(
Expand Down
Loading
Loading