source: trunk/src/corelib/animation/qvariantanimation.cpp@ 1168

Last change on this file since 1168 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

  • Property svn:eol-style set to native
File size: 23.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtCore module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qvariantanimation.h"
43#include "qvariantanimation_p.h"
44
45#include <QtCore/qrect.h>
46#include <QtCore/qline.h>
47#include <QtCore/qmutex.h>
48#include <private/qmutexpool_p.h>
49
50#ifndef QT_NO_ANIMATION
51
52QT_BEGIN_NAMESPACE
53
54/*!
55 \class QVariantAnimation
56 \ingroup animation
57 \brief The QVariantAnimation class provides an abstract base class for animations.
58 \since 4.6
59
60 This class is part of \l{The Animation Framework}. It serves as a
61 base class for property and item animations, with functions for
62 shared functionality.
63
64 QVariantAnimation cannot be used directly as it is an abstract
65 class; it has a pure virtual method called updateCurrentValue().
66 The class performs interpolation over
67 \l{QVariant}s, but leaves using the interpolated values to its
68 subclasses. Currently, Qt provides QPropertyAnimation, which
69 animates Qt \l{Qt's Property System}{properties}. See the
70 QPropertyAnimation class description if you wish to animate such
71 properties.
72
73 You can then set start and end values for the property by calling
74 setStartValue() and setEndValue(), and finally call start() to
75 start the animation. QVariantAnimation will interpolate the
76 property of the target object and emit valueChanged(). To react to
77 a change in the current value you have to reimplement the
78 updateCurrentValue() virtual function.
79
80 It is also possible to set values at specified steps situated
81 between the start and end value. The interpolation will then
82 touch these points at the specified steps. Note that the start and
83 end values are defined as the key values at 0.0 and 1.0.
84
85 There are two ways to affect how QVariantAnimation interpolates
86 the values. You can set an easing curve by calling
87 setEasingCurve(), and configure the duration by calling
88 setDuration(). You can change how the QVariants are interpolated
89 by creating a subclass of QVariantAnimation, and reimplementing
90 the virtual interpolated() function.
91
92 Subclassing QVariantAnimation can be an alternative if you have
93 \l{QVariant}s that you do not wish to declare as Qt properties.
94 Note, however, that you in most cases will be better off declaring
95 your QVariant as a property.
96
97 Not all QVariant types are supported. Below is a list of currently
98 supported QVariant types:
99
100 \list
101 \o \l{QMetaType::}{Int}
102 \o \l{QMetaType::}{Double}
103 \o \l{QMetaType::}{Float}
104 \o \l{QMetaType::}{QLine}
105 \o \l{QMetaType::}{QLineF}
106 \o \l{QMetaType::}{QPoint}
107 \o \l{QMetaType::}{QPointF}
108 \o \l{QMetaType::}{QSize}
109 \o \l{QMetaType::}{QSizeF}
110 \o \l{QMetaType::}{QRect}
111 \o \l{QMetaType::}{QRectF}
112 \o \l{QMetaType::}{QColor}
113 \endlist
114
115 If you need to interpolate other variant types, including custom
116 types, you have to implement interpolation for these yourself.
117 To do this, you can register an interpolator function for a given
118 type. This function takes 3 parameters: the start value, the end value
119 and the current progress.
120
121 Example:
122 \code
123 QVariant myColorInterpolator(const QColor &start, const QColor &end, qreal progress)
124 {
125 ...
126 return QColor(...);
127 }
128 ...
129 qRegisterAnimationInterpolator<QColor>(myColorInterpolator);
130 \endcode
131
132 Another option is to reimplement interpolated(), which returns
133 interpolation values for the value being interpolated.
134
135 \omit We need some snippets around here. \endomit
136
137 \sa QPropertyAnimation, QAbstractAnimation, {The Animation Framework}
138*/
139
140/*!
141 \fn void QVariantAnimation::valueChanged(const QVariant &value)
142
143 QVariantAnimation emits this signal whenever the current \a value changes.
144
145 \sa currentValue, startValue, endValue
146*/
147
148/*!
149 \fn void QVariantAnimation::updateCurrentValue(const QVariant &value) = 0;
150
151 This pure virtual function is called every time the animation's current
152 value changes. The \a value argument is the new current value.
153
154 \sa currentValue
155*/
156
157static bool animationValueLessThan(const QVariantAnimation::KeyValue &p1, const QVariantAnimation::KeyValue &p2)
158{
159 return p1.first < p2.first;
160}
161
162static QVariant defaultInterpolator(const void *, const void *, qreal)
163{
164 return QVariant();
165}
166
167template<> Q_INLINE_TEMPLATE QRect _q_interpolate(const QRect &f, const QRect &t, qreal progress)
168{
169 QRect ret;
170 ret.setCoords(_q_interpolate(f.left(), t.left(), progress),
171 _q_interpolate(f.top(), t.top(), progress),
172 _q_interpolate(f.right(), t.right(), progress),
173 _q_interpolate(f.bottom(), t.bottom(), progress));
174 return ret;
175}
176
177template<> Q_INLINE_TEMPLATE QRectF _q_interpolate(const QRectF &f, const QRectF &t, qreal progress)
178{
179 qreal x1, y1, w1, h1;
180 f.getRect(&x1, &y1, &w1, &h1);
181 qreal x2, y2, w2, h2;
182 t.getRect(&x2, &y2, &w2, &h2);
183 return QRectF(_q_interpolate(x1, x2, progress), _q_interpolate(y1, y2, progress),
184 _q_interpolate(w1, w2, progress), _q_interpolate(h1, h2, progress));
185}
186
187template<> Q_INLINE_TEMPLATE QLine _q_interpolate(const QLine &f, const QLine &t, qreal progress)
188{
189 return QLine( _q_interpolate(f.p1(), t.p1(), progress), _q_interpolate(f.p2(), t.p2(), progress));
190}
191
192template<> Q_INLINE_TEMPLATE QLineF _q_interpolate(const QLineF &f, const QLineF &t, qreal progress)
193{
194 return QLineF( _q_interpolate(f.p1(), t.p1(), progress), _q_interpolate(f.p2(), t.p2(), progress));
195}
196
197QVariantAnimationPrivate::QVariantAnimationPrivate() : duration(250), interpolator(&defaultInterpolator)
198{ }
199
200void QVariantAnimationPrivate::convertValues(int t)
201{
202 //this ensures that all the keyValues are of type t
203 for (int i = 0; i < keyValues.count(); ++i) {
204 QVariantAnimation::KeyValue &pair = keyValues[i];
205 pair.second.convert(static_cast<QVariant::Type>(t));
206 }
207 //we also need update to the current interval if needed
208 currentInterval.start.second.convert(static_cast<QVariant::Type>(t));
209 currentInterval.end.second.convert(static_cast<QVariant::Type>(t));
210
211 //... and the interpolator
212 updateInterpolator();
213}
214
215void QVariantAnimationPrivate::updateInterpolator()
216{
217 int type = currentInterval.start.second.userType();
218 if (type == currentInterval.end.second.userType())
219 interpolator = getInterpolator(type);
220 else
221 interpolator = 0;
222
223 //we make sure that the interpolator is always set to something
224 if (!interpolator)
225 interpolator = &defaultInterpolator;
226}
227
228/*!
229 \internal
230 The goal of this function is to update the currentInterval member. As a consequence, we also
231 need to update the currentValue.
232 Set \a force to true to always recalculate the interval.
233*/
234void QVariantAnimationPrivate::recalculateCurrentInterval(bool force/*=false*/)
235{
236 // can't interpolate if we don't have at least 2 values
237 if ((keyValues.count() + (defaultStartEndValue.isValid() ? 1 : 0)) < 2)
238 return;
239
240 const qreal progress = easing.valueForProgress(((duration == 0) ? qreal(1) : qreal(currentTime) / qreal(duration)));
241
242 //0 and 1 are still the boundaries
243 if (force || (currentInterval.start.first > 0 && progress < currentInterval.start.first)
244 || (currentInterval.end.first < 1 && progress > currentInterval.end.first)) {
245 //let's update currentInterval
246 QVariantAnimation::KeyValues::const_iterator it = qLowerBound(keyValues.constBegin(),
247 keyValues.constEnd(),
248 qMakePair(progress, QVariant()),
249 animationValueLessThan);
250 if (it == keyValues.constBegin()) {
251 //the item pointed to by it is the start element in the range
252 if (it->first == 0 && keyValues.count() > 1) {
253 currentInterval.start = *it;
254 currentInterval.end = *(it+1);
255 } else {
256 currentInterval.start = qMakePair(qreal(0), defaultStartEndValue);
257 currentInterval.end = *it;
258 }
259 } else if (it == keyValues.constEnd()) {
260 --it; //position the iterator on the last item
261 if (it->first == 1 && keyValues.count() > 1) {
262 //we have an end value (item with progress = 1)
263 currentInterval.start = *(it-1);
264 currentInterval.end = *it;
265 } else {
266 //we use the default end value here
267 currentInterval.start = *it;
268 currentInterval.end = qMakePair(qreal(1), defaultStartEndValue);
269 }
270 } else {
271 currentInterval.start = *(it-1);
272 currentInterval.end = *it;
273 }
274
275 // update all the values of the currentInterval
276 updateInterpolator();
277 }
278 setCurrentValueForProgress(progress);
279}
280
281void QVariantAnimationPrivate::setCurrentValueForProgress(const qreal progress)
282{
283 Q_Q(QVariantAnimation);
284
285 const qreal startProgress = currentInterval.start.first;
286 const qreal endProgress = currentInterval.end.first;
287 const qreal localProgress = (progress - startProgress) / (endProgress - startProgress);
288
289 QVariant ret = q->interpolated(currentInterval.start.second,
290 currentInterval.end.second,
291 localProgress);
292 qSwap(currentValue, ret);
293 q->updateCurrentValue(currentValue);
294 static QBasicAtomicInt changedSignalIndex = Q_BASIC_ATOMIC_INITIALIZER(0);
295 if (!changedSignalIndex) {
296 //we keep the mask so that we emit valueChanged only when needed (for performance reasons)
297 changedSignalIndex.testAndSetRelaxed(0, signalIndex("valueChanged(QVariant)"));
298 }
299 if (isSignalConnected(changedSignalIndex) && currentValue != ret) {
300 //the value has changed
301 emit q->valueChanged(currentValue);
302 }
303}
304
305QVariant QVariantAnimationPrivate::valueAt(qreal step) const
306{
307 QVariantAnimation::KeyValues::const_iterator result =
308 qBinaryFind(keyValues.begin(), keyValues.end(), qMakePair(step, QVariant()), animationValueLessThan);
309 if (result != keyValues.constEnd())
310 return result->second;
311
312 return QVariant();
313}
314
315void QVariantAnimationPrivate::setValueAt(qreal step, const QVariant &value)
316{
317 if (step < qreal(0.0) || step > qreal(1.0)) {
318 qWarning("QVariantAnimation::setValueAt: invalid step = %f", step);
319 return;
320 }
321
322 QVariantAnimation::KeyValue pair(step, value);
323
324 QVariantAnimation::KeyValues::iterator result = qLowerBound(keyValues.begin(), keyValues.end(), pair, animationValueLessThan);
325 if (result == keyValues.end() || result->first != step) {
326 keyValues.insert(result, pair);
327 } else {
328 if (value.isValid())
329 result->second = value; // replaces the previous value
330 else
331 keyValues.erase(result); // removes the previous value
332 }
333
334 recalculateCurrentInterval(/*force=*/true);
335}
336
337void QVariantAnimationPrivate::setDefaultStartEndValue(const QVariant &value)
338{
339 defaultStartEndValue = value;
340 recalculateCurrentInterval(/*force=*/true);
341}
342
343/*!
344 Construct a QVariantAnimation object. \a parent is passed to QAbstractAnimation's
345 constructor.
346*/
347QVariantAnimation::QVariantAnimation(QObject *parent) : QAbstractAnimation(*new QVariantAnimationPrivate, parent)
348{
349}
350
351/*!
352 \internal
353*/
354QVariantAnimation::QVariantAnimation(QVariantAnimationPrivate &dd, QObject *parent) : QAbstractAnimation(dd, parent)
355{
356}
357
358/*!
359 Destroys the animation.
360*/
361QVariantAnimation::~QVariantAnimation()
362{
363}
364
365/*!
366 \property QVariantAnimation::easingCurve
367 \brief the easing curve of the animation
368
369 This property defines the easing curve of the animation. By
370 default, a linear easing curve is used, resulting in linear
371 interpolation. Other curves are provided, for instance,
372 QEasingCurve::InCirc, which provides a circular entry curve.
373 Another example is QEasingCurve::InOutElastic, which provides an
374 elastic effect on the values of the interpolated variant.
375
376 QVariantAnimation will use the QEasingCurve::valueForProgress() to
377 transform the "normalized progress" (currentTime / totalDuration)
378 of the animation into the effective progress actually
379 used by the animation. It is this effective progress that will be
380 the progress when interpolated() is called. Also, the steps in the
381 keyValues are referring to this effective progress.
382
383 The easing curve is used with the interpolator, the interpolated()
384 virtual function, the animation's duration, and iterationCount, to
385 control how the current value changes as the animation progresses.
386*/
387QEasingCurve QVariantAnimation::easingCurve() const
388{
389 Q_D(const QVariantAnimation);
390 return d->easing;
391}
392
393void QVariantAnimation::setEasingCurve(const QEasingCurve &easing)
394{
395 Q_D(QVariantAnimation);
396 d->easing = easing;
397 d->recalculateCurrentInterval();
398}
399
400typedef QVector<QVariantAnimation::Interpolator> QInterpolatorVector;
401Q_GLOBAL_STATIC(QInterpolatorVector, registeredInterpolators)
402
403/*!
404 \fn void qRegisterAnimationInterpolator(QVariant (*func)(const T &from, const T &to, qreal progress))
405 \relates QVariantAnimation
406 \threadsafe
407
408 Registers a custom interpolator \a func for the template type \c{T}.
409 The interpolator has to be registered before the animation is constructed.
410 To unregister (and use the default interpolator) set \a func to 0.
411 */
412
413/*!
414 \internal
415 \typedef QVariantAnimation::Interpolator
416
417 This is a typedef for a pointer to a function with the following
418 signature:
419 \code
420 QVariant myInterpolator(const QVariant &from, const QVariant &to, qreal progress);
421 \endcode
422
423*/
424
425/*! \internal
426 * Registers a custom interpolator \a func for the specific \a interpolationType.
427 * The interpolator has to be registered before the animation is constructed.
428 * To unregister (and use the default interpolator) set \a func to 0.
429 */
430void QVariantAnimation::registerInterpolator(QVariantAnimation::Interpolator func, int interpolationType)
431{
432 // will override any existing interpolators
433 QInterpolatorVector *interpolators = registeredInterpolators();
434 // we may be called from the static destruction code at program termination
435 // when registeredInterpolators() has already gone
436 if (!interpolators)
437 return;
438#ifndef QT_NO_THREAD
439 QMutexLocker locker(QMutexPool::globalInstanceGet(interpolators));
440#endif
441 if (int(interpolationType) >= interpolators->count())
442 interpolators->resize(int(interpolationType) + 1);
443 interpolators->replace(interpolationType, func);
444}
445
446
447template<typename T> static inline QVariantAnimation::Interpolator castToInterpolator(QVariant (*func)(const T &from, const T &to, qreal progress))
448{
449 return reinterpret_cast<QVariantAnimation::Interpolator>(func);
450}
451
452QVariantAnimation::Interpolator QVariantAnimationPrivate::getInterpolator(int interpolationType)
453{
454 QInterpolatorVector *interpolators = registeredInterpolators();
455#ifndef QT_NO_THREAD
456 QMutexLocker locker(QMutexPool::globalInstanceGet(interpolators));
457#endif
458 QVariantAnimation::Interpolator ret = 0;
459 if (interpolationType < interpolators->count()) {
460 ret = interpolators->at(interpolationType);
461 if (ret) return ret;
462 }
463
464 switch(interpolationType)
465 {
466 case QMetaType::Int:
467 return castToInterpolator(_q_interpolateVariant<int>);
468 case QMetaType::Double:
469 return castToInterpolator(_q_interpolateVariant<double>);
470 case QMetaType::Float:
471 return castToInterpolator(_q_interpolateVariant<float>);
472 case QMetaType::QLine:
473 return castToInterpolator(_q_interpolateVariant<QLine>);
474 case QMetaType::QLineF:
475 return castToInterpolator(_q_interpolateVariant<QLineF>);
476 case QMetaType::QPoint:
477 return castToInterpolator(_q_interpolateVariant<QPoint>);
478 case QMetaType::QPointF:
479 return castToInterpolator(_q_interpolateVariant<QPointF>);
480 case QMetaType::QSize:
481 return castToInterpolator(_q_interpolateVariant<QSize>);
482 case QMetaType::QSizeF:
483 return castToInterpolator(_q_interpolateVariant<QSizeF>);
484 case QMetaType::QRect:
485 return castToInterpolator(_q_interpolateVariant<QRect>);
486 case QMetaType::QRectF:
487 return castToInterpolator(_q_interpolateVariant<QRectF>);
488 default:
489 return 0; //this type is not handled
490 }
491}
492
493/*!
494 \property QVariantAnimation::duration
495 \brief the duration of the animation
496
497 This property describes the duration in milliseconds of the
498 animation. The default duration is 250 milliseconds.
499
500 \sa QAbstractAnimation::duration()
501 */
502int QVariantAnimation::duration() const
503{
504 Q_D(const QVariantAnimation);
505 return d->duration;
506}
507
508void QVariantAnimation::setDuration(int msecs)
509{
510 Q_D(QVariantAnimation);
511 if (msecs < 0) {
512 qWarning("QVariantAnimation::setDuration: cannot set a negative duration");
513 return;
514 }
515 if (d->duration == msecs)
516 return;
517 d->duration = msecs;
518 d->recalculateCurrentInterval();
519}
520
521/*!
522 \property QVariantAnimation::startValue
523 \brief the optional start value of the animation
524
525 This property describes the optional start value of the animation. If
526 omitted, or if a null QVariant is assigned as the start value, the
527 animation will use the current position of the end when the animation
528 is started.
529
530 \sa endValue
531*/
532QVariant QVariantAnimation::startValue() const
533{
534 return keyValueAt(0);
535}
536
537void QVariantAnimation::setStartValue(const QVariant &value)
538{
539 setKeyValueAt(0, value);
540}
541
542/*!
543 \property QVariantAnimation::endValue
544 \brief the end value of the animation
545
546 This property describes the end value of the animation.
547
548 \sa startValue
549 */
550QVariant QVariantAnimation::endValue() const
551{
552 return keyValueAt(1);
553}
554
555void QVariantAnimation::setEndValue(const QVariant &value)
556{
557 setKeyValueAt(1, value);
558}
559
560
561/*!
562 Returns the key frame value for the given \a step. The given \a step
563 must be in the range 0 to 1. If there is no KeyValue for \a step,
564 it returns an invalid QVariant.
565
566 \sa keyValues(), setKeyValueAt()
567*/
568QVariant QVariantAnimation::keyValueAt(qreal step) const
569{
570 return d_func()->valueAt(step);
571}
572
573/*!
574 \typedef QVariantAnimation::KeyValue
575
576 This is a typedef for QPair<qreal, QVariant>.
577*/
578/*!
579 \typedef QVariantAnimation::KeyValues
580
581 This is a typedef for QVector<KeyValue>
582*/
583
584/*!
585 Creates a key frame at the given \a step with the given \a value.
586 The given \a step must be in the range 0 to 1.
587
588 \sa setKeyValues(), keyValueAt()
589*/
590void QVariantAnimation::setKeyValueAt(qreal step, const QVariant &value)
591{
592 d_func()->setValueAt(step, value);
593}
594
595/*!
596 Returns the key frames of this animation.
597
598 \sa keyValueAt(), setKeyValues()
599*/
600QVariantAnimation::KeyValues QVariantAnimation::keyValues() const
601{
602 return d_func()->keyValues;
603}
604
605/*!
606 Replaces the current set of key frames with the given \a keyValues.
607 the step of the key frames must be in the range 0 to 1.
608
609 \sa keyValues(), keyValueAt()
610*/
611void QVariantAnimation::setKeyValues(const KeyValues &keyValues)
612{
613 Q_D(QVariantAnimation);
614 d->keyValues = keyValues;
615 qSort(d->keyValues.begin(), d->keyValues.end(), animationValueLessThan);
616 d->recalculateCurrentInterval(/*force=*/true);
617}
618
619/*!
620 \property QVariantAnimation::currentValue
621 \brief the current value of the animation.
622
623 This property describes the current value; an interpolated value
624 between the \l{startValue}{start value} and the \l{endValue}{end
625 value}, using the current time for progress. The value itself is
626 obtained from interpolated(), which is called repeatedly as the
627 animation is running.
628
629 QVariantAnimation calls the virtual updateCurrentValue() function
630 when the current value changes. This is particularly useful for
631 subclasses that need to track updates. For example,
632 QPropertyAnimation uses this function to animate Qt \l{Qt's
633 Property System}{properties}.
634
635 \sa startValue, endValue
636*/
637QVariant QVariantAnimation::currentValue() const
638{
639 Q_D(const QVariantAnimation);
640 if (!d->currentValue.isValid())
641 const_cast<QVariantAnimationPrivate*>(d)->recalculateCurrentInterval();
642 return d->currentValue;
643}
644
645/*!
646 \reimp
647 */
648bool QVariantAnimation::event(QEvent *event)
649{
650 return QAbstractAnimation::event(event);
651}
652
653/*!
654 \reimp
655*/
656void QVariantAnimation::updateState(QAbstractAnimation::State newState,
657 QAbstractAnimation::State oldState)
658{
659 Q_UNUSED(oldState);
660 Q_UNUSED(newState);
661}
662
663/*!
664
665 This virtual function returns the linear interpolation between
666 variants \a from and \a to, at \a progress, usually a value
667 between 0 and 1. You can reimplement this function in a subclass
668 of QVariantAnimation to provide your own interpolation algorithm.
669
670 Note that in order for the interpolation to work with a
671 QEasingCurve that return a value smaller than 0 or larger than 1
672 (such as QEasingCurve::InBack) you should make sure that it can
673 extrapolate. If the semantic of the datatype does not allow
674 extrapolation this function should handle that gracefully.
675
676 You should call the QVariantAnimation implementation of this
677 function if you want your class to handle the types already
678 supported by Qt (see class QVariantAnimation description for a
679 list of supported types).
680
681 \sa QEasingCurve
682 */
683QVariant QVariantAnimation::interpolated(const QVariant &from, const QVariant &to, qreal progress) const
684{
685 return d_func()->interpolator(from.constData(), to.constData(), progress);
686}
687
688/*!
689 \reimp
690 */
691void QVariantAnimation::updateCurrentTime(int)
692{
693 d_func()->recalculateCurrentInterval();
694}
695
696QT_END_NAMESPACE
697
698#include "moc_qvariantanimation.cpp"
699
700#endif //QT_NO_ANIMATION
Note: See TracBrowser for help on using the repository browser.