File: ConversationLayoutManager.py

package info (click to toggle)
emesene 1.0-dist-4
  • links: PTS, VCS
  • area: main
  • in suites: lenny
  • size: 4,596 kB
  • ctags: 3,006
  • sloc: python: 25,171; makefile: 14; sh: 1
file content (359 lines) | stat: -rw-r--r-- 13,411 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# -*- coding: utf-8 -*-

#   This file is part of emesene.
#
#    Emesene is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    emesene is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with emesene; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import os
import re
import time
import emesenelib.common
from emesenecommon import PATH
from Parser import ConversationDataType


tagRe = re.compile('%(.*?)%')

class ConversationLayoutManager(object):
    ''' This class handles the conversation theming system '''
    
    def __init__(self, controller):
        self.infos = {}
        self.theme = {}
        self.reset()
        self.setDefault()        
        self.controller = controller
        self.config = controller.config
        self.unifiedParser = controller.unifiedParser
         
    def reset(self):
        ''' Resets the theme '''
        self.infos['name'] = ''
        self.infos['description'] = ''
        self.infos['author'] = ''
        self.infos['website'] = ''
        self.theme['incoming'] = ''
        self.theme['consecutive_incoming'] = ''
        self.theme['offline_incoming'] = ''
        self.theme['outgoing'] = ''
        self.theme['consecutive_outgoing'] = ''
        self.theme['information'] = ''
        self.theme['error'] = ''
    
    def setValue(self, key, value):
        ''' Sets a key according to the given value '''
        if key in self.theme.keys():    
            self.theme[key] = value
        elif key in self.infos.keys():
            self.infos[key] = value
        else:
            emesenelib.common.debug(key + ' is not a valid property for a conversation layout !')
            
    def resolveValue(self):
        ''' Resolve shortcut like "incoming=outgoing" '''
        for key, value in self.theme.iteritems():
            if value in self.theme.keys():
                self.theme[key] = self.theme[value]
                
    def setDefault(self):
        ''' Sets the default theme (you should use that when a load fails) '''
        self.theme['incoming'] = '<span style="font-weight: bold;">%nick% says :</span><br/>    [%h%:%m%:%s%] %formattedmessage%<br/>'
        self.theme['consecutive_incoming'] = '    [%h%:%m%:%s%] %formattedmessage%<br/>'
        self.theme['offline_incoming'] = 'incoming'
        self.theme['outgoing'] = 'incoming'
        self.theme['consecutive_outgoing'] = 'consecutive_incoming'
        self.theme['information'] = '<span style="font-weight: bold;">%message%</span><br/>'
        self.theme['error'] = '<span style="font-weight: bold; color: #FF0000;">%message%</span><br/>'
        self.resolveValue()
                        
    def load(self, name):
        '''Loads a theme, return true on success, false otherwise.
        It seeks the theme in config dir, and then in app dir.
        If the loads fails, the old layout is restored''' 
        def doLoad(filename):
            self.reset()
            themefile = None
            try:
                themefile = open(filename, 'r')
                themestring = themefile.read()
                
                for i in themestring.splitlines():
                    i = i.lstrip()
                    if i != '' and not i.startswith('#'):
                        delim = i.find('=')
                        key = i[:delim].lower()
                        value = i[delim+1:]
                        self.setValue(key, value)
                
                self.resolveValue()
                themefile.close()
                
                if not self.isValid():
                    return False
                else:
                    return True
                    
            except:
                if themefile:
                    themefile.close()
                return False
        
        oldInfos = self.infos
        oldTheme = self.theme
        
        filename = PATH.CONVTHEMES_HOME_PATH + os.sep + name + os.sep + 'theme'
        if not os.path.exists(filename):
            filename = PATH.CONVTHEMES_SYSTEM_WIDE_PATH + os.sep + name + \
                os.sep + 'theme'
        
        if os.path.exists(filename):
            success = doLoad(filename)
        else:
            success = False
            
        if not success:
            self.infos = oldInfos
            self.theme = oldTheme
            
        return success
          
    def isValid(self):
        ''' Checks if the theme is valid '''
        for value in self.theme.values():
            if value == None or value in self.theme.keys():
                return False         
        return True
                
    def listAvailableThemes(self):
        ''' Lists all available and valid themes and return a list '''
        conversationLayout = ConversationLayoutManager(self.controller)
        validThemesList = []
        
        try:
            homethemes = os.listdir(PATH.CONVTHEMES_HOME_PATH)
        except OSError:
            homethemes = []
        try:
            globalthemes = os.listdir(PATH.CONVTHEMES_SYSTEM_WIDE_PATH)
        except OSError:
            globalthemes = []
        
        for currentTheme in homethemes + globalthemes:
            if conversationLayout.load(currentTheme):
                validThemesList.append(currentTheme)
                                
        return validThemesList

    def layout(self, username, message, format, conversation, type, timestamp, \
               ink=False):
        ''' Returns HTML code according to the the current theme '''
        
        if type not in self.theme.keys():
            return ''
            
        switchboard = conversation.switchboard

        if not ink:
            messageDataObject = self.unifiedParser.getParser(message, ConversationDataType)
            messageDataObject.setConversation(conversation)
            messageDataObject.setUser(username)

            if self.config.user['parseSmilies']:
                message = messageDataObject.get()
            else:
                message = messageDataObject.get(smileys=False)
            
            message = message.replace('\n', '<br/>')
        else:
            message = '<img src="file://%s" alt="Ink message" />' % message
            
        arguments = {}
        
        # Fills nick
        arguments['nick'] = ''
        if username != None:
            arguments['nick'] = self.controller.msn.getUserDisplayName(username)
                
        # Fills message
        arguments['message'] = message
        
        # Fills avatar
        arguments['avatar'] = ''
        if type == 'outgoing' or type == 'consecutive_outgoing':
            arguments['avatar'] = self.controller.avatar.getImagePath()
        elif type == 'incoming' or type == 'consecutive_incoming':
            arguments['avatar'] = self.controller.msn.cacheDir + os.sep + username.split('@')[0] + '.tmp'        
        
        # Fills (?) timestamp
        arguments['timestamp'] = timestamp

        # Layout
        if format is None:
            parser = LayoutTagParser(self, arguments, self.getCssStyle(), type)
        else:
            parser = LayoutTagParser(self, arguments, format, type)
            
        result = tagRe.sub(parser.sub, self.theme[type])
        
        return '<span><span style="font-size: %spt;">%s</span></span>' % \
               (self.config.user['fontSize'], result)
               
    def getPreview(self):
        ''' Returns a preview of the layout '''
        preview = ''
        arguments = {}
        userStyle = self.getCssStyle()
        
        # Fills arguments with faked ones
        # First message
        arguments['nick'] = self.controller.msn.nick
        arguments['message'] = _('This is an outgoing message')
        arguments['avatar'] = self.controller.avatar.getImagePath()
        arguments['timestamp'] = time.time()
        parser = LayoutTagParser(self, arguments, userStyle, 'outgoing')
        preview += tagRe.sub(parser.sub, self.theme['outgoing'])
        
        # Second message
        arguments['message'] = _('This is a consecutive outgoing message')
        parser = LayoutTagParser(self, arguments, userStyle, 'consecutive_outgoing')
        preview += tagRe.sub(parser.sub, self.theme['consecutive_outgoing'])
        
        # Third message
        arguments['nick'] = 'John Doe'
        arguments['message'] = _('This is an incoming message')
        # TODO : have an example picture
        arguments['avatar'] = self.controller.avatar.getImagePath()
        parser = LayoutTagParser(self, arguments, None, 'incoming')
        preview += tagRe.sub(parser.sub, self.theme['incoming'])
        
        # Fourth message
        arguments['message'] = _('This is a consecutive incoming message')
        parser = LayoutTagParser(self, arguments, None, 'consecutive_incoming')
        preview += tagRe.sub(parser.sub, self.theme['consecutive_incoming'])
        
        # Information message
        arguments['message'] = _('This is an information message')
        parser = LayoutTagParser(self, arguments, None, 'information')
        preview += tagRe.sub(parser.sub, self.theme['information'])
        
        # Fourth message
        arguments['message'] = _('This is an error message')
        parser = LayoutTagParser(self, arguments, None, 'error')
        preview += tagRe.sub(parser.sub, self.theme['error'])       
        
        return '<span><span style="font-size: %spt;">%s</span></span>' % \
               (self.config.user['fontSize'], preview)
               
    def getCssStyle(self):
        '''return the css style of the user'''
    
        style = ''

        if self.config.user['fontBold']:
            style += 'font-weight: bold;'

        if self.config.user['fontItalic']:
            style += 'font-style: italic;'

        if self.config.user['fontUnderline']:
            if self.config.user['fontStrike']:
                style += 'text-decoration: underline line-through;'
            else:
                style += 'text-decoration: underline;'
        elif self.config.user['fontStrike']:
            style += 'text-decoration: line-through;'

        style += 'font-size: ' + str(self.config.user['fontSize']) + 'pt;'
        style += 'color: ' + str(self.config.user['fontColor']) + ';'
        style += 'font-family: ' + str(self.config.user['fontFace']) + ';'
        
        return style
        
    def getName(self):
        return self.infos['name']
    
    def getDescription(self):
        return self.infos['description']
        
    def getAuthor(self):
        return self.infos['author']
        
    def getWebsite(self):
        return self.infos['website']
        
