paramview: implemented passthrough editing
This commit is contained in:
@@ -31,7 +31,8 @@ NodePanel::NodePanel(QWidget *parent) :
|
||||
// Connect node view signals to this panel - MAY REMOVE
|
||||
connect(node_widget_->view(), &NodeView::NodesSelected, this, &NodePanel::NodesSelected);
|
||||
connect(node_widget_->view(), &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected);
|
||||
connect(node_widget_->view(), &NodeView::NodeGroupOpenRequested, this, &NodePanel::NodeGroupOpenRequested);
|
||||
connect(node_widget_->view(), &NodeView::NodeGroupOpened, this, &NodePanel::NodeGroupOpened);
|
||||
connect(node_widget_->view(), &NodeView::NodeGroupClosed, this, &NodePanel::NodeGroupClosed);
|
||||
|
||||
// Set it as the main widget of this panel
|
||||
SetWidgetWithPadding(node_widget_);
|
||||
|
||||
+6
-10
@@ -40,10 +40,9 @@ public:
|
||||
return node_widget_;
|
||||
}
|
||||
|
||||
const QVector<Node*> &GetContexts() const
|
||||
{
|
||||
return node_widget_->view()->GetContexts();
|
||||
}
|
||||
const QVector<Node*> &GetContexts() const { return node_widget_->view()->GetContexts(); }
|
||||
|
||||
bool IsGroupOverlay() const { return node_widget_->view()->IsGroupOverlay(); }
|
||||
|
||||
void SetContexts(const QVector<Node*> &nodes)
|
||||
{
|
||||
@@ -55,11 +54,6 @@ public:
|
||||
node_widget_->view()->CloseContextsBelongingToProject(project);
|
||||
}
|
||||
|
||||
const QVector<Node*> &GetCurrentContexts() const
|
||||
{
|
||||
return node_widget_->view()->GetCurrentContexts();
|
||||
}
|
||||
|
||||
virtual void SelectAll() override
|
||||
{
|
||||
node_widget_->view()->SelectAll();
|
||||
@@ -122,7 +116,9 @@ signals:
|
||||
|
||||
void NodesDeselected(const QVector<Node*>& nodes);
|
||||
|
||||
void NodeGroupOpenRequested(NodeGroup *group);
|
||||
void NodeGroupOpened(NodeGroup *group);
|
||||
|
||||
void NodeGroupClosed();
|
||||
|
||||
private:
|
||||
virtual void Retranslate() override
|
||||
|
||||
@@ -61,9 +61,9 @@ void ParamPanel::DeselectAll()
|
||||
static_cast<NodeParamView*>(GetTimeBasedWidget())->DeselectAll();
|
||||
}
|
||||
|
||||
void ParamPanel::SetContexts(const QVector<Node *> &contexts)
|
||||
void ParamPanel::SetContexts(const QVector<Node *> &contexts, bool group_mode)
|
||||
{
|
||||
static_cast<NodeParamView*>(GetTimeBasedWidget())->SetContexts(contexts);
|
||||
static_cast<NodeParamView*>(GetTimeBasedWidget())->SetContexts(contexts, group_mode);
|
||||
}
|
||||
|
||||
void ParamPanel::Retranslate()
|
||||
|
||||
+1
-11
@@ -43,16 +43,6 @@ public:
|
||||
return GetParamView()->GetContexts();
|
||||
}
|
||||
|
||||
void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e)
|
||||
{
|
||||
GetParamView()->SetCreateCheckBoxes(e);
|
||||
}
|
||||
|
||||
void SetIgnoreNodeFlags(bool e)
|
||||
{
|
||||
GetParamView()->SetIgnoreNodeFlags(e);
|
||||
}
|
||||
|
||||
void CloseContextsBelongingToProject(Project *p)
|
||||
{
|
||||
GetParamView()->CloseContextsBelongingToProject(p);
|
||||
@@ -68,7 +58,7 @@ public slots:
|
||||
|
||||
virtual void DeselectAll() override;
|
||||
|
||||
void SetContexts(const QVector<Node*> &contexts);
|
||||
void SetContexts(const QVector<Node*> &contexts, bool group_mode);
|
||||
|
||||
signals:
|
||||
void RequestSelectNode(const QVector<Node*>& target);
|
||||
|
||||
@@ -37,9 +37,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) :
|
||||
super(true, false, parent),
|
||||
last_scroll_val_(0),
|
||||
focused_node_(nullptr),
|
||||
create_checkboxes_(kNoCheckBoxes),
|
||||
time_target_(nullptr),
|
||||
ignore_flags_(false)
|
||||
group_mode_(false)
|
||||
{
|
||||
// Create horizontal layout to place scroll area in (and keyframe editing eventually)
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
@@ -174,7 +173,7 @@ void NodeParamView::CloseContextsBelongingToProject(Project *p)
|
||||
}
|
||||
}
|
||||
|
||||
SetContexts(new_contexts);
|
||||
SetContexts(new_contexts, group_mode_);
|
||||
}
|
||||
|
||||
/*void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
|
||||
@@ -236,15 +235,7 @@ void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
|
||||
}
|
||||
}*/
|
||||
|
||||
void NodeParamView::SetInputChecked(const NodeInput &input, bool e)
|
||||
{
|
||||
input_checked_.insert(input, e);
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
ctx->SetInputChecked(input, e);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::SetContexts(const QVector<Node *> &contexts)
|
||||
void NodeParamView::SetContexts(const QVector<Node *> &contexts, bool group_mode)
|
||||
{
|
||||
//TIME_THIS_FUNCTION;
|
||||
|
||||
@@ -258,6 +249,9 @@ void NodeParamView::SetContexts(const QVector<Node *> &contexts)
|
||||
}
|
||||
|
||||
contexts_ = contexts;
|
||||
group_mode_ = group_mode;
|
||||
|
||||
Q_ASSERT((contexts_.size() == 1 && dynamic_cast<NodeGroup*>(contexts_.first())) || !group_mode_ || contexts_.isEmpty());
|
||||
|
||||
foreach (Node *ctx, contexts_) {
|
||||
// Queued so that if any further work is done in connecting this node to the context, it'll be
|
||||
@@ -285,6 +279,17 @@ void NodeParamView::SetContexts(const QVector<Node *> &contexts)
|
||||
}
|
||||
}
|
||||
|
||||
if (group_mode_) {
|
||||
// Check inputs that have been passed through
|
||||
NodeGroup *group = static_cast<NodeGroup*>(contexts_.first());
|
||||
for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) {
|
||||
GroupInputPassthroughAdded(group, it->second);
|
||||
}
|
||||
|
||||
connect(group, &NodeGroup::InputPassthroughAdded, this, &NodeParamView::GroupInputPassthroughAdded);
|
||||
connect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeParamView::GroupInputPassthroughRemoved);
|
||||
}
|
||||
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
SortItemsInContext(ctx);
|
||||
}
|
||||
@@ -386,24 +391,16 @@ void NodeParamView::QueueKeyframePositionUpdate()
|
||||
|
||||
void NodeParamView::AddNode(Node *n, NodeParamViewContext *context)
|
||||
{
|
||||
if ((n->GetFlags() & Node::kDontShowInParamView) && !ignore_flags_) {
|
||||
if ((n->GetFlags() & Node::kDontShowInParamView) && !group_mode_) {
|
||||
return;
|
||||
}
|
||||
|
||||
NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, context);
|
||||
NodeParamViewItem* item = new NodeParamViewItem(n, group_mode_ ? kCheckBoxesOnNonConnected : kNoCheckBoxes, context);
|
||||
|
||||
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal);
|
||||
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
|
||||
connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode);
|
||||
connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::SetInputChecked);
|
||||
|
||||
if (create_checkboxes_) {
|
||||
for (auto it=input_checked_.cbegin(); it!=input_checked_.cend(); it++) {
|
||||
if (it.key().node() == n) {
|
||||
item->SetInputChecked(it.key(), it.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::InputCheckBoxChanged);
|
||||
|
||||
item->SetTimeTarget(GetTimeTarget());
|
||||
item->SetTimebase(timebase());
|
||||
@@ -616,4 +613,35 @@ void NodeParamView::NodeAddedToContext(Node *n)
|
||||
SortItemsInContext(item);
|
||||
}
|
||||
|
||||
void NodeParamView::InputCheckBoxChanged(const NodeInput &input, bool e)
|
||||
{
|
||||
NodeGroup *group = static_cast<NodeGroup*>(contexts_.first());
|
||||
|
||||
if (e) {
|
||||
group->AddInputPassthrough(input);
|
||||
} else {
|
||||
group->RemoveInputPassthrough(input);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::GroupInputPassthroughAdded(NodeGroup *group, const NodeInput &input)
|
||||
{
|
||||
foreach (NodeParamViewContext *pvctx, context_items_) {
|
||||
NodeParamViewItem *item = pvctx->GetItems().value(input.node());
|
||||
if (item) {
|
||||
item->SetInputChecked(input, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::GroupInputPassthroughRemoved(NodeGroup *group, const NodeInput &input)
|
||||
{
|
||||
foreach (NodeParamViewContext *pvctx, context_items_) {
|
||||
NodeParamViewItem *item = pvctx->GetItems().value(input.node());
|
||||
if (item) {
|
||||
item->SetInputChecked(input, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,16 +45,6 @@ public:
|
||||
|
||||
virtual ~NodeParamView() override;
|
||||
|
||||
void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e)
|
||||
{
|
||||
create_checkboxes_ = e;
|
||||
}
|
||||
|
||||
bool IsInputChecked(const NodeInput &input) const
|
||||
{
|
||||
return input_checked_.value(input);
|
||||
}
|
||||
|
||||
void CloseContextsBelongingToProject(Project *p);
|
||||
|
||||
Node* GetTimeTarget() const;
|
||||
@@ -71,11 +61,6 @@ public:
|
||||
keyframe_view_->DeselectAll();
|
||||
}
|
||||
|
||||
void SetIgnoreNodeFlags(bool e)
|
||||
{
|
||||
ignore_flags_ = e;
|
||||
}
|
||||
|
||||
void SelectNodes(const QVector<Node*> &nodes);
|
||||
void DeselectNodes(const QVector<Node*> &nodes);
|
||||
|
||||
@@ -85,9 +70,7 @@ public:
|
||||
}
|
||||
|
||||
public slots:
|
||||
void SetInputChecked(const NodeInput &input, bool e);
|
||||
|
||||
void SetContexts(const QVector<Node*> &contexts);
|
||||
void SetContexts(const QVector<Node*> &contexts, bool group_mode);
|
||||
|
||||
void UpdateElementY();
|
||||
|
||||
@@ -136,16 +119,12 @@ private:
|
||||
|
||||
NodeParamViewItem* focused_node_;
|
||||
|
||||
NodeParamViewCheckBoxBehavior create_checkboxes_;
|
||||
|
||||
Node *time_target_;
|
||||
|
||||
QHash<NodeInput, bool> input_checked_;
|
||||
|
||||
bool ignore_flags_;
|
||||
|
||||
QVector<Node*> contexts_;
|
||||
|
||||
bool group_mode_;
|
||||
|
||||
private slots:
|
||||
void UpdateGlobalScrollBar();
|
||||
|
||||
@@ -157,6 +136,12 @@ private slots:
|
||||
|
||||
void NodeAddedToContext(Node *n);
|
||||
|
||||
void InputCheckBoxChanged(const NodeInput &input, bool e);
|
||||
|
||||
void GroupInputPassthroughAdded(olive::NodeGroup *group, const olive::NodeInput &input);
|
||||
|
||||
void GroupInputPassthroughRemoved(olive::NodeGroup *group, const olive::NodeInput &input);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QToolTip>
|
||||
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "node/factory.h"
|
||||
#include "node/group/group.h"
|
||||
#include "node/traverser.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/menu/menushared.h"
|
||||
#include "widget/timebased/timebasedview.h"
|
||||
|
||||
@@ -47,6 +49,7 @@ NodeView::NodeView(QWidget *parent) :
|
||||
create_edge_(nullptr),
|
||||
create_edge_output_item_(nullptr),
|
||||
create_edge_input_item_(nullptr),
|
||||
overlay_view_(nullptr),
|
||||
scale_(1.0)
|
||||
{
|
||||
setScene(&scene_);
|
||||
@@ -83,6 +86,10 @@ NodeView::~NodeView()
|
||||
|
||||
void NodeView::SetContexts(const QVector<Node*> &nodes)
|
||||
{
|
||||
if (overlay_view_) {
|
||||
CloseOverlay();
|
||||
}
|
||||
|
||||
// Remove contexts that are no longer in the list
|
||||
foreach (Node *n, contexts_) {
|
||||
if (!nodes.contains(n)) {
|
||||
@@ -350,6 +357,8 @@ void NodeView::keyPressEvent(QKeyEvent *event)
|
||||
break;
|
||||
}
|
||||
|
||||
emit EscPressed();
|
||||
|
||||
/* fall through */
|
||||
default:
|
||||
super::keyPressEvent(event);
|
||||
@@ -657,6 +666,10 @@ void NodeView::resizeEvent(QResizeEvent *event)
|
||||
super::resizeEvent(event);
|
||||
|
||||
RepositionMiniMap();
|
||||
|
||||
if (overlay_view_) {
|
||||
ResizeOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeView::UpdateSelectionCache()
|
||||
@@ -1284,7 +1297,37 @@ void NodeView::ShowNodeProperties()
|
||||
Node *first_node = selected_nodes_.first();
|
||||
|
||||
if (NodeGroup *group = dynamic_cast<NodeGroup*>(first_node)) {
|
||||
emit NodeGroupOpenRequested(group);
|
||||
if (!overlay_view_) {
|
||||
overlay_view_ = new NodeView(this);
|
||||
overlay_view_->show();
|
||||
|
||||
QPushButton *overlay_close_btn = new QPushButton(overlay_view_);
|
||||
overlay_close_btn->setIcon(icon::Error);
|
||||
int offset = overlay_close_btn->sizeHint().width()/2;
|
||||
overlay_close_btn->move(offset, offset);
|
||||
overlay_close_btn->show();
|
||||
|
||||
connect(overlay_view_, &NodeView::NodesSelected, this, &NodeView::NodesSelected);
|
||||
connect(overlay_view_, &NodeView::NodesDeselected, this, &NodeView::NodesDeselected);
|
||||
connect(overlay_view_, &NodeView::NodeGroupOpened, this, &NodeView::NodeGroupOpened);
|
||||
connect(overlay_view_, &NodeView::NodeGroupClosed, this, &NodeView::NodeGroupClosed);
|
||||
connect(overlay_view_, &NodeView::EscPressed, this, &NodeView::CloseOverlay);
|
||||
connect(overlay_close_btn, &QPushButton::clicked, this, &NodeView::CloseOverlay);
|
||||
|
||||
const QColor &bgcol = overlay_view_->palette().base().color();
|
||||
overlay_view_->setStyleSheet(QStringLiteral("QGraphicsView { background: rgba(%1, %2, %3, 0.8); }").arg(QString::number(bgcol.red()), QString::number(bgcol.green()), QString::number(bgcol.blue())));
|
||||
|
||||
overlay_close_btn->setStyleSheet(QStringLiteral("background: transparent; border: none;"));
|
||||
}
|
||||
overlay_view_->SetContexts({group});
|
||||
ResizeOverlay();
|
||||
QMetaObject::invokeMethod(overlay_view_, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection);
|
||||
overlay_view_->setFocus();
|
||||
|
||||
emit NodesDeselected(selected_nodes_);
|
||||
overlay_view_->SelectAll();
|
||||
|
||||
emit NodeGroupOpened(group);
|
||||
} else {
|
||||
LabelSelectedNodes();
|
||||
}
|
||||
@@ -1320,6 +1363,17 @@ void NodeView::ItemAboutToBeDeleted(NodeViewItem *item)
|
||||
}
|
||||
}
|
||||
|
||||
void NodeView::CloseOverlay()
|
||||
{
|
||||
if (overlay_view_->overlay_view_) {
|
||||
overlay_view_->CloseOverlay();
|
||||
}
|
||||
|
||||
overlay_view_->deleteLater();
|
||||
overlay_view_ = nullptr;
|
||||
emit NodeGroupClosed();
|
||||
}
|
||||
|
||||
void NodeView::AddContext(Node *n)
|
||||
{
|
||||
NodeViewContext *ctx = scene_.AddContext(n);
|
||||
@@ -1477,6 +1531,11 @@ void NodeView::PostPaste(const QVector<Node *> &new_nodes, const Node::PositionM
|
||||
SetAttachedItems(new_attached);
|
||||
}
|
||||
|
||||
void NodeView::ResizeOverlay()
|
||||
{
|
||||
overlay_view_->resize(this->size());
|
||||
}
|
||||
|
||||
void NodeView::SetAttachedItems(const QVector<AttachedItem> &items)
|
||||
{
|
||||
// Detach anything currently attached
|
||||
|
||||
@@ -54,7 +54,16 @@ public:
|
||||
|
||||
const QVector<Node*> &GetContexts() const
|
||||
{
|
||||
return contexts_;
|
||||
if (overlay_view_) {
|
||||
return overlay_view_->GetContexts();
|
||||
} else {
|
||||
return contexts_;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsGroupOverlay() const
|
||||
{
|
||||
return overlay_view_;
|
||||
}
|
||||
|
||||
void CloseContextsBelongingToProject(Project *project);
|
||||
@@ -109,7 +118,10 @@ signals:
|
||||
|
||||
void NodesDeselected(const QVector<Node*>& nodes);
|
||||
|
||||
void NodeGroupOpenRequested(NodeGroup *group);
|
||||
void NodeGroupOpened(NodeGroup *group);
|
||||
void NodeGroupClosed();
|
||||
|
||||
void EscPressed();
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent(QKeyEvent *event) override;
|
||||
@@ -169,6 +181,8 @@ private:
|
||||
|
||||
void PostPaste(const QVector<Node*> &new_nodes, const Node::PositionMap &map);
|
||||
|
||||
void ResizeOverlay();
|
||||
|
||||
NodeViewMiniMap *minimap_;
|
||||
|
||||
struct AttachedItem {
|
||||
@@ -202,6 +216,8 @@ private:
|
||||
|
||||
QMap<NodeViewItem*, QPointF> dragging_items_;
|
||||
|
||||
NodeView* overlay_view_;
|
||||
|
||||
double scale_;
|
||||
|
||||
static const double kMinimumScale;
|
||||
@@ -252,6 +268,8 @@ private slots:
|
||||
|
||||
void ItemAboutToBeDeleted(NodeViewItem *item);
|
||||
|
||||
void CloseOverlay();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
// Make node-related connections
|
||||
connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes);
|
||||
connect(node_panel_, &NodePanel::NodesDeselected, param_panel_, &ParamPanel::DeselectNodes);
|
||||
connect(node_panel_, &NodePanel::NodeGroupOpenRequested, this, &MainWindow::NodeGroupRequested);
|
||||
connect(node_panel_, &NodePanel::NodeGroupOpened, this, &MainWindow::NodePanelGroupOpenedOrClosed);
|
||||
connect(node_panel_, &NodePanel::NodeGroupClosed, this, &MainWindow::NodePanelGroupOpenedOrClosed);
|
||||
connect(param_panel_, &ParamPanel::RequestSelectNode, this, [this](const QVector<Node*>& target){
|
||||
node_panel_->Select(target, true);
|
||||
});
|
||||
@@ -455,15 +456,10 @@ void MainWindow::StatusBarDoubleClicked()
|
||||
task_man_panel_->raise();
|
||||
}
|
||||
|
||||
void MainWindow::NodeGroupRequested(NodeGroup *group)
|
||||
void MainWindow::NodePanelGroupOpenedOrClosed()
|
||||
{
|
||||
NodePanel *panel = new NodePanel(this);
|
||||
panel->setFloating(true);
|
||||
panel->setVisible(true);
|
||||
panel->SetContexts({group});
|
||||
panel->SetSignalInsteadOfClose(true);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, panel);
|
||||
connect(panel, &NodePanel::CloseRequested, panel, &NodePanel::deleteLater);
|
||||
NodePanel *p = static_cast<NodePanel*>(sender());
|
||||
param_panel_->SetContexts(p->GetContexts(), p->IsGroupOverlay());
|
||||
}
|
||||
|
||||
void MainWindow::TimelinePanelSelectionChanged(const QVector<Block *> &blocks)
|
||||
@@ -732,7 +728,7 @@ void MainWindow::UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel)
|
||||
}
|
||||
|
||||
node_panel_->SetContexts(context);
|
||||
param_panel_->SetContexts(context);
|
||||
param_panel_->SetContexts(context, false);
|
||||
}
|
||||
|
||||
void MainWindow::FocusedPanelChanged(PanelWidget *panel)
|
||||
@@ -747,10 +743,7 @@ void MainWindow::FocusedPanelChanged(PanelWidget *panel)
|
||||
const QVector<Node*> &new_ctxs = node_panel->GetContexts();
|
||||
|
||||
if (new_ctxs != param_panel_->GetContexts()) {
|
||||
bool is_default_node_panel = node_panel == node_panel_;
|
||||
param_panel_->SetIgnoreNodeFlags(!is_default_node_panel);
|
||||
param_panel_->SetCreateCheckBoxes(is_default_node_panel ? kNoCheckBoxes : kCheckBoxesOnNonConnected);
|
||||
param_panel_->SetContexts(node_panel->GetContexts());
|
||||
param_panel_->SetContexts(new_ctxs, node_panel->IsGroupOverlay());
|
||||
}
|
||||
} else if (TimelinePanel* timeline = dynamic_cast<TimelinePanel*>(panel)) {
|
||||
// Signal timeline focus
|
||||
|
||||
@@ -185,7 +185,7 @@ private slots:
|
||||
|
||||
void StatusBarDoubleClicked();
|
||||
|
||||
void NodeGroupRequested(NodeGroup *group);
|
||||
void NodePanelGroupOpenedOrClosed();
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
void ShowNouveauWarning();
|
||||
|
||||
Reference in New Issue
Block a user