traintastic/client/src/board/boardwidget.cpp
Reinder Feenstra be918bb999 board: when placing tiles it is now drawn as overlay
todo: handle larger (>1x1) tiles
2021-06-04 00:31:43 +02:00

383 Zeilen
13 KiB
C++

/**
* client/src/board/boardwidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2021 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "boardwidget.hpp"
#include <array>
#include <QVBoxLayout>
#include <QToolBar>
#include <QToolButton>
#include <QMenu>
#include <QAction>
#include <QScrollArea>
#include <QStatusBar>
#include <QLabel>
#include <QApplication>
#include <traintastic/locale/locale.hpp>
#include "../mainwindow.hpp"
#include "../network/board.hpp"
#include "../network/connection.hpp"
#include "../network/property.hpp"
#include "../network/callmethod.hpp"
#include "../theme/theme.hpp"
struct TileInfo
{
QString classId;
TileId id;
uint8_t rotates;
};
const std::array<TileInfo, 25> tileInfo = {
TileInfo{QStringLiteral("board_tile.rail.straight"), TileId::RailStraight, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.buffer_stop"), TileId::RailBufferStop, 0xFF},
TileInfo{QStringLiteral(""), TileId::None, 0},
TileInfo{QStringLiteral("board_tile.rail.curve_45"), TileId::RailCurve45, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.curve_90"), TileId::RailCurve90, 0xFF},
TileInfo{QStringLiteral(""), TileId::None, 0},
TileInfo{QStringLiteral("board_tile.rail.cross_45"), TileId::RailCross45, 0x03},
TileInfo{QStringLiteral("board_tile.rail.cross_90"), TileId::RailCross90, 0x03},
TileInfo{QStringLiteral(""), TileId::None, 0},
TileInfo{QStringLiteral("board_tile.rail.turnout_left_45"), TileId::RailTurnoutLeft45, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.turnout_left_90"), TileId::RailTurnoutLeft90, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.turnout_left_curved"), TileId::RailTurnoutLeftCurved, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.turnout_right_45"), TileId::RailTurnoutRight45, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.turnout_right_90"), TileId::RailTurnoutRight90, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.turnout_right_curved"), TileId::RailTurnoutRightCurved, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.turnout_wye"), TileId::RailTurnoutWye, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.turnout_3way"), TileId::RailTurnout3Way, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.turnout_singleslip"), TileId::RailTurnoutSingleSlip, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.turnout_doubleslip"), TileId::RailTurnoutDoubleSlip, 0xFF},
TileInfo{QStringLiteral(""), TileId::None, 0},
TileInfo{QStringLiteral("board_tile.rail.block"), TileId::RailBlock, 0x05},
TileInfo{QStringLiteral("board_tile.rail.sensor"), TileId::RailSensor, 0xFF},
TileInfo{QStringLiteral(""), TileId::None, 0},
TileInfo{QStringLiteral("board_tile.rail.signal_2_aspect"), TileId::RailSignal2Aspect, 0xFF},
TileInfo{QStringLiteral("board_tile.rail.signal_3_aspect"), TileId::RailSignal3Aspect, 0xFF}
};
inline void validRotate(TileRotate& rotate, uint8_t rotates)
{
Q_ASSERT(rotates != 0);
while(((1 << static_cast<uint8_t>(rotate)) & rotates) == 0)
rotate += TileRotate::Deg45;
}
BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
QWidget(parent),
m_object{std::move(object)},
m_boardArea{new BoardAreaWidget(*this, this)},
m_statusBar{new QStatusBar(this)},
m_statusBarMessage{new QLabel(this)},
m_statusBarCoords{new QLabel(this)},
m_editActions{new QActionGroup(this)},
m_editRotate{TileRotate::Deg0}
{
if(AbstractProperty* name = m_object->getProperty("name"))
{
connect(name, &AbstractProperty::valueChangedString, this, &BoardWidget::setWindowTitle);
setWindowTitle(name->toString());
}
QMenu* m;
QVBoxLayout* l = new QVBoxLayout();
l->setMargin(0);
// main toolbar:
QToolBar* toolbar = new QToolBar(this);
l->addWidget(toolbar);
toolbar->addAction(/*Theme::getIcon("properties"),*/ Locale::tr("qtapp:board_properties"),
[]()
{
// TODO
});
toolbar->addSeparator();
m_actionZoomIn = toolbar->addAction(Theme::getIcon("zoom_in"), Locale::tr("qtapp:zoom_in"), m_boardArea, &BoardAreaWidget::zoomIn);
m_actionZoomOut = toolbar->addAction(Theme::getIcon("zoom_out"), Locale::tr("qtapp:zoom_out"), m_boardArea, &BoardAreaWidget::zoomOut);
toolbar->addSeparator();
m_toolButtonGrid = new QToolButton(this);
m_toolButtonGrid->setIcon(Theme::getIcon("grid_dot"));
m_toolButtonGrid->setToolTip(Locale::tr("qtapp:grid"));
m_toolButtonGrid->setPopupMode(QToolButton::MenuButtonPopup);
connect(m_toolButtonGrid, &QToolButton::pressed, m_toolButtonGrid, &QToolButton::showMenu);
m = new QMenu(this);
m_actionGridNone = m->addAction(Theme::getIcon("grid_none"), Locale::tr("qtapp:grid_none"),
[this]()
{
m_boardArea->setGrid(BoardAreaWidget::Grid::None);
});
m_actionGridDot = m->addAction(Theme::getIcon("grid_dot"), Locale::tr("qtapp:grid_dot"),
[this]()
{
m_boardArea->setGrid(BoardAreaWidget::Grid::Dot);
});
m_actionGridLine = m->addAction(Theme::getIcon("grid_line"), Locale::tr("qtapp:grid_line"),
[this]()
{
m_boardArea->setGrid(BoardAreaWidget::Grid::Line);
});
m_toolButtonGrid->setMenu(m);
toolbar->addWidget(m_toolButtonGrid);
// edit toolbar:
m_toolbarEdit = new QToolBar(this);
l->addWidget(m_toolbarEdit);
m_editActions->setExclusive(true);
m_editActionNone = m_editActions->addAction(m_toolbarEdit->addAction(Theme::getIcon("mouse"), "", this,
[this]()
{
actionSelected(nullptr);
}));
m_editActionNone->setCheckable(true);
m_editActionNone->setData(-1);
m_editActionMove = m_editActions->addAction(m_toolbarEdit->addAction(Theme::getIcon("move_tile"), Locale::tr("board:move_tile"), this,
[this]()
{
actionSelected(nullptr);
}));
m_editActionMove->setCheckable(true);
m_editActionMove->setData(-1);
m_editActionMove->setEnabled(false); // todo: implement
m_editActionDelete = m_editActions->addAction(m_toolbarEdit->addAction(Theme::getIcon("delete"), Locale::tr("board:delete_tile"), this,
[this]()
{
actionSelected(nullptr);
}));
m_editActionDelete->setCheckable(true);
m_editActionDelete->setData(-1);
m_toolbarEdit->addSeparator();
{
QVector<QAction*> actions;
for(size_t i = 0; i < tileInfo.size(); i++)
{
const TileInfo& info = tileInfo[i];
if(info.classId.isEmpty()) // next item group
{
if(actions.isEmpty())
continue;
else if(actions.length() == 1)
{
actions[0]->setCheckable(true);
m_toolbarEdit->addAction(m_editActions->addAction(actions[0]));
connect(actions[0], &QAction::triggered, this,
[this, action=actions[0]]()
{
actionSelected(&tileInfo[action->data().toInt()]);
});
}
else // > 1
{
QAction* action = m_editActions->addAction(m_toolbarEdit->addAction(""));
if(auto* tb = dynamic_cast<QToolButton*>(m_toolbarEdit->widgetForAction(action)))
tb->setPopupMode(QToolButton::MenuButtonPopup);
QMenu* m = new QMenu(this);
for(auto subAction : actions)
{
m->addAction(subAction);
connect(subAction, &QAction::triggered, this,
[this, action, subAction]()
{
action->setIcon(subAction->icon());
action->setText(subAction->text());
action->setData(subAction->data());
action->setChecked(true);
actionSelected(&tileInfo[subAction->data().toInt()]);
});
}
action->setIcon(actions[0]->icon());
action->setText(actions[0]->text());
action->setData(actions[0]->data());
action->setMenu(m);
action->setCheckable(true);
connect(action, &QAction::triggered, this,
[this, action]()
{
actionSelected(&tileInfo[action->data().toInt()]);
});
}
actions.clear();
}
else
{
QAction* act = new QAction(QIcon(QString(":/dark/").append(info.classId).append("")), Locale::tr(QString("class_id:").append(info.classId)));
act->setData(static_cast<qint64>(i));
actions.append(act);
}
}
}
m_editActions->actions().first()->setChecked(true);
QScrollArea* sa = new QScrollArea(this);
sa->setWidget(m_boardArea);
l->addWidget(sa);
m_statusBar->addWidget(m_statusBarMessage, 1);
m_statusBar->addWidget(m_statusBarCoords, 0);
l->addWidget(m_statusBar);
AbstractProperty* edit = m_object->connection()->world()->getProperty("edit");
worldEditChanged(Q_LIKELY(edit) && edit->toBool());
if(Q_LIKELY(edit))
connect(edit, &AbstractProperty::valueChangedBool, this, &BoardWidget::worldEditChanged);
setLayout(l);
m_object->getTileData();
connect(m_object.get(), &Board::tileDataChanged, this, [this](){ m_boardArea->update(); });
connect(m_boardArea, &BoardAreaWidget::gridChanged, this, &BoardWidget::gridChanged);
connect(m_boardArea, &BoardAreaWidget::zoomLevelChanged, this, &BoardWidget::zoomLevelChanged);
connect(m_boardArea, &BoardAreaWidget::tileClicked, this, &BoardWidget::tileClicked);
connect(m_boardArea, &BoardAreaWidget::rightClicked, this, &BoardWidget::rightClicked);
connect(m_boardArea, &BoardAreaWidget::mouseTileLocationChanged, this,
[this](int16_t x, int16_t y)
{
m_statusBarCoords->setText(QString::number(x) + ", " + QString::number(y));
});
gridChanged(m_boardArea->grid());
zoomLevelChanged(m_boardArea->zoomLevel());
}
void BoardWidget::worldEditChanged(bool value)
{
m_toolbarEdit->setVisible(value);
m_statusBar->setVisible(value);
m_boardArea->setMouseTracking(value);
}
void BoardWidget::gridChanged(BoardAreaWidget::Grid value)
{
switch(value)
{
case BoardAreaWidget::Grid::None:
m_toolButtonGrid->setIcon(m_actionGridNone->icon());
break;
case BoardAreaWidget::Grid::Dot:
m_toolButtonGrid->setIcon(m_actionGridDot->icon());
break;
case BoardAreaWidget::Grid::Line:
m_toolButtonGrid->setIcon(m_actionGridLine->icon());
break;
}
}
void BoardWidget::zoomLevelChanged(int value)
{
m_actionZoomIn->setEnabled(value < BoardAreaWidget::zoomLevelMax);
m_actionZoomOut->setEnabled(value > BoardAreaWidget::zoomLevelMin);
}
void BoardWidget::tileClicked(int16_t x, int16_t y)
{
if(m_toolbarEdit->isVisible()) // edit mode
{
QAction* act = m_editActions->checkedAction();
if(!act)
return;
if(act == m_editActionNone)
{
auto it = m_object->tileData().find({x, y});
if(it != m_object->tileData().end())
if(ObjectPtr obj = m_object->getTileObject({x, y}))
MainWindow::instance->showObject(obj);
}
else if(act == m_editActionMove)
{
}
else if(act == m_editActionDelete)
{
m_object->deleteTile(x, y,
[this](const bool& r, Message::ErrorCode ec)
{
});
}
else // add
{
const QString& classId = tileInfo[act->data().toInt()].classId;
const Qt::KeyboardModifiers kbMod = QApplication::keyboardModifiers();
if(kbMod == Qt::NoModifier || kbMod == Qt::ControlModifier)
m_object->addTile(x, y, m_editRotate, classId, kbMod == Qt::ControlModifier,
[this](const bool& r, Message::ErrorCode ec)
{
});
}
}
else
{
auto it = m_object->tileData().find({x, y});
if(it != m_object->tileData().end())
{
if(ObjectPtr obj = m_object->getTileObject({x, y}))
{
if(isRailTurnout(it->second.id()))
{
if(auto* m = obj->getMethod("next_position"))
callMethod(*m, nullptr, false);
}
else if(isRailSignal(it->second.id()))
{
if(auto* m = obj->getMethod("next_aspect"))
callMethod(*m, nullptr, false);
}
}
}
}
}
void BoardWidget::rightClicked()
{
if(QAction* act = m_editActions->checkedAction())
if(int index = act->data().toInt(); index >= 0 && Q_LIKELY(index < tileInfo.size()))
{
m_editRotate += TileRotate::Deg45;
validRotate(m_editRotate, tileInfo[index].rotates);
m_boardArea->setMouseMoveTileRotate(m_editRotate);
}
}
void BoardWidget::actionSelected(const TileInfo* tileInfo)
{
if(tileInfo)
{
validRotate(m_editRotate, tileInfo->rotates);
m_boardArea->setMouseMoveTileRotate(m_editRotate);
m_boardArea->setMouseMoveTileId(tileInfo->id);
}
else
m_boardArea->setMouseMoveTileId(TileId::None);
}