1 // File: QtxListAction.cxx
2 // Author: Sergey TELKOV (Based on code by Eugene AKSENOV)
4 #include "QtxListAction.h"
13 #include <qpopupmenu.h>
14 #include <qtoolbutton.h>
15 #include <qobjectlist.h>
16 #include <qapplication.h>
18 static const char* list_arrow_icon[] = {
31 Class: QtxListAction::ToolButton
35 class QtxListAction::ToolButton : public QToolButton
38 ToolButton( QtxListAction*, QWidget* = 0, const char* = 0 );
39 virtual ~ToolButton();
41 virtual QSize sizeHint() const;
44 QtxListAction* myAction;
47 QtxListAction::ToolButton::ToolButton( QtxListAction* a, QWidget* parent, const char* name )
48 : QToolButton( parent, name ),
51 setIconSet( QPixmap( list_arrow_icon ) );
54 QtxListAction::ToolButton::~ToolButton()
57 myAction->controlDeleted( this );
60 QSize QtxListAction::ToolButton::sizeHint() const
62 QSize sz = iconSet().pixmap().size();
63 return QSize( sz.width() + 2, sz.height() + 2 );
72 Name: QtxListAction [public]
73 Desc: Constructs an list action with given parent and name. If toggle is true the
74 action will be a toggle action, otherwise it will be a command action.
77 QtxListAction::QtxListAction( QObject* parent, const char* name, bool toggle )
78 : QtxAction( parent, name, toggle ),
87 Name: QtxListAction [public]
88 Desc: This constructor creates an action with the following properties: the
89 description text, the icon or iconset icon, the menu text and keyboard
90 accelerator. It is a child of given parent and named specified name.
91 If toggle is true the action will be a toggle action, otherwise it will
95 QtxListAction::QtxListAction( const QString& text, const QIconSet& icon,
96 const QString& menuText, int accel,
97 QObject* parent, const char* name, bool toggle )
98 : QtxAction( text, icon, menuText, accel, parent, name, toggle ),
107 Name: QtxListAction [public]
108 Desc: This constructor creates an action with the following properties: the
109 description text, the menu text and keyboard accelerator. It is a child
110 of given parent and named specified name. If toggle is true the action
111 will be a toggle action, otherwise it will be a command action.
114 QtxListAction::QtxListAction( const QString& text, const QString& menuText,
115 int accel, QObject* parent, const char* name, bool toggle )
116 : QtxAction( text, menuText, accel, parent, name, toggle ),
125 Name: ~QtxListAction [virtual public]
129 QtxListAction::~QtxListAction()
134 Name: popupMode [public]
135 Desc: Returns popup mode. If popup mode "Item" (default) then action will
136 be added into popup menu as menu item. If popup mode "SubMenu" then
137 action will be added into popup menu as sub menu with list of items.
140 int QtxListAction::popupMode() const
146 Name: setPopupMode [public]
147 Desc: Set the popup mode. Popup mode define way in this action will be
148 added into popup menu. This function should be used before addTo.
151 void QtxListAction::setPopupMode( const int mode )
156 QStringList QtxListAction::names() const
160 lst = myFrame->names();
165 Name: addNames [public]
166 Desc: Fills the list of actions. Removes the old contents from
167 the list if 'clear' is true.
170 void QtxListAction::addNames( const QStringList& names, bool clear )
178 myFrame->addNames( names );
180 QStringList lst = myFrame->names();
181 for ( PopupsMap::Iterator pit = myPopups.begin(); pit != myPopups.end(); ++pit )
184 QPopupMenu* pm = (QPopupMenu*)pit.key();
185 for ( QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it )
186 pit.data().popup->insertItem( *it, i++ );
187 pm->setItemEnabled( pit.data().id, isEnabled() && pit.data().popup->count() );
190 for ( ButtonsMap::Iterator bit = myButtons.begin(); bit != myButtons.end(); ++bit )
192 bit.data().drop->setEnabled( isEnabled() && !lst.isEmpty() );
193 bit.data().main->setEnabled( isEnabled() && !lst.isEmpty() );
198 Name: addTo [virtual public]
199 Desc: Adds this control to 'popup' or 'toolbar'.
202 bool QtxListAction::addTo( QWidget* w )
204 if ( myButtons.contains( w ) || myPopups.contains( w ) )
207 if ( !w->inherits( "QPopupMenu" ) || popupMode() != SubMenu )
208 if ( !QtxAction::addTo( w ) )
213 addedTo( (QWidget*)w->children()->getLast(), w );
216 if ( w->inherits( "QToolBar" ) )
218 Buttons& entry = myButtons[w];
219 QHBox* dropWrap = new QHBox( w );
220 entry.drop = new ToolButton( this, dropWrap, "qt_dockwidget_internal" );
222 entry.drop->setTextLabel( text() );
223 entry.drop->setToggleButton( true );
224 entry.drop->setAutoRaise( entry.main->autoRaise() );
226 entry.main->setEnabled( isEnabled() && !myFrame->names().isEmpty() );
227 entry.drop->setEnabled( isEnabled() && !myFrame->names().isEmpty() );
229 entry.main->installEventFilter( this );
230 entry.drop->installEventFilter( this );
232 QToolTip::add( entry.drop, toolTip(), myTipGroup, statusTip() );
234 connect( entry.drop, SIGNAL( toggled( bool ) ), this, SLOT( onExpand( bool ) ) );
236 else if ( w->inherits( "QPopupMenu" ) && popupMode() == SubMenu )
239 QPopupMenu* pm = (QPopupMenu*)w;
241 entry.popup = new QPopupMenu( pm );
242 entry.id = pm->insertItem( text(), entry.popup );
245 QStringList lst = myFrame->names();
246 for ( QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it )
248 int id = entry.popup->insertItem( *it );
249 entry.popup->setItemParameter( id, i++ );
251 pm->setItemEnabled( entry.id, isEnabled() && entry.popup->count() );
252 myPopups.insert( w, entry );
254 connect( entry.popup, SIGNAL( activated( int ) ), this, SLOT( onActivated( int ) ) );
258 connect( w, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
265 Name: addTo [virtual public]
266 Desc: Adds this control to 'popup' or 'toolbar'. Allow to specify index
267 for adding into 'popup'.
270 bool QtxListAction::addTo( QWidget* w, const int idx )
272 return QtxAction::addTo( w, idx );
276 Name: removeFrom [virtual public]
277 Desc: Removes this control from 'popup' or 'toolbar'.
280 bool QtxListAction::removeFrom( QWidget* w )
282 if ( !QtxAction::removeFrom( w ) )
285 if ( w->inherits( "QToolBar" ) )
290 if ( myButtons.contains( w ) )
292 Buttons& entry = myButtons[w];
294 if ( entry.drop->parent() && entry.drop->parent()->parent() == w )
295 delete entry.drop->parent();
299 myButtons.remove( w );
301 else if ( w->inherits( "QPopupMenu" ) )
302 myPopups.remove( w );
308 Name: setEnabled [virtual public slot]
309 Desc: Enables/disables this control.
312 void QtxListAction::setEnabled( bool enable )
314 QtxAction::setEnabled( enable );
316 bool isOn = enable && !myFrame->names().isEmpty();
318 for ( ButtonsMap::Iterator bit = myButtons.begin(); bit != myButtons.end(); ++bit )
320 bit.data().drop->setEnabled( isOn );
321 bit.data().main->setEnabled( isOn );
324 for ( PopupsMap::Iterator pit = myPopups.begin(); pit != myPopups.end(); ++pit )
326 QPopupMenu* cont = (QPopupMenu*)pit.key();
327 cont->setItemEnabled( pit.data().id, isOn );
332 Name: setMaxLines [public]
333 Desc: Sets max number of lines that list frame shows
334 without vertical scroll bar. Default value is 5.
337 void QtxListAction::setMaxLines( int nlines )
339 myFrame->setMaxLines( nlines );
343 Name: setMaxLineChars [public]
344 Desc: Sets max number of characters in a line which list frame shows
345 without truncation. Default value is 12 (the widest char size is used).
348 void QtxListAction::setMaxLineChars( int nchars )
350 myFrame->setMaxLineChars( nchars );
354 Name: setComment [public]
355 Desc: Sets the format Qt string for comments displayed under the list
356 of actions for one action and for several actions.
357 Ex. "Undo %1 actions" format string will work as "Undo 3 actions"
358 when 3 actions are selected. The default format string is "%1".
361 void QtxListAction::setComment( const QString& c, const QString& sc )
366 myFrame->setSingleComment( sc.isEmpty() ? c : sc );
367 myFrame->setMultipleComment( c );
371 Name: eventFilter [virtual public]
372 Desc: Reimplemented to paint the tool buttons in 2D/3D.
375 bool QtxListAction::eventFilter( QObject* o, QEvent* e )
377 if ( !myRaise && ( e->type() == QEvent::Enter || e->type() == QEvent::Leave ) )
380 QWidget* wid = widget( (QWidget*)o );
381 if ( o == mainButton( wid ) )
382 obj = dropButton( wid );
383 else if ( o == dropButton( wid ) )
384 obj = mainButton( wid );
389 QApplication::sendEvent( obj, e );
394 return QObject::eventFilter( o, e );
398 Name: addedTo [protected]
399 Desc: Reimplemented for internal reasons.
402 void QtxListAction::addedTo( QWidget* actionWidget, QWidget* container )
405 QtxAction::addedTo( actionWidget, container );
408 if ( !container->inherits( "QToolBar" ) )
412 entry.main = (QToolButton*)actionWidget;
414 myButtons.insert( container, entry );
418 Name: initialize [private]
419 Desc: Initialization of object QtxListAction.
422 void QtxListAction::initialize()
424 myTipGroup = new QToolTipGroup( this );
426 myFrame = new QtxListFrame( qApp->mainWidget() );
427 myFrame->setMaxLines( 5 );
428 myFrame->setMaxLineChars( 7 );
432 connect( myFrame, SIGNAL( hided() ), this, SLOT( onHided() ) );
433 connect( this, SIGNAL( activated() ), this, SLOT( onSingle() ) );
434 connect( myFrame, SIGNAL( selected( int ) ), this, SLOT( onMultiple( int ) ) );
436 connect( myTipGroup, SIGNAL( removeTip() ), this, SLOT( clearStatusText() ) );
437 connect( myTipGroup, SIGNAL( showTip( const QString& ) ), this, SLOT( showStatusText( const QString& ) ) );
441 Name: onSingle [private slot]
442 Desc: Called when a single action is selected.
445 void QtxListAction::onSingle()
451 Name: onMultiple [private slot]
452 Desc: Called when multiple actions are selected.
455 void QtxListAction::onMultiple( int numActions )
460 if ( numActions > 0 )
461 emit activated( numActions );
465 Name: onExpand [private slot]
466 Desc: Activates the list of actions.
469 void QtxListAction::onExpand( bool on )
471 const QObject* obj = sender();
474 QWidget* wid = widget( (QToolButton*)obj );
475 QToolButton* main = mainButton( wid );
476 myFrame->setOwner( main );
484 void QtxListAction::onHided()
486 for ( ButtonsMap::Iterator bit = myButtons.begin(); bit != myButtons.end(); ++bit )
488 bool block = bit.data().drop->signalsBlocked();
489 bit.data().drop->blockSignals( true );
490 bit.data().drop->setOn( false );
491 bit.data().drop->blockSignals( block );
496 Name: onActivated [private slot]
497 Desc: Called when a sub menu item is activated.
500 void QtxListAction::onActivated( int id )
502 QPopupMenu* pm = (QPopupMenu*)sender();
503 int num = pm->itemParameter( id );
505 emit activated( num );
509 Name: onDestroyed [private slot]
510 Desc: Called when a container widget is destroyed.
513 void QtxListAction::onDestroyed( QObject* obj )
515 if ( !obj->isWidgetType() )
518 myPopups.remove( (QWidget*)obj );
519 myButtons.remove( (QWidget*)obj );
523 Name: widget [private]
524 Desc: Returns container widget for specified control.
527 QWidget* QtxListAction::widget( QWidget* obj ) const
530 for ( PopupsMap::ConstIterator pit = myPopups.begin(); pit != myPopups.end() && !wid; ++pit )
531 if ( pit.data().popup == obj )
534 for ( ButtonsMap::ConstIterator bit = myButtons.begin(); bit != myButtons.end() && !wid; ++bit )
535 if ( bit.data().main == obj || bit.data().drop == obj )
542 Name: listPopup [private]
543 Desc: Returns sub popup menu widget for specified container.
546 QPopupMenu* QtxListAction::listPopup( QWidget* wid ) const
549 if ( myPopups.contains( wid ) )
550 p = myPopups[wid].popup;
555 Name: mainButton [private]
556 Desc: Returns main tool button for specified container.
559 QToolButton* QtxListAction::mainButton( QWidget* wid ) const
562 if ( myButtons.contains( wid ) )
563 mb = myButtons[wid].main;
568 Name: dropButton [private]
569 Desc: Returns drop tool button for specified container.
572 QToolButton* QtxListAction::dropButton( QWidget* wid ) const
575 if ( myButtons.contains( wid ) )
576 db = myButtons[wid].drop;
581 Name: controlDeleted [private]
582 Desc: Called when action child controls deleted.
585 void QtxListAction::controlDeleted( QWidget* wid )
588 for ( ButtonsMap::Iterator it = myButtons.begin(); it != myButtons.end() && !w; ++it )
590 if ( it.data().main == wid || it.data().drop == wid )
599 myButtons.remove( w );
603 /**********************************************************************
604 ** Class: QtxListFrame
605 ** Descr: Frame for the list of actions
607 ***********************************************************************/
609 class QtxListFrame::ScrollEvent : public QCustomEvent
612 enum { Scroll = User + 1 };
614 ScrollEvent( bool down ) : QCustomEvent( Scroll ), myDown( down ) {};
615 virtual ~ScrollEvent() {};
617 bool isDown() const { return myDown; };
631 QtxListFrame::QtxListFrame( QWidget* parent, WFlags f )
632 : QFrame( parent, 0, WStyle_Customize | WStyle_NoBorderEx | WType_Popup | WStyle_Tool | WStyle_StaysOnTop ),
637 myMaxLineChars( 10 ),
639 myScrollBlock( false )
641 QVBoxLayout* theLayout = new QVBoxLayout( this, 3 );
642 theLayout->setResizeMode( QLayout::FreeResize );
644 myList = new QListBox( this );
645 myList->setSelectionMode( QListBox::Multi );
646 myList->setHScrollBarMode( QScrollView::AlwaysOff );
647 myList->setFocusPolicy( NoFocus );
649 QPalette p = myList->palette();
650 p.setColor( QPalette::Inactive, QColorGroup::Highlight,
651 p.color( QPalette::Active, QColorGroup::Highlight ) );
652 p.setColor( QPalette::Inactive, QColorGroup::HighlightedText,
653 p.color( QPalette::Active, QColorGroup::HighlightedText ) );
654 myList->setPalette( p );
656 /* We'll have the vertical scroll bar only and
657 truncate the names which are too wide */
658 connect( myList, SIGNAL( contentsMoving( int, int ) ), this, SLOT( onScroll( int, int ) ) );
660 myComment = new QLabel( this );
661 myComment->setFrameStyle( Panel | Sunken );
662 myComment->setAlignment( AlignCenter );
663 myMultipleComment = "%1";
665 theLayout->addWidget( myList );
666 theLayout->addWidget( myComment );
668 setFrameStyle( Panel | Raised );
671 QtxListFrame::~QtxListFrame()
676 Clears list of names [ public ]
679 void QtxListFrame::clear()
685 void QtxListFrame::addNames( const QStringList& names )
687 for ( QStringList::ConstIterator it = names.begin(); it != names.end(); ++it )
688 myNames.append( *it );
693 Sets a names to the list. Truncates the name to fit to the frame width.
694 Use QtxListAction::setMaxLineChar( int ) to set the width in characters. [ public ]
697 void QtxListFrame::setNames( const QStringList& names )
704 for ( QStringList::ConstIterator it = names.begin(); it != names.end(); ++it )
707 QFontMetrics fm = myList->fontMetrics();
708 int maxW = myMaxLineChars * fm.maxWidth();
709 int w = fm.width( s );
712 QString extra( "..." );
713 int len = s.length();
714 int extraLen = fm.width( extra ) + 1;
717 w = fm.width( s, --len );
718 if ( w + extraLen < maxW )
726 myList->insertItem( s );
730 const QStringList QtxListFrame::names() const
736 Sets max number of lines shown without activation of vertical scroll bar. [ public ]
739 void QtxListFrame::setMaxLines( int maxLines )
741 myMaxLines = maxLines;
745 Sets max number of chars in line ( the rest will be truncated ). [ public ]
748 void QtxListFrame::setMaxLineChars( int maxChars )
750 if ( myMaxLineChars == maxChars )
753 myMaxLineChars = maxChars;
758 Sets the format of single comment. [ public ]
761 void QtxListFrame::setSingleComment( const QString& comment )
763 mySingleComment = comment;
769 Sets the format of multiple comment. [ public ]
772 void QtxListFrame::setMultipleComment( const QString& comment )
774 myMultipleComment = comment;
780 Updates comment display. [ public ]
783 void QtxListFrame::updateComment()
786 int selNum = selected();
788 com = myMultipleComment;
789 else if ( selNum > 0 && !mySingleComment.isEmpty() )
790 com = mySingleComment;
792 if ( !com.isEmpty() )
793 com = com.arg( selNum );
795 myComment->setText( com );
798 void QtxListFrame::setOwner( QWidget* wo )
804 if ( myOwner->parentWidget() && myOwner->parentWidget()->inherits( "QToolBar" ) &&
805 ((QToolBar*)myOwner->parentWidget())->orientation() == Qt::Vertical )
806 lpos = QPoint( myOwner->x() + myOwner->width() + 2, myOwner->y() );
808 lpos = QPoint( myOwner->x(), myOwner->y() + myOwner->height() + 2 );
809 QPoint gpos = myOwner->parentWidget() ? myOwner->parentWidget()->mapToGlobal( lpos )
810 : myOwner->mapToGlobal( lpos );
811 if ( parentWidget() )
812 move( parentWidget()->mapFromGlobal( gpos ) );
819 Validates the action. [ private slot ]
822 void QtxListFrame::accept()
824 emit selected( selected() );
828 Cancels the action. [ private slot ]
831 void QtxListFrame::reject()
837 Initializes / shows the frame. [ virtual public slot ]
840 void QtxListFrame::show()
842 int cnt = (int)myList->count();
846 myList->setTopItem( 0 );
847 myList->clearSelection();
848 myList->setMinimumSize( 0, ( QMIN( cnt + 1, myMaxLines ) ) * myList->itemHeight() + 1 );
851 int linstep = myList->itemHeight();
852 myList->verticalScrollBar()->setLineStep( linstep );
853 myList->verticalScrollBar()->setPageStep( myMaxLines * linstep );
855 QFontMetrics fm = myList->fontMetrics();
856 layout()->invalidate();
857 int maxHeight = layout()->minimumSize().height() + layout()->margin();
858 int maxWidth = myMaxLineChars * fm.maxWidth();
859 for ( uint i = 0; i <= myList->count(); i++ )
860 maxWidth = QMAX( maxWidth, fm.width( myList->text( i ) ) );
862 resize( width(), maxHeight );
864 myList->updateGeometry();
866 QApplication::sendPostedEvents();
868 myList->resizeContents( myList->contentsWidth(),
869 myList->itemHeight() * cnt );
870 if ( myList->contentsHeight() > myList->visibleHeight() )
871 maxWidth += myList->verticalScrollBar()->width();
873 QString single = mySingleComment.arg( cnt );
874 QString multi = myMultipleComment.arg( cnt );
875 int comWidth = QMAX( myComment->fontMetrics().width( single ), myComment->fontMetrics().width( multi ) );
876 if ( myComment->frameWidth() )
877 comWidth += myComment->fontMetrics().width( "x" );
879 maxWidth = QMAX( maxWidth, comWidth );
881 resize( maxWidth, maxHeight );
884 qApp->installEventFilter( this );
891 Cleanup. [ virtual public slot ]
894 void QtxListFrame::hide()
896 qApp->removeEventFilter( this );
902 Processes KeyUp/KeyDown, PageUp/PageDown, CR and Esc keys.
903 Returns 'true' if event is eaten, 'false' otherwise. [ private ]
906 bool QtxListFrame::handleKeyEvent( QObject* , QKeyEvent* e )
908 if ( e->type() == QEvent::KeyRelease )
911 int selNum = selected();
915 setSelected( QMAX( 1, selNum - 1 ) );
918 setSelected( QMAX( 1, selNum + 1 ) );
921 setSelected( QMAX( 1, selNum - myMaxLines ) );
924 setSelected( selNum += myMaxLines );
930 setSelected( myList->count() );
943 Selects items on move, validates on button release. If object 'o' is not our name list,
944 we close the frame. Returns 'true' if event is eaten, 'false' otherwise. [ private ]
947 bool QtxListFrame::handleMouseEvent( QObject* o, QMouseEvent* e )
951 case QEvent::MouseButtonPress:
953 if ( o != myList->viewport() && !isPopup() )
957 case QEvent::MouseMove:
959 if ( o == myList->viewport() )
961 QListBoxItem* lbi = myList->itemAt( e->pos() );
963 setSelected( myList->index( lbi ) + 1 );
967 case QEvent::MouseButtonRelease:
969 if ( o == myList->viewport() )
981 bool QtxListFrame::event( QEvent* e )
983 if ( e->type() != (int)ScrollEvent::Scroll )
984 return QFrame::event( e );
986 ScrollEvent* se = (ScrollEvent*)e;
988 setSelected( myList->topItem() + myList->numItemsVisible() );
990 setSelected( myList->topItem() + 1 );
996 Watches mouse events on the viewport of the list. [ virtual public ]
999 bool QtxListFrame::eventFilter( QObject* o, QEvent* e )
1001 bool isKeyEvent = ( e->type() == QEvent::KeyPress ||
1002 e->type() == QEvent::KeyRelease );
1003 bool isMouseEvent = ( e->type() == QEvent::MouseMove ||
1004 e->type() == QEvent::MouseButtonPress ||
1005 e->type() == QEvent::MouseButtonRelease ||
1006 e->type() == QEvent::MouseButtonDblClick );
1010 if ( handleKeyEvent( o, ( QKeyEvent* )e ) )
1013 else if ( isMouseEvent && o != myList->verticalScrollBar() )
1015 if ( handleMouseEvent( o, ( QMouseEvent*)e ) )
1019 if ( o != this && ( e->type() == QEvent::Resize || e->type() == QEvent::Move ) )
1020 setOwner( myOwner );
1022 return QFrame::eventFilter( o, e );
1026 Selects operations while scrolling the list. [ private slot ]
1029 void QtxListFrame::onScroll( int x, int y )
1031 int dx = y - myScrollVal;
1032 if ( !myScrollBlock )
1033 QApplication::postEvent( this, new ScrollEvent( dx > 0 ) );
1038 Selects the actions [ 0 - lastSel ]. [ public ]
1041 void QtxListFrame::setSelected( const int lastSel )
1043 int last = QMIN( lastSel, (int)myList->count() );
1045 for ( int i = 0; i < (int)myList->count(); i++ )
1046 myList->setSelected( i, i < last );
1048 int item = last - 1;
1050 myScrollBlock = true;
1052 if ( item < myList->topItem() )
1053 myList->setTopItem( item );
1055 if ( item >= myList->topItem() + myList->numItemsVisible() )
1056 myList->setTopItem( item - myList->numItemsVisible() + 1 );
1058 myScrollBlock = false;
1060 myList->clearFocus();
1065 int QtxListFrame::selected() const
1068 while ( sel < myList->count() && myList->isSelected( sel ) )