The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*-
 * Copyright (c) 1997-2002 The Protein Laboratory, University of Copenhagen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id$
 */

/***********************************************************/
/*                                                         */
/*  System dependent color management                      */
/*                                                         */
/***********************************************************/

#include "unix/guts.h"
#include "Drawable.h"
#include "Window.h"

/* have two color layouts for panel widgets (lists, edits) and gray widgets (buttons, labels) */
#define COLOR_DEFAULT_TEXT         0x000000
#define COLOR_DEFAULT_GRAY         0xcccccc
#define COLOR_DEFAULT_BACK         0xffffff

#define COLOR_GRAY_NORMAL_TEXT     COLOR_DEFAULT_TEXT
#define COLOR_GRAY_NORMAL_BACK     COLOR_DEFAULT_GRAY
#define COLOR_GRAY_HILITE_TEXT     COLOR_DEFAULT_TEXT
#define COLOR_GRAY_HILITE_BACK     COLOR_DEFAULT_GRAY
#define COLOR_GRAY_DISABLED_TEXT   0x606060
#define COLOR_GRAY_DISABLED_BACK   0xcccccc

#define COLOR_PANEL_NORMAL_TEXT    COLOR_DEFAULT_TEXT
#define COLOR_PANEL_NORMAL_BACK    COLOR_DEFAULT_BACK
#define COLOR_PANEL_HILITE_TEXT    COLOR_DEFAULT_BACK
#define COLOR_PANEL_HILITE_BACK    COLOR_DEFAULT_TEXT
#define COLOR_PANEL_DISABLED_TEXT  0x606060
#define COLOR_PANEL_DISABLED_BACK  0xdddddd

#define COLOR_LIGHT3D              0xffffff
#define COLOR_DARK3D               0x808080

#define COLORSET_GRAY_NORMAL       COLOR_GRAY_NORMAL_TEXT,   COLOR_GRAY_NORMAL_BACK
#define COLORSET_GRAY_HILITE       COLOR_GRAY_HILITE_TEXT,   COLOR_GRAY_HILITE_BACK
#define COLORSET_GRAY_ALT_HILITE   COLOR_GRAY_HILITE_BACK,   COLOR_GRAY_HILITE_TEXT
#define COLORSET_GRAY_DISABLED     COLOR_GRAY_DISABLED_TEXT, COLOR_GRAY_DISABLED_BACK

#define COLORSET_PANEL_NORMAL      COLOR_PANEL_NORMAL_TEXT,   COLOR_PANEL_NORMAL_BACK
#define COLORSET_PANEL_HILITE      COLOR_PANEL_HILITE_TEXT,   COLOR_PANEL_HILITE_BACK
#define COLORSET_PANEL_DISABLED    COLOR_PANEL_DISABLED_TEXT, COLOR_PANEL_DISABLED_BACK

#define COLORSET_3D                COLOR_LIGHT3D, COLOR_DARK3D

#define COLORSET_GRAY              COLORSET_GRAY_NORMAL, COLORSET_GRAY_HILITE, \
                                   COLORSET_GRAY_DISABLED, COLORSET_3D
#define COLORSET_ALT_GRAY          COLORSET_GRAY_NORMAL, COLORSET_GRAY_ALT_HILITE, \
                                   COLORSET_GRAY_DISABLED, COLORSET_3D
#define COLORSET_PANEL             COLORSET_PANEL_NORMAL, COLORSET_PANEL_HILITE, \
                                   COLORSET_PANEL_DISABLED, COLORSET_3D

static Color standard_button_colors[]      = { COLORSET_GRAY     };
static Color standard_checkbox_colors[]    = { COLORSET_GRAY     };
static Color standard_combo_colors[]       = { COLORSET_GRAY     };
static Color standard_dialog_colors[]      = { COLORSET_GRAY     };
static Color standard_edit_colors[]        = { COLORSET_PANEL    };
static Color standard_inputline_colors[]   = { COLORSET_PANEL    };
static Color standard_label_colors[]       = { COLORSET_GRAY     };
static Color standard_listbox_colors[]     = { COLORSET_PANEL    };
static Color standard_menu_colors[]        = { COLORSET_ALT_GRAY };
static Color standard_popup_colors[]       = { COLORSET_ALT_GRAY };
static Color standard_radio_colors[]       = { COLORSET_GRAY     };
static Color standard_scrollbar_colors[]   = { COLORSET_ALT_GRAY };
static Color standard_slider_colors[]      = { COLORSET_GRAY     };
static Color standard_widget_colors[]      = { COLORSET_ALT_GRAY };
static Color standard_window_colors[]      = { COLORSET_GRAY     };
static Color standard_application_colors[] = { COLORSET_GRAY     };

static Color* standard_colors[] = {
   nil,
   standard_button_colors,		/* Prima.Button.* */
   standard_checkbox_colors,		/* Prima.Checkbox.* */
   standard_combo_colors,		/* Prima.Combo.* */
   standard_dialog_colors,		/* Prima.Dialog.* */
   standard_edit_colors,		/*   ...etc... */
   standard_inputline_colors,
   standard_label_colors,
   standard_listbox_colors,
   standard_menu_colors,
   standard_popup_colors,
   standard_radio_colors,
   standard_scrollbar_colors,
   standard_slider_colors,
   standard_widget_colors,
   standard_window_colors,
   standard_application_colors,
};

static const int MAX_COLOR_CLASS = sizeof( standard_colors) / sizeof( standard_colors[ 0]) - 1;

/* maps RGB or cl-constant value to RGB value.  */
Color 
prima_map_color( Color clr, int * hint)
{
   long cls;
   if ( hint) *hint = COLORHINT_NONE;
   if (( clr & clSysFlag) == 0) return clr;
   
   cls = (clr & wcMask) >> 16;
   if ( cls <= 0 || cls > MAX_COLOR_CLASS) cls = (wcWidget) >> 16;
   if (( clr = ( clr & ~wcMask)) > clMaxSysColor) clr = clMaxSysColor;
   if ( clr == clSet)   {
      if ( hint) *hint = COLORHINT_WHITE;
      return 0xffffff; 
   } else if ( clr == clClear) {
      if ( hint) *hint = COLORHINT_BLACK;
      return 0; 
   } else return standard_colors[cls][(clr & clSysMask) - 1];
}   

Color
apc_widget_map_color( Handle self, Color color)
{
   if ((( color & clSysFlag) != 0) && (( color & wcMask) == 0)) color |= PWidget(self)-> widgetClass;
   return prima_map_color( color, nil);
}   

static PHash  hatches;
static Bool   kill_hatches( Pixmap pixmap, int keyLen, void * key, void * dummy);
static Bool   prima_color_new( XColor * xc);

