Skip to content

Instantly share code, notes, and snippets.

@kuldeepdhaka
Last active August 29, 2015 14:03
Show Gist options
  • Save kuldeepdhaka/2e1186dc985c130efc1a to your computer and use it in GitHub Desktop.
Save kuldeepdhaka/2e1186dc985c130efc1a to your computer and use it in GitHub Desktop.
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(QRearrangeableLayout)
FIND_PACKAGE(Qt4 REQUIRED)
INCLUDE(${QT_USE_FILE})
ADD_DEFINITIONS(${QT_DEFINITIONS})
SET(qrearrangeablelayout_SOURCES QRearrangeableLayout.cpp)
SET(qrearrangeablelayout_HEADERS QRearrangeableLayout.h)
QT4_WRAP_CPP(qrearrangeablelayout_HEADERS_MOC ${qrearrangeablelayout_HEADERS})
ADD_LIBRARY(qrearrangeablelayout SHARED ${qrearrangeablelayout_HEADERS_MOC} ${qrearrangeablelayout_HEADERS} ${qrearrangeablelayout_SOURCES})
TARGET_LINK_LIBRARIES(qrearrangeablelayout ${QT_LIBRARIES})
ADD_EXECUTABLE(test main.cpp)
TARGET_LINK_LIBRARIES(test ${QT_LIBRARIES})
TARGET_LINK_LIBRARIES(test qrearrangeablelayout)
#include <QApplication>
#include <QMainWindow>
#include <QListWidget>
#include <QListWidgetItem>
#include <QHBoxLayout>
#include <QSplitter>
#include <QLabel>
#include <QPushButton>
#include "QRearrangeableLayout.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow w;
w.setWindowTitle("QRearrangeableLayout - Test");
w.setObjectName(QString::fromUtf8("QMain-test"));
QListWidget *list1 = new QListWidget();
list1->addItem(new QListWidgetItem("List1"));
QListWidget *list2 = new QListWidget();
list2->addItem(new QListWidgetItem("List2"));
for(int i = 0; i < 10; i++ ) {
list1->addItem(new QListWidgetItem("Another item"));
list2->addItem(new QListWidgetItem("Another item"));
}
QRearrangeableLayout *central = new QRearrangeableLayout(&a, &w);
central->setRearrangeable(true);
w.setCentralWidget(central);
QHBoxLayout *mainhbox = new QHBoxLayout();
central->setLayout(mainhbox);
QSplitter *hbox = new QSplitter(Qt::Horizontal);
hbox->addWidget(list1);
hbox->addWidget(list2);
hbox->addWidget(new QLabel());
hbox->addWidget(new QPushButton());
hbox->addWidget(new QListWidget());
hbox->addWidget(new QListWidget());;
hbox->addWidget(new QListWidget());
for(int i = 0; i < hbox->count(); i++) {
hbox->setCollapsible(i, false);
}
mainhbox->addWidget(hbox);
w.show();
return a.exec();
}
#include "QRearrangeableLayout.h"
#include <QBoxLayout>
#include <QMimeData>
#include <QDrag>
#include <QList>
#include <QMouseEvent>
#include <iostream>
/* blind(mostly) conversion of QRearrangeableLayout.py */
QRearrangeableLayout::QRearrangeableLayout(QApplication *app, QWidget *parent) : QWidget(parent)
{
this->app = app;
app->installEventFilter(this);
dragStarting = false;
dragRunning = false;
setAcceptDrops(true);
_rearrangeable = false;
dragSource = 0;
}
void QRearrangeableLayout::setRearrangeable(bool rearrangeable)
{
_rearrangeable = rearrangeable;
}
bool QRearrangeableLayout::rearrangeable(void)
{
return _rearrangeable;
}
QSplitter* QRearrangeableLayout::removeWidget(QWidget *widget)
{
/*
* Removes a widget from its parent splitter. If the splitter is left with
* one or less widgets in it, it is removed from its parent itself and the
*(possibly) remaining widget is moved to the splitters parent.
* Returns the splitter if there are more then one widgets left in there,
* otherwise returns the parent of the splitter.
*/
QSplitter *splitter = static_cast<QSplitter *>(widget->parent());
if(! splitter->inherits("QSplitter")) {
//~ raise Exception /* ("Expecting a widget whose parent is a QSplitter") */;
printf("Exception(\"Expecting a widget whose parent is a QSplitter\")\n");
return 0;
}
widget->setParent(0);
QSplitter *parent = static_cast<QSplitter *>(splitter->parent());
if(splitter->count() > 2) {
return splitter;
}
if(splitter->count() == 1) {
/*
* the splitter's parent is also a splitter, we only need to need to
* move that one remaining widget to the parent
*/
if(parent->inherits("QSplitter")) {
QWidget *child = splitter->widget(0);
child->setParent(0);
parent->insertWidget(parent->indexOf(splitter), child);
}
/*
* if not: check if the remaining child is another splitter and if
* so, change the splitters layout to the one of the remaining
* splitter, move it's children to our top level splitter and
* remove the remaining splitter afterwards
*/
else {
QSplitter *child = static_cast<QSplitter *>(splitter->widget(0));
if(child->inherits("QSplitter")) {
QList<int>sizes = child->sizes();
splitter->setOrientation(child->orientation());
child->setParent(0);
QList<QWidget *> widgets;
for(int i = child->count() - 1; i >= 0; i--) {
QWidget *w = child->widget(i);
w->setParent(0);
widgets.append(w);
}
while(! widgets.isEmpty()) {
QWidget *w = widgets.takeLast();
splitter->addWidget(w);
}
splitter->setSizes(sizes);
}
return splitter;
}
}
if(parent->inherits("QSplitter")) {
splitter->setParent(0);
return parent;
} else {
return splitter;
}
}
QWidget* QRearrangeableLayout::findChildOfSplitter(QWidget *widget)
{
/*
* Walks up the family tree of the given widget until it finds a widget
* whose parent is a QSplitter.
* Returns that widget->
*/
/* FIX: if we have clicked on a area that is under QRearrangeableLayout but not the underlying widget or click (check if parent is NULL) */
while((widget != 0) and (widget->parent() != 0 and ! widget->parent()->inherits("QSplitter"))) {
widget = static_cast<QWidget *>(widget->parent());
}
return widget;
}
bool QRearrangeableLayout::eventFilter(QObject *source, QEvent *event)
{
if(_rearrangeable == false) {
return QWidget::eventFilter(source, event);
//~ return QBoxLayout::eventFilter(source, event);
//~ return false;
}
QMouseEvent *mouseEvent;
if( event->type() == QEvent::MouseButtonPress or
event->type() == QEvent::MouseButtonRelease or
event->type() == QEvent::MouseMove) {
mouseEvent = static_cast<QMouseEvent *>(event);
/* ignore events that aren't going to our widget */
if(! rect().contains(mouseEvent->pos())) {
//~ return QBoxLayout::eventFilter(source, event);
return QWidget::eventFilter(source, event);
//~ return false;
}
}
if(event->type() == QEvent::MouseButtonPress) {
dragSource = QRearrangeableLayout::findChildOfSplitter(static_cast<QWidget *>(source));
QWidget *widget = dragSource;
while(widget != 0 and ! widget->inherits("QRearrangeableLayout")) {
widget = static_cast<QWidget *>(widget->parent());
}
if(! (widget == 0 or widget == this) and widget->inherits("QRearrangeableLayout")) {
return widget->eventFilter(source, event);
}
if(dragSource != 0) {
dragStarting = true;
dragPos = mouseEvent->pos();
}
if(dragSource->inherits("QSplitterHandle")) {
dragStarting = false;
}
if(dragStarting) {
emit draggingStarted(dragSource, mouseEvent->pos());
}
}
else if(event->type() == QEvent::MouseButtonRelease) {
dragStarting = false;
}
else if(event->type() == QEvent::MouseMove and dragStarting and
((mouseEvent->pos() - dragPos).manhattanLength() > QApplication::startDragDistance())) {
dragStarting = false;
dragRunning = true;
/* begin dragging procedure */
QMimeData *mimeData = new QMimeData();
QDrag *drag = new QDrag(dragSource);
drag->setMimeData(mimeData);
drag->setHotSpot(dragPos);
//~ QPixmap pixmap = dragSource->pixmap();
//~ drag->setPixmap(pixmap);
Qt::DropAction dropAction = drag->exec(Qt::MoveAction);
//~ std::cout << drag->supportedActions() << " " << dropAction << std::endl;
std::cout << dropAction << " " << dragSource->metaObject()->className() << std::endl;
dragRunning = false;
}
//~ return false;
//~ return QBoxLayout::eventFilter(source, event);
return QWidget::eventFilter(source, event);
}
void QRearrangeableLayout::dragEnterEvent(QDragEnterEvent *event)
{
event->setAccepted(dragRunning);
}
void QRearrangeableLayout::dragMoveEvent(QDragMoveEvent *event)
{
QWidget *under = app->widgetAt(QCursor::pos());
/*
* check if this event should go to another rearrangeable layout more
* nested
*/
QWidget *widget = under;
while(widget != 0 and ! widget->inherits("QRearrangeableLayout")) {
widget = static_cast<QWidget *>(widget->parent());
}
if(! (widget == 0 or widget == this)) {
under = widget;
}
if(! rect().contains(event->pos())) {
return;
}
/* is the widget underneath a child of a QSplitter? if yes, put it there */
widget = QRearrangeableLayout::findChildOfSplitter(under);
if(widget == 0 or widget == dragSource or widget->inherits("QSplitterHandle")) {
return;
}
QSplitter *splitter = static_cast<QSplitter *>(widget->parent());
/*
* the direction in which we want to expand:
* 0 = up, 1 = right, 2 = left, 3 = down(order for arithmetic reasons)
*/
int direction = 0;
int i = 0;
QPoint pos = event->pos() - widget->mapTo(this, QPoint(0,0));
QSize size = widget->size();
int w = size.width();
int h = size.height();
int d = pos.x() * h / w; /* function value of the diagonal */
QSize cornersize = size / 4;
int ww = w * 3/4;
int wh = h * 3/4;
QRect rects[5] = {
/* disable a region in the middle of the widget,
* half the size of the widget itself
*/
QRect(QPoint(cornersize.width(), cornersize.height()), size / 2),
/*
* disable corner regions;
* these regions touch the middle regions at one point
*/
QRect(QPoint(0, 0), cornersize),
QRect(QPoint(ww, 0), cornersize),
QRect(QPoint(0, wh), cornersize),
QRect(QPoint(ww, wh), cornersize)
};
for(i = 0; i < 5; i++) {
QRect r = rects[i];
/*
*(need to ajust a bit, so that we don't collide with the borders,
* is a little bit strange)
*/
r.adjust(-10, -10, 10, 10);
if (r.contains(pos)) {
return;
}
}
/* calculate the direction in which to expand */
if(pos.y() > d) {
direction += 2;
}
/* ... ? */
if(pos.y() > (-d + h)) {
direction += 1;
}
/*
* break down to the actual direction and if we insert before or after
* the widget underneath
*/
Qt::Orientation orientation = (direction == 0 or direction == 3) ? Qt::Vertical : Qt::Horizontal;
bool before =((direction % 2) == 0);
/* do not remove ourselves just to push us back afterwards */
if(dragSource->parent() == splitter) {
int indexDrag = splitter->indexOf(dragSource);
int indexWidget = splitter->indexOf(widget);
int indexTarget = indexWidget + 1 - before -(indexDrag < indexWidget);
if(splitter->orientation() == orientation and indexTarget == indexDrag) {
return;
}
}
QRearrangeableLayout::removeWidget(dragSource);
splitter = static_cast<QSplitter *>(widget->parent());
i = splitter->indexOf(widget);
if(splitter->orientation() == orientation) {
/* we want to insert the dragging source into the same direction the
* underneath splitter already is so we just need to put it in there
*/
if(! before) {
i += 1;
}
splitter->insertWidget(i, dragSource);
} else {
/* here we put the dragging widget and widget underneath the mouse
* into a new splitter and put it to the index the widget was located
* before
*/
widget->setParent(0);
QSplitter *newSplitter = new QSplitter(orientation);
if(before) {
newSplitter->addWidget(dragSource);
newSplitter->addWidget(widget);
} else {
newSplitter->addWidget(widget);
newSplitter->addWidget(dragSource);
}
splitter->insertWidget(i, newSplitter);
}
}
void QRearrangeableLayout::dropEvent(QDropEvent *event)
{
dragRunning = false;
emit draggingEnded(dragSource, event->pos());
}
#ifndef QREARRANGABLELAYOUT_H
#define QREARRANGABLELAYOUT_H
#include <QApplication>
#include <QWidget>
#include <QPoint>
#include <QSplitter>
#include <QEvent>
#include <QPoint>
#include <QDrag>
class QRearrangeableLayout : public QWidget
{
Q_OBJECT
signals:
/* Gets emitted, when the user started dragging a widget. */
void draggingStarted(QWidget *widget, QPoint point);
/* Gets emitted, when the user ended dragging a widget by releasing the mouse button. */
void draggingEnded(QWidget *widget, QPoint point);
public:
QRearrangeableLayout(QApplication *app, QWidget *parent);
//~ ~QRearrangeableLayout(void);
void setRearrangeable(bool rearrangeable);
bool rearrangeable(void);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
bool eventFilter(QObject *source, QEvent *event);
private:
QApplication *app;
bool dragStarting;
bool dragRunning;
//~ bool setAcceptDrops[];
bool _rearrangeable;
QWidget *dragSource;
QPoint dragPos;
static QSplitter* removeWidget(QWidget *widget);
static QWidget* findChildOfSplitter(QWidget *widget);
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment