diff options
Diffstat (limited to 'src/widgets/kremenu.cpp')
-rw-r--r-- | src/widgets/kremenu.cpp | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/src/widgets/kremenu.cpp b/src/widgets/kremenu.cpp new file mode 100644 index 0000000..365fba9 --- /dev/null +++ b/src/widgets/kremenu.cpp @@ -0,0 +1,631 @@ +/*************************************************************************** +* Copyright (C) 2003 by * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([email protected]) * +* * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as publishfed by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +***************************************************************************/ +#include "kremenu.h" + +#include <tqbitmap.h> +#include <tqcursor.h> +#include <tqfont.h> +#include <tqimage.h> +#include <tqobjectlist.h> +#include <tqpainter.h> +#include <tqpixmap.h> +#include <tqsignalmapper.h> + +#include <tdeapplication.h> +#include <kcursor.h> +#include <kdebug.h> +#include <tdeglobalsettings.h> +#include <kiconloader.h> +#include <kimageeffect.h> +#include <tdelocale.h> +#include <kpixmap.h> +#include <kpixmapeffect.h> + +KreMenu::KreMenu( TQWidget *parent, const char *name ) : + TQWidget( parent, name, TQt::WNoAutoErase ) +{ + Menu newMenu; + + mainMenuId = menus.append( newMenu ); + + currentMenuId = mainMenuId; + m_currentMenu = &( *currentMenuId ); + + setMouseTracking( true ); + setFocusPolicy( TQWidget::StrongFocus ); + setSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Preferred ); +} + + +KreMenu::~KreMenu() +{} + +void KreMenu::childEvent ( TQChildEvent *e ) +{ + if ( e->type() == TQChildEvent::ChildInserted ) { + + TQObject * child = e->child(); + if ( child->inherits( "KreMenuButton" ) ) { + KreMenuButton * button = ( KreMenuButton* ) child; + + Menu *buttonMenu = &( *( button->menuId ) ); + + + + if ( !( buttonMenu->activeButton ) ) // Highlight the button if it's the first in the menu + { + button->setActive( true ); + buttonMenu->activeButton = button; + } + + buttonMenu->addButton( button ); + + if ( buttonMenu != m_currentMenu ) + button->hide(); + else + button->show(); + + connect ( button, TQ_SIGNAL( clicked( KreMenuButton* ) ), this, TQ_SLOT( collectClicks( KreMenuButton* ) ) ); + } + } + else if ( e->type() == TQChildEvent::ChildRemoved ) { + TQObject * child = e->child(); + + KreMenuButton *button = ( KreMenuButton* ) child; + if ( m_currentMenu->positionList.find( button ) != m_currentMenu->positionList.end() ) // Ensure that what was removed was a button + { + // Remove the button from the list first + int pos = m_currentMenu->positionList[ button ]; // FIXME: this works only if the button is removed from the main menu + m_currentMenu->widgetList.remove( pos ); // FIXME: this works only if the button is removed from the main menu + m_currentMenu->positionList.remove( button ); // FIXME: this works only if the button is removed from the main menu + + // Now recalculate the position of the next button + ( m_currentMenu->widgetNumber ) --; // FIXME: this works only if the button is removed from the main menu + + KreMenuButton *lastButton = m_currentMenu->widgetList[ ( m_currentMenu->widgetNumber ) - 1 ]; + if ( lastButton ) + m_currentMenu->childPos = lastButton->y() + lastButton->height(); + m_currentMenu->activeButton = 0; + + setMinimumWidth( minimumSizeHint().width() + 10 ); //update the minimum width + } + + } + TQWidget::childEvent( e ); +} + +void KreMenu::collectClicks( KreMenuButton *w ) +{ + setFocus(); + + highlightButton( w ); + + // Emit signal indicating button activation with button ID + KrePanel panel = w->getPanel(); + emit clicked( panel ); +} + +MenuId KreMenu::createSubMenu( const TQString &title, const TQString &icon ) +{ + + // Create the new menu + Menu newMenu; + MenuId id = menus.append( newMenu ); + + // Add a button to the main menu for this submenu + TDEIconLoader il; + KreMenuButton *newMenuButton = new KreMenuButton( this ); + newMenuButton->subMenuId = id; + newMenuButton->setTitle( title ); + newMenuButton->setIconSet( il.loadIconSet( icon, TDEIcon::Panel ) ); + + // Add a button to the submenu to go back to the top menu + KreMenuButton *newSubMenuButton = new KreMenuButton( this ); + newSubMenuButton->menuId = id; + newSubMenuButton->subMenuId = mainMenuId; + newSubMenuButton->setTitle( i18n( "Up" ) ); + newSubMenuButton->setIconSet( il.loadIconSet( "1uparrow", TDEIcon::Panel ) ); + + connect( newMenuButton, TQ_SIGNAL( clicked( MenuId ) ), this, TQ_SLOT( showMenu( MenuId ) ) ); + connect( newSubMenuButton, TQ_SIGNAL( clicked( MenuId ) ), this, TQ_SLOT( showMenu( MenuId ) ) ); + + + return id; +} + +void KreMenu::highlightButton( KreMenuButton *button ) +{ + MenuId buttonMenuId = button->menuId; + Menu *buttonMenu = &( *buttonMenuId ); + + //Deactivate the old button + if ( buttonMenu->activeButton ) { + buttonMenu->activeButton->setActive( false ); + buttonMenu->activeButton->update(); + } + + //Activate the new button + + button->setActive( true ); + button->update(); + buttonMenu->activeButton = button; +} + +void KreMenu::keyPressEvent( TQKeyEvent *e ) +{ + switch ( e->key() ) { + case TQt::Key_Up: { + int current_index = m_currentMenu->positionList[ m_currentMenu->activeButton ]; + if ( current_index > 0 ) { + highlightButton( m_currentMenu->widgetList[ current_index - 1 ] ); + + //simulate a mouse click + TQMouseEvent me( TQEvent::MouseButtonPress, TQPoint(), 0, 0 ); + TDEApplication::sendEvent( m_currentMenu->activeButton, &me ); + + e->accept(); + } + break; + } + case TQt::Key_Down: { + int current_index = m_currentMenu->positionList[ m_currentMenu->activeButton ]; + if ( current_index < int( m_currentMenu->positionList.count() ) - 1 ) { + highlightButton( m_currentMenu->widgetList[ current_index + 1 ] ); + + //simulate a mouse click + TQMouseEvent me( TQEvent::MouseButtonPress, TQPoint(), 0, 0 ); + TDEApplication::sendEvent( m_currentMenu->activeButton, &me ); + + e->accept(); + } + break; + } + case TQt::Key_Enter: + case TQt::Key_Return: + case TQt::Key_Space: { + //simulate a mouse click + TQMouseEvent me( TQEvent::MouseButtonPress, TQPoint(), 0, 0 ); + TDEApplication::sendEvent( m_currentMenu->activeButton, &me ); + + e->accept(); + break; + } + default: + e->ignore(); + } +} + +TQSize KreMenu::sizeHint() const +{ + return minimumSizeHint(); +} + +//the minimum size hint will be the minimum size hint of the largest child +TQSize KreMenu::minimumSizeHint() const +{ + int width = 30; + + TQObjectList *childElements = queryList( 0, 0, false, false ); //only first-generation children (not recursive) + TQObjectListIterator it( *childElements ); + + TQObject *obj; + while ( ( obj = it.current() ) != 0 ) { + ++it; + + if ( obj->isWidgetType() ) { + int obj_width_hint = ( ( TQWidget* ) obj ) ->minimumSizeHint().width(); + + if ( obj_width_hint > width ) + width = obj_width_hint; + } + } + + return TQSize( width, 150 ); +} + +void KreMenu::paintEvent( TQPaintEvent * ) +{ + // Make sure the size is bigger than the minimum necessary + //if (minimumWidth() <45) setMinimumWidth(45); // FIXME: can somehow setMinimumWidth be restricted? This may not be the best place to do this + + // Get gradient colors + TQColor c = colorGroup().button(); + TQColor c1 = c.dark( 130 ); + TQColor c2 = c.light( 120 ); + + // Draw the gradient + KPixmap kpm; + kpm.resize( size() ); + KPixmapEffect::unbalancedGradient ( kpm, c2, c1, KPixmapEffect::HorizontalGradient, -150, -150 ); + + // Draw the handle + TQPainter painter( &kpm ); + painter.setPen( c1 ); + painter.drawLine( width() - 5, 20, width() - 5, height() - 20 ); + painter.end(); + + //Set the border transparent using a mask + TQBitmap mask( kpm.size() ); + mask.fill( TQt::color0 ); + painter.begin( &mask ); + painter.setPen( TQt::color1 ); + painter.setBrush( TQt::color1 ); + painter.drawRoundRect( 0, 0, width(), height(), ( int ) ( 2.0 / width() * height() ), 2 ); + painter.end(); + kpm.setMask( mask ); + + //Draw the border line + painter.begin( &kpm ); + painter.setPen( c1 ); + painter.drawRoundRect( 0, 0, width(), height(), ( int ) ( 2.0 / width() * height() ), 2 ); + + //Draw the top line bordering with the first button + if ( m_currentMenu->activeButton ) // draw only if there's a button + { + int w = m_currentMenu->activeButton->width(); + painter.setPen( c1 ); + painter.drawLine( w / 5, 8, w - 1, 8 ); + painter.setPen( c2 ); + painter.drawLine( w / 5, 9, w - 1, 9 ); + } + + painter.end(); + + // Copy the pixmap to the widget + bitBlt( this, 0, 0, &kpm ); +} + +void KreMenu::resizeEvent( TQResizeEvent* e ) +{ + emit resized( ( e->size() ).width(), ( e->size() ).height() ); +} + +void KreMenu::showMenu( MenuId id ) +{ + + // Hide the buttons in the current menu + // and show the ones in the new menu + + TQObjectList * childElements = queryList(); + TQObjectListIterator it( *childElements ); + + TQObject *obj; + while ( ( obj = it.current() ) != 0 ) { + ++it; + if ( obj->inherits( "KreMenuButton" ) ) { + KreMenuButton * button = ( KreMenuButton* ) obj; + if ( button->menuId == currentMenuId ) + button->hide(); + else if ( button->menuId == id ) + button->show(); + } + } + + + // Set the new menu as current + currentMenuId = id; + m_currentMenu = &( *( currentMenuId ) ); +} + + + + + +KreMenuButton::KreMenuButton( KreMenu *parent, KrePanel _panel, MenuId id, const char *name ) : + TQWidget( parent, name, TQt::WNoAutoErase ), + panel( _panel ) +{ + icon = 0; + highlighted = false; + text = TQString::null; + + if ( id == 0 ) + menuId = parent->mainMenu(); + else + menuId = id; + + subMenuId = 0; // By default it's not a submenu button + + resize( parent->size().width(), 55 ); + connect ( parent, TQ_SIGNAL( resized( int, int ) ), this, TQ_SLOT( rescale() ) ); + connect( this, TQ_SIGNAL( clicked() ), this, TQ_SLOT( forwardClicks() ) ); + setCursor( TQCursor( KCursor::handCursor() ) ); +} + + +KreMenuButton::~KreMenuButton() +{ + delete icon; +} + +void KreMenuButton::setTitle( const TQString &s ) +{ + text = s; + +#if 0 //this causes problems for the button to go back to editing a recipe + //adjust text to two lines if needed + if ( fontMetrics().width( text ) > 110 ) { + text.replace( ' ', "\n" ); + } +#endif + + setMinimumWidth( minimumSizeHint().width() ); + if ( parentWidget() ->minimumWidth() < minimumSizeHint().width() ) + parentWidget() ->setMinimumWidth( minimumSizeHint().width() + 10 ); + + update(); +} + +void KreMenuButton::mousePressEvent ( TQMouseEvent * ) +{ + emit clicked(); +} + +void KreMenuButton::rescale() +{ + resize( parentWidget() ->width() - 10, height() ); +} +TQSize KreMenuButton::sizeHint() const +{ + if ( parentWidget() ) + return ( TQSize( parentWidget() ->size().width() - 10, 40 ) ); + else + return TQSize( 100, 30 ); +} + +TQSize KreMenuButton::minimumSizeHint() const +{ + int text_width = TQMAX( fontMetrics().width( text.section( '\n', 0, 0 ) ), fontMetrics().width( text.section( '\n', 1, 1 ) ) ); + + if ( icon ) + return TQSize( 40 + icon->width() + text_width, 30 ); + else + return TQSize( 40 + text_width, 30 ); +} + +void KreMenuButton::paintEvent( TQPaintEvent * ) +{ + if ( !isShown() ) + return ; + // First draw the gradient + int darken = 130, lighten = 120; + TQColor c1, c2, c1h, c2h; //non-highlighted and highlighted versions + + // Set the gradient colors + + c1 = colorGroup().button().dark( darken ); + c2 = colorGroup().button().light( lighten ); + + if ( highlighted ) { + darken -= 10; + lighten += 10; + c1h = TDEGlobalSettings::highlightColor().dark( darken ); + c2h = TDEGlobalSettings::highlightColor().light( lighten ); + } + + // draw the gradient now + + TQPainter painter; + KPixmap kpm; + kpm.resize( ( ( TQWidget * ) parent() ) ->size() ); // use parent's same size to obtain the same gradient + + if ( !highlighted ) { + + // first the gradient + KPixmapEffect::unbalancedGradient ( kpm, c2, c1, KPixmapEffect::HorizontalGradient, -150, -150 ); + + } + else { + + // top gradient (highlighted) + kpm.resize( width(), height() ); + KPixmapEffect::unbalancedGradient ( kpm, c2h, c1h, KPixmapEffect::HorizontalGradient, -150, -150 ); + // low gradient besides the line (not hightlighted) + KPixmap kpmb; + kpmb.resize( width(), 2 ); + KPixmapEffect::unbalancedGradient ( kpmb, c2, c1, KPixmapEffect::HorizontalGradient, -150, -150 ); + // mix the two + bitBlt( &kpm, 0, height() - 2, &kpmb ); + + } + + // Draw the line + painter.begin( &kpm ); + painter.setPen( colorGroup().button().dark( darken ) ); + painter.drawLine( width() / 5, height() - 2, width() - 1, height() - 2 ); + painter.setPen( colorGroup().button().light( lighten ) ); + painter.drawLine( width() / 5, height() - 1, width() - 1, height() - 1 ); + painter.end(); + + + // Now Add the icon + + painter.begin( &kpm ); + int xPos, yPos; + if ( icon ) { + // Set the icon's desired horizontal position + + xPos = 10; + yPos = 0; + + + // Make sure it fits in the area + // If not, resize and reposition horizontally to be centered + + TQPixmap scaledIcon = *icon; + + if ( ( icon->height() > height() ) || ( icon->width() > width() / 3 ) ) // Nice effect, make sure you take less than half in width and fit in height (try making the menu very short in width) + { + TQImage image; + image = ( *icon ); + scaledIcon.convertFromImage( image.smoothScale( width() / 3, height(), TQImage::ScaleMin ) ); + } + + // Calculate the icon's vertical position + + yPos = ( height() - scaledIcon.height() ) / 2 - 1; + + + // Now draw it + + painter.drawPixmap( xPos, yPos, scaledIcon ); + + xPos += scaledIcon.width(); // increase it to place the text area correctly + } + + painter.end(); + + // If it's highlighted, draw a rounded area around the text + + // Calculate the rounded area + + int areax = xPos + 10; + int areah = fontMetrics().height() * ( text.contains( '\n' ) + 1 ) + fontMetrics().lineSpacing() * text.contains( '\n' ) + 6; // Make sure the area is sensible for text and adjust for multiple lines + + int areaw = width() - areax - 10; + + if ( areah > ( height() - 4 ) ) { + areah = height() - 4; // Limit to button height + } + + int areay = ( height() - areah - 2 ) / 2 + 1; // Center the area vertically + + + // Calculate roundness + + int roundy = 99, roundx = ( int ) ( ( float ) roundy * areah / areaw ); //Make corners round + + + if ( highlighted && areaw > 0 ) // If there is no space for the text area do not draw it + { + + // Draw the gradient + KPixmap area; + area.resize( areaw, areah ); + + + KPixmapEffect::gradient( area, c2h.light( 150 ), c1h.light( 150 ), KPixmapEffect::VerticalGradient ); + + painter.begin( &area ); + painter.setPen( c1h ); + painter.setBrush( TQt::NoBrush ); + painter.drawRoundRect( 0, 0, areaw, areah, roundx, roundy ); + painter.end(); + + // Make it round + TQBitmap mask( TQSize( areaw, areah ) ); + mask.fill( TQt::color0 ); + painter.begin( &mask ); + painter.setPen( TQt::color1 ); + painter.setBrush( TQt::color1 ); + painter.drawRoundRect( 0, 0, areaw, areah, roundx, roundy ); + painter.end(); + area.setMask( mask ); + + // Copy it to the button + bitBlt( &kpm, areax, areay, &area ); + } + + // Finally, draw the text besides the icon + TQRect r = rect(); + r.setLeft( areax + 5 ); + r.setWidth( areaw - 10 ); + + painter.begin( &kpm ); + if ( highlighted ) + painter.setPen( TDEGlobalSettings::highlightedTextColor() ); + else + painter.setPen( TDEGlobalSettings::textColor() ); + painter.setClipRect( r ); + painter.drawText( r, TQt::AlignVCenter, text ); + painter.end(); + + // Copy the offscreen button to the widget + bitBlt( this, 0, 0, &kpm, 0, 0, width(), height() ); // Copy the image with correct button size (button is already smaller than parent in width to leave space for the handle, so no need to use -10) + +} + +void KreMenuButton::setIconSet( const TQIconSet &is ) +{ + delete icon; + + icon = new TQPixmap( is.pixmap( TQIconSet::Small, TQIconSet::Normal, TQIconSet::On ) ); + + setMinimumWidth( minimumSizeHint().width() ); + if ( parentWidget() ->minimumWidth() < minimumSizeHint().width() ) + parentWidget() ->setMinimumWidth( minimumSizeHint().width() + 10 ); +} + +Menu::Menu( void ) +{ + childPos = 10; // Initial button is on top (10px), then keep scrolling down + widgetNumber = 0; // Initially we have no buttons + activeButton = 0; // Button that is highlighted +} + + +Menu::Menu( const Menu &m ) +{ + activeButton = m.activeButton; + childPos = m.childPos; + widgetNumber = m.widgetNumber; + + copyMap( positionList, m.positionList ); + copyMap( widgetList, m.widgetList ); +} + +Menu::~Menu( void ) +{} + +Menu& Menu::operator=( const Menu &m ) +{ + + activeButton = m.activeButton; + childPos = m.childPos; + widgetNumber = m.widgetNumber; + + copyMap( positionList, m.positionList ); + copyMap( widgetList, m.widgetList ); + + return *this; +} + + +void Menu::addButton( KreMenuButton* button ) +{ + button->move( 0, childPos ); + button->rescale(); + childPos += button->height(); + positionList[ button ] = widgetNumber; // Store index for this widget, and increment number + widgetList[ widgetNumber ] = button; // Store the button in the list (the inverse mapping of the previous one) + widgetNumber++; +} + +void Menu::copyMap( TQMap <int, KreMenuButton*> &destMap, const TQMap <int, KreMenuButton*> &origMap ) +{ + TQMap<int, KreMenuButton*>::ConstIterator it; + destMap.clear(); + for ( it = origMap.begin(); it != origMap.end(); ++it ) { + destMap[ it.key() ] = it.data(); + } +} + +void Menu::copyMap( TQMap <KreMenuButton*, int> &destMap, const TQMap <KreMenuButton*, int> &origMap ) +{ + TQMap<KreMenuButton*, int>::ConstIterator it; + destMap.clear(); + for ( it = origMap.begin(); it != origMap.end(); ++it ) { + destMap[ it.key() ] = it.data(); + } +} + +#include "kremenu.moc" |