/*
static int card[256];
static int cardi = 0;
static Bool
my_XAllocColor( Display * disp, Colormap cm, XColor * xc, int line)
{
   if ( !cardi) {
      cardi = 1;
      bzero( card, 256*sizeof(int));
   }
   if ( !XAllocColor(disp, cm, xc)) {
      printf("Failed alloc of %02x%02x%02x, at %d\n", 
          xc-> red>>8, xc-> green>>8, xc-> blue>>8, line);
      return false;
   }
   printf("Alloc %02x%02x%02x, at %d: %d\n", 
          xc-> red>>8, xc-> green>>8, xc-> blue>>8,
          line, xc-> pixel);
   card[xc-> pixel]++;
   return true;
}

static void
my_XFreeColors( Display * disp, Colormap cm, long * ls, int count, long pal, int line)
{
   XSynchronize( DISP, true);
   printf("Free at %d:%d items\n", line, count);
   for ( pal = 0; pal < count; pal++, ls++) {
      printf("%ld.", *ls);
      XFreeColors( disp, cm, ls, 1, 0);
      XSync( disp, false);
      if ( !card[*ls]) printf("jopa!\n");
       else card[*ls]--;
   }
   printf("done\n");
}

#define XAllocColor(a,b,c) my_XAllocColor(a,b,c,__LINE__)
#define XFreeColors(a,b,c,d,e) my_XFreeColors(a,b,c,d,e,__LINE__)
*/

static Bool
alloc_color( XColor * c) 
{
   int diff = 5 * 256,
     r = c-> red,
     g = c-> green,
     b = c-> blue;
   if ( !XAllocColor( DISP, guts. defaultColormap, c)) return false;
   if ( 
      diff > abs( c-> red   - r) &&
      diff > abs( c-> green - g) &&
      diff > abs( c-> blue  - b)
   ) return true;
   XFreeColors( DISP, guts. defaultColormap, &c->pixel, 1, 0);
   return false;
}

/*
     Fills Brush structure. If dithering is needed,
  brush.secondary and brush.balance are set. Tries to
  get new colors via XAllocColor, assigns new color cells
  to self if successfull.
     If no brush structure is given, no dithering is
  preformed. 
     Returns closest matching color, always the same as
  brush-> primary.
 */
unsigned long
prima_allocate_color( Handle self, Color color, Brush * brush)
{
   DEFXX;
   Brush b;
   int a[3], hint;

   if ( !brush) brush = &b;
   brush-> balance = 0;
   brush-> color = color = prima_map_color( color, &hint);

   if ( hint) 
      return ( brush-> primary = (( hint == COLORHINT_BLACK) ? LOGCOLOR_BLACK : LOGCOLOR_WHITE));
   
   a[0] = COLOR_R(color);
   a[1] = COLOR_G(color);
   a[2] = COLOR_B(color);

   if ( guts. grayScale) {
      a[0] = a[1] = a[2] = ( a[0] + a[1] + a[2]) / 3;
      color = a[0] * ( 65536 + 256 + 1);
   }
   Pdebug("color: %s asked for %06x\n", self?PWidget(self)->name:"null", color);
   if (self && XT_IS_BITMAP(XX)) {
      Byte balance = ( a[0] + a[1] + a[2] + 6) / (3 * 4);
      if ( balance < 64) {
         brush-> primary   = 0;
         brush-> secondary = 1;
         brush-> balance   = balance;
      } else
         brush-> primary = 1;
   } else {
      if ( guts. palSize > 0) {
         int ab2;
         Bool dyna = guts. dynamicColors && self && X(self)-> type. widget && ( self != application);
         brush-> primary = prima_color_find( self, color, -1, &ab2, RANK_FREE);
         
         if ( dyna && ab2 > 12) {
            XColor xc;
            xc. red   = COLOR_R16(color);
            xc. green = COLOR_G16(color);
            xc. blue  = COLOR_B16(color);
            xc. flags = DoGreen | DoBlue | DoRed;
            prima_color_sync();
            if ( alloc_color( &xc)) {
               prima_color_new( &xc);
               Pdebug("color: %s alloc %d ( wanted %06x). got %02x %02x %02x\n", PWidget(self)-> name, xc.pixel, color, xc.red>>8,xc.green>>8,xc.blue>>8);
               prima_color_add_ref( self, xc. pixel, RANK_NORMAL);
               return brush-> primary = xc. pixel;
            }
         }

         if ( guts. useDithering && (brush != &b) && (ab2 > 12)) {
            if ( guts. grayScale && guts. systemColorMapSize > guts. palSize / 2) {
               int clr = ( COLOR_R(color) + COLOR_G(color) + 
                     COLOR_B(color)) / 3;
               int grd  = 256 / ( guts. systemColorMapSize - 1);
               int left = clr / grd;
               brush-> balance = ( clr - left * grd) * 64 / grd;
               brush-> primary = guts. systemColorMap[ left];
               brush-> secondary = guts. systemColorMap[ left + 1];
            } else {
               int i;
               Bool cubic = (XX-> type.dbm && guts. dynamicColors) ||
                            ( guts. colorCubeRib > 4);
DITHER:               
               if ( cubic) {
/* fast search of dithering colors - based on color cube.
   used either on restricted drawables ( as dbm) or when
   have enough colors - small cubes give noisy picture
                          
     .  .  .  *R"G"   assume here that blue component does not require dithering
 R |                  R'G' and R"G" are 2 colors blended with proprotion to make
   | '''* A           color A. R'G' is a closest cubic color. If A(G)/A(R) < y,
   |    |             R"G" is G-point, if A(G)/A(R) > 1 + y, it's R-point, otherwise
   *---------- G      it's RG-point. (y=sqrt(2)-1=0.41; y=0.41x and y=1.41x are
R'G' , B=0            maximal error lines). balance is computed as diff between
                      R'G' and R"G"
 */              
                  int base[3], l[3], z[3], r[3], cnt = 0, sum = 0;
                  int grd = 256 / ( guts. colorCubeRib - 1);
                  for ( i = 0; i < 3; i++) {
                     base[i] = a[i] / grd;
                     r[i] = l[i] = ( a[i] >= base[i] + grd / 2) ? 1 : 0;
                     z[i] = l[i] ? (base[i] + 1) * grd - a[i]: a[i] - base[i] * grd;
                  }
                  if ( z[1] > 1) {
                     int ratio1 = 100 * z[0] / z[1];
                     int ratio2 = 100 * z[2] / z[1];
                     if ( ratio1 > 59)  r[0] = r[0] ? 0 : 1;
                     if ( ratio2 > 59)  r[2] = r[2] ? 0 : 1;
                     if ( ratio1 < 141 && ratio2 < 141) r[1] = r[1] ? 0 : 1;
                  }  else if ( z[2] > 1) {
                     int ratio = 100 * z[0] / z[2];
                     if ( ratio > 59) r[0] = r[0] ? 0 : 1;
                     if ( ratio < 141) r[2] = r[2] ? 0 : 1;
                  } else if ( z[0] > 1) {
                     r[0] = r[0] ? 0 : 1;
                  }
                  for ( i = 0; i < 3; i++) 
                     if ( r[i] != l[i]) {
                        sum += z[i];
                        cnt++;
                     }
                  brush-> primary = guts. systemColorMap[ 
                      l[2] + base[2] + 
                    ( l[1] + base[1]) * guts.colorCubeRib + 
                    ( l[0] + base[0]) * guts.colorCubeRib * guts.colorCubeRib];
                  brush-> secondary = guts. systemColorMap[ 
                      r[2] + base[2] + 
                    ( r[1] + base[1]) * guts.colorCubeRib + 
                    ( r[0] + base[0]) * guts.colorCubeRib * guts.colorCubeRib];
                  brush-> balance = cnt ? (sum / cnt) * 64 / grd : 0;
               } else {
/*  slow search for dithering counterpart color; takes long time
    but gives closest possible colors. 

     A*          A - color to be expressed by B and D
     /|  .       B - closest color
    / |    .     D - candidate color
   /  |      .   C - closest color that can be expressed using B and D
  *---*-------*  The objective is to find such D whose AC is minimal and
  B   C       D  CD>BD. ( CD = (AD*AD-AB*AB+BD*BD)/2BD, AC=sqrt(AD*AD-CD*CD))
*/   
                  int b[3], d[3], i;
                  int ab2, bd2, ac2, ad2;
                  float cd, bd, BMcd=0, BMbd=0;
                  int maxDiff = 16777216, bestMatch = -1;
                  int mincd = maxDiff;
                  b[0] = guts. palette[brush-> primary].r;
                  b[1] = guts. palette[brush-> primary].g;
                  b[2] = guts. palette[brush-> primary].b;
                  Pdebug("color:want %06x, closest is %06x\n", color, guts.palette[brush-> primary].composite);
                  ab2 = (a[0]-b[0])*(a[0]-b[0]) +
                        (a[1]-b[1])*(a[1]-b[1]) +
                        (a[2]-b[2])*(a[2]-b[2]);
                  for ( i = 0; i < guts.palSize; i++) {
                     if ( guts.palette[i].rank == RANK_FREE) continue;
                     d[0] = guts. palette[i].r;
                     d[1] = guts. palette[i].g;
                     d[2] = guts. palette[i].b;
                     Pdebug("color:tasting %06x\n", guts.palette[i].composite);
                     bd2 = (d[0]-b[0])*(d[0]-b[0]) +
                           (d[1]-b[1])*(d[1]-b[1]) +
                           (d[2]-b[2])*(d[2]-b[2]);
                     bd  = sqrt( bd2);
                     if ( bd == 0) continue;
                     ad2 = (d[0]-a[0])*(d[0]-a[0]) +
                           (d[1]-a[1])*(d[1]-a[1]) +
                           (d[2]-a[2])*(d[2]-a[2]);
                     cd  = ( ad2 - ab2 + bd2) / (2 * bd);
                     Pdebug("color:bd:%g,bd2:%d, ad2:%d, cd:%g\n", bd, bd2, ad2, cd);
                     if ( cd < bd) {
                        ac2 = ad2 - cd * cd;
                        Pdebug("color:ac2:%d\n", ac2);
                        if ( ac2 < maxDiff || (( ac2 < maxDiff + 12) && (cd < mincd))) {
                           maxDiff = ac2;
                           bestMatch = i;
                           BMcd = cd;
                           BMbd = bd;
                           mincd = cd;
                           if ( mincd < 42) goto ENOUGH;
                        }
                     }
                  }
ENOUGH:;                  
                  if ( !guts. grayScale && maxDiff > (64/(guts.colorCubeRib-1))) {
                     cubic = true;
                     goto DITHER;
                  } 
                  brush-> secondary = bestMatch;
                  brush-> balance   = 63 - BMcd * 64 / BMbd;
               }
            }
         }
         
         if ( dyna) {
            if ( wlpal_get( self, brush-> primary) == RANK_FREE) 
               prima_color_add_ref( self, brush-> primary, RANK_NORMAL);
            if (( brush-> balance > 0) &&
               ( wlpal_get( self, brush->secondary) == RANK_FREE)) 
               prima_color_add_ref( self, brush-> secondary, RANK_NORMAL);
         }
      } else  
         brush-> primary = 
            (((a[0] << guts. red_range  ) >> 8) << guts.   red_shift) |
            (((a[1] << guts. green_range) >> 8) << guts. green_shift) |
            (((a[2] << guts. blue_range ) >> 8) << guts.  blue_shift);
   }
   return brush-> primary;
}


