/*!	\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
Exemplo n.º 4
0
/*!
 *	\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
Exemplo n.º 5
0
/*!	
 *	\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