Browse Source

ENH: Show/Hide actions that are separator/menus/empty in ctkActionsWidget

Julien Finet 15 years ago
parent
commit
de02c45539
2 changed files with 275 additions and 30 deletions
  1. 210 24
      Libs/Widgets/ctkActionsWidget.cpp
  2. 65 6
      Libs/Widgets/ctkActionsWidget.h

+ 210 - 24
Libs/Widgets/ctkActionsWidget.cpp

@@ -21,8 +21,11 @@
 // Qt includes
 #include <QAction>
 #include <QDebug>
+#include <QPainter>
+#include <QSortFilterProxyModel>
 #include <QStandardItem>
 #include <QStandardItemModel>
+#include <QTextDocument>
 #include <QTreeView>
 #include <QVBoxLayout>
 
@@ -37,16 +40,17 @@ public:
   void setupUI();
   void updateItems(QList<QStandardItem*>& items, QAction* action);
 
-  QStandardItemModel* ActionsModel;
-  QTreeView*          ActionsTreeView;
-  bool                ShowActionsWithNoShortcut;
+  QStandardItemModel*    ActionsModel;
+  ctkSortFilterActionsProxyModel* SortFilterActionsProxyModel;
+  QTreeView*             ActionsTreeView;
 };
 
 //-----------------------------------------------------------------------------
 ctkActionsWidgetPrivate::ctkActionsWidgetPrivate()
 {
+  this->ActionsModel = 0;
+  this->SortFilterActionsProxyModel = 0;
   this->ActionsTreeView = 0;
-  this->ShowActionsWithNoShortcut = false;
 }
 
 //-----------------------------------------------------------------------------
@@ -60,12 +64,16 @@ void ctkActionsWidgetPrivate::setupUI()
   headers << "Action" << "Shortcut" << "Context" << "Details";
   this->ActionsModel->setHorizontalHeaderLabels(headers);
 
+  this->SortFilterActionsProxyModel = new ctkSortFilterActionsProxyModel(p);
+  this->SortFilterActionsProxyModel->setSourceModel(this->ActionsModel);
+
   this->ActionsTreeView = new QTreeView(p);
   QVBoxLayout* layout = new QVBoxLayout(p);
   layout->addWidget(this->ActionsTreeView);
   p->setLayout(layout);
-  this->ActionsTreeView->setModel(this->ActionsModel);
+  this->ActionsTreeView->setModel(this->SortFilterActionsProxyModel);
   this->ActionsTreeView->setAlternatingRowColors(true);
+  //this->ActionsTreeView->setItemDelegate(new ctkRichTextItemDelegate);
 }
 
 //-----------------------------------------------------------------------------
@@ -74,10 +82,16 @@ void ctkActionsWidgetPrivate
 {
   Q_ASSERT(items.size() == 4);
   // Name
-  items[0]->setText(action->text());
-  items[0]->setIcon(action->icon());
+  QString actionText = action->text();
+  if (actionText.indexOf('&') != -1)
+    {
+    actionText = actionText.remove(actionText.indexOf('&'),1);  // remove mnemonic
+    }
+  items[ctkActionsWidget::NameColumn]->setText(actionText);
+  items[ctkActionsWidget::NameColumn]->setIcon(action->icon());
   // Shortcut
-  items[1]->setText(action->shortcut().toString(QKeySequence::NativeText));
+  items[ctkActionsWidget::ShortcutColumn]->setText(
+    action->shortcut().toString(QKeySequence::NativeText));
   // Context
   QString shortcutContext;
   switch (action->shortcutContext())
@@ -91,8 +105,8 @@ void ctkActionsWidgetPrivate
     default:
       shortcutContext = "Application";
     }
-  items[2]->setText(shortcutContext);
-  items[3]->setText(action->toolTip() != action->text()
+  items[ctkActionsWidget::ContextColumn]->setText(shortcutContext);
+  items[ctkActionsWidget::DetailsColumn]->setText(action->toolTip() != actionText
     ? action->toolTip() : QString(""));
 }
 
@@ -125,9 +139,11 @@ void ctkActionsWidget::addAction(QAction* action, const QString& group)
   actionGroupItem->appendRow(actionItems);
   if (expandGroupItem)
     {
+    qDebug() << d->ActionsModel->indexFromItem(actionGroupItem);
     d->ActionsTreeView->expand(
-      d->ActionsModel->indexFromItem(actionGroupItem));
+      d->SortFilterActionsProxyModel->mapFromSource(d->ActionsModel->indexFromItem(actionGroupItem)));
     }