static Bool
alloc_main_color_range( XColor * xc, int count, int maxDiff)
{
   int idx;
   Bool err = false;

   if ( count > guts. palSize) return false;

   for ( idx = 0; idx < count; idx++) 
      xc[idx]. pixel = 0xFFFFFFFF;
   
   for ( idx = 0; idx < count; idx++) {
      int R = xc[idx]. red;
      int G = xc[idx]. green;
      int B = xc[idx]. blue;
      if ( !XAllocColor( DISP, guts. defaultColormap, &xc[idx])) {
          err = true; 
          break;
      }
      if ( xc[idx]. pixel >= guts. palSize) {
         warn("color index out of range returned from XAllocColor()\n");
         return false; 
      }
      if (( xc[idx]. blue / 256 - B / 256) * ( xc[idx]. blue / 256 - B / 256) +
          ( xc[idx]. green / 256 - G / 256) * ( xc[idx]. green / 256 - G / 256) +
          ( xc[idx]. red / 256 - R / 256) * ( xc[idx]. red / 256 - R / 256) > 
          maxDiff) {
         err = true;
         break;
      }
   }

   if ( err) {
      unsigned long cnt = 0, free[32];
      for ( idx = 0; idx < count; idx++) 
         if ( xc[idx]. pixel != 0xFFFFFFFF) {
            free[ cnt++] = xc[idx]. pixel;
            if ( cnt == 32) {
               XFreeColors( DISP, guts. defaultColormap, free, 32, 0);
               cnt = 0;
            }
         }
      if ( cnt > 0)
         XFreeColors( DISP, guts. defaultColormap, free, cnt, 0);
      return false;
   }
   return true;
}

static Bool
create_std_palettes( XColor * xc, int count)
{
   int idx;
   if ( !( guts. palette = malloc( sizeof( MainColorEntry) * guts. palSize)))
      return false;
   if ( !( guts. systemColorMap = malloc( sizeof( int) * count))) {
      free( guts. palette);
      guts. palette = nil;
      return false;
   }
   bzero( guts. palette, sizeof( MainColorEntry) * guts. palSize);
   
   for ( idx = 0; idx < guts. palSize; idx++) {
      guts. palette[ idx]. rank = RANK_FREE;
      list_create( &guts. palette[idx]. users, 0, 16); 
   }

   for ( idx = 0; idx < count; idx++) {
      int pixel = xc[idx]. pixel;
      guts. palette[ pixel]. r = xc[idx]. red / 256; 
      guts. palette[ pixel]. g = xc[idx]. green / 256; 
      guts. palette[ pixel]. b = xc[idx]. blue / 256;
      guts. palette[ pixel]. composite = RGB_COMPOSITE( 
         guts. palette[ pixel]. r,
         guts. palette[ pixel]. g,
         guts. palette[ pixel]. b);
      guts. palette[ pixel]. rank = RANK_IMMUTABLE;
      guts. systemColorMap[ idx] = pixel;
   }
   
   guts. systemColorMapSize = count;

   return true;
}

