Skip to content

Commit

Permalink
Merge pull request #59 from regrainb/master
Browse files Browse the repository at this point in the history
PR: Add the support for QRadialGradient
  • Loading branch information
ccordoba12 authored Oct 6, 2022
2 parents a4b13fa + 6a25afe commit 01bb740
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 7 deletions.
8 changes: 6 additions & 2 deletions qtsass/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

# Local imports
from qtsass.conformers import qt_conform, scss_conform
from qtsass.functions import qlineargradient, rgba
from qtsass.functions import qlineargradient, qradialgradient, rgba
from qtsass.importers import qss_importer


Expand All @@ -35,7 +35,11 @@
# yapf: enable

# Constants
DEFAULT_CUSTOM_FUNCTIONS = {'qlineargradient': qlineargradient, 'rgba': rgba}
DEFAULT_CUSTOM_FUNCTIONS = {
'qlineargradient': qlineargradient,
'qradialgradient': qradialgradient,
'rgba': rgba
}
DEFAULT_SOURCE_COMMENTS = False

# Logger setup
Expand Down
118 changes: 114 additions & 4 deletions qtsass/conformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

# yapf: enable

_DEFAULT_COORDS = ('x1', 'y1', 'x2', 'y2')


class Conformer(object):
"""Base class for all text transformations."""
Expand Down Expand Up @@ -48,6 +46,8 @@ def to_qss(self, css):
class QLinearGradientConformer(Conformer):
"""Conform QSS qlineargradient function."""

_DEFAULT_COORDS = ('x1', 'y1', 'x2', 'y2')

