Tutorial: Interaktive Skript Actions

Inhaltsverzeichnis

 

Einleitung

Script Actions sind Skripte, die einen Eintrag in ein Menü und/oder eine Symbolleiste einfügen und die Benutzerinteraktionen handhaben können. Script Aktionen bleiben solange aktiv, bis sie vom Benutzer beendet werden oder sich selbst beenden.

Ereignisse (Events)

Sobald die Skript Action gestartet wird, behandelt sie verschiedene Ereignisse, bis sie beendet wird. Ein Ereignis ist etwas, das eintritt, wenn etwas passiert. Zum Beispiel, wenn die Skript Action gestartet wird, wird beginEvent aufgerufen. Wenn der Benutzer auf ein Objekt klickt, wird ein pickEntity Ereignis ausgelöst, wenn der Benutzer auf eine Koordinate klickt, tritt ein pickCoordinate Ereignis auf, etc.

Die minimale Struktur einer Skript-Aktion ist wie folgt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
include("scripts/EAction.js");

function ExMinimal(guiAction) {
    EAction.call(this, guiAction);
}

ExMinimal.prototype = new EAction();

ExMinimal.init = function(basePath) {
    var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setScriptFile(basePath + "/ExMinimal.js");
    action.setGroupSortOrder(100000);
    action.setSortOrder(0);
    action.setWidgetNames(["ExamplesMenu"]);
};

Dieses Beispielskript fügt ein Menü am unteren Rand des Menüs Diverses > Beispiele hinzu. Der Menütext lautet "Minimal Example".

Hinzufügen von beginEvent

Das obige Skript ist voll funktionsfähig und kann gestartet werden. Allerdings macht es nichts, wenn es ausgelöst wird. Ausserdem bleibt das Script nach dem Start so lange aktiv, bis der Benutzer es mit der rechten Maustaste beendet. Um dies zu ändern, implementieren wir beginEvent, um etwas in den Befehlszeilenverlauf von QCAD auszugeben und die Aktion zu beenden:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
include("scripts/EAction.js");

function ExMinimal(guiAction) {
    EAction.call(this, guiAction);
}

ExMinimal.prototype = new EAction();

ExMinimal.prototype.beginEvent = function() {
    EAction.prototype.beginEvent.call(this);

    EAction.handleUserMessage("Hello World!");

    this.terminate();
};

ExMinimal.init = function(basePath) {
    var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setScriptFile(basePath + "/ExMinimal.js");
    action.setGroupSortOrder(100000);
    action.setSortOrder(0);
    action.setWidgetNames(["ExamplesMenu"]);
};

Wenn nun das Werkzeug Diverses > Beispiele > Minimal Example gestartet wird, gibt es "Hello World!" aus.

Wenn ein Skript keine Benutzerinteraktion erfordert, kann ein solches Skript verwendet werden, das etwas tut und dann beendet wird. Beispiele für solche Actions sind Ansicht > Autozoom, Auswählen > Alles auswählen, Bearbeiten > Löschen, etc.

Interaktion hinzufügen

Sobald ein Skript irgendeine Art von Benutzerinteraktion erfordert, müssen wir mehr Ereignissbehandler implementieren und dem Skript mitteilen, was der Benutzer als nächstes tun muss (z.B. ein Objekt auswählen oder eine Koordinate definieren). Im nächsten Schritt schalten wir ein einen Zustand, in dem die Action eine Koordinate vom Benutzer erwartet. Wir zeichnen dann einen Kreis an jeder Position, die der Benutzer anklickt oder eingibt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
include("scripts/EAction.js");

function ExMinimal(guiAction) {
    EAction.call(this, guiAction);

    this.pos = undefined;
}

ExMinimal.prototype = new EAction();

ExMinimal.prototype.beginEvent = function() {
    EAction.prototype.beginEvent.call(this);

    var di = this.getDocumentInterface();
    di.setClickMode(RAction.PickCoordinate);
};

ExMinimal.prototype.pickCoordinate = function(event, preview) {
    this.pos = event.getModelPosition();

    if (preview) {
        this.updatePreview();
    }
    else {
        this.applyOperation();
    }
};

ExMinimal.prototype.getOperation = function(preview) {
    var doc = this.getDocument();

    var op = new RAddObjectOperation();
    var circle = new RCircle(this.pos, 1);
    op.addObject(shapeToEntity(doc, circle));
    return op;
};

ExMinimal.init = function(basePath) {
    var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setScriptFile(basePath + "/ExMinimal.js");
    action.setGroupSortOrder(100000);
    action.setSortOrder(0);
    action.setWidgetNames(["ExamplesMenu"]);
};