static void
fill_cubic( XColor * xc, int d)
{
   int b, g, r, d2 = d * d, frac = 65535 / ( d - 1);
   for ( b = 0; b < d; b++) for ( g = 0; g < d; g++) for ( r = 0; r < d; r++) {
      int idx = b + g * d + r * d2;
      xc[idx]. blue = b * frac;
      xc[idx]. green = g * frac;
      xc[idx]. red = r * frac;
   }
}

static char * do_visual = nil;
static PList color_options = nil;

static void
set_color_class( int class, char * option, char * value)
{
   if ( !value) {
      warn("`%s' must be given a value -- skipped\n", option);
      return;
   }
   if ( !color_options) color_options = plist_create( 8, 8);
   if ( !color_options) return;
   list_add( color_options, ( Handle) class);
   list_add( color_options, ( Handle) duplicate_string(value));
}   

static void
apply_color_class( int c_class, Color value) 
{
   int i;
   Color ** t = standard_colors + 1;
   for ( i = 1; i < MAX_COLOR_CLASS; i++, t++) (*t)[c_class] = value;
   Mdebug("color: class %d=%06x\n", c_class, value);
}

Bool
prima_init_color_subsystem(char * error_buf)
{
   int id, count, mask = VisualScreenMask|VisualDepthMask|VisualIDMask;
   XVisualInfo template, *list = nil;
   
   /* check if non-default depth is selected */
   id = -1;
   {
      char * c, * end;
      if (( c = do_visual) || 
	   apc_fetch_resource( "Prima", "", "Visual", "visual", 
                                nilHandle, frString, &c)) {
         id = strtol( c, &end, 0);
         if ( *end) id = -1;
         if ( c != do_visual) free( c);
         template. visualid = id;
         list = XGetVisualInfo( DISP, VisualIDMask, &template, &count);
         if ( count <= 0) {
            warn("warning: visual id '%s' is not found\n", c);
            if ( list) XFree( list);
            id = -1;
         }
      }
   }
   free( do_visual);
   do_visual = nil;

FALLBACK_TO_DEFAULT_VISUAL:
   if ( id < 0) {
      template. screen   = SCREEN;
      template. depth    = guts. depth;
      template. visualid = XVisualIDFromVisual( XDefaultVisual( DISP, SCREEN));

      list = XGetVisualInfo( DISP, mask, &template, &count);
      if ( count == 0) {
         sprintf( error_buf, "panic: no visuals found");
         return false;
      }
   }
   
   guts. visual = list[0];
   guts. visualClass = guts. visual. 
#if defined(__cplusplus) || defined(c_plusplus)
c_class;
#else
class;
#endif
   XFree( list);

   if ( guts. depth > 11 && guts. visualClass != TrueColor && guts. visualClass != DirectColor) {/* XXX */
      if ( id >= 0) {
         warn("warning: visual 0x%x is unusable: cannot use %d bit depth for something not TrueColor or DirectColor\n", id, guts. depth);
         id = -1;
         goto FALLBACK_TO_DEFAULT_VISUAL;
      } else
         sprintf( error_buf, "panic: %d bit depth is not true color", guts. depth);
      return false;
   }

   guts. useDithering     = true;
   guts. dynamicColors    = false;
   guts. grayScale        = false;
   guts. palSize          = 1 << guts. depth;
   guts. palette          = nil;
   guts. systemColorMap   = nil;
   guts. systemColorMapSize = 0;
   guts. colorCubeRib     = 0;

   if ( id >= 0) {
      guts. defaultColormap = XCreateColormap( DISP, guts. root, VISUAL, AllocNone);
      guts. privateColormap = 1;
   } else
      guts. defaultColormap  = DefaultColormap( DISP, SCREEN); 

   guts. monochromeMap[0] = BlackPixel( DISP, SCREEN);
   guts. monochromeMap[1] = WhitePixel( DISP, SCREEN);
   switch ( guts. visualClass) {
   case DirectColor:
   case TrueColor:
      guts. useDithering = false;
      guts. palSize = 0;
      break;

   case PseudoColor:
      {
         int max;
         XColor xc[216];
         guts. dynamicColors = true;
         if ( guts. privateColormap && guts. palSize > 26 ) {
            if ( guts. palSize > 125) max = 6; else
            if ( guts. palSize > 64)  max = 5; else
            if ( guts. palSize > 27)  max = 4; else max = 3;
         } else
            max = 2;
         fill_cubic( xc, max);
         if ( !alloc_main_color_range( xc, max * max * max, 27))
            goto BLACK_WHITE;
         if ( !create_std_palettes( xc, max * max * max)) {
	    sprintf( error_buf, "No memory");
	    return false;
	 }
         guts. colorCubeRib = max;
      }
      break;
      
   case StaticColor:
      {
         int d = 6, cd = 1;
         XColor xc[216];
         while ( d > 1) {
            if ( d * d * d <= guts. palSize) {
               fill_cubic( xc, d);
               if ( alloc_main_color_range( xc, d * d * d, cd * 3)) {
                  if ( !create_std_palettes( xc, d * d * d)) {
	             sprintf( error_buf, "No memory");
		     return false;
		  }
                  break;
               }
            }
            d--; 
            cd += 2;
         }
         if ( !guts. palette) goto BLACK_WHITE;
         if ( d < 2) goto BLACK_WHITE_ALLOCATED;
         guts. colorCubeRib = d;
      }
      break;

   case StaticGray:
   case GrayScale:
      {
         XColor xc[256];
         int maxSteps = ( guts. visualClass == GrayScale && !guts. privateColormap ) ? 5 : 8;
         int wantSteps = ( maxSteps > guts. depth) ? guts. depth : maxSteps;
         while ( wantSteps > 1) {
            int i, shades = 1 << wantSteps, c = 0, ndiv = 65536 / (shades-1);
            for ( i = 0; i < shades; i++) {
               xc[i].red = xc[i]. green = xc[i]. blue = c;
               if (( c += ndiv) > 65535) c = 65535;
            }
            if ( alloc_main_color_range( xc, shades, 768 / shades)) {
               if ( !create_std_palettes( xc, shades)) {
	          sprintf( error_buf, "No memory");
		  return false;
	       }
               break;
            }
            wantSteps--;
         }
         if ( !guts. palette) goto BLACK_WHITE;
         if ( wantSteps < 1) goto BLACK_WHITE_ALLOCATED;
         if ( wantSteps > 4) guts. useDithering = false;
         guts. colorCubeRib = wantSteps;
         guts. grayScale = true;
      }
      break;
   default:      
BLACK_WHITE:
      {
         XColor xc[2];
         if ( guts. privateColormap) {
            xc[0]. red = xc[0]. green = xc[0]. blue = 0;
            xc[1]. red = xc[1]. green = xc[1]. blue = 0xffff;
            XAllocColor( DISP, guts. defaultColormap, xc);
            XAllocColor( DISP, guts. defaultColormap, xc + 1);
            guts. monochromeMap[0] = xc[0].pixel;
            guts. monochromeMap[1] = xc[1].pixel;
         } else {
            xc[0]. pixel = BlackPixel( DISP, SCREEN);
            xc[1]. pixel = WhitePixel( DISP, SCREEN);
            XQueryColors( DISP, guts. defaultColormap, xc, 2);
         }
         XCHECKPOINT;
         if ( !alloc_main_color_range( xc, 2, 65536) || 
              !create_std_palettes( xc, 2)) {
            sprintf( error_buf, "panic: unable to initialize color system");
            return false;
         }
      }   
BLACK_WHITE_ALLOCATED:         
      guts. useDithering = true;
      guts. grayScale    = true;
      guts. colorCubeRib = 1;
      break;   
   }

   if ( guts. palSize > 0 && 
        (guts. visualClass == StaticColor ||
        guts. visualClass == StaticGray)) {
      int i;
      XColor * xc;
      MainColorEntry * p;
      if ( !( xc = malloc( sizeof( XColor) * guts. palSize))) {
	 sprintf( error_buf, "No memory");
         return false;
      }
      for ( i = 0; i < guts. palSize; i++) xc[i]. pixel = i;
      XQueryColors( DISP, guts. defaultColormap, xc, guts. palSize);
      XCHECKPOINT;
      p = guts. palette;
      for ( i = 0; i < guts. palSize; i++) {
         p-> r = xc[i]. red / 256;
         p-> g = xc[i]. green / 256;
         p-> b = xc[i]. blue / 256;
         p-> composite = RGB_COMPOSITE( p-> r, p-> g, p-> b);
         if ( p-> rank == RANK_FREE) p-> rank  = RANK_IMMUTABLE + 1;
         p++;
      }
      free( xc);
   }

   if (( guts. ditherPatterns = malloc( sizeof( FillPattern) * 65))) {
      int i, x, y;
      FillPattern * p = guts. ditherPatterns;
      Byte map [64] = {
          0, 48, 12, 60,  3, 51, 15, 63,
         32, 16, 44, 28, 35, 19, 47, 31,
          8, 56,  4, 52, 11, 59,  7, 55,
         40, 24, 36, 20, 43, 27, 39, 23,
          2, 50, 14, 62,  1, 49, 13, 61,
         34, 18, 46, 30, 33, 17, 45, 29,
         10, 58,  6, 54,  9, 57,  5, 53,
         42, 26, 38, 22, 41, 25, 37, 21
      };
      bzero( p, sizeof( FillPattern) * 65);
      for ( i = 0; i < 65; i++, p++) 
         for ( y = 0; y < 8; y++) 
             for ( x = 0; x < 8; x++) 
                if ( i <= map[y * 8 + x])
                   (*p)[y] |= 1 << x;
      
   } else {
      sprintf( error_buf, "No memory");
      return false;
   }
   if ( guts. palSize) {
      int sz = ( guts. palSize < 256) ? 256 : guts. palSize;
      if (!( guts. mappingPlace = malloc( sizeof( int) * sz))) {
         sprintf( error_buf, "No memory");
         return false;
      }
   } else {
      int i, j, from[3] = {0,0,0}, to[3] = {0,0,0}, stage[3] = {0,0,0}, lim[3]; 
      unsigned long mask[3];
      mask[0] = guts. visual. red_mask;
      mask[1] = guts. visual. green_mask;
      mask[2] = guts. visual. blue_mask;
      /* find color bounds and test if they are contiguous */
      for ( j = 0; j < 3; j++) {
         for ( i = 0; i < 32; i++) {
            switch ( stage[j]) {
            case 0:
               if (( mask[j] & ( 1 << i)) != 0) {
                  from[j] = i;
                  stage[j]++;
               }
               break;
            case 1:
               if (( mask[j] & ( 1 << i)) == 0) {
                  to[j] = i;
                  stage[j]++;
               }
               break;
            case 2:
               if (( mask[j] & ( 1 << i)) != 0) {
                  sprintf( error_buf, "panic: unsupported pixel representation (0x%08lx,0x%08lx,0x%08lx)", 
                     mask[0], mask[1], mask[2]);
                  return false;
               }
            }
         }
         if ( to[j] == 0) to[j] = 32;
         lim[j] = 1 << (to[j] - from[j]);
      }

      guts. red_shift   = from[0];
      guts. green_shift = from[1];
      guts. blue_shift  = from[2];
      guts. red_range   = to[0] - from[0];
      guts. green_range = to[1] - from[1];
      guts. blue_range  = to[2] - from[2];
   }
   guts. localPalSize = guts. palSize / 4 + ((guts. palSize % 4) ? 1 : 0);
   hatches = hash_create();

   /* get XRDB colors */
   {
      Color c;
      if ( apc_fetch_resource( "Prima", "", "Color", "color", nilHandle, frColor, &c))
         apply_color_class( ciFore, c);
      if ( apc_fetch_resource( "Prima", "", "Back", "backColor", nilHandle, frColor, &c))
         apply_color_class( ciBack, c);
      if ( apc_fetch_resource( "Prima", "", "HiliteColor", "hiliteColor", nilHandle, frColor, &c))
         apply_color_class( ciHiliteText, c);
      if ( apc_fetch_resource( "Prima", "", "HiliteBackColor", "hiliteBackColor", nilHandle, frColor, &c))
         apply_color_class( ciHilite, c);
      if ( apc_fetch_resource( "Prima", "", "DisabledColor", "disabledColor", nilHandle, frColor, &c))
         apply_color_class( ciDisabledText, c);
      if ( apc_fetch_resource( "Prima", "", "DisabledBackColor", "disabledBackColor", nilHandle, frColor, &c))
         apply_color_class( ciDisabled, c);
      if ( apc_fetch_resource( "Prima", "", "Light3DColor", "light3DColor", nilHandle, frColor, &c))
         apply_color_class( ciLight3DColor, c);
      if ( apc_fetch_resource( "Prima", "", "Dark3DColor", "dark3DColor", nilHandle, frColor, &c))
         apply_color_class( ciDark3DColor, c);
   }


   /* parse user colors */
   if ( color_options) {
      int i, c_class;
      char *value;
      XColor xcolor;
      for ( i = 0; i < color_options-> count; i+=2) {
	 c_class = (int)   color_options-> items[i];
	 value   = (char*) color_options-> items[i+1];
         if ( XParseColor( DISP, DefaultColormap( DISP, SCREEN), value, &xcolor)) {
	    apply_color_class( c_class, ARGB(xcolor.red >> 8, xcolor.green >> 8, xcolor.blue >> 8));
	 } else {
	    warn("Cannot parse color value `%s`", value);
	 }
	 free( value);
      }
      plist_destroy( color_options);
   }
   return true;
}

