Source code for schrodinger.ui.qt.standard.icons.icons

"""
Module to access all Schrodinger standard icons filepaths. Instructions on
using, adding, and editing icons can be found below.

====================
Using standard icons
====================

Standard icon filepaths are exposed as constants. To browse the available icons
and what constants they correspond to, run::

    $SCHRODINGER/run catalog_gui.py

Once an icon of interest has been identified, import `icons.py` and access
the icon's path. Example::

    from schrodinger.ui.qt.standard.icons import icons
    from schrodinger.Qt import QtGui
    ok_lb_icon = QtGui.QIcon(icons.OK_LB)

=====================
Adding standard icons
=====================

1) If the new icon belongs to an existing family (e.g. a new icon state
    variation), skip to step 2.

   If the new icon does not belong to an existing family, add a blank row to
   the metadata csv (schrodinger/ui/qt/standard/icons/icon_data/icon_data.csv)
   and fill in the columns.:

    - **Action:** The action for the icon (UX should provide)
    - **Keywords:** Any words not already present in `Action` that would be helpful
        to search for that icon. Should be space-separated. (UX can provide,
        but devs can also supplement with whatever seems helpful)
    - **Directory name:** The name of the directory where all icons in this family
        will be stored. This should pretty much be the same as the action in
        most cases but lowercase and hyphen-separated. (UX won't provide. Use
        best judgment to make most concise name possible)
    - **Original File Name:** Copy/paste the file name that UX gives you. This
        helps map the new filenames to the old ones that UX has in case UX
        needs to know which is which.
    - **Current or Proposed Symbol:** What the symbol looks like (Include if UX
        provides, but not essential)

2) Rename the icon file according to the naming convention outlined below in
    "File structure". If this is a completely new family of icons, also create
    the icon subdirectory, as outlined in "File structure".

3) Place the icon into the appropriate icon subdirectory.

4) Run unit tests. If test_get_icon_metadata_from_csv() fails, run
    test_identify_undetected_files() (skipped by default) to identify which
    icon files are not being registered by the module. Most likely there is
    misspelling in the metadata csv, the directory name, or the file name.

5) Confirm the icon appears as expected in the icon viewer with the desired
    search terms. If not, edit the metadata csv as needed.

6) Commit changes.

======================
Editing standard icons
======================

1) Edit the metadata csv as needed. See 1) from "Adding standard icons" for
    information on each of the csv columns.

2) Perform 4) - 6) from "Adding standard icons"

==============
File structure
==============

The following must be true in order for this to work properly.

1) An icon metadata csv must be present at
    schrodinger/ui/qt/standard/icons/icon_data/icon_data.csv.
2) The first four columns of the CSV must be 'Variable name', 'Action',
    'Keywords', and 'Directory name' in that order. Other columns are ignored,
    so they can contain other any information helpful to whoever is
    editing the CSV.
3) Each row of the CSV corresponds to only the default version of an icon.
4) The icons directory MUST follow a particular structure::

    - icons
    | - <icon-dir-name-1>
      | - <icon-dir-name-1>_<icon_state_information_1>.png
      | - <icon-dir-name-1>_<icon_state_information_2>.png
      | - ...
      | - <icon-dir-name-1>_<icon_state_information_N>.png
    | - <icon-dir-name-2>
      | - etc.

    Note that each icon in an icon subdirectory must have a base name equal
    to the directory name. Dashes are used in place of spaces for the dir name.
    Each icon filename then has additional information denoting its icon state
    and is separated by underscores. This naming convention helps us identify
    the icon family name from just the icon's file name.

    Here's an example using the collapse-chevron-vert subdirectory::

    | - collapse-chevron-vert
      | - collapse-chevron-vert_db_d.png
      | - collapse-chevron-vert_db_h.png
      | - collapse-chevron-vert_db.png
      | - collapse-chevron-vert_lb_d.png
      | - collapse-chevron-vert_lb_h.png
      | - collapse-chevron-vert_lb.png

5) See LEGEND_TEXT below for a description of the different icon suffixes.
"""

import csv
from pathlib import Path

from schrodinger.models import parameters

