##// END OF EJS Templates
florianlink -
r162:1182b71738ca
parent child
Show More
@@ -1,182 +1,188
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
4 4 ** All rights reserved.
5 5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 6 **
7 7 ** This file is part of the Qt Script Generator project on Qt Labs.
8 8 **
9 9 ** $QT_BEGIN_LICENSE:LGPL$
10 10 ** No Commercial Usage
11 11 ** This file contains pre-release code and may not be distributed.
12 12 ** You may use this file in accordance with the terms and conditions
13 13 ** contained in the Technology Preview License Agreement accompanying
14 14 ** this package.
15 15 **
16 16 ** GNU Lesser General Public License Usage
17 17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 18 ** General Public License version 2.1 as published by the Free Software
19 19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 20 ** packaging of this file. Please review the following information to
21 21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 23 **
24 24 ** In addition, as a special exception, Nokia gives you certain additional
25 25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 27 **
28 28 ** If you have questions regarding the use of this file, please contact
29 29 ** Nokia at qt-info@nokia.com.
30 30 **
31 31 **
32 32 **
33 33 **
34 34 **
35 35 **
36 36 **
37 37 **
38 38 ** $QT_END_LICENSE$
39 39 **
40 40 ****************************************************************************/
41 41
42 42 #include "main.h"
43 43 #include "asttoxml.h"
44 44 #include "reporthandler.h"
45 45 #include "typesystem.h"
46 46 #include "generatorset.h"
47 47 #include "fileout.h"
48 48
49 49 #include <QDir>
50 50
51 51 void displayHelp(GeneratorSet *generatorSet);
52 52
53 53 #include <QDebug>
54 54 int main(int argc, char *argv[])
55 55 {
56 56 GeneratorSet *gs = GeneratorSet::getInstance();
57 57
58 58 QString default_file = ":/trolltech/generator/qtscript_masterinclude.h";
59 59 QString default_system = ":/trolltech/generator/build_all.txt";
60 60
61 61 QString fileName;
62 62 QString typesystemFileName;
63 63 QString pp_file = ".preprocessed.tmp";
64 64 QStringList rebuild_classes;
65 65
66 66 QMap<QString, QString> args;
67 67
68 68 int argNum = 0;
69 69 for (int i=1; i<argc; ++i) {
70 70 QString arg(argv[i]);
71 71 arg = arg.trimmed();
72 72 if( arg.startsWith("--") ) {
73 73 int split = arg.indexOf("=");
74 74 if( split > 0 )
75 75 args[arg.mid(2).left(split-2)] = arg.mid(split + 1).trimmed();
76 76 else
77 77 args[arg.mid(2)] = QString();
78 78 } else if( arg.startsWith("-")) {
79 79 args[arg.mid(1)] = QString();
80 80 } else {
81 81 argNum++;
82 82 args[QString("arg-%1").arg(argNum)] = arg;
83 83 }
84 84 }
85 85
86 86 if (args.contains("no-suppress-warnings")) {
87 87 TypeDatabase *db = TypeDatabase::instance();
88 88 db->setSuppressWarnings(false);
89 89 }
90 90
91 91 if (args.contains("debug-level")) {
92 92 QString level = args.value("debug-level");
93 93 if (level == "sparse")
94 94 ReportHandler::setDebugLevel(ReportHandler::SparseDebug);
95 95 else if (level == "medium")
96 96 ReportHandler::setDebugLevel(ReportHandler::MediumDebug);
97 97 else if (level == "full")
98 98 ReportHandler::setDebugLevel(ReportHandler::FullDebug);
99 99 }
100 100
101 101 if (args.contains("dummy")) {
102 102 FileOut::dummy = true;
103 103 }
104 104
105 105 if (args.contains("diff")) {
106 106 FileOut::diff = true;
107 107 }
108 108
109 109 if (args.contains("license"))
110 110 FileOut::license = true;
111 111
112 112 if (args.contains("rebuild-only")) {
113 113 QStringList classes = args.value("rebuild-only").split(",", QString::SkipEmptyParts);
114 114 TypeDatabase::instance()->setRebuildClasses(classes);
115 115 }
116 116
117 117 fileName = args.value("arg-1");
118 118
119 119 typesystemFileName = args.value("arg-2");
120 120 if (args.contains("arg-3"))
121 121 displayHelp(gs);
122 122
123 123 if (fileName.isEmpty())
124 124 fileName = default_file;
125 125
126 126 if (typesystemFileName.isEmpty())
127 127 typesystemFileName = default_system;
128 128
129 129 if (fileName.isEmpty() || typesystemFileName.isEmpty() )
130 130 displayHelp(gs);
131 131
132 132 if (!gs->readParameters(args))
133 133 displayHelp(gs);
134 134
135 135 printf("Please wait while source files are being generated...\n");
136 136
137 printf("Parsing typesystem file [%s]\n", qPrintable(typesystemFileName));
137 138 if (!TypeDatabase::instance()->parseFile(typesystemFileName))
138 139 qFatal("Cannot parse file: '%s'", qPrintable(typesystemFileName));
139 140
141 printf("PreProcessing - Generate [%s] using [%s] and include-paths [%s]\n",
142 qPrintable(pp_file), qPrintable(fileName), qPrintable(args.value("include-paths")));
140 143 if (!Preprocess::preprocess(fileName, pp_file, args.value("include-paths"))) {
141 144 fprintf(stderr, "Preprocessor failed on file: '%s'\n", qPrintable(fileName));
142 145 return 1;
143 146 }
144 147
145 148 if (args.contains("ast-to-xml")) {
149 printf("Running ast-to-xml on file [%s] using pp_file [%s] and include-paths [%s]\n",
150 qPrintable(fileName), qPrintable(pp_file), qPrintable(args.value("include-paths")));
146 151 astToXML(pp_file);
147 152 return 0;
148 153 }
149 154
155 printf("Building model using [%s]\n", qPrintable(pp_file));
150 156 gs->buildModel(pp_file);
151 157 if (args.contains("dump-object-tree")) {
152 158 gs->dumpObjectTree();
153 159 return 0;
154 160 }
155 161 printf("%s\n", qPrintable(gs->generate()));
156 162
157 163 printf("Done, %d warnings (%d known issues)\n", ReportHandler::warningCount(),
158 164 ReportHandler::suppressedCount());
159 165 }
160 166
161 167
162 168 void displayHelp(GeneratorSet* generatorSet) {
163 169 #if defined(Q_OS_WIN32)
164 170 char path_splitter = ';';
165 171 #else
166 172 char path_splitter = ':';
167 173 #endif
168 174 printf("Usage:\n generator [options] header-file typesystem-file\n\n");
169 175 printf("Available options:\n\n");
170 176 printf("General:\n");
171 177 printf(" --debug-level=[sparse|medium|full] \n"
172 178 " --dump-object-tree \n"
173 179 " --help, -h or -? \n"
174 180 " --no-suppress-warnings \n"
175 181 " --output-directory=[dir] \n"
176 182 " --include-paths=<path>[%c<path>%c...] \n"
177 183 " --print-stdout \n",
178 184 path_splitter, path_splitter);
179 185
180 186 printf("%s", qPrintable( generatorSet->usage()));
181 187 exit(0);
182 188 }
@@ -1,74 +1,74
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
4 4 ** All rights reserved.
5 5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 6 **
7 7 ** This file is part of the Qt Script Generator project on Qt Labs.
8 8 **
9 9 ** $QT_BEGIN_LICENSE:LGPL$
10 10 ** No Commercial Usage
11 11 ** This file contains pre-release code and may not be distributed.
12 12 ** You may use this file in accordance with the terms and conditions
13 13 ** contained in the Technology Preview License Agreement accompanying
14 14 ** this package.
15 15 **
16 16 ** GNU Lesser General Public License Usage
17 17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 18 ** General Public License version 2.1 as published by the Free Software
19 19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 20 ** packaging of this file. Please review the following information to
21 21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 23 **
24 24 ** In addition, as a special exception, Nokia gives you certain additional
25 25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 27 **
28 28 ** If you have questions regarding the use of this file, please contact
29 29 ** Nokia at qt-info@nokia.com.
30 30 **
31 31 **
32 32 **
33 33 **
34 34 **
35 35 **
36 36 **
37 37 **
38 38 ** $QT_END_LICENSE$
39 39 **
40 40 ****************************************************************************/
41 41
42 42 #include "reporthandler.h"
43 43 #include "typesystem.h"
44 44
45 45 int ReportHandler::m_warning_count = 0;
46 46 int ReportHandler::m_suppressed_count = 0;
47 47 QString ReportHandler::m_context;
48 48 ReportHandler::DebugLevel ReportHandler::m_debug_level = NoDebug;
49 49 QSet<QString> ReportHandler::m_reported_warnings;
50 50
51 51
52 52 void ReportHandler::warning(const QString &text)
53 53 {
54 54 QString warningText = QString("WARNING(%1) :: %2").arg(m_context).arg(text);
55 55
56 56 TypeDatabase *db = TypeDatabase::instance();
57 57 if (db && db->isSuppressedWarning(warningText)) {
58 58 ++m_suppressed_count;
59 59 } else if (!m_reported_warnings.contains(warningText)) {
60 qDebug(qPrintable(warningText));
60 qDebug("%s", qPrintable(warningText));
61 61 ++m_warning_count;
62 62
63 63 m_reported_warnings.insert(warningText);
64 64 }
65 65 }
66 66
67 67 void ReportHandler::debug(DebugLevel level, const QString &text)
68 68 {
69 69 if (m_debug_level == NoDebug)
70 70 return;
71 71
72 72 if (level <= m_debug_level)
73 73 qDebug(" - DEBUG(%s) :: %s", qPrintable(m_context), qPrintable(text));
74 74 }
@@ -1,303 +1,305
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
4 4 ** All rights reserved.
5 5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 6 **
7 7 ** This file is part of the Qt Script Generator project on Qt Labs.
8 8 **
9 9 ** $QT_BEGIN_LICENSE:LGPL$
10 10 ** No Commercial Usage
11 11 ** This file contains pre-release code and may not be distributed.
12 12 ** You may use this file in accordance with the terms and conditions
13 13 ** contained in the Technology Preview License Agreement accompanying
14 14 ** this package.
15 15 **
16 16 ** GNU Lesser General Public License Usage
17 17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 18 ** General Public License version 2.1 as published by the Free Software
19 19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 20 ** packaging of this file. Please review the following information to
21 21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 23 **
24 24 ** In addition, as a special exception, Nokia gives you certain additional
25 25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 27 **
28 28 ** If you have questions regarding the use of this file, please contact
29 29 ** Nokia at qt-info@nokia.com.
30 30 **
31 31 **
32 32 **
33 33 **
34 34 **
35 35 **
36 36 **
37 37 **
38 38 ** $QT_END_LICENSE$
39 39 **
40 40 ****************************************************************************/
41 41
42 42 #include "setupgenerator.h"
43 43 #include "shellgenerator.h"
44 44 #include "reporthandler.h"
45 45 #include "fileout.h"
46 46
47 47 //#define Q_SCRIPT_LAZY_GENERATOR
48 48
49 49 void SetupGenerator::addClass(const QString& package, const AbstractMetaClass *cls)
50 50 {
51 51 packHash[package].append(cls);
52 52 }
53 53
54 54 void maybeDeclareMetaType(QTextStream &stream, const QString &typeName,
55 55 QSet<QString> &registeredTypeNames);
56 56 bool hasDefaultConstructor(const AbstractMetaClass *meta_class);
57 57
58 58 static QStringList getOperatorCodes(const AbstractMetaClass* cls) {
59 59 QSet<QString> operatorCodes;
60 60 AbstractMetaFunctionList returned;
61 61 AbstractMetaFunctionList functions = cls->functions();
62 62 foreach (AbstractMetaFunction *function, functions) {
63 63 if (function->originalName().startsWith("operator")) {
64 64 QString op = function->originalName().mid(8);
65 65 operatorCodes.insert(op);
66 66 }
67 67 }
68 68 QSet<QString> r;
69 69 foreach(QString op, operatorCodes.toList()) {
70 70 if (op == ">" || op == "<" || op == ">=" || op == "<=" || op == "==" || op == "!=") {
71 71 r.insert("PythonQt::Type_RichCompare");
72 72 } else if (op == "+") {
73 73 r.insert("PythonQt::Type_Add");
74 74 } else if (op == "-") {
75 75 r.insert("PythonQt::Type_Subtract");
76 76 } else if (op == "/") {
77 77 r.insert("PythonQt::Type_Divide");
78 78 } else if (op == "*") {
79 79 r.insert("PythonQt::Type_Multiply");
80 80 } else if (op == "%") {
81 81 r.insert("PythonQt::Type_Mod");
82 82 } else if (op == "&") {
83 83 r.insert("PythonQt::Type_And");
84 84 } else if (op == "|") {
85 85 r.insert("PythonQt::Type_Or");
86 86 } else if (op == "^") {
87 87 r.insert("PythonQt::Type_Xor");
88 88 } else if (op == "~") {
89 89 r.insert("PythonQt::Type_Invert");
90 90
91 91 } else if (op == "+=") {
92 92 r.insert("PythonQt::Type_InplaceAdd");
93 93 } else if (op == "-=") {
94 94 r.insert("PythonQt::Type_InplaceSubtract");
95 95 } else if (op == "/=") {
96 96 r.insert("PythonQt::Type_InplaceDivide");
97 97 } else if (op == "*=") {
98 98 r.insert("PythonQt::Type_InplaceMultiply");
99 99 } else if (op == "%=") {
100 100 r.insert("PythonQt::Type_InplaceMod");
101 101 } else if (op == "&=") {
102 102 r.insert("PythonQt::Type_InplaceAnd");
103 103 } else if (op == "|=") {
104 104 r.insert("PythonQt::Type_InplaceOr");
105 105 } else if (op == "^=") {
106 106 r.insert("PythonQt::Type_InplaceXor");
107 107 }
108 108 }
109 109 if (cls->hasDefaultIsNull()) {
110 110 r.insert("PythonQt::Type_NonZero");
111 111 }
112 112 QStringList result = r.toList();
113 113 return result;
114 114 }
115 115
116 116 static bool class_less_than(const AbstractMetaClass *a, const AbstractMetaClass *b)
117 117 {
118 118 return a->name() < b->name();
119 119 }
120 120
121 121 void SetupGenerator::generate()
122 122 {
123 123 AbstractMetaClassList classes_with_polymorphic_id;
124 124 {
125 125 QHashIterator<QString, QList<const AbstractMetaClass*> > pack(packHash);
126 126 while (pack.hasNext()) {
127 127 pack.next();
128 128 QList<const AbstractMetaClass*> list = pack.value();
129 129 foreach (const AbstractMetaClass *cls, list) {
130 130 if (cls->typeEntry()->isPolymorphicBase()) {
131 131 classes_with_polymorphic_id.append((AbstractMetaClass*)cls);
132 132 }
133 133 }
134 134 }
135 135 }
136 136 qSort(classes_with_polymorphic_id.begin(), classes_with_polymorphic_id.end(), class_less_than);
137 137
138 138 QHashIterator<QString, QList<const AbstractMetaClass*> > pack(packHash);
139 139 while (pack.hasNext()) {
140 140 pack.next();
141 141 QList<const AbstractMetaClass*> list = pack.value();
142 142 if (list.isEmpty())
143 143 continue;
144 144 qSort(list.begin(), list.end(), class_less_than);
145 145
146 146 QString packKey = pack.key();
147 147 QString packName = pack.key();
148 148 QStringList components = packName.split("_");
149 149 if ((components.size() > 2) && (components.at(0) == "com")
150 150 && (components.at(1) == "trolltech")) {
151 151 // kill com.trolltech in key
152 152 components.removeAt(0);
153 153 components.removeAt(0);
154 154 }
155 155
156 156 QString shortPackName;
157 157 foreach (QString comp, components) {
158 158 comp[0] = comp[0].toUpper();
159 159 shortPackName += comp;
160 160 }
161 161 // add missing camel case (workaround..)
162 162 if (shortPackName == "QtWebkit") {
163 163 shortPackName = "QtWebKit";
164 164 } else if (shortPackName == "QtXmlpatterns") {
165 165 shortPackName = "QtXmlPatterns";
166 166 } else if (shortPackName == "QtOpengl") {
167 167 shortPackName = "QtOpenGL";
168 168 } else if (shortPackName == "QtUitools") {
169 169 shortPackName = "QtUiTools";
170 170 }
171 171
172 172
173 173 {
174 FileOut initFile(m_out_dir + "/generated_cpp/" + packName + "/" + packKey + "_init.cpp");
174 QString fileName(packName + "/" + packKey + "_init.cpp");
175 FileOut initFile(m_out_dir + "/generated_cpp/" + fileName);
176 ReportHandler::debugSparse(QString("generating: %1").arg(fileName));
175 177 QTextStream &s = initFile.stream;
176 178
177 179 s << "#include <PythonQt.h>" << endl;
178 180
179 181 for (int i=0; i<(list.count()+MAX_CLASSES_PER_FILE-1) / MAX_CLASSES_PER_FILE; i++) {
180 182 s << "#include \"" << packKey << QString::number(i) << ".h\"" << endl;
181 183 }
182 184 s << endl;
183 185
184 186 QStringList polymorphicHandlers;
185 187 if (!packName.endsWith("_builtin")) {
186 188 polymorphicHandlers = writePolymorphicHandler(s, list.at(0)->package(), classes_with_polymorphic_id, list);
187 189 s << endl;
188 190 }
189 191
190 192 // declare individual class creation functions
191 193 s << "void PythonQt_init_" << shortPackName << "(PyObject* module) {" << endl;
192 194
193 195 if (shortPackName.endsWith("Builtin")) {
194 196 shortPackName = shortPackName.mid(0, shortPackName.length()-strlen("builtin"));
195 197 }
196 198
197 199 QStringList cppClassNames;
198 200 foreach (const AbstractMetaClass *cls, list) {
199 201
200 202 QString shellCreator;
201 203 if (cls->generateShellClass()) {
202 204 shellCreator = ", PythonQtSetInstanceWrapperOnShell<" + ShellGenerator::shellClassName(cls) + ">";
203 205 } else {
204 206 shellCreator = ", NULL";
205 207 }
206 208 QString operatorCodes = getOperatorCodes(cls).join("|");
207 209 if (operatorCodes.isEmpty()) {
208 210 operatorCodes = "0";
209 211 }
210 212 if (cls->isQObject()) {
211 213 s << "PythonQt::priv()->registerClass(&" << cls->qualifiedCppName() << "::staticMetaObject, \"" << shortPackName <<"\", PythonQtCreateObject<PythonQtWrapper_" << cls->name() << ">" << shellCreator << ", module, " << operatorCodes <<");" << endl;
212 214 } else {
213 215 QString baseName = cls->baseClass()?cls->baseClass()->qualifiedCppName():"";
214 216 s << "PythonQt::priv()->registerCPPClass(\""<< cls->qualifiedCppName() << "\", \"" << baseName << "\", \"" << shortPackName <<"\", PythonQtCreateObject<PythonQtWrapper_" << cls->name() << ">" << shellCreator << ", module, " << operatorCodes <<");" << endl;
215 217 }
216 218 foreach(AbstractMetaClass* interface, cls->interfaces()) {
217 219 // the interface might be our own class... (e.g. QPaintDevice)
218 220 if (interface->qualifiedCppName() != cls->qualifiedCppName()) {
219 221 s << "PythonQt::self()->addParentClass(\""<< cls->qualifiedCppName() << "\", \"" << interface->qualifiedCppName() << "\",PythonQtUpcastingOffset<" << cls->qualifiedCppName() <<","<<interface->qualifiedCppName()<<">());" << endl;
220 222 }
221 223 }
222 224 }
223 225 s << endl;
224 226 foreach (QString handler, polymorphicHandlers) {
225 227 s << "PythonQt::self()->addPolymorphicHandler(\""<< handler << "\", polymorphichandler_" << handler << ");" << endl;
226 228 }
227 229
228 230 s << "}";
229 231 s << endl;
230 232 }
231 233 }
232 234 }
233 235
234 236 QStringList SetupGenerator::writePolymorphicHandler(QTextStream &s, const QString &package,
235 237 const AbstractMetaClassList &polybase, QList<const AbstractMetaClass*>& allClasses)
236 238 {
237 239 QStringList handlers;
238 240 foreach (AbstractMetaClass *cls, polybase) {
239 241 const ComplexTypeEntry *centry = cls->typeEntry();
240 242 if (!centry->isPolymorphicBase())
241 243 continue;
242 244 bool isGraphicsItem = (cls->qualifiedCppName()=="QGraphicsItem");
243 245
244 246 bool first = true;
245 247 foreach (const AbstractMetaClass *clazz, allClasses) {
246 248 bool inherits = false;
247 249 if (isGraphicsItem) {
248 250 foreach(AbstractMetaClass* interfaze, clazz->interfaces()) {
249 251 if (interfaze->qualifiedCppName()=="QGraphicsItem") {
250 252 inherits = true;
251 253 break;
252 254 }
253 255 }
254 256 } else {
255 257 inherits = clazz->inheritsFrom(cls);
256 258 }
257 259 if (clazz->package() == package && inherits) {
258 260 if (!clazz->typeEntry()->polymorphicIdValue().isEmpty() || isGraphicsItem) {
259 261 // On first find, open the function
260 262 if (first) {
261 263 first = false;
262 264
263 265 QString handler = cls->name();
264 266 handlers.append(handler);
265 267
266 268 s << "static void* polymorphichandler_" << handler
267 << "(const void *ptr, char **class_name)" << endl
269 << "(const void *ptr, const char **class_name)" << endl
268 270 << "{" << endl
269 271 << " Q_ASSERT(ptr != 0);" << endl
270 272 << " " << cls->qualifiedCppName() << " *object = ("
271 273 << cls->qualifiedCppName() << " *)ptr;" << endl;
272 274 }
273 275
274 276 // For each, add case label
275 277 QString polyId = clazz->typeEntry()->polymorphicIdValue();
276 278 if (isGraphicsItem) {
277 279 polyId = "%1->type() == " + clazz->qualifiedCppName() + "::Type";
278 280 }
279 281 s << " if ("
280 282 << polyId.replace("%1", "object")
281 283 << ") {" << endl
282 284 << " *class_name = \"" << clazz->name() << "\";" << endl
283 285 << " return (" << clazz->qualifiedCppName() << "*)object;" << endl
284 286 << " }" << endl;
285 287 } else {
286 288 QString warning = QString("class '%1' inherits from polymorphic class '%2', but has no polymorphic id set")
287 289 .arg(clazz->name())
288 290 .arg(cls->name());
289 291
290 292 ReportHandler::warning(warning);
291 293 }
292 294 }
293 295 }
294 296
295 297 // Close the function if it has been opened
296 298 if (!first) {
297 299 s << " return NULL;" << endl
298 300 << "}" << endl;
299 301 }
300 302 }
301 303
302 304 return handlers;
303 305 }
General Comments 0
You need to be logged in to leave comments. Login now