Skip to content

Commit

Permalink
Merge pull request #78 from mappu/miqt-subclass
Browse files Browse the repository at this point in the history
Add subclassing support
  • Loading branch information
mappu authored Nov 19, 2024
2 parents 47d4581 + 60e0968 commit 7d59d56
Show file tree
Hide file tree
Showing 2,996 changed files with 765,142 additions and 56,523 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ examples/mdoutliner/mdoutliner6
examples/windowsmanifest/windowsmanifest
examples/uidesigner/uidesigner
examples/trivialwizard6/trivialwizard6
examples/subclass/subclass
examples/libraries/extras-scintillaedit/extras-scintillaedit
examples/libraries/qt-multimedia/qt-multimedia
examples/libraries/qt-network/qt-network
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ Where Qt returns a C++ object by value (e.g. `QSize`), the binding may have move

The `connect(sourceObject, sourceSignal, targetObject, targetSlot)` is projected as `targetObject.onSourceSignal(func()...)`.

- You can also override virtual methods like PaintEvent in the same way. Your callback `func()` receives `super()` as a first argument that can be used to call the base class implementation.

Qt class inherited types are projected as a Go embedded struct. For example, to pass a `var myLabel *qt.QLabel` to a function taking only the `*qt.QWidget` base class, write `myLabel.QWidget`.

- When a Qt subclass adds a method overload (e.g. `QMenu::addAction(QString)` vs `QWidget::addAction(QAction*)`), the base class version is shadowed and can only be called via `myQMenu.QWidget.AddAction(QAction*)`.
Expand Down
78 changes: 58 additions & 20 deletions cmd/genbindings/clang2il.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ func processTypedef(node map[string]interface{}, addNamePrefix string) (CppTyped
return CppTypedef{}, errors.New("processTypedef: ???")
}

type visibilityState int

const (
VsPublic visibilityState = 1
VsProtected = 2
VsPrivate = 3
)

// processClassType parses a single C++ class definition into our intermediate format.
func processClassType(node map[string]interface{}, addNamePrefix string) (CppClass, error) {
var ret CppClass
Expand Down Expand Up @@ -229,10 +237,10 @@ func processClassType(node map[string]interface{}, addNamePrefix string) (CppCla
}
}

// Check if this was 'struct' (default visible) or 'class' (default invisible)
visibility := true
// Check if this was 'struct' (default public) or 'class' (default private)
visibility := VsPublic
if tagUsed, ok := node["tagUsed"].(string); ok && tagUsed == "class" {
visibility = false
visibility = VsPrivate
}

// Check if this is an abstract class
Expand All @@ -257,7 +265,7 @@ func processClassType(node map[string]interface{}, addNamePrefix string) (CppCla

if typ, ok := base["type"].(map[string]interface{}); ok {
if qualType, ok := typ["qualType"].(string); ok {
ret.Inherits = append(ret.Inherits, qualType)
ret.DirectInherits = append(ret.DirectInherits, qualType)
}
}
}
Expand Down Expand Up @@ -289,9 +297,11 @@ nextMethod:

switch access {
case "public":
visibility = true
case "private", "protected":
visibility = false
visibility = VsPublic
case "protected":
visibility = VsProtected
case "private":
visibility = VsPrivate
default:
panic("unexpected access visibility '" + access + "'")
}
Expand All @@ -315,7 +325,7 @@ nextMethod:
// Child class type definition e.g. QAbstractEventDispatcher::TimerInfo
// Parse as a whole child class

if !visibility {
if visibility != VsPublic {
continue // Skip private/protected
}

Expand All @@ -341,8 +351,8 @@ nextMethod:
case "EnumDecl":
// Child class enum

if !visibility {
continue // Skip private/protected
if visibility == VsPrivate {
continue // Skip private, ALLOW protected
}

en, err := processEnum(node, nodename+"::")
Expand All @@ -359,7 +369,7 @@ nextMethod:
// This is an implicit ctor. Therefore the class is constructable
// even if we're currently in a `private:` block.

} else if !visibility {
} else if visibility != VsPublic {
continue // Skip private/protected
}

Expand Down Expand Up @@ -403,7 +413,8 @@ nextMethod:
continue
}