Bool
prima_color_subsystem_set_option( char * option, char * value)
{
   if ( strcmp( option, "visual") == 0) {
      if ( value) {
	 free( do_visual);
	 do_visual = duplicate_string( value);
	 Mdebug( "set visual: %s\n", do_visual);
      } else 
	 warn("`--visual' must be given value");
      return true;
   } else if ( strcmp( option, "fg") == 0) {
      set_color_class( ciFore, option, value);
   } else if ( strcmp( option, "bg") == 0) {
      set_color_class( ciBack, option, value);
   } else if ( strcmp( option, "hilite-bg") == 0) {
      set_color_class( ciHilite, option, value);
   } else if ( strcmp( option, "hilite-fg") == 0) {
      set_color_class( ciHiliteText, option, value);
   } else if ( strcmp( option, "disabled-bg") == 0) {
      set_color_class( ciDisabled, option, value);
   } else if ( strcmp( option, "disabled-fg") == 0) {
      set_color_class( ciDisabledText, option, value);
   } else if ( strcmp( option, "light") == 0) {
      set_color_class( ciLight3DColor, option, value);
   } else if ( strcmp( option, "dark") == 0) {
      set_color_class( ciDark3DColor, option, value);
   }
   return false;
}   

typedef struct 
{
   int count;
   unsigned long free[256];
} FreeColorsStruct;

