##// END OF EJS Templates
updated license info...
florianlink -
r89:48cbcd0dff79
parent child
Show More
@@ -1,510 +1,509
1 1 #ifndef _PYTHONQTDOC_H
2 2 #define _PYTHONQTDOC_H
3 3
4 4 /*
5 5 *
6 6 * Copyright (C) 2006 MeVis Research GmbH All Rights Reserved.
7 7 *
8 8 * This library is free software; you can redistribute it and/or
9 9 * modify it under the terms of the GNU Lesser General Public
10 10 * License as published by the Free Software Foundation; either
11 11 * version 2.1 of the License, or (at your option) any later version.
12 12 *
13 13 * This library is distributed in the hope that it will be useful,
14 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 16 * Lesser General Public License for more details.
17 17 *
18 18 * Further, this software is distributed without any warranty that it is
19 19 * free of the rightful claim of any third person regarding infringement
20 20 * or the like. Any license provided herein, whether implied or
21 21 * otherwise, applies only to this software file. Patent licenses, if
22 22 * any, provided herein do not apply to combinations of this program with
23 23 * other software, or any other product whatsoever.
24 24 *
25 25 * You should have received a copy of the GNU Lesser General Public
26 26 * License along with this library; if not, write to the Free Software
27 27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 28 *
29 29 * Contact information: MeVis Research GmbH, Universitaetsallee 29,
30 30 * 28359 Bremen, Germany or:
31 31 *
32 32 * http://www.mevis.de
33 33 *
34 34 */
35 35
36 36 //----------------------------------------------------------------------------------
37 37 /*!
38 38 // \file PythonQtDoc.h
39 39 // \author Florian Link
40 40 // \author Last changed by $Author: florian $
41 41 // \date 2006-10
42 42 */
43 43 //----------------------------------------------------------------------------------
44 44
45 45 /*!
46 46 \if USE_GLOBAL_DOXYGEN_DOC
47 47 \page PythonQtPage PythonQt Overview
48 48 \else
49 49 \mainpage PythonQt Overview
50 50 \endif
51 51
52 52 \section Introduction
53 53
54 54 \b PythonQt is a dynamic Python (http://www.python.org) binding for Qt (http://www.qtsoftware.com).
55 55 It offers an easy way to embed the Python scripting language into
56 56 your Qt applications. It makes heavy use of the QMetaObject system and thus requires Qt4.x.
57 57
58 58 The focus of PythonQt is on embedding Python into an existing C++ application, not on writing the whole
59 59 application completely in Python. If you want to write your whole application in Python,
60 60 you should use <a href="http://www.riverbankcomputing.co.uk/pyqt/">PyQt</a> instead.
61 61
62 62 If you are looking for a simple way to embed Python objects into your C++/Qt Application
63 63 and to script parts of your application via Python, PythonQt is the way to go!
64 64
65 65 PythonQt is a stable library that was developed to make the
66 66 Image Processing and Visualization platform MeVisLab (http://www.mevislab.de)
67 67 scriptable from Python.
68 68
69 69 \section Download
70 70
71 71 PythonQt is hosted on SourceForge at http://sourceforge.net/projects/pythonqt , you can access it via SVN
72 72 or download a tarball.
73 73
74 74 \section Licensing
75 75
76 76 PythonQt is distributed under the LGPL license, so it pairs well with the LGPL of the Qt 4.5 release and allows
77 77 to be used in commercial applications when following the LGPL 2.1 obligations.
78 78
79 79 \section LicensingWrapper Licensing of Wrapper Generator
80 80
81 The build system of PythonQt makes use of a modified version of the GPL'ed QtScript generator,
81 The build system of PythonQt makes use of a modified version of the LGPL'ed QtScript generator,
82 82 located in the "generator" directory.
83 83
84 See http://labs.trolltech.com/page/Projects/QtScript/Generator for details on the original project.
84 See http://qt.gitorious.org/qt-labs/qtscriptgenerator for details on the original project.
85 85 Thanks a lot to the QtJambi guys and the QtScript Generator project for the C++ parser and
86 86 Qt typesystem files!
87 87
88 The PythonQt wrappers generated by the generator located in the "generated_cpp" directory are distributed under the LGPL,
89 they are not restriced by the GPL.
88 The PythonQt wrappers generated by the generator located in the "generated_cpp" directory are distributed under the LGPL as well.
90 89
91 90 The generated wrappers are pre-generated and checked-in for Qt 4.4.3, so you only need to build and run the
92 91 generator when you want to build additional wrappers or you want to upgrade/downgrade to an newer Qt version.
93 92 You may use the generator to generate C++ bindings for your own C++ classes (e.g. to make them deriveable in Python),
94 93 , but this is currently not documented and involves creating your own typesystem files.
95 94
96 95 \section Features
97 96
98 97 The following are the built-in features of the PythonQt library:
99 98
100 99 - Access all \b slots, \b properties, children and registered enums of any QObject derived class from Python
101 100 - Connecting Qt Signals to Python functions (both from within Python and from C++)
102 101 - Easy wrapping of Python objects from C++ with smart, reference-counting PythonQtObjectPtr.
103 102 - Convenient conversions to/from QVariant for PythonQtObjectPtr.
104 103 - Wrapping of C++ objects (which are not derived from QObject) via PythonQtCppWrapperFactory
105 104 - Extending C++ and QObject derived classes with additional slots, static methods and constructors (see Decorators)
106 105 - StdOut/Err redirection to Qt signals instead of cout
107 106 - Interface for creating your own \c import replacement, so that Python scripts can be e.g. signed/verified before they are executed (PythonQtImportFileInterface)
108 107 - Mapping of plain-old-datatypes and ALL QVariant types to and from Python
109 108 - Support for wrapping of user QVariant types which are registerd via QMetaType
110 109 - Support for Qt namespace (with all enumerators)
111 110 - All PythonQt wrapped objects support the dir() statement, so that you can see easily which attributes a QObject, CPP object or QVariant has
112 111 - No preprocessing/wrapping tool needs to be started, PythonQt can script any QObject without prior knowledge about it (except for the MetaObject information from the \b moc)
113 112 - Multiple inheritance for C++ objects (e.g. a QWidget is derived from QObject and QPaintDevice, PythonQt will automatically cast a QWidget to a QPaintDevice when needed)
114 113 - Polymorphic downcasting (if e.g. PythonQt sees a QEvent, it can downcast it depending on the type(), so the Python e.g. sees a QPaintEvent instead of a plain QEvent)
115 114 - Deriving C++ objects from Python and overwriting virtual method with a Python implementation (requires usage of wrapper generator or manual work!)
116 115 - Extensible handler for Python/C++ conversion of complex types, e.g. mapping of QVector<SomeObject> to/from a Python array
117 116
118 117 \section FeaturesQtAll Features (with PythonQt_QtAll linked in)
119 118
120 119 Thanks to the new wrapper generator, PythonQt now offers the additional PythonQt_QtAll library which wraps the complete Qt API, including all C++ classes and all non-slots on QObject derived classes.
121 120 This offers the following features:
122 121
123 122 - Complete Qt API wrapped and accessible
124 123 - The following modules are available as submodule of the PythonQt module:
125 124 - QtCore
126 125 - QtGui
127 126 - QtNetwork
128 127 - QtOpenGL
129 128 - QtSql
130 129 - QtSvg
131 130 - QtUiTools
132 131 - QtWebKit
133 132 - QtXml
134 133 - QtXmlPatterns
135 134 - (phonon, QtHelp, assistant, designer are currently not supported, this would require some additional effort on the code generator)
136 135 - For convenience, all classes are also available in the PythonQt.Qt module, for people who do not care in which module a class is located
137 136 - Any Qt class that has virtual methods can be easily derived from Python and the virtual methods can be reimplemented in Python
138 137 - Polymorphic downcasting on QEvent, QGraphicsItem, QStyleOption, ...
139 138 - Multiple inheritance support (e.g. QGraphicsTextItem is a QObject AND a QGraphicsItem, PythonQt will handle this well)
140 139
141 140 \section Comparision Comparision with PyQt
142 141
143 142 - PythonQt is not as Pythonic as PyQt in many details (e.g. operator mapping, pickling, translation support, ...) and it is mainly thought for embedding and intercommunication between Qt/Cpp and Python
144 143 - PythonQt allows to communicate in both directions, e.g. calling a Python object from C++ AND calling a C++ method from Python, while PyQt only handles the Python->C++ direction
145 144 - PythonQt offers properties as Python attributes, while PyQt offers them as setter/getter methods (e.g. QWidget.width is a property in PythonQt and a method in PyQt)
146 145 - PythonQt does not support instanceof checks for Qt classes, except for the exact match and derived Python classes
147 146 - QObject.emit to emit Qt signals from Python is not yet implemented but PythonQt allows to just emit a signal by calling it
148 147 - PythonQt does not offer to add new signals to Python/C++ objects
149 148 - Ownership of objects is a bit different in PythonQt, currently Python classes derived from a C++ class need to be manually references in PythonQt to not get deleted too early (this will be fixed)
150 149 - Probably there are lots of details that differ, I do not know PyQt that well to list them all.
151 150
152 151
153 152 \section Interface
154 153
155 154 The main interface to PythonQt is the PythonQt singleton.
156 155 PythonQt needs to be initialized via PythonQt::init() once.
157 156 Afterwards you communicate with the singleton via PythonQt::self().
158 157 PythonQt offers a complete Qt binding, which
159 158 needs to be enabled via PythonQt_QtAll::init().
160 159
161 160
162 161 \section Datatype Datatype Mapping
163 162
164 163 The following table shows the mapping between Python and Qt objects:
165 164 <table>
166 165 <tr><th>Qt/C++</th><th>Python</th></tr>
167 166 <tr><td>bool</td><td>bool</td></tr>
168 167 <tr><td>double</td><td>float</td></tr>
169 168 <tr><td>float</td><td>float</td></tr>
170 169 <tr><td>char/uchar,int/uint,short,ushort,QChar</td><td>integer</td></tr>
171 170 <tr><td>long</td><td>integer</td></tr>
172 171 <tr><td>ulong,longlong,ulonglong</td><td>long</td></tr>
173 172 <tr><td>QString</td><td>unicode string</td></tr>
174 173 <tr><td>QByteArray</td><td>str</td></tr>
175 174 <tr><td>char*</td><td>str</td></tr>
176 175 <tr><td>QStringList</td><td>tuple of unicode strings</td></tr>
177 176 <tr><td>QVariantList</td><td>tuple of objects</td></tr>
178 177 <tr><td>QVariantMap</td><td>dict of objects</td></tr>
179 178 <tr><td>QVariant</td><td>depends on type, see below</td></tr>
180 179 <tr><td>QSize, QRect and all other standard Qt QVariants</td><td>variant wrapper that supports complete API of the respective Qt classes</td></tr>
181 180 <tr><td>OwnRegisteredMetaType</td><td>C++ wrapper, optionally with additional information/wrapping provided by registerCPPClass()</td></tr>
182 181 <tr><td>QList<AnyObject*></td><td>converts to a list of CPP wrappers</td></tr>
183 182 <tr><td>EnumType</td><td>integer (all enums that are known via the moc and the Qt namespace are supported)</td></tr>
184 183 <tr><td>QObject (and derived classes)</td><td>QObject wrapper</td></tr>
185 184 <tr><td>C++ object</td><td>CPP wrapper, either wrapped via PythonQtCppWrapperFactory or just decorated with decorators</td></tr>
186 185 <tr><td>PyObject</td><td>PyObject</td></tr>
187 186 </table>
188 187
189 188 PyObject is passed as simple pointer, which allows to pass/return any Python Object directly to/from
190 189 a Qt slot.
191 190 QVariants are mapped recursively as given above, e.g. a dictionary can
192 191 contain lists of dictionaries of doubles.
193 192 For example a QVariant of type "String" is mapped to a python unicode string.
194 193 All Qt QVariant types are implemented, PythonQt supports the complete Qt API for these object.
195 194
196 195 \section QObject QObject Wrapping
197 196
198 197 All classes derived from QObject are automatically wrapped with a python wrapper class
199 198 when they become visible to the Python interpreter. This can happen via
200 199 - the PythonQt::addObject() method
201 200 - when a Qt \b slot returns a QObject derived object to python
202 201 - when a Qt \b signal contains a QObject and is connected to a python function
203 202
204 203 It is important that you call PythonQt::registerClass() for any QObject derived class
205 204 that may become visible to Python, except when you add it via PythonQt::addObject().
206 205 This will register the complete parent hierachy of the registered class, so that
207 206 when you register e.g. a QPushButton, QWidget will be registered as well (and all intermediate
208 207 parents).
209 208
210 209 From Python, you can talk to the returned QObjects in a natural way by calling
211 210 their slots and receiving the return values. You can also read/write all
212 211 properties of the objects as if they where normal python properties.
213 212
214 213 In addition to this, the wrapped objects support
215 214 - className() - returns a string that reprents the classname of the QObject
216 215 - help() - shows all properties, slots, enums, decorator slots and constructors of the object, in a printable form
217 216 - delete() - deletes the object (use with care, especially if you passed the ownership to C++)
218 217 - connect(signal, function) - connect the signal of the given object to a python function
219 218 - connect(signal, qobject, slot) - connect the signal of the given object to a slot of another QObject
220 219 - disconnect(signal, function) - disconnect the signal of the given object from a python function
221 220 - disconnect(signal, qobject, slot) - disconnect the signal of the given object from a slot of another QObject
222 221 - children() - returns the children of the object
223 222 - setParent(QObject) - set the parent
224 223 - QObject* parent() - get the parent
225 224
226 225 The below example shows how to connect signals in Python:
227 226
228 227 \code
229 228 # define a signal handler function
230 229 def someFunction(flag):
231 230 print flag
232 231
233 232 # button1 is a QPushButton that has been added to Python via addObject()
234 233 # connect the clicked signal to a python function:
235 234 button1.connect("clicked(bool)", someFunction)
236 235
237 236 \endcode
238 237
239 238 \section CPP CPP Wrapping
240 239
241 240 You can create dedicated wrapper QObjects for any C++ class. This is done by deriving from PythonQtCppWrapperFactory
242 241 and adding your factory via addWrapperFactory().
243 242 Whenever PythonQt encounters a CPP pointer (e.g. on a slot or signal)
244 243 and it does not known it as a QObject derived class, it will create a generic CPP wrapper. So even unknown C++ objects
245 244 can be passed through Python. If the wrapper factory supports the CPP class, a QObject wrapper will be created for each
246 245 instance that enters Python. An alternative to a complete wrapper via the wrapper factory are decorators, see \ref Decorators
247 246
248 247 \section MetaObject Meta Object/Class access
249 248
250 249 For each known C++ class, PythonQt provides a Python class. These classes are visible
251 250 inside of the "PythonQt" python module or in subpackages if a package is given when the class is registered.
252 251
253 252 A Meta class supports:
254 253
255 254 - access to all declared enum values
256 255 - constructors
257 256 - static methods
258 257 - unbound non-static methods
259 258 - help() and className()
260 259
261 260 From within Python, you can import the module "PythonQt" to access these classes and the Qt namespace.
262 261
263 262 \code
264 263 from PythonQt import QtCore
265 264
266 265 # namespace access:
267 266 print QtCore.Qt.AlignLeft
268 267
269 268 # constructors
270 269 a = QtCore.QSize(12,13)
271 270 b = QtCore.QFont()
272 271
273 272 # static method
274 273 QtCore.QDate.currentDate()
275 274
276 275 # enum value
277 276 QtCore.QFont.UltraCondensed
278 277
279 278 \endcode
280 279
281 280 \section Decorators Decorator slots
282 281
283 282 PythonQt introduces a new generic approach to extend any wrapped QObject or CPP object with
284 283
285 284 - constructors
286 285 - destructors (for CPP objects)
287 286 - additional slots
288 287 - static slots (callable on both the Meta object and the instances)
289 288
290 289 The idea behind decorators is that we wanted to make it as easy as possible to extend
291 290 wrapped objects. Since we already have an implementation for invoking any Qt Slot from
292 291 Python, it looked promising to use this approach for the extension of wrapped objects as well.
293 292 This avoids that the PythonQt user needs to care about how Python arguments are mapped from/to
294 293 Qt when he wants to create static methods, constructors and additional member functions.
295 294
296 295 The basic idea about decorators is to create a QObject derived class that implements slots
297 296 which take one of the above roles (e.g. constructor, destructor etc.) via a naming convention.
298 297 These slots are then assigned to other classes via the naming convention.
299 298
300 299 - SomeClassName* new_SomeClassName(...) - defines a constructor for "SomeClassName" that returns a new object of type SomeClassName (where SomeClassName can be any CPP class, not just QObject classes)
301 300 - void delete_SomeClassName(SomeClassName* o) - defines a destructor, which should delete the passed in object o
302 301 - anything static_SomeClassName_someMethodName(...) - defines a static method that is callable on instances and the meta class
303 302 - anything someMethodName(SomeClassName* o, ...) - defines a slot that will be available on SomeClassName instances (and derived instances). When such a slot is called the first argument is the pointer to the instance and the rest of the arguments can be used to make a call on the instance.
304 303
305 304 The below example shows all kinds of decorators in action:
306 305
307 306 \code
308 307
309 308 // an example CPP object
310 309 class YourCPPObject {
311 310 public:
312 311 YourCPPObject(int arg1, float arg2) { a = arg1; b = arg2; }
313 312
314 313 float doSomething(int arg1) { return arg1*a*b; };
315 314
316 315 private:
317 316
318 317 int a;
319 318 float b;
320 319 };
321 320
322 321 // an example decorator
323 322 class ExampleDecorator : public QObject
324 323 {
325 324 Q_OBJECT
326 325
327 326 public slots:
328 327 // add a constructor to QSize that takes a QPoint
329 328 QSize* new_QSize(const QPoint& p) { return new QSize(p.x(), p.y()); }
330 329
331 330 // add a constructor for QPushButton that takes a text and a parent widget
332 331 QPushButton* new_QPushButton(const QString& text, QWidget* parent=NULL) { return new QPushButton(text, parent); }
333 332
334 333 // add a constructor for a CPP object
335 334 YourCPPObject* new_YourCPPObject(int arg1, float arg2) { return new YourCPPObject(arg1, arg2); }
336 335
337 336 // add a destructor for a CPP object
338 337 void delete_YourCPPObject(YourCPPObject* obj) { delete obj; }
339 338
340 339 // add a static method to QWidget
341 340 QWidget* static_QWidget_mouseGrabber() { return QWidget::mouseGrabber(); }
342 341
343 342 // add an additional slot to QWidget (make move() callable, which is not declared as a slot in QWidget)
344 343 void move(QWidget* w, const QPoint& p) { w->move(p); }
345 344
346 345 // add an additional slot to QWidget, overloading the above move method
347 346 void move(QWidget* w, int x, int y) { w->move(x,y); }
348 347
349 348 // add a method to your own CPP object
350 349 int doSomething(YourCPPObject* obj, int arg1) { return obj->doSomething(arg1); }
351 350 };
352 351
353 352 ...
354 353
355 354 PythonQt::self()->addDecorators(new ExampleDecorator());
356 355 PythonQt::self()->registerCPPClass("YourCPPObject");
357 356
358 357 \endcode
359 358
360 359 After you have registered an instance of the above ExampleDecorator, you can do the following from Python
361 360 (all these calls are mapped to the above decorator slots):
362 361
363 362 \code
364 363 from PythonQt import QtCore, QtGui, YourCPPObject
365 364
366 365 # call our new constructor of QSize
367 366 size = QtCore.QSize(QPoint(1,2));
368 367
369 368 # call our new QPushButton constructor
370 369 button = QtGui.QPushButton("sometext");
371 370
372 371 # call the move slot (overload1)
373 372 button.move(QPoint(0,0))
374 373
375 374 # call the move slot (overload2)
376 375 button.move(0,0)
377 376
378 377 # call the static method
379 378 grabber = QtGui.QWidget.mouseWrapper();
380 379
381 380 # create a CPP object via constructor
382 381 yourCpp = YourCPPObject(1,11.5)
383 382
384 383 # call the wrapped method on CPP object
385 384 print yourCpp.doSomething(1);
386 385
387 386 # destructor will be called:
388 387 yourCpp = None
389 388
390 389 \endcode
391 390
392 391 \section Building
393 392
394 393 PythonQt requires at least Qt 4.2.2 (or higher) and Python 2.3, 2.4, 2.5 or 2.6 on Windows, Linux and MacOS X. It has not yet been tested with Python 3.x, but it should only require minor changes.
395 394 To compile PythonQt, you will need a python developer installation which includes Python's header files and
396 395 the python2x.[lib | dll | so | dynlib].
397 396 The build scripts a currently set to use Python 2.5.
398 397 You may need to tweak the \b build/python.prf file to set the correct Python includes and libs on your system.
399 398
400 399 \subsection Windows
401 400
402 401 On Windows, the (non-source) Python Windows installer can be used.
403 402 Make sure that you use the same compiler, the current Python distribution is built
404 403 with Visual Studio 2003. If you want to use another compiler, you will need to build
405 404 Python yourself, using your compiler.
406 405
407 406 To build PythonQt, you need to set the environment variable \b PYTHON_PATH to point to the root
408 407 dir of the python installation and \b PYTHON_LIB to point to
409 408 the directory where the python lib file is located.
410 409
411 410 When using the prebuild Python installer, this will be:
412 411
413 412 \code
414 413 > set PYTHON_PATH = c:\Python25
415 414 > set PYTHON_LIB = c:\Python25\libs
416 415 \endcode
417 416
418 417 When using the python sources, this will be something like:
419 418
420 419 \code
421 420 > set PYTHON_PATH = c:\yourDir\Python-2.5.1\
422 421 > set PYTHON_LIB = c:\yourDir\Python-2.5.1\PCbuild8\Win32
423 422 \endcode
424 423
425 424 To build all, do the following (after setting the above variables):
426 425
427 426 \code
428 427 > cd PythonQtRoot
429 428 > vcvars32
430 429 > qmake
431 430 > nmake
432 431 \endcode
433 432
434 433 This should build everything. If Python can not be linked or include files can not be found,
435 434 you probably need to tweak \b build/python.prf
436 435
437 436 The tests and examples are located in PythonQt/lib.
438 437
439 438 \subsection Linux
440 439
441 440 On Linux, you need to install a Python-dev package.
442 441 If Python can not be linked or include files can not be found,
443 442 you probably need to tweak \b build/python.prf
444 443
445 444 To build PythonQt, just do a:
446 445
447 446 \code
448 447 > cd PythonQtRoot
449 448 > qmake
450 449 > make all
451 450 \endcode
452 451
453 452 The tests and examples are located in PythonQt/lib.
454 453 You should add PythonQt/lib to your LD_LIBRARY_PATH so that the runtime
455 454 linker can find the *.so files.
456 455
457 456 \subsection MacOsX
458 457
459 458 On Mac, Python is installed as a Framework, so you should not need to install it.
460 459 To build PythonQt, just do a:
461 460
462 461 \code
463 462 > cd PythonQtRoot
464 463 > qmake
465 464 > make all
466 465 \endcode
467 466
468 467 \section Tests
469 468
470 469 There is a unit test that tests most features of PythonQt, see the \b tests subdirectory for details.
471 470
472 471 \section Examples
473 472
474 473 Examples are available in the \b examples directory. The PyScriptingConsole implements a simple
475 474 interactive scripting console that shows how to script a simple application.
476 475
477 476 The following shows how to integrate PythonQt into you Qt application:
478 477
479 478 \code
480 479 #include "PythonQt.h"
481 480 #include <QApplication>
482 481 ...
483 482
484 483 int main( int argc, char **argv )
485 484 {
486 485
487 486 QApplication qapp(argc, argv);
488 487
489 488 // init PythonQt and Python itself
490 489 PythonQt::init(PythonQt::IgnoreSiteModule | PythonQt::RedirectStdOut);
491 490
492 491
493 492 // get a smart pointer to the __main__ module of the Python interpreter
494 493 PythonQtObjectPtr mainContext = PythonQt::self()->getMainModule();
495 494
496 495 // add a QObject as variable of name "example" to the namespace of the __main__ module
497 496 PyExampleObject example;
498 497 PythonQt::self()->addObject(mainContext, "example", &example);
499 498
500 499 // do something
501 500 PythonQt::self()->runScript(mainContext, "print example\n");
502 501 PythonQt::self()->runScript(mainContext, "def multiply(a,b):\n return a*b;\n");
503 502 QVariantList args;
504 503 args << 42 << 47;
505 504 QVariant result = PythonQt::self()->call(mainContext,"multiply", args);
506 505 ...
507 506 \endcode
508 507
509 508
510 509 */
General Comments 0
You need to be logged in to leave comments. Login now