class LayoutTagParser:
    '''thread-safe environment to parse layout tags'''

    def __init__(self, parent, arguments, format, type):
        self.parent = parent
        self.arguments = arguments
        self.format = format
        self.type = type

        self.controller = parent.controller
        self.config = parent.controller.config
        self.unifiedParser = parent.unifiedParser

    def sub(self, data):
        ''' Called by re.sub for each tag found '''
        tag = data.groups()[0].lower()

        if tag == 'nick':
            nick = self.arguments['nick']
            
            nickDataObject = self.unifiedParser.getParser(nick, ConversationDataType)
            if self.config.user['parseSmilies']:
                nick = nickDataObject.get(urls=False)
            else:
                nick = nickDataObject.get(urls=False, smileys=False)
            
            return nick
                    
        elif tag == 'message' or \
             (self.config.user['disableFormat'] and tag == 'formattedmessage'):
            return self.arguments['message']
        
        elif tag == 'formattedmessage':
            message = self.arguments['message']
            return '<span style="%s">%s</span>' % (self.format, message)
            
        elif tag == 'h':
            return time.strftime('%H', time.localtime(self.arguments['timestamp']))
         
        elif tag == 'm':
            return time.strftime('%M', time.localtime(self.arguments['timestamp']))
         
        elif tag == 's':
            return time.strftime('%S', time.localtime(self.arguments['timestamp']))
             
        elif tag == 'date':
            return time.strftime('%x', time.localtime(self.arguments['timestamp']))

        elif tag == 'avatar':               
            return '<img src="file://' + self.arguments['avatar'] + '"/>'
        
        elif tag == 'says':
            return _('says')

        else:
            return data.groups()[0]
        
    def extractArgument(self, tag):
        ''' Retrieves arguments from a tag '''
        start = tag.find('(')+1
        end = tag.find(')')
        arglist = tag[start:end].replace(' ', '')
        return arglist.split(',')