소스 검색

BUG: Fix item selection in ctkTreeComboBox and resize popup automatically

Julien Finet 14 년 전
부모
커밋
fb3de90dee
2개의 변경된 파일291개의 추가작업 그리고 76개의 파일을 삭제
  1. 290 73
      Libs/Widgets/ctkTreeComboBox.cpp
  2. 1 3
      Libs/Widgets/ctkTreeComboBox.h

+ 290 - 73
Libs/Widgets/ctkTreeComboBox.cpp

@@ -19,9 +19,17 @@
 =========================================================================*/
 
 // Qt includes
+#include <QApplication>
+#include <QDesktopWidget>
 #include <QEvent>
+#include <QHeaderView>
+#include <QKeyEvent>
+#include <QLayout>
+#include <QScrollBar>
+#include <QInputContext>
 #include <QMouseEvent>
 #include <QModelIndex>
+#include <QStack>
 #include <QTreeView>
 #include <QDebug>
 
@@ -33,17 +41,27 @@ class ctkTreeComboBoxPrivate: public ctkPrivate<ctkTreeComboBox>
 {
 public:
   ctkTreeComboBoxPrivate();
+  int computeWidthHint()const;
+
   bool SkipNextHide;
-  bool ResetPopupSize;
   bool RootSet;
+  bool SendCurrentItem;
   QPersistentModelIndex Root;
 };
 
+// -------------------------------------------------------------------------
 ctkTreeComboBoxPrivate::ctkTreeComboBoxPrivate()
 {
   this->SkipNextHide = false;
-  this->ResetPopupSize = false;
   this->RootSet = false;
+  this->SendCurrentItem = false;
+}
+
+// -------------------------------------------------------------------------
+int ctkTreeComboBoxPrivate::computeWidthHint()const
+{
+  CTK_P(const ctkTreeComboBox);
+  return p->view()->sizeHintForColumn(p->modelColumn());
 }
 
 // -------------------------------------------------------------------------
@@ -57,70 +75,72 @@ ctkTreeComboBox::ctkTreeComboBox(QWidget* _parent):Superclass(_parent)
   // so that our eventFilter will be called first
   this->view()->viewport()->installEventFilter(this);
   connect(treeView, SIGNAL(collapsed(const QModelIndex&)),
-          this, SLOT(onCollapsed(const QModelIndex&)));
+          this, SLOT(resizePopup()));
   connect(treeView, SIGNAL(expanded(const QModelIndex&)),
-          this, SLOT(onExpanded(const QModelIndex&)));
+          this, SLOT(resizePopup()));
 }
 
 // -------------------------------------------------------------------------
 bool ctkTreeComboBox::eventFilter(QObject* object, QEvent* _event)
 {
   CTK_D(ctkTreeComboBox);
-  
+  Q_UNUSED(object);
   bool res = false;
-  if (_event->type() == QEvent::MouseButtonRelease && 
-      object == this->view()->viewport())
+  d->SendCurrentItem = false;
+  switch (_event->type())
     {
-    QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(_event); 
-    QModelIndex index = this->view()->indexAt(mouseEvent->pos());
-    // do we click the branch (+ or -) or the item itself ?
-    if (this->view()->model()->hasChildren(index) && 
-        (index.flags() & Qt::ItemIsSelectable) &&
-        !this->view()->visualRect(index).contains(mouseEvent->pos()))
-      {//qDebug() << "Set skip on";
-      // if the branch is clicked, then we don't want to close the 
-      // popup. (we don't want to select the item, just expand it.)
-      // of course, all that doesn't apply with unselectable items, as
-      // they won't close the popup.
-      d->SkipNextHide = true;
-      }
+    default:
+      break;
+    case QEvent::ShortcutOverride:
+      switch (static_cast<QKeyEvent*>(_event)->key())
+        {
+        case Qt::Key_Enter:
+        case Qt::Key_Return:
+        case Qt::Key_Select:
+          d->SendCurrentItem = true;
+          break;
+        default:
+          break;
+        }
+      break;
+    case QEvent::MouseButtonRelease:
+      QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(_event); 
+      QModelIndex index = this->view()->indexAt(mouseEvent->pos());
+      // do we click the branch (+ or -) or the item itself ?
+      if (this->view()->model()->hasChildren(index) && 
+          (index.flags() & Qt::ItemIsSelectable) &&
+          !this->view()->visualRect(index).contains(mouseEvent->pos()))
+        {//qDebug() << "Set skip on";
+        // if the branch is clicked, then we don't want to close the 
+        // popup. (we don't want to select the item, just expand it.)
+        // of course, all that doesn't apply with unselectable items, as
+        // they won't close the popup.
+        d->SkipNextHide = true;
+        }
 
-    // we want to get rid of an odd behavior. 
-    // If the user highlight a selectable item and then 
-    // click on the branch of an unselectable item while keeping the 
-    // previous selection. The popup would be normally closed in that
-    // case. We don't want that.
-    if ( this->view()->model()->hasChildren(index) && 
-        !(index.flags() & Qt::ItemIsSelectable) &&         
-        !this->view()->visualRect(index).contains(mouseEvent->pos()))
-      {//qDebug() << "eat";
-      // eat the event, don't go to the QComboBox event filters.
-      res = true;
-      }
+      // we want to get rid of an odd behavior. 
+      // If the user highlight a selectable item and then 
+      // click on the branch of an unselectable item while keeping the 
+      // previous selection. The popup would be normally closed in that
+      // case. We don't want that.
+      if ( this->view()->model()->hasChildren(index) && 
+           !(index.flags() & Qt::ItemIsSelectable) &&         
+           !this->view()->visualRect(index).contains(mouseEvent->pos()))
+        {//qDebug() << "eat";
+        // eat the event, don't go to the QComboBox event filters.
+        res = true;
+        }
 
-    if (d->ResetPopupSize)
-      {
-      d->ResetPopupSize = false;
-      //this->QComboBox::showPopup();
-      }
+      d->SendCurrentItem = this->view()->rect().contains(mouseEvent->pos()) &&
+        this->view()->currentIndex().isValid() &&
+        (this->view()->currentIndex().flags() & Qt::ItemIsEnabled) &&
+        (this->view()->currentIndex().flags() & Qt::ItemIsSelectable);
+      break;
     }
   return res;
 }
 
 // -------------------------------------------------------------------------
