Перейти к содержимому

Интерактивные скриптовые действия

Скриптовые действия — это скрипты, которые добавляют пункт в меню и/или панель инструментов и могут обрабатывать взаимодействия с пользователем. Скриптовые действия остаются активными до тех пор, пока не будут завершены пользователем или пока не завершатся сами.

Как только скриптовое действие запущено, оно обрабатывает различные события, пока не будет завершено. Событие — это то, что происходит, когда что-то случается. Например, если скриптовое действие запущено, вызывается beginEvent. Если пользователь щёлкает по объекту, срабатывает событие pickEntity, если пользователь щёлкает по координате, происходит событие pickCoordinate и т. д.

Минимальная структура скриптового действия выглядит следующим образом:

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

Этот пример скрипта добавляет меню в нижнюю часть меню Разное > Примеры. Текст меню — «Minimal Example».

Обратите внимание, что для того, чтобы скрипт был найден, имя файла должно совпадать с именем класса, то есть «ExMyMinimal.js» в данном случае. Он также должен находиться внутри каталога с тем же именем «ExMyMinimal», поэтому этот скрипт можно, например, поместить в scripts/Misc/ExMyMinimal/ExMyMinimal.js.

Вы также можете поместить свои скрипты в локальную папку scripts внутри вашей домашней папки пользователя. Чтобы узнать точную папку, откройте диалог «О программе» (Справка > О программе QCAD…) и перейдите на вкладку Система. Там расположение данных указано в строке Data directory. Это каталог, в котором необходимо создать подпапку с именем scripts, а в ней — подпапки, по одной папке на каждый скриптовый инструмент, например /путь/к/каталогу данных/scripts/MyScripts/MyScript1/MyScript1.js

Точное расположение зависит от вашей системы и её настроек.

Приведённый выше скрипт полностью функционален и может быть запущен. Однако при запуске он на самом деле ничего не делает. Более того, после запуска скрипт остаётся активным, пока пользователь не завершит его, щёлкнув правой кнопкой мыши. Чтобы изменить это, давайте реализуем beginEvent, чтобы вывести что-то в историю командной строки QCAD и завершить действие:

include("scripts/EAction.js");
function ExMyMinimal(guiAction) {
EAction.call(this, guiAction);
}
ExMyMinimal.prototype = new EAction();
ExMyMinimal.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
EAction.handleUserMessage("Hello World!");
this.terminate();
};
ExMyMinimal.init = function(basePath) {
var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
action.setScriptFile(basePath + "/ExMyMinimal.js");
action.setGroupSortOrder(100000);
action.setSortOrder(0);
action.setWidgetNames(["ExamplesMenu"]);
};

Если теперь запустить инструмент Разное > Примеры > Minimal Example, он выводит «Hello World!» в историю командной строки (строка 12), а затем завершается (строка 14).

Если скрипт не требует никакого взаимодействия с пользователем, такой скрипт можно использовать для добавления меню, которое что-то делает, а затем завершается. Примерами таких действий являются Вид > Автомасштабирование, Выделение > Выделить всё, Правка > Удалить и т. д.

Как только скрипт требует какого-либо взаимодействия с пользователем, нам нужно реализовать больше обработчиков событий и сообщить скрипту, что пользователю нужно сделать дальше (например, выбрать объект или определить координату). На следующем шаге мы входим в состояние, в котором действие ожидает координату от пользователя. Затем мы рисуем окружность в каждой позиции, по которой пользователь щёлкает или которую вводит.

include("scripts/EAction.js");
function ExMyMinimal(guiAction) {
EAction.call(this, guiAction);
this.pos = undefined;
}
ExMyMinimal.prototype = new EAction();
ExMyMinimal.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
var di = this.getDocumentInterface();
di.setClickMode(RAction.PickCoordinate);
};
ExMyMinimal.prototype.pickCoordinate = function(event, preview) {
this.pos = event.getModelPosition();
if (preview) {
this.updatePreview();
}
else {
this.applyOperation();
}
};
ExMyMinimal.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;
};
ExMyMinimal.init = function(basePath) {
var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
action.setScriptFile(basePath + "/ExMyMinimal.js");
action.setGroupSortOrder(100000);
action.setSortOrder(0);
action.setWidgetNames(["ExamplesMenu"]);
};

