Skip to content

Commit

Permalink
Fix decimal separator handling (#4547)
Browse files Browse the repository at this point in the history
Makes LMMS can handle both periods and commas properly when loading real numbers.
  • Loading branch information
PhysSong authored Sep 12, 2018
1 parent c3db486 commit f37ca49
Show file tree
Hide file tree
Showing 13 changed files with 101 additions and 81 deletions.
19 changes: 0 additions & 19 deletions include/DataFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,25 +84,6 @@ class EXPORT DataFile : public QDomDocument
return m_type;
}

// small helper class for adjusting application's locale settings
// when loading or saving floating point values rendered to strings
class LocaleHelper
{
public:
enum Modes
{
ModeLoad,
ModeSave,
ModeCount
};
typedef Modes Mode;

LocaleHelper( Mode mode );
~LocaleHelper();

};


private:
static Type type( const QString& typeName );
static QString typeName( Type type );
Expand Down
67 changes: 67 additions & 0 deletions include/LocaleHelper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* LocaleHelper.h - compatibility functions for handling decimal separators
* Providing helper functions which handle both periods and commas
* for decimal separators to load old projects correctly
*
* Copyright (c) 2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2018 Hyunjin Song <tteu.ingog/at/gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/

#ifndef LOCALEHELPER_H
#define LOCALEHELPER_H

#include <QLocale>

#include <limits>
#include <cmath>

namespace LocaleHelper
{
inline double toDouble(QString str, bool* ok = nullptr)
{
bool isOkay;
double value;
QLocale c(QLocale::C);
c.setNumberOptions(QLocale::RejectGroupSeparator);
value = c.toDouble(str, &isOkay);
if (!isOkay)
{
QLocale german(QLocale::German);
german.setNumberOptions(QLocale::RejectGroupSeparator);
value = german.toDouble(str, &isOkay);
}
if (ok != nullptr) {*ok = isOkay;}
return value;
}

inline float toFloat(QString str, bool* ok = nullptr)
{
double d = toDouble(str, ok);
if (!std::isinf(d) && std::fabs(d) > std::numeric_limits<float>::max())
{
if (ok != nullptr) {*ok = false;}
return 0.0f;
}
return static_cast<float>(d);
}
}

#endif // LOCALEHELPER_H
7 changes: 4 additions & 3 deletions plugins/MidiExport/MidiExport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "TrackContainer.h"
#include "BBTrack.h"
#include "InstrumentTrack.h"
#include "LocaleHelper.h"


extern "C"
Expand Down Expand Up @@ -133,7 +134,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks,
{
base_pitch += masterPitch;
}
base_volume = it.attribute("volume", "100").toDouble()/100.0;
base_volume = LocaleHelper::toDouble(it.attribute("volume", "100"))/100.0;
}

if (n.nodeName() == "pattern")
Expand Down Expand Up @@ -204,7 +205,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks,
{
base_pitch += masterPitch;
}
base_volume = it.attribute("volume", "100").toDouble() / 100.0;
base_volume = LocaleHelper::toDouble(it.attribute("volume", "100")) / 100.0;
}

