Implemented basic grid rendering of task strips

This commit is contained in:
2020-03-06 20:58:55 +01:00
parent 228360c6cf
commit c96c0d538f
13 changed files with 800 additions and 41 deletions

103
.gitignore vendored
View File

@@ -1,52 +1,73 @@
# C++ objects and libs
*.slo
*.lo
*.o
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.la
*.lai
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*.dll
*.dylib
# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
*.qm
*.prl
# Qt unit tests
target_wrapper.*
# qtcreator generated files
*.pro.user*
# QtCreator
*.autosave
# xemacs temporary files
*.flc
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# Vim temporary files
.*.swp
# QtCreator CMake
CMakeLists.txt.user*
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# QtCreator 4.8< compilation database
compile_commands.json
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe
# QtCreator local machine specific files for imported projects
*creator.user*

21
SprintPlanningTool.pro Normal file
View File

@@ -0,0 +1,21 @@
QT = core gui widgets
CONFIG += c++17
DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000
SOURCES += main.cpp \
flowlayout.cpp \
mainwindow.cpp \
stripsgrid.cpp \
stripwidget.cpp
FORMS += \
mainwindow.ui \
stripwidget.ui
HEADERS += \
flowlayout.h \
mainwindow.h \
stripsgrid.h \
stripwidget.h

150
flowlayout.cpp Normal file
View File

@@ -0,0 +1,150 @@
#include "flowlayout.h"
#include <QWidget>
FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
: m_hSpace(hSpacing), m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
FlowLayout::~FlowLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}
void FlowLayout::addItem(QLayoutItem *item)
{
itemList.append(item);
}
int FlowLayout::horizontalSpacing() const
{
if (m_hSpace >= 0) {
return m_hSpace;
} else {
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
}
}
int FlowLayout::verticalSpacing() const
{
if (m_vSpace >= 0) {
return m_vSpace;
} else {
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
}
}
int FlowLayout::count() const
{
return itemList.size();
}
QLayoutItem *FlowLayout::itemAt(int index) const
{
return itemList.value(index);
}
QLayoutItem *FlowLayout::takeAt(int index)
{
if (index >= 0 && index < itemList.size())
return itemList.takeAt(index);
return nullptr;
}
Qt::Orientations FlowLayout::expandingDirections() const
{
return 0;
}
bool FlowLayout::hasHeightForWidth() const
{
return true;
}
int FlowLayout::heightForWidth(int width) const
{
int height = doLayout(QRect(0, 0, width, 0), true);
return height;
}
void FlowLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
doLayout(rect, false);
}
QSize FlowLayout::sizeHint() const
{
return minimumSize();
}
QSize FlowLayout::minimumSize() const
{
QSize size;
for (const QLayoutItem *item : qAsConst(itemList))
size = size.expandedTo(item->minimumSize());
const QMargins margins = contentsMargins();
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
return size;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
{
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
for (QLayoutItem *item : qAsConst(itemList)) {
const QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
return y + lineHeight - rect.y() + bottom;
}
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
{
QObject *parent = this->parent();
if (!parent) {
return -1;
} else if (parent->isWidgetType()) {
QWidget *pw = static_cast<QWidget *>(parent);
return pw->style()->pixelMetric(pm, nullptr, pw);
} else {
return static_cast<QLayout *>(parent)->spacing();
}
}

34
flowlayout.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include <QLayout>
#include <QRect>
#include <QStyle>
class FlowLayout : public QLayout
{
public:
explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
~FlowLayout();
void addItem(QLayoutItem *item) override;
int horizontalSpacing() const;
int verticalSpacing() const;
Qt::Orientations expandingDirections() const override;
bool hasHeightForWidth() const override;
int heightForWidth(int) const override;
int count() const override;
QLayoutItem *itemAt(int index) const override;
QSize minimumSize() const override;
void setGeometry(const QRect &rect) override;
QSize sizeHint() const override;
QLayoutItem *takeAt(int index) override;
private:
int doLayout(const QRect &rect, bool testOnly) const;
int smartSpacing(QStyle::PixelMetric pm) const;
QList<QLayoutItem *> itemList;
int m_hSpace;
int m_vSpace;
};

14
main.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include <QApplication>
#include "mainwindow.h"
#include "stripwidget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow mainWindow;
mainWindow.show();
return a.exec();
}

11
mainwindow.cpp Normal file
View File

@@ -0,0 +1,11 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow{parent},
m_ui{std::make_unique<Ui::MainWindow>()}
{
m_ui->setupUi(this);
}
MainWindow::~MainWindow() = default;

19
mainwindow.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <QMainWindow>
#include <memory>
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
private:
std::unique_ptr<Ui::MainWindow> m_ui;
};

