/*! \brief Reads categories from message. * \details Finds all categories in the message and adds them into global * list of categories. * \param[in] in The BMessage to read categories from. * \returns B_OK if everything went good. * \note Assumptions * Categories' names and corresponding colors are located in * the message under the names "Category0", "Color0" until * "CategoryX", "ColorX" for X+1 categories. There are no repetitions * and no skipped items. */ static void ReadCategoriesFromMessage( BMessage* in ) { BString sbCategory, //!< That will be identifier of category name sbColor; //!< That will be identifier of the corresponding color BString catName; //!< This is the name of the category read from message or from hard disk. rgb_color catColor; //!< This is the color of the category read from message or created randomly. uint32 tempUint32 = 0; //!< Used to read the color. status_t status = B_OK; if ( !in ) return; // Nothing to parse. unsigned int index = 0; while ( index < USHRT_MAX ) // Almost infinite loop { sbCategory.SetTo( "Category" ); // Clear the previous string. sbCategory << index; sbColor.SetTo( "Color" ); // Clear the previous string. sbColor << index; // Now we have proper strings "CategoryX" and "ColorX" both // sharing the same number X. Let's find out if the message // contains the data on category with this ID. status = in->FindString( sbCategory.String(), &catName ); if ( status != B_OK ) { return; // Didn't find name of the category } else { // Name of the category was found successfully. Fetch the color: status = in->FindInt32( sbColor.String(), ( int32* )&tempUint32 ); if ( status != B_OK ) { // The name was found, but the color wasn't... Define a random color! catColor = CreateRandomColor(); } else { catColor = RepresentUint32AsColor( tempUint32 ); } } ++index; // Don't forget to move to the next placeholder in the message // Actually add the category. AddCategoryToGlobalList( catName, catColor ); } // <-- end of "while (there are categories in the message)" return; } // <-- end of function ReadCategoriesFromMessage
/*! \brief Main function of the class * \param[in] in The received message. */ void CategoryPreferencesView::MessageReceived( BMessage *in ) { if ( !in ) { return; } BMessage* toSend = NULL; BString sb; Category stub( BString("") ), receivedFromUpdate( BString("") ); ColorUpdateWindow* cuWindow; CategoryListItem* listItem = NULL; CategoryMenuItem* menuItem = NULL; uint32 tempUint32 = 0; int tempInt = 0; status_t errorCode = B_OK; bool tempBool = false; switch ( in->what ) { case ( kAddNewCategory ): // Creating a stub of category without a name and with random color. stub.categoryName.SetTo( "" ); stub.categoryColor = CreateRandomColor(); cuWindow = new ColorUpdateWindow( stub, true, "Add new category", ( BHandler* )this, this->Looper(), NULL ); // No message break; case ( kColorSelected ): in->FindString( "Original string", &stub.categoryName ); in->FindInt32( "Original color", ( int32* )&tempUint32 ); stub.categoryColor = RepresentUint32AsColor( tempUint32 ); in->FindString( "New string", &receivedFromUpdate.categoryName ); in->FindInt32( "New color", ( int32* )&tempUint32 ); receivedFromUpdate.categoryColor = RepresentUint32AsColor( tempUint32 ); // If the received category name is empty, don't change anything. if ( receivedFromUpdate.categoryName == "" ) { return; } listItem = new CategoryListItem( receivedFromUpdate ); if ( ! listItem ) { /* Panic! */ exit( 1 ); } if ( listView->AddItem( listItem ) ) { AddCategoryToGlobalList( receivedFromUpdate ); } // Adding category to the Menu toSend = new BMessage( kMergeIntoCategory ); if ( ! toSend ) { /* Panic! */ exit( 1 ); } toSend->AddString( "Category", receivedFromUpdate.categoryName ); menuItem = new CategoryMenuItem( receivedFromUpdate, toSend ); if ( ! menuItem ) { /* Panic! */ exit( 1 ); } menuItem->SetTarget( this ); listMenu->AddItem( menuItem ); break; case ( kColorReverted ): /* Nothing should be done. */ break; case ( kCategorySelected ): /* Enabling the "Edit" button and the "Merge to..." menu. */ tempInt = listView->CurrentSelection(); if ( tempInt < 0 || ( ( ( CategoryListItem* )listView->ItemAt( tempInt ))->GetLabel() == "Default" ) ) { editButton->SetEnabled( false ); menuField->SetEnabled( false ); } else { editButton->SetEnabled( true ); menuField->SetEnabled( true ); } break; case ( kMergeIntoCategory ): // Here "stub" is the category selected in the listView - the source of merge. // "receivedFromUpdate" is the category selected in the menu - the target of merge. // Actually, only the names are interesting. tempInt = listView->CurrentSelection(); if ( tempInt < 0 || ( ( listItem = ( CategoryListItem* )listView->ItemAt( tempInt ))->GetLabel() == "Default" ) ) { // "Default" category can't be merged. break; } stub.categoryName = listItem->GetLabel(); errorCode = in->FindString( "Category", &( receivedFromUpdate.categoryName ) ); if ( ( errorCode != B_OK ) || ( stub.categoryName == receivedFromUpdate.categoryName ) ) { // Can't merge category. utl_Deb = new DebuggerPrintout( "You're probably trying to merge a category into itself." ); break; } tempBool = MergeCategories( stub.categoryName, receivedFromUpdate.categoryName ); if ( tempBool ) { // The merge was successful. Remove old category both from list and from menu. listView->DeselectAll(); // This also disables the menu. listView->RemoveItem( tempInt ); listMenu->RemoveItem( listMenu->FindItem( stub.categoryName ) ); } break; case ( kCategoryInvoked ): // Modifying currently existing category tempInt = listView->CurrentSelection(); if ( tempInt < 0 ) { break; } listItem = ( CategoryListItem* )listView->ItemAt( tempInt ); cuWindow = new ColorUpdateWindow( Category( listItem->GetLabel(), listItem->GetColor() ), false, // Name shouldn't be edited "Edit category", ( BHandler* )this, this->Looper(), NULL ); // No message break; default: BView::MessageReceived( in ); }; } // <-- end of CategoryPreferencesView::MessageReceived
/*! \brief Search the filesystem for additional categories. * \details Starts a whole-filesystem query for the Event files with * categories which may be copied to the system and not appear * in the Categories' database. */ static void SearchFilesystemForAdditionalCategories( void ) { BQuery* categoryQuery = NULL; //!< The way to fill the previously-uncatched categories. Category* pCategory = NULL; //!< Used to traverse the list of categories status_t status; //!< Result of the last action. ssize_t bytesTransferred; //!< Used in I/O operations entry_ref fileToReadAttributesFrom; //!< This is the reference to file with unknown category. BFile* file = NULL; //!< This file will be initialized with fileToGetTheAttributesFrom. attr_info attribute_info; //!< Information about the attribute. rgb_color catColor; //!< Category color char buffer[ 255 ]; //!< I'll use this buffer to read Categories from files categoryQuery = new BQuery(); if ( !categoryQuery ) { /* Nothing to do */ return; } // For initialization of the BQuery, we need to find the Volume with user's data. BVolumeRoster volumeRoster; BVolume bootVolume; volumeRoster.GetBootVolume( &bootVolume ); // Setting the query to look in the boot volume categoryQuery->SetVolume( &bootVolume ); /* First item of the predicate is the type of the file. */ categoryQuery->PushAttr( "BEOS:TYPE" ); categoryQuery->PushString( kEventFileMIMEType ); categoryQuery->PushOp( B_EQ ); /* Check the category attribute type's name. */ int i = 0; BString categoryAttributeInternalName; while ( AttributesArray[ i ].internalName != 0 ) { if ( strcmp( AttributesArray[ i ].humanReadableName, "Category" ) == 0 ) { // Found the correct attribute! Now, let's take its internal name... break; } ++i; } /* Build the query predicate. * This is meaningful only if global list of categories contains any items, * and if we succeeded to find the attribute with human-readable name "Category". */ if ( ! global_ListOfCategories.IsEmpty() && ( AttributesArray[ i ].internalName != NULL ) ) { for ( int i = 0, limit = global_ListOfCategories.CountItems(); i < limit; ++i ) { pCategory = ( Category* )global_ListOfCategories.ItemAt( i ); if ( !pCategory ) continue; categoryQuery->PushAttr( AttributesArray[ i ].internalName ); categoryQuery->PushString( pCategory->categoryName.String(), true ); categoryQuery->PushOp( B_NE ); categoryQuery->PushOp( B_AND ); } // <-- end of "for ( all currently known categories )" } // <-- end of "if ( there are any items in the list of known categories )" /* The predicate that we currently have looks like this: * ((( type is Eventual ) && ( category != "Cat1" )) && ( category != "Cat2" )) && ... * The order does not matter, since we're using "AND". * * Well, let's fire and see what comes... */ categoryQuery->Fetch(); while ( ( status = categoryQuery->GetNextRef( &fileToReadAttributesFrom ) ) == B_OK ) { // Successfully retrieved next entry file = new BFile( &fileToReadAttributesFrom, B_READ_ONLY ); if ( !file || file->InitCheck() != B_OK ) continue; status = file->GetAttrInfo( AttributesArray[ i ].internalName, &attribute_info ); if ( status != B_OK ) continue; status = file->ReadAttr( AttributesArray[ i ].internalName, attribute_info.type, 0, buffer, ( attribute_info.size > 255 ) ? 255 : attribute_info.size ); if ( status != B_OK ) continue; // Succeeded to read the category name, it's in "buffer". Create the color... catColor = CreateRandomColor(); // ...and add the category to the list of categories. AddCategoryToGlobalList( BString( buffer ), catColor ); // We don't need the file anymore. delete file; } delete categoryQuery; } // <-- end of function SearchFilesystemForAdditionalCategories
/*! * \brief Populate the list with categories * \param[in] preferences Message with list of categories and corresponding colors * \note How the list is populated? * If the preferences message is not NULL, the categories are taken * from there. If it is NULL, the categories are taken from the * global list of categories. */ void CategoryListView::RefreshList( BMessage* preferences ) { int index; bool bLocked = false; CategoryListItem *listItem = NULL; Category* pCat = NULL; /* Part 1. Firstly, we need to clear the current list. */ // Lock the parent window. if ( this->Window() && ( this->Window() )->Lock() ) { bLocked = true; } // Remove the items. while ( ( listItem = ( CategoryListItem* )this->ItemAt( 0 ) ) != NULL ) { this->RemoveItem( listItem ); delete listItem; } // At this point the list is empty. Let's fill it! /* Part 2. If the message is NULL, take the items from global list of categories. */ if ( preferences == NULL ) { index = 0; while ( ( pCat = ( Category* )global_ListOfCategories.ItemAt( index ) ) != NULL ) { listItem = new CategoryListItem( pCat ); if ( !listItem ) { /* Panic! */ exit( 1 ); } this->AddItem( listItem ); } } /* Part 3. The message is not NULL, so take the categories from the message. */ else { rgb_color catColor; BString sbCategory, sbColor, catName; status_t status; index = 0; while ( index < USHRT_MAX ) // Almost infinite loop { sbCategory.Truncate( 0 ); // Clear the previous string. sbCategory << "Category" << index; sbColor.Truncate( 0 ); // Clear the previous string. sbColor << "Color" << index; // Now we have proper strings "CategoryX" and "ColorX" both // sharing the same number X. Let's find out if the message // contains the data on category with this ID. status = preferences->FindString( sbCategory.String(), &catName ); if ( status != B_OK ) { // Didn't find name of the category break; // Move on to part 2 } else { // Name of the category was found successfully. Fetch the color: status = preferences->FindInt32( sbColor.String(), ( int32* )&catColor ); if ( status != B_OK ) { // The name was found, but the color wasn't... Define a random color! catColor = CreateRandomColor(); } } ++index; // Don't forget to move to the next placeholder in the message listItem = new CategoryListItem( catColor, catName ); if ( ! listItem ) { /* Panic! */ exit( 1 ); } this->AddItem( listItem ); } // <-- end of "while ( there are items in the message )" } /* Part 4. Sort the items. */ this->SortItems( CategoriesCompareFunction ); /* Unlock the parent if needed. */ if ( bLocked ) { ( this->Window() )->Unlock(); } } // <-- end of function CategoryListView::RefreshList
/*! * \details This function removes all items from the menu and populates it * again according to the data found in the preferences message. * \param[in] preferences The message with categories used to populate the menu. * If "preferences" is NULL, items are taken from the global list of * categories. */ void CategoryMenu::RefreshMenu( BMessage* preferences ) { Category *pCat; CategoryMenuItem *menuItem = NULL; DebuggerPrintout *deb = NULL; BMessage* toSend = NULL; bool bLocked = false; int index, limit = global_ListOfCategories.CountItems(); status_t status; /* Part 1. Clean the old menu. * We should lock the window during the whole process. */ if ( this->Window() && ( this->Window() )->Lock() ) { bLocked = true; } while ( ( menuItem = ( CategoryMenuItem* )this->ItemAt( 0 ) ) != NULL ) { if ( ! this->RemoveItem( menuItem ) ) { // Something wrong went with the item's deletion. deb = new DebuggerPrintout( "Didn't succeed to remove item from menu!" ); } else // Everything went good; item was removed - let's delete it! { delete( menuItem ); } } // At this point, the menu is clear. Let's populate it! /* Part 2. If the preferences message was not submitted, build the menu from the * global list of categories. * I assume the items in list are sorted alphabetically. */ if ( !preferences ) { for ( index = 0; index < limit; index++ ) { pCat = ( Category* )global_ListOfCategories.ItemAt( index ); if ( pCat ) { if ( fTemplateMessage ) { toSend = new BMessage( *fTemplateMessage ); } else { toSend = new BMessage( kCategorySelected ); } if ( !toSend ) { /* Panic! */ exit( 1 ); } toSend->AddString( "Category", pCat->categoryName ); menuItem = new CategoryMenuItem( pCat, toSend ); if ( ! menuItem ) { /* Panic! */ exit( 1 ); } this->AddItem( menuItem ); } } } /* Part 3. If the preferences message was submitted, parse it and build the * menu from items in the message. * Utilizind the code from PopulateListOfCategories. */ else { index = 0; BString sbCategory, sbColor, catName; rgb_color catColor; while ( index < USHRT_MAX ) // Almost infinite loop { sbCategory.Truncate( 0 ); // Clear the previous string. sbCategory << "Category" << index; sbColor.Truncate( 0 ); // Clear the previous string. sbColor << "Color" << index; // Now we have proper strings "CategoryX" and "ColorX" both // sharing the same number X. Let's find out if the message // contains the data on category with this ID. status = preferences->FindString( sbCategory.String(), &catName ); if ( status != B_OK ) { // Didn't find name of the category break; // Move on to part 2 } else { // Name of the categ ory was found successfully. Fetch the color: status = preferences->FindInt32( sbColor.String(), ( int32* )&catColor ); if ( status != B_OK ) { // The name was found, but the color wasn't... Define a random color! catColor = CreateRandomColor(); } } ++index; // Don't forget to move to the next placeholder in the message // Create the menu item from given data. toSend = new BMessage( kCategorySelected ); if ( !toSend ) { /* Panic! */ exit( 1 ); } toSend->AddString( "Category", catName ); menuItem = new CategoryMenuItem( catName, catColor, toSend ); if ( ! menuItem ) { /* Panic! */ exit( 1 ); } this->AddItem( menuItem ); } // <-- end of "while (there are categories in the message)" } // <-- end of "the message with categories was submitted". // Unlock the window if it was locked. if ( bLocked ) { ( this->Window() )->Unlock(); } } // <-- end of function CategoryMenu::RefreshMenu