Im beginEvent beenden wir die Aktion nicht mehr sofort, sondern lassen sie laufen, bis der Benutzer sie beendet (Rechtsklick oder Escape). Dann setzen wir pickCoordinate ein, um die Position des Mauszeigers oder der eingegebenen Koordinate zu speichern und entweder die Vorschau zu aktualisieren oder die gewünschte Operation anzuwenden (d.h. den Kreis hinzuzufügen). pickCoordinate wird immer dann aufgerufen, wenn der Benutzer die Maus bewegt, um eine Vorschau der geplanten Operation anzuzeigen. Wenn der Benutzer eine Koordinate anklickt oder eingibt, wird sie aufgerufen, wobei der Parameter preview dann auf false gesetzt ist, um anzuzeigen, dass eine endgültige Koordinate ausgewählt oder eingegeben wurde.

updatePreview auf Linie 22 zeigt die von getOperation zurückgegebene Operation an, während applyOperation auf Linie 25 die Operation tatsächlich auf unser Dokument anwendet.

getOperation muss implementiert sein, um die Operation in die Vorschau oder auf das Dokument anzuwenden. Dies ist etwas komplexer als das, was wir in der einfachen API oben gesehen haben. Dies liegt daran, dass mit einer einzigen Operation mehrere Objekte hinzugefügt, modifiziert oder gelöscht werden können.

Hinzufügen von Widgets zur Optionenwerkzeugleiste

Der in unserem Beispiel gezeichnete Kreis hat immer einen Radius von einer Zeicheneinheit (siehe Zeile 33). In einem nächsten Schritt wollen wir dem Benutzer erlauben, einen Radius für den Kreis einzugeben. QCAD verwendet in der Regel die Optionenwerkzeugleiste oben, um solche Werkzeugparameter anzuzeigen und zu ändern. Dazu müssen wir definieren, welche Widgets wir in der Optionsleiste anzeigen wollen und welche Parameter sie steuern. Dies kann mit einer UI-Datei geschehen, einer XML-Datei, die ein Widget und dessen Inhalt definiert. UI-Dateien können bequem mit einer Software namens Qt Designer erstellt werden, die Bestandteil des Qt-Toolkits ist. Für dieses Beispiel verwenden wir eine einfache UI-Datei, die auch in einem Texteditor erstellt werden kann (Datei ExMinimal.ui):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ExMinimal</class>
 <widget class="QWidget" name="ExMinimal">
  <layout class="QHBoxLayout">
   <item>
    <widget class="QLabel" name="RadiusLabel">
     <property name="text">
      <string>&amp;Radius:</string>
     </property>
     <property name="buddy">
      <cstring>Radius</cstring>
     </property>
    </widget>
   </item>
   <item>
    <widget class="RMathLineEdit" name="Radius">
     <property name="text">
      <string notr="true">1</string>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <customwidgets>
  <customwidget>
   <class>RMathLineEdit</class>
   <extends>QLineEdit</extends>
   <header>RMathLineEdit.h</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

Die UI-Datei definiert zwei Widgets: ein Textlabel (QLabel) und eine Eingabefeld (RMathLineEdit). Wichtig ist der Name des Eingabefeldes ("Radius"). Das Widget wird über diesen Namen automatisch mit unserem Skript verknüpft. Alles, was wir in unserem Skript tun müssen, ist zu definieren, welche UI-Datei wir verwenden wollen (Zeile 9) und einen neuen Ereignissbehandler namens slotRadiusChanged zu implementieren, das ist "slot" + [Name des Eingabefeldes] + "Changed" (Zeile 45):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
include("scripts/EAction.js");

function ExMinimal(guiAction) {
    EAction.call(this, guiAction);

    this.pos = undefined;
    this.radius = undefined;

    this.setUiOptions("ExMinimal.ui");
}

ExMinimal.prototype = new EAction();

ExMinimal.prototype.beginEvent = function() {
    EAction.prototype.beginEvent.call(this);

    var di = this.getDocumentInterface();
    di.setClickMode(RAction.PickCoordinate);
};

ExMinimal.prototype.pickCoordinate = function(event, preview) {
    this.pos = event.getModelPosition();

    if (preview) {
        this.updatePreview();
    }
    else {
        this.applyOperation();
    }
};

ExMinimal.prototype.getOperation = function(preview) {
    if (isNull(this.pos) || isNull(this.radius)) {
        return undefined;
    }

    var doc = this.getDocument();

    var op = new RAddObjectOperation();
    var circle = new RCircle(this.pos, this.radius);
    op.addObject(shapeToEntity(doc, circle));
    return op;
};

ExMinimal.prototype.slotRadiusChanged = function(v) {
    this.radius = v;
    this.updatePreview();
};

ExMinimal.init = function(basePath) {
    var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setScriptFile(basePath + "/ExMinimal.js");
    action.setGroupSortOrder(100000);
    action.setSortOrder(0);
    action.setWidgetNames(["ExamplesMenu"]);
};

Diese neue Funktion slotRadiusChanged wird immer dann aufgerufen, wenn der Benutzer einen neuen Radius eingibt. Er setzt die Member-Variable this.radius, die wiederum beim Erstellen des Kreises in getOperation verwendet wird.

Da jedes Tool in QCAD auf der obersten Ebene als Skript implementiert ist, stehen viele Beispielskripte zur Verfügung. Sie finden sie in unserem Git-Repository.