81
mainwindow.ui Normal file
View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="StripsGrid" name="stripsGrid">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>798</width>
<height>558</height>
</rect>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>18</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="action_Quit"/>
</widget>
<addaction name="menu_File"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="action_Quit">
<property name="text">
<string>&amp;Quit</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>StripsGrid</class>
<extends>QWidget</extends>
<header>stripsgrid.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>action_Quit</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>299</y>
</hint>
</hints>
</connection>
</connections>
</ui>

187
stripsgrid.cpp Normal file
View File

@@ -0,0 +1,187 @@
#include "stripsgrid.h"
#include <QDebug>
#include <QGridLayout>
#include <QMouseEvent>
#include <QGraphicsOpacityEffect>
#include <QLabel>
#include <QSpacerItem>
#include <QRandomGenerator>
#include <QDateTime>
#include "stripwidget.h"
#include "flowlayout.h"
namespace {
QDebug &operator<<(QDebug &debug, const QMouseEvent &event)
{
return debug << "QMouseEvent(" << event.pos() << event.source() << event.flags() << ")";
}
template<typename T>
void call_n_times(int count, T func)
{
for (int i = 0; i < count; i++)
func();
}
}
StripsGrid::StripsGrid(QWidget *parent) :
QWidget{parent}
{
m_layout = new QGridLayout{this};
QRandomGenerator random((qint32)(QDateTime::currentMSecsSinceEpoch()));
const auto stories = random.bounded(3, 10);
int row{0};
{
int column{0};
for (const auto text : {"Story", "To Do:", "In Progress:", "In Review:", "Done:"})
{
auto label = new QLabel{text};
{
auto font = label->font();
font.setPointSize(24);
label->setFont(font);
}
label->setFixedHeight(40);
m_layout->addWidget(label, row, column++);
{
auto line = new QFrame;
line->setFrameShape(QFrame::VLine);
line->setFrameShadow(QFrame::Sunken);
m_layout->addWidget(line, row, column++, (stories*2)+1, 1);
}
}
}
row++;
call_n_times(stories, [&]()
{
{
auto line = new QFrame;
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
m_layout->addWidget(line, row, 0, 1, 9);
}
row++;
int storyPoints{0};
auto storyWidget = new StripWidget;
storyWidget->setTitle(QString("ATC-%0").arg(random.bounded(1000, 5000)));
storyWidget->setStyleSheet("StripWidget { background-color: #AFA; }");
for (int column = 0; column < 5; column++)
{
if (column == 0)
{
auto layout = new QVBoxLayout;
layout->addSpacing(0);
layout->addWidget(storyWidget);
layout->addStretch(1);
m_layout->addLayout(layout, row, column*2);
}
else
{
int count = random.bounded(0, 5);
if (!count)
continue;
auto layout = new FlowLayout;
call_n_times(count, [&]()
{
auto widget = new StripWidget;
widget->setTitle(QString("ATC-%0").arg(random.bounded(1000, 5000)));
{
const auto points = std::array<int, 5>{1,3,5,8,13}[random.bounded(5)];
widget->setPoints(points);
storyPoints += points;
}
widget->setOwner(std::array<const char *, 5>{"DB", "KW", "BK", "MS", "AS"}[random.bounded(5)]);
widget->setStyleSheet("StripWidget { background-color: #FCC; }");
layout->addWidget(widget);
});
m_layout->addLayout(layout, row, column*2);
}
}
storyWidget->setPoints(storyPoints);
row++;
});
m_layout->addItem(new QSpacerItem{0, 40, QSizePolicy::Minimum, QSizePolicy::Expanding}, ++row, 0, 1, 5);
m_widget = new StripWidget{this};
m_widget->setStyleSheet("StripWidget { background-color: #88FFFF; }");
m_widget->move(100, 100);
// auto widget = new QWidget;
// setCentralWidget(widget);
// m_layout = new QGridLayout{widget};
// const auto addColumn = [&](const QString &name)
// {
// auto label = new QLabel{name};
// {
// auto font = label->font();
// font.setPointSize(30);
// label->setFont(font);
// }
// m_layout->addWidget(label, 0, m_layout->columnCount());
// };
// addColumn("To Do");
// addColumn("In Progress");
// addColumn("In Review");
// addColumn("Done");
// m_layout->addWidget(new QWidget, 1, 0);
}
void StripsGrid::mousePressEvent(QMouseEvent *event)
{
qDebug() << "mousePressEvent" << *event;
if (event->button() == Qt::LeftButton &&
!m_isDragging &&
m_widget->startDragging(event->pos() - m_widget->pos()))
{
m_widget->setGraphicsEffect(new QGraphicsOpacityEffect);
m_isDragging = true;
m_dragOffset = event->pos() - m_widget->pos();
}
QWidget::mousePressEvent(event);
}
void StripsGrid::mouseReleaseEvent(QMouseEvent *event)
{
qDebug() << "mouseReleaseEvent" << *event;
if (event->button() == Qt::LeftButton &&
m_isDragging)
{
m_widget->setGraphicsEffect(nullptr);
m_isDragging = false;
}
QWidget::mouseReleaseEvent(event);
}
void StripsGrid::mouseMoveEvent(QMouseEvent *event)
{
qDebug() << "mouseMoveEvent" << *event;
if (m_isDragging)
m_widget->move(event->pos() - m_dragOffset);
QWidget::mouseMoveEvent(event);
}