+  d->ActionsTreeView->resizeColumnToContents(0);
   connect(action, SIGNAL(changed()), this, SLOT(updateAction()));
 }
 
@@ -141,6 +157,13 @@ void ctkActionsWidget::addActions(QList<QAction*> actions, const QString& group)
 }
 
 //-----------------------------------------------------------------------------
+void ctkActionsWidget::clear()
+{
+  CTK_D(ctkActionsWidget);
+  d->ActionsModel->clear();
+}
+
+//-----------------------------------------------------------------------------
 QStandardItem* ctkActionsWidget::groupItem(const QString& group)
 {
   CTK_D(ctkActionsWidget);
@@ -156,6 +179,7 @@ QStandardItem* ctkActionsWidget::groupItem(const QString& group)
     return foundGroup[0];
     }
   QStandardItem* groupItem = new QStandardItem(group);
+  groupItem->setFlags(Qt::ItemIsEnabled);
   d->ActionsModel->appendRow(groupItem);
   return groupItem;
 }
@@ -168,28 +192,59 @@ QStandardItemModel* ctkActionsWidget::model()const
 }
 
 //-----------------------------------------------------------------------------
+void ctkActionsWidget::setActionsWithNoShortcutVisible(bool show)
+{
+  CTK_D(ctkActionsWidget);
+  d->SortFilterActionsProxyModel->setActionsWithNoShortcutVisible(show);
+}
+
+//-----------------------------------------------------------------------------
+bool ctkActionsWidget::areActionsWithNoShortcutVisible()const
+{
+  CTK_D(const ctkActionsWidget);
+  return d->SortFilterActionsProxyModel->areActionsWithNoShortcutVisible();
+}
+
+//-----------------------------------------------------------------------------
+void ctkActionsWidget::setMenuActionsVisible(bool show)
+{
+  CTK_D(ctkActionsWidget);
+  d->SortFilterActionsProxyModel->setMenuActionsVisible(show);
+}
+
+//-----------------------------------------------------------------------------
+bool ctkActionsWidget::areMenuActionsVisible()const
+{
+  CTK_D(const ctkActionsWidget);
+  return d->SortFilterActionsProxyModel->areMenuActionsVisible();
+}
+
+//-----------------------------------------------------------------------------
 void ctkActionsWidget::updateAction()
 {
   CTK_D(ctkActionsWidget);
   QAction* action = qobject_cast<QAction*>(this->sender());
   Q_ASSERT(action);
-  QModelIndexList foundAction = 
+  QModelIndexList foundActions =
     d->ActionsModel->match(d->ActionsModel->index(0,0),
     Qt::UserRole + 1, qVariantFromValue(qobject_cast<QObject*>(action)),
-    1, Qt::MatchExactly | Qt::MatchRecursive);
-  Q_ASSERT(foundAction.size());
-  QModelIndex parentIndex = foundAction[0].parent();
-  QStandardItem* parent = parentIndex.isValid()
-    ? d->ActionsModel->itemFromIndex(parentIndex)
-    : d->ActionsModel->invisibleRootItem();
-  int actionRow = foundAction[0].row();
-  Q_ASSERT(actionRow >= 0);
-  QList<QStandardItem*> actionItems;
-  for(int i = 0; i < 4; ++i)
+    -1, Qt::MatchExactly | Qt::MatchRecursive);
+  Q_ASSERT(foundActions.size());
+  foreach (QModelIndex actionIndex, foundActions)
     {
-    actionItems << parent->child(actionRow, i);
+    QModelIndex parentIndex = actionIndex.parent();
+    QStandardItem* parent = parentIndex.isValid()
+      ? d->ActionsModel->itemFromIndex(parentIndex)
+      : d->ActionsModel->invisibleRootItem();
+    int actionRow = actionIndex.row();
+    Q_ASSERT(actionRow >= 0);
+    QList<QStandardItem*> actionItems;
+    for(int i = 0; i < 4; ++i)
+      {
+      actionItems << parent->child(actionRow, i);
+      }
+    d->updateItems(actionItems, action);
     }
-  d->updateItems(actionItems, action);
 }
 
 //-----------------------------------------------------------------------------
