|
@@ -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);
|
|
|
+}
|