-void ctkTreeComboBox::showPopup()
-{
-  CTK_D(ctkTreeComboBox);
-  if (!d->RootSet)
-    {
-    d->Root = this->rootModelIndex();
-    d->RootSet = true;
-    }
-  this->setRootModelIndex(QModelIndex(d->Root));
-  this->QComboBox::showPopup();
-}
-
-// -------------------------------------------------------------------------
 void ctkTreeComboBox::hidePopup()
 {
   CTK_D(ctkTreeComboBox);
@@ -134,38 +154,24 @@ void ctkTreeComboBox::hidePopup()
     }
   else
     {
-    QModelIndex _currentIndex = this->view()->currentIndex();
+    //QModelIndex _currentIndex = this->view()->currentIndex();
     //qDebug() << "ctkTreeComboBox::hidePopup() " << _currentIndex << " " << _currentIndex.row();
     //qDebug() << "before: " << this->currentIndex() << this->view()->currentIndex();
     this->QComboBox::hidePopup();
     //qDebug() << "after: " << this->currentIndex() << this->view()->currentIndex();
-    this->setRootModelIndex(_currentIndex.parent());
-    this->setCurrentIndex(_currentIndex.row());
+    //this->setRootModelIndex(_currentIndex.parent());
+    //this->setCurrentIndex(_currentIndex.row());
+    if (d->SendCurrentItem)
+      {
+      d->SendCurrentItem = false;
+      QKeyEvent event(QEvent::ShortcutOverride, Qt::Key_Enter, Qt::NoModifier);
+      QApplication::sendEvent(this->view(), &event);
+      }
     //qDebug() << "after2: " << this->currentIndex() << this->view()->currentIndex();
     }
 }
 
 // -------------------------------------------------------------------------