if (n.nodeName() == "pattern")
Expand Down Expand Up @@ -273,7 +274,7 @@ void MidiExport::writePattern(MidiNoteVector &pat, QDomNode n,
// TODO interpret pan="0" fxch="0" pitchrange="1"
MidiNote mnote;
mnote.pitch = qMax(0, qMin(127, note.attribute("key", "0").toInt() + base_pitch));
mnote.volume = qMin(qRound(base_volume * note.attribute("vol", "100").toDouble()), 127);
mnote.volume = qMin(qRound(base_volume * LocaleHelper::toDouble(note.attribute("vol", "100"))), 127);
mnote.time = base_time + note.attribute("pos", "0").toInt();
mnote.duration = note.attribute("len", "0").toInt();
pat.push_back(mnote);
Expand Down
9 changes: 5 additions & 4 deletions plugins/VstEffect/VstEffectControls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "VstEffectControls.h"
#include "VstEffect.h"

#include "LocaleHelper.h"
#include "MainWindow.h"
#include "GuiApplication.h"
#include <QMdiArea>
Expand Down Expand Up @@ -85,8 +86,8 @@ void VstEffectControls::loadSettings( const QDomElement & _this )
if( !( knobFModel[ i ]->isAutomated() ||
knobFModel[ i ]->controllerConnection() ) )
{
knobFModel[ i ]->setValue( (s_dumpValues.at( 2 ) ).toFloat() );
knobFModel[ i ]->setInitValue( (s_dumpValues.at( 2 ) ).toFloat() );
knobFModel[ i ]->setValue(LocaleHelper::toFloat(s_dumpValues.at(2)));
knobFModel[ i ]->setInitValue(LocaleHelper::toFloat(s_dumpValues.at(2)));
}

connect( knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) );
Expand Down Expand Up @@ -381,7 +382,7 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls *
if( !hasKnobModel )
{
sprintf( paramStr, "%d", i);
m_vi->knobFModel[ i ] = new FloatModel( ( s_dumpValues.at( 2 ) ).toFloat(),
m_vi->knobFModel[ i ] = new FloatModel( LocaleHelper::toFloat(s_dumpValues.at(2)),
0.0f, 1.0f, 0.01f, _eff, tr( paramStr ) );
}
connect( m_vi->knobFModel[ i ], SIGNAL( dataChanged() ), this,
Expand Down Expand Up @@ -445,7 +446,7 @@ void manageVSTEffectView::syncPlugin( void )
{
sprintf( paramStr, "param%d", i );
s_dumpValues = dump[ paramStr ].split( ":" );
f_value = ( s_dumpValues.at( 2 ) ).toFloat();
f_value = LocaleHelper::toFloat(s_dumpValues.at(2));
m_vi2->knobFModel[ i ]->setAutomatedValue( f_value );
m_vi2->knobFModel[ i ]->setInitValue( f_value );
}
Expand Down
9 changes: 5 additions & 4 deletions plugins/vestige/vestige.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "InstrumentPlayHandle.h"
#include "InstrumentTrack.h"
#include "VstPlugin.h"
#include "LocaleHelper.h"
#include "MainWindow.h"
#include "Mixer.h"
#include "GuiApplication.h"
Expand Down Expand Up @@ -203,8 +204,8 @@ void vestigeInstrument::loadSettings( const QDomElement & _this )

if( !( knobFModel[ i ]->isAutomated() || knobFModel[ i ]->controllerConnection() ) )
{
knobFModel[ i ]->setValue( ( s_dumpValues.at( 2 )).toFloat() );
knobFModel[ i ]->setInitValue( ( s_dumpValues.at( 2 )).toFloat() );
knobFModel[ i ]->setValue(LocaleHelper::toFloat(s_dumpValues.at(2)));
knobFModel[ i ]->setInitValue(LocaleHelper::toFloat(s_dumpValues.at(2)));
}

connect( knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) );
Expand Down Expand Up @@ -991,7 +992,7 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume
if( !hasKnobModel )
{
sprintf( paramStr, "%d", i);
m_vi->knobFModel[ i ] = new FloatModel( (s_dumpValues.at( 2 )).toFloat(),
m_vi->knobFModel[ i ] = new FloatModel( LocaleHelper::toFloat(s_dumpValues.at(2)),
0.0f, 1.0f, 0.01f, castModel<vestigeInstrument>(), tr( paramStr ) );
}
connect( m_vi->knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) );
Expand Down Expand Up @@ -1052,7 +1053,7 @@ void manageVestigeInstrumentView::syncPlugin( void )
{
sprintf( paramStr, "param%d", i );
s_dumpValues = dump[ paramStr ].split( ":" );
f_value = ( s_dumpValues.at( 2 ) ).toFloat();
f_value = LocaleHelper::toFloat(s_dumpValues.at(2));
m_vi->knobFModel[ i ]->setAutomatedValue( f_value );
m_vi->knobFModel[ i ]->setInitValue( f_value );
}
Expand Down
3 changes: 2 additions & 1 deletion plugins/vst_base/VstPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

#include "ConfigManager.h"
#include "GuiApplication.h"
#include "LocaleHelper.h"
#include "MainWindow.h"
#include "Mixer.h"
#include "Song.h"
Expand Down Expand Up @@ -299,7 +300,7 @@ void VstPlugin::setParameterDump( const QMap<QString, QString> & _pdump )
{
( *it ).section( ':', 0, 0 ).toInt(),
"",
( *it ).section( ':', 2, -1 ).toFloat()
LocaleHelper::toFloat((*it).section(':', 2, -1))
} ;
m.addInt( item.index );
m.addString( item.shortLabel );
Expand Down
9 changes: 5 additions & 4 deletions plugins/zynaddsubfx/zynaddsubfx/src/Misc/QtXmlWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
// Include LMMS headers
#include "lmmsconfig.h"
#include "IoHelper.h"
#include "LocaleHelper.h"


struct XmlData
Expand Down Expand Up @@ -228,14 +229,14 @@ int QtXmlWrapper::dosavefile(const char *filename,

void QtXmlWrapper::addpar(const std::string &name, int val)
{
d->addparams("par", 2, "name", name.c_str(), "value", stringFrom<int>(
val).c_str());
d->addparams("par", 2, "name", name.c_str(), "value",
QString::number(val).toLocal8Bit().constData());
}

void QtXmlWrapper::addparreal(const std::string &name, float val)
{
d->addparams("par_real", 2, "name", name.c_str(), "value",
stringFrom<float>(val).c_str());
QString::number(val, 'f').toLocal8Bit().constData());
}

void QtXmlWrapper::addparbool(const std::string &name, int val)
Expand Down Expand Up @@ -510,7 +511,7 @@ float QtXmlWrapper::getparreal(const char *name, float defaultpar) const
return defaultpar;
}

return QLocale().toFloat( tmp.attribute( "value" ) );
return LocaleHelper::toFloat( tmp.attribute( "value" ) );
}