void
prima_done_color_subsystem( void)
{
   int i;
   FreeColorsStruct fc;

   if ( DISP) {
      hash_first_that( hatches, (void*)kill_hatches, nil, nil, nil);
      fc. count = 0;
      
      for ( i = 0; i < guts. palSize; i++) {
         list_destroy( &guts. palette[i]. users);
         if ( 
             !guts. privateColormap &&
             guts. palette[i]. rank > RANK_FREE && 
             guts. palette[i]. rank <= RANK_IMMUTABLE) {
            fc. free[ fc. count++] = i;
            if ( fc. count == 256) {
               XFreeColors( DISP, guts. defaultColormap, fc. free, 256, 0);
               fc. count = 0;
            }
         }
      }
      if ( fc. count > 0)
         XFreeColors( DISP, guts. defaultColormap, fc. free, fc. count, 0);
      XFreeColormap( DISP, guts. defaultColormap);
   }

   hash_destroy( hatches, false);
   guts. defaultColormap = 0;
   free( guts. mappingPlace);
   free( guts. ditherPatterns);
   free( guts. palette);
   free( guts. systemColorMap);
   guts. palette = nil;
   guts. systemColorMap = nil;
   guts. ditherPatterns = nil;
   guts. mappingPlace = nil;
}

/*
   Finds closest possible color in system palette.
   Colors can be selectively filtered using maxRank
   parameter - if it is greater that RANK_FREE, the colors
   with rank lower that maxRank are not matched. Ranking can
   make sense when self != nil and self != application, and
   of course when color cell manipulation is possible. In other
   words, local palette is never used if maxRank > RANK_FREE.
   maxDiff tells the maximal difference for a color. If
   no color is found that is closer than maxDiff, -1 is returned
   and pointer to actual diff is returned.
   */
int
prima_color_find( Handle self, long color, int maxDiff, int * diff, int maxRank)
{
   int i, j, ret = -1;
   int global;
   int b = color & 0xff;
   int g = (color >> 8) & 0xff;
   int r = (color >> 16) & 0xff;
   int lossy = maxDiff != 0;
   if ( maxDiff < 0) maxDiff = 256 * 256 * 3;
   global = self ? (X(self)-> type. widget && ( self != application)) : true;

   maxDiff++;
   if ( global || !guts. dynamicColors || (maxRank > RANK_FREE)) {
      for ( i = 0; i < guts. palSize; i++) {
         if ( guts. palette[i]. rank > maxRank) {
            if ( lossy) {
               int d = 
                    ( b - guts. palette[i].b) * ( b - guts. palette[i].b) +
                    ( g - guts. palette[i].g) * ( g - guts. palette[i].g) +
                    ( r - guts. palette[i].r) * ( r - guts. palette[i].r);
               if ( d < maxDiff) {
                  ret = i;
                  maxDiff = d;
                  if ( maxDiff == 0) break;
               }
            } else {
               if ( color == guts. palette[i]. composite) {
                  ret = i;
                  break;
               }
            }
         }
      }
   } else {
      for ( j = 0; j < guts. systemColorMapSize + guts. palSize; j++) {
         if ( j < guts. systemColorMapSize) 
            i = guts. systemColorMap[j];
         else {
            i = j - guts. systemColorMapSize;
            if ( wlpal_get( self, i) == RANK_FREE) continue;
         }
         if ( lossy) {
            int d = 
                 ( b - guts. palette[i].b) * ( b - guts. palette[i].b) +
                 ( g - guts. palette[i].g) * ( g - guts. palette[i].g) +
                 ( r - guts. palette[i].r) * ( r - guts. palette[i].r);
            if ( d < maxDiff) {
               ret = i;
               maxDiff = d;
               if ( maxDiff == 0) break;
            }
         } else {
            if ( color == guts. palette[i]. composite) {
               ret = i;
               break;
            }
         }
      }
   }
   if ( diff) *diff = maxDiff;
   return ret;
}

static Bool
prima_color_new( XColor * xc)
{
   MainColorEntry * p = guts. palette + xc-> pixel;
   if ( p-> rank != RANK_FREE) {
      XFreeColors( DISP, guts. defaultColormap, &xc-> pixel, 1, 0);
      return false;
   }
   p-> r = xc-> red >> 8;
   p-> g = xc-> green >> 8;
   p-> b = xc-> blue >> 8;
   p-> composite = RGB_COMPOSITE(p->r,p->g,p->b);
   return true;
}

/*
   Adds reference to widget that is responsible
   for a color cell with given rank. Main palette
   rank can be risen in response, but not lowered -
   that is accomplished by prima_color_sync. 
   */
Bool
prima_color_add_ref( Handle self, int index, int rank)
{
   int r, nr = (rank == RANK_PRIORITY) ? 2 : 1;
   if ( index < 0 || index >= guts. palSize) return false;
   if ( guts. palette[index]. rank == RANK_IMMUTABLE) return false;
   if ( !self || ( self == application)) return false;
   r = wlpal_get(self, index);
   if ( r != 0 && r <= nr) return false;
   if ( r == 0) list_add( &guts. palette[index]. users, self);
   if ( rank > guts. palette[index]. rank)
      guts. palette[index]. rank = rank;
   wlpal_set( self, index, nr);
   Pdebug("color:%s %s %d %d\n", PWidget(self)-> name, r ? "raised to " : "added as", nr, index);
   return true;
}

