diff --git a/docs/source/conf.py b/docs/source/conf.py index ce2f9c9..926e961 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,47 +16,51 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys -import os -import shlex # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # We load the ipython release info into a dict by explicit execution _release = {} -exec(compile(open('../../qtawesome/_version.py').read(), '../../qtawesome/_version.py', 'exec'), _release) +exec( + compile( + open("../../qtawesome/_version.py").read(), + "../../qtawesome/_version.py", + "exec", + ), + _release, +) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.autosummary', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', - 'sphinx_rtd_theme', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.autosummary", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx_rtd_theme", ] autosummary_generate = True # Add any paths that contain templates here, relative to this directory. -#templates_path = ['_templates'] +# templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # Add dev disclaimer. -if _release['version_info'][-1] == 'dev': +if _release["version_info"][-1] == "dev": rst_prolog = """ .. note:: @@ -66,24 +70,24 @@ """ # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'qtawesome' -copyright = u'2015-, The Spyder Development Team' -author = u'Sylvain Corlay and the Spyder Development Team' +project = "qtawesome" +copyright = "2015-, The Spyder Development Team" +author = "Sylvain Corlay and the Spyder Development Team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '.'.join(map(str, _release['version_info'][:2])) +version = ".".join(map(str, _release["version_info"][:2])) # The full version, including alpha/beta/rc tags. -release = _release['__version__'] +release = _release["__version__"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -94,9 +98,9 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -104,27 +108,27 @@ # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -134,31 +138,31 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -168,122 +172,121 @@ # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'qtawesomedoc' +htmlhelp_basename = "qtawesomedoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', + # Latex figure (float) alignment + #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'qtawesome.tex', u'qtawesome Documentation', - u'The Spyder Development Team', 'manual'), + ( + master_doc, + "qtawesome.tex", + "qtawesome Documentation", + "The Spyder Development Team", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'qtawesome', u'qtawesome Documentation', - [author], 1) -] +man_pages = [(master_doc, "qtawesome", "qtawesome Documentation", [author], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -292,23 +295,29 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'qtawesome', u'qtawesome Documentation', - author, 'qtawesome', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "qtawesome", + "qtawesome Documentation", + author, + "qtawesome", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} diff --git a/example.py b/example.py index 5fb4a24..4fdf930 100644 --- a/example.py +++ b/example.py @@ -11,129 +11,138 @@ class AwesomeExample(QtWidgets.QDialog): - def __init__(self): super().__init__() # Label for supported fonts - supported_fonts_label = QtWidgets.QLabel('Supported fonts (prefix)') + supported_fonts_label = QtWidgets.QLabel("Supported fonts (prefix)") supported_fonts_label.setAlignment(QtCore.Qt.AlignCenter) # Get FontAwesome 5.x icons by name in various styles by name - fa5_icon = qta.icon('fa5.flag') - fa5_button = QtWidgets.QPushButton(fa5_icon, 'Font Awesome regular (fa5)') + fa5_icon = qta.icon("fa5.flag") + fa5_button = QtWidgets.QPushButton(fa5_icon, "Font Awesome regular (fa5)") - fa5s_icon = qta.icon('fa5s.flag') - fa5s_button = QtWidgets.QPushButton(fa5s_icon, 'Font Awesome solid (fa5s)') + fa5s_icon = qta.icon("fa5s.flag") + fa5s_button = QtWidgets.QPushButton(fa5s_icon, "Font Awesome solid (fa5s)") - fa5b_icon = qta.icon('fa5b.github') - fa5b_button = QtWidgets.QPushButton(fa5b_icon, 'Font Awesome brands (fa5b)') + fa5b_icon = qta.icon("fa5b.github") + fa5b_button = QtWidgets.QPushButton(fa5b_icon, "Font Awesome brands (fa5b)") # Get Elusive icons by name - asl_icon = qta.icon('ei.asl') - elusive_button = QtWidgets.QPushButton(asl_icon, 'Elusive Icons (ei)') + asl_icon = qta.icon("ei.asl") + elusive_button = QtWidgets.QPushButton(asl_icon, "Elusive Icons (ei)") # Get Material Design icons by name - apn_icon = qta.icon('mdi6.access-point-network') - mdi6_button = QtWidgets.QPushButton(apn_icon, 'Material Design (mdi, mdi6)') + apn_icon = qta.icon("mdi6.access-point-network") + mdi6_button = QtWidgets.QPushButton(apn_icon, "Material Design (mdi, mdi6)") # Get Phosphor by name - mic_icon = qta.icon('ph.microphone-fill') - ph_button = QtWidgets.QPushButton(mic_icon, 'Phosphor Icons (ph)') + mic_icon = qta.icon("ph.microphone-fill") + ph_button = QtWidgets.QPushButton(mic_icon, "Phosphor Icons (ph)") # Get Remix Icon by name - truck_icon = qta.icon('ri.truck-fill') - ri_button = QtWidgets.QPushButton(truck_icon, 'Remix Icons (ri)') + truck_icon = qta.icon("ri.truck-fill") + ri_button = QtWidgets.QPushButton(truck_icon, "Remix Icons (ri)") # Get Microsoft's Codicons by name - squirrel_icon = qta.icon('msc.squirrel') - msc_button = QtWidgets.QPushButton(squirrel_icon, 'Codicons (msc)') + squirrel_icon = qta.icon("msc.squirrel") + msc_button = QtWidgets.QPushButton(squirrel_icon, "Codicons (msc)") # Label for style options and animations - styles_label = QtWidgets.QLabel('Styles') + styles_label = QtWidgets.QLabel("Styles") styles_label.setAlignment(QtCore.Qt.AlignCenter) # Rotated - rot_icon = qta.icon('mdi.access-point-network', rotated=45) - rot_button = QtWidgets.QPushButton(rot_icon, 'Rotated Icons') + rot_icon = qta.icon("mdi.access-point-network", rotated=45) + rot_button = QtWidgets.QPushButton(rot_icon, "Rotated Icons") # Horizontal flip - hflip_icon = qta.icon('mdi.account-alert', hflip=True) - hflip_button = QtWidgets.QPushButton(hflip_icon, 'Horizontally Flipped Icons') + hflip_icon = qta.icon("mdi.account-alert", hflip=True) + hflip_button = QtWidgets.QPushButton(hflip_icon, "Horizontally Flipped Icons") # Vertical flip - vflip_icon = qta.icon('mdi.account-alert', vflip=True) - vflip_button = QtWidgets.QPushButton(vflip_icon, 'Vertically Flipped Icons') + vflip_icon = qta.icon("mdi.account-alert", vflip=True) + vflip_button = QtWidgets.QPushButton(vflip_icon, "Vertically Flipped Icons") # Styling - styling_icon = qta.icon('fa5s.music', - active='fa5s.balance-scale', - color='blue', - color_active='orange') - music_button = QtWidgets.QPushButton(styling_icon, 'Changing colors') + styling_icon = qta.icon( + "fa5s.music", + active="fa5s.balance-scale", + color="blue", + color_active="orange", + ) + music_button = QtWidgets.QPushButton(styling_icon, "Changing colors") # Setting an alpha of 165 to the color of this icon. Alpha must be a number # between 0 and 255. - icon_with_alpha = qta.icon('mdi.heart', color=('red', 120)) - heart_button = QtWidgets.QPushButton(icon_with_alpha, 'Setting alpha') + icon_with_alpha = qta.icon("mdi.heart", color=("red", 120)) + heart_button = QtWidgets.QPushButton(icon_with_alpha, "Setting alpha") # Toggle - toggle_icon = qta.icon('fa5s.home', selected='fa5s.balance-scale', - color_off='black', - color_off_active='blue', - color_on='orange', - color_on_active='yellow') - toggle_button = QtWidgets.QPushButton(toggle_icon, 'Toggle') + toggle_icon = qta.icon( + "fa5s.home", + selected="fa5s.balance-scale", + color_off="black", + color_off_active="blue", + color_on="orange", + color_on_active="yellow", + ) + toggle_button = QtWidgets.QPushButton(toggle_icon, "Toggle") toggle_button.setCheckable(True) iconwidget = qta.IconWidget() - spin_icon = qta.icon('mdi.loading', color='red', - animation=qta.Spin(iconwidget)) + spin_icon = qta.icon("mdi.loading", color="red", animation=qta.Spin(iconwidget)) iconwidget.setIcon(spin_icon) iconwidget.setIconSize(QtCore.QSize(32, 32)) iconwidgetholder = QtWidgets.QWidget() lo = QtWidgets.QHBoxLayout() lo.addWidget(iconwidget) - lo.addWidget(QtWidgets.QLabel('IconWidget')) + lo.addWidget(QtWidgets.QLabel("IconWidget")) iconwidgetholder.setLayout(lo) - iconwidget2 = qta.IconWidget('mdi.web', color='blue', size=QtCore.QSize(16, 16)) + iconwidget2 = qta.IconWidget("mdi.web", color="blue", size=QtCore.QSize(16, 16)) # Icon drawn with the `image` option - drawn_image_icon = qta.icon('ri.truck-fill', - options=[{'draw': 'image'}]) - drawn_image_button = QtWidgets.QPushButton(drawn_image_icon, - 'Icon drawn as an image') + drawn_image_icon = qta.icon("ri.truck-fill", options=[{"draw": "image"}]) + drawn_image_button = QtWidgets.QPushButton( + drawn_image_icon, "Icon drawn as an image" + ) # Stack icons - camera_ban = qta.icon('fa5s.camera', 'fa5s.ban', - options=[{'scale_factor': 0.5, - 'active': 'fa5s.balance-scale'}, - {'color': 'red', 'opacity': 0.7}]) - stack_button = QtWidgets.QPushButton(camera_ban, 'Stack') + camera_ban = qta.icon( + "fa5s.camera", + "fa5s.ban", + options=[ + {"scale_factor": 0.5, "active": "fa5s.balance-scale"}, + {"color": "red", "opacity": 0.7}, + ], + ) + stack_button = QtWidgets.QPushButton(camera_ban, "Stack") stack_button.setIconSize(QtCore.QSize(32, 32)) # Stack and offset icons - saveall = qta.icon('fa5.save', 'fa5.save', - options=[{'scale_factor': 0.8, - 'offset': (0.2, 0.2), - 'color': 'gray'}, - {'scale_factor': 0.8}]) - saveall_button = QtWidgets.QPushButton(saveall, 'Stack, offset') + saveall = qta.icon( + "fa5.save", + "fa5.save", + options=[ + {"scale_factor": 0.8, "offset": (0.2, 0.2), "color": "gray"}, + {"scale_factor": 0.8}, + ], + ) + saveall_button = QtWidgets.QPushButton(saveall, "Stack, offset") # Spin icons - spin_button = QtWidgets.QPushButton(' Spinning icon') + spin_button = QtWidgets.QPushButton(" Spinning icon") animation1 = qta.Spin(spin_button) - spin_icon = qta.icon('fa5s.spinner', color='red', animation=animation1) + spin_icon = qta.icon("fa5s.spinner", color="red", animation=animation1) spin_button.setIcon(spin_icon) timer1 = QtCore.QTimer() timer1.singleShot(3000, animation1.stop) # Pulse icons - pulse_button = QtWidgets.QPushButton(' Pulsing icon') + pulse_button = QtWidgets.QPushButton(" Pulsing icon") animation2 = qta.Pulse(pulse_button, autostart=False) - pulse_icon = qta.icon('fa5s.spinner', color='green', - animation=animation2) + pulse_icon = qta.icon("fa5s.spinner", color="green", animation=animation2) pulse_button.setIcon(pulse_icon) timer2 = QtCore.QTimer() @@ -142,18 +151,18 @@ def __init__(self): timer3.singleShot(6000, animation2.stop) # Stacked spin icons - stack_spin_button = QtWidgets.QPushButton('Stack spin') - options = [{'scale_factor': 0.4, - 'animation': qta.Spin(stack_spin_button)}, - {'color': 'blue'}] - stack_spin_icon = qta.icon('ei.asl', 'fa5.square', - options=options) + stack_spin_button = QtWidgets.QPushButton("Stack spin") + options = [ + {"scale_factor": 0.4, "animation": qta.Spin(stack_spin_button)}, + {"color": "blue"}, + ] + stack_spin_icon = qta.icon("ei.asl", "fa5.square", options=options) stack_spin_button.setIcon(stack_spin_icon) stack_spin_button.setIconSize(QtCore.QSize(32, 32)) # Render a label with this font - label = QtWidgets.QLabel(chr(0xf19c) + ' ' + 'Label') - label.setFont(qta.font('fa', 16)) + label = QtWidgets.QLabel(chr(0xF19C) + " " + "Label") + label.setFont(qta.font("fa", 16)) # Layout grid = QtWidgets.QGridLayout() @@ -176,7 +185,7 @@ def __init__(self): hflip_button, vflip_button, toggle_button, - drawn_image_button + drawn_image_button, ] animated_widgets = [ spin_button, @@ -185,11 +194,7 @@ def __init__(self): saveall_button, stack_spin_button, ] - other_widgets = [ - label, - iconwidgetholder, - iconwidget2 - ] + other_widgets = [label, iconwidgetholder, iconwidget2] for idx, w in enumerate(fonts_widgets): grid.addWidget(w, idx, 0) @@ -203,10 +208,10 @@ def __init__(self): for idx, w in enumerate(other_widgets): grid.addWidget(w, idx + len(styled_widgets) + len(animated_widgets), 1) - title = 'Awesome' - args = ' '.join(sys.argv[1:]).strip() + title = "Awesome" + args = " ".join(sys.argv[1:]).strip() if args: - title += ' (' + args + ')' + title += " (" + args + ")" self.setLayout(grid) self.setWindowTitle(title) @@ -215,11 +220,10 @@ def __init__(self): def main(): - global_defaults = {} for arg in sys.argv[1:]: try: - key, val = arg.split('=', maxsplit=1) + key, val = arg.split("=", maxsplit=1) global_defaults[key] = val except Exception: pass @@ -229,7 +233,7 @@ def main(): app = QtWidgets.QApplication(sys.argv) # Enable High DPI display with PyQt5 - if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'): + if hasattr(QtCore.Qt, "AA_UseHighDpiPixmaps"): app.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps) # Timer needed to close the example application @@ -239,5 +243,5 @@ def main(): sys.exit(app.exec_()) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/qtawesome/__init__.py b/qtawesome/__init__.py index 736279c..921dde3 100644 --- a/qtawesome/__init__.py +++ b/qtawesome/__init__.py @@ -30,54 +30,54 @@ from .styles import dark as dark, light as light # Constants -_resource = { 'iconic': None } +_resource = {"iconic": None} _BUNDLED_FONTS = ( - ('fa', - 'fontawesome4-webfont-4.7.ttf', - 'fontawesome4-webfont-charmap-4.7.json'), - ('fa5', - 'fontawesome5-regular-webfont-5.15.4.ttf', - 'fontawesome5-regular-webfont-charmap-5.15.4.json'), - ('fa5s', - 'fontawesome5-solid-webfont-5.15.4.ttf', - 'fontawesome5-solid-webfont-charmap-5.15.4.json'), - ('fa5b', - 'fontawesome5-brands-webfont-5.15.4.ttf', - 'fontawesome5-brands-webfont-charmap-5.15.4.json'), - ('ei', - 'elusiveicons-webfont-2.0.ttf', - 'elusiveicons-webfont-charmap-2.0.json'), - ('mdi', - 'materialdesignicons5-webfont-5.9.55.ttf', - 'materialdesignicons5-webfont-charmap-5.9.55.json'), - ('mdi6', - 'materialdesignicons6-webfont-6.9.96.ttf', - 'materialdesignicons6-webfont-charmap-6.9.96.json'), - ('ph', - 'phosphor-1.3.0.ttf', - 'phosphor-charmap-1.3.0.json'), - ('ri', - 'remixicon-2.5.0.ttf', - 'remixicon-charmap-2.5.0.json'), - ('msc', - 'codicon-0.0.35.ttf', - 'codicon-charmap-0.0.35.json'), + ("fa", "fontawesome4-webfont-4.7.ttf", "fontawesome4-webfont-charmap-4.7.json"), + ( + "fa5", + "fontawesome5-regular-webfont-5.15.4.ttf", + "fontawesome5-regular-webfont-charmap-5.15.4.json", + ), + ( + "fa5s", + "fontawesome5-solid-webfont-5.15.4.ttf", + "fontawesome5-solid-webfont-charmap-5.15.4.json", + ), + ( + "fa5b", + "fontawesome5-brands-webfont-5.15.4.ttf", + "fontawesome5-brands-webfont-charmap-5.15.4.json", + ), + ("ei", "elusiveicons-webfont-2.0.ttf", "elusiveicons-webfont-charmap-2.0.json"), + ( + "mdi", + "materialdesignicons5-webfont-5.9.55.ttf", + "materialdesignicons5-webfont-charmap-5.9.55.json", + ), + ( + "mdi6", + "materialdesignicons6-webfont-6.9.96.ttf", + "materialdesignicons6-webfont-charmap-6.9.96.json", + ), + ("ph", "phosphor-1.3.0.ttf", "phosphor-charmap-1.3.0.json"), + ("ri", "remixicon-2.5.0.ttf", "remixicon-charmap-2.5.0.json"), + ("msc", "codicon-0.0.35.ttf", "codicon-charmap-0.0.35.json"), ) # MD5 Hashes for font files bundled with qtawesome: _MD5_HASHES = { - 'fontawesome4-webfont-4.7.ttf': 'b06871f281fee6b241d60582ae9369b9', - 'fontawesome5-regular-webfont-5.15.4.ttf': 'dc47e4089f5bcb25f241bdeb2de0fb58', - 'fontawesome5-solid-webfont-5.15.4.ttf': '5de19800fc9ae73438c2e5c61d041b48', - 'fontawesome5-brands-webfont-5.15.4.ttf': '513aa607d398efaccc559916c3431403', - 'elusiveicons-webfont-2.0.ttf': '207966b04c032d5b873fd595a211582e', - 'materialdesignicons5-webfont-5.9.55.ttf': 'b7d40e7ef80c1d4af6d94902af66e524', - 'materialdesignicons6-webfont-6.9.96.ttf': 'ecaabfbb23fdac4ddbaf897b97257a92', - 'phosphor-1.3.0.ttf': '5b8dc57388b2d86243566b996cc3a789', - 'remixicon-2.5.0.ttf': '888e61f04316f10bddfff7bee10c6dd0', - 'codicon-0.0.35.ttf': '8478f5b3df2158f7e4864473e34efda1', + "fontawesome4-webfont-4.7.ttf": "b06871f281fee6b241d60582ae9369b9", + "fontawesome5-regular-webfont-5.15.4.ttf": "dc47e4089f5bcb25f241bdeb2de0fb58", + "fontawesome5-solid-webfont-5.15.4.ttf": "5de19800fc9ae73438c2e5c61d041b48", + "fontawesome5-brands-webfont-5.15.4.ttf": "513aa607d398efaccc559916c3431403", + "elusiveicons-webfont-2.0.ttf": "207966b04c032d5b873fd595a211582e", + "materialdesignicons5-webfont-5.9.55.ttf": "b7d40e7ef80c1d4af6d94902af66e524", + "materialdesignicons6-webfont-6.9.96.ttf": "ecaabfbb23fdac4ddbaf897b97257a92", + "phosphor-1.3.0.ttf": "5b8dc57388b2d86243566b996cc3a789", + "remixicon-2.5.0.ttf": "888e61f04316f10bddfff7bee10c6dd0", + "codicon-0.0.35.ttf": "8478f5b3df2158f7e4864473e34efda1", } @@ -89,9 +89,7 @@ def has_valid_font_ids(inst): """ # Check stored font ids are still available for font_id in inst.fontids.values(): - font_families = QtGui.QFontDatabase.applicationFontFamilies( - font_id - ) + font_families = QtGui.QFontDatabase.applicationFontFamilies(font_id) if not font_families: return False return True @@ -104,14 +102,11 @@ def _instance(): Functions ``icon``, ``load_font``, ``charmap``, ``font`` and ``set_defaults`` all rebind to methods of the singleton instance of IconicFont. """ - if ( - _resource['iconic'] is not None - and not has_valid_font_ids(_resource['iconic']) - ): + if _resource["iconic"] is not None and not has_valid_font_ids(_resource["iconic"]): # Reset cached instance - _resource['iconic'] = None + _resource["iconic"] = None - if _resource['iconic'] is None: + if _resource["iconic"] is None: # Verify that vendorized fonts are not corrupt if not _SYSTEM_FONTS: for fargs in _BUNDLED_FONTS: @@ -120,20 +115,20 @@ def _instance(): if ttf_hash is None: continue ttf_filepath = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "fonts", ttf_filename) + os.path.dirname(os.path.realpath(__file__)), "fonts", ttf_filename + ) with open(ttf_filepath, "rb") as f: ttf_calculated_hash_code = hashlib.md5(f.read()).hexdigest() if ttf_calculated_hash_code != ttf_hash: raise FontError(f"Font is corrupt at: '{ttf_filepath}'") - _resource['iconic'] = IconicFont(*_BUNDLED_FONTS) - return _resource['iconic'] + _resource["iconic"] = IconicFont(*_BUNDLED_FONTS) + return _resource["iconic"] def reset_cache(): - if _resource['iconic'] is not None: - _resource['iconic'].icon_cache = {} + if _resource["iconic"] is not None: + _resource["iconic"].icon_cache = {} def icon(*names, **kwargs): @@ -290,7 +285,7 @@ def charmap(prefixed_name): The dictionary mapping the icon names to the corresponding unicode character. """ - prefix, name = prefixed_name.split('.') + prefix, name = prefixed_name.split(".") return _instance().charmap[prefix][name] @@ -348,9 +343,9 @@ class IconWidget(QtWidgets.QLabel): """ def __init__(self, *names, **kwargs): - super().__init__(parent=kwargs.get('parent')) + super().__init__(parent=kwargs.get("parent")) self._icon = None - self._size = kwargs.get('size', QtCore.QSize(16, 16)) + self._size = kwargs.get("size", QtCore.QSize(16, 16)) self.setIcon(icon(*names, **kwargs)) def setIcon(self, _icon): diff --git a/qtawesome/_version.py b/qtawesome/_version.py index d48e8a4..406a485 100644 --- a/qtawesome/_version.py +++ b/qtawesome/_version.py @@ -1,2 +1,2 @@ -version_info = (1, 4, 0, 'dev0') -__version__ = '.'.join(map(str, version_info)) +version_info = (1, 4, 0, "dev0") +__version__ = ".".join(map(str, version_info)) diff --git a/qtawesome/animation.py b/qtawesome/animation.py index 0d8754a..6f97b99 100644 --- a/qtawesome/animation.py +++ b/qtawesome/animation.py @@ -2,7 +2,6 @@ class Spin: - def __init__(self, parent_widget, interval=10, step=1, autostart=True): self.parent_widget = parent_widget self.interval = interval @@ -23,7 +22,6 @@ def _update(self): self.parent_widget.update() def setup(self, icon_painter, painter, rect): - if self.parent_widget not in self.info: timer = QTimer(self.parent_widget) timer.timeout.connect(self._update) @@ -48,11 +46,5 @@ def stop(self): class Pulse(Spin): - def __init__(self, parent_widget, autostart=True): - super().__init__( - parent_widget, - interval=300, - step=45, - autostart=autostart - ) + super().__init__(parent_widget, interval=300, step=45, autostart=autostart) diff --git a/qtawesome/icon_browser.py b/qtawesome/icon_browser.py index fcff5c6..f3d7bd2 100644 --- a/qtawesome/icon_browser.py +++ b/qtawesome/icon_browser.py @@ -1,4 +1,3 @@ - import sys from qtpy import QtCore, QtGui, QtWidgets @@ -11,7 +10,7 @@ DEFAULT_VIEW_COLUMNS = 5 VIEW_COLUMNS_OPTIONS = [5, 8, 10, 15, 20, 25, 30] AUTO_SEARCH_TIMEOUT = 500 -ALL_COLLECTIONS = 'All' +ALL_COLLECTIONS = "All" class IconBrowser(QtWidgets.QMainWindow): @@ -25,15 +24,15 @@ def __init__(self): super().__init__() qtawesome._instance() - fontMaps = qtawesome._resource['iconic'].charmap + fontMaps = qtawesome._resource["iconic"].charmap iconNames = [] for fontCollection, fontData in fontMaps.items(): for iconName in fontData: - iconNames.append('%s.%s' % (fontCollection, iconName)) + iconNames.append("%s.%s" % (fontCollection, iconName)) self.setMinimumSize(300, 300) - self.setWindowTitle('QtAwesome Icon Browser') + self.setWindowTitle("QtAwesome Icon Browser") self.setWindowIcon(qtawesome.icon("fa5s.icons")) self._filterTimer = QtCore.QTimer(self) @@ -54,23 +53,18 @@ def __init__(self): self._listView.setModel(self._proxyModel) self._listView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self._listView.doubleClicked.connect(self._copyIconText) - self._listView.selectionModel().selectionChanged.connect( - self._updateNameField - ) + self._listView.selectionModel().selectionChanged.connect(self._updateNameField) toolbar = QtWidgets.QHBoxLayout() # Filter section self._comboFont = QtWidgets.QComboBox(self) self._comboFont.setToolTip( - "Select the font prefix whose icons will " - "be included in the filtering." + "Select the font prefix whose icons will " "be included in the filtering." ) self._comboFont.setMaximumWidth(75) self._comboFont.addItems([ALL_COLLECTIONS] + sorted(fontMaps.keys())) - self._comboFont.currentIndexChanged.connect( - self._triggerImmediateUpdate - ) + self._comboFont.currentIndexChanged.connect(self._triggerImmediateUpdate) toolbar.addWidget(self._comboFont) self._lineEditFilter = QtWidgets.QLineEdit(self) @@ -79,9 +73,7 @@ def __init__(self): self._lineEditFilter.setToolTip("Filter icons by name") self._lineEditFilter.setAlignment(QtCore.Qt.AlignLeft) self._lineEditFilter.textChanged.connect(self._triggerDelayedUpdate) - self._lineEditFilter.returnPressed.connect( - self._triggerImmediateUpdate - ) + self._lineEditFilter.returnPressed.connect(self._triggerImmediateUpdate) self._lineEditFilter.setClearButtonEnabled(True) toolbar.addWidget(self._lineEditFilter, stretch=10) @@ -99,7 +91,7 @@ def __init__(self): self._nameField.setFont(fnt) toolbar.addWidget(self._nameField, stretch=10) - self._copyButton = QtWidgets.QPushButton('Copy Name', self) + self._copyButton = QtWidgets.QPushButton("Copy Name", self) self._copyButton.setToolTip( "Copy selected icon full identifier to the clipboard" ) @@ -298,7 +290,6 @@ def resizeEvent(self, event): class IconModel(QtCore.QStringListModel): - def __init__(self): super().__init__() @@ -337,5 +328,5 @@ def run(): sys.exit(app.exec_()) -if __name__ == '__main__': +if __name__ == "__main__": run() diff --git a/qtawesome/iconic_font.py b/qtawesome/iconic_font.py index 53a4ac6..ebff9e8 100644 --- a/qtawesome/iconic_font.py +++ b/qtawesome/iconic_font.py @@ -22,11 +22,20 @@ # Third party imports from qtpy import PYSIDE_VERSION -from qtpy.QtCore import (QObject, QPoint, QRect, Qt, - QSizeF, QRectF, QPointF, QThread) -from qtpy.QtGui import (QColor, QFont, QFontDatabase, QIcon, QIconEngine, - QPainter, QPixmap, QTransform, QPalette, QRawFont, - QImage) +from qtpy.QtCore import QObject, QPoint, QRect, Qt, QSizeF, QRectF, QPointF, QThread +from qtpy.QtGui import ( + QColor, + QFont, + QFontDatabase, + QIcon, + QIconEngine, + QPainter, + QPixmap, + QTransform, + QPalette, + QRawFont, + QImage, +) from qtpy.QtWidgets import QApplication try: @@ -42,39 +51,40 @@ # Needed imports and constants to install bundled fonts on Windows # Based on https://stackoverflow.com/a/41841088/15954282 -if os.name == 'nt': +if os.name == "nt": from ctypes import wintypes import winreg - - user32 = ctypes.WinDLL('user32', use_last_error=True) - gdi32 = ctypes.WinDLL('gdi32', use_last_error=True) - FONTS_REG_PATH = r'Software\Microsoft\Windows NT\CurrentVersion\Fonts' + user32 = ctypes.WinDLL("user32", use_last_error=True) + gdi32 = ctypes.WinDLL("gdi32", use_last_error=True) + + FONTS_REG_PATH = r"Software\Microsoft\Windows NT\CurrentVersion\Fonts" GFRI_DESCRIPTION = 1 - GFRI_ISTRUETYPE = 3 + GFRI_ISTRUETYPE = 3 - if not hasattr(wintypes, 'LPDWORD'): + if not hasattr(wintypes, "LPDWORD"): wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD) user32.SendMessageTimeoutW.restype = wintypes.LPVOID user32.SendMessageTimeoutW.argtypes = ( - wintypes.HWND, # hWnd - wintypes.UINT, # Msg - wintypes.LPVOID, # wParam - wintypes.LPVOID, # lParam - wintypes.UINT, # fuFlags - wintypes.UINT, # uTimeout - wintypes.LPVOID) # lpdwResult + wintypes.HWND, # hWnd + wintypes.UINT, # Msg + wintypes.LPVOID, # wParam + wintypes.LPVOID, # lParam + wintypes.UINT, # fuFlags + wintypes.UINT, # uTimeout + wintypes.LPVOID, + ) # lpdwResult - gdi32.AddFontResourceW.argtypes = ( - wintypes.LPCWSTR,) # lpszFilename + gdi32.AddFontResourceW.argtypes = (wintypes.LPCWSTR,) # lpszFilename # http://www.undocprint.org/winspool/getfontresourceinfo gdi32.GetFontResourceInfoW.argtypes = ( - wintypes.LPCWSTR, # lpszFilename - wintypes.LPDWORD, # cbBuffer + wintypes.LPCWSTR, # lpszFilename + wintypes.LPDWORD, # cbBuffer wintypes.LPVOID, # lpBuffer - wintypes.DWORD) # dwQueryType + wintypes.DWORD, + ) # dwQueryType def text_color(): @@ -94,10 +104,10 @@ def text_color_disabled(): _default_options = { - 'color': text_color, - 'color_disabled': text_color_disabled, - 'opacity': 1.0, - 'scale_factor': 1.0, + "color": text_color, + "color_disabled": text_color_disabled, + "opacity": 1.0, + "scale_factor": 1.0, } @@ -105,16 +115,37 @@ def set_global_defaults(**kwargs): """Set global defaults for the options passed to the icon painter.""" valid_options = [ - 'active', 'selected', 'disabled', 'on', 'off', - 'on_active', 'on_selected', 'on_disabled', - 'off_active', 'off_selected', 'off_disabled', - 'color', 'color_on', 'color_off', - 'color_active', 'color_selected', 'color_disabled', - 'color_on_selected', 'color_on_active', 'color_on_disabled', - 'color_off_selected', 'color_off_active', 'color_off_disabled', - 'animation', 'offset', 'scale_factor', 'rotated', 'hflip', 'vflip', - 'draw' - ] + "active", + "selected", + "disabled", + "on", + "off", + "on_active", + "on_selected", + "on_disabled", + "off_active", + "off_selected", + "off_disabled", + "color", + "color_on", + "color_off", + "color_active", + "color_selected", + "color_disabled", + "color_on_selected", + "color_on_active", + "color_on_disabled", + "color_off_selected", + "color_off_active", + "color_off_disabled", + "animation", + "offset", + "scale_factor", + "rotated", + "hflip", + "vflip", + "draw", + ] for kw in kwargs: if kw in valid_options: @@ -125,7 +156,6 @@ def set_global_defaults(**kwargs): class CharIconPainter: - """Char icon painter.""" def paint(self, iconic, painter, rect, mode, state, options): @@ -139,24 +169,23 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): color_options = { QIcon.On: { - QIcon.Normal: (options['color_on'], options['on']), - QIcon.Disabled: (options['color_on_disabled'], - options['on_disabled']), - QIcon.Active: (options['color_on_active'], - options['on_active']), - QIcon.Selected: (options['color_on_selected'], - options['on_selected']) + QIcon.Normal: (options["color_on"], options["on"]), + QIcon.Disabled: (options["color_on_disabled"], options["on_disabled"]), + QIcon.Active: (options["color_on_active"], options["on_active"]), + QIcon.Selected: (options["color_on_selected"], options["on_selected"]), }, - QIcon.Off: { - QIcon.Normal: (options['color_off'], options['off']), - QIcon.Disabled: (options['color_off_disabled'], - options['off_disabled']), - QIcon.Active: (options['color_off_active'], - options['off_active']), - QIcon.Selected: (options['color_off_selected'], - options['off_selected']) - } + QIcon.Normal: (options["color_off"], options["off"]), + QIcon.Disabled: ( + options["color_off_disabled"], + options["off_disabled"], + ), + QIcon.Active: (options["color_off_active"], options["off_active"]), + QIcon.Selected: ( + options["color_off_selected"], + options["off_selected"], + ), + }, } color, char = color_options[state][mode] @@ -178,43 +207,45 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): # The reason why the glyph size is smaller than the icon size is to # account for font bearing. - draw_size = round(0.875 * rect.height() * options['scale_factor']) - prefix = options['prefix'] + draw_size = round(0.875 * rect.height() * options["scale_factor"]) + prefix = options["prefix"] # Animation setup hook - animation = options.get('animation') + animation = options.get("animation") if animation is not None: animation.setup(self, painter, rect) - if 'offset' in options: + if "offset" in options: rect = QRect(rect) - rect.translate(round(options['offset'][0] * rect.width()), - round(options['offset'][1] * rect.height())) + rect.translate( + round(options["offset"][0] * rect.width()), + round(options["offset"][1] * rect.height()), + ) x_center = rect.width() * 0.5 y_center = rect.height() * 0.5 transform = QTransform() transform.translate(+x_center, +y_center) - if 'vflip' in options and options['vflip'] is True: - transform.scale(1,-1) - if 'hflip' in options and options['hflip'] is True: + if "vflip" in options and options["vflip"] is True: + transform.scale(1, -1) + if "hflip" in options and options["hflip"] is True: transform.scale(-1, 1) - if 'rotated' in options: - transform.rotate(options['rotated']) + if "rotated" in options: + transform.rotate(options["rotated"]) transform.translate(-x_center, -y_center) painter.setTransform(transform, True) - painter.setOpacity(options.get('opacity', 1.0)) + painter.setOpacity(options.get("opacity", 1.0)) - draw = options.get('draw') - if draw not in ('text', 'path', 'glyphrun', 'image'): + draw = options.get("draw") + if draw not in ("text", "path", "glyphrun", "image"): # Use QPainterPath when setting an animation # to fix tremulous spinning icons. # See spyder-ide/qtawesome#39 - draw = 'path' if animation is not None else 'text' + draw = "path" if animation is not None else "text" def try_draw_rawfont(): - if draw == 'glyphrun' and animation is not None: + if draw == "glyphrun" and animation is not None: # Disable font hinting to mitigate tremulous spinning to some extent # See spyder-ide/qtawesome#39 rawfont = iconic.rawfont(prefix, draw_size, QFont.PreferNoHinting) @@ -223,7 +254,7 @@ def try_draw_rawfont(): # Check glyf table and fallback to draw text if missing # because font glyph is necessary to draw path/glyphrun/image. - if not rawfont.fontTable('glyf'): + if not rawfont.fontTable("glyf"): return False glyph = rawfont.glyphIndexesForString(char)[0] @@ -233,14 +264,14 @@ def try_draw_rawfont(): painter.translate(QRectF(rect).center()) painter.translate(-size.width() / 2, -size.height() / 2) - if draw == 'path': + if draw == "path": path = rawfont.pathForGlyph(glyph) path.translate(0, ascent) path.setFillRule(Qt.WindingFill) painter.setRenderHint(QPainter.Antialiasing, True) painter.fillPath(path, painter.pen().color()) - elif draw == 'glyphrun': + elif draw == "glyphrun": if QGlyphRun: glyphrun = QGlyphRun() glyphrun.setRawFont(rawfont) @@ -248,12 +279,15 @@ def try_draw_rawfont(): glyphrun.setPositions((QPointF(0, ascent),)) painter.drawGlyphRun(QPointF(0, 0), glyphrun) else: - warnings.warn("QGlyphRun is unavailable for the current Qt binding! " - "QtAwesome will use the default draw values") + warnings.warn( + "QGlyphRun is unavailable for the current Qt binding! " + "QtAwesome will use the default draw values" + ) return False - elif draw == 'image': - image = rawfont.alphaMapForGlyph(glyph, QRawFont.PixelAntialiasing) \ - .convertToFormat(QImage.Format_ARGB32_Premultiplied) + elif draw == "image": + image = rawfont.alphaMapForGlyph( + glyph, QRawFont.PixelAntialiasing + ).convertToFormat(QImage.Format_ARGB32_Premultiplied) painter2 = QPainter(image) painter2.setCompositionMode(QPainter.CompositionMode_SourceIn) painter2.fillRect(image.rect(), painter.pen().color()) @@ -269,7 +303,7 @@ def try_draw_rawfont(): return True - if draw == 'text' or not try_draw_rawfont(): + if draw == "text" or not try_draw_rawfont(): font = iconic.font(prefix, draw_size) # Disable font hinting to mitigate tremulous spinning to some extent # See spyder-ide/qtawesome#39 @@ -286,7 +320,6 @@ class FontError(Exception): class CharIconEngine(QIconEngine): - """Specialization of QIconEngine used to draw font-based icons.""" def __init__(self, iconic, painter, options): @@ -296,8 +329,7 @@ def __init__(self, iconic, painter, options): self.options = options def paint(self, painter, rect, mode, state): - self.painter.paint( - self.iconic, painter, rect, mode, state, self.options) + self.painter.paint(self.iconic, painter, rect, mode, state, self.options) def pixmap(self, size, mode, state): pm = QPixmap(size) @@ -307,7 +339,6 @@ def pixmap(self, size, mode, state): class IconicFont(QObject): - """Main class for managing iconic fonts.""" def __init__(self, *args): @@ -360,13 +391,14 @@ def hook(obj): try: result[key] = chr(int(obj[key], 16)) except ValueError: - if int(obj[key], 16) > 0xffff: + if int(obj[key], 16) > 0xFFFF: # ignoring unsupported code in Python 2.7 32bit Windows # ValueError: chr() arg not in range(0x10000) pass else: - raise FontError(u'Failed to load character ' - '{0}:{1}'.format(key, obj[key])) + raise FontError( + "Failed to load character " "{0}:{1}".format(key, obj[key]) + ) return result if directory is None: @@ -374,7 +406,7 @@ def hook(obj): # Load font if QApplication.instance() is not None: - with open(os.path.join(directory, ttf_filename), 'rb') as font_data: + with open(os.path.join(directory, ttf_filename), "rb") as font_data: data = font_data.read() id_ = QFontDatabase.addApplicationFontFromData(data) font_data.close() @@ -386,32 +418,35 @@ def hook(obj): self.fontname[prefix] = loadedFontFamilies[0] self.fontdata[prefix] = data else: - raise FontError(u"Font at '{0}' appears to be empty. " - "If you are on Windows 10, please read " - "https://support.microsoft.com/" - "en-us/kb/3053676 " - "to know how to prevent Windows from blocking " - "the fonts that come with QtAwesome.".format( - os.path.join(directory, ttf_filename))) - - with open(os.path.join(directory, charmap_filename), 'r') as codes: + raise FontError( + "Font at '{0}' appears to be empty. " + "If you are on Windows 10, please read " + "https://support.microsoft.com/" + "en-us/kb/3053676 " + "to know how to prevent Windows from blocking " + "the fonts that come with QtAwesome.".format( + os.path.join(directory, ttf_filename) + ) + ) + + with open(os.path.join(directory, charmap_filename), "r") as codes: self.charmap[prefix] = json.load(codes, object_hook=hook) def icon(self, *names, **kwargs): """Return a QIcon object corresponding to the provided icon name.""" - cache_key = '{}{}'.format(names,kwargs) + cache_key = "{}{}".format(names, kwargs) - if names and 'fa.' in names[0]: + if names and "fa." in names[0]: warnings.warn( "The FontAwesome 4.7 ('fa' prefix) icon set will be " "removed in a future release in favor of FontAwesome 6. " "We recommend you to move to FontAwesome 5 ('fa5*' prefix) " "to prevent any issues in the future", - DeprecationWarning + DeprecationWarning, ) if cache_key not in self.icon_cache: - options_list = kwargs.pop('options', [{}] * len(names)) + options_list = kwargs.pop("options", [{}] * len(names)) general_options = kwargs if len(options_list) != len(names): @@ -422,17 +457,20 @@ def icon(self, *names, **kwargs): parsed_options = [] for i in range(len(options_list)): specific_options = options_list[i] - parsed_options.append(self._parse_options(specific_options, - general_options, - names[i])) + parsed_options.append( + self._parse_options(specific_options, general_options, names[i]) + ) # Process high level API api_options = parsed_options - self.icon_cache[cache_key] = self._icon_by_painter(self.painter, api_options) + self.icon_cache[cache_key] = self._icon_by_painter( + self.painter, api_options + ) else: - warnings.warn("You need to have a running " - "QApplication to use QtAwesome!") + warnings.warn( + "You need to have a running " "QApplication to use QtAwesome!" + ) return QIcon() return self.icon_cache[cache_key] @@ -444,73 +482,86 @@ def _parse_options(self, specific_options, general_options, name): # Handle icons for modes (Active, Disabled, Selected, Normal) # and states (On, Off) - icon_kw = ['char', 'on', 'off', 'active', 'selected', 'disabled', - 'on_active', 'on_selected', 'on_disabled', 'off_active', - 'off_selected', 'off_disabled'] - char = options.get('char', name) - on = options.get('on', char) - off = options.get('off', char) - active = options.get('active', on) - selected = options.get('selected', active) - disabled = options.get('disabled', char) - on_active = options.get('on_active', active) - on_selected = options.get('on_selected', selected) - on_disabled = options.get('on_disabled', disabled) - off_active = options.get('off_active', active) - off_selected = options.get('off_selected', selected) - off_disabled = options.get('off_disabled', disabled) - - icon_dict = {'char': char, - 'on': on, - 'off': off, - 'active': active, - 'selected': selected, - 'disabled': disabled, - 'on_active': on_active, - 'on_selected': on_selected, - 'on_disabled': on_disabled, - 'off_active': off_active, - 'off_selected': off_selected, - 'off_disabled': off_disabled, - } + icon_kw = [ + "char", + "on", + "off", + "active", + "selected", + "disabled", + "on_active", + "on_selected", + "on_disabled", + "off_active", + "off_selected", + "off_disabled", + ] + char = options.get("char", name) + on = options.get("on", char) + off = options.get("off", char) + active = options.get("active", on) + selected = options.get("selected", active) + disabled = options.get("disabled", char) + on_active = options.get("on_active", active) + on_selected = options.get("on_selected", selected) + on_disabled = options.get("on_disabled", disabled) + off_active = options.get("off_active", active) + off_selected = options.get("off_selected", selected) + off_disabled = options.get("off_disabled", disabled) + + icon_dict = { + "char": char, + "on": on, + "off": off, + "active": active, + "selected": selected, + "disabled": disabled, + "on_active": on_active, + "on_selected": on_selected, + "on_disabled": on_disabled, + "off_active": off_active, + "off_selected": off_selected, + "off_disabled": off_disabled, + } names = [icon_dict.get(kw, name) for kw in icon_kw] prefix, chars = self._get_prefix_chars(names) options.update(dict(zip(*(icon_kw, chars)))) - options.update({'prefix': prefix}) + options.update({"prefix": prefix}) # Handle colors for modes (Active, Disabled, Selected, Normal) # and states (On, Off) - color = options.get('color') - options.setdefault('color_on', color) - options.setdefault('color_active', options['color_on']) - options.setdefault('color_selected', options['color_active']) - options.setdefault('color_on_active', options['color_active']) - options.setdefault('color_on_selected', options['color_selected']) - options.setdefault('color_on_disabled', options['color_disabled']) - options.setdefault('color_off', color) - options.setdefault('color_off_active', options['color_active']) - options.setdefault('color_off_selected', options['color_selected']) - options.setdefault('color_off_disabled', options['color_disabled']) + color = options.get("color") + options.setdefault("color_on", color) + options.setdefault("color_active", options["color_on"]) + options.setdefault("color_selected", options["color_active"]) + options.setdefault("color_on_active", options["color_active"]) + options.setdefault("color_on_selected", options["color_selected"]) + options.setdefault("color_on_disabled", options["color_disabled"]) + options.setdefault("color_off", color) + options.setdefault("color_off_active", options["color_active"]) + options.setdefault("color_off_selected", options["color_selected"]) + options.setdefault("color_off_disabled", options["color_disabled"]) return options def _get_prefix_chars(self, names): chars = [] for name in names: - if '.' in name: - prefix, n = name.split('.') + if "." in name: + prefix, n = name.split(".") if prefix in self.charmap: if n in self.charmap[prefix]: chars.append(self.charmap[prefix][n]) else: error = 'Invalid icon name "{0}" in font "{1}"'.format( - n, prefix) + n, prefix + ) raise Exception(error) else: error = 'Invalid font prefix "{0}"'.format(prefix) raise Exception(error) else: - raise Exception('Invalid icon name') + raise Exception("Invalid icon name") return prefix, chars @@ -519,8 +570,8 @@ def font(self, prefix, size): font = QFont() font.setFamily(self.fontname[prefix]) font.setPixelSize(round(size)) - if prefix[-1] == 's': # solid style - font.setStyleName('Solid') + if prefix[-1] == "s": # solid style + font.setStyleName("Solid") return font def rawfont(self, prefix, size, hintingPreference=QFont.PreferDefaultHinting): @@ -539,7 +590,10 @@ def rawfont(self, prefix, size, hintingPreference=QFont.PreferDefaultHinting): if tid not in cache: cache[tid] = {} - def clear_cache(): cache.pop(tid) + + def clear_cache(): + cache.pop(tid) + QThread().currentThread().finished.connect(clear_cache) key = prefix, size, hintingPreference if key not in cache[tid]: @@ -587,25 +641,27 @@ def _get_fonts_directory(self): context. """ fonts_directory = os.path.join( - os.path.dirname(os.path.realpath(__file__)), 'fonts') - if os.name == 'nt': + os.path.dirname(os.path.realpath(__file__)), "fonts" + ) + if os.name == "nt": fonts_directory = self._install_fonts(fonts_directory) return fonts_directory def _install_fonts(self, fonts_directory): """ Copy the fonts to the user Fonts folder. - + Based on https://stackoverflow.com/a/41841088/15954282 """ # Try to get LOCALAPPDATA path - local_appdata_dir = os.environ.get('LOCALAPPDATA', None) + local_appdata_dir = os.environ.get("LOCALAPPDATA", None) if not local_appdata_dir: return fonts_directory # Construct path to fonts from LOCALAPPDATA user_fonts_dir = os.path.join( - local_appdata_dir, 'Microsoft', 'Windows', 'Fonts') + local_appdata_dir, "Microsoft", "Windows", "Fonts" + ) os.makedirs(user_fonts_dir, exist_ok=True) # Setup bundled fonts on the LOCALAPPDATA fonts directory @@ -613,10 +669,7 @@ def _install_fonts(self, fonts_directory): for filename in files: src_path = os.path.join(root, filename) dst_filename = filename - dst_path = os.path.join( - user_fonts_dir, - dst_filename - ) + dst_path = os.path.join(user_fonts_dir, dst_filename) # Check if font already exists and proceed with copy font # process if needed or skip it @@ -625,7 +678,7 @@ def _install_fonts(self, fonts_directory): shutil.copy(src_path, dst_path) # Further process the font file (`.ttf`) - if os.path.splitext(filename)[-1] == '.ttf': + if os.path.splitext(filename)[-1] == ".ttf": # Load the font in the current session if not gdi32.AddFontResourceW(dst_path): try: @@ -642,27 +695,36 @@ def _install_fonts(self, fonts_directory): # Try to get the font's real name cb = wintypes.DWORD() if gdi32.GetFontResourceInfoW( - filename, ctypes.byref(cb), None, GFRI_DESCRIPTION): + filename, ctypes.byref(cb), None, GFRI_DESCRIPTION + ): buf = (ctypes.c_wchar * cb.value)() if gdi32.GetFontResourceInfoW( - filename, ctypes.byref(cb), buf, GFRI_DESCRIPTION): + filename, ctypes.byref(cb), buf, GFRI_DESCRIPTION + ): fontname = buf.value is_truetype = wintypes.BOOL() cb.value = ctypes.sizeof(is_truetype) gdi32.GetFontResourceInfoW( - filename, ctypes.byref(cb), ctypes.byref(is_truetype), - GFRI_ISTRUETYPE) + filename, + ctypes.byref(cb), + ctypes.byref(is_truetype), + GFRI_ISTRUETYPE, + ) if is_truetype: - fontname += ' (TrueType)' + fontname += " (TrueType)" try: - with winreg.OpenKey(winreg.HKEY_CURRENT_USER, FONTS_REG_PATH, 0, - winreg.KEY_SET_VALUE) as key: + with winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + FONTS_REG_PATH, + 0, + winreg.KEY_SET_VALUE, + ) as key: winreg.SetValueEx(key, fontname, 0, winreg.REG_SZ, filename) except OSError: # Needed to support older Windows version where # font installation per user is not possible/related registry # entry is not available - # See spyder-ide/qtawesome#214 + # See spyder-ide/qtawesome#214 return fonts_directory return user_fonts_dir diff --git a/qtawesome/styles.py b/qtawesome/styles.py index bd28ef9..c635c63 100644 --- a/qtawesome/styles.py +++ b/qtawesome/styles.py @@ -27,8 +27,8 @@ from qtpy.QtGui import QPalette, QColor # Constant to reference default themes -DEFAULT_DARK_PALETTE = 'Dark' -DEFAULT_LIGHT_PALETTE = 'Light' +DEFAULT_DARK_PALETTE = "Dark" +DEFAULT_LIGHT_PALETTE = "Light" def dark(app): @@ -62,21 +62,18 @@ def dark(app): dark_palette.setColor(QPalette.LinkVisited, QColor(80, 80, 80)) # disabled - dark_palette.setColor(QPalette.Disabled, QPalette.WindowText, - QColor(127, 127, 127)) - dark_palette.setColor(QPalette.Disabled, QPalette.Text, - QColor(127, 127, 127)) - dark_palette.setColor(QPalette.Disabled, QPalette.ButtonText, - QColor(127, 127, 127)) - dark_palette.setColor(QPalette.Disabled, QPalette.Highlight, - QColor(80, 80, 80)) - dark_palette.setColor(QPalette.Disabled, QPalette.HighlightedText, - QColor(127, 127, 127)) + dark_palette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(127, 127, 127)) + dark_palette.setColor(QPalette.Disabled, QPalette.Text, QColor(127, 127, 127)) + dark_palette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(127, 127, 127)) + dark_palette.setColor(QPalette.Disabled, QPalette.Highlight, QColor(80, 80, 80)) + dark_palette.setColor( + QPalette.Disabled, QPalette.HighlightedText, QColor(127, 127, 127) + ) app.style().unpolish(app) app.setPalette(dark_palette) - app.setStyle('Fusion') + app.setStyle("Fusion") def light(app): @@ -110,18 +107,19 @@ def light(app): light_palette.setColor(QPalette.LinkVisited, QColor(222, 222, 222)) # disabled - light_palette.setColor(QPalette.Disabled, QPalette.WindowText, - QColor(115, 115, 115)) - light_palette.setColor(QPalette.Disabled, QPalette.Text, - QColor(115, 115, 115)) - light_palette.setColor(QPalette.Disabled, QPalette.ButtonText, - QColor(115, 115, 115)) - light_palette.setColor(QPalette.Disabled, QPalette.Highlight, - QColor(190, 190, 190)) - light_palette.setColor(QPalette.Disabled, QPalette.HighlightedText, - QColor(115, 115, 115)) + light_palette.setColor( + QPalette.Disabled, QPalette.WindowText, QColor(115, 115, 115) + ) + light_palette.setColor(QPalette.Disabled, QPalette.Text, QColor(115, 115, 115)) + light_palette.setColor( + QPalette.Disabled, QPalette.ButtonText, QColor(115, 115, 115) + ) + light_palette.setColor(QPalette.Disabled, QPalette.Highlight, QColor(190, 190, 190)) + light_palette.setColor( + QPalette.Disabled, QPalette.HighlightedText, QColor(115, 115, 115) + ) app.style().unpolish(app) app.setPalette(light_palette) - app.setStyle('Fusion') + app.setStyle("Fusion") diff --git a/qtawesome/tests/test_icon_browser.py b/qtawesome/tests/test_icon_browser.py index 8e8c89c..67eeaed 100644 --- a/qtawesome/tests/test_icon_browser.py +++ b/qtawesome/tests/test_icon_browser.py @@ -24,6 +24,7 @@ def test_browser_init(browser): """ Ensure the browser opens without error """ + def close(): browser.close() @@ -39,19 +40,21 @@ def test_copy(qtbot, browser): """ clipboard = QtWidgets.QApplication.instance().clipboard() - clipboard.setText('') + clipboard.setText("") assert clipboard.text() == "" # Enter a search term and press enter - qtbot.keyClicks(browser._lineEditFilter, 'penguin') + qtbot.keyClicks(browser._lineEditFilter, "penguin") qtbot.keyPress(browser._lineEditFilter, QtCore.Qt.Key_Enter) # TODO: Figure out how to do this via a qtbot.mouseClick call # Select the first item in the list model = browser._listView.model() selectionModel = browser._listView.selectionModel() - selectionModel.setCurrentIndex(model.index(0, 0), QtCore.QItemSelectionModel.ClearAndSelect) + selectionModel.setCurrentIndex( + model.index(0, 0), QtCore.QItemSelectionModel.ClearAndSelect + ) # Click the copy button qtbot.mouseClick(browser._copyButton, QtCore.Qt.LeftButton) @@ -67,7 +70,7 @@ def test_filter(qtbot, browser): assert initRowCount > 0 # Enter a search term and click - qtbot.keyClicks(browser._lineEditFilter, 'penguin') + qtbot.keyClicks(browser._lineEditFilter, "penguin") qtbot.keyPress(browser._lineEditFilter, QtCore.Qt.Key_Enter) filteredRowCount = browser._listView.model().rowCount() @@ -82,7 +85,7 @@ def test_filter_no_results(qtbot, browser): assert initRowCount > 0 # Enter a search term - qtbot.keyClicks(browser._lineEditFilter, 'I-AM-NOT-penguin-A-penguin') + qtbot.keyClicks(browser._lineEditFilter, "I-AM-NOT-penguin-A-penguin") # Press Enter to perform the filter qtbot.keyPress(browser._lineEditFilter, QtCore.Qt.Key_Enter) diff --git a/qtawesome/tests/test_qtawesome.py b/qtawesome/tests/test_qtawesome.py index 52c17af..4b431fa 100644 --- a/qtawesome/tests/test_qtawesome.py +++ b/qtawesome/tests/test_qtawesome.py @@ -1,6 +1,7 @@ r""" Tests for QtAwesome. """ + # Standard library imports import collections import os @@ -16,8 +17,9 @@ def test_segfault_import(): - output_number = subprocess.call(sys.executable + ' -c "import qtawesome ' - '; qtawesome.icon()"', shell=True) + output_number = subprocess.call( + sys.executable + ' -c "import qtawesome ' '; qtawesome.icon()"', shell=True + ) assert output_number == 0 @@ -38,8 +40,11 @@ def test_unique_font_family_name(qtbot): assert fontnames # Check that qtawesome does not load fonts with duplicate family names. - duplicates = [fontname for fontname, count in - collections.Counter(fontnames).items() if count > 1] + duplicates = [ + fontname + for fontname, count in collections.Counter(fontnames).items() + if count > 1 + ] assert not duplicates @@ -47,7 +52,7 @@ def test_unique_font_family_name(qtbot): def test_bundled_font_installation(): """ Test that the bundled fonts are being installed on Windows. - + See spyder-ide/qtawesome#167 and spyder-ide/spyder#18642 """ qta._instance() @@ -57,7 +62,7 @@ def test_bundled_font_installation(): ] fonts_command = [ "powershell.exe", - r'Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts"' + r'Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts"', ] fonts_result = subprocess.run( fonts_command, capture_output=True, check=True, text=True diff --git a/setup.py b/setup.py index 5d6057a..2191b05 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ import io from setuptools import setup -from setuptools.command.install import install # Code to add custom build commands comes from here: import setupbase @@ -11,47 +10,47 @@ HERE = os.path.abspath(os.path.dirname(__file__)) VERSION_NS = {} -with open(os.path.join(HERE, 'qtawesome', '_version.py')) as f: +with open(os.path.join(HERE, "qtawesome", "_version.py")) as f: exec(f.read(), {}, VERSION_NS) -with io.open(os.path.join(HERE, 'README.md'), encoding='utf-8') as f: +with io.open(os.path.join(HERE, "README.md"), encoding="utf-8") as f: LONG_DESCRIPTION = f.read() setup( - name='QtAwesome', - version=VERSION_NS['__version__'], - description='FontAwesome icons in PyQt and PySide applications', + name="QtAwesome", + version=VERSION_NS["__version__"], + description="FontAwesome icons in PyQt and PySide applications", long_description=LONG_DESCRIPTION, - long_description_content_type='text/markdown', - author='Sylvain Corlay and the Spyder Development Team', - author_email='spyder.python@gmail.com', - maintainer='Spyder Development Team and QtAwesome Contributors', - maintainer_email='spyder.python@gmail.com', - license='MIT', - url='https://github.com/spyder-ide/qtawesome', - keywords=['PyQt', 'PySide', 'Icons', 'Font Awesome', 'Fonts'], - packages=['qtawesome'], - install_requires=['qtpy'], + long_description_content_type="text/markdown", + author="Sylvain Corlay and the Spyder Development Team", + author_email="spyder.python@gmail.com", + maintainer="Spyder Development Team and QtAwesome Contributors", + maintainer_email="spyder.python@gmail.com", + license="MIT", + url="https://github.com/spyder-ide/qtawesome", + keywords=["PyQt", "PySide", "Icons", "Font Awesome", "Fonts"], + packages=["qtawesome"], + install_requires=["qtpy"], include_package_data=True, - python_requires='>=3.7', - platforms=['OS-independent'], + python_requires=">=3.7", + platforms=["OS-independent"], classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: User Interfaces', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Software Development :: User Interfaces", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ], cmdclass={ - 'update_fa5': setupbase.UpdateFA5Command, - 'update_msc': setupbase.UpdateCodiconCommand, + "update_fa5": setupbase.UpdateFA5Command, + "update_msc": setupbase.UpdateCodiconCommand, }, entry_points={ - 'console_scripts': ['qta-browser=qtawesome.icon_browser:run'], - } + "console_scripts": ["qta-browser=qtawesome.icon_browser:run"], + }, ) diff --git a/setupbase.py b/setupbase.py index e93ed54..6abcb3a 100644 --- a/setupbase.py +++ b/setupbase.py @@ -19,7 +19,7 @@ LOG_INFO = 2 HERE = os.path.abspath(os.path.dirname(__file__)) -INIT_PY_PATH = os.path.join(HERE, 'qtawesome', '__init__.py') +INIT_PY_PATH = os.path.join(HERE, "qtawesome", "__init__.py") def rename_font(font_path, font_name): @@ -40,7 +40,9 @@ def rename_font(font_path, font_name): # test that a variant name was found in the OpenType tables of the font if len(variant) == 0: raise ValueError( - "Unable to detect the font variant from the OpenType name table in: %s" % font_path) + "Unable to detect the font variant from the OpenType name table in: %s" + % font_path + ) # Here are some sample name records to give you an idea of the name tables: # ID 0: 'Copyright (c) Font Awesome' @@ -61,7 +63,9 @@ def rename_font(font_path, font_name): # modify the opentype table data in memory with updated values for record in namerecord_list: if record.nameID in (1, 4, 16, 21): - print(f"Renaming font name record at ID {record.nameID}: {record.string} --> {font_name}") + print( + f"Renaming font name record at ID {record.nameID}: {record.string} --> {font_name}" + ) record.string = font_name # write changes to the font file @@ -69,19 +73,21 @@ def rename_font(font_path, font_name): tt.save(font_path, reorderTables=False) except Exception: raise RuntimeError( - f"ERROR: unable to write new name to OpenType tables for: {font_path}") + f"ERROR: unable to write new name to OpenType tables for: {font_path}" + ) class UpdateFA5Command(setuptools.Command): """A custom command to make updating FontAwesome 5.x easy!""" - description = 'Try to update the FontAwesome 5.x data in the project.' + + description = "Try to update the FontAwesome 5.x data in the project." user_options = [ - ('fa-version=', None, 'FA version.'), - ('zip-path=', None, 'Read from local zip file path.'), + ("fa-version=", None, "FA version."), + ("zip-path=", None, "Read from local zip file path."), ] # Update these below if the FontAwesome changes their structure: - FA_STYLES = ('regular', 'solid', 'brands') + FA_STYLES = ("regular", "solid", "brands") CHARMAP_PATH_TEMPLATE = os.path.join( HERE, "qtawesome", @@ -89,25 +95,25 @@ class UpdateFA5Command(setuptools.Command): "fontawesome5-{style}-webfont-charmap-{version}.json", ) TTF_PATH_TEMPLATE = os.path.join( - HERE, - "qtawesome", - "fonts", - "fontawesome5-{style}-webfont-{version}.ttf" + HERE, "qtawesome", "fonts", "fontawesome5-{style}-webfont-{version}.ttf" + ) + URL_TEMPLATE = ( + "https://github.com/FortAwesome/Font-Awesome/" + "releases/download/{version}/fontawesome-free-{version}-web.zip" ) - URL_TEMPLATE = 'https://github.com/FortAwesome/Font-Awesome/' \ - 'releases/download/{version}/fontawesome-free-{version}-web.zip' def initialize_options(self): """Set default values for the command options.""" - self.fa_version = '' - self.zip_path = '' + self.fa_version = "" + self.zip_path = "" def finalize_options(self): """Validate the command options.""" - assert bool(self.fa_version), 'FA version is mandatory for this command.' + assert bool(self.fa_version), "FA version is mandatory for this command." if self.zip_path: assert os.path.exists(self.zip_path), ( - 'Local zipfile does not exist: %s' % self.zip_path) + "Local zipfile does not exist: %s" % self.zip_path + ) def __print(self, msg): """Shortcut for printing with the setuptools logger.""" @@ -115,15 +121,11 @@ def __print(self, msg): def __get_charmap_path(self, style): """Get the project FA charmap path for a given style.""" - return self.CHARMAP_PATH_TEMPLATE.format( - style=style, version=self.fa_version - ) + return self.CHARMAP_PATH_TEMPLATE.format(style=style, version=self.fa_version) def __get_ttf_path(self, style): """Get the project FA font path for a given style.""" - return self.TTF_PATH_TEMPLATE.format( - style=style, version=self.fa_version - ) + return self.TTF_PATH_TEMPLATE.format(style=style, version=self.fa_version) @property def __release_url(self): @@ -135,12 +137,12 @@ def __zip_file(self): """Get a file object of the FA zip file.""" if self.zip_path: # If using a local file, just open it: - self.__print('Opening local zipfile: %s' % self.zip_path) - return open(self.zip_path, 'rb') + self.__print("Opening local zipfile: %s" % self.zip_path) + return open(self.zip_path, "rb") # Otherwise, download it and make a file object in-memory: url = self.__release_url - self.__print('Downloading from URL: %s' % url) + self.__print("Downloading from URL: %s" % url) response = urlopen(url) return io.BytesIO(response.read()) @@ -150,25 +152,26 @@ def __zipped_files_data(self): files = {} with zipfile.ZipFile(self.__zip_file) as thezip: for zipinfo in thezip.infolist(): - if zipinfo.filename.endswith('metadata/icons.json'): + if zipinfo.filename.endswith("metadata/icons.json"): with thezip.open(zipinfo) as compressed_file: - files['icons.json'] = compressed_file.read() - elif zipinfo.filename.endswith('.ttf'): + files["icons.json"] = compressed_file.read() + elif zipinfo.filename.endswith(".ttf"): # For the record, the paths usually look like this: # webfonts/fa-brands-400.ttf # webfonts/fa-regular-400.ttf # webfonts/fa-solid-900.ttf name = os.path.basename(zipinfo.filename) - tokens = name.split('-') + tokens = name.split("-") style = tokens[1] if style in self.FA_STYLES: with thezip.open(zipinfo) as compressed_file: files[style] = compressed_file.read() # Safety checks: - assert all(style in files for style in self.FA_STYLES), \ - 'Not all FA styles found! Update code is broken.' - assert 'icons.json' in files, 'icons.json not found! Update code is broken.' + assert all( + style in files for style in self.FA_STYLES + ), "Not all FA styles found! Update code is broken." + assert "icons.json" in files, "icons.json not found! Update code is broken." return files @@ -179,31 +182,31 @@ def run(self): icons = {} # Read icons.json (from the webfont zip download) - data = json.loads(files['icons.json']) + data = json.loads(files["icons.json"]) # Group icons by style, since not all icons exist for all styles: for icon, info in data.items(): - for style in info['styles']: + for style in info["styles"]: icons.setdefault(str(style), {}) - icons[str(style)][icon] = str(info['unicode']) + icons[str(style)][icon] = str(info["unicode"]) # For every FA "style": for style, details in icons.items(): # Dump a .json charmap file: charmapPath = self.__get_charmap_path(style) self.__print('Dumping updated "%s" charmap: %s' % (style, charmapPath)) - with open(charmapPath, 'w+') as f: + with open(charmapPath, "w+") as f: json.dump(details, f, indent=4, sort_keys=True) # Dump a .ttf font file: font_path = self.__get_ttf_path(style) data = files[style] self.__print('Dumping updated "%s" font: %s' % (style, font_path)) - with open(font_path, 'wb+') as f: + with open(font_path, "wb+") as f: f.write(data) # Fix to prevent repeated font names: - if style in ('regular', 'solid'): + if style in ("regular", "solid"): new_name = str("Font Awesome 5 Free %s") % style.title() self.__print('Renaming font to "%s" in: %s' % (new_name, font_path)) if ttLib is not None: @@ -211,10 +214,11 @@ def run(self): else: sys.exit( "This special command requires the module 'fonttools': " - "https://github.com/fonttools/fonttools/") + "https://github.com/fonttools/fonttools/" + ) # Reread the data since we just edited the font file: - with open(font_path, 'rb') as f: + with open(font_path, "rb") as f: data = f.read() files[style] = data @@ -223,54 +227,52 @@ def run(self): # Now it's time to patch "__init__.py": init_path = INIT_PY_PATH - self.__print('Patching new MD5 hashes in: %s' % init_path) - with open(init_path, 'r') as init_file: + self.__print("Patching new MD5 hashes in: %s" % init_path) + with open(init_path, "r") as init_file: contents = init_file.read() # We read it in full, then use regex substitution: for style, md5 in hashes.items(): self.__print('New "%s" hash is: %s' % (style, md5)) regex = r"('fontawesome5-%s-webfont-%s.ttf':\s+)'(\w+)'" % ( style, - self.fa_version + self.fa_version, ) subst = r"\g<1>'" + md5 + "'" contents = re.sub(regex, subst, contents, 1) # and finally overwrite with the modified file: - self.__print('Dumping updated file: %s' % init_path) - with open(init_path, 'w') as init_file: + self.__print("Dumping updated file: %s" % init_path) + with open(init_path, "w") as init_file: init_file.write(contents) self.__print( - '\nFinished!\n' - 'Please check the git diff to make sure everything went okay.\n' - 'You should also edit README.md and ' - 'qtawesome/docs/source/usage.rst to reflect the changes.') + "\nFinished!\n" + "Please check the git diff to make sure everything went okay.\n" + "You should also edit README.md and " + "qtawesome/docs/source/usage.rst to reflect the changes." + ) class UpdateCodiconCommand(setuptools.Command): """A custom command to make updating Microsoft's Codicons easy!""" - description = 'Try to update the Codicon font data in the project.' - user_options = [('msc-version=', None, 'Codicon version.')] + + description = "Try to update the Codicon font data in the project." + user_options = [("msc-version=", None, "Codicon version.")] CHARMAP_PATH = os.path.join( HERE, "qtawesome", "fonts", "codicon-charmap-{version}.json" ) - TTF_PATH = os.path.join( - HERE, "qtawesome", "fonts", "codicon-{version}.ttf" - ) - DOWNLOAD_URL_TTF = 'https://raw.githubusercontent.com/microsoft/vscode-codicons/{version}/dist/codicon.ttf' - DOWNLOAD_URL_CSS = 'https://raw.githubusercontent.com/microsoft/vscode-codicons/{version}/dist/codicon.css' - DOWNLOAD_URL_JSON = 'https://raw.githubusercontent.com/microsoft/vscode-codicons/{version}/package.json' + TTF_PATH = os.path.join(HERE, "qtawesome", "fonts", "codicon-{version}.ttf") + DOWNLOAD_URL_TTF = "https://raw.githubusercontent.com/microsoft/vscode-codicons/{version}/dist/codicon.ttf" + DOWNLOAD_URL_CSS = "https://raw.githubusercontent.com/microsoft/vscode-codicons/{version}/dist/codicon.css" + DOWNLOAD_URL_JSON = "https://raw.githubusercontent.com/microsoft/vscode-codicons/{version}/package.json" def initialize_options(self): """Set default values for the command options.""" - self.msc_version = '' + self.msc_version = "" def finalize_options(self): """Validate the command options.""" - assert bool( - self.msc_version - ), 'Codicons version is mandatory for this command.' + assert bool(self.msc_version), "Codicons version is mandatory for this command." def __print(self, msg): """Shortcut for printing with the setuptools logger.""" @@ -280,12 +282,10 @@ def run(self): """Run command.""" # Download .json to a temporary path: - download_url_json = self.DOWNLOAD_URL_JSON.format( - version=self.msc_version - ) + download_url_json = self.DOWNLOAD_URL_JSON.format(version=self.msc_version) package_json = urlopen(download_url_json) package_info = json.load(package_json) - package_version = package_info['version'] + package_version = package_info["version"] assert self.msc_version == package_version, ( "Codicons version does not match with `package.json` info. %s and %s" % ( @@ -293,15 +293,13 @@ def run(self): package_version, ) ) - self.__print('Will download codicons version: %s' % package_version) + self.__print("Will download codicons version: %s" % package_version) # Download .css: - donwload_url_css = self.DOWNLOAD_URL_CSS.format( - version=self.msc_version - ) + donwload_url_css = self.DOWNLOAD_URL_CSS.format(version=self.msc_version) req = urlopen(donwload_url_css) if req.status != 200: - raise Exception('Failed to download CSS Charmap') + raise Exception("Failed to download CSS Charmap") rawcss = req.read().decode() req.close() @@ -311,45 +309,42 @@ def run(self): pattern = '^\.codicon-(.+):before {\s*content: "(.+)"\s*}$' data = re.findall(pattern, rawcss, re.MULTILINE) for name, key in data: - key = key.replace('\\', '0x') + key = key.replace("\\", "0x") name = name.lower() charmap[name] = key - self.__print('Identified %s icons in the CSS.' % len(charmap)) + self.__print("Identified %s icons in the CSS." % len(charmap)) # Dump a .json charmap file the way we like it: charmap_path = self.CHARMAP_PATH.format(version=package_version) - self.__print('Dumping updated charmap: %s' % charmap_path) - with open(charmap_path, 'w+') as f: + self.__print("Dumping updated charmap: %s" % charmap_path) + with open(charmap_path, "w+") as f: json.dump(charmap, f, indent=4, sort_keys=True) # Dump a .ttf font file: - download_url_ttf = self.DOWNLOAD_URL_TTF.format( - version=self.msc_version - ) + download_url_ttf = self.DOWNLOAD_URL_TTF.format(version=self.msc_version) ttf_path = self.TTF_PATH.format(version=package_version) - with open(ttf_path, 'wb+') as ttfFile: - self.__print( - "Downloading %s --> %s" % (download_url_ttf, ttf_path) - ) + with open(ttf_path, "wb+") as ttfFile: + self.__print("Downloading %s --> %s" % (download_url_ttf, ttf_path)) response = urlopen(download_url_ttf) data = response.read() ttfFile.write(data) md5 = hashlib.md5(data).hexdigest() - self.__print('New hash is: %s' % md5) + self.__print("New hash is: %s" % md5) # Now it's time to patch "__init__.py": - self.__print('Patching new MD5 hashes in: %s' % INIT_PY_PATH) - with open(INIT_PY_PATH, 'r') as init_file: + self.__print("Patching new MD5 hashes in: %s" % INIT_PY_PATH) + with open(INIT_PY_PATH, "r") as init_file: contents = init_file.read() regex = r"('codicon-%s.ttf':\s+)'(\w+)'" % self.msc_version subst = r"\g<1>'" + md5 + "'" contents = re.sub(regex, subst, contents, 1) - self.__print('Dumping updated file: %s' % INIT_PY_PATH) - with open(INIT_PY_PATH, 'w') as init_file: + self.__print("Dumping updated file: %s" % INIT_PY_PATH) + with open(INIT_PY_PATH, "w") as init_file: init_file.write(contents) self.__print( - '\nFinished!\n' - 'Please check the git diff to make sure everything went okay.\n' - 'You should also edit README.md and ' - 'qtawesome/docs/source/usage.rst to reflect the changes.') + "\nFinished!\n" + "Please check the git diff to make sure everything went okay.\n" + "You should also edit README.md and " + "qtawesome/docs/source/usage.rst to reflect the changes." + )