MuseScore/libmscore/articulation.cpp

561 lines
20 KiB
C++
Raw Normal View History

2012-05-26 14:26:10 +02:00
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2002-2011 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "articulation.h"
#include "score.h"
#include "chordrest.h"
#include "system.h"
#include "measure.h"
#include "staff.h"
#include "stafftype.h"
#include "undo.h"
#include "page.h"
#include "barline.h"
2013-05-13 18:49:17 +02:00
namespace Ms {
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// Articulation::articulationList
//---------------------------------------------------------
ArticulationInfo Articulation::articulationList[ARTICULATIONS] = {
{ ufermataSym, dfermataSym,
"fermata", QT_TRANSLATE_NOOP("articulation", "fermata"),
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ ushortfermataSym, dshortfermataSym,
"shortfermata", QT_TRANSLATE_NOOP("articulation", "shortfermata"),
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ ulongfermataSym, dlongfermataSym,
"longfermata", QT_TRANSLATE_NOOP("articulation", "longfermata"),
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ uverylongfermataSym, dverylongfermataSym,
"verylongfermata", QT_TRANSLATE_NOOP("articulation", "verylongfermata"),
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ thumbSym, thumbSym,
"thumb", QT_TRANSLATE_NOOP("articulation", "thumb"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ sforzatoaccentSym, sforzatoaccentSym,
"sforzato", QT_TRANSLATE_NOOP("articulation", "sforzato"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ esprSym, esprSym ,
"espressivo", QT_TRANSLATE_NOOP("articulation", "espressivo"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ staccatoSym, staccatoSym,
"staccato", QT_TRANSLATE_NOOP("articulation", "staccato"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ ustaccatissimoSym, dstaccatissimoSym,
"staccatissimo", QT_TRANSLATE_NOOP("articulation", "staccatissimo"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ tenutoSym, tenutoSym,
"tenuto", QT_TRANSLATE_NOOP("articulation", "tenuto"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ dportatoSym, uportatoSym,
"portato", QT_TRANSLATE_NOOP("articulation", "portato"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ umarcatoSym, dmarcatoSym,
"marcato", QT_TRANSLATE_NOOP("articulation", "marcato"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ ouvertSym, ouvertSym,
"ouvert", QT_TRANSLATE_NOOP("articulation", "ouvert"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ plusstopSym, plusstopSym,
"plusstop", QT_TRANSLATE_NOOP("articulation", "plusstop"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ upbowSym, upbowSym,
"upbow", QT_TRANSLATE_NOOP("articulation", "upbow"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ downbowSym, downbowSym,
"downbow", QT_TRANSLATE_NOOP("articulation", "downbow"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ reverseturnSym, reverseturnSym,
"reverseturn", QT_TRANSLATE_NOOP("articulation", "reverseturn"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ turnSym, turnSym,
"turn", QT_TRANSLATE_NOOP("articulation", "turn"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ trillSym, trillSym,
"trill", QT_TRANSLATE_NOOP("articulation", "trill"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ prallSym, prallSym,
"prall", QT_TRANSLATE_NOOP("articulation", "prall"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ mordentSym, mordentSym,
"mordent", QT_TRANSLATE_NOOP("articulation", "mordent"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ prallprallSym, prallprallSym,
"prallprall", QT_TRANSLATE_NOOP("articulation", "prallprall"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ prallmordentSym, prallmordentSym,
"prallmordent", QT_TRANSLATE_NOOP("articulation", "prallmordent"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ upprallSym, upprallSym,
"upprall", QT_TRANSLATE_NOOP("articulation", "upprall"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ downprallSym, downprallSym,
"downprall", QT_TRANSLATE_NOOP("articulation", "downprall"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ upmordentSym, upmordentSym,
"upmordent", QT_TRANSLATE_NOOP("articulation", "upmordent"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ downmordentSym, downmordentSym,
"downmordent", QT_TRANSLATE_NOOP("articulation", "downmordent"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ pralldownSym, pralldownSym,
"pralldown", QT_TRANSLATE_NOOP("articulation", "pralldown"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ prallupSym, prallupSym,
"prallup", QT_TRANSLATE_NOOP("articulation", "prallup"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ lineprallSym, lineprallSym,
"lineprall", QT_TRANSLATE_NOOP("articulation", "lineprall"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ schleiferSym, schleiferSym,
"schleifer", QT_TRANSLATE_NOOP("articulation", "schleifer"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ snappizzicatoSym, snappizzicatoSym,
"snappizzicato", QT_TRANSLATE_NOOP("articulation", "snappizzicato"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_PITCHED_STAFF | ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ letterTSym, letterTSym,
"tapping", QT_TRANSLATE_NOOP("articulation", "tapping"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ letterSSym, letterSSym,
"slapping", QT_TRANSLATE_NOOP("articulation", "slapping"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
{ letterPSym, letterPSym,
"popping", QT_TRANSLATE_NOOP("articulation", "popping"),
2013-06-03 14:39:59 +02:00
1.0, ARTICULATION_SHOW_IN_TABLATURE
2012-05-26 14:26:10 +02:00
},
};
//---------------------------------------------------------
// Articulation
//---------------------------------------------------------
Articulation::Articulation(Score* s)
: Element(s)
{
2012-08-04 15:46:43 +02:00
_direction = MScore::AUTO;
2012-05-26 14:26:10 +02:00
_up = true;
setFlags(ELEMENT_MOVABLE | ELEMENT_SELECTABLE);
setArticulationType(Articulation_Fermata);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// setArticulationType
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
void Articulation::setArticulationType(ArticulationType idx)
2012-05-26 14:26:10 +02:00
{
_articulationType = idx;
_anchor = score()->style()->articulationAnchor(_articulationType);
anchorStyle = PropertyStyle::STYLED;
2013-06-03 14:39:59 +02:00
_timeStretch = articulationList[articulationType()].timeStretch;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
2013-01-11 18:10:18 +01:00
void Articulation::read(XmlReader& e)
2012-05-26 14:26:10 +02:00
{
setArticulationType(Articulation_Fermata); // default // backward compatibility (no type = ufermata in 1.2)
2013-01-11 18:10:18 +01:00
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
2012-05-26 14:26:10 +02:00
if (tag == "subtype")
2013-01-11 18:10:18 +01:00
setSubtype(e.readElementText());
else if (tag == "channel") {
2012-05-26 14:26:10 +02:00
_channelName = e.attribute("name");
2013-01-11 18:10:18 +01:00
e.readNext();
}
else if (tag == "anchor") {
2013-01-11 18:10:18 +01:00
_anchor = ArticulationAnchor(e.readInt());
anchorStyle = PropertyStyle::UNSTYLED;
}
2012-05-26 14:26:10 +02:00
else if (tag == "direction") {
2013-06-06 11:55:02 +02:00
setProperty(P_DIRECTION, Ms::getProperty(P_DIRECTION, e));
2012-05-26 14:26:10 +02:00
}
2013-06-03 14:39:59 +02:00
else if (tag == "timeStretch")
_timeStretch = e.readDouble();
2012-05-26 14:26:10 +02:00
else if (!Element::readProperties(e))
2013-01-11 18:10:18 +01:00
e.unknown();
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void Articulation::write(Xml& xml) const
{
xml.stag("Articulation");
if (!_channelName.isEmpty())
xml.tagE(QString("channel name=\"%1\"").arg(_channelName));
2013-06-06 11:55:02 +02:00
writeProperty(xml, P_DIRECTION);
2013-07-17 09:41:06 +02:00
xml.tag("subtype", subtypeName());
2013-06-03 14:39:59 +02:00
if (_timeStretch != 1.0)
xml.tag("timeStretch", _timeStretch);
2012-05-26 14:26:10 +02:00
Element::writeProperties(xml);
if (anchorStyle == PropertyStyle::UNSTYLED)
2012-05-26 14:26:10 +02:00
xml.tag("anchor", int(_anchor));
xml.etag();
}
//---------------------------------------------------------
// subtypeName
//---------------------------------------------------------
QString Articulation::subtypeName() const
{
return articulationList[articulationType()].name;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// setSubtype
//---------------------------------------------------------
void Articulation::setSubtype(const QString& s)
{
if (s[0].isDigit()) { // for backward compatibility
setArticulationType(ArticulationType(s.toInt()));
2012-05-26 14:26:10 +02:00
return;
}
int st;
for (st = 0; st < ARTICULATIONS; ++st) {
if (articulationList[st].name == s)
break;
}
if (st == ARTICULATIONS) {
struct {
const char* name;
bool up;
ArticulationType type;
} al[] = {
{ "umarcato", true, Articulation_Marcato },
{ "dmarcato", false, Articulation_Marcato },
{ "ufermata", true, Articulation_Fermata },
{ "dfermata", false, Articulation_Fermata },
{ "ushortfermata", true, Articulation_Shortfermata },
{ "dshortfermata", false, Articulation_Shortfermata },
{ "ulongfermata", true, Articulation_Longfermata },
{ "dlongfermata", false, Articulation_Longfermata },
{ "uverylongfermata", true, Articulation_Verylongfermata },
{ "dverylongfermata", false, Articulation_Verylongfermata },
// watch out, bug in 1.2 uportato and dportato are reversed
2012-08-04 15:46:43 +02:00
{ "dportato", true, Articulation_Portato },
{ "uportato", false, Articulation_Portato },
2012-05-26 14:26:10 +02:00
{ "ustaccatissimo", true, Articulation_Staccatissimo },
{ "dstaccatissimo", false, Articulation_Staccatissimo }
};
int i;
int n = sizeof(al) / sizeof(*al);
for (i = 0; i < n; ++i) {
if (s == al[i].name) {
_up = al[i].up;
2012-08-04 15:46:43 +02:00
_direction = (_up ? MScore::UP : MScore::DOWN);
2012-05-26 14:26:10 +02:00
st = int(al[i].type);
break;
}
}
if (i == n) {
st = 0;
qDebug("Articulation: unknown <%s>\n", qPrintable(s));
}
}
setArticulationType(ArticulationType(st));
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// idx2name
//---------------------------------------------------------
QString Articulation::idx2name(int idx)
{
return articulationList[idx].name;
}
//---------------------------------------------------------
// pagePos
//---------------------------------------------------------
QPointF Articulation::pagePos() const
{
if (parent() == 0)
return pos();
return parent()->pagePos() + pos();
}
//---------------------------------------------------------
// canvasPos
//---------------------------------------------------------
QPointF Articulation::canvasPos() const
{
if (parent() == 0)
return pos();
return parent()->canvasPos() + pos();
}
//---------------------------------------------------------
// Symbol::draw
//---------------------------------------------------------
void Articulation::draw(QPainter* painter) const
{
SymId sym = _up ? articulationList[articulationType()].upSym : articulationList[articulationType()].downSym;
int flags = articulationList[articulationType()].flags;
2012-05-26 14:26:10 +02:00
if (staff()) {
if (staff()->staffGroup() == TAB_STAFF) {
2012-05-26 14:26:10 +02:00
if (!(flags & ARTICULATION_SHOW_IN_TABLATURE))
return;
}
else {
if (!(flags & ARTICULATION_SHOW_IN_PITCHED_STAFF))
return;
}
}
painter->setPen(curColor());
symbols[score()->symIdx()][sym].draw(painter, magS());
}
//---------------------------------------------------------
// chordRest
//---------------------------------------------------------
ChordRest* Articulation::chordRest() const
{
if (parent() && parent()->isChordRest())
return static_cast<ChordRest*>(parent());
return 0;
}
//---------------------------------------------------------
// subtypeUserName
//---------------------------------------------------------
QString Articulation::subtypeUserName() const
{
return articulationList[articulationType()].description;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// layout
// height() and width() should return sensible
// values when calling this method
//---------------------------------------------------------
void Articulation::layout()
{
SymId sym = _up ? articulationList[articulationType()].upSym : articulationList[articulationType()].downSym;
2013-01-02 09:29:17 +01:00
setbbox(score()->sym(sym).bbox(magS()));
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// setDirection
//---------------------------------------------------------
2012-08-04 15:46:43 +02:00
void Articulation::setDirection(MScore::Direction d)
2012-05-26 14:26:10 +02:00
{
_direction = d;
2012-08-04 15:46:43 +02:00
if (d != MScore::AUTO)
_up = (d == MScore::UP);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
2012-11-19 10:08:15 +01:00
// reset
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2012-11-19 10:08:15 +01:00
void Articulation::reset()
2012-05-26 14:26:10 +02:00
{
2012-08-04 15:46:43 +02:00
if (_direction != MScore::AUTO)
score()->undoChangeProperty(this, P_DIRECTION, int(MScore::AUTO));
ArticulationAnchor a = score()->style()->articulationAnchor(articulationType());
2012-05-26 14:26:10 +02:00
if (_anchor != a)
score()->undoChangeProperty(this, P_ARTICULATION_ANCHOR, int(a));
2012-11-19 10:08:15 +01:00
Element::reset();
2012-05-26 14:26:10 +02:00
if (chordRest())
chordRest()->layoutArticulations();
score()->addRefresh(canvasBoundingRect());
}
//---------------------------------------------------------
// dragAnchor
//---------------------------------------------------------
QLineF Articulation::dragAnchor() const
{
return QLineF(canvasPos(), parent()->canvasPos());
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant Articulation::getProperty(P_ID propertyId) const
{
switch(propertyId) {
case P_DIRECTION: return int(direction());
case P_ARTICULATION_ANCHOR: return int(anchor());
2013-06-03 14:39:59 +02:00
case P_TIME_STRETCH: return timeStretch();
2012-05-26 14:26:10 +02:00
default:
return Element::getProperty(propertyId);
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool Articulation::setProperty(P_ID propertyId, const QVariant& v)
{
score()->addRefresh(canvasBoundingRect());
switch (propertyId) {
case P_DIRECTION:
setDirection(MScore::Direction(v.toInt()));
break;
case P_ARTICULATION_ANCHOR:
anchorStyle = PropertyStyle::UNSTYLED;
setAnchor(ArticulationAnchor(v.toInt()));
break;
2013-06-03 14:39:59 +02:00
case P_TIME_STRETCH:
setTimeStretch(v.toDouble());
score()->fixTicks();
break;
2012-05-26 14:26:10 +02:00
default:
return Element::setProperty(propertyId, v);
}
score()->addRefresh(canvasBoundingRect());
// layout:
if (chordRest())
chordRest()->layoutArticulations();
else if (parent() && parent()->type() == BAR_LINE)
static_cast<BarLine*>(parent())->layout();
score()->addRefresh(canvasBoundingRect());
score()->setLayoutAll(false); //DEBUG
return true;
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant Articulation::propertyDefault(P_ID propertyId) const
{
switch(propertyId) {
case P_DIRECTION:
return int(MScore::AUTO);
case P_ARTICULATION_ANCHOR:
return int(score()->style()->articulationAnchor(_articulationType));
2013-06-03 14:39:59 +02:00
case P_TIME_STRETCH:
return articulationList[articulationType()].timeStretch;
default:
break;
}
return Element::propertyDefault(propertyId);
}
//---------------------------------------------------------
// propertyStyle
//---------------------------------------------------------
PropertyStyle Articulation::propertyStyle(P_ID id) const
{
switch (id) {
case P_DIRECTION:
case P_TIME_STRETCH:
return PropertyStyle::NOSTYLE;
case P_ARTICULATION_ANCHOR:
return anchorStyle;
default:
break;
}
return Element::propertyStyle(id);
}
//---------------------------------------------------------
// resetProperty
//---------------------------------------------------------
void Articulation::resetProperty(P_ID id)
{
switch (id) {
case P_DIRECTION:
case P_TIME_STRETCH:
return;
case P_ARTICULATION_ANCHOR:
setProperty(id, propertyDefault(id));
anchorStyle = PropertyStyle::STYLED;
return;
default:
break;
}
Element::resetProperty(id);
}
//---------------------------------------------------------
// styleChanged
// reset all styled values to actual style
//---------------------------------------------------------
void Articulation::styleChanged()
{
if (anchorStyle == PropertyStyle::STYLED)
_anchor = score()->style()->articulationAnchor(_articulationType);
}
2013-05-13 18:49:17 +02:00
}