if !visibility {
if visibility != VsPublic {
// TODO Is there any use case for allowing MIQT to overload a virtual destructor?
ret.CanDelete = false
continue
}
Expand All @@ -415,21 +426,26 @@ nextMethod:
}

case "CXXMethodDecl":
if !visibility {
continue // Skip private/protected
}

// Check if this is `= delete`
if isExplicitlyDeleted(node) {
continue
}

// Method
methodName, ok := node["name"].(string)
if !ok {
return CppClass{}, errors.New("method has no name")
}

// If this is a virtual method, we want to allow overriding it even
// if it is protected
// But we can only call it if it is public
if visibility == VsPrivate {
ret.PrivateMethods = append(ret.PrivateMethods, methodName)
continue // Skip private, ALLOW protected
}

// Check if this is `= delete`
if isExplicitlyDeleted(node) {
continue
}

var mm CppMethod
mm.MethodName = methodName

Expand All @@ -445,6 +461,14 @@ nextMethod:
}

mm.IsSignal = isSignal && !mm.IsStatic && AllowSignal(mm)
mm.IsProtected = (visibility == VsProtected)

if mm.IsProtected && !mm.IsVirtual {
// Protected method, so we can't call it
// Non-virtual, so we can't override it
// There is nothing we can do with this function
continue nextMethod
}

// Once all processing is complete, pass to exceptions for final decision

Expand Down Expand Up @@ -648,6 +672,14 @@ func parseMethod(node map[string]interface{}, mm *CppMethod) error {
mm.IsStatic = true
}

if virtual, ok := node["virtual"].(bool); ok && virtual {
mm.IsVirtual = true
}

if pure, ok := node["pure"].(bool); ok && pure {
mm.IsPureVirtual = true
}

if methodInner, ok := node["inner"].([]interface{}); ok {
paramCounter := 0
for _, methodObj := range methodInner {
Expand Down Expand Up @@ -690,6 +722,12 @@ func parseMethod(node map[string]interface{}, mm *CppMethod) error {
// Next
paramCounter++

case "OverrideAttr":
// void keyPressEvent(QKeyEvent *e) override;
// This is a virtual method being overridden and is a replacement
// for actually using the 'virtual' keyword
mm.IsVirtual = true

default:
// Something else inside a declaration??
log.Printf("==> NOT IMPLEMENTED CXXMethodDecl->%q\n", methodObj["kind"])
Expand Down
15 changes: 14 additions & 1 deletion cmd/genbindings/config-allowlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ func ImportHeaderForClass(className string) bool {

func AllowClass(className string) bool {

if strings.HasSuffix(className, "Private") || strings.HasSuffix(className, "PrivateShared") {
if strings.HasSuffix(className, "Private") || strings.HasSuffix(className, "PrivateShared") ||
strings.Contains(className, "Private::") || strings.HasSuffix(className, "PrivateShared::") {
return false
}

Expand Down Expand Up @@ -186,6 +187,15 @@ func AllowSignal(mm CppMethod) bool {
}
}

func AllowVirtual(mm CppMethod) bool {

if mm.MethodName == "metaObject" || mm.MethodName == "qt_metacast" {
return false
}

return true // AllowSignal(mm)
}

func AllowMethod(className string, mm CppMethod) error {

for _, p := range mm.Parameters {
Expand Down Expand Up @@ -436,6 +446,9 @@ func AllowType(p CppParameter, isReturnType bool) error {
"QAbstractAudioBuffer", // Qt 5 Multimedia, this is a private/internal type only
"QAbstractVideoBuffer", // Works in Qt 5, but in Qt 6 Multimedia this type is used in qvideoframe.h but is not defined anywhere (it was later added in Qt 6.8)
"QRhi", // Qt 6 unstable types, used in Multimedia
"QPostEventList", // Qt QCoreApplication: private headers required
"QMetaCallEvent", // ..
"QPostEvent", // ..
"____last____":
return ErrTooComplex
}
Expand Down
Loading

0 comments on commit 7d59d56

Please sign in to comment.