Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
16f8d01
Add C++ ScriptConstructor to replace QML filter/computed-column drag-…
Aug 26, 2026
242510e
Fix ScriptConstructor view bugs found in review
Aug 26, 2026
bb9b27a
Finish ScriptConstructor rewrite: R display, sqrt/!, fuzz test
Aug 26, 2026
38a1397
Fix ScriptConstructor column type cycling and right-click removal
Aug 26, 2026
76a6c82
Improve ScriptConstructor column type and row-function UX
Aug 26, 2026
c13c0e7
Use a non-modal tooltip for the column-type restriction message
Aug 26, 2026
dd81314
Add commas/parentheses, centre operator bar, autosize palettes
Aug 26, 2026
03a22e0
Fix sqrt radical rendering and add constructor background watermark
Aug 26, 2026
a42a003
stretch
Aug 26, 2026
e809dab
Restore translatable tooltips on ScriptConstructor elements
Aug 26, 2026
52c74d9
Translate ScriptConstructor tooltips at display time
Aug 26, 2026
abcb2ae
Fix division operators and filter watermark in ScriptConstructor
Aug 27, 2026
a648b5b
Fix constructor watermark, trash double-click, and drop-spot centering
Aug 27, 2026
24d7d21
Wire ScriptConstructor undo and make computed-column apply undoable
Aug 27, 2026
dfb308d
add 1 pixel space above args of sqrt and make background image actual…
JorisGoosen Aug 27, 2026
1958f38
also make a bit of space above the lower division arg
JorisGoosen Aug 27, 2026
8f81fda
Fix drop-spot hover outlines and watermark resize staleness
Aug 27, 2026
5f006a0
Speed up ScriptConstructor load, silence QML warnings, add profiling …
Aug 27, 2026
b19b956
Defer ScriptConstructor chrome until visible, native tooltips, per-ki…
Aug 27, 2026
93c2c60
ScriptConstructor: skip double init build, add fine-grained init time…
Sep 3, 2026
0e507a6
ScriptConstructor: load image leaves asynchronously so PNG decode no …
Sep 3, 2026
d30f313
ScriptConstructor trash: drop the visible border and apply the emptie…
Sep 3, 2026
cf43a57
ScriptConstructor trash: proper ScriptTrashItem with hand cursor + do…
Sep 3, 2026
bca66fb
Remove the temporary ScriptConstructor trash diagnostics (kept the de…
Sep 3, 2026
b0efd20
ScriptConstructor: best-spot drop resolution - leftmost-empty fill an…
Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
669 changes: 669 additions & 0 deletions CommonData/scriptconstructormodel.cpp

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions CommonData/scriptconstructormodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#ifndef SCRIPTCONSTRUCTORMODEL_H
#define SCRIPTCONSTRUCTORMODEL_H

#include <QObject>
#include <QUndoCommand>
#include <json/json.h>
#include <vector>
#include "scriptnode.h"
#include "scriptconstructorregistry.h"

class QUndoStack;

/// Describes a place where a ScriptNode can be (or is requested to be) inserted.
struct DropTarget
{
enum class Kind { None, Root, OperatorLeft, OperatorRight, FunctionArg, RowFunctionArg };

Kind kind = Kind::None;
ScriptNode * parent = nullptr; ///< Node owning the slot (nullptr for Root)
int index = -1; ///< Formula index for Root, argument index for Function/RowFunction
stringvec dropKeys; ///< Keys accepted at this spot
bool optional = false; ///< Empty spot does not fail completeness checks
bool dropsNested = false; ///< Content dropped here renders nested (e.g. operators get parentheses)

bool isValid() const { return kind != Kind::None; }
bool isRoot() const { return kind == Kind::Root; }
bool accepts(ScriptNode * node) const;

static DropTarget none() { return {}; }
static DropTarget root(int formulaIndex = -1) { DropTarget t; t.kind = Kind::Root; t.index = formulaIndex; return t; }
};

///
/// Owns the tree of ScriptNodes that make up a drag-and-drop filter / computed column formula.
/// All mutations go through this class so that undo and change-notification have a single source.
/// It is UI-agnostic: the QQuickItem view layer renders the tree and forwards user gestures here.
class ScriptConstructorModel : public QObject
{
Q_OBJECT

public:
explicit ScriptConstructorModel(QObject * parent = nullptr);
~ScriptConstructorModel() override;

// (Re)build the whole tree from stored JSON. Does not push undo.
void fromJson(const std::string & json);
void fromJson(const Json::Value & json);

std::string toString() const; ///< {"formulas":[...]}
Json::Value toJson() const;
std::string toR() const; ///< R code for all formulas

bool checkCompleteness() const; ///< true when every required slot is filled
bool allBoolean() const; ///< true when every root formula returns boolean
int formulaCount() const { return static_cast<int>(_formulas.size()); }
ScriptNode * formulaAt(int index) const;
const std::vector<ScriptNode*> & formulas() const { return _formulas; }

ScriptConstructorMode mode() const { return _mode; }
void setMode(ScriptConstructorMode mode) { _mode = mode; }

void setColumnTypeProvider(const ScriptColumnTypeProvider * provider) { _typeProvider = provider; }
const ScriptColumnTypeProvider * columnTypeProvider() const { return _typeProvider; }

void setUndoStack(QUndoStack * stack) { _undoStack = stack; }

// --- editing operations (each pushes an undo command when an undo stack is set) ---
void insertNode(ScriptNode * node, DropTarget target); ///< takes ownership of node
void removeNode(ScriptNode * node); ///< deletes the node subtree
void moveNode(ScriptNode * node, DropTarget target);
void setColumnTypeUser(ScriptNodeColumn * node, int columnType);
void setLiteralNumber(ScriptNodeLiteral * node, double value);
void setLiteralBool(ScriptNodeLiteral * node, bool value);
void setLiteralString(ScriptNodeLiteral * node, const std::string & value);
void clear();

/// Resolves where a freshly created node should go when the user did not drop it anywhere specific.
DropTarget findReasonableInsertionSpot(ScriptNode * node) const;

/// Returns the column types (1=scale, 2=ordinal, 3=nominal) that the containing drop slot
/// accepts. At the root (no parent) all three are returned.
std::vector<int> allowedColumnTypes(ScriptNode * node) const;

/// Returns the drop keys accepted at a given target (used by the view for hover feedback).
static bool keysOverlap(const stringvec & a, const stringvec & b);

signals:
void changed(); ///< tree contents changed (any edit)
void reset(); ///< whole tree rebuilt (fromJson/clear/undo); view must rebuild items

private:
void deleteAllFormulas();
void detachFromParent(ScriptNode * node);
void placeAt(ScriptNode * node, const DropTarget & target);
void resolveColumnTypeDrop(ScriptNodeColumn * col, const stringvec & slotKeys);
ScriptNode * rootFormulaOf(ScriptNode * node) const;
int rootIndexOf(ScriptNode * node) const;
bool isAncestor(ScriptNode * ancestor, ScriptNode * descendant) const;
bool tryGobbleLeft(ScriptNode * node);

void beginEdit();
void endEdit(const QString & description);

std::vector<ScriptNode*> _formulas;
ScriptConstructorMode _mode = ScriptConstructorMode::Filter;
const ScriptColumnTypeProvider * _typeProvider = nullptr;
QUndoStack * _undoStack = nullptr;
std::string _editBeforeJson;
};

/// Undo command that snapshots the constructor JSON before and after an edit.
class ScriptConstructorEditCommand : public QUndoCommand
{
public:
ScriptConstructorEditCommand(ScriptConstructorModel * model, const std::string & beforeJson, const std::string & afterJson, const QString & description);

void undo() override;
void redo() override;

private:
ScriptConstructorModel * _model;
std::string _beforeJson,
_afterJson;
bool _firstRedo = true;
};

#endif // SCRIPTCONSTRUCTORMODEL_H
Loading
Loading