Commit 8aa41aea authored by Lefebvre, Jordan's avatar Lefebvre, Jordan
Browse files

WIP: Working on navigation widget.

parent 51983682
Pipeline #14502 passed with stages
in 7 minutes and 32 seconds
......@@ -44,10 +44,16 @@ IF(USE_QT4)
ELSE()
SET(HEADERS
${HEADERS}
numberpadwidget.hh)
numberpadwidget.hh
navigationitem.hh
navigationwidget.hh
)
SET(SOURCES
${SOURCES}
numberpadwidget.cc)
numberpadwidget.cc
navigationitem.cc
navigationwidget.cc
)
QT5_WRAP_CPP(MOC_FILES
${HEADERS}
)
......
......@@ -21,9 +21,18 @@ ELSE()
QT5_WRAP_CPP(QMOC_FILES3
radixnumberpadwidget.hh
)
QT5_WRAP_CPP(QMOC_FILES4
radixnavigationwidget.hh
)
TRIBITS_ADD_EXECUTABLE(radixnumberpadwidget
NOEXEPREFIX
SOURCES radixnumberpadwidget.cc ${QMOC_FILES3}
radixnumberpadwidget.hh
)
TRIBITS_ADD_EXECUTABLE(radixnavigationwidget
NOEXEPREFIX
SOURCES radixnavigationwidget.cc ${QMOC_FILES4}
radixnavigationwidget.hh
)
ENDIF()
TRIBITS_ADD_EXECUTABLE(radixtabwidget
......
/*
* @file: mainwindow.cpp
* @author: Jordan P. Lefebvre, lefebvrejp@ornl.gov
*
* Created on May 31, 2016, 10:52 PM
*/
#include "radixnavigationwidget.hh"
#include <QApplication>
#include <QGridLayout>
#include <QLabel>
#include <limits>
#include "radixwidgets/navigationwidget.hh"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setGeometry(400, 250, 542, 390);
mWidget = new radix::NavigationWidget(this);
setCentralWidget(mWidget);
}
MainWindow::~MainWindow() {}
/******************************************************************************/
/******************************** MAIN PROGRAM ********************************/
/******************************************************************************/
int main(int argc, char **argv)
{
QApplication app(argc, argv);
MainWindow mainWindow;
mainWindow.show();
mainWindow.raise();
return app.exec();
}
#ifndef RADIX_RADIXWIDGETS_EXAMPLE_RADIXNAVIGATIONWIDGET_HH_
#define RADIX_RADIXWIDGETS_EXAMPLE_RADIXNAVIGATIONWIDGET_HH_
#include <QMainWindow>
#include <QWidget>
#include "radixwidgets/navigationwidget.hh"
namespace Ui
{
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
radix::NavigationWidget *mWidget;
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
};
#endif // RADIX_RADIXWIDGETS_EXAMPLE_RADIXNAVIGATIONWIDGET_HH_
#include "radixwidgets/navigationitem.hh"
#include "radixbug/bug.hh"
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QMenu>
#include <QSignalMapper>
namespace radix
{
class NavigationItem::PImpl : public QObject
{
};
NavigationItem::NavigationItem()
: QObject()
, QStandardItem()
, p(new PImpl(), [](PImpl *impl) { delete impl; })
{
}
void NavigationItem::addActions(QMenu &m) const {}
} // namespace radix
#ifndef RADIX_RADIXWIDGETS_NAVIGATIONITEM_HH_
#define RADIX_RADIXWIDGETS_NAVIGATIONITEM_HH_
#include <QStandardItem>
#include <memory>
QT_BEGIN_NAMESPACE
class QAction;
class QMenu;
QT_END_NAMESPACE
namespace radix
{
class NavigationItem : public QObject, public QStandardItem
{
Q_OBJECT
public:
virtual void addActions(QMenu &m) const;
public slots:
virtual void activate()
{ /**empty*/
}
virtual void select()
{ /**empty*/
}
protected:
NavigationItem();
private:
class PImpl;
std::unique_ptr<PImpl, void (*)(PImpl *)> p;
};
} // namespace radix
#endif
#include "radixwidgets/navigationwidget.hh"
#include "radixbug/bug.hh"
#include "radixwidgets/navigationitem.hh"
#include <QClipboard>
#include <QEvent>
#include <QFileDialog>
#include <QFileInfo>
#include <QHeaderView>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QShortcut>
#include <QStandardItemModel>
#include <QTextDocument>
#include <QToolTip>
#include <QTreeView>
#include <QVBoxLayout>
#include <QtDebug>
namespace radix
{
ProNavDelegate::ProNavDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{
}
bool ProNavDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view,
const QStyleOptionViewItem& option,
const QModelIndex& index)
{
// event, view, and index must be valid
if (event == nullptr || view == nullptr || !index.isValid())
{
return false;
}
// determine what to do based on the event type
switch (event->type())
{
// displaying/hiding a tooltip
case QEvent::ToolTip:
{
// index's visual rect, and view's size
auto rect = view->visualRect(index);
auto size = view->size();
// move rect and resize based on size hint
rect.moveTo(view->viewport()->mapTo(view, rect.topLeft()));
rect.setSize(sizeHint(option, index));
// size hint rect is cut off by the view, show tooltip
if (rect.right() > size.width())
{
// item text is cut off, so start with the item text as the tooltip
auto tooltip = index.data(Qt::DisplayRole).toString();
// get specified tooltip
auto original = index.data(Qt::ToolTipRole);
// tooltip specified, tack it on the end
if (original.isValid())
{
tooltip.append(QString(" (%1)").arg(original.toString()));
}
QToolTip::showText(event->globalPos(), tooltip, view);
return true;
}
// full item is visible, so call base implementation
if (QStyledItemDelegate::helpEvent(event, view, option, index))
{
return true;
}
QToolTip::hideText();
return false;
}
default:
break;
}
return QStyledItemDelegate::helpEvent(event, view, option, index);
}
void ProNavDelegate::paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
// default implementation
QStyledItemDelegate::paint(painter, option, index);
}
QSize ProNavDelegate::sizeHint(const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
// default size
auto s = QStyledItemDelegate::sizeHint(option, index);
return s;
}
class NavigationItemSortFilterProxyModel::PImpl
{
public:
QHash<QModelIndex, bool> seen;
};
NavigationItemSortFilterProxyModel::NavigationItemSortFilterProxyModel(
QObject* parent)
: QSortFilterProxyModel(parent)
, p(new PImpl(), [](PImpl* impl) { delete impl; })
{
}
bool NavigationItemSortFilterProxyModel::accepts(const QModelIndex& index) const
{
const QString& text = index.data().toString();
const QRegExp& regex = filterRegExp();
bool accept = text.contains(regex);
for (int i = 0, ie = sourceModel()->rowCount(index); i < ie; i++)
{
if (accepts(sourceModel()->index(i, 0, index)))
{
accept = true;
break;
}
}
p->seen[index] = accept;
return accept;
}
void NavigationItemSortFilterProxyModel::clearCache() { p->seen.clear(); }
bool NavigationItemSortFilterProxyModel::filterAcceptsRow(
int source_row, const QModelIndex& source_parent) const
{
const QModelIndex& index = sourceModel()->index(source_row, 0, source_parent);
if (p->seen.contains(index))
{
return p->seen.value(index);
}
return accepts(index);
}
class NavigationWidget::PImpl
{
public:
QLineEdit* filter;
QStandardItemModel* navModel;
QTreeView* navTree;
NavigationItemSortFilterProxyModel* proxyModel;
};
NavigationWidget::NavigationWidget(QWidget* parent)
: QWidget(parent)
, p(new PImpl(), [](PImpl* impl) { delete impl; })
{
initMembers();
initLayout();
}
void NavigationWidget::activateItem()
{
// selected indices
const auto& selected = p->navTree->selectionModel()->selectedIndexes();
// selected at least one index
if (selected.isEmpty())
{
return;
}
// properly map selected items
}
void NavigationWidget::addItem(QStandardItem* item)
{
// perform linear search to determine where to insert the item (alphabetized)
for (auto i = 0, ie = p->navModel->rowCount(); i < ie; i++)
{
// current item and text comparison result
auto si = p->navModel->item(i);
auto c = item->text().compare(si->text(), Qt::CaseInsensitive);
// new item should be inserted before the current item
if (c < 0)
{
p->navModel->insertRow(i, item);
return;
}
}
// just append new item to model
p->navModel->appendRow(item);
}
void NavigationWidget::closeTriggered()
{
// selected item
auto ani = selectedRoot();
radix_tagged_line("closeTriggered");
}
void NavigationWidget::collapse(QStandardItem* item)
{
p->navTree->collapse(p->proxyModel->mapFromSource(item->index()));
}
void NavigationWidget::collapse()
{
// selected item
auto ani = selectedItem();
// no-op
if (ani == nullptr)
{
return;
}
// item's index
const auto& index = ani->index();
const auto& mapped = p->proxyModel->mapFromSource(index);
// current index is expanded, so collapse it
if (p->navTree->isExpanded(mapped))
{
p->navTree->collapse(mapped);
}
// current index is collapsed (or a leaf), so move to its parent
else
{
p->navTree->selectionModel()->setCurrentIndex(
mapped.parent(), QItemSelectionModel::ClearAndSelect);
}
}
bool NavigationWidget::eventFilter(QObject* o, QEvent* e)
{
radix_tagged_line("eventFilter");
return QWidget::eventFilter(o, e);
}
void NavigationWidget::expand(QStandardItem* item)
{
p->navTree->expand(p->proxyModel->mapFromSource(item->index()));
}
void NavigationWidget::filterModel(const QString& pattern)
{
// regex used to filter the model
QRegExp regex(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
// update the model
p->proxyModel->clearCache();
p->proxyModel->setFilterRegExp(regex);
// update the tree
if (pattern.isEmpty())
{
p->navTree->collapseAll();
for (int i = 0, ie = p->navModel->rowCount(); i < ie; i++)
{
p->navTree->expand(
p->proxyModel->mapFromSource(p->navModel->index(i, 0)));
}
}
else
{
p->navTree->expandAll();
}
}
void NavigationWidget::filterSelectionChanged()
{
radix_tagged_line("filterSelectionChanged");
}
void NavigationWidget::initLayout()
{
auto vlayout = new QVBoxLayout(this);
vlayout->addWidget(p->filter);
vlayout->addWidget(p->navTree);
}
void NavigationWidget::initMembers()
{
// allocate
p->filter = new QLineEdit();
p->navModel = new QStandardItemModel();
p->navTree = new QTreeView();
p->proxyModel = new NavigationItemSortFilterProxyModel();
// init properties
p->filter->setPlaceholderText("Filter");
p->navTree->installEventFilter(this);
p->navTree->setContextMenuPolicy(Qt::CustomContextMenu);
p->navTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
p->navTree->setItemDelegate(new ProNavDelegate(p->navTree));
p->navTree->setModel(p->proxyModel);
p->navTree->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
p->navTree->header()->setStretchLastSection(false);
p->navTree->header()->setVisible(false);
p->proxyModel->setSourceModel(p->navModel);
// shortcuts
auto activateEnter = new QShortcut(p->navTree);
auto activateReturn = new QShortcut(p->navTree);
auto collapseBranch = new QShortcut(p->navTree);
activateEnter->setContext(Qt::WidgetShortcut);
activateEnter->setKey(Qt::Key_Enter);
activateReturn->setContext(Qt::WidgetShortcut);
activateReturn->setKey(Qt::Key_Return);
collapseBranch->setContext(Qt::WidgetShortcut);
collapseBranch->setKey(Qt::Key_Left);
// connect signals/slots
connect(p->filter, SIGNAL(textChanged(QString)), this,
SLOT(filterModel(QString)));
connect(p->navTree, SIGNAL(doubleClicked(QModelIndex)), this,
SLOT(itemDoubleClicked(QModelIndex)));
connect(p->navTree, SIGNAL(customContextMenuRequested(QPoint)), this,
SLOT(showItemMenu(QPoint)));
connect(p->navTree->selectionModel(),
SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this,
SLOT(itemChanged(QItemSelection, QItemSelection)));
connect(activateEnter, SIGNAL(activated()), this, SLOT(activateItem()));
connect(activateReturn, SIGNAL(activated()), this, SLOT(activateItem()));
connect(collapseBranch, SIGNAL(activated()), this, SLOT(collapse()));
}
void NavigationWidget::itemChanged(const QItemSelection& selected,
const QItemSelection& deselected)
{
// selected item
auto ani = selectedItem();
radix_tagged_line("itemChanged");
}
void NavigationWidget::itemDoubleClicked(const QModelIndex& index)
{
const auto& mapped = p->proxyModel->mapToSource(index);
if (index.isValid() && mapped.isValid())
{
auto si = p->navModel->itemFromIndex(mapped);
if (auto ani = dynamic_cast<NavigationItem*>(si))
{
ani->activate();
}
}
}
QTreeView* NavigationWidget::navigationTree() const { return p->navTree; }
QSortFilterProxyModel* NavigationWidget::proxyModel() const
{
return p->proxyModel;
}
void NavigationWidget::reloadTriggered()
{
// selected item
auto ani = selectedItem();
radix_tagged_line("reloadTriggered");
}
void NavigationWidget::saveAsTriggered()
{
// selected item
auto ani = selectedItem();
radix_tagged_line("saveAsTriggered");
}
void NavigationWidget::saveTriggered()
{
// selected item
auto ani = selectedItem();
radix_tagged_line("saveTriggered");
}
NavigationItem* NavigationWidget::selectedItem()
{
// current index, mapped index, selected flag
const auto& current = p->navTree->currentIndex();
const auto& mapped = p->proxyModel->mapToSource(p->navTree->currentIndex());
const auto selected = p->navTree->selectionModel()->isSelected(current);
// no-op
if (!current.isValid() || !mapped.isValid() || !selected)
{
return nullptr;
}
return dynamic_cast<NavigationItem*>(p->navModel->itemFromIndex(mapped));
}
NavigationItem* NavigationWidget::selectedRoot()
{
// selected item
auto item = selectedItem();
// nothing selected, return null
if (item == nullptr)
{
return nullptr;
}
// current and parent items
auto current = dynamic_cast<QStandardItem*>(item);
auto parent = current->parent();
// ascend the tree
while (parent != nullptr)
{
current = parent;
parent = current->parent();
}
return dynamic_cast<NavigationItem*>(current);
}
void NavigationWidget::showItemMenu(const QPoint& point)
{
// selected item