Merge commit 'a743ad9496701894406c0d7ded6a44fcecd4219e' as 'deps/QDark'
This commit is contained in:
5
deps/QDark/qdarkstyle/utils/__init__.py
vendored
Executable file
5
deps/QDark/qdarkstyle/utils/__init__.py
vendored
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Utilities for processing SASS and images from default and custom palette.
|
||||
"""
|
286
deps/QDark/qdarkstyle/utils/images.py
vendored
Executable file
286
deps/QDark/qdarkstyle/utils/images.py
vendored
Executable file
@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Utilities to process and convert svg images to png using palette colors.
|
||||
"""
|
||||
|
||||
# Standard library imports
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
# Third party imports
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtGui import QIcon
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
# Local imports
|
||||
from qdarkstyle import (IMAGES_PATH, STYLES_SCSS_FILEPATH, QRC_FILEPATH, RC_PATH,
|
||||
SVG_PATH)
|
||||
from qdarkstyle.palette import DarkPalette
|
||||
|
||||
IMAGE_BLACKLIST = ['base_palette']
|
||||
|
||||
TEMPLATE_QRC_HEADER = '''
|
||||
<RCC warning="File created programmatically. All changes made in this file will be lost!">
|
||||
<qresource prefix="{resource_prefix}">
|
||||
'''
|
||||
|
||||
TEMPLATE_QRC_FILE = ' <file>rc/{fname}</file>'
|
||||
|
||||
TEMPLATE_QRC_FOOTER = '''
|
||||
</qresource>
|
||||
<qresource prefix="{style_prefix}">
|
||||
<file>style.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
'''
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_file_color_map(fname, palette):
|
||||
"""
|
||||
Return map of files (i.e states) to color from given palette.
|
||||
"""
|
||||
color_disabled = palette.COLOR_BACKGROUND_NORMAL
|
||||
color_focus = palette.COLOR_SELECTION_LIGHT
|
||||
color_pressed = palette.COLOR_SELECTION_NORMAL
|
||||
color_normal = palette.COLOR_FOREGROUND_DARK
|
||||
|
||||
name, ext = fname.split('.')
|
||||
|
||||
files_map = {
|
||||
fname: {
|
||||
fname: color_normal,
|
||||
name + '_disabled.' + ext: color_disabled,
|
||||
name + '_focus.' + ext: color_focus,
|
||||
name + '_pressed.' + ext: color_pressed,
|
||||
}
|
||||
}
|
||||
|
||||
for f, file_colors in files_map.items():
|
||||
if f == fname:
|
||||
break
|
||||
|
||||
assert file_colors
|
||||
|
||||
return file_colors
|
||||
|
||||
|
||||
def _create_colored_svg(svg_path, temp_svg_path, color):
|
||||
"""
|
||||
Replace base svg with fill color.
|
||||
"""
|
||||
with open(svg_path, 'r') as fh:
|
||||
data = fh.read()
|
||||
|
||||
base_color = '#ff0000' # Hardcoded in base svg files
|
||||
new_data = data.replace(base_color, color)
|
||||
|
||||
with open(temp_svg_path, 'w') as fh:
|
||||
fh.write(new_data)
|
||||
|
||||
|
||||
def convert_svg_to_png(svg_path, png_path, height, width):
|
||||
"""
|
||||
Convert svg files to png files using Qt.
|
||||
"""
|
||||
size = QSize(height, width)
|
||||
icon = QIcon(svg_path)
|
||||
pixmap = icon.pixmap(size)
|
||||
img = pixmap.toImage()
|
||||
img.save(png_path)
|
||||
|
||||
|
||||
def create_palette_image(base_svg_path=SVG_PATH, path=IMAGES_PATH,
|
||||
palette=DarkPalette):
|
||||
"""
|
||||
Create palette image svg and png image on specified path.
|
||||
"""
|
||||
# Needed to use QPixmap
|
||||
_ = QApplication([])
|
||||
|
||||
base_palette_svg_path = os.path.join(base_svg_path, 'base_palette.svg')
|
||||
palette_svg_path = os.path.join(path, 'palette.svg')
|
||||
palette_png_path = os.path.join(path, 'palette.png')
|
||||
|
||||
_logger.info("Creating palette image ...")
|
||||
_logger.info("Base SVG: %s" % base_palette_svg_path)
|
||||
_logger.info("To SVG: %s" % palette_svg_path)
|
||||
_logger.info("To PNG: %s" % palette_png_path)
|
||||
|
||||
with open(base_palette_svg_path, 'r') as fh:
|
||||
data = fh.read()
|
||||
|
||||
color_palette = palette.color_palette()
|
||||
|
||||
for color_name, color_value in color_palette.items():
|
||||
data = data.replace('{{ ' + color_name + ' }}', color_value.lower())
|
||||
|
||||
with open(palette_svg_path, 'w+') as fh:
|
||||
fh.write(data)
|
||||
|
||||
convert_svg_to_png(palette_svg_path, palette_png_path, 4000, 4000)
|
||||
|
||||
return palette_svg_path, palette_png_path
|
||||
|
||||
|
||||
def create_images(base_svg_path=SVG_PATH, rc_path=RC_PATH,
|
||||
palette=DarkPalette):
|
||||
"""Create resources `rc` png image files from base svg files and palette.
|
||||
|
||||
Search all SVG files in `base_svg_path` excluding IMAGE_BLACKLIST,
|
||||
change its colors using `palette` creating temporary SVG files, for each
|
||||
state generating PNG images for each size `heights`.
|
||||
|
||||
Args:
|
||||
base_svg_path (str, optional): [description]. Defaults to SVG_PATH.
|
||||
rc_path (str, optional): [description]. Defaults to RC_PATH.
|
||||
palette (DarkPalette, optional): Palette . Defaults to DarkPalette.
|
||||
"""
|
||||
|
||||
# Needed to use QPixmap
|
||||
_ = QApplication([])
|
||||
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
svg_fnames = [f for f in os.listdir(base_svg_path) if f.endswith('.svg')]
|
||||
base_height = 32
|
||||
|
||||
# See: https://doc.qt.io/qt-5/scalability.html
|
||||
heights = {
|
||||
32: '.png',
|
||||
64: '@2x.png',
|
||||
}
|
||||
|
||||
_logger.info("Creating images ...")
|
||||
_logger.info("SVG folder: %s" % base_svg_path)
|
||||
_logger.info("TMP folder: %s" % temp_dir)
|
||||
_logger.info("PNG folder: %s" % rc_path)
|
||||
|
||||
num_svg = len(svg_fnames)
|
||||
num_png = 0
|
||||
num_ignored = 0
|
||||
|
||||
# Get rc links from scss to check matches
|
||||
rc_list = get_rc_links_from_scss()
|
||||
num_rc_list = len(rc_list)
|
||||
|
||||
for height, ext in heights.items():
|
||||
width = height
|
||||
|
||||
_logger.debug(" Size HxW (px): %s X %s" % (height, width))
|
||||
|
||||
for svg_fname in svg_fnames:
|
||||
svg_name = svg_fname.split('.')[0]
|
||||
|
||||
# Skip blacklist
|
||||
if svg_name not in IMAGE_BLACKLIST:
|
||||
svg_path = os.path.join(base_svg_path, svg_fname)
|
||||
color_files = _get_file_color_map(svg_fname, palette=palette)
|
||||
|
||||
_logger.debug(" Working on: %s"
|
||||
% os.path.basename(svg_fname))
|
||||
|
||||
# Replace colors and create all file for different states
|
||||
for color_svg_name, color in color_files.items():
|
||||
temp_svg_path = os.path.join(temp_dir, color_svg_name)
|
||||
_create_colored_svg(svg_path, temp_svg_path, color)
|
||||
|
||||
png_fname = color_svg_name.replace('.svg', ext)
|
||||
png_path = os.path.join(rc_path, png_fname)
|
||||
convert_svg_to_png(temp_svg_path, png_path, height, width)
|
||||
num_png += 1
|
||||
_logger.debug(" Creating: %s"
|
||||
% os.path.basename(png_fname))
|
||||
|
||||
# Check if the rc_name is in the rc_list from scss
|
||||
# only for the base size
|
||||
if height == base_height:
|
||||
rc_base = os.path.basename(rc_path)
|
||||
png_base = os.path.basename(png_fname)
|
||||
rc_name = '/' + os.path.join(rc_base, png_base)
|
||||
try:
|
||||
rc_list.remove(rc_name)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
num_ignored += 1
|
||||
_logger.debug(" Ignored blacklist: %s"
|
||||
% os.path.basename(svg_fname))
|
||||
|
||||
_logger.info("# SVG files: %s" % num_svg)
|
||||
_logger.info("# SVG ignored: %s" % num_ignored)
|
||||
_logger.info("# PNG files: %s" % num_png)
|
||||
_logger.info("# RC links: %s" % num_rc_list)
|
||||
_logger.info("# RC links not in RC: %s" % len(rc_list))
|
||||
_logger.info("RC links not in RC: %s" % rc_list)
|
||||
|
||||
|
||||
def generate_qrc_file(resource_prefix='qss_icons', style_prefix='qdarkstyle'):
|
||||
"""
|
||||
Generate the QRC file programmaticaly.
|
||||
|
||||
Search all RC folder for PNG images and create a QRC file.
|
||||
|
||||
Args:
|
||||
resource_prefix (str, optional): Prefix used in resources.
|
||||
Defaults to 'qss_icons'.
|
||||
style_prefix (str, optional): Prefix used to this style.
|
||||
Defaults to 'qdarkstyle'.
|
||||
"""
|
||||
|
||||
files = []
|
||||
|
||||
_logger.info("Generating QRC file ...")
|
||||
_logger.info("Resource prefix: %s" % resource_prefix)
|
||||
_logger.info("Style prefix: %s" % style_prefix)
|
||||
|
||||
_logger.info("Searching in: %s" % RC_PATH)
|
||||
|
||||
# Search by png images
|
||||
for fname in sorted(os.listdir(RC_PATH)):
|
||||
files.append(TEMPLATE_QRC_FILE.format(fname=fname))
|
||||
|
||||
# Join parts
|
||||
qrc_content = (TEMPLATE_QRC_HEADER.format(resource_prefix=resource_prefix)
|
||||
+ '\n'.join(files)
|
||||
+ TEMPLATE_QRC_FOOTER.format(style_prefix=style_prefix))
|
||||
|
||||
_logger.info("Writing in: %s" % QRC_FILEPATH)
|
||||
|
||||
# Write qrc file
|
||||
with open(QRC_FILEPATH, 'w') as fh:
|
||||
fh.write(qrc_content)
|
||||
|
||||
|
||||
def get_rc_links_from_scss(pattern=r"\/.*\.png"):
|
||||
"""
|
||||
Get all rc links from scss file returning the list of unique links.
|
||||
|
||||
Args:
|
||||
pattern (str): regex pattern to find the links.
|
||||
|
||||
Returns:
|
||||
list(str): list of unique links found.
|
||||
"""
|
||||
|
||||
with open(STYLES_SCSS_FILEPATH, 'r') as fh:
|
||||
data = fh.read()
|
||||
|
||||
lines = data.split("\n")
|
||||
compiled_exp = re.compile('(' + pattern + ')')
|
||||
|
||||
rc_list = []
|
||||
|
||||
for line in lines:
|
||||
match = re.search(compiled_exp, line)
|
||||
if match:
|
||||
rc_list.append(match.group(1))
|
||||
|
||||
rc_list = list(set(rc_list))
|
||||
|
||||
return rc_list
|
277
deps/QDark/qdarkstyle/utils/scss.py
vendored
Normal file
277
deps/QDark/qdarkstyle/utils/scss.py
vendored
Normal file
@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Utilities for compiling SASS files."""
|
||||
|
||||
# Standard library imports
|
||||
import keyword
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# Third party imports
|
||||
import qtsass
|
||||
|
||||
# Local imports
|
||||
from qdarkstyle import (MAIN_SCSS_FILE, MAIN_SCSS_FILEPATH, QSS_PATH,
|
||||
QSS_FILEPATH, RC_PATH, QSS_FILE,
|
||||
VARIABLES_SCSS_FILE, VARIABLES_SCSS_FILEPATH)
|
||||
from qdarkstyle.palette import DarkPalette
|
||||
from qdarkstyle.utils.images import create_images, create_palette_image
|
||||
|
||||
# Constants
|
||||
PY2 = sys.version[0] == '2'
|
||||
|
||||
HEADER_SCSS = '''// ---------------------------------------------------------------------------
|
||||
//
|
||||
// File created programmatically
|
||||
//
|
||||
// The definitions are in the "qdarkstyle.palette" module
|
||||
//
|
||||
// WARNING! All changes made in this file will be lost!
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
'''
|
||||
|
||||
HEADER_QSS = '''/* ---------------------------------------------------------------------------
|
||||
|
||||
Created by the qtsass compiler v{}
|
||||
|
||||
The definitions are in the "qdarkstyle.qss._styles.scss" module
|
||||
|
||||
WARNING! All changes made in this file will be lost!
|
||||
|
||||
--------------------------------------------------------------------------- */
|
||||
'''
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _dict_to_scss(data):
|
||||
"""Create a scss variables string from a dict."""
|
||||
lines = []
|
||||
template = "${}: {};"
|
||||
for key, value in data.items():
|
||||
line = template.format(key, value)
|
||||
lines.append(line)
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def _scss_to_dict(string):
|
||||
"""Parse variables and return a dict."""
|
||||
data = {}
|
||||
lines = string.split('\n')
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if line and line.startswith('$'):
|
||||
key, value = line.split(':')
|
||||
key = key[1:].strip()
|
||||
key = key.replace('-', '_')
|
||||
value = value.split(';')[0].strip()
|
||||
|
||||
data[key] = value
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def _create_scss_variables(variables_scss_filepath, palette,
|
||||
header=HEADER_SCSS):
|
||||
"""Create a scss variables file."""
|
||||
scss = _dict_to_scss(palette.to_dict())
|
||||
data = header + scss + '\n'
|
||||
|
||||
with open(variables_scss_filepath, 'w') as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
def _create_qss(main_scss_path, qss_filepath, header=HEADER_QSS):
|
||||
"""Create a styles.qss file from qtsass."""
|
||||
data = ''
|
||||
|
||||
qtsass.compile_filename(main_scss_path, qss_filepath,
|
||||
output_style='expanded')
|
||||
|
||||
with open(qss_filepath, 'r') as f:
|
||||
data = f.read()
|
||||
|
||||
data = header.format(qtsass.__version__) + data
|
||||
|
||||
with open(qss_filepath, 'w') as f:
|
||||
f.write(data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def create_qss(qss_filepath=QSS_FILEPATH, main_scss_filepath=MAIN_SCSS_FILEPATH,
|
||||
variables_scss_filepath=VARIABLES_SCSS_FILEPATH,
|
||||
palette=DarkPalette):
|
||||
"""Create variables files and run qtsass compilation."""
|
||||
_create_scss_variables(variables_scss_filepath, palette)
|
||||
stylesheet = _create_qss(main_scss_filepath, qss_filepath)
|
||||
|
||||
return stylesheet
|
||||
|
||||
|
||||
def is_identifier(name):
|
||||
"""Check that `name` string is a valid identifier in Python."""
|
||||
if PY2:
|
||||
is_not_keyword = name not in keyword.kwlist
|
||||
pattern = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)
|
||||
matches_pattern = bool(pattern.match(name))
|
||||
check = is_not_keyword and matches_pattern
|
||||
else:
|
||||
check = name.isidentifier()
|
||||
|
||||
return check
|
||||
|
||||
|
||||
def create_custom_qss(
|
||||
name,
|
||||
path,
|
||||
color_background_light,
|
||||
color_background_normal,
|
||||
color_background_dark,
|
||||
color_foreground_light,
|
||||
color_foreground_normal,
|
||||
color_foreground_dark,
|
||||
color_selection_light,
|
||||
color_selection_normal,
|
||||
color_selection_dark,
|
||||
border_radius,
|
||||
):
|
||||
"""
|
||||
Create a custom palette based on the parameters defined.
|
||||
|
||||
The `name` must be a valid Python identifier and will be stored
|
||||
as a lowercased folder (even if the identifier had uppercase letters).
|
||||
|
||||
This fuction returns the custom stylesheet pointing to resources stored at
|
||||
.../path/name/.
|
||||
"""
|
||||
stylesheet = ''
|
||||
|
||||
# Check if name is valid
|
||||
if is_identifier(name):
|
||||
name = name if name[0].isupper() else name.capitalize()
|
||||
else:
|
||||
raise Exception('The custom palette name must be a valid Python '
|
||||
'identifier!')
|
||||
|
||||
# Copy resources folder
|
||||
rc_loc = os.path.basename(RC_PATH)
|
||||
qss_loc = os.path.basename(QSS_PATH)
|
||||
theme_root_path = os.path.join(path, name.lower())
|
||||
theme_rc_path = os.path.join(theme_root_path, rc_loc)
|
||||
|
||||
if os.path.isdir(theme_root_path):
|
||||
shutil.rmtree(theme_root_path)
|
||||
|
||||
shutil.copytree(RC_PATH, theme_rc_path)
|
||||
|
||||
# Copy QSS folder and contents
|
||||
theme_qss_path = os.path.join(theme_root_path, qss_loc)
|
||||
|
||||
if os.path.isdir(theme_qss_path):
|
||||
os.removedirs(theme_qss_path)
|
||||
|
||||
shutil.copytree(QSS_PATH, theme_qss_path)
|
||||
|
||||
# Create custom palette
|
||||
custom_palette = type(name, (DarkPalette, ), {})
|
||||
custom_palette.COLOR_BACKGROUND_LIGHT = color_background_light
|
||||
custom_palette.COLOR_BACKGROUND_NORMAL = color_background_normal
|
||||
custom_palette.COLOR_BACKGROUND_DARK = color_background_dark
|
||||
custom_palette.COLOR_FOREGROUND_LIGHT = color_foreground_light
|
||||
custom_palette.COLOR_FOREGROUND_NORMAL = color_foreground_normal
|
||||
custom_palette.COLOR_FOREGROUND_DARK = color_foreground_dark
|
||||
custom_palette.COLOR_SELECTION_LIGHT = color_selection_light
|
||||
custom_palette.COLOR_SELECTION_NORMAL = color_selection_normal
|
||||
custom_palette.COLOR_SELECTION_DARK = color_selection_dark
|
||||
custom_palette.SIZE_BORDER_RADIUS = border_radius
|
||||
custom_palette.PATH_RESOURCES = "'{}'".format(theme_root_path)
|
||||
|
||||
# Process images and save them to the custom platte rc folder
|
||||
create_images(rc_path=theme_rc_path, palette=custom_palette)
|
||||
create_palette_image(path=theme_root_path, palette=custom_palette)
|
||||
|
||||
# Compile SCSS
|
||||
variables_scss_filepath = os.path.join(theme_qss_path, VARIABLES_SCSS_FILE)
|
||||
theme_main_scss_filepath = os.path.join(theme_qss_path, MAIN_SCSS_FILE)
|
||||
theme_qss_filepath = os.path.join(theme_root_path, QSS_FILE)
|
||||
stylesheet = create_qss(
|
||||
qss_filepath=theme_qss_filepath,
|
||||
main_scss_filepath=theme_main_scss_filepath,
|
||||
variables_scss_filepath=variables_scss_filepath,
|
||||
palette=custom_palette,
|
||||
)
|
||||
|
||||
# Update colors in text
|
||||
with open(theme_main_scss_filepath, 'r') as fh:
|
||||
data = fh.read()
|
||||
|
||||
for key, color in DarkPalette.color_palette().items():
|
||||
custom_color = custom_palette.color_palette()[key].upper()
|
||||
data = data.replace(color, custom_color)
|
||||
stylesheet = stylesheet.replace(color, custom_color)
|
||||
|
||||
with open(theme_main_scss_filepath, 'w') as fh:
|
||||
fh.write(data)
|
||||
|
||||
with open(theme_qss_filepath, 'w') as fh:
|
||||
fh.write(stylesheet)
|
||||
|
||||
return stylesheet
|
||||
|
||||
|
||||
def create_custom_qss_from_palette(name, path, palette):
|
||||
"""
|
||||
Create a custom palette based on a palette class.
|
||||
"""
|
||||
kwargs = {
|
||||
'name': name,
|
||||
'path': path,
|
||||
'border_radius': palette.SIZE_BORDER_RADIUS,
|
||||
}
|
||||
kwargs.update(palette.color_palette())
|
||||
stylesheet = create_custom_qss(**kwargs)
|
||||
|
||||
return stylesheet
|
||||
|
||||
|
||||
def create_custom_qss_from_dict(name, path, palette_dict):
|
||||
"""
|
||||
Create a custom palette based on a palette dictionary.
|
||||
"""
|
||||
kwargs = {
|
||||
'name': name,
|
||||
'path': path,
|
||||
'border_radius': palette_dict.get('SIZE_BORDER_RADIUS', '4px'),
|
||||
}
|
||||
kwargs.update(palette_dict)
|
||||
stylesheet = create_custom_qss(**kwargs)
|
||||
|
||||
return stylesheet
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Example of a custom palette
|
||||
# TODO: change to not use a specfic path
|
||||
# TODO: may move to other place, e.g., example.py
|
||||
qss = create_custom_qss(
|
||||
'MyAwesomePalette',
|
||||
'/Users/gpena-castellanos/Desktop',
|
||||
'#ff0000',
|
||||
'#cc0000',
|
||||
'#aa0000',
|
||||
'#00ff00',
|
||||
'#00cc00',
|
||||
'#00aa00',
|
||||
'#0000ff',
|
||||
'#0000cc',
|
||||
'#0000aa',
|
||||
'0px',
|
||||
)
|
Reference in New Issue
Block a user