@@ -198,3 +253,134 @@ QTreeView* ctkActionsWidget::view()const
   CTK_D(const ctkActionsWidget);
   return d->ActionsTreeView;
 }
+
+//-----------------------------------------------------------------------------
+class ctkSortFilterActionsProxyModelPrivate: public ctkPrivate<ctkSortFilterActionsProxyModel>
+{
+public:
+  ctkSortFilterActionsProxyModelPrivate();
+  bool                   ActionsWithNoShortcutVisible;
+  bool                   MenuActionsVisible;
+};
+
+//-----------------------------------------------------------------------------
+ctkSortFilterActionsProxyModelPrivate::ctkSortFilterActionsProxyModelPrivate()
+{
+  this->ActionsWithNoShortcutVisible = true;
+  this->MenuActionsVisible = true;
+}
+
+//-----------------------------------------------------------------------------
+ctkSortFilterActionsProxyModel::ctkSortFilterActionsProxyModel(QObject* parentObject)
+ :QSortFilterProxyModel(parentObject)
+{
+  CTK_INIT_PRIVATE(ctkSortFilterActionsProxyModel);
+}
+
+//-----------------------------------------------------------------------------
+void ctkSortFilterActionsProxyModel::setActionsWithNoShortcutVisible(bool visible)
+{
+  CTK_D(ctkSortFilterActionsProxyModel);
+  d->ActionsWithNoShortcutVisible = visible;
+  this->invalidateFilter();
+}
+
+//-----------------------------------------------------------------------------
+bool ctkSortFilterActionsProxyModel::areActionsWithNoShortcutVisible()const
+{
+  CTK_D(const ctkSortFilterActionsProxyModel);
+  return d->ActionsWithNoShortcutVisible;
+}
+
+//-----------------------------------------------------------------------------
+void ctkSortFilterActionsProxyModel::setMenuActionsVisible(bool visible)
+{
+  CTK_D(ctkSortFilterActionsProxyModel);
+  d->MenuActionsVisible = visible;
+  this->invalidateFilter();
+}
+
+//-----------------------------------------------------------------------------
+bool ctkSortFilterActionsProxyModel::areMenuActionsVisible()const
+{
+  CTK_D(const ctkSortFilterActionsProxyModel);
+  return d->MenuActionsVisible;
+}
+
+//-----------------------------------------------------------------------------
+bool ctkSortFilterActionsProxyModel::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const
+{
+  CTK_D(const ctkSortFilterActionsProxyModel);
+  QStandardItem* shortcutItem = qobject_cast<QStandardItemModel*>(
+    this->sourceModel())->itemFromIndex(
+      source_parent.child(source_row, ctkActionsWidget::ShortcutColumn));
+  QAction* action = shortcutItem ?
+    qobject_cast<QAction*>(shortcutItem->data().value<QObject*>()) : 0;
+  if (!action)
+    {
+    return true;
+    }
+  if (action->isSeparator())
+    {
+    return false;
+    }
+  if (action->text().isEmpty())
+    {// not sure what the empty text actions are
+    return false;
+    }
+  if (!d->ActionsWithNoShortcutVisible && shortcutItem->text().isEmpty())
+    {
+    return false;
+    }
+  if (!d->MenuActionsVisible && action->menu())
+    {
+    return false;
+    }
+  return true;
+}
+
+//---------------------------------------------------------------------------
+void ctkRichTextItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option,
+                                    const QModelIndex &index) const
+{
+  QStyleOptionViewItemV4 options = option;
+  initStyleOption(&options, index);
+  if (! Qt::mightBeRichText(options.text))
+    {
+    this->QStyledItemDelegate::paint(painter, option, index);
+    return;
+    }
+
+  painter->save();
+
+  QTextDocument doc;
+  doc.setHtml(options.text);
+
+  /* Call this to get the focus rect and selection background. */
+  options.text = "";
+  options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget);
+
+  /* Draw using our rich text document. */
+  painter->translate(options.rect.left(), options.rect.top());
+  QRect clip(0, 0, options.rect.width(), options.rect.height());
+  doc.drawContents(painter, clip);
+
+  painter->restore();
+}
+
+//---------------------------------------------------------------------------
+QSize ctkRichTextItemDelegate::sizeHint(const QStyleOptionViewItem & option,
+                                        const QModelIndex & index)const
+{
+  QStyleOptionViewItemV4 options = option;
+  initStyleOption(&options, index);
+  if (! Qt::mightBeRichText(options.text))
+    {
+    return this->QStyledItemDelegate::sizeHint(option, index);;
+    }
+
+  QTextDocument doc;
+  doc.setHtml(options.text);
+  doc.setTextWidth(options.rect.width());
+  return QSize(doc.idealWidth(), doc.size().height());
+}