/* Frees stale color references */ 
int
prima_color_sync( void)
{
   int i, count = 0, freed = 0;
   unsigned long free[32];
   MainColorEntry * p = guts. palette;
   for ( i = 0; i < guts. palSize; i++, p++) {
      if ( p-> touched) {
         int j, max = RANK_FREE;
         for ( j = 0; j < p-> users. count; j++) {
            int rank;
            if ( X(p-> users. items[j])-> type. widget) {
               rank = wlpal_get( p-> users. items[j], i);
               if ( rank > 0)
                  rank = ( rank > 1) ? RANK_PRIORITY : RANK_NORMAL;
            } else
               rank = RANK_LOCKED;
            if ( max < rank) max = rank;
            if ( max == RANK_LOCKED) break;
         }
         p-> rank = max;
         if ( max == RANK_FREE) {
            free[ count++] = i;
            if ( count == 32) {
               XFreeColors( DISP, guts. defaultColormap, free, 32, 0);
               count = 0;
               freed += 32;
            }
         }
         p-> touched = false;
      }
   }
   if ( count > 0)
      XFreeColors( DISP, guts. defaultColormap, free, count, 0);
   return freed + count;
}

/* updates contents of DefaultColormap.  */
/* NB - never to be called with 'fast' set to true. */

Bool
prima_palette_replace( Handle self, Bool fast)
{
   DEFXX;
   Bool restricted = fast || XX-> type. dbm;
   int rank, psz, i, j, granted, stage, menu = 0;
   unsigned long * req;
   RGBColor * rqx;
   MainColorEntry * p;
   List widgets;

   if ( !guts. dynamicColors) return true;
   if ( self == application) return true;
   
   if ( XX-> type.widget) rank = RANK_PRIORITY; else
   if ( XX-> type.image || XX-> type. dbm) rank = RANK_LOCKED; else
      return false;

   if ( !fast) prima_palette_free( self, true); /* remove old entries */
  
   psz = PDrawable( self)-> palSize + menu;
   if ( XT_IS_WINDOW(X(self)) && PWindow(self)-> menu) 
      psz += (menu = ciMaxId + 1);
   if ( psz == 0) {
      prima_color_sync();
      return true; 
   }
   
   if ( !( req = malloc( sizeof( unsigned long) * psz))) 
      return false;

   for ( i = 0; i < psz - menu; i++) 
      req[i] = RGB_COMPOSITE( 
         PWidget( self)-> palette[i].r,
         PWidget( self)-> palette[i].g,
         PWidget( self)-> palette[i].b);
   for ( i = psz - menu; i < psz; i++) 
      req[i] = PWindow(self)-> menuColor[ i - psz + menu]; 
   
   granted = 0;

   if ( !restricted) XGrabServer( DISP);
   
   /* fetch actual colors - they are useful when no free colorcells
      available, but colormap has some good colors, which we don't
      possess */
   if ( !restricted) {
      int count = 0, j;
      XColor xc[32];
      for ( i = 0; i < guts.palSize; i++) 
         if ( guts.palette[i].rank == RANK_FREE) {
            xc[count++].pixel = i;
            if ( count == 32) {
               XQueryColors( DISP, guts. defaultColormap, xc, 32);
               for ( j = 0; j < 32; j++) prima_color_new( &xc[j]);
               count = 0;
            }
         }
      if ( count > 0) {
         XQueryColors( DISP, guts. defaultColormap, xc, count);
         for ( j = 0; j < count; j++) prima_color_new( &xc[j]);
      }
   }
   
   Pdebug("color replace:%s find match for %d colors\n", PWidget(self)-> name, psz);
   /* find out if any allocated entries are present already */
   for ( i = 0; i < psz; i++) 
      if (( req[i] & 0x80000000) == 0) {
         unsigned long c = req[i];
         for ( j = 0; j < guts. palSize; j++) {
            MainColorEntry * p = guts. palette + j;
            int pixel = j;
            if ( p-> composite == c) {
               if ( !restricted && (p-> rank == RANK_FREE)) {
                  XColor xc;
                  xc. red   = COLOR_R16(req[i]);
                  xc. green = COLOR_G16(req[i]);
                  xc. blue  = COLOR_B16(req[i]);
                  if ( alloc_color(&xc)) {
                     if ( prima_color_new( &xc))
                        /* to protect from sync - give actual status on SUCCESS */
                        guts.palette[xc.pixel].rank = RANK_IMMUTABLE + 1; 
                     pixel = xc.pixel;
                  } else
                     continue;
               }
               req[i] |= 0x80000000;
               if ( !restricted) prima_color_add_ref( self, pixel, rank);
               granted++;
               break;
            }
         }
      }

   Pdebug("color replace: granted %d\n", granted);
   if ( restricted) {
      free( req);
      return true;
   }


   stage = RANK_NORMAL;
   list_create( &widgets, 32, 128);
   if ( granted == psz) {
      free( req);
      goto SUCCESS;
   }

ALLOC_STAGE:   
   /* allocate some colors */
   prima_color_sync();
   XCHECKPOINT;
   for ( i = 0; i < psz; i++) 
      if (( req[i] & 0x80000000) == 0) {
         XColor xc;
         xc. red   = COLOR_R16(req[i]);
         xc. green = COLOR_G16(req[i]);
         xc. blue  = COLOR_B16(req[i]);
         if ( alloc_color( &xc)) {
            prima_color_new( &xc);
            prima_color_add_ref( self, xc. pixel, rank);
            granted++;
            req[i] |= 0x80000000;
         } else 
            break;
      }
      Pdebug("color replace :ok - now %d are granted\n", granted);
  
   if ( granted == psz) {
      free( req);
      goto SUCCESS;
   }
       
   if ( stage == RANK_NORMAL) {
       /* try to remove RANK_NORMAL colors */
       p = guts. palette;
       for ( i = 0; i < guts. palSize; i++, p++) {
          if ( p-> rank == RANK_NORMAL) {
             int j;
             for ( j = 0; j < p-> users. count; j++) {
                Handle wij = p-> users. items[j];
                if ( list_index_of( &widgets, wij) < 0)
                   list_add( &widgets, wij);
                if ( wlpal_get(wij, i) == RANK_NORMAL) 
                   wlpal_set( wij, i, RANK_FREE); 
             }
             list_delete_all( &p-> users, false);
             p-> touched = true;
             stage = RANK_PRIORITY;
          }
       }
      if ( stage == RANK_PRIORITY) goto ALLOC_STAGE;
   } 

   free( req);
   
   if ( XX-> type. image) goto SUCCESS;
  
   /* try to remove RANK_PRIORITY entries */
   p = guts. palette;
   for ( i = 0; i < guts. palSize; i++, p++) {
      if ( p-> rank == RANK_PRIORITY) {
         int j;
         for ( j = 0; j < p-> users. count; j++) {
            Handle wij = p-> users. items[j];
            if ( X(wij)-> type. widget && list_index_of( &widgets, wij) < 0)
               list_add( &widgets, wij);
            wlpal_set( wij, i, RANK_FREE);
         }
         list_delete_all( &p-> users, false);
         p-> touched = true;
      }
   }
   
   psz = prima_color_sync();
   if ( psz == 0) goto SUCCESS; /* free no RANK_PRIORITY colors :(  */
   XCHECKPOINT;

   /* collect big palette */
   j = 0;
   for ( i = 0; i < guts. palSize; i++)
      if ( guts. palette[i]. rank != RANK_FREE) 
         j++;
   stage = j; /* immutable and locked colors */
   for ( i = 0; i < widgets. count; i++) {
      j += PWidget( widgets. items[i])-> palSize;
      if ( XT_IS_WINDOW(X(widgets. items[i])) && 
           PWindow(widgets. items[i])-> menu)
         j += ciMaxId + 1;
   }
   
   Pdebug("color: BIG:%d vs %d\n", j, psz);
   if ( !( rqx = malloc( sizeof( RGBColor) * j))) goto SUCCESS; /* :O */
   
   {
      RGBColor * r = rqx;
      for ( i = 0; i < guts. palSize; i++) 
         if ( guts. palette[i]. rank != RANK_FREE) {
            r-> r = guts. palette[i]. r;
            r-> g = guts. palette[i]. g;
            r-> b = guts. palette[i]. b;
            r++;
         }
      for ( i = 0; i < widgets. count; i++) {
         memcpy( r, PWidget( widgets. items[i])-> palette, 
            PWidget( widgets. items[i])-> palSize * sizeof( RGBColor));
         r += PWidget( widgets. items[i])-> palSize;
         if ( XT_IS_WINDOW(X(widgets. items[i])) && 
              PWindow(widgets. items[i])-> menu) {
            int k;
            for ( k = 0; k <= ciMaxId; k++, r++) {
               r-> r = COLOR_R(PWindow(widgets. items[i])-> menuColor[k]);
               r-> g = COLOR_G(PWindow(widgets. items[i])-> menuColor[k]);
               r-> b = COLOR_B(PWindow(widgets. items[i])-> menuColor[k]);
            }
         }
      }
   }
   
   /* squeeze palette */
   if ( j > psz + stage) {
      int k, tolerance = 0, t2 = 0, lim = psz + stage;
      while ( 1) {
         for ( i = 0; i < j; i++) {
            RGBColor r = rqx[i];
            for ( k = (( i + 1) > stage) ? i + 1 : stage; k < j; ) {
               if ( 
                    ( r.r - rqx[k].r) * ( r.r - rqx[k].r) +
                    ( r.g - rqx[k].g) * ( r.g - rqx[k].g) +
                    ( r.b - rqx[k].b) * ( r.b - rqx[k].b) 
                    <= t2) {
                  if ( k < j - 1) rqx[k] = rqx[j-1];
                  if ( --j <= lim) goto ENOUGH;
               } else
                  k++;
            }
         }
         tolerance += 2;
         t2 = tolerance * tolerance;
      }
ENOUGH:;    
   }
   
   Pdebug("color replace: ok. XAllocColor again\n");
   granted = 0;
   for ( i = stage; i < stage + psz; i++) {
      XColor xc;
      xc. red   = rqx[i]. r << 8;
      xc. green = rqx[i]. g << 8;
      xc. blue  = rqx[i]. b << 8;
      if ( alloc_color( &xc)) {
         if ( prima_color_new( &xc)) {
            /* give new color NORMAL status - to be cleaned automatically */
            /* upon 1st sync() invocation */
            guts. palette[xc. pixel]. touched = 1;
            guts. palette[xc. pixel]. rank = RANK_NORMAL;
            granted++;
          } 
      } else
         break;
   }
   free( rqx);
   Pdebug("color replace: ok - %d out of %d \n", granted, psz);
   XCHECKPOINT;
   
   /* now give away colors that can be mapped to reduced palette */
   prima_palette_replace( self, true);
   for ( i = 0; i < widgets. count; i++) 
      prima_palette_replace( widgets. items[i], true);
   XCHECKPOINT;
   
SUCCESS:

   /* restore status of pre-fetched colors */
   for ( i = 0; i < guts. palSize; i++)
      if ( guts.palette[i].rank == RANK_IMMUTABLE + 1)
         guts.palette[i].rank = RANK_PRIORITY;
           
   prima_color_sync(); 

   XUngrabServer( DISP);
   
   for ( i = 0; i < widgets. count; i++)
      if ( PWidget( widgets. items[i])-> stage < csDead)
         apc_widget_invalidate_rect( widgets. items[i], nil);
   
   Pdebug("color replace: exit\n");
   list_destroy( &widgets);
   return true;
}

