a color cache to be proud of!
[mikachu/openbox.git] / otk_c / color.c
1 // -*- mode: C; indent-tabs-mode: nil; -*-
2
3 #include "../config.h"
4 #include "color.h"
5 #include "display.h"
6 #include "screeninfo.h"
7
8 #ifdef HAVE_STDLIB_H
9 # include <stdlib.h>
10 #endif
11
12 static Bool cleancache = False;
13 static PyObject *colorcache = NULL;
14
15 static void otkcolor_dealloc(OtkColor* self)
16 {
17   // when this is called, the color has already been cleaned out of the cache
18   PyObject_Del((PyObject*)self);
19 }
20
21 static int otkcolor_compare(OtkColor *c1, OtkColor *c2)
22 {
23   long result;
24   unsigned long p1, p2;
25
26   p1 = c1->red << 16 | c1->green << 8 | c1->blue;
27   p2 = c2->red << 16 | c2->green << 8 | c2->blue;
28
29   if (p1 < p2)
30     result = -1;
31   else if (p1 > p2)
32     result = 1;
33   else
34     result = 0;
35   return result;
36 }
37
38 static PyObject *otkcolor_repr(OtkColor *self)
39 {
40   return PyString_FromFormat("rgb:%x/%x/%x", self->red, self->green,
41                              self->blue);
42 }
43
44 static long otkcolor_hash(OtkColor *self)
45 {
46   return self->screen << 24 | self->red << 16 | self->green << 8 | self->blue;
47 }
48
49 static PyTypeObject OtkColor_Type = {
50   PyObject_HEAD_INIT(NULL)
51   0,
52   "Color",
53   sizeof(OtkColor),
54   0,
55   (destructor)otkcolor_dealloc, /*tp_dealloc*/
56   0,                            /*tp_print*/
57   0,                            /*tp_getattr*/
58   0,                            /*tp_setattr*/
59   (cmpfunc)otkcolor_compare,    /*tp_compare*/
60   (reprfunc)otkcolor_repr,      /*tp_repr*/
61   0,                            /*tp_as_number*/
62   0,                            /*tp_as_sequence*/
63   0,                            /*tp_as_mapping*/
64   (hashfunc)otkcolor_hash,      /*tp_hash */
65 };
66
67
68 static void parseColorName(OtkColor *self, const char *name) {
69   XColor xcol;
70
71   // get rgb values from colorname
72   xcol.red = 0;
73   xcol.green = 0;
74   xcol.blue = 0;
75   xcol.pixel = 0;
76
77   if (!XParseColor(OBDisplay->display,
78                    OtkDisplay_ScreenInfo(OBDisplay, self->screen)->colormap,
79                    name, &xcol)) {
80     fprintf(stderr, "OtkColor: color parse error: \"%s\"\n", name);
81     self->red = self->green = self->blue = 0;
82   } else {
83     self->red = xcol.red >> 8;
84     self->green = xcol.green >> 8;
85     self->blue = xcol.blue >> 8;
86   }
87 }
88
89 #include <stdio.h>
90 static void doCacheCleanup() {
91   unsigned long *pixels;
92   int i, ppos;
93   unsigned int count;
94   PyObject *key; // this is a color too, but i dont need to use it as such
95   OtkColor *color;
96
97   // ### TODO - support multiple displays!
98   if (!PyDict_Size(colorcache)) return; // nothing to do
99
100   printf("Cleaning Cache...\n");
101   
102   pixels = malloc(sizeof(unsigned long) * PyDict_Size(colorcache));
103
104   for (i = 0; i < ScreenCount(OBDisplay->display); i++) {
105     printf("Screen %d\n", i);
106     count = 0;
107     ppos = 0;
108
109     while (PyDict_Next(colorcache, &ppos, &key, (PyObject**)&color)) {
110       // get the screen from the hash
111       if (color->screen != i) continue; // wrong screen
112
113       printf("has %d refs\n", color->ob_refcnt);
114
115       // does someone other than the cache have a reference? (the cache gets 2)
116       if (color->ob_refcnt > 2)
117         continue;
118
119       printf("ppos: %d\n", ppos);
120       printf("Cleaning pixel: %lx Count: %d\n", color->pixel, count+1);
121       
122       pixels[count++] = color->pixel;
123       printf("pixref references before: %d\n", color->ob_refcnt);
124       PyDict_DelItem(colorcache, key);
125       printf("pixref references after: %d\n", color->ob_refcnt);
126       --ppos; // back up one in the iteration
127     }
128
129     if (count > 0)
130       XFreeColors(OBDisplay->display,
131                   OtkDisplay_ScreenInfo(OBDisplay, i)->colormap,
132                   pixels, count, 0);
133     printf("Done Cleaning Cache. Cleaned %d pixels\n", count);
134   }
135
136   free(pixels);
137   cleancache = False;
138 }
139
140 static void allocate(OtkColor *self) {
141   XColor xcol;
142
143   assert(!self->allocated);
144
145   printf("allocating! %d\n", cleancache);
146
147   // allocate color from rgb values
148   xcol.red =   self->red   | self->red   << 8;
149   xcol.green = self->green | self->green << 8;
150   xcol.blue =  self->blue  | self->blue  << 8;
151   xcol.pixel = 0;
152   
153   if (!XAllocColor(OBDisplay->display,
154                    OtkDisplay_ScreenInfo(OBDisplay, self->screen)->colormap,
155                    &xcol)) {
156     fprintf(stderr, "OtkColor: color alloc error: rgb:%x/%x/%x\n",
157             self->red, self->green, self->blue);
158     xcol.pixel = 0;
159   }
160   
161   self->pixel = xcol.pixel;
162   self->allocated = True;
163   
164   if (cleancache)
165     doCacheCleanup();
166 }
167
168 PyObject *OtkColor_FromRGB(int r, int g, int b, int screen)
169 {
170   OtkColor *self = PyObject_New(OtkColor, &OtkColor_Type);
171   PyObject *cached;
172
173   assert(screen >= 0); assert(r >= 0); assert(g >= 0); assert(b >= 0);
174   assert(r <= 0xff); assert(g <= 0xff); assert(b <= 0xff);
175
176   if (!colorcache) colorcache = PyDict_New();
177
178   self->allocated = False;
179   self->red = r;
180   self->green = g;
181   self->blue = b;
182   self->pixel = 0;
183   self->screen = screen;
184
185   // does this color already exist in the cache?
186   cached = PyDict_GetItem(colorcache, (PyObject*)self);
187   if (cached) {
188     Py_INCREF(cached);
189     return cached;
190   }
191
192   // add it to the cache
193   PyDict_SetItem(colorcache, (PyObject*)self, (PyObject*)self);
194   return (PyObject*)self;
195 }
196
197 PyObject *OtkColor_FromName(const char *name, int screen)
198 {
199   OtkColor *self = PyObject_New(OtkColor, &OtkColor_Type);
200   PyObject *cached;
201
202   assert(screen >= 0); assert(name);
203
204   if (!colorcache) colorcache = PyDict_New();
205   
206   self->allocated = False;
207   self->red = -1;
208   self->green = -1;
209   self->blue = -1;
210   self->pixel = 0;
211   self->screen = screen;
212
213   parseColorName(self, name);
214
215   // does this color already exist in the cache?
216   cached = PyDict_GetItem(colorcache, (PyObject*)self);
217   if (cached) {
218     Py_INCREF(cached);
219     return cached;
220   }
221
222   // add it to the cache
223   PyDict_SetItem(colorcache, (PyObject*)self, (PyObject*)self);
224   return (PyObject*)self;
225 }
226
227 unsigned long OtkColor_Pixel(OtkColor *self)
228 {
229   if (!self->allocated)
230     allocate(self);
231   return self->pixel;
232 }
233
234 void OtkColor_CleanupColorCache()
235 {
236   cleancache = True;
237 }