1#! /usr/bin/python
2
3# themeupdate.py for the Openbox window manager
4# This utility is for updating old themes from Blackbox, Fluxbox, and Openbox2
5# to Openbox3
6#
7# Copyright (c) 2003-2007        Dana Jansens
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# See the COPYING file for a copy of the GNU General Public License.
20
21import sys
22
23data = []
24valid = True
25
26def out(str):
27    sys.stderr.write(str)
28    sys.stderr.flush()
29
30def read_bool():
31    while True:
32        inp = sys.stdin.readline(1).strip()
33        if inp == 'y' or inp == '': return True
34        if inp == 'n': return False
35
36def getkeyval(line):
37    key = line[:line.find(':')].strip()
38    value = line[line.find(':') + 1:].strip()
39    if not (key and value):
40        key = value = None
41    return key, value
42
43def find_key(data, keysubstr, exact = False):
44    i = 0
45    n = len(data)
46    while i < n:
47        l = data[i]
48        key, value = getkeyval(l)
49        if key and value:
50            if (exact and key == keysubstr) or \
51               (not exact and key.find(keysubstr) != -1):
52                return i, key, value
53        i += 1
54    return -1, None, None
55
56def simple_replace(data):
57    pairs = {}
58    pairs['.picColor'] = '.imageColor'
59    pairs['menu.frame'] = 'menu.items'
60    pairs['menu.hilite'] = 'menu.selected'
61    pairs['borderColor'] = 'border.color'
62    pairs['imageColor'] = 'image.color'
63    pairs['textColor'] = 'text.color'
64    pairs['interlaceColor'] = 'interlace.color'
65
66    for k in pairs.keys():
67        while 1:
68            i, key, nul = find_key(data, k);
69            if i >= 0:
70                newl = data[i].replace(k, pairs[k])
71                out('Updating "' + key +
72                    '" to "' + key.replace(k, pairs[k]) + '"\n')
73                data[i] = newl
74            else:
75                break
76
77    pairs = {}
78    pairs['window.focus.font'] = 'window.active.label.text.font'
79    pairs['window.unfocus.font'] = 'window.inactive.label.text.font'
80    pairs['window.justify'] = 'window.label.justify'
81    pairs['menu.frame.disableColor'] = 'menu.disabled.textColor'
82    pairs['window.label.focus.font'] = 'window.active.label.text.font'
83    pairs['window.label.unfocus.font'] = 'window.inactive.label.text.font'
84    pairs['window.label.justify'] = 'window.label.text.justify'
85    pairs['menu.title.font'] = 'menu.title.text.font'
86    pairs['menu.title.justify'] = 'menu.title.text.justify'
87    pairs['menuOverlap'] = 'menu.overlap'
88    pairs['handleWidth'] = 'window.handle.width'
89    pairs['borderWidth'] = 'border.width'
90    pairs['bevelWidth'] = 'padding.width'
91    pairs['frameWidth'] = 'window.client.padding.width'
92    pairs['window.frame.focusColor'] = 'window.active.client.color'
93    pairs['window.frame.unfocusColor'] = 'window.inactive.client.color'
94    pairs['window.title.focus'] = 'window.active.title.bg'
95    pairs['window.title.unfocus'] = 'window.inactive.title.bg'
96    pairs['window.label.focus'] = 'window.active.label.bg'
97    pairs['window.label.unfocus'] = 'window.inactive.label.bg'
98    pairs['window.handle.focus'] = 'window.active.handle.bg'
99    pairs['window.handle.unfocus'] = 'window.inactive.handle.bg'
100    pairs['window.grip.focus'] = 'window.active.grip.bg'
101    pairs['window.grip.unfocus'] = 'window.inactive.grip.bg'
102    pairs['menu.items'] = 'menu.items.bg'
103    pairs['menu.title'] = 'menu.title.bg'
104    pairs['menu.selected'] = 'menu.items.active.bg'
105    pairs['window.title.focus'] = 'window.active.title.bg'
106    pairs['window.label.focus'] = 'window.active.label.bg'
107    pairs['window.title.unfocus'] = 'window.inactive.title.bg'
108    pairs['window.label.unfocus'] = 'window.inactive.label.bg'
109    pairs['window.button.disabled.focus'] = 'window.active.button.disabled.bg'
110    pairs['window.button.disabled.unfocus'] = \
111        'window.inactive.button.disabled.bg'
112    pairs['window.button.pressed.focus'] = 'window.active.button.pressed.bg'
113    pairs['window.button.pressed.unfocus'] = \
114        'window.inactive.button.pressed.bg'
115    pairs['window.button.toggled.focus'] = 'window.active.button.toggled.bg'
116    pairs['window.button.toggled.unfocus'] = \
117        'window.inactive.button.toggled.bg'
118    pairs['window.button.focus'] = 'window.active.button.unpressed.bg'
119    pairs['window.button.unfocus'] = 'window.inactive.button.unpressed.bg'
120    pairs['window.button.hover.focus'] = 'window.active.button.hover.bg'
121    pairs['window.button.hover.unfocus'] = 'window.inactive.button.hover.bg'
122
123    for k in pairs.keys():
124        while 1:
125            i, key, nul = find_key(data, k, True);
126            if i >= 0:
127                newl = data[i].replace(k, pairs[k])
128                out('Updating "' + key +
129                    '" to "' + key.replace(k, pairs[k]) + '"\n')
130                data[i] = newl
131            else:
132                break
133
134    pairs = {}
135    pairs['window.title.focus'] = 'window.active.title'
136    pairs['window.title.unfocus'] = 'window.inactive.title'
137    pairs['window.label.focus'] = 'window.active.label'
138    pairs['window.label.unfocus'] = 'window.inactive.label'
139    pairs['window.handle.focus'] = 'window.active.handle'
140    pairs['window.handle.unfocus'] = 'window.inactive.handle'
141    pairs['window.grip.focus'] = 'window.active.grip'
142    pairs['window.grip.unfocus'] = 'window.inactive.grip'
143    pairs['menu.selected'] = 'menu.items.active'
144    pairs['window.title.focus'] = 'window.active.title'
145    pairs['window.label.focus'] = 'window.active.label'
146    pairs['window.title.unfocus'] = 'window.inactive.title'
147    pairs['window.label.unfocus'] = 'window.inactive.label'
148    pairs['window.button.disabled.focus'] = 'window.active.button.disabled'
149    pairs['window.button.disabled.unfocus'] = \
150        'window.inactive.button.disabled'
151    pairs['window.button.pressed.focus'] = 'window.active.button.pressed'
152    pairs['window.button.pressed.unfocus'] = \
153        'window.inactive.button.pressed'
154    pairs['window.button.toggled.focus'] = 'window.active.button.toggled'
155    pairs['window.button.toggled.unfocus'] = \
156        'window.inactive.button.toggled'
157    pairs['window.button.focus'] = 'window.active.button'
158    pairs['window.button.unfocus'] = 'window.inactive.button'
159    pairs['window.button.hover.focus'] = 'window.active.button.hover'
160    pairs['window.button.hover.unfocus'] = 'window.inactive.button.hover'
161    pairs['window.label.unfocus'] = 'window.inactive.label'
162    pairs['window.label.focus'] = 'window.active.label'
163    pairs['window.button.focus'] = 'window.active.button.unpressed'
164    pairs['menu.disabled'] = 'menu.items.disabled'
165    pairs['menu.selected'] = 'menu.items.active'
166    pairs['window.button.unfocus'] = 'window.inactive.button.unpressed'
167    pairs['window.button.pressed.focus'] = 'window.active.button.pressed'
168    pairs['window.button.pressed.unfocus'] = 'window.inactive.button.pressed'
169    pairs['window.button.disabled.focus'] = 'window.active.button.disabled'
170    pairs['window.button.disabled.unfocus'] = 'window.inactive.button.disabled'
171    pairs['window.button.hover.focus'] = 'window.active.button.hover'
172    pairs['window.button.hover.unfocus'] = 'window.inactive.button.hover'
173    pairs['window.button.toggled.focus'] = 'window.active.button.toggled'
174    pairs['window.button.toggled.unfocus'] = 'window.inactive.button.toggled'
175
176    for k in pairs.keys():
177        while 1:
178            i, key, nul = find_key(data, k);
179            if i >= 0:
180                newl = data[i].replace(k, pairs[k])
181                out('Updating "' + key +
182                    '" to "' + key.replace(k, pairs[k]) + '"\n')
183                data[i] = newl
184            else:
185                break
186
187def replace_colors(data):
188    i = 0
189    n = len(data)
190    while i < n:
191        l = data[i]
192        key, value = getkeyval(l)
193        if key and value:
194            if key.find('.color') != -1:
195                if key.find('client.color') == -1 \
196                       and key.find('image.color') == -1 \
197                       and key.find('bg.color') == -1 \
198                       and key.find('border.color') == -1 \
199                       and key.find('interlace.color') == -1 \
200                       and key.find('text.color') == -1:
201                    newl = data[i].replace('.color', '.bg.color')
202                    out('Updating "' + key +
203                        '" to "' + key.replace('.color', '.bg.color') + '"\n')
204                    data[i] = newl
205            if key.find('.border.color') != -1 \
206               and key.find('bg.border.color') == -1:
207                newl = data[i].replace('.border.color', '.bg.border.color')
208                out('Updating "' + key +
209                    '" to "' + key.replace('.border.color',
210                                           '.bg.border.color') + '"\n')
211                data[i] = newl
212            if key.find('.interlace.color') != -1 \
213               and key.find('bg.interlace.color') == -1:
214                newl = data[i].replace('.interlace.color',
215                                       '.bg.interlace.color')
216                out('Updating "' + key +
217                    '" to "' + key.replace('.interlace.color',
218                                           '.bg.interlace.color') + '"\n')
219                data[i] = newl
220        i += 1
221
222def remove(data):
223    invalid = []
224    invalid.append('toolbar')
225    for inv in invalid:
226        while 1:
227            i, key, nul = find_key(data, inv)
228            if i >= 0:
229                out(key + ' is no longer supported.\nRemove (Y/n)? ')
230                if read_bool():
231                    out('Removing "' + key + '"\n')
232                    data.pop(i)
233            else:
234                break
235    invalid.append('rootCommand')
236    invalid.append('menu.bullet')
237    invalid.append('menu.bullet.image.color')
238    invalid.append('menu.bullet.selected.image.color')
239    invalid.append('menu.frame.justify')
240    for inv in invalid:
241        while 1:
242            i, key, nul = find_key(data, inv, True)
243            if i >= 0:
244                out(key + ' is no longer supported.\nRemove (Y/n)? ')
245                if read_bool():
246                    out('Removing "' + key + '"\n')
247                    data.pop(i)
248            else:
249                break
250
251def pressed(data):
252    i, nul, nul = find_key(data, 'window.button.pressed', True)
253    if i >= 0:
254        out('The window.button.pressed option has been replaced by ' +
255            'window.button.pressed.focus and ' +
256            'window.button.pressed.unfocus.\nUpdate (Y/n)? ')
257        if read_bool():
258            l = data[i]
259            out('Removing "window.button.pressed"\n')
260            data.pop(i)
261            out('Adding "window.button.pressed.unfocus"\n')
262            data.insert(i, l.replace('window.button.pressed',
263                                             'window.button.pressed.unfocus'))
264            out('Adding "window.button.pressed.focus"\n')
265            data.insert(i, l.replace('window.button.pressed',
266                                     'window.button.pressed.focus'))
267
268def x_fonts(data):
269    i, nul, nul = find_key(data, 'window.font')
270    if i >= 0:
271        out('You appear to specify fonts using the old X fonts ' +
272            'syntax.\nShall I remove all fonts from the theme (Y/n)? ')
273        if not read_bool():
274            return
275    else: return
276    while 1:
277        i, key, nul = find_key(data, '.font')
278        if i < 0:
279            break
280        out('Removing "' + key + '"\n')
281        data.pop(i)
282
283def xft_fonts(data):
284    i, nul, nul = find_key(data, '.xft.')
285    if i >= 0:
286        out('You appear to specify fonts using the old Xft fonts ' +
287            'syntax.\nShall I update these to the new syntax (Y/n)? ')
288        if not read_bool():
289            return
290    else: return
291    fonts = {}
292    fonts['window'] = 'window.label.focus.font'
293    fonts['menu.items'] = 'menu.items.font'
294    fonts['menu.title'] = 'menu.title.font'
295    for f in fonts.keys():
296        li, nul, flags = find_key(data, f + '.xft.flags')
297        if li < 0:
298            li, nul, flags = find_key(data, '*.xft.flags')
299        else:
300            out('Removing ' + f + '.xft.flags\n')
301            data.pop(li)
302        oi, nul, offset = find_key(data, f + '.xft.shadow.offset')
303        if oi < 0:
304            oi, nul, offset = find_key(data, '*.xft.shadow.offset')
305        else:
306            out('Removing ' + f + '.xft.shadow.offset\n')
307            data.pop(oi)
308        ti, nul, tint = find_key(data, f + '.xft.shadow.tint')
309        if ti < 0:
310            ti, nul, tint = find_key(data, '*.xft.shadow.tint')
311        else:
312            out('Removing ' + f + '.xft.shadow.tint\n')
313            data.pop(ti)
314        fi, nul, face = find_key(data, f + '.xft.font')
315        if fi < 0:
316            fi, nul, face = find_key(data, '*.xft.font')
317            if fi >= 0: fi = len(data) - 1
318        else:
319            out('Removing ' + f + '.xft.font\n')
320            data.pop(fi)
321
322        if fi >= 0:
323            s = face
324            if li >= 0:
325                if flags.find('bold'):
326                    s = s + ':bold'
327                if flags.find('shadow'):
328                    s = s + ':shadow=y'
329            if oi >= 0:
330                s = s + ':shadowoffset=' + offset
331            if ti >= 0:
332                s = s + ':shadowtint=' + tint
333        out('Adding ' + fonts[f] + '\n')
334        data.insert(fi, fonts[f] + ': ' + s)
335
336    for stars in ('*.xft.flags', '*.xft.shadow.offset' ,
337                  '*.xft.shadow.tint', '*.xft.font'):
338        i, key, nul = find_key(data, stars)
339        if i >= 0:
340            out('Removing ' + key + '\n')
341            data.pop(i)
342
343def pixelsize(data):
344    fonts = ('window.label.focus.font',
345             'menu.items.font',
346             'menu.title.font')
347    for f in fonts:
348        i, key, value = find_key(data, f, True)
349        if value:
350            if value.find('pixelsize') == -1:
351                out('*** ERROR *** The ' + key + ' font size is not being '
352                    'specified by pixelsize. It is recommended that you use '
353                    'pixelsize instead of pointsize for specifying theme '
354                    'fonts. e.g. "sans:pixelsize=12"\n')
355                global valid
356                valid = False
357
358def warn_missing(data):
359    need = ('window.active.button.hover',  'window.inactive.button.hover',
360            'menu.overlap')
361    for n in need:
362        i, nul, nul = find_key(data, n)
363        if i < 0:
364            out('The ' + n + ' value was not found in the theme, but it '
365                'can optionally be set.\n')
366
367def err_missing(data):
368    need = ('window.active.button.disabled',
369            'window.inactive.button.disabled',
370            'window.active.client.color',
371            'window.inactive.client.color')
372    for n in need:
373        i, nul, nul = find_key(data, n)
374        if i < 0:
375            out('*** ERROR *** The ' + n + ' value was not found in the '
376                'theme, but it is required to be set.\n')
377            global valid
378            valid = False
379
380
381def usage():
382    out('Usage: themupdate.py /path/to/themerc > newthemerc\n\n')
383    sys.exit()
384
385try:
386    file = open(sys.argv[1])
387except IndexError:
388    usage()
389except IOError:
390    out('Unable to open file "' + sys.argv[1] + '"\n\n')
391    usage()
392
393data = file.readlines()
394for i in range(len(data)):
395    data[i] = data[i].strip()
396
397simple_replace(data)
398replace_colors(data)
399remove(data)
400pressed(data)
401x_fonts(data)
402xft_fonts(data)
403pixelsize(data)
404warn_missing(data)
405err_missing(data)
406
407for l in data:
408    print l
409
410sys.exit(not valid)
411