float QtXmlWrapper::getparreal(const char *name,
Expand Down
5 changes: 3 additions & 2 deletions src/core/AutomatableModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include "AutomationPattern.h"
#include "ControllerConnection.h"
#include "LocaleHelper.h"
#include "Mixer.h"
#include "ProjectJournal.h"

Expand Down Expand Up @@ -183,7 +184,7 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString&
if( node.isElement() )
{
changeID( node.toElement().attribute( "id" ).toInt() );
setValue( node.toElement().attribute( "value" ).toFloat() );
setValue( LocaleHelper::toFloat( node.toElement().attribute( "value" ) ) );
if( node.toElement().hasAttribute( "scale_type" ) )
{
if( node.toElement().attribute( "scale_type" ) == "linear" )
Expand All @@ -204,7 +205,7 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString&
if( element.hasAttribute( name ) )
// attribute => read the element's value from the attribute list
{
setInitValue( element.attribute( name ).toFloat() );
setInitValue( LocaleHelper::toFloat( element.attribute( name ) ) );
}
else
{
Expand Down
7 changes: 4 additions & 3 deletions src/core/AutomationPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include "AutomationPatternView.h"
#include "AutomationTrack.h"
#include "LocaleHelper.h"
#include "Note.h"
#include "ProjectJournal.h"
#include "BBTrackContainer.h"
Expand Down Expand Up @@ -154,11 +155,11 @@ void AutomationPattern::setProgressionType(
void AutomationPattern::setTension( QString _new_tension )
{
bool ok;
float nt = _new_tension.toFloat( & ok );
float nt = LocaleHelper::toFloat(_new_tension, & ok);

if( ok && nt > -0.01 && nt < 1.01 )
{
m_tension = _new_tension.toFloat();
m_tension = nt;
}
}

Expand Down Expand Up @@ -595,7 +596,7 @@ void AutomationPattern::loadSettings( const QDomElement & _this )
if( element.tagName() == "time" )
{
m_timeMap[element.attribute( "pos" ).toInt()]
= element.attribute( "value" ).toFloat();
= LocaleHelper::toFloat(element.attribute("value"));
}
else if( element.tagName() == "object" )
{
Expand Down
38 changes: 4 additions & 34 deletions src/core/DataFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "Effect.h"
#include "embed.h"
#include "GuiApplication.h"
#include "LocaleHelper.h"
#include "PluginFactory.h"
#include "ProjectVersion.h"
#include "SongEditor.h"
Expand Down Expand Up @@ -65,37 +66,6 @@ DataFile::typeDescStruct



DataFile::LocaleHelper::LocaleHelper( Mode mode )
{
switch( mode )
{
case ModeLoad:
// set a locale for which QString::fromFloat() returns valid values if
// floating point separator is a comma - otherwise we would fail to load
// older projects made by people from various countries due to their
// locale settings
QLocale::setDefault( QLocale::German );
break;

case ModeSave:
// set default locale to C so that floating point decimals are rendered to
// strings with periods as decimal point instead of commas in some countries
QLocale::setDefault( QLocale::C );

default: break;
}
}



DataFile::LocaleHelper::~LocaleHelper()
{
// revert to original locale
QLocale::setDefault( QLocale::system() );
}




DataFile::DataFile( Type type ) :
QDomDocument( "lmms-project" ),
Expand Down Expand Up @@ -416,8 +386,8 @@ void DataFile::upgrade_0_2_1_20070501()
QDomElement el = list.item( i ).toElement();
if( el.attribute( "vol" ) != "" )
{
el.setAttribute( "vol", el.attribute(
"vol" ).toFloat() * 100.0f );
el.setAttribute( "vol", LocaleHelper::toFloat(
el.attribute( "vol" ) ) * 100.0f );
}
else
{
Expand Down Expand Up @@ -543,7 +513,7 @@ void DataFile::upgrade_0_2_1_20070508()
QDomElement el = list.item( i ).toElement();
if( el.hasAttribute( "vol" ) )
{
float value = el.attribute( "vol" ).toFloat();
float value = LocaleHelper::toFloat( el.attribute( "vol" ) );
value = roundf( value * 0.585786438f );
el.setAttribute( "vol", value );
}
Expand Down
4 changes: 0 additions & 4 deletions src/core/Song.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1034,8 +1034,6 @@ void Song::loadProject( const QString & fileName )

clearErrors();

DataFile::LocaleHelper localeHelper( DataFile::LocaleHelper::ModeLoad );

Engine::mixer()->requestChangeInModel();

// get the header information from the DOM
Expand Down Expand Up @@ -1191,8 +1189,6 @@ void Song::loadProject( const QString & fileName )
// only save current song as _filename and do nothing else
bool Song::saveProjectFile( const QString & filename )
{
DataFile::LocaleHelper localeHelper( DataFile::LocaleHelper::ModeSave );

DataFile dataFile( DataFile::SongProject );

m_tempoModel.saveSettings( dataFile, dataFile.head(), "bpm" );
Expand Down
Loading

0 comments on commit f37ca49

Please sign in to comment.