27
stripsgrid.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <QWidget>
class QGridLayout;
class StripWidget;
class StripsGrid : public QWidget
{
Q_OBJECT
public:
explicit StripsGrid(QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private:
QGridLayout *m_layout;
bool m_isDragging{false};
QPoint m_dragOffset;
StripWidget *m_widget;
};

40
stripwidget.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include "stripwidget.h"
#include "ui_stripwidget.h"
StripWidget::StripWidget(QWidget *parent) :
QFrame{parent},
m_ui{std::make_unique<Ui::StripWidget>()}
{
m_ui->setupUi(this);
setMinimumSize(size());
setMaximumSize(size());
setFixedSize(size());
}
StripWidget::~StripWidget() = default;
bool StripWidget::startDragging(const QPoint &pos) const
{
return m_ui->title->geometry().contains(pos);
}
void StripWidget::setTitle(const QString &title)
{
m_ui->title->setText(title);
}
void StripWidget::setDescription(const QString &description)
{
m_ui->description->setText(description);
}
void StripWidget::setPoints(int points)
{
m_ui->points->setText(QString::number(points));
}
void StripWidget::setOwner(const QString &owner)
{
m_ui->owner->setText(owner);
}

26
stripwidget.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <QFrame>
#include <memory>
namespace Ui { class StripWidget; }
class StripWidget : public QFrame
{
Q_OBJECT
public:
explicit StripWidget(QWidget *parent = nullptr);
~StripWidget() override;
bool startDragging(const QPoint &pos) const;
void setTitle(const QString &title);
void setDescription(const QString &description);
void setPoints(int points);
void setOwner(const QString &owner);
private:
std::unique_ptr<Ui::StripWidget> m_ui;
};

128
stripwidget.ui Normal file
View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StripWidget</class>
<widget class="QFrame" name="StripWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>195</width>
<height>105</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="title">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string notr="true">ATC-1234</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="description">
<property name="text">
<string notr="true">Refactor once again everything</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="points">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="owner">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>