diff options
Diffstat (limited to 'src/theming.py')
-rw-r--r-- | src/theming.py | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/src/theming.py b/src/theming.py new file mode 100644 index 00000000..97a09540 --- /dev/null +++ b/src/theming.py @@ -0,0 +1,232 @@ +# Copyright 2010-2011 Florent Le Coz <louiz@louiz.org> +# +# This file is part of Poezio. +# +# Poezio is free software: you can redistribute it and/or modify +# it under the terms of the zlib license. See the COPYING file. + +""" +Define the variables (colors and some other stuff) that are +used when drawing the interface. + +Colors are numbers from -1 to 7 (if only 8 colors are supported) or -1 to 255 +if 256 colors are available. +We check the number of available colors at startup, and we load a theme accordingly. +A 8 color theme should NEVER use colors not in the -1 -> 7 range. We won't check that +at run time. If the case occurs, the THEME should be fixed. +XHTML-IM colors are converted to -1 -> 255 colors if available, or directly to +-1 -> 8 if we are in 8-color-mode. + +A pair_color is a background-foreground pair. All possible pairs are not created +at startup, because that would create 256*256 pairs, and almost all of them +would never be used. +So, a theme should define color tuples, like (200, -1), and when they are to +be used by poezio's interface, they will be created once, and kept in a list for +later usage. +A color tuple is of the form (foreground, background, optional) +A color of -1 means the default color. So if you do not want to have +a background color, use (x, -1). +The optional third value of the tuple defines additional information. It +is a string and can contain one or more of these characteres: +'b': bold +'u': underlined +'x': blink + +For example, (200, 208, 'bu') is bold, underlined and pink foreground on orange background. + +A theme file is a python file containing two objects named 'theme' and 'theme8', which both are +instances of two classes (derived from the Theme class) defined in that same file. +For example, in darktheme.py: + +import theming +class DarkTheme(theming.Theme): + COLOR_NORMAL_TEXT = (200, -1) + [...] + +class DarkTheme8(theming.Theme): + COLOR_NORMAL_TEXT = (1, -1) + [...] + +theme = DarkTheme() +theme8 = DarkTheme8() + +if the command '/theme darktheme' is issued, we import the darktheme.py file +and set the global variable 'theme' to darktheme.theme. + +And in poezio's code we just use theme.COLOR_NORMAL_TEXT etc + +Since a theme inherites from the Theme class (defined here), if a color is not defined in a +theme file, the color is the default one. + +Some values in that class are a list of color tuple. +For example [(1, -1), (2, -1), (3, -1)] +Such a list SHOULD contain at list one color tuple. +It is used for example to define color gradient, etc. +""" + +import logging +log = logging.getLogger(__name__) + +from config import config + +import curses +import imp +import os + +class Theme(object): + """ + The theme class, from which all theme should inherit. + All of the following value can be replaced in subclasses, in + order to create a new theme. + Do not edit this file if you want to change the theme to suit your + needs. Create a new theme and share it if you think it can be useful + for others. + """ + # Message text color + COLOR_NORMAL_TEXT = (-1, -1) + COLOR_INFORMATION_TEXT = (137, -1) + COLOR_HIGHLIGHT_NICK = (208, 22) + + # User list color + COLOR_USER_VISITOR = (13, -1) + COLOR_USER_PARTICIPANT = (7, -1) + COLOR_USER_NONE = (243, -1) + COLOR_USER_MODERATOR = (160, -1) + + # nickname colors + COLOR_REMOTE_USER = (5, -1) + + # The character printed in color (COLOR_STATUS_*) before the nickname + # in the user list + CHAR_STATUS = '|' + + # Separators + COLOR_VERTICAL_SEPARATOR = (4, -1) + COLOR_NEW_TEXT_SEPARATOR = (2, -1) + COLOR_MORE_INDICATOR = (6, 4) + + # Time + COLOR_TIME_SEPARATOR = (6, -1) + COLOR_TIME_LIMITER = (0, -1) + CHAR_TIME_LEFT = '' + CHAR_TIME_RIGHT = '' + COLOR_TIME_NUMBERS = (0, -1) + + # Tabs + COLOR_TAB_NORMAL = (7, 4) + COLOR_TAB_CURRENT = (7, 6) + COLOR_TAB_NEW_MESSAGE = (7, 3) + COLOR_TAB_HIGHLIGHT = (7, 9) + COLOR_TAB_PRIVATE = (7, 2) + COLOR_TAB_DISCONNECTED = (7, 8) + + # Nickname colors + LIST_COLOR_NICKNAMES = [(1, -1), (2, -1), (3, -1), (4, -1), (5, -1), (6, -1), (7, -1), (8, -1), (21, -1), (154, -1), (202, -1), (123, -1), (216, -1)] + COLOR_OWN_NICK = (254, -1) + + # Status color + COLOR_STATUS_XA = (234, 90) + COLOR_STATUS_NONE = (234, 4) + COLOR_STATUS_DND = (234, 1) + COLOR_STATUS_AWAY = (234, 3) + COLOR_STATUS_CHAT = (234, 2) + COLOR_STATUS_UNAVAILABLE = (-1, 247) + COLOR_STATUS_ONLINE = (234, 4) + + # Bars + COLOR_INFORMATION_BAR = (7, 4) + COLOR_TOPIC_BAR = (7, 4) + COLOR_PRIVATE_ROOM_BAR = (-1, 4) + COLOR_SCROLLABLE_NUMBER = (214, 4, 'b') + COLOR_SELECTED_ROW = (-1, 33) + COLOR_PRIVATE_NAME = (-1, 4) + COLOR_CONVERSATION_NAME = (4, 4) + COLOR_GROUPCHAT_NAME = (7, 4) + COLOR_COLUMN_HEADER = (36, 4) + + # Strings for special messages (like join, quit, nick change, etc) + # Special messages + CHAR_JOIN = '--->' + CHAR_QUIT = '<---' + CHAR_KICK = '-!-' + + COLOR_JOIN_CHAR = (4, -1) + COLOR_QUIT_CHAR = (1, -1) + COLOR_KICK_CHAR = (1, -1) + +# This is the default theme object, used if no theme is defined in the conf +theme = Theme() + +# a dict "color tuple -> color_pair" +# Each time we use a color tuple, we check if it has already been used. +# If not we create a new color_pair and keep it in that dict, to use it +# the next time. +curses_colors_dict = {} + +def to_curses_attr(color_tuple): + """ + Takes a color tuple (as defined at the top of this file) and + returns a valid curses attr that can be passed directly to attron() or attroff() + """ + # extract the color from that tuple + if len(color_tuple) == 3: + colors = (color_tuple[0], color_tuple[1]) + else: + colors = color_tuple + + # check if we already used these colors + try: + pair = curses_colors_dict[colors] + except KeyError: + pair = len(curses_colors_dict) + 1 + curses.init_pair(pair, colors[0], colors[1]) + curses_colors_dict[colors] = pair + log.debug('New pair: %s (%s)' % (pair, colors,)) + curses_pair = curses.color_pair(pair) + if len(color_tuple) == 3: + additional_val = color_tuple[2] + if 'b' in additional_val: + curses_pair = curses_pair | curses.A_BOLD + if 'u' in additional_val: + curses_pair = curses_pair | curses.A_UNDERLINE + if 'x' in additional_val: + curses_pair = curses_pair | curses.A_BLINK + return curses_pair + +def get_theme(): + """ + Returns the current theme + """ + return theme + +def reload_theme(): + themes_dir = config.get('themes_dir', '') + themes_dir = themes_dir or\ + os.path.join(os.environ.get('XDG_DATA_HOME') or\ + os.path.join(os.environ.get('HOME'), '.local', 'share'), + 'poezio', 'themes') + try: + os.makedirs(themes_dir) + except OSError: + pass + theme_name = config.get('theme', '') + if not theme_name: + return + try: + new_theme = imp.load_source('theme', os.path.join(themes_dir, theme_name)) + except: # TODO warning: theme not found + return + global theme + theme = new_theme + +if __name__ == '__main__': + """ + Display some nice text with nice colors + """ + s = curses.initscr() + curses.start_color() + curses.use_default_colors() + s.addstr('%s' % curses.COLORS, to_curses_attr((3, -1, 'x'))) + s.refresh() + s.getkey() + curses.endwin() |