Bool
prima_palette_alloc( Handle self)
{
   if ( !guts. dynamicColors) return true;
   if ( !( X(self)-> palette = malloc( guts. localPalSize)))
      return false;        
   bzero( X(self)-> palette,  guts. localPalSize);
   return true;
}

void
prima_palette_free( Handle self, Bool priority)
{
   int i, max = priority ? 2 : 1;
   if ( !guts. dynamicColors) return;
   for ( i = 0; i < guts. palSize; i++) {
      int rank = wlpal_get(self,i);
      if ( rank > RANK_FREE && max >= rank) {
         wlpal_set( self, i, RANK_FREE);
         list_delete( &guts. palette[i]. users, self);
         Pdebug("color: %s free %d, %d\n", PWidget(self)-> name, i, rank);
         guts. palette[i]. touched = true;
      }
   }
   Pdebug(":%s for %s\n", priority ? "PRIO" : "", PWidget(self)-> name);
}

int
prima_lpal_get( Byte * palette, int index)
{
   return LPAL_GET( index, palette[ LPAL_ADDR( index ) ]);
}

void 
prima_lpal_set( Byte * palette, int index, int rank )
{
   palette[ LPAL_ADDR( index ) ] &=~ LPAL_MASK( index);
   palette[ LPAL_ADDR( index ) ] |=  LPAL_SET( index, rank);
}
          

static Bool kill_hatches( Pixmap pixmap, int keyLen, void * key, void * dummy)
{
   XFreePixmap( DISP, pixmap);
   return false;
}

Pixmap 
prima_get_hatch( FillPattern * fp)
{
   int i;
   Pixmap p;
   FillPattern fprev;
   if ( memcmp( fp, fillPatterns[fpSolid], sizeof( FillPattern)) == 0)
      return nilHandle;
   if (( p = ( Pixmap) hash_fetch( hatches, fp, sizeof( FillPattern))))
      return p;
   for ( i = 0; i < sizeof( FillPattern); i++)
      fprev[i] = (*fp)[ sizeof(FillPattern) - i - 1];
   if (( p = XCreateBitmapFromData( DISP, guts. root, (char*)fprev, 8, 8)) == None) {
      hash_first_that( hatches, (void*)kill_hatches, nil, nil, nil);
      hash_destroy( hatches, false);
      hatches = hash_create();
      if (( p = XCreateBitmapFromData( DISP, guts. root, (char*)fprev, 8, 8)) == None) 
         return nilHandle;
   }
   hash_store( hatches, fp, sizeof( FillPattern), ( void*) p);
   return p;
}