radioapp/radiouiengine/src/radiostation.cpp
branchGCC_SURGE
changeset 37 451b2e1545b2
parent 16 f54ebcfc1b80
parent 33 11b6825f0862
equal deleted inserted replaced
26:6bcf277166c1 37:451b2e1545b2
    23 #include "radiostation_p.h"
    23 #include "radiostation_p.h"
    24 #include "radiologger.h"
    24 #include "radiologger.h"
    25 #include "radio_global.h"
    25 #include "radio_global.h"
    26 
    26 
    27 // Constants
    27 // Constants
    28 const QString KTagArtist = "artist";
    28 const QLatin1String HTML_TAG_START( "<font color='cyan'><u>" );
    29 const QString KTagTitle = "title";
    29 const QLatin1String HTML_TAG_END( "</u></font>" );
    30 const QString KLinkArtist = "<a href=\"" + KTagArtist + "\">";
    30 
    31 const QString KLinkTitle = "<a href=\"" + KTagTitle + "\">";
    31 const uint LAST_CALLSIGN_CHAR_CODE = 25;
    32 const QString KLinkClose = "</a>";
    32 const uint THREE_LETTER_CALLSIGN_COUNT = 72;
    33 
    33 const uint KXXX_CALLSIGN_PI_FIRST = 0x1000;
    34 const char* callSign[KThreeLetterCallSignCount] =
    34 const uint WXXX_CALLSIGN_PI_FIRST = 0x54A8;
       
    35 const uint WXXX_CALLSIGN_PI_LAST = 0x994F;
       
    36 const uint XXX_CALLSIGN_PI_FIRST = 0x9950;
       
    37 const uint XXX_CALLSIGN_PI_LAST = 0x99B9;
       
    38 
       
    39 const char* CALLSIGN_TABLE[THREE_LETTER_CALLSIGN_COUNT] =
    35    {"KBW", "KCY", "KDB", "KDF", "KEX", "KFH","KFI","KGA","KGB",
    40    {"KBW", "KCY", "KDB", "KDF", "KEX", "KFH","KFI","KGA","KGB",
    36     "KGO", "KGU", "KGW", "KGY", "KHQ", "KID", "KIT", "KJR", "KLO",
    41     "KGO", "KGU", "KGW", "KGY", "KHQ", "KID", "KIT", "KJR", "KLO",
    37     "KLZ", "KMA", "KMJ", "KNX", "KOA", "KOB", "KOY", "KPQ", "KQV",
    42     "KLZ", "KMA", "KMJ", "KNX", "KOA", "KOB", "KOY", "KPQ", "KQV",
    38     "KSD", "KSL", "KUJ", "KUT", "KVI", "KWG", "KXL", "KXO", "KYW",
    43     "KSD", "KSL", "KUJ", "KUT", "KVI", "KWG", "KXL", "KXO", "KYW",
    39     "WBT", "WBZ", "WDZ", "WEW", "WGH", "WGL", "WGN", "WGR", "WGY",
    44     "WBT", "WBZ", "WDZ", "WEW", "WGH", "WGL", "WGN", "WGR", "WGY",
    40     "WHA", "WHB", "WHK", "WHO", "WHP", "WIL", "WIP", "WIS", "WJR",
    45     "WHA", "WHB", "WHK", "WHO", "WHP", "WIL", "WIP", "WIS", "WJR",
    41     "WJW", "WJZ", "WKY", "WLS", "WLW", "WMC", "WMT", "WOC", "WOI",
    46     "WJW", "WJZ", "WKY", "WLS", "WLW", "WMC", "WMT", "WOC", "WOI",
    42     "WOL", "WOR", "WOW", "WRC", "WRR", "WSB", "WSM", "WWJ", "WWL"};
    47     "WOL", "WOR", "WOW", "WRC", "WRR", "WSB", "WSM", "WWJ", "WWL"};
    43 
    48 
    44 const uint piCode[KThreeLetterCallSignCount] =
    49 const uint PI_CODE_TABLE[THREE_LETTER_CALLSIGN_COUNT] =
    45    {0x99A5, 0x99A6, 0x9990, 0x99A7, 0x9950, 0x9951, 0x9952, 0x9953,
    50    {0x99A5, 0x99A6, 0x9990, 0x99A7, 0x9950, 0x9951, 0x9952, 0x9953,
    46     0x9991, 0x9954, 0x9955, 0x9956, 0x9957, 0x99AA, 0x9958, 0x9959,
    51     0x9991, 0x9954, 0x9955, 0x9956, 0x9957, 0x99AA, 0x9958, 0x9959,
    47     0x995A, 0x995B, 0x995C, 0x995D, 0x995E, 0x995F, 0x9960, 0x99AB,
    52     0x995A, 0x995B, 0x995C, 0x995D, 0x995E, 0x995F, 0x9960, 0x99AB,
    48     0x9992, 0x9993, 0x9964, 0x9994, 0x9965, 0x9966, 0x9995, 0x9967,
    53     0x9992, 0x9993, 0x9964, 0x9994, 0x9965, 0x9966, 0x9995, 0x9967,
    49     0x9968, 0x9996, 0x9997, 0x996B, 0x9999, 0x996D, 0x996E, 0x996F,
    54     0x9968, 0x9996, 0x9997, 0x996B, 0x9999, 0x996D, 0x996E, 0x996F,
    50     0x999A, 0x9971, 0x9972, 0x9973, 0x999B, 0x9975, 0x9976, 0x9977,
    55     0x999A, 0x9971, 0x9972, 0x9973, 0x999B, 0x9975, 0x9976, 0x9977,
    51     0x9978, 0x999C, 0x999D, 0x997A, 0x99B3, 0x997B, 0x99B4, 0x99B5,
    56     0x9978, 0x999C, 0x999D, 0x997A, 0x99B3, 0x997B, 0x99B4, 0x99B5,
    52     0x997C, 0x997D, 0x997E, 0x999E, 0x999F, 0x9981, 0x99A0, 0x9983,
    57     0x997C, 0x997D, 0x997E, 0x999E, 0x999F, 0x9981, 0x99A0, 0x9983,
    53     0x9984, 0x99A1, 0x99B9, 0x99A2, 0x99A3, 0x99A4, 0x9988, 0x9989};
    58     0x9984, 0x99A1, 0x99B9, 0x99A2, 0x99A3, 0x99A4, 0x9988, 0x9989};
    54 
    59 
    55 const uint KDisableLocalAreaCoverageMask = 0x0800;
    60 const uint DISABLE_LOCAL_AREA_COVERAGE_MASK = 0x0800;
    56 
    61 
    57 const int KPsNameChangeThresholdSeconds = 10;
    62 const int PS_NAME_CHANGE_THRESHOLD_SECONDS = 10;
       
    63 
       
    64 // Macros to help protect shared null and manual station instances to help debugging
       
    65 // Enabled in emulator or win32 builds by default
       
    66 #if defined __WINS__ || defined BUILD_WIN32
       
    67 #   define PROTECT_SHAREDNULL_AND_MANUALSTATION
       
    68 #endif
       
    69 
       
    70 #ifdef PROTECT_SHAREDNULL_AND_MANUALSTATION
       
    71 #   define ASSERT_SHARED_NULL_IS_INTACT \
       
    72         Q_ASSERT_X( mData->mPresetIndex != SharedNull, "RadioStation", "Shared null modified illegally!" );
       
    73 #   define ASSERT_MANUAL_STATION \
       
    74         Q_ASSERT_X( mData->mPresetIndex != ManualStation, "RadioStation", "Illegally modifying manual station" );
       
    75 #else
       
    76 #   define ASSERT_SHARED_NULL_IS_INTACT
       
    77 #   define ASSERT_MANUAL_STATION
       
    78 #endif // PROTECT_SHAREDNULL_AND_MANUALSTATION
    58 
    79 
    59 /**
    80 /**
    60  * Static shared data instance that is used by all default-constructed RadioStation instances
    81  * Static shared data instance that is used by all default-constructed RadioStation instances
    61  */
    82  */
    62 Q_GLOBAL_STATIC_WITH_ARGS( RadioStationPrivate, shared_null, ( RadioStation::SharedNull ) )
    83 static RadioStationPrivate shared_null( RadioStation::SharedNull, 0 );
    63 
    84 
    64 /*!
    85 /*!
    65  *
    86  *
    66  */
    87  */
    67 QString RadioStation::parseFrequency( uint frequency )
    88 QString RadioStation::parseFrequency( uint frequency )
    68 {
    89 {
    69     QString freqString;
    90     QString freqString;
    70     freqString.sprintf( "%.1f", qreal( frequency ) / KFrequencyMultiplier );
    91     freqString.sprintf( "%.1f", qreal( frequency ) / FREQUENCY_MULTIPLIER );
    71     return freqString;
    92     return freqString;
    72 }
    93 }
    73 
    94 
    74 /*!
    95 /*!
    75  *
    96  *
    76  */
    97  */
    77 RadioStation::RadioStation() :
    98 RadioStation::RadioStation() :
    78     QObject( 0 )
    99     mData( &shared_null )
    79 {
   100 {
    80     mData = shared_null();
       
    81     mData->ref.ref();
       
    82 }
   101 }
    83 
   102 
    84 /*!
   103 /*!
    85  *
   104  *
    86  */
   105  */
    87 RadioStation::RadioStation( const RadioStation& other ) :
   106 RadioStation::RadioStation( const RadioStation& other ) :
    88     QObject( 0 )
   107     mData( other.mData )
       
   108 {
       
   109 }
       
   110 
       
   111 /*!
       
   112  *
       
   113  */
       
   114 RadioStation::RadioStation( int presetIndex, uint frequency ) :
       
   115     mData( new RadioStationPrivate( presetIndex, frequency ) )
       
   116 {
       
   117 }
       
   118 
       
   119 /*!
       
   120  *
       
   121  */
       
   122 RadioStation::~RadioStation()
       
   123 {
       
   124 }
       
   125 
       
   126 /*!
       
   127  *
       
   128  */
       
   129 RadioStation& RadioStation::operator=( const RadioStation& other )
    89 {
   130 {
    90     mData = other.mData;
   131     mData = other.mData;
    91     mData->ref.ref();
       
    92 }
       
    93 
       
    94 /*!
       
    95  *
       
    96  */
       
    97 RadioStation::RadioStation( int presetIndex, uint frequency ) :
       
    98     QObject( 0 )
       
    99 {
       
   100     mData = new RadioStationPrivate( presetIndex, frequency );
       
   101 }
       
   102 
       
   103 /*!
       
   104  *
       
   105  */
       
   106 RadioStation::~RadioStation()
       
   107 {
       
   108     decrementReferenceCount();
       
   109 }
       
   110 
       
   111 /*!
       
   112  *
       
   113  */
       
   114 RadioStation& RadioStation::operator=( const RadioStation& other )
       
   115 {
       
   116     qAtomicAssign( mData, other.mData );
       
   117     return *this;
   132     return *this;
   118 }
   133 }
   119 
   134 
   120 /*!
   135 /*!
   121  *
   136  *
   122  */
   137  */
   123 void RadioStation::reset()
   138 void RadioStation::reset()
   124 {
   139 {
   125     decrementReferenceCount();
   140     mData = &shared_null;
   126     mData = shared_null();
   141     ASSERT_SHARED_NULL_IS_INTACT
   127     mData->ref.ref();
       
   128 }
   142 }
   129 
   143 
   130 /*!
   144 /*!
   131  *
   145  *
   132  */
   146  */
   133 void RadioStation::setChangeFlags( RadioStation::Change flags )
   147 void RadioStation::setChangeFlags( RadioStation::Change flags )
   134 {
   148 {
   135     if ( mData->mChangeFlags != flags ) {
   149     if ( mData->mChangeFlags != flags ) {
   136         detach();
       
   137         mData->mChangeFlags = flags;
   150         mData->mChangeFlags = flags;
   138     }
   151     }
   139 }
   152 }
   140 
   153 
   141 /*!
   154 /*!
   142  *
   155  *
   143  */
   156  */
   144 void RadioStation::setPresetIndex( int presetIndex )
   157 void RadioStation::setPresetIndex( int presetIndex )
   145 {
   158 {
   146     if ( mData->mPresetIndex != presetIndex ) {
   159     if ( mData->mPresetIndex != presetIndex ) {
   147         detach();
   160         ASSERT_MANUAL_STATION
   148         mData->mPresetIndex = presetIndex;
   161         mData->mPresetIndex = presetIndex;
   149         mData->mChangeFlags |= RadioStation::PersistentDataChanged;
   162         mData->mChangeFlags |= RadioStation::PersistentDataChanged;
   150     }
   163     }
   151 }
   164 }
   152 
   165 
   154  *
   167  *
   155  */
   168  */
   156 void RadioStation::setFrequency( uint frequency )
   169 void RadioStation::setFrequency( uint frequency )
   157 {
   170 {
   158     if ( mData->mFrequency != frequency ) {
   171     if ( mData->mFrequency != frequency ) {
   159         detach();
       
   160         mData->mFrequency = frequency;
   172         mData->mFrequency = frequency;
   161         mData->mChangeFlags |= RadioStation::PersistentDataChanged;
   173         mData->mChangeFlags |= RadioStation::PersistentDataChanged;
       
   174         ASSERT_SHARED_NULL_IS_INTACT
   162     }
   175     }
   163 }
   176 }
   164 
   177 
   165 /*!
   178 /*!
   166  * Sets the preset name
   179  * Sets the preset name
   168 void RadioStation::setName( const QString& name )
   181 void RadioStation::setName( const QString& name )
   169 {
   182 {
   170     // Name emptiness is checked because this name setter is used by incoming RDS PS name
   183     // Name emptiness is checked because this name setter is used by incoming RDS PS name
   171     // and empty names should be ignored
   184     // and empty names should be ignored
   172     if ( !name.isEmpty() && !mData->mRenamedByUser && mData->mName.compare( name ) != 0 ) {
   185     if ( !name.isEmpty() && !mData->mRenamedByUser && mData->mName.compare( name ) != 0 ) {
   173         detach();
       
   174         mData->mName = name.trimmed();
   186         mData->mName = name.trimmed();
   175         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::NameChanged;
   187         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::NameChanged;
   176 
   188 
   177         // Save the time when PS name changed and if the last change was too close to the current time
   189         // Save the time when PS name changed and if the last change was too close to the current time
   178         // change the PS type to dynamic if it has already been incorrectly determined to be static.
   190         // change the PS type to dynamic if it has already been incorrectly determined to be static.
   179         QTime previousChange = mData->mLastPsNameChangeTime;
   191         QTime previousChange = mData->mLastPsNameChangeTime;
   180         mData->mLastPsNameChangeTime = QTime::currentTime();
   192         mData->mLastPsNameChangeTime = QTime::currentTime();
   181         if ( previousChange.isValid() && mData->mPsType == RadioStation::Static &&
   193         if ( previousChange.isValid() && mData->mPsType == RadioStation::Static &&
   182              previousChange.secsTo( mData->mLastPsNameChangeTime ) < KPsNameChangeThresholdSeconds ) {
   194              previousChange.secsTo( mData->mLastPsNameChangeTime ) < PS_NAME_CHANGE_THRESHOLD_SECONDS ) {
   183             LOG( "Station changed PS name too often. PS type changed to Dynamic" );
   195             LOG( "Station changed PS name too often. PS type changed to Dynamic" );
   184             mData->mPsType = RadioStation::Dynamic;
   196             mData->mPsType = RadioStation::Dynamic;
   185             mData->mDynamicPsText = mData->mName;
   197             mData->mDynamicPsText = mData->mName;
   186             mData->mName = "";
   198             mData->mName = "";
   187             mData->mChangeFlags |= RadioStation::PsTypeChanged | RadioStation::DynamicPsChanged;
   199             mData->mChangeFlags |= RadioStation::PsTypeChanged | RadioStation::DynamicPsChanged;
   188             mData->mCallSignCheckDone = false;
   200             mData->mCallSignCheckDone = false;
   189         }
   201         }
   190 
   202 
   191         //TODO: This is a temporary thing to see some URL. Remove this
   203         //TODO: This is a temporary thing to see some URL. Remove this
   192         if ( !mData->mName.isEmpty() ) {
   204         if ( !mData->mName.isEmpty() ) {
   193             mData->mUrl = "<a href=\"buu\">www." + mData->mName.toLower() + ".fi</a>";
   205             QString url = mData->mName.toLower().remove( " " );
       
   206             mData->mUrl = "www." + url + ".fi";
   194         } else {
   207         } else {
   195             mData->mUrl = "";
   208             mData->mUrl = "";
   196         }
   209         }
   197         mData->mChangeFlags |= RadioStation::UrlChanged;
   210         mData->mChangeFlags |= RadioStation::UrlChanged;
       
   211         ASSERT_SHARED_NULL_IS_INTACT
   198     }
   212     }
   199 }
   213 }
   200 
   214 
   201 /*!
   215 /*!
   202  *
   216  *
   203  */
   217  */
   204 void RadioStation::setGenre( const int genre )
   218 void RadioStation::setGenre( const int genre )
   205 {
   219 {
   206     if ( mData->mGenre != genre ) {
   220     if ( mData->mGenre != genre ) {
   207         detach();
       
   208         mData->mGenre = genre;
   221         mData->mGenre = genre;
   209         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::GenreChanged;
   222         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::GenreChanged;
       
   223         ASSERT_SHARED_NULL_IS_INTACT
   210     }
   224     }
   211 }
   225 }
   212 
   226 
   213 /*!
   227 /*!
   214  *
   228  *
   215  */
   229  */
   216 void RadioStation::setUrl( const QString& url )
   230 void RadioStation::setUrl( const QString& url )
   217 {
   231 {
   218     if ( mData->mUrl.compare( url ) != 0 ) {
   232     if ( mData->mUrl.compare( url ) != 0 ) {
   219         detach();
       
   220         mData->mUrl = url;
   233         mData->mUrl = url;
   221         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::UrlChanged;
   234         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::UrlChanged;
       
   235         ASSERT_SHARED_NULL_IS_INTACT
   222     }
   236     }
   223 }
   237 }
   224 
   238 
   225 /*!
   239 /*!
   226  * Sets the PI code
   240  * Sets the PI code
   228  */
   242  */
   229 bool RadioStation::setPiCode( int piCode, RadioRegion::Region region )
   243 bool RadioStation::setPiCode( int piCode, RadioRegion::Region region )
   230 {
   244 {
   231     LOG_FORMAT( "RadioStation::setPiCode new PI: %d", piCode );
   245     LOG_FORMAT( "RadioStation::setPiCode new PI: %d", piCode );
   232     // toggling local area coverage bit code must not be interpreted as new PI code
   246     // toggling local area coverage bit code must not be interpreted as new PI code
   233     if( region != RadioRegion::America )
   247     if ( region != RadioRegion::America ) {
   234     {
   248         piCode &= ~DISABLE_LOCAL_AREA_COVERAGE_MASK;
   235         piCode &= ~KDisableLocalAreaCoverageMask;
       
   236     }
   249     }
   237 
   250 
   238     LOG_FORMAT( "stored PI: %d", mData->mPiCode );
   251     LOG_FORMAT( "stored PI: %d", mData->mPiCode );
   239     LOG_FORMAT( "call sign check done: %d", mData->mCallSignCheckDone );
   252     LOG_FORMAT( "call sign check done: %d", mData->mCallSignCheckDone );
   240     //prevent executing the below code when unnessesary
   253     //prevent executing the below code when unnessesary
   241     if ( mData->mPiCode != piCode || !mData->mCallSignCheckDone )
   254     if ( mData->mPiCode != piCode || !mData->mCallSignCheckDone ) {
   242 	{
       
   243         detach();
       
   244         mData->mPiCode = piCode;
   255         mData->mPiCode = piCode;
   245         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::PiCodeChanged;
   256         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::PiCodeChanged;
   246         // call sign not calculated for clear channel stations
   257         // call sign not calculated for clear channel stations
   247 		//TODO: Remove magic numbers
   258 		//TODO: Remove magic numbers
   248         if( ( (mData->mPiCode & 0xF000 ) >> 12 ) == 0x1 )
   259         if ( ( (mData->mPiCode & 0xF000 ) >> 12 ) == 0x1 ) {
   249         {
       
   250             LOG( "Clear channel station" );
   260             LOG( "Clear channel station" );
   251             mData->mCallSignCheckDone = true;
   261             mData->mCallSignCheckDone = true;
   252         }
   262         } else if ( region == RadioRegion::America && mData->mName.isEmpty() && !isRenamed() ) {
   253 		// if America region, not PS name received and not user renamed station
       
   254         else if ( region == RadioRegion::America && mData->mName.isEmpty() && !isRenamed() )
       
   255         {
       
   256             LOG( "Calculate call sign" );
   263             LOG( "Calculate call sign" );
   257             mData->mName = piCodeToCallSign( mData->mPiCode );
   264             mData->mName = piCodeToCallSign( mData->mPiCode );
   258             mData->mChangeFlags |= RadioStation::NameChanged;
   265             mData->mChangeFlags |= RadioStation::NameChanged;
   259         }
   266         }
   260 
   267 
   261         if ( mData->mChangeFlags.testFlag( RadioStation::PsTypeChanged ) )
   268         if ( mData->mChangeFlags.testFlag( RadioStation::PsTypeChanged ) ) {
   262 		{
       
   263             LOG( "Call sign check done" );
   269             LOG( "Call sign check done" );
   264             mData->mCallSignCheckDone = true;
   270             mData->mCallSignCheckDone = true;
   265         }
   271         }
   266 
   272 
       
   273         ASSERT_SHARED_NULL_IS_INTACT
   267         return true;
   274         return true;
   268     }
   275     }
   269     return false;
   276     return false;
   270 }
   277 }
   271 
   278 
   273  *
   280  *
   274  */
   281  */
   275 void RadioStation::setPsType( PsType psType )
   282 void RadioStation::setPsType( PsType psType )
   276 {
   283 {
   277     if ( mData->mPsType != psType ) {
   284     if ( mData->mPsType != psType ) {
   278         detach();
       
   279         mData->mPsType = psType;
   285         mData->mPsType = psType;
   280         mData->mChangeFlags |= RadioStation::PsTypeChanged;
   286         mData->mChangeFlags |= RadioStation::PsTypeChanged;
       
   287         ASSERT_SHARED_NULL_IS_INTACT
   281     }
   288     }
   282 }
   289 }
   283 
   290 
   284 /*!
   291 /*!
   285  *
   292  *
   286  */
   293  */
   287 void RadioStation::setRadioText( const QString& radioText )
   294 void RadioStation::setRadioText( const QString& radioText )
   288 {
   295 {
   289     if ( mData->mRadioText.compare( radioText ) != 0 ) {
   296     if ( mData->mRadioText.compare( radioText ) != 0 ) {
   290         detach();
       
   291         mData->mRadioText = radioText.isEmpty() ? "" : radioText.trimmed();
   297         mData->mRadioText = radioText.isEmpty() ? "" : radioText.trimmed();
   292         mData->mChangeFlags |= RadioStation::RadioTextChanged;
   298         mData->mChangeFlags |= RadioStation::RadioTextChanged;
       
   299         ASSERT_SHARED_NULL_IS_INTACT
   293     }
   300     }
   294 }
   301 }
   295 
   302 
   296 /*!
   303 /*!
   297  *
   304  *
   306         if ( rtPlusClass == RtPlus::Homepage ) {
   313         if ( rtPlusClass == RtPlus::Homepage ) {
   307             setUrl( rtPlusItem );
   314             setUrl( rtPlusItem );
   308             return;
   315             return;
   309         }
   316         }
   310 
   317 
   311         detach();
   318         const QString replacement = HTML_TAG_START + rtPlusItem + HTML_TAG_END;
   312         QString replacement = "";
       
   313         if ( rtPlusClass == RtPlus::Artist ) {
       
   314             replacement = KLinkArtist;
       
   315         } else if ( rtPlusClass == RtPlus::Title ) {
       
   316             replacement = KLinkTitle;
       
   317         }
       
   318         replacement += rtPlusItem + KLinkClose;
       
   319 
   319 
   320         mData->mRadioText.replace( rtPlusItem, replacement );
   320         mData->mRadioText.replace( rtPlusItem, replacement );
   321         mData->mChangeFlags |= RadioStation::RadioTextChanged;
   321         mData->mChangeFlags |= RadioStation::RadioTextChanged;
   322     }
   322     }
   323 }
   323 }
   326  *
   326  *
   327  */
   327  */
   328 void RadioStation::setDynamicPsText( const QString& dynamicPsText )
   328 void RadioStation::setDynamicPsText( const QString& dynamicPsText )
   329 {
   329 {
   330     if ( mData->mDynamicPsText.compare( dynamicPsText ) != 0 ) {
   330     if ( mData->mDynamicPsText.compare( dynamicPsText ) != 0 ) {
   331         detach();
       
   332         mData->mDynamicPsText = dynamicPsText;
   331         mData->mDynamicPsText = dynamicPsText;
   333         mData->mChangeFlags |= RadioStation::DynamicPsChanged;
   332         mData->mChangeFlags |= RadioStation::DynamicPsChanged;
       
   333         ASSERT_SHARED_NULL_IS_INTACT
   334     }
   334     }
   335 }
   335 }
   336 
   336 
   337 /*!
   337 /*!
   338  *
   338  *
   339  */
   339  */
   340 bool RadioStation::isValid() const
   340 bool RadioStation::isValid() const
   341 {
   341 {
   342     return mData->mPresetIndex >= 0 && mData->mFrequency > 0;
   342     return mData->mFrequency > 0 && ( isType( ManualStation ) || mData->mPresetIndex >= 0 );
   343 }
   343 }
   344 
   344 
   345 /*!
   345 /*!
   346  *
   346  *
   347  */
   347  */
   356 void RadioStation::setUserDefinedName( const QString& name )
   356 void RadioStation::setUserDefinedName( const QString& name )
   357 {
   357 {
   358     // We don't check for name emptiness because this setter is used also to remove the renaming
   358     // We don't check for name emptiness because this setter is used also to remove the renaming
   359     // of a station by setting an empty name
   359     // of a station by setting an empty name
   360     if ( mData->mName.compare( name ) != 0 ) {
   360     if ( mData->mName.compare( name ) != 0 ) {
   361         detach();
       
   362         mData->mName = name;
   361         mData->mName = name;
   363         mData->mRenamedByUser = !name.isEmpty();
   362         mData->mRenamedByUser = !name.isEmpty();
   364         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::NameChanged;
   363         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::NameChanged;
   365     }
   364     }
   366 }
   365 }
   382 }
   381 }
   383 
   382 
   384 /*!
   383 /*!
   385  *
   384  *
   386  */
   385  */
   387 QString RadioStation::frequencyMhz() const
   386 QString RadioStation::frequencyString() const
   388 {
   387 {
   389     return parseFrequency( mData->mFrequency );
   388     return parseFrequency( mData->mFrequency );
   390 }
   389 }
   391 
   390 
   392 /*!
   391 /*!
   409  *
   408  *
   410  */
   409  */
   411 void RadioStation::setFavorite( bool favorite )
   410 void RadioStation::setFavorite( bool favorite )
   412 {
   411 {
   413     if ( isFavorite() != favorite ) {
   412     if ( isFavorite() != favorite ) {
   414         detach();
       
   415         favorite ? setType( RadioStation::Favorite ) : unsetType( RadioStation::Favorite );
   413         favorite ? setType( RadioStation::Favorite ) : unsetType( RadioStation::Favorite );
   416         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::FavoriteChanged;
   414         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::FavoriteChanged;
       
   415         ASSERT_SHARED_NULL_IS_INTACT
   417     }
   416     }
   418 }
   417 }
   419 
   418 
   420 /*!
   419 /*!
   421  *
   420  *
   434 }
   433 }
   435 
   434 
   436 /*!
   435 /*!
   437  *
   436  *
   438  */
   437  */
   439 bool RadioStation::hasPiCode() const
   438 int RadioStation::piCode() const
   440 {
   439 {
   441     return mData->mPiCode != -1;
   440     return mData->mPiCode;
   442 }
       
   443 
       
   444 /*!
       
   445  *
       
   446  */
       
   447 bool RadioStation::hasRds() const
       
   448 {
       
   449     return hasPiCode() ||
       
   450             mData->mGenre != -1 ||
       
   451             !mData->mDynamicPsText.isEmpty() ||
       
   452             !mData->mRadioText.isEmpty() ||
       
   453             ( !mData->mName.isEmpty() && !isRenamed() );
       
   454 }
   441 }
   455 
   442 
   456 /*!
   443 /*!
   457  *
   444  *
   458  */
   445  */
   459 void RadioStation::setType( RadioStation::Type type )
   446 void RadioStation::setType( RadioStation::Type type )
   460 {
   447 {
   461     if ( !isType( type ) ) {
   448     if ( !isType( type ) ) {
   462         detach();
       
   463 
   449 
   464         // Check if favorite-status changed
   450         // Check if favorite-status changed
   465         if ( mData->mType.testFlag( RadioStation::Favorite ) != type.testFlag( RadioStation::Favorite ) ) {
   451         if ( mData->mType.testFlag( RadioStation::Favorite ) != type.testFlag( RadioStation::Favorite ) ) {
   466             mData->mChangeFlags |= RadioStation::FavoriteChanged;
   452             mData->mChangeFlags |= RadioStation::FavoriteChanged;
   467         }
   453         }
   468 
   454 
   469         mData->mType |= type;
   455         mData->mType |= type;
   470         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::TypeChanged;
   456         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::TypeChanged;
       
   457         ASSERT_SHARED_NULL_IS_INTACT
   471     }
   458     }
   472 }
   459 }
   473 
   460 
   474 /*!
   461 /*!
   475  *
   462  *
   476  */
   463  */
   477 void RadioStation::unsetType( RadioStation::Type type )
   464 void RadioStation::unsetType( RadioStation::Type type )
   478 {
   465 {
   479     if ( isType( type ) ) {
   466     if ( isType( type ) ) {
   480         detach();
       
   481 
   467 
   482         // Check if favorite-status changed
   468         // Check if favorite-status changed
   483         if ( mData->mType.testFlag( RadioStation::Favorite ) != type.testFlag( RadioStation::Favorite ) ) {
   469         if ( mData->mType.testFlag( RadioStation::Favorite ) != type.testFlag( RadioStation::Favorite ) ) {
   484             mData->mChangeFlags |= RadioStation::FavoriteChanged;
   470             mData->mChangeFlags |= RadioStation::FavoriteChanged;
   485         }
   471         }
   486 
   472 
   487         mData->mType &= ~type;
   473         mData->mType &= ~type;
   488         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::TypeChanged;
   474         mData->mChangeFlags |= RadioStation::PersistentDataChanged | RadioStation::TypeChanged;
       
   475         ASSERT_SHARED_NULL_IS_INTACT
   489     }
   476     }
   490 }
   477 }
   491 
   478 
   492 /*!
   479 /*!
   493  *
   480  *
   549  *
   536  *
   550  */
   537  */
   551 void RadioStation::resetChangeFlags()
   538 void RadioStation::resetChangeFlags()
   552 {
   539 {
   553     if ( mData->mChangeFlags != RadioStation::NoChange ) {
   540     if ( mData->mChangeFlags != RadioStation::NoChange ) {
   554         detach();
       
   555         mData->mChangeFlags = RadioStation::NoChange;
   541         mData->mChangeFlags = RadioStation::NoChange;
   556     }
       
   557 }
       
   558 
       
   559 /**
       
   560  * Decrements the reference count of the implicitly shared data.
       
   561  */
       
   562 void RadioStation::decrementReferenceCount()
       
   563 {
       
   564     if ( !mData->ref.deref() ) {
       
   565         delete mData;
       
   566         mData = 0;
       
   567     }
   542     }
   568 }
   543 }
   569 
   544 
   570 /*!
   545 /*!
   571  *
   546  *
   574  {
   549  {
   575     QString callSign;
   550     QString callSign;
   576 
   551 
   577     LOG_FORMAT( "RadioStation::piCodeToCallSign PI: %d", programmeIdentification );
   552     LOG_FORMAT( "RadioStation::piCodeToCallSign PI: %d", programmeIdentification );
   578     // call signs beginning with 'K'
   553     // call signs beginning with 'K'
   579     if( ( programmeIdentification>=KKxxxCallSignPiFirst ) && ( programmeIdentification < KWxxxCallSignPiFirst ) ) {
   554     if ( ( programmeIdentification>=KXXX_CALLSIGN_PI_FIRST ) && ( programmeIdentification < WXXX_CALLSIGN_PI_FIRST ) ) {
   580         callSign += "K";
   555         callSign += "K";
   581         callSign += iterateCallSign( KKxxxCallSignPiFirst, programmeIdentification );
   556         callSign += iterateCallSign( KXXX_CALLSIGN_PI_FIRST, programmeIdentification );
   582     }
   557     }
   583     // call signs beginning with 'W'
   558     // call signs beginning with 'W'
   584     else if (( programmeIdentification >= KWxxxCallSignPiFirst ) && ( programmeIdentification <= KWxxxCallSignPiLast )) {
   559     else if ( ( programmeIdentification >= WXXX_CALLSIGN_PI_FIRST ) && ( programmeIdentification <= WXXX_CALLSIGN_PI_LAST ) ) {
   585         callSign += "W";
   560         callSign += "W";
   586         callSign += iterateCallSign( KWxxxCallSignPiFirst, programmeIdentification );
   561         callSign += iterateCallSign( WXXX_CALLSIGN_PI_FIRST, programmeIdentification );
   587     }
   562     }
   588     // 3 letter only call signs
   563     // 3 letter only call signs
   589     else if(( programmeIdentification >= KxxxCallSignPiFirst ) && ( programmeIdentification <= KxxxCallSignPiLast)) {
   564     else if ( ( programmeIdentification >= XXX_CALLSIGN_PI_FIRST ) && ( programmeIdentification <= XXX_CALLSIGN_PI_LAST ) ) {
   590         callSign += callSignString( programmeIdentification );
   565         callSign += callSignString( programmeIdentification );
   591     }
   566     } else {
   592     else
       
   593     {
       
   594         LOG( "RadioStation::piCodeToCallSign - Unhandled else" );
   567         LOG( "RadioStation::piCodeToCallSign - Unhandled else" );
   595     }
   568     }
   596 
   569 
   597     LOG_FORMAT( "RadioStation::piCodeToCallSign, call sign: %s", GETSTRING(callSign) );
   570     LOG_FORMAT( "RadioStation::piCodeToCallSign, call sign: %s", GETSTRING(callSign) );
   598 
   571 
   605 QString RadioStation::iterateCallSign( int piBase, int programmeIdentification )
   578 QString RadioStation::iterateCallSign( int piBase, int programmeIdentification )
   606 {
   579 {
   607     QString callSign;
   580     QString callSign;
   608     LOG_FORMAT( "RadioStation::iterateCallSign base: %d", piBase );
   581     LOG_FORMAT( "RadioStation::iterateCallSign base: %d", piBase );
   609 
   582 
   610     int sum(0), i(0);
   583     int sum = 0;
   611 
   584     int i = 0;
   612     while( sum < programmeIdentification ) {
   585     while( sum < programmeIdentification ) {
   613         i++;
   586         ++i;
   614         sum = piBase + i * 676 + 0 + 0;
   587         sum = piBase + i * 676 + 0 + 0;
   615     }
   588     }
   616     callSign += callSignChar( i - 1 );
   589     callSign += callSignChar( i - 1 );
   617 
   590 
   618     int tmpSum( sum - 676 );
   591     int tmpSum( sum - 676 );
   619     sum -= 676;
   592     sum -= 676;
   620     i = 0;
   593     i = 0;
   621     while( sum <= programmeIdentification ) {
   594     while( sum <= programmeIdentification ) {
   622         i++;
   595         ++i;
   623         sum = tmpSum + 0 + i * 26 + 0;
   596         sum = tmpSum + 0 + i * 26 + 0;
   624     }
   597     }
   625     callSign += callSignChar( i - 1 );
   598     callSign += callSignChar( i - 1 );
   626 
   599 
   627     tmpSum = sum - 26;
   600     tmpSum = sum - 26;
   628     sum -= 26;
   601     sum -= 26;
   629     i = 0;
   602     i = 0;
   630     while( sum <= programmeIdentification ) {
   603     while( sum <= programmeIdentification ) {
   631         i++;
   604         ++i;
   632         sum = tmpSum + 0 + 0 + i;
   605         sum = tmpSum + 0 + 0 + i;
   633     }
   606     }
   634     callSign += callSignChar( i - 1 );
   607     callSign += callSignChar( i - 1 );
   635 
   608 
   636     return callSign;
   609     return callSign;
   639 /*!
   612 /*!
   640  *
   613  *
   641  */
   614  */
   642 QString RadioStation::callSignString( uint programmeIdentification )
   615 QString RadioStation::callSignString( uint programmeIdentification )
   643 {
   616 {
   644     for ( uint i = 0; i < KThreeLetterCallSignCount; ++i ) {
   617     for ( uint i = 0; i < THREE_LETTER_CALLSIGN_COUNT; ++i ) {
   645         if( piCode[i] == programmeIdentification ) {
   618         if( PI_CODE_TABLE[i] == programmeIdentification ) {
   646             return callSign[i];
   619             return CALLSIGN_TABLE[i];
   647         }
   620         }
   648     }
   621     }
   649 
   622 
   650     LOG_FORMAT( "RadioStation::callSignString, Not found PI: %d", programmeIdentification );
   623     LOG_FORMAT( "RadioStation::callSignString, Not found PI: %d", programmeIdentification );
   651 
   624 
   652     return QString("????");
   625     return QString( "????" );
   653 }
   626 }
   654 
   627 
   655 /*!
   628 /*!
   656  *
   629  *
   657  */
   630  */
   658 char RadioStation::callSignChar( uint decimalValue )
   631 char RadioStation::callSignChar( uint decimalValue )
   659 {
   632 {
   660     LOG_FORMAT( "RadioStation::callSignChar A+: %d", decimalValue );
   633     LOG_FORMAT( "RadioStation::callSignChar A+: %d", decimalValue );
   661     if ( decimalValue <= KLastCallSignCharCode ) {
   634     if ( decimalValue <= LAST_CALLSIGN_CHAR_CODE ) {
   662         return static_cast<char>( 'A' + decimalValue );
   635         return static_cast<char>( 'A' + decimalValue );
   663     }
   636     }
   664     return '?';
   637     return '?';
   665 }
       
   666 
       
   667 /**
       
   668  * Detach from the implicitly shared data
       
   669  */
       
   670 void RadioStation::detach()
       
   671 {
       
   672     if ( !isDetached() ) {
       
   673         RadioStationPrivate* newData = new RadioStationPrivate( *mData );
       
   674 
       
   675         decrementReferenceCount();
       
   676 
       
   677         newData->ref = 1;
       
   678         mData = newData;
       
   679 
       
   680         // The shared null instance of the data has its preset index set to -200 (RadioStation::SharedNull).
       
   681         // We change the preset index of the detached data to -100 (RadioStation::Invalid) just to ease
       
   682         // debugging. This guarantees that the only instance that has value -200 is the actual shared null.
       
   683         #ifdef _DEBUG
       
   684         if ( mData->mPresetIndex == RadioStation::SharedNull ) {
       
   685             mData->mPresetIndex = RadioStation::Invalid;
       
   686         }
       
   687         #endif
       
   688     }
       
   689 }
   638 }
   690 
   639 
   691 /**
   640 /**
   692  * Checks if the class is detached from implicitly shared data
   641  * Checks if the class is detached from implicitly shared data
   693  */
   642  */