qss_pattern = re.compile(
r'qlineargradient\('
r'((?:(?:\s+)?(?:x1|y1|x2|y2):(?:\s+)?[0-9A-Za-z$_\.-]+,?)+)' # coords
Expand All @@ -68,8 +68,8 @@ def _conform_coords_to_scss(self, group):
try:
key, value = key_values
key = key.strip()
if key in _DEFAULT_COORDS:
pos = _DEFAULT_COORDS.index(key)
if key in self._DEFAULT_COORDS:
pos = self._DEFAULT_COORDS.index(key)
if pos >= 0 and pos <= 3:
values[pos] = value.strip()
except ValueError:
Expand Down Expand Up @@ -130,6 +130,116 @@ def to_qss(self, css):
return css


class QRadialGradientConformer(Conformer):
"""Conform QSS qradialgradient function."""

_DEFAULT_COORDS = ('cx', 'cy', 'radius', 'fx', 'fy')

qss_pattern = re.compile(
r'qradialgradient\('
# spread
r'((?:(?:\s+)?(?:spread):(?:\s+)?[0-9A-Za-z$_\.-]+,?)+)?'
# coords
r'((?:(?:\s+)?(?:cx|cy|radius|fx|fy):(?:\s+)?[0-9A-Za-z$_\.-]+,?)+)'
# stops
r'((?:(?:\s+)?stop:.*,?)+(?:\s+)?)?'
r'\)',
re.MULTILINE,
)

def _conform_spread_to_scss(self, group):
"""
Take a qss str with xy coords and returns the values.
'spread: pad|repeat|reflect'
"""
value = 'pad'
for key_values in [part.split(':', 1) for part in group.split(',')]:
try:
key, value = key_values
key = key.strip()
if key == 'spread':
value = value.strip()
except ValueError:
pass
return value

def _conform_coords_to_scss(self, group):
"""
Take a qss str with xy coords and returns the values.
'cx: 0, cy: 0, radius: 0, fx: 0, fy: 0' => '0, 0, 0, 0, 0'
'cy: 1' => '0, 1, 0, 0, 0'
"""
values = ['0', '0', '0', '0', '0']
for key_values in [part.split(':', 1) for part in group.split(',')]:
try:
key, value = key_values
key = key.strip()
if key in self._DEFAULT_COORDS:
pos = self._DEFAULT_COORDS.index(key)
if pos >= 0:
values[pos] = value.strip()
except ValueError:
pass
return ', '.join(values)

def _conform_stops_to_scss(self, group):
"""
Take a qss str with stops and returns the values.
'stop: 0 red, stop: 1 blue' => '0 red, 1 blue'
"""
new_group = []
split = [""]
bracket_level = 0
for char in group:
if not bracket_level and char == ",":
split.append("")
continue
elif char == "(":
bracket_level += 1
elif char == ")":
bracket_level -= 1
split[-1] += char

for part in split:
if part:
_, value = part.split(':', 1)
new_group.append(value.strip())
return ', '.join(new_group)

def to_scss(self, qss):
"""
Conform qss qradialgradient to scss qradialgradient form.
Normalize all whitespace including the removal of newline chars.
qradialgradient(cx: 0, cy: 0, radius: 0,
fx: 0, fy: 0, stop: 0 red, stop: 1 blue)
=>
qradialgradient(0, 0, 0, 0, 0, (0 red, 1 blue))
"""
conformed = qss

for spread, coords, stops in self.qss_pattern.findall(qss):
new_spread = "'" + self._conform_spread_to_scss(spread) + "', "
conformed = conformed.replace(spread, new_spread, 1)
new_coords = self._conform_coords_to_scss(coords)
conformed = conformed.replace(coords, new_coords, 1)
if not stops:
continue

new_stops = ', ({})'.format(self._conform_stops_to_scss(stops))
conformed = conformed.replace(stops, new_stops, 1)

return conformed

def to_qss(self, css):
"""Transform to qss from css."""
return css


conformers = [c() for c in Conformer.__subclasses__() if c is not Conformer]


Expand Down
27 changes: 27 additions & 0 deletions qtsass/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,30 @@ def qlineargradient(x1, y1, x2, y2, stops):
template = 'qlineargradient(x1: {}, y1: {}, x2: {}, y2: {}, {})'
return template.format(x1.value, y1.value, x2.value, y2.value,
', '.join(stops_str))


def qradialgradient(spread, cx, cy, radius, fx, fy, stops):
"""
Implement qss qradialgradient function for scss.
:type spread: string
:type cx: sass.SassNumber
:type cy: sass.SassNumber
:type radius: sass.SassNumber
:type fx: sass.SassNumber
:type fy: sass.SassNumber
:type stops: sass.SassList
:return:
"""
stops_str = []
for stop in stops[0]:
pos, color = stop[0]
stops_str.append('stop: {} {}'.format(
pos.value,
rgba_from_color(color),
))
template = ('qradialgradient('
'spread: {}, cx: {}, cy: {}, radius: {}, fx: {}, fy: {}, {}'
')')
return template.format(spread, cx.value, cy.value, radius.value, fx.value,
fy.value, ', '.join(stops_str))
14 changes: 14 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@
);
}
"""
QRADIANTGRADIENTS_STR = """
QWidget {
background: qradialgradient(
spread: repeat,
cx: 0,
cy: 0,
fx: 0,
fy: 1,
stop: 0.1 blue,
stop: 0.8 green
);
}
"""
QNOT_STR = """
QLineEdit:!editable {
background: white;
Expand Down Expand Up @@ -71,6 +84,7 @@ def test_compile_strings():

qtsass.compile(COLORS_STR)
qtsass.compile(QLINEARGRADIENTS_STR)
qtsass.compile(QRADIANTGRADIENTS_STR)
qtsass.compile(QNOT_STR)


Expand Down
119 changes: 118 additions & 1 deletion tests/test_conformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
import unittest

# Local imports
from qtsass.conformers import NotConformer, QLinearGradientConformer
from qtsass.conformers import (
NotConformer,
QLinearGradientConformer,
QRadialGradientConformer,
)


class TestNotConformer(unittest.TestCase):
Expand Down Expand Up @@ -155,5 +159,118 @@ def test_float_coords(self):
self.css_float_coords_str)


class TestQRadialGradientConformer(unittest.TestCase):

css_vars_str = "qradialgradient('$spread', $cx, $cy, $radius, $fx, $fy, (0 $red, 1 $blue))"
qss_vars_str = (
'qradialgradient(spread:$spread, cx:$cx, cy:$cy, radius:$radius, fx:$fx, fy:$fy,'
'stop: 0 $red, stop: 1 $blue)'
)

css_nostops_str = "qradialgradient('pad', 0, 0, 0, 0, 0)"
qss_nostops_str = 'qradialgradient(spread: pad, cx: 0, cy: 0, fx: 0, fy: 0)'

css_str = "qradialgradient('pad', 0, 0, 0, 0, 0, (0 red, 1 blue))"
qss_singleline_str = (
'qradialgradient(spread: pad, cx: 0, cy: 0, fx: 0, fy: 0, '
'stop: 0 red, stop: 1 blue)'
)
qss_multiline_str = dedent("""
qradialgradient(
spread: pad,
cx: 0,
cy: 0,
fx: 0,
fy: 0,
stop: 0 red,
stop: 1 blue
)
""").strip()
qss_weird_whitespace_str = (
'qradialgradient( spread: pad, cx: 0, cy:0, fx: 0, fy:0, '
' stop:0 red, stop: 1 blue )'
)