LEGEND_TEXT = """
Each set of letters separated by underscores describes the icon state:
    lb = light background
    db = dark background
    eb = either background (works for light/dark)
    hvr = hover state
    dis = disabled state
    act = active state
    clk = click state (during a click event)

Icons with just "_lb" or "_db" are the default light or dark background
state versions of the icon, whereas those with e.g. "_lb_hvr" would be the
hover state of the icon against a light background panel.

Some other icon states like "on", "off", and special cases will be written
out completely since they are uncommon, and thus don't need any
special codes like the above.
"""

_ICONS_DIR = Path(__file__).parent
_ICON_DATA_DIR = _ICONS_DIR / 'icon_data'
_ICON_DATA_CSV = _ICON_DATA_DIR / 'icon_data.csv'
# str(path_obj) should make the string appropriate to each OS
_ICON_DATA_CSV_PATH = str(_ICON_DATA_CSV)
_ICON_METADATA_MAP = {}


[docs]class IconMetadata(parameters.CompoundParam): """ Param to store the metadata for a given icon file. """ var_name: str action: str filepath: str filter_terms: str
[docs]def get_icon_metadata_from_csv(): """ Get the icon metadata for all icons files in the icons dir from _ICON_DATA_CSV. Results are stored in a module-level dictionary mapping variable names to the corresponding IconMetadata object. If the module-level dictionary has already been populated, simply return it without reading the metadata csv again. :return: Mapping of icon variable names to their IconMetadata objects :rtype: dict(str: IconMetadata) """ if _ICON_METADATA_MAP: return _ICON_METADATA_MAP with open(_ICON_DATA_CSV_PATH, 'r') as all_metadata: metadata_reader = csv.reader(all_metadata) header = next(metadata_reader) if header is not None: for row in metadata_reader: action, filter_terms, icon_family_name = row[:3] icon_states_metadata = _get_icon_states_metadata( action, filter_terms, icon_family_name) _ICON_METADATA_MAP.update(icon_states_metadata) return _ICON_METADATA_MAP
def _get_icon_states_metadata(action, filter_terms, icon_family_name): """ Get the metadata of any and all states of an icon. :param action: The action that this icon family is meant to portray. :type action: str :param filter_terms: A collection of terms that one could use to filter this particular icon family out from a larger set of icons. It is space-separated for simplicity because ultimately filtering is done via regular expression matching. :type filter_terms: str :param icon_family_name: The name of the given icon family. Also the name of the subdirectory inside ./icons_dir where these icon files live. :type icon_family_name: str :return: Dict of variable names mapped to IconMetadata objects for all icons of the given `icon_family_name`. :rtype: Dict(str: IconMetadata) """ icon_family_dir = _ICON_DATA_DIR / icon_family_name states_metadata = {} for icon_state_path in icon_family_dir.glob('*.png'): var_name = _generate_var_name(icon_state_path, icon_family_name) if var_name is None: continue # Convert to posix path for Qt to be happy with windows paths filepath = icon_state_path.as_posix() state_metadata = IconMetadata(var_name=var_name, action=action, filepath=filepath, filter_terms=filter_terms) states_metadata[var_name] = state_metadata return states_metadata def _generate_var_name(icon_path, family_name): """ Generate the variable name of a given icon path. If the icon's name is misspelled or belongs to a different icon family entirely (e.g. file got misplaced), return None instead of a variable name. See unit test for examples. :param icon_path: The absolute path to an icon :type icon_path: pathlib.Path :param family_name: The name of an icon family. Also the name of the subdirectory inside ./icons_dir where `icon_path` is located. :type family_name: str :return: The variable name or None :rtype: str or None """ var_name = icon_path.stem fam_name_end_idx = var_name.find('_') icon_family_name = var_name[:fam_name_end_idx] if icon_family_name != family_name: return None return var_name.upper().replace('-', '_') def __getattr__(name): """ Return any attribute that exists in this module. The idea is to expose icon filepaths with the following API: from schrodinger.ui.qt.icons import icons ok_lb_path = icons.OK_LB All icon filepath global variables are assigned dynamically by _initialize(), which thus must be called the first time a filepath variable is accessed in this module. In order to avoid repeated access to the metadata csv and variable declaration, we cache _initialize(). :return: The requested attribute :raise: AttributeError when the requested attribute doesn't exist. """ icon_path_map = get_icon_metadata_from_csv() if name in globals().keys(): return globals()[name] elif name in icon_path_map.keys(): return icon_path_map[name].filepath raise AttributeError(f"module {__name__} has no attribute {name}")