В beginEvent мы больше не завершаем действие немедленно, а позволяем ему работать, пока пользователь не завершит его (щелчок правой кнопкой или Escape). Затем мы реализуем pickCoordinate, чтобы сохранить позицию курсора мыши или введённую координату и либо обновить предпросмотр, либо применить операцию (то есть добавить окружность). pickCoordinate вызывается всякий раз, когда пользователь перемещает мышь, чтобы отобразить предпросмотр планируемой операции. Когда пользователь щёлкает или вводит координату, он вызывается с параметром preview, установленным в false, чтобы указать, что была выбрана или введена окончательная координата.

updatePreview в строке 22 показывает предпросмотр операции, возвращённой getOperation, тогда как applyOperation в строке 25 фактически применяет операцию к нашему документу.

getOperation должен быть реализован для возврата операции, которую нужно показать в предпросмотре или применить к документу. Это несколько сложнее, чем то, что мы видели в простом API выше. Это потому, что одну операцию можно использовать для добавления нескольких объектов, изменения объектов или удаления объектов.

Добавление виджетов на панель параметров

Заголовок раздела «Добавление виджетов на панель параметров»

Окружность, нарисованная в нашем примере, всегда имеет радиус 1 единица чертежа (см. строку 33). На следующем шаге мы хотим позволить пользователю ввести радиус для окружности. QCAD обычно использует панель параметров вверху для отображения и изменения таких параметров инструмента. Для этого нам нужно определить, какие виджеты мы хотим показать на панели параметров и какими параметрами они управляют. Это можно сделать с помощью файла UI, файла XML, который определяет виджет и его содержимое. Файлы UI можно удобно проектировать с помощью программы Qt Designer, которая входит в состав инструментария Qt. Для этого примера мы используем простой файл UI, который также можно создать в текстовом редакторе (файл ExMyMinimal.ui):

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ExMyMinimal</class>
<widget class="QWidget" name="ExMyMinimal">
<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>

Файл UI определяет два виджета: метку (QLabel) и поле ввода (RMathLineEdit). Важно имя поля ввода («Radius»). Виджет автоматически связывается с нашим скриптом через это имя. Всё, что нам нужно сделать в нашем скрипте, — это определить, какой файл UI мы хотим использовать (строка 9), и реализовать новый обработчик событий с именем slotRadiusChanged, то есть «slot» + [имя нашего поля ввода] + «Changed» (строка 45):

include("scripts/EAction.js");
function ExMyMinimal(guiAction) {
EAction.call(this, guiAction);
this.pos = undefined;
this.radius = undefined;
this.setUiOptions("ExMyMinimal.ui");
}
ExMyMinimal.prototype = new EAction();
ExMyMinimal.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
var di = this.getDocumentInterface();
di.setClickMode(RAction.PickCoordinate);
};
ExMyMinimal.prototype.pickCoordinate = function(event, preview) {
this.pos = event.getModelPosition();
if (preview) {
this.updatePreview();
}
else {
this.applyOperation();
}
};
ExMyMinimal.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;
};
ExMyMinimal.prototype.slotRadiusChanged = function(v) {
this.radius = v;
this.updatePreview();
};
ExMyMinimal.init = function(basePath) {
var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
action.setScriptFile(basePath + "/ExMyMinimal.js");
action.setGroupSortOrder(100000);
action.setSortOrder(0);
action.setWidgetNames(["ExamplesMenu"]);
};

Эта новая функция slotRadiusChanged вызывается всякий раз, когда пользователь вводит новый радиус. Она устанавливает переменную-член this.radius, которая, в свою очередь, используется при создании окружности в getOperation.

Все скрипты в QCAD основаны на одной из этих концепций, изложенных в этом уроке.

Поскольку каждый инструмент в QCAD реализован как скрипт на верхнем уровне, доступно множество примеров скриптов. Вы можете найти их в нашем репозитории git.