css_rgba_str = (
"qradialgradient('pad', 0, 0, 0, 0, 0, "
"(0 rgba(0, 1, 2, 30%), 0.99 rgba(7, 8, 9, 100%)))"
)
qss_rgba_str = (
'qradialgradient(spread: pad, cx: 0, cy: 0, fx: 0, fy: 0, '
'stop: 0 rgba(0, 1, 2, 30%), stop: 0.99 rgba(7, 8, 9, 100%))'
)

css_incomplete_coords_str = (
"qradialgradient('pad', 0, 1, 0, 0, 0, (0 red, 1 blue))"
)

qss_incomplete_coords_str = (
'qradialgradient(spread:pad, cy:1, stop:0 red, stop: 1 blue)'
)

css_float_coords_str = (
"qradialgradient('pad', 0, 0.75, 0, 0, 0, (0 green, 1 pink))"
)

qss_float_coords_str = (
'qradialgradient(spread: pad, cy:0.75, stop:0 green, stop: 1 pink)'
)

def test_does_not_affect_css_form(self):
"""QRadialGradientConformer no affect on css qradialgradient func."""

c = QRadialGradientConformer()
self.assertEqual(c.to_scss(self.css_str), self.css_str)
self.assertEqual(c.to_qss(self.css_str), self.css_str)

def test_conform_singleline_str(self):
"""QRadialGradientConformer singleline qss to scss."""

c = QRadialGradientConformer()
self.assertEqual(c.to_scss(self.qss_singleline_str), self.css_str)

def test_conform_multiline_str(self):
"""QRadialGradientConformer multiline qss to scss."""

c = QRadialGradientConformer()
self.assertEqual(c.to_scss(self.qss_multiline_str), self.css_str)

def test_conform_weird_whitespace_str(self):
"""QRadialGradientConformer weird whitespace qss to scss."""

c = QRadialGradientConformer()
self.assertEqual(c.to_scss(self.qss_weird_whitespace_str), self.css_str)

def test_conform_nostops_str(self):
"""QRadialGradientConformer qss with no stops to scss."""

c = QRadialGradientConformer()
self.assertEqual(c.to_scss(self.qss_nostops_str), self.css_nostops_str)

def test_conform_vars_str(self):
"""QRadialGradientConformer qss with vars to scss."""

c = QRadialGradientConformer()
self.assertEqual(c.to_scss(self.qss_vars_str), self.css_vars_str)

def test_conform_rgba_str(self):
"""QRadialGradientConformer qss with rgba to scss."""

c = QRadialGradientConformer()
self.assertEqual(c.to_scss(self.qss_rgba_str), self.css_rgba_str)

def test_incomplete_coords(self):
"""QRadialGradientConformer qss with not all 4 coordinates given."""

c = QRadialGradientConformer()
self.assertEqual(c.to_scss(self.qss_incomplete_coords_str),
self.css_incomplete_coords_str)

def test_float_coords(self):
c = QRadialGradientConformer()
self.assertEqual(c.to_scss(self.qss_float_coords_str),
self.css_float_coords_str)


if __name__ == "__main__":
unittest.main(verbosity=2)
16 changes: 16 additions & 0 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,21 @@ def test_rgba(self):
)


class TestQRadialGradientFunc(BaseCompileTest):
def test_color(self):
self.assertEqual(
self.compile_scss('qradialgradient(pad, 1, 2, 1, 3, 4, (0 red, 1 blue))'),
'qradialgradient(spread: pad, cx: 1.0, cy: 2.0, radius: 1.0, fx: 3.0, fy: 4.0, '
'stop: 0.0 rgba(255, 0, 0, 100%), stop: 1.0 rgba(0, 0, 255, 100%))'
)

def test_rgba(self):
self.assertEqual(
self.compile_scss('qradialgradient(pad, 1, 2, 1, 3, 4, (0 red, 0.2 rgba(5, 6, 7, 0.8)))'),
'qradialgradient(spread: pad, cx: 1.0, cy: 2.0, radius: 1.0, fx: 3.0, fy: 4.0, '
'stop: 0.0 rgba(255, 0, 0, 100%), stop: 0.2 rgba(5, 6, 7, 80%))'
)


if __name__ == "__main__":
unittest.main(verbosity=2)

0 comments on commit 01bb740

Please sign in to comment.