-void ctkTreeComboBox::onCollapsed(const QModelIndex& index)
-{
-  CTK_D(ctkTreeComboBox);
-  
-  if (this->view()->currentIndex().parent() == index)
-    {
-    // in the case the current item is a child of the collapsed/expanded item.
-    // we don't want to resize the popup as it would undo the collapsed item.
-    return;
-    }
-  d->ResetPopupSize = true;
-}
-
-// -------------------------------------------------------------------------
-void ctkTreeComboBox::onExpanded(const QModelIndex& /*index*/)
-{
-  ctk_d()->ResetPopupSize = true;
-}
-
-// -------------------------------------------------------------------------
 void ctkTreeComboBox::paintEvent(QPaintEvent *p)
 {
   //qDebug() << __FUNCTION__ << " " << this->currentText() << " " << this->currentIndex() ;
@@ -178,3 +184,214 @@ QTreeView* ctkTreeComboBox::treeView()const
 {
   return qobject_cast<QTreeView*>(this->view());
 }
+// -------------------------------------------------------------------------
+void ctkTreeComboBox::resizePopup()
+{
+  // copied from QComboBox.cpp
+  CTK_D(ctkTreeComboBox);
+
+  QStyle * const style = this->style();
+  QWidget* container = qobject_cast<QWidget*>(this->view()->parent());
+
+  QStyleOptionComboBox opt;
+  this->initStyleOption(&opt);
+  QRect listRect(style->subControlRect(QStyle::CC_ComboBox, &opt,
+                                       QStyle::SC_ComboBoxListBoxPopup, this));
+  QRect screen = QApplication::desktop()->availableGeometry(
+    QApplication::desktop()->screenNumber(this));
+  QPoint below = this->mapToGlobal(listRect.bottomLeft());
+  int belowHeight = screen.bottom() - below.y();
+  QPoint above = this->mapToGlobal(listRect.topLeft());
+  int aboveHeight = above.y() - screen.y();
+  bool boundToScreen = !this->window()->testAttribute(Qt::WA_DontShowOnScreen);
+
+  const bool usePopup = style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this);
+    {
+    int listHeight = 0;
+    int count = 0;
+    QStack<QModelIndex> toCheck;
+    toCheck.push(this->view()->rootIndex());
+#ifndef QT_NO_TREEVIEW
+    QTreeView *treeView = qobject_cast<QTreeView*>(this->view());
+    if (treeView && treeView->header() && !treeView->header()->isHidden())
+      listHeight += treeView->header()->height();
+#endif
+    while (!toCheck.isEmpty())
+      {
+      QModelIndex parent = toCheck.pop();
+      for (int i = 0; i < this->model()->rowCount(parent); ++i)
+        {
+        QModelIndex idx = this->model()->index(i, this->modelColumn(), parent);
+        if (!idx.isValid())
+          {
+          continue;
+          }
+        listHeight += this->view()->visualRect(idx).height(); /* + container->spacing() */;
+#ifndef QT_NO_TREEVIEW
+        if (this->model()->hasChildren(idx) && treeView && treeView->isExpanded(idx))
+          {
+          toCheck.push(idx);
+          }
+#endif
+        ++count;
+        if (!usePopup && count > this->maxVisibleItems())
+          {
+          toCheck.clear();
+          break;
+          }
+        }
+      }
+    listRect.setHeight(listHeight);
+    }
+      {
+      // add the spacing for the grid on the top and the bottom;
+      int heightMargin = 0;//2*container->spacing();
+
+      // add the frame of the container
+      int marginTop, marginBottom;
+      container->getContentsMargins(0, &marginTop, 0, &marginBottom);
+      heightMargin += marginTop + marginBottom;
+
+      //add the frame of the view
+      this->view()->getContentsMargins(0, &marginTop, 0, &marginBottom);
+      //marginTop += static_cast<QAbstractScrollAreaPrivate *>(QObjectPrivate::get(this->view()))->top;
+      //marginBottom += static_cast<QAbstractScrollAreaPrivate *>(QObjectPrivate::get(this->view()))->bottom;
+      heightMargin += marginTop + marginBottom;
+
+      listRect.setHeight(listRect.height() + heightMargin);
+      }
+
+      // Add space for margin at top and bottom if the style wants it.
+      if (usePopup)
+        {
+        listRect.setHeight(listRect.height() + style->pixelMetric(QStyle::PM_MenuVMargin, &opt, this) * 2);
+        }
+
+      // Make sure the popup is wide enough to display its contents.
+      if (usePopup)
+        {
+        const int diff = d->computeWidthHint() - this->width();
+        if (diff > 0)
+          {
+          listRect.setWidth(listRect.width() + diff);
+          }
+        }
+
+      //we need to activate the layout to make sure the min/maximum size are set when the widget was not yet show
+      container->layout()->activate();
+      //takes account of the minimum/maximum size of the container
+      listRect.setSize( listRect.size().expandedTo(container->minimumSize())
+                        .boundedTo(container->maximumSize()));
+
+      // make sure the widget fits on screen
+      if (boundToScreen)
+        {
+        if (listRect.width() > screen.width() )
+          {
+          listRect.setWidth(screen.width());
+          }
+        if (this->mapToGlobal(listRect.bottomRight()).x() > screen.right())
+          {
+          below.setX(screen.x() + screen.width() - listRect.width());
+          above.setX(screen.x() + screen.width() - listRect.width());
+          }
+        if (this->mapToGlobal(listRect.topLeft()).x() < screen.x() )
+          {
+          below.setX(screen.x());
+          above.setX(screen.x());
+          }
+        }
+
+      if (usePopup)
+        {
+        // Position horizontally.
+        listRect.moveLeft(above.x());
+
+#ifndef Q_WS_S60
+        // Position vertically so the curently selected item lines up
+        // with the combo box.
+        const QRect currentItemRect = this->view()->visualRect(this->view()->currentIndex());
+        const int offset = listRect.top() - currentItemRect.top();
+        listRect.moveTop(above.y() + offset - listRect.top());
+#endif
+
+      // Clamp the listRect height and vertical position so we don't expand outside the
+      // available screen geometry.This may override the vertical position, but it is more
+      // important to show as much as possible of the popup.
+        const int height = !boundToScreen ? listRect.height() : qMin(listRect.height(), screen.height());
+#ifdef Q_WS_S60
+        //popup needs to be stretched with screen minimum dimension
+        listRect.setHeight(qMin(screen.height(), screen.width()));
+#else
+        listRect.setHeight(height);
+#endif
+
+        if (boundToScreen)
+          {
+          if (listRect.top() < screen.top())
+            {
+            listRect.moveTop(screen.top());
+            }
+          if (listRect.bottom() > screen.bottom())
+            {
+            listRect.moveBottom(screen.bottom());
+            }
+          }
+#ifdef Q_WS_S60
+        if (screen.width() < screen.height())
+          {
+          // in portait, menu should be positioned above softkeys
+          listRect.moveBottom(screen.bottom());
+          }
+        else
+          {
+          TRect staConTopRect = TRect();
+          AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EStaconTop, staConTopRect);
+          listRect.setWidth(listRect.height());
+          //by default popup is centered on screen in landscape
+          listRect.moveCenter(screen.center());
+          if (staConTopRect.IsEmpty())
+            {
+            // landscape without stacon, menu should be at the right
+            (opt.direction == Qt::LeftToRight) ? listRect.setRight(screen.right()) :
+              listRect.setLeft(screen.left());
+            }
+          }
+#endif
+        }
+      else if (!boundToScreen || listRect.height() <= belowHeight)
+        {
+        listRect.moveTopLeft(below);
+        }
+      else if (listRect.height() <= aboveHeight)
+        {
+        listRect.moveBottomLeft(above);
+        }
+      else if (belowHeight >= aboveHeight)
+        {
+        listRect.setHeight(belowHeight);
+        listRect.moveTopLeft(below);
+        }
+      else
+        {
+        listRect.setHeight(aboveHeight);
+        listRect.moveBottomLeft(above);
+        }
+
+#ifndef QT_NO_IM
+      if (QInputContext *qic = this->inputContext())
+        {
+        qic->reset();
+        }
+#endif
+      QScrollBar *sb = this->view()->horizontalScrollBar();
+      Qt::ScrollBarPolicy policy = this->view()->horizontalScrollBarPolicy();
+      bool needHorizontalScrollBar =
+        (policy == Qt::ScrollBarAsNeeded || policy == Qt::ScrollBarAlwaysOn)
+        && sb->minimum() < sb->maximum();
+      if (needHorizontalScrollBar)
+        {
+        listRect.adjust(0, 0, 0, sb->height());
+        }
+      container->setGeometry(listRect);
+}

+ 1 - 3
Libs/Widgets/ctkTreeComboBox.h

@@ -55,7 +55,6 @@ public:
   virtual ~ctkTreeComboBox(){}
 
   virtual bool eventFilter(QObject* object, QEvent* event);
-  virtual void showPopup();
   virtual void hidePopup();
   
   /// ctkTreeComboBox uses a QTreeView for its model view. treeView() is a
@@ -67,8 +66,7 @@ protected:
   virtual void paintEvent(QPaintEvent*);
   
 protected slots:
-  void onExpanded(const QModelIndex&);
-  void onCollapsed(const QModelIndex&);
+  void resizePopup();
   
 private:
   CTK_DECLARE_PRIVATE(ctkTreeComboBox);