+ 65 - 6
Libs/Widgets/ctkActionsWidget.h

@@ -22,40 +22,99 @@
 #define __ctkActionsWidget_h
 
 // Qt includes
+#include <QStyledItemDelegate>
+#include <QSortFilterProxyModel>
 #include <QWidget>
 
 // CTK includes
 #include "ctkPimpl.h"
 #include "CTKWidgetsExport.h"
 class ctkActionsWidgetPrivate;
+class ctkSortFilterActionsProxyModelPrivate;
 
 class QAction;
 class QStandardItemModel;
 class QStandardItem;
 class QTreeView;
 
+//---------------------------------------------------------------------------
 class CTK_WIDGETS_EXPORT ctkActionsWidget : public QWidget
 {
   Q_OBJECT
-  //Q_PROPERTY(bool showActionsWithNoShortcut read showActionsWithNoShortcut write setShowActionsWithNoShortcut)
+  Q_PROPERTY(bool actionsWithNoShortcutVisible READ areActionsWithNoShortcutVisible WRITE setActionsWithNoShortcutVisible)
+  Q_PROPERTY(bool menuActionsVisible READ areMenuActionsVisible WRITE setMenuActionsVisible)
 public:
   explicit ctkActionsWidget(QWidget* parent = 0);
-  
-  void addAction(QAction* action, const QString& category = QString());
-  void addActions(QList<QAction*> actions, const QString& category = QString());
 
+  /// Add an action into a specified group (or at top level if group is empty)
+  /// An action can be added multiple times (in a different group). Once added,
+  /// ctkActionsWidget listens to the QAction and updates the action properties
+  /// TODO: check that the action hasn't been already added into a group
+  void addAction(QAction* action, const QString& group = QString());
+
+  /// Convenient function to add a list of action at once
+  void addActions(QList<QAction*> actions, const QString& group = QString());
+
+  /// Remove all the actions and groups
+  void clear();
+
+  /// Return a pointer on a group item (you probably have no use for it)
   QStandardItem* groupItem(const QString& category);
 
-  //void setShowActionsWithNoShortcut(bool show);
-  //bool showActionsWithNoShortcut()const;
+  /// Show/hide QActions that have an empty shortcut
+  void setActionsWithNoShortcutVisible(bool show);
+  bool areActionsWithNoShortcutVisible()const;
+
+  /// Show/hide QActions that have an empty shortcut
+  void setMenuActionsVisible(bool show);
+  bool areMenuActionsVisible()const;
 
   QStandardItemModel* model()const;
   QTreeView* view()const;
+
 protected slots:
   void updateAction();
 
+protected:
+  enum ActionColumn{
+    NameColumn = 0,
+    ShortcutColumn,
+    ContextColumn,
+    DetailsColumn
+  };
 private:
+  friend class ctkSortFilterActionsProxyModel;
   CTK_DECLARE_PRIVATE(ctkActionsWidget);
 };
 
+//---------------------------------------------------------------------------
+class ctkSortFilterActionsProxyModel : public QSortFilterProxyModel
+{
+  Q_OBJECT
+public:
+  ctkSortFilterActionsProxyModel(QObject* parent=0);
+
+  void setActionsWithNoShortcutVisible(bool);
+  bool areActionsWithNoShortcutVisible()const;
+
+  void setMenuActionsVisible(bool);
+  bool areMenuActionsVisible()const;
+
+protected:
+  bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const;
+private:
+  CTK_DECLARE_PRIVATE(ctkSortFilterActionsProxyModel);
+};
+
+//---------------------------------------------------------------------------
+class ctkRichTextItemDelegate : public QStyledItemDelegate
+{
+  Q_OBJECT
+protected:
+  virtual void paint(QPainter * painter, const QStyleOptionViewItem & option,
+             const QModelIndex & index) const;
+  virtual QSize sizeHint(const QStyleOptionViewItem & option,
+                         const QModelIndex & index)const;
+};
+
 #endif