2014-11-13 19:30:51 +02:00
/*
Copyright 2007 Robert Knight < robertknight @ gmail . com >
Copyright 2008 - 2009 Sebastian Sauer < mail @ dipe . org >
This library is free software ; you can redistribute it and / or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation ; either
version 2 of the License , or ( at your option ) any later version .
This library is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Library General Public License for more details .
You should have received a copy of the GNU Library General Public License
along with this library ; see the file COPYING . LIB . If not , write to
the Free Software Foundation , Inc . , 51 Franklin Street , Fifth Floor ,
Boston , MA 02110 - 1301 , USA .
*/
// Own
# include "menuview.h"
// Qt
# include <QtCore/QAbstractItemModel>
# include <QtCore/QPersistentModelIndex>
# include <QtCore/QStack>
# include <QtGui/QApplication>
# include <QtGui/QMouseEvent>
# include <QtGui/QStandardItem>
# include <QtGui/QStyleOptionMenuItem>
# include <QtGui/QPainter>
# include <QtGui/QToolTip>
// KDE
# include <KDebug>
# include <KUrl>
# include <KIconLoader>
// Local
# include "core/models.h"
# include "core/itemhandlers.h"
# define MAX_NAME_SIZE 50
Q_DECLARE_METATYPE ( QPersistentModelIndex )
Q_DECLARE_METATYPE ( QAction * )
using namespace Kickoff ;
/// @internal d-pointer class
class MenuView : : Private
{
public :
enum { ActionRole = Qt : : UserRole + 52 } ;
Private ( MenuView * q ) : q ( q ) , column ( 0 ) , launcher ( new UrlItemLauncher ( q ) ) , formattype ( MenuView : : DescriptionName ) { }
~ Private ( )
{
qDeleteAll ( items ) ;
}
QAction * createActionForIndex ( QAbstractItemModel * model , const QModelIndex & index , QMenu * parent )
{
Q_ASSERT ( index . isValid ( ) ) ;
QAction * action = 0 ;
if ( model - > hasChildren ( index ) ) {
KMenu * childMenu = new KMenu ( parent ) ;
childMenu - > installEventFilter ( q ) ;
childMenu - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
connect ( childMenu , SIGNAL ( customContextMenuRequested ( QPoint ) ) ,
q , SLOT ( contextMenuRequested ( QPoint ) ) ) ;
action = childMenu - > menuAction ( ) ;
buildBranch ( childMenu , model , index ) ;
} else {
action = q - > createLeafAction ( index , parent ) ;
}
q - > updateAction ( model , action , index ) ;
return action ;
}
QString trunctuateName ( QString name , int maxSize )
{
if ( name . length ( ) < = maxSize ) {
return name ;
}
maxSize - = 2 ; // remove the 3 placeholder points
const int start = maxSize / 3 ; //use one third of the chars for the start of the name
const int end = maxSize - start ;
return name . left ( start ) + " .. " + name . right ( end ) ;
}
void buildBranch ( KMenu * menu , QAbstractItemModel * model , const QModelIndex & parent )
{
if ( model - > canFetchMore ( parent ) ) {
model - > fetchMore ( parent ) ;
}
const int rowCount = model - > rowCount ( parent ) ;
for ( int i = 0 ; i < rowCount ; i + + ) {
QAction * action = createActionForIndex ( model , model - > index ( i , column , parent ) , menu ) ;
action - > setText ( trunctuateName ( action - > text ( ) , MAX_NAME_SIZE ) ) ;
menu - > addAction ( action ) ;
}
}
QModelIndex findByRelPath ( QAbstractItemModel * model , const QModelIndex & parent , const QString & relPath )
{
QModelIndex newRoot ;
if ( model - > canFetchMore ( parent ) ) {
model - > fetchMore ( parent ) ;
}
const int rowCount = model - > rowCount ( parent ) ;
for ( int row = 0 ; row < rowCount ; row + + ) {
QModelIndex index = model - > index ( row , 0 , parent ) ;
Q_ASSERT ( index . isValid ( ) ) ;
if ( index . data ( Kickoff : : RelPathRole ) . isValid ( ) ) {
QString indexRelPath = index . data ( Kickoff : : RelPathRole ) . toString ( ) ;
if ( relPath = = indexRelPath ) {
newRoot = index ;
break ;
}
if ( ! indexRelPath . isEmpty ( ) & & relPath . startsWith ( indexRelPath ) ) {
newRoot = findByRelPath ( model , index , relPath ) ;
break ;
}
}
}
return newRoot ;
}
MenuView * const q ;
int column ;
UrlItemLauncher * launcher ;
MenuView : : FormatType formattype ;
QPoint mousePressPos ;
QList < QStandardItem * > items ;
QHash < QAbstractItemModel * , QAction * > modelsHeader ;
QList < QWeakPointer < QAbstractItemModel > > models ;
} ;
MenuView : : MenuView ( QWidget * parent , const QString & title , const QIcon & icon )
: KMenu ( parent )
, d ( new Private ( this ) )
{
if ( ! title . isNull ( ) )
setTitle ( title ) ;
if ( ! icon . isNull ( ) )
setIcon ( icon ) ;
installEventFilter ( this ) ;
connect ( this , SIGNAL ( customContextMenuRequested ( QPoint ) ) ,
this , SLOT ( contextMenuRequested ( QPoint ) ) ) ;
}
MenuView : : ~ MenuView ( )
{
QListIterator < QWeakPointer < QAbstractItemModel > > it ( d - > models ) ;
while ( it . hasNext ( ) ) {
QAbstractItemModel * model = it . next ( ) . data ( ) ;
if ( model ) {
model - > disconnect ( this ) ;
}
}
delete d ;
}
QAction * MenuView : : createLeafAction ( const QModelIndex & index , QObject * parent )
{
Q_UNUSED ( index ) ;
return new QAction ( parent ) ;
}
void MenuView : : updateAction ( QAbstractItemModel * model , QAction * action , const QModelIndex & index )
{
bool isSeparator = index . data ( Kickoff : : SeparatorRole ) . value < bool > ( ) ;
// if Description or DescriptionName -> displayOrder = Kickoff::NameAfterDescription
// Qt::DisplayRole returns genericName, the generic name e.g. "Advanced Text Editor" or "Spreadsheet" or just "" (right, it's a mess too)
// Kickoff::SubTitleRole returns appName, the name e.g. "Kate" or "OpenOffice.org Calc" (right, sometimes the text is also used for the generic app-name)
//
// if Name or NameDescription or NameDashDescription -> displayOrder = Kickoff::NameBeforeDescription
// Qt::DisplayRole returns appName,
// Kickoff::SubTitleRole returns genericName.
QString mainText = index . data ( Qt : : DisplayRole ) . value < QString > ( ) . replace ( ' & ' , " && " ) ;
QString altText = index . data ( Kickoff : : SubTitleRole ) . value < QString > ( ) . replace ( ' & ' , " && " ) ;
if ( action - > menu ( ) ! = 0 ) { // if it is an item with sub-menuitems, we probably like to thread them another way...
action - > setText ( mainText ) ;
} else {
switch ( d - > formattype ) {
case Name :
case Description : {
action - > setText ( mainText ) ;
action - > setToolTip ( altText ) ;
} break ;
case NameDescription : // fall through
case NameDashDescription : // fall through
case DescriptionName : {
if ( ! mainText . isEmpty ( ) ) { // seems we have a program, but some of them don't define a name at all
if ( mainText . contains ( altText , Qt : : CaseInsensitive ) ) { // sometimes the description contains also the name
action - > setText ( mainText ) ;
} else if ( altText . contains ( mainText , Qt : : CaseInsensitive ) ) { // and sometimes the name also contains the description
action - > setText ( altText ) ;
} else { // seems we have a perfect desktop-file (likely a KDE one, heh) and name+description are clear separated
if ( d - > formattype = = NameDashDescription ) {
action - > setText ( QString ( " %1 - %2 " ) . arg ( mainText ) . arg ( altText ) ) ;
} else {
action - > setText ( QString ( " %1 (%2) " ) . arg ( mainText ) . arg ( altText ) ) ;
}
}
} else { // if there is no name, let's just use the describing text
action - > setText ( altText ) ;
}
} break ;
}
}
action - > setSeparator ( isSeparator ) ;
if ( ! isSeparator ) {
action - > setIcon ( index . data ( Qt : : DecorationRole ) . value < QIcon > ( ) ) ;
}
// we map modelindex and action together
action - > setData ( qVariantFromValue ( QPersistentModelIndex ( index ) ) ) ;
// don't emit the dataChanged-signal cause else we may end in a infinite loop
model - > blockSignals ( true ) ;
model - > setData ( index , qVariantFromValue ( action ) , Private : : ActionRole ) ;
model - > blockSignals ( false ) ;
}
bool MenuView : : eventFilter ( QObject * watched , QEvent * event )
{
switch ( event - > type ( ) ) {
case QEvent : : MouseMove : {
QMouseEvent * mouseEvent = static_cast < QMouseEvent * > ( event ) ;
QMenu * watchedMenu = qobject_cast < QMenu * > ( watched ) ;
const int mousePressDistance = ! d - > mousePressPos . isNull ( ) ? ( mouseEvent - > pos ( ) - d - > mousePressPos ) . manhattanLength ( ) : 0 ;
if ( watchedMenu & & mouseEvent - > buttons ( ) & Qt : : LeftButton
& & mousePressDistance > = QApplication : : startDragDistance ( ) ) {
QAction * action = watchedMenu - > actionAt ( mouseEvent - > pos ( ) ) ;
if ( ! action ) {
return KMenu : : eventFilter ( watched , event ) ;
}
QPersistentModelIndex index = action - > data ( ) . value < QPersistentModelIndex > ( ) ;
if ( ! index . isValid ( ) ) {
return KMenu : : eventFilter ( watched , event ) ;
}
QUrl url = index . data ( UrlRole ) . toUrl ( ) ;
if ( url . isEmpty ( ) ) {
return KMenu : : eventFilter ( watched , event ) ;
}
QMimeData * mimeData = new QMimeData ( ) ;
mimeData - > setUrls ( QList < QUrl > ( ) < < url ) ;
mimeData - > setText ( url . toString ( ) ) ;
QDrag * drag = new QDrag ( this ) ;
drag - > setMimeData ( mimeData ) ;
QIcon icon = action - > icon ( ) ;
drag - > setPixmap ( icon . pixmap ( IconSize ( KIconLoader : : Desktop ) ) ) ;
d - > mousePressPos = QPoint ( ) ;
Qt : : DropAction dropAction = drag - > exec ( ) ;
Q_UNUSED ( dropAction ) ;
return true ;
}
break ;
}
case QEvent : : MouseButtonPress : {
QMouseEvent * mouseEvent = static_cast < QMouseEvent * > ( event ) ;
QMenu * watchedMenu = qobject_cast < QMenu * > ( watched ) ;
if ( watchedMenu ) {
d - > mousePressPos = mouseEvent - > pos ( ) ;
}
break ;
}
case QEvent : : MouseButtonRelease : {
QMenu * watchedMenu = qobject_cast < QMenu * > ( watched ) ;
if ( watchedMenu ) {
d - > mousePressPos = QPoint ( ) ;
}
break ;
}
case QEvent : : Hide : {
emit afterBeingHidden ( ) ;
break ;
}
case QEvent : : ToolTip : {
bool hide = true ;
if ( ( d - > formattype = = Name ) | | ( d - > formattype = = Description ) ) {
QHelpEvent * helpEvent = static_cast < QHelpEvent * > ( event ) ;
QMenu * watchedMenu = qobject_cast < QMenu * > ( watched ) ;
if ( watchedMenu & & watchedMenu - > activeAction ( ) ) {
QString toolTip = watchedMenu - > activeAction ( ) - > toolTip ( ) ;
if ( ( toolTip ! = watchedMenu - > activeAction ( ) - > text ( ) ) & & ( watchedMenu - > activeAction ( ) - > menu ( ) = = 0 ) ) {
QToolTip : : showText ( helpEvent - > globalPos ( ) , toolTip ) ;
hide = false ;
}
}
}
if ( hide ) {
QToolTip : : hideText ( ) ;
event - > ignore ( ) ;
}
break ;
}
default :
break ;
}
return KMenu : : eventFilter ( watched , event ) ;
}
void MenuView : : addModel ( QAbstractItemModel * model , MenuView : : ModelOptions options , const QString & relativePath )
{
QString title = model - > headerData ( 0 , Qt : : Horizontal , Qt : : DisplayRole ) . toString ( ) ;
QAction * header = addTitle ( title ) ;
header - > setVisible ( false ) ;
d - > modelsHeader . insert ( model , header ) ;
d - > models . append ( model ) ;
if ( options & MergeFirstLevel ) {
const int count = model - > rowCount ( ) ;
for ( int row = 0 ; row < count ; + + row ) {
QModelIndex index = model - > index ( row , 0 , QModelIndex ( ) ) ;
Q_ASSERT ( index . isValid ( ) ) ;
const QString title = index . data ( Qt : : DisplayRole ) . value < QString > ( ) ;
if ( count > 1 & & ! title . isEmpty ( ) & & model - > rowCount ( index ) > 0 ) {
addTitle ( title ) ;
}
model - > blockSignals ( true ) ;
model - > setData ( index , qVariantFromValue ( this - > menuAction ( ) ) , Private : : ActionRole ) ;
model - > blockSignals ( false ) ;
d - > buildBranch ( this , model , index ) ;
}
} else {
QModelIndex root ;
if ( ! relativePath . isEmpty ( ) ) {
root = d - > findByRelPath ( model , root , relativePath ) ;
}
d - > buildBranch ( this , model , root ) ;
}
connect ( model , SIGNAL ( rowsInserted ( QModelIndex , int , int ) ) , this , SLOT ( rowsInserted ( QModelIndex , int , int ) ) ) ;
connect ( model , SIGNAL ( rowsAboutToBeRemoved ( QModelIndex , int , int ) ) , this , SLOT ( rowsAboutToBeRemoved ( QModelIndex , int , int ) ) ) ;
connect ( model , SIGNAL ( dataChanged ( QModelIndex , QModelIndex ) ) , this , SLOT ( dataChanged ( QModelIndex , QModelIndex ) ) ) ;
connect ( model , SIGNAL ( modelReset ( ) ) , this , SLOT ( modelReset ( ) ) ) ;
}
void MenuView : : addItem ( QStandardItem * item )
{
QAction * action = new QAction ( item - > icon ( ) , item - > text ( ) , this ) ;
KUrl url ( item - > data ( Kickoff : : UrlRole ) . toString ( ) ) ;
Q_ASSERT ( url . isValid ( ) ) ;
action - > setData ( url ) ;
addAction ( action ) ;
d - > items < < item ;
}
UrlItemLauncher * MenuView : : launcher ( ) const
{
return d - > launcher ;
}
QModelIndex MenuView : : indexForAction ( QAction * action ) const
{
Q_ASSERT ( action ! = 0 ) ;
QPersistentModelIndex index = action - > data ( ) . value < QPersistentModelIndex > ( ) ;
return index ;
}
QAction * MenuView : : actionForIndex ( const QModelIndex & index ) const
{
if ( ! index . isValid ( ) ) {
return this - > menuAction ( ) ;
}
const QAbstractItemModel * model = index . model ( ) ;
Q_ASSERT ( model ) ;
QVariant v = model - > data ( index , Private : : ActionRole ) ;
Q_ASSERT ( v . isValid ( ) ) ;
QAction * a = v . value < QAction * > ( ) ;
Q_ASSERT ( a ) ;
return a ;
}
bool MenuView : : isValidIndex ( const QModelIndex & index ) const
{
QVariant v = ( index . isValid ( ) & & index . model ( ) ) ? index . model ( ) - > data ( index , Private : : ActionRole ) : QVariant ( ) ;
return v . isValid ( ) & & v . value < QAction * > ( ) ;
}
void MenuView : : rowsInserted ( const QModelIndex & parent , int start , int end )
{
kDebug ( ) < < start < < end ;
Q_ASSERT ( parent . isValid ( ) ) ;
Q_ASSERT ( parent . model ( ) ) ;
//Q_ASSERT( ! isValidIndex(parent) );
QMenu * menu = isValidIndex ( parent ) ? actionForIndex ( parent ) - > menu ( ) : this ;
QAbstractItemModel * model = const_cast < QAbstractItemModel * > ( parent . model ( ) ) ;
if ( ! model ) {
return ;
}
QList < QAction * > newActions ;
for ( int row = start ; row < = end ; row + + ) {
QModelIndex index = model - > index ( row , d - > column , parent ) ;
QAction * newAction = d - > createActionForIndex ( model , index , menu ) ;
kDebug ( ) < < " new action= " < < newAction - > text ( ) ;
newActions < < newAction ;
}
int lastidx = - 1 , offset = - 1 ;
for ( int i = 0 ; i < menu - > actions ( ) . count ( ) ; + + i ) {
QAction * action = menu - > actions ( ) [ i ] ;
Q_ASSERT ( action ) ;
QModelIndex index = indexForAction ( action ) ;
if ( index . isValid ( ) & & index . model ( ) = = model ) {
lastidx = i ;
if ( + + offset = = start )
break ;
}
}
if ( lastidx < 0 & & d - > modelsHeader . contains ( model ) ) {
QAction * header = d - > modelsHeader [ model ] ;
lastidx = menu - > actions ( ) . indexOf ( header ) ;
}
if ( lastidx > = 0 ) {
if ( offset < start ) {
lastidx + + ; // insert action the item right after the last valid index
}
if ( lastidx < menu - > actions ( ) . count ( ) ) {
menu - > insertActions ( menu - > actions ( ) [ lastidx ] , newActions ) ;
} else {
lastidx = - 1 ;
}
}
if ( lastidx < 0 ) {
// just append the action
menu - > addActions ( newActions ) ;
}
}
void MenuView : : rowsAboutToBeRemoved ( const QModelIndex & parent , int start , int end )
{
kDebug ( ) < < start < < end ;
Q_UNUSED ( parent ) ;
Q_UNUSED ( start ) ;
Q_UNUSED ( end ) ;
modelReset ( ) ;
}
void MenuView : : dataChanged ( const QModelIndex & topLeft , const QModelIndex & bottomRight )
{
QAbstractItemModel * model = const_cast < QAbstractItemModel * > ( topLeft . model ( ) ) ;
Q_ASSERT ( model ) ;
//Q_ASSERT( ! isValidIndex(topLeft) );
QMenu * menu = isValidIndex ( topLeft ) ? actionForIndex ( topLeft ) - > menu ( ) : this ;
QList < QAction * > actions = menu - > actions ( ) ;
Q_ASSERT ( bottomRight . row ( ) < actions . count ( ) ) ;
for ( int row = topLeft . row ( ) ; row < = bottomRight . row ( ) & & row < actions . count ( ) ; + + row ) {
QModelIndex index = model - > index ( row , d - > column , topLeft . parent ( ) ) ;
kDebug ( ) < < row < < index . data ( Qt : : DisplayRole ) . value < QString > ( ) ;
updateAction ( model , actions [ row ] , index ) ;
}
}
void MenuView : : modelReset ( )
{
kDebug ( ) ;
deleteLater ( ) ; // we need to force clearance of the menu and rebuild from scratch
}
void MenuView : : setColumn ( int column )
{
d - > column = column ;
modelReset ( ) ;
}
int MenuView : : column ( ) const
{
return d - > column ;
}
MenuView : : FormatType MenuView : : formatType ( ) const
{
return d - > formattype ;
}
void MenuView : : setFormatType ( MenuView : : FormatType formattype )
{
d - > formattype = formattype ;
}
void MenuView : : setModelTitleVisible ( QAbstractItemModel * model , bool visible )
{
if ( d - > modelsHeader . contains ( model ) ) {
QAction * header = d - > modelsHeader [ model ] ;
header - > setVisible ( visible ) ;
}
}
void MenuView : : actionTriggered ( QAction * action )
{
KUrl url = action - > data ( ) . value < KUrl > ( ) ;
if ( url . isValid ( ) ) {
d - > launcher - > openUrl ( url . url ( ) ) ;
} else {
QModelIndex index = indexForAction ( action ) ;
if ( index . isValid ( ) ) {
d - > launcher - > openItem ( index ) ;
} else {
kWarning ( ) < < " Invalid action objectName= " < < action - > objectName ( ) < < " text= " < < action - > text ( ) < < " parent= " < < ( action - > parent ( ) ? action - > parent ( ) - > metaObject ( ) - > className ( ) : " NULL " ) ;
}
}
}
void MenuView : : contextMenuRequested ( const QPoint & pos )
{
KMenu * menu = qobject_cast < KMenu * > ( sender ( ) ) ;
emit customContextMenuRequested ( menu , pos ) ;
}
2015-02-27 09:28:46 +00:00
# include "moc_menuview.cpp"