Changeset 186

Show
Ignore:
Timestamp:
03/20/2006 12:16:17 PM (3 years ago)
Author:
petli
Message:

New mixin: DynamicColorBorderClient?. border.py was restructured for this, but the old BorderClient? should still work as it used to.

Location:
trunk
Files:
3 modified

Legend:

Unmodified
Added
Removed
  • trunk/NEWS

    r172 r186  
    88wmevents.ClientIconified and wmevents.ClientDeiconified is sent, 
    99respectively.  
     10 
     11** New border mixin: DynamicColorBorderClient 
     12 
     13This mixin chooses the border color dynamically based on the window 
     14title.  Will only work well on non-palette visuals, on PseudoColor 
     15displays and the like it will soon have gobbled all colors. 
    1016 
    1117 
  • trunk/examples/petliwm.py

    r184 r186  
    173173class MyClient(wmanager.Client, 
    174174               outline.XorOutlineClient, 
    175                border.BorderClient, 
     175               border.DynamicColorBorderClient, 
    176176               modestatus.ModeFocusedTitleClient, 
    177177               misc.InitialKeepOnScreenClient, 
     
    189189    border_color_name = "grey20" 
    190190    border_focuscolor_name = "grey60" 
     191 
     192    border_dynamic_saturation = 0.3 
     193    border_dynamic_brightness = 0.7 
     194 
     195    border_dynamic_focus_saturation = 1.0 
     196    border_dynamic_focus_brightness = 0.7 
    191197 
    192198    traceim_filters = [ 
  • trunk/plwm/border.py

    r121 r186  
    1 # $Id: border.py,v 1.7 2002-03-04 13:35:59 petli Exp $ 
     1# $Id: border.py,v 1.8 2006-03-20 18:16:17 petli Exp $ 
    22# 
    33# border.py -- change border color on focused client 
     
    1919#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
    2020 
    21 import wmevents, cfilter 
    22  
    23 class BorderClient: 
     21from Xlib import X, Xatom 
     22import wmanager, wmevents, cfilter 
     23 
     24class BorderClientBase: 
    2425    no_border_clients = cfilter.false 
    2526    border_default_width = 3 
     
    2930     
    3031    def __client_init__(self): 
     32        self.border_color = None 
     33        self.border_focuscolor = None 
     34 
    3135        if self.no_border_clients(self): 
    32             self.border_color = None 
    33             self.border_focuscolor = None 
    3436            self.setborderwidth(0) 
    3537 
    3638        else: 
    37             resname = self.wm.rdb_get('.border.color', '.Border.Color', '#000000') 
    38             if self.border_color_name is not None: 
    39                 self.border_color = self.screen.get_color( 
    40                     self.border_color_name, default = resname) 
    41             else: 
    42                 self.border_color = self.screen.get_color(resname) 
    43  
    44             resname = self.wm.rdb_get('.border.focus.color', 
    45                                       '.Border.Focus.Color', '#000000') 
    46             if self.border_focuscolor_name is not None: 
    47                 self.border_focuscolor = self.screen.get_color( 
    48                     self.border_focuscolor_name, default = resname) 
    49             else: 
    50                 self.border_focuscolor = self.screen.get_color(resname) 
    51  
    5239            self.setborderwidth(self.border_default_width) 
    53             self.window.change_attributes(border_pixel = self.border_color)  
    5440 
    5541            self.dispatch.add_handler(wmevents.ClientFocusIn, self.border_get_focus) 
    5642            self.dispatch.add_handler(wmevents.ClientFocusOut, self.border_lose_focus) 
    57     
     43 
     44            self.border_set_colors() 
     45            self.border_update() 
     46 
     47    def border_update(self): 
     48        """Update the border color.  Call this after chaning 
     49        border_color or border_focuscolor 
     50        """ 
     51        if self.focused: 
     52            if self.border_focuscolor is not None: 
     53                self.window.change_attributes(border_pixel = self.border_focuscolor) 
     54        else: 
     55            if self.border_color is not None: 
     56                self.window.change_attributes(border_pixel = self.border_color)  
     57 
     58    def border_set_colors(self): 
     59        """Override this to set border_color and border_focuscolor. 
     60        This is called during client initialization to get the initial colors. 
     61        """ 
     62        raise NotImplementedError('%s.border_set_colors()' % self.__class__) 
     63             
    5864    def border_get_focus(self, event): 
    5965        if self.border_focuscolor is not None: 
     
    6470            self.window.change_attributes(border_pixel = self.border_color) 
    6571 
     72 
     73class BorderClient(BorderClientBase): 
     74    """Set border color to one or another if the client is focused or not.""" 
     75    border_color_name = None 
     76    border_focuscolor_name = None 
     77 
     78    def border_set_colors(self): 
     79        resname = self.wm.rdb_get('.border.color', '.Border.Color', '#000000') 
     80        if self.border_color_name is not None: 
     81            self.border_color = self.screen.get_color( 
     82                self.border_color_name, default = resname) 
     83        else: 
     84            self.border_color = self.screen.get_color(resname) 
     85 
     86        resname = self.wm.rdb_get('.border.focus.color', 
     87                                  '.Border.Focus.Color', '#000000') 
     88        if self.border_focuscolor_name is not None: 
     89            self.border_focuscolor = self.screen.get_color( 
     90                self.border_focuscolor_name, default = resname) 
     91        else: 
     92            self.border_focuscolor = self.screen.get_color(resname) 
     93 
     94 
     95class DynamicColorBorderClient(BorderClientBase): 
     96 
     97    """Change the color of the border based on some client property, 
     98    by default the window title. 
     99 
     100    Obviously this class shouldn't be used with palette-based visuals. 
     101 
     102 
     103    Subclass and override border_dynamic_install_handler() and 
     104    border_dynamic_get_hue() to change this behaviour. 
     105 
     106    The hue will be the same for a client (as long as the title or 
     107    whatever doesn't change) but the saturation and brightness is 
     108    changed depending on the client is focused or not.  This is 
     109    controlled with four attributes, in the range [0.0, 1.0]: 
     110 
     111    Non-focused: 
     112      border_dynamic_saturation  
     113      border_dynamic_brightness 
     114 
     115    Focused: 
     116      border_dynamic_focus_saturation  
     117      border_dynamic_focus_brightness 
     118 
     119 
     120    However, experimentation shows that HSV isn't very good, as 
     121    confirmed by 
     122    http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC36 
     123 
     124    Varying the brightness changes the perceived color, only changing 
     125    the saturation works reasonably well.  This might be a problem 
     126    with the colorsys module, too. 
     127 
     128    If anyone manages to understand it, maybe the CIE XYZ model should 
     129    be tried instead. 
     130    """ 
     131 
     132    border_dynamic_saturation = 0.3 
     133    border_dynamic_brightness = 0.7 
     134 
     135    border_dynamic_focus_saturation = 1.0 
     136    border_dynamic_focus_brightness = 0.7 
     137 
     138    def __client_init__(self): 
     139        # FIXME: check visual, and scream if it isn't suitable for 
     140        # this kind of color-grabbing.  Even better: fall back on 
     141        # default colors. 
     142         
     143        BorderClientBase.__client_init__(self) 
     144        self.border_dynamic_install_handler() 
     145 
     146    # 
     147    # Override these methods to change color property 
     148    # 
     149     
     150    def border_dynamic_install_handler(self): 
     151        self.dispatch.add_handler(X.PropertyNotify, self.border_dynamic_property_notify) 
     152 
     153    def border_dynamic_get_hue(self): 
     154        """Return the hue for this client, in the range [0.0, 1.0]""" 
     155 
     156        # We can't really know the range of hash, but there's probably 
     157        # at least 24 bits.  Xor them into eight bits and use that for hue 
     158 
     159        th = hash(self.get_title()) 
     160 
     161        h = th & 0xff 
     162        h ^= (th >> 8) & 0xff 
     163        h ^= (th >> 16) & 0xff 
     164         
     165        return float(h) / 0xff 
     166 
     167    # 
     168    # Internalish methods 
     169    # 
     170 
     171    def border_dynamic_property_notify(self, event): 
     172        if event.atom == Xatom.WM_NAME: 
     173            self.border_set_colors() 
     174            self.border_update() 
     175 
     176    def border_set_colors(self): 
     177        hue = self.border_dynamic_get_hue() 
     178 
     179        self.border_color = self.border_dynamic_get_color_hsv( 
     180            hue, self.border_dynamic_saturation, 
     181            self.border_dynamic_brightness) 
     182 
     183        self.border_focuscolor = self.border_dynamic_get_color_hsv( 
     184            hue, self.border_dynamic_focus_saturation, 
     185            self.border_dynamic_focus_brightness) 
     186 
     187         
     188    def border_dynamic_get_color_hsv(self, hue, saturation, brightness): 
     189        import colorsys 
     190        r, g, b = colorsys.hsv_to_rgb(hue, saturation, brightness) 
     191        return self.screen.get_color((r * 65535, g * 65535, b * 65535)) 
     192 
     193    # 
     194    # I tested using chroma instead of HSV.  Didn't really work that 
     195    # much better, but I keep the code here if anyone want's to experiment with it 
     196    # 
     197     
     198    border_dynamic_luminance = 0.3 
     199    border_dynamic_focus_luminance = 0.7 
     200 
     201    def border_dynamic_get_chroma(self): 
     202        """Return the Cr, Cb for this client, in the range [0, 255]""" 
     203 
     204        h = hash(self.get_title()) 
     205        return h & 0xff, (h >> 8) & 0xff 
     206 
     207    def border_set_colors_chroma(self): 
     208        cr, cb = self.border_dynamic_get_chroma() 
     209 
     210        self.border_color = self.border_dynamic_get_color( 
     211            self.border_dynamic_luminance, cr, cb) 
     212        self.border_focuscolor = self.border_dynamic_get_color( 
     213            self.border_dynamic_focus_luminance, cr, cb) 
     214 
     215    def border_dynamic_get_chroma_color(self, y, cb, cr): 
     216        # See http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC30 
     217 
     218        # scale [0.0, 1.0] to [16, 235] => [0, 219] 
     219        y *= 219 
     220 
     221        # get cr and cb in range [-112, +112] 
     222        cb = max(min(cb - 128, 112), -112) 
     223        cr = max(min(cr - 128, 112), -112) 
     224 
     225        # Do the matrix multiplication, but skip the divide-by-256 as 
     226        # we actually want the range [0, 65535] 
     227         
     228        r = (298.082 * y + 408.583 * cr) 
     229        g = (298.082 * y - 100.291 * cb - 208.120 * cr) 
     230        b = (298.082 * y + 516.411 * cb) 
     231 
     232        # clamp into range 
     233        r = min(max(r, 0), 65535) 
     234        g = min(max(g, 0), 65535) 
     235        b = min(max(b, 0), 65535) 
     236 
     237        # and return the color 
     238        return self.screen.get_color((r, g, b))