Fix nasty drawing bug. Add preliminary release roadmap to dev-notes.
[quassel.git] / src / uisupport / uistyle.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-07 by the Quassel IRC Team                         *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include "uistyle.h"
22
23 UiStyle::UiStyle() {
24   // Default format
25   QTextCharFormat def;
26   def.setForeground(QBrush("#000000"));
27   def.setFont(QFont("Verdana",9));
28
29   _formats = QVector<QTextCharFormat>(NumFormatTypes, def);
30
31   // Initialize color codes according to mIRC "standard"
32   QStringList colors;
33   //colors << "white" << "black" << "navy" << "green" << "red" << "maroon" << "purple" << "orange";
34   //colors << "yellow" << "lime" << "teal" << "aqua" << "royalblue" << "fuchsia" << "grey" << "silver";
35   colors << "#ffffff" << "#000000" << "#000080" << "#008000" << "#ff0000" << "#800000" << "#800080" << "#ffa500";
36   colors << "#ffff00" << "#00ff00" << "#008080" << "#00ffff" << "#4169E1" << "#ff00ff" << "#808080" << "#c0c0c0";
37
38   // Now initialize the mapping between FormatCodes and FormatTypes...
39   _formatCodes["%O"] = None;
40   _formatCodes["%B"] = Bold;
41   _formatCodes["%S"] = Italic;
42   _formatCodes["%U"] = Underline;
43   _formatCodes["%R"] = Reverse;
44
45   _formatCodes["%D0"] = PlainMsg;
46   _formatCodes["%Dn"] = NoticeMsg;
47   _formatCodes["%Ds"] = ServerMsg;
48   _formatCodes["%De"] = ErrorMsg;
49   _formatCodes["%Dj"] = JoinMsg;
50   _formatCodes["%Dp"] = PartMsg;
51   _formatCodes["%Dq"] = QuitMsg;
52   _formatCodes["%Dk"] = KickMsg;
53   _formatCodes["%Dr"] = RenameMsg;
54   _formatCodes["%Dm"] = ModeMsg;
55   _formatCodes["%Da"] = ActionMsg;
56
57   _formatCodes["%DT"] = Timestamp;
58   _formatCodes["%DS"] = Sender;
59   _formatCodes["%DN"] = Nick;
60   _formatCodes["%DH"] = Hostmask;
61   _formatCodes["%DC"] = ChannelName;
62   _formatCodes["%DM"] = ModeFlags;
63   _formatCodes["%DU"] = Url;
64
65   // Set color formats
66   for(int i = 0; i < 16; i++) {
67     QString idx = QString("%1").arg(i, (int)2, (int)10, (QChar)'0');
68     _formatCodes[QString("%Dcf%1").arg(idx)] = (FormatType)(FgCol00 + i);
69     _formatCodes[QString("%Dcb%1").arg(idx)] = (FormatType)(BgCol00 + i);
70     QTextCharFormat fgf, bgf;
71     fgf.setForeground(QBrush(QColor(colors[i]))); _formats[FgCol00 + i] = fgf;
72     bgf.setBackground(QBrush(QColor(colors[i]))); _formats[BgCol00 + i] = bgf;
73   }
74
75   // Set a few more standard formats
76   QTextCharFormat bold; bold.setFontWeight(QFont::Bold);
77   setFormat(Bold, bold);
78
79   QTextCharFormat italic; italic.setFontItalic(true);
80   setFormat(Italic, italic);
81
82   QTextCharFormat underline; underline.setFontUnderline(true);
83   setFormat(Underline, underline);
84
85   // All other formats should be defined in derived classes.
86 }
87
88 UiStyle::~ UiStyle() {
89   
90 }
91
92 void UiStyle::setFormat(FormatType ftype, QTextCharFormat fmt) {
93   _formats[ftype] = fmt;
94 }
95
96 QTextCharFormat UiStyle::format(FormatType ftype) const {
97   return _formats[ftype];
98 }
99
100 UiStyle::FormatType UiStyle::formatType(const QString & code) const {
101   if(_formatCodes.contains(code)) return _formatCodes.value(code);
102   return Invalid;
103 }
104
105 QString UiStyle::formatCode(FormatType ftype) const {
106   return _formatCodes.key(ftype);
107 }
108
109 UiStyle::StyledText UiStyle::styleString(QString s) {
110   StyledText result;
111   QList<FormatType> fmtList;
112   fmtList.append(None);
113   QTextLayout::FormatRange curFmtRng;
114   curFmtRng.format = format(None);
115   curFmtRng.start = 0;
116   result.formats.append(curFmtRng);
117   int pos = 0; int length;
118   int fgCol = -1, bgCol = -1;  // marks current mIRC color
119   for(;;) {
120     pos = s.indexOf('%', pos);
121     if(pos < 0) break;
122     if(s[pos+1] == '%') { // escaped %, just remove one and continue
123       s.remove(pos, 1);
124       pos++;
125       continue;
126     } else if(s[pos+1] == 'D' && s[pos+2] == 'c') { // color code
127       if(s[pos+3] == '-') { // color off
128         if(fgCol >= 0) {
129           fmtList.removeAll((FormatType)(FgCol00 + fgCol));
130           fgCol = -1;
131         }
132         if(bgCol >= 0) {
133           fmtList.removeAll((FormatType)(BgCol00 + bgCol));
134           bgCol = -1;
135         }
136         curFmtRng.format = mergedFormat(fmtList);
137         length = 4;
138       } else {
139         int color = 10 * s[pos+4].digitValue() + s[pos+5].digitValue();
140         int *colptr; FormatType coltype;
141         if(s[pos+3] == 'f') { // foreground
142           colptr = &fgCol; coltype = FgCol00;
143         } else {              // background
144           Q_ASSERT(s[pos+3] == 'b');
145           colptr = &bgCol; coltype = BgCol00;
146         }
147         if(*colptr >= 0) {
148           // color already set, remove format code and add new one
149           Q_ASSERT(fmtList.contains((FormatType)(coltype + *colptr)));
150           fmtList.removeAll((FormatType)(coltype + *colptr));
151           fmtList.append((FormatType)(coltype + color));
152           curFmtRng.format = mergedFormat(fmtList);
153         } else {
154           fmtList.append((FormatType)(coltype + color));
155           curFmtRng.format.merge(format(fmtList.last()));
156         }
157         *colptr = color;
158         length = 6;
159       }
160     } else if(s[pos+1] == 'O') { // reset formatting
161       fmtList.clear(); fmtList.append(None);
162       curFmtRng.format = format(None);
163       fgCol = bgCol = -1;
164       length = 2;
165     } else if(s[pos+1] == 'R') { // reverse
166       // TODO: implement reverse formatting
167
168       length = 2;
169     } else { // all others are toggles
170       QString code = QString("%") + s[pos+1];
171       if(s[pos+1] == 'D') code += s[pos+2];
172       FormatType ftype = formatType(code);
173       if(ftype == Invalid) {
174         qWarning(qPrintable(QString("Invalid format code in string: %1").arg(s)));
175         continue;
176       }
177       //Q_ASSERT(ftype != Invalid);
178       length = code.length();
179       if(!fmtList.contains(ftype)) {
180         // toggle it on
181         fmtList.append(ftype);
182         curFmtRng.format.merge(format(ftype));
183       } else {
184         // toggle it off
185         fmtList.removeAll(ftype);
186         curFmtRng.format = mergedFormat(fmtList);
187       }
188     }
189     s.remove(pos, length); // remove format code from string
190     // now see if something changed and else insert the format
191     if(curFmtRng.format == result.formats.last().format) continue;  // no change, so we just ignore
192     curFmtRng.start = pos;
193     if(pos == result.formats.last().start) {
194       // same starting point -> we just overwrite the old format
195       result.formats.last() = curFmtRng;
196     } else {
197       // fix length of last format
198       result.formats.last().length = pos - result.formats.last().start;
199       result.formats.append(curFmtRng);
200     }
201   }
202   result.formats.last().length = s.length() - result.formats.last().start;
203   if(result.formats.last().length == 0) result.formats.removeLast();
204   result.text = s;
205   return result;
206 }
207
208 QTextCharFormat UiStyle::mergedFormat(QList<FormatType> formatList) {
209   QTextCharFormat fmt;
210   foreach(FormatType ftype, formatList) {
211     fmt.merge(format(ftype));
212   }
213   return fmt;
214 }
215
216