Search code examples
javascriptqtecmascript-3

Use of QtWidgets crashes Toon Boom Harmony while trying to use variable/object from another scope


So I'm trying to create some kind of animation pipeline tool that will handle saving, loading, etc. All of this should be a separate tab in a tab widget.

For this purpose I have a base widget class and then the functional widget itself, which inherits from the base widget.

WidgetBase class:

var FrosyaWidgetBase = function(asUiFilePath, asWidgetName) {
    this._msWidgetName = asWidgetName;
    this._msUiFilePath = asUiFilePath;
}
 
FrosyaWidgetBase.prototype.loadUi = function() {

}

FrosyaWidgetBase.prototype.setupSignals = function() {

}

FrosyaWidgetBase.prototype.setWidgetLayout = function(aWidget) {
    FrosyaWidgetBase.prototype.setLayout(aWidget);
}

FrosyaWidgetBase.prototype.getWidgetName = function() {
    return this._msWidgetName;
}

SaveLoadWidget class:

var FrosyaSaveLoadWidget = function() {
    FrosyaWidgetBase.call(this, MEL_FROSYA_RES + "/ui/SaveLoadWidget.ui", "SaveLoadWidget");

    this._msWidgetTitle = "Save/Load";
    this._maProjectList = {};
    this._mWidgetUi = UiLoader.load(this._msUiFilePath);

    var _xmlReader = new XMLReader(MEL_FROSYA_CFG + "/ProjectPaths.xml");
    _xmlReader.read();
    projArray = _xmlReader.findAllElelemntsByNodeName("project");
    for (var i = 0; i < projArray.length; i++) {
        var projName = _xmlReader.getNodeAttributeValue(projArray[i], "name");
        var projPath = _xmlReader.getNodeAttributeValue(projArray[i], "path");
        var projType = _xmlReader.getNodeAttributeValue(projArray[i], "type");

        this._maProjectList[projName] = ProjectFactory(projPath, projName, projType);
    }

    this.addTreeWidgetRoot = function(asName) {
        treeItem = new QTreeWidgetItem(this._mWidgetUi.SearchTreeWidget);
        treeItem.setText(0, asName);
        return treeItem;
    }

    this.addTreeWidgetChild = function(asName) {

    }

    this.loadUi();
    this.setupSignals();
}

FrosyaSaveLoadWidget.prototype = Object.create(FrosyaWidgetBase.prototype);

FrosyaSaveLoadWidget.prototype.startSearch = function() {
    this._mWidgetUi.ProjectSearch.blockSignals(true);
    
    const searchAssetType = this._mWidgetUi.TypeComboBox.currentText;
    const selectedItem = this._mWidgetUi.ProjectComboBox.currentText;
    const searchText = this._mWidgetUi.ProjectSearch.text;
    const project = this._maProjectList[selectedItem];

    var searchResult = project.findAsset(searchText, searchAssetType);
    for (var i = 0; i < searchResult.length; i++) {
        this.addTreeWidgetRoot(searchResult[i]);
    }

    this._mWidgetUi.ProjectSearch.blockSignals(false);
}

FrosyaSaveLoadWidget.prototype.setupSignals = function() {
    this._mWidgetUi.ProjectSearch.returnPressed.connect(this, this.startSearch);
}

FrosyaSaveLoadWidget.prototype.loadUi = function() {
    for (var proj in this._maProjectList) {
        this._mWidgetUi.ProjectComboBox.addItem(proj);
    }

    this._mWidgetUi.TypeComboBox.addItem("Animation");
    this._mWidgetUi.TypeComboBox.addItem("L-out");
}

FrosyaSaveLoadWidget.prototype.widget = function() {
    return this._mWidgetUi;
}

As you can see, widget ui loads via UiLoader, and then I setup returnPressed signal which leads to the function using project class findAsset method from another JS file.

project.findAsset method:

TVProject.prototype.findAsset = function(asSearchText, asSearchAssetType) {
    var result = []

    if (asSearchAssetType == "Animation") {
        result = this.findAnimationScene(asSearchText);
    } else if (asSearchAssetType == "L-Out") {
        result = this.findLayoutScene(asSearchText);
    }

    return result;
}

TVProject.prototype.findLayoutScene = function(asSearchText) {
    return;
}

TVProject.prototype.findAnimationScene = function(asSearchText) {
    var srIndx = (asSearchText.match(/sr\d+/)) ? asSearchText.match(/sr\d+/)[0] : "";
    var epIndx = (asSearchText.match(/ep\d+/)) ? asSearchText.match(/ep\d+/)[0] : "";
    var scIndx = (asSearchText.match(/sc\d+/)) ? asSearchText.match(/sc\d+/)[0] : "";

    if (srIndx && epIndx && scIndx) {
        var dir = this.getAnimationPath().path() + "/" + srIndx + "/" + epIndx + "/" + scIndx;
        if (isDir(dir)) {
            return [dir];
        }
        return [];
    }

    var srIndxHasExactValue = true;
    var epIndxHasExactValue = true;
    var scIndxHasExactValue = true;

    if (!srIndx) {
        srIndx = "sr001";
        srIndxHasExactValue = false;
    }

    if (!epIndx) {
        epIndx = "ep01";
        epIndxHasExactValue = false;
    }

    if (!scIndx) {
        scIndx = "sc01";
        scIndxHasExactValue = false;
    }

    var firstLevelDirs  = [srIndx];
    var secondLevelDirs = [epIndx];
    var thirdLevelDirs  = [scIndx];
    var result = [];

    if (!srIndxHasExactValue) {
        firstLevelDirs = this.getAnimationPath().entryList(QDir.Filters(QDir.Dirs, QDir.NoDotAndDotDot, QDir.NoSymLinks));
    }

    for (var i = 0; i < firstLevelDirs.length; i++) {
        var srDir = new QDir(this.getAnimationPath().path() + "/" + firstLevelDirs[i]);
        secondLevelDirs = [epIndx];

        if (!epIndxHasExactValue) {
            secondLevelDirs = srDir.entryList(QDir.Filters(QDir.Dirs, QDir.NoDotAndDotDot, QDir.NoSymLinks));
        }
        
        for (var j = 0; j < secondLevelDirs.length; j++) {
            var epDir = new QDir(srDir.path() + "/" + secondLevelDirs[j]);
            thirdLevelDirs = [scIndx];
            
            if (!scIndxHasExactValue) {
                thirdLevelDirs = epDir.entryList(QDir.Filters(QDir.Dirs, QDir.NoDotAndDotDot, QDir.NoSymLinks));
            }
            
            for (var k = 0; k < thirdLevelDirs.length; k++) {
                result.push(epDir.path() + "/" + thirdLevelDirs[k]);
            }
        }
    }

    return result;
}

Problem:

With the first run of the script all works just fine, but when I close the the widget window and rerun the script, Toon Boom immediately crashes with error:

QThread: destroyed while thread is still running

The same happens if try to access MessageLog object from the connected function. Actually this particular case can be solved with adding MessageLog to the function scope like:

this._console = MessageLog. 

Also, if I try to run the script for the first time using the Toon Boom debugger, it will also throw an error like:

Uncaught exception at Project.js:200: TypeError: Result of expression 'QDir' [undefined] is not an object

How can I add objects into another object scope or something like that?


Solution

  • Okay, I figured it out. You need to work with QDialog widgets only. I think because it's the only widget that have exec method which is runs separate loop instead of working in Toon Boom loop. So, all the subwidgets may be any type you want, but the main one should be QDialog.