diff --git a/ecosystem/standalone-extensions/qt/architecture/errors-and-threads.md b/ecosystem/standalone-extensions/qt/architecture/errors-and-threads.md index 354f40f..26ec8b6 100644 --- a/ecosystem/standalone-extensions/qt/architecture/errors-and-threads.md +++ b/ecosystem/standalone-extensions/qt/architecture/errors-and-threads.md @@ -12,7 +12,7 @@ Each reads `if (!PHPQT_PROBE_OFF("") && ())`, marks its throw line `/ | Site | Calls | Outcome | |---|---|---| -| `qapp-widget`, `qapp-text-widget`, `qapp-slider`, `qapp-table`, `qapp-splitter`, `qapp-gl-painter`, `qapp-menubar`, `qapp-menu` | widget constructors before a `QApplication` | qFatal abort | +| `qapp-widget`, `qapp-text-widget`, `qapp-slider`, `qapp-table`, `qapp-tree-widget`, `qapp-splitter`, `qapp-gl-painter`, `qapp-menubar`, `qapp-menu` | widget constructors before a `QApplication` | qFatal abort | | `qguiapp-window`, `qguiapp-pixmap`, `qguiapp-pixmap-from-image` | `QWindow`, `QPixmap`, `QPixmap::fromImage` before a `QGuiApplication` | qFatal abort | | `qguiapp-vulkan-create` | `QVulkanInstance::create()` before a `QGuiApplication` | SIGSEGV | | `main-thread-macos` | an application, widget or `QWindow` constructor off the process main thread, macOS only | AppKit's uncaught NSException | diff --git a/ecosystem/standalone-extensions/qt/architecture/object-model.md b/ecosystem/standalone-extensions/qt/architecture/object-model.md index 315f74b..e5ca4b9 100644 --- a/ecosystem/standalone-extensions/qt/architecture/object-model.md +++ b/ecosystem/standalone-extensions/qt/architecture/object-model.md @@ -16,13 +16,17 @@ One PHP object per QObject, guarded by QPointer; PHP deletes what it created unl ## Values -Qt's value classes (`QPoint`, `QPointF`, `QSize`, `QRect`, `QDate`, `QKeySequence`, `QUrl`, `QByteArray`, `QFont`, `QPixmap`, `QImage`, `QColor`, `QBrush`, the text values) and `QTableWidgetItem` are not QObjects. They use `phpqt_value_object { void *ptr; bool constructed; bool owned; void (*destroy)(void *); …; zend_object std; }`: a heap copy the PHP object owns and deletes through `destroy`. Copies in, copies out: `QWidget::font()` returns a new wrapper around a copy; `setFont()` copies the wrapped value in. No identity map; two reads give two objects. A second `__construct()` is refused by `phpqt_value_fresh`.[^values] +Qt's value classes (`QPoint`, `QPointF`, `QSize`, `QRect`, `QDate`, `QKeySequence`, `QUrl`, `QByteArray`, `QFont`, `QPixmap`, `QImage`, `QColor`, `QBrush`, the text values), `QTableWidgetItem` and `QTreeWidgetItem` are not QObjects. They use `phpqt_value_object { void *ptr; bool constructed; bool owned; void (*destroy)(void *); …; zend_object std; }`: a heap copy the PHP object owns and deletes through `destroy`. Copies in, copies out: `QWidget::font()` returns a new wrapper around a copy; `setFont()` copies the wrapped value in. No identity map; two reads give two objects. A second `__construct()` is refused by `phpqt_value_fresh`.[^values] A `QImage` made from an address borrows that buffer, as Qt does: the image holds no copy, and the buffer (a `QByteArray`'s `pointer()`, a framebuffer's) must outlive every image sharing it. Its cleanup function is a `PhpSlot` holding the callable and the bound `cleanupInfo`; Qt's `cleanupInfo` is a `QPointer` to that slot, so after request end, when the slot is gone, nothing runs. A value may also be borrowed from an owner (`phpqt_value_borrow`): a `QLayoutItem` read from a layout reads as deleted once the layout no longer lists it (`QLayout::indexOf`). -`QTableWidgetItem` is the one value with a second owner. Once `QTableWidget::setItem()` stored the item (`item->tableWidget()` is that table), the table owns it (`owned = false`); an item another table holds stays that table's, as Qt warns. The item is a `PhpTableWidgetItem` subclass whose destructor clears its wrapper's `ptr`, so a call after `clearContents()` or a row removal throws `QtException("QTableWidgetItem has been deleted by Qt")` instead of reading freed memory. `item(r, c)` and `horizontalHeaderItem(c)` return the wrapper the item already has, or a new table-owned one. Items Qt makes itself (header labels, a cell a user types into) are clones of an item prototype the `QTableWidget` constructor installs, so their wrappers learn of deletion the same way. A cell outside the table is refused (site `table-item-range`: Qt drops the item unstored, its view pointer on the table).[^values] +`QTableWidgetItem` and `QTreeWidgetItem` are the values with a second owner. Once `QTableWidget::setItem()` stored the item (`item->tableWidget()` is that table), the table owns it (`owned = false`); an item another table holds stays that table's, as Qt warns. The item is a `PhpTableWidgetItem` subclass whose destructor clears its wrapper's `ptr`, so a call after `clearContents()` or a row removal throws `QtException("QTableWidgetItem has been deleted by Qt")` instead of reading freed memory. `item(r, c)` and `horizontalHeaderItem(c)` return the wrapper the item already has, or a new table-owned one. Items Qt makes itself (header labels, a cell a user types into) are clones of an item prototype the `QTableWidget` constructor installs, so their wrappers learn of deletion the same way. A cell outside the table is refused (site `table-item-range`: Qt drops the item unstored, its view pointer on the table).[^values] + +`QTreeWidgetItem` follows the same pattern. Once `QTreeWidget::addTopLevelItem()` or `QTreeWidgetItem::addChild()` stored the item (afterwards `item->treeWidget()` is that tree with no parent, or `child->parent()` is that item), the tree or parent owns it. Qt ignores an item that already has a tree or a parent, and that item stays where it was. The item is a `PhpTreeWidgetItem` whose destructor clears its wrapper's `ptr`. Qt's base destructor then deletes the children, and each child clears its own wrapper. So after `clear()`, after the tree goes, or after the last reference to a PHP-owned parent is dropped, a call on any of those items throws `QtException("QTreeWidgetItem has been deleted by Qt")`. `child()`, `parent()`, `currentItem()`, `selectedItems()` and the signal arguments return the wrapper the item already has, or a new tree-owned one. Only PHP adds items: Qt's header item is not bound, and Qt clones items only for drag and drop, which no bound call turns on. + +`addChild()` passes through Qt's own behaviour for a cycle. An item added under itself or under one of its descendants, while neither belongs to a tree, makes Qt's subtree walk (`propagateDisabled`) loop forever. Probed 2026-10-18: the process spins with flat memory and does not crash, so this is not a refusal under rule 3. `QTextEdit\ExtraSelection` is a plain object, the struct: typed public `cursor` and `format` properties holding PHP values, copied into Qt's list by `setExtraSelections()`. diff --git a/ecosystem/standalone-extensions/qt/architecture/signals.md b/ecosystem/standalone-extensions/qt/architecture/signals.md index c7c783b..392505c 100644 --- a/ecosystem/standalone-extensions/qt/architecture/signals.md +++ b/ecosystem/standalone-extensions/qt/architecture/signals.md @@ -6,7 +6,7 @@ String-signature connect through a moc-free dynamic slot; argument marshalling; `PhpSlot` = a `QObject` subclass holding a PHP callable. `QObject::connect()` normalises the signature (`QMetaObject::normalizedSignature`), finds the signal with `indexOfSignal`, and calls `QMetaObject::connect(sender, index, slot, QObject::staticMetaObject.methodCount(), Qt::DirectConnection)`. Activation arrives in `PhpSlot::qt_metacall`; after `QObject::qt_metacall` subtracts QObject's own methods the id is 0, which invokes the callable. No moc, no generated metaobject. A signal the class lacks gets what Qt's own string connect gives: Qt's warning and the invalid connection.[^qobject] -Arguments, typed by the signal's `QMetaMethod::parameterMetaType`: bool, integer and float types as PHP scalars; `QString`/`QByteArray` as strings; `QDate`, `QPoint`, `QPointF`, `QSize`, `QRect`, `QUrl` and `QKeySequence` as copies in their PHP classes (`QDateEdit::dateChanged` hands a `QDate` over); `QSocketDescriptor` as the int fd; enums as ints (`QMediaPlayer::MediaStatus` arrives as the `QMediaPlayer\MediaStatus` case value); `QObject*` boxed. A `QObject*` that is the emitting sender maps to the sender's own wrapper even during `destroyed()`, when Qt has already cleared its QPointers.[^runtime] +Arguments, typed by the signal's `QMetaMethod::parameterMetaType`: bool, integer and float types as PHP scalars; `QString`/`QByteArray` as strings; `QDate`, `QPoint`, `QPointF`, `QSize`, `QRect`, `QUrl`, `QKeySequence`, `QBrush`, `QColor`, `QFont` and `QPixmap` as copies in their PHP classes (`QDateEdit::dateChanged` hands a `QDate` over); `QSocketDescriptor` as the int fd; enums as ints (`QMediaPlayer::MediaStatus` arrives as the `QMediaPlayer\MediaStatus` case value); `QObject*` boxed; `QTreeWidgetItem*` as the wrapper PHP holds for the item (a new tree-owned one if none), null as null. The same conversion (`phpqt_variant_to_zval`) reads a `QVariant`: `QTreeWidgetItem::data()` returns what it holds, null when it holds nothing or a type with no PHP class. A `QObject*` that is the emitting sender maps to the sender's own wrapper even during `destroyed()`, when Qt has already cleared its QPointers.[^runtime] Lifetime: @@ -17,7 +17,7 @@ Lifetime: * `QImage`'s cleanup function: a slot with one bound argument (`cleanupInfo`), fired once on its own thread (queued there from another), then freed. * RSHUTDOWN deletes every remaining slot (disconnecting it) and frees its callable while the engine can still free it. -`QEventFilter` is a `PhpSlot` subclass overriding `eventFilter(watched, event)`: it shares the slot's call path and request-end teardown, hands PHP every event as `(watched, event)` and returns PHP's bool to Qt. The callable reads `$event->type()` itself. +`QEventFilter` is a `PhpSlot` subclass overriding `eventFilter(watched, event)`: it shares the slot's call path and request-end teardown, hands PHP every event as `(watched, event)` and returns PHP's bool to Qt. The callable reads `$event->type()` itself. `phpqt_box_event` boxes the most specific bound class by `dynamic_cast`, in order: `QContextMenuEvent`, `QMouseEvent`, `QKeyEvent`, `QWheelEvent` (a `QSinglePointEvent`, not a `QMouseEvent`, so it needs its own branch), `QInputEvent`, else `QEvent`. `QMetaObject::invokeMethod()` converts PHP arguments the other way, to the parameter types of the member of that name and arity (`phpqt_zval_to_variant`), the value classes included, and makes the native call with the connection type given. diff --git a/ecosystem/standalone-extensions/qt/changelog.md b/ecosystem/standalone-extensions/qt/changelog.md index 123d55f..5918f05 100644 --- a/ecosystem/standalone-extensions/qt/changelog.md +++ b/ecosystem/standalone-extensions/qt/changelog.md @@ -2,8 +2,30 @@ What changed in this package, newest first. +### 2026-10-23 + +* `QWidget::clearFocus`. No new site. Shipped in tag v0.10.5 (`PHP_QT_VERSION` 0.10.5; there are no v0.10.3 or v0.10.4 tags). [surface](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/binding-surface) + +### 2026-10-22 + +* `QWidget` `focusPolicy` / `setFocusPolicy` (`Qt\FocusPolicy`, generated with its checks) and static `setTabOrder`; `QKeyEvent` `text`, `isAutoRepeat`, `nativeScanCode`, `nativeVirtualKey`. No new site. Shipped in tag v0.10.5 (`PHP_QT_VERSION` 0.10.5; there are no v0.10.3 or v0.10.4 tags). [surface](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/binding-surface) +* Coverage: `scripts/support.php` writes `SUPPORT.md` (every public member function name of QtCore, QtGui and QtWidgets classes, from clang++'s AST dump of each public class header; signals found through `QT_ANNOTATE_ACCESS_SPECIFIER`; a stub method on a subclass counts for the base that declares it, through every public base) and the README API badge; `--check` compares without the date. 570 / 11435 on Qt 6.11.2. +* `QMessageBox` `addButton`, `setDefaultButton`, `setEscapeButton`, `clickedButton`; `QFileDialog` (its constructor at site `qapp-file-dialog`), `setFileMode`, `setAcceptMode`, `setOption`, `setDirectory`, `selectFile`, `selectedFiles`; `QGuiApplication::clipboard` (`?QClipboard`: Qt warns and returns nullptr before an application, and lives, so no refusal), `QClipboard` `setText`, `text`; the generated `QMessageBox\ButtonRole`, `QFileDialog\FileMode`, `AcceptMode`, `Option`, `QClipboard\Mode`; shipped in tag v0.10.5 (`PHP_QT_VERSION` 0.10.5; there are no v0.10.3 or v0.10.4 tags). An unshown native `QFileDialog` keeps no selection; Qt's own dialog (`DONT_USE_NATIVE_DIALOG`) does. [surface](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/binding-surface) + +### 2026-10-20 + +* `QWidget` `window`, `windowFlags`, `setWindowFlags`, `windowModality`, `setWindowModality`; the generated `Qt\WindowModality`; `QPushButton` `isDefault`, `setDefault`; shipped in tag v0.10.5 (`PHP_QT_VERSION` 0.10.5; there are no v0.10.3 or v0.10.4 tags). `setParent` keeps its one-argument form: PHP holds it to `QObject::setParent(?QObject)`, so the flags of a child window go through `setWindowFlags`. [surface](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/binding-surface) + +### 2026-10-19 + +* `QWheelEvent` (constructor, `pixelDelta`, `angleDelta`, `position`, `globalPosition`, `source`; filters box wheels as it), the generated `Qt\ScrollPhase` and `Qt\MouseEventSource`, and `QStyleHints::mouseDoubleClickInterval` / `mouseDoubleClickDistance`, shipped in tag v0.10.5 (`PHP_QT_VERSION` 0.10.5; there are no v0.10.3 or v0.10.4 tags). [surface](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/binding-surface), [signals](/docs/{{version}}/ecosystem/standalone-extensions/qt/architecture/signals) + ### 2026-10-18 +* `QTreeWidget` and `QTreeWidgetItem`. Shipped in tag v0.10.5 (`PHP_QT_VERSION` 0.10.5; there are no v0.10.3 or v0.10.4 tags). Item lifetime as for table items: `PhpTreeWidgetItem`, owned by the tree or parent once Qt stored it. `QTreeWidgetItem*` signal arguments arrive as the item's wrapper. `data()` reads a QVariant through the signal-argument conversion. `QBrush`, `QColor`, `QFont` and `QPixmap` now cross both ways as QVariants and as signal arguments. Also `QAbstractItemView\ScrollHint`, `QTreeWidgetItem\ChildIndicatorPolicy`, `QKeyEvent` (filters box key presses and releases as it), and the generated `Qt\ItemDataRole` and `Qt\Key`. The constructor carries the `qapp-tree-widget` site. [surface](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/binding-surface), [object model](/docs/{{version}}/ecosystem/standalone-extensions/qt/architecture/object-model), [signals](/docs/{{version}}/ecosystem/standalone-extensions/qt/architecture/signals) +* Event constructors refuse ints wider than their C type instead of truncating them: `QKeyEvent` key (int) and modifiers, `QMouseEvent` button, buttons and modifiers, `QContextMenuEvent` modifiers (32-bit masks). [surface](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/binding-surface) +* `scripts/gen-enum.php` handles three more cases: deprecation messages that hold commas, digit names (`KEY_0`), and names that differ only in case (`DEAD_a` / `DEAD_A`). Its output for the enums already generated is unchanged. [generating enums](/docs/{{version}}/ecosystem/standalone-extensions/qt/runbooks/generating-enums) + * Qt 1:1 (0.10.2): every method is one native call under Qt's name, parameters and defaults. Value classes `QPoint`, `QPointF`, `QSize`, `QRect`, `QDate`, `QKeySequence`, `QByteArray` replace arrays and strings; `QThread`, `QLayoutItem`, `QPointingDevice`, `QCalendarWidget`; window flags on the widget constructors; applications take `(int &$argc, array $argv, int $flags)`; `invokeMethod` takes the connection type; `QTextEdit\ExtraSelection` is the struct; `QImage` borrows its buffer with Qt's cleanup function; `QMenu::popup(QPoint)`, `addMenu(title)`, `QUrl::toString(options)`; abstract classes are abstract. `QSurfaceFormat::setDefaultFormat` and `QCoreApplication::setAttribute`/`testAttribute` with `Qt\ApplicationAttribute`: a GL widget composites only when its context shares with the window's, which on macOS means the same profile as Qt's default. [surface](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/binding-surface), [layout](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/layout-and-geometry), [object model](/docs/{{version}}/ecosystem/standalone-extensions/qt/architecture/object-model) * Qt answers for itself: refusals stay only where a probe showed a crash, each with its `bindings.json` site and a test in `tests/Probes` (`--enable-qt-probes`); the main-thread and second-application checks are macOS-only. [errors and threads](/docs/{{version}}/ecosystem/standalone-extensions/qt/architecture/errors-and-threads), [build](/docs/{{version}}/ecosystem/standalone-extensions/qt/runbooks/build-install-test) * Signal arguments of the value types reach PHP in their classes; `QEventFilter` hands over every event as `(watched, event)`. [signals](/docs/{{version}}/ecosystem/standalone-extensions/qt/architecture/signals) diff --git a/ecosystem/standalone-extensions/qt/guide/binding-surface.md b/ecosystem/standalone-extensions/qt/guide/binding-surface.md index 235c1af..421b0d1 100644 --- a/ecosystem/standalone-extensions/qt/guide/binding-surface.md +++ b/ecosystem/standalone-extensions/qt/guide/binding-surface.md @@ -22,7 +22,7 @@ Naming and shapes, all fixed: |---|---| | `QObject` | ctor(?parent), objectName, setObjectName, inherits, parent, setParent, children, deleteLater, thread, pointer; static connect(sender, signature, callable): `QMetaObject\Connection` (Qt's invalid connection, with Qt's warning, for a signal the class lacks), disconnect(Connection): bool | | `QThread` | ctor(?parent), static currentThread; `QObject::thread()` names the thread an object lives in | -| `QCoreApplication` / `QGuiApplication` / `QApplication` | ctor(int &$argc, array $argv, int $flags = `QCoreApplication::APPLICATION_FLAGS`): Qt's argc after it strips its own options is written back; static instance, sendEvent, processEvents, sendPostedEvents, exec, quit, exit, applicationName/set, applicationPid, closingDown, startingUp, setAttribute/testAttribute(`Qt\ApplicationAttribute`); Gui: platformName, desktopFileName/set, applicationDisplayName/set, quitOnLastWindowClosed/set, styleHints; widgetAt, focusWidget | +| `QCoreApplication` / `QGuiApplication` / `QApplication` | ctor(int &$argc, array $argv, int $flags = `QCoreApplication::APPLICATION_FLAGS`): Qt's argc after it strips its own options is written back; static instance, sendEvent, processEvents, sendPostedEvents, exec, quit, exit, applicationName/set, applicationPid, closingDown, startingUp, setAttribute/testAttribute(`Qt\ApplicationAttribute`); Gui: platformName, desktopFileName/set, applicationDisplayName/set, quitOnLastWindowClosed/set, styleHints; widgetAt, focusWidget, activeModalWidget | | `QTimer` | ctor(?parent), start(?msec), stop, isActive, interval/set, isSingleShot/set, timerType/set, remainingTime, timerId; static singleShot(msec, callable) | | `QSocketNotifier` | ctor(int\|resource\|Socket, Type, ?parent), socket, type, isEnabled, setEnabled, isValid | | `QAbstractEventDispatcher` | static instance(?QThread), processEvents(flags), wakeUp, interrupt | @@ -33,7 +33,7 @@ Naming and shapes, all fixed: | `QDate` | value: ctor(y, m, d), year, month, day, isValid, toString(`Qt\DateFormat`), static fromString(string, `Qt\DateFormat`) | | `QKeySequence` | value: ctor(string, `QKeySequence\SequenceFormat` = NativeText), toString(format = PortableText), isEmpty, static fromString | | `QByteArray` | value: ctor(string $data, int $size = -1) copies $size bytes (to the first NUL when negative; a size past the string is refused), size, pointer (the bytes' address, for an API that borrows them) | -| `QWidget` | ctor(?parent, `Qt\WindowType`\|int $f), static createWindowContainer(QWindow, ?parent, flags) (the container owns the window), devicePixelRatioF, show/hide/close/visible, isWindow, setFocus/hasFocus, isActiveWindow, activateWindow, raise, window title, resize/width/height, size/sizeHint (`QSize`), pos (`QPoint`), geometry (`QRect`), mapFromGlobal(`QPointF`): `QPointF`, setGeometry, move, minimum/fixed size, setSizePolicy, setLayout/layout, enabled, style sheet, font/setFont, setParent (a non-widget parent is a TypeError: the native parameter is `QWidget *`), parentWidget, set/testAttribute | +| `QWidget` | ctor(?parent, `Qt\WindowType`\|int $f), static createWindowContainer(QWindow, ?parent, flags) (the container owns the window), devicePixelRatioF, show/hide/close/visible, isWindow, setFocus/clearFocus/hasFocus, focusPolicy/setFocusPolicy (`Qt\FocusPolicy`), static setTabOrder(first, second), isActiveWindow, activateWindow, raise, window title, resize/width/height, size/sizeHint (`QSize`), pos (`QPoint`), geometry (`QRect`), mapFromGlobal(`QPointF`): `QPointF`, setGeometry, move, minimum/fixed size, setSizePolicy, setLayout/layout, enabled, style sheet, font/setFont, setParent (a non-widget parent is a TypeError: the native parameter is `QWidget *`), parentWidget, set/testAttribute | | `QWindow`, `QSurface\SurfaceType` | ctor(?QWindow parent), setSurfaceType/surfaceType, winId (native handle as int: an NSView address on macOS; creates the platform window when it has none), create, show, hide, destroy (the platform window and the Vulkan surface Qt made for it; the QWindow stays), isExposed, resize, close, width, height, devicePixelRatio; on a Qt with Vulkan, setVulkanInstance(?QVulkanInstance) / vulkanInstance (the same PHP object back) | | `QVulkanInstance` (`QT_CONFIG(vulkan)`) | value: ctor, setVkInstance(int address) (Qt adopts it on create() and never destroys it), create, isValid, errorCode (VkResult), vkInstance (address; 0 before set or created), destroy, static surfaceForWindow(QWindow) (VkSurfaceKHR address; 0 without an instance). Freed: every window using it is destroyed (platform window) and detached first. Qt loads the loader by name `vulkan`: on macOS Homebrew's is outside dyld's search path, so `QT_VULKAN_LIB` names it (`$(pkg-config --variable=libdir vulkan)/libvulkan.1.dylib`) | | `QMainWindow` | ctor(?parent, flags), menuBar (made on first use, owned by the window), setMenuBar, central widget | @@ -44,9 +44,9 @@ Naming and shapes, all fixed: | `QSurfaceFormat`, `QSurfaceFormat\OpenGLContextProfile`, `QSurfaceFormat\RenderableType` | value: ctor, static defaultFormat/setDefaultFormat, majorVersion/minorVersion/setVersion, profile/setProfile, renderableType/setRenderableType | | `QOpenGLPainter` | trampoline: `new QOpenGLPainter(callable $paint, ?QWidget $parent)`; `paintGL()` calls `$paint(QOpenGLPainter $widget)` with the context current and `defaultFramebufferObject()` bound | | `QEventFilter` | trampoline for installEventFilter/removeEventFilter: `$filter(QObject $watched, QEvent $event): bool` for every event of each watched object; true stops delivery to the watched object. The event is Qt's, boxed as its most specific class, and reads as gone (QtException) after the call | -| `QEvent` → `QInputEvent` → `QMouseEvent`, `QContextMenuEvent` | value wrappers: type/accept/ignore/isAccepted/spontaneous; modifiers, timestamp/setTimestamp, deviceType; mouse: ctor(type, `QPointF` local, `QPointF` global, button, buttons, modifiers, `QPointingDevice`), button, buttons, position/globalPosition (`QPointF`); context menu: ctor(reason, `QPoint`, `QPoint`, modifiers), reason, pos/globalPos (`QPoint`). A plain `QEvent` of a type a receiver casts to a subclass is refused | +| `QEvent` → `QInputEvent` → `QMouseEvent`, `QKeyEvent`, `QWheelEvent`, `QContextMenuEvent` | value wrappers: type/accept/ignore/isAccepted/spontaneous; modifiers, timestamp/setTimestamp, deviceType; mouse: ctor(type, `QPointF` local, `QPointF` global, button, buttons, modifiers, `QPointingDevice`), button, buttons, position/globalPosition (`QPointF`); key: ctor(type, int key, int modifiers, text = "", autorep = false, count = 1; a key outside C int, modifiers outside 32 bits or a count outside quint16 is a ValueError; mouse buttons and modifiers and context-menu modifiers likewise), key (a `Qt\Key` value), text, isAutoRepeat, nativeScanCode (X11: evdev + 8; 1 or 0 on macOS), nativeVirtualKey (macOS: the kVK_ code), filters get key presses and releases as `QKeyEvent`; wheel: ctor(`QPointF` pos, `QPointF` global, `QPoint` pixelDelta, `QPoint` angleDelta, buttons, modifiers, `Qt\ScrollPhase`, inverted, `Qt\MouseEventSource`, `QPointingDevice`; buttons and modifiers outside 32 bits are a ValueError), pixelDelta/angleDelta (`QPoint`), position/globalPosition (`QPointF`), source (`Qt\MouseEventSource`), filters get wheels as `QWheelEvent`; context menu: ctor(reason, `QPoint`, `QPoint`, modifiers), reason, pos/globalPos (`QPoint`). A plain `QEvent` of a type a receiver casts to a subclass is refused | | `QInputDevice` → `QPointingDevice` | static primaryPointingDevice(seatName = "") | -| `QStyleHints` | press-and-hold interval, drag distance | +| `QStyleHints` | press-and-hold interval, drag distance, double-click interval and distance | | `QLayoutItem`, `QLayout`, `QBoxLayout`, `QVBoxLayout`, `QHBoxLayout`, `QGridLayout`, `QSpacerItem` | `QLayout` is abstract; concrete ctors take ?parent widget (owner); a widget added before the layout is installed is held, not deleted ([layout](/docs/{{version}}/ecosystem/standalone-extensions/qt/guide/layout-and-geometry)); spacing, contents margins, count, removeWidget, indexOf, itemAt(int): ?QLayoutItem, invalidate; box: addWidget(w, stretch, alignment), insertWidget (Qt answers an index past the end), addLayout, addStretch, setStretch, stretch, setAlignment; grid: addWidget(w, row, col, rowSpan, colSpan, alignment), row/columnCount, h/v spacing, itemAtPosition: ?QLayoutItem, row/column stretch, addItem(QSpacerItem) (layout takes it; one another layout owns is refused). `QLayoutItem`: widget, layout, spacerItem; one borrowed from a layout reads as deleted once the layout no longer lists it. `QSpacerItem`: ctor(w, h, hPolicy, vPolicy), changeSize, sizeHint (`QSize`) | | `QLabel` | ctor(text, ?parent, flags), text, alignment/setAlignment, wordWrap, setScaledContents, pixmap/setPixmap(?QPixmap), textFormat/setTextFormat, openExternalLinks/setOpenExternalLinks, textInteractionFlags/set; signals `linkActivated(QString)`, `linkHovered(QString)` | | `QDesktopServices` | static openUrl(QUrl) | @@ -65,12 +65,14 @@ Naming and shapes, all fixed: | `QTabWidget` | ctor, addTab → index, removeTab (page not deleted), indexOf (-1), count, currentIndex (+set), currentWidget, setTabText / tabText, widget, documentMode (+set). `currentChanged(int)` fires for every change | | `QAbstractItemView` (abstract) → `QTableWidget` | parented to QFrame here (QAbstractScrollArea/QTableView unbound); selection behavior/mode, setEditTriggers(int), `NO_EDIT_TRIGGERS`, clearSelection; table ctor(rows, columns, ?parent; installs an item prototype so Qt-made items are tracked), row/column counts, header labels, horizontalHeaderItem, selectedItems, setItem (the table owns the item once Qt stored it; a cell outside the table is refused; an item another table holds is Qt's warning), item, clearContents, currentRow, selectRow | | `QTableWidgetItem` | value: ctor(text = "", type = `QTableWidgetItem::TYPE`), type, text/setText, row (-1 outside a table); `USER_TYPE` | +| `QAbstractItemView` → `QTreeWidget` | ctor(?parent), columnCount/setColumnCount, setHeaderLabels, addTopLevelItem (the tree owns the item once Qt stored it; Qt ignores an item that already has a tree or a parent), clear, currentItem/setCurrentItem(?item), scrollToItem(item, `QAbstractItemView\ScrollHint` = ENSURE_VISIBLE), expandItem/collapseItem, selectedItems; signals currentItemChanged, itemSelectionChanged, itemActivated, itemExpanded, itemCollapsed hand over the items' wrappers | +| `QTreeWidgetItem` | value: ctor(type = `QTreeWidgetItem::TYPE`), `USER_TYPE`; text/setText, foreground/setForeground (`QBrush`) and data/setData per column (role an int: a `Qt\ItemDataRole` value or a user role; the value as QVariant holds it, null for empty or a type with no PHP class); addChild (the parent owns the child; a cycle never returns, as in Qt), childCount, child, parent (null at top level), isExpanded/setExpanded (through its tree), childIndicatorPolicy/set (`QTreeWidgetItem\ChildIndicatorPolicy`) | | `QFont`, `QPixmap` | values: `QFont(family, pointSize = -1, weight = -1, italic = false)`, family/pointSizeF/weight (`QFont\Weight` or an int; Qt clamps to its 1–1000 scale); pixmap ctor, load(fileName, ?format, flags), isNull, width, height, scaled(w, h, `Qt\AspectRatioMode` = IgnoreAspectRatio, `Qt\TransformationMode` = Fast), devicePixelRatio/set, static fromImage(QImage, `Qt\ImageConversionFlag`\|int) | | `QImage`, `QImage\Format` | value: ctor(int $data, width, height, bytesPerLine, format, ?callable $cleanupFunction, mixed $cleanupInfo) borrows the buffer at the address (a `QByteArray`'s or a framebuffer's pointer()), which must outlive every image sharing it; Qt calls `$cleanupFunction($cleanupInfo)` once the last one goes, on the thread that made the image. A null address or bad dimensions make Qt's null image. isNull, width, height, format, copy(?QRect) (an image of its own) | | `QUrl` | value: ctor(string, `QUrl\ParsingMode`), static fromLocalFile, toString(int $options = `QUrl::PRETTY_DECODED`) with the formatting option constants, isValid | | `QAudioOutput`, `QMediaPlayer`, `QVideoWidget` | ctor(?parent QObject); muted, volume; setVideoOutput/setAudioOutput, setSource(QUrl)/source, play/pause/stop, position/duration/setPosition (ms), playbackState/mediaStatus enums, setLoops/`INFINITE_LOOPS`, errorString, hasVideo, isAvailable; signals `playbackStateChanged`, `mediaStatusChanged`, `errorOccurred(Error,QString)`, `positionChanged(qint64)`, `sourceChanged(QUrl)`; video widget ctor(?parent) | -Generated enums: `scripts/gen-enum.php
[prefix]` writes the stub cases and `src/checks/_.inc`, one `static_assert` per case, compiled into `src/qt.cpp`, so each build proves the values against its own Qt. A member newer than the 6.4 floor is checked under `#if QT_VERSION >= QT_VERSION_CHECK(…)` (`QEvent::DevicePixelRatioChange` 6.6, the child and parent window events 6.7, `Qt::NoTitleBarBackgroundHint` 6.9). Short enums are hand-written with a `static_assert` beside their registration. +Generated enums: `scripts/gen-enum.php
[prefix]` writes the stub cases and `src/checks/_.inc`, one `static_assert` per case, compiled into `src/qt.cpp`, so each build proves the values against its own Qt. A member newer than the 6.4 floor is checked under `#if QT_VERSION >= QT_VERSION_CHECK(…)` (`QEvent::DevicePixelRatioChange` 6.6, the child and parent window events 6.7, `Qt::NoTitleBarBackgroundHint` 6.9, `Qt::Key_micro` 6.7, `Qt::Key_Keyboard` 6.10, `Qt::RangeModelDataRole` / `StandardItemFlagsRole` / `FileInfoRole` 6.10, `Qt::RangeModelAdapterRole` 6.11). Short enums are hand-written with a `static_assert` beside their registration. Behaviour confirmed on both platforms: macOS (cocoa) makes the process a regular app on construction (Dock icon); Qt has no call to withdraw it. Linux: the app connects to the display server (`xcb` through XWayland on the Pi until `qt6-wayland` is installed); the taskbar lists windows, not processes. diff --git a/ecosystem/standalone-extensions/qt/overview.md b/ecosystem/standalone-extensions/qt/overview.md index ec11acc..896d2fd 100644 --- a/ecosystem/standalone-extensions/qt/overview.md +++ b/ecosystem/standalone-extensions/qt/overview.md @@ -3,6 +3,7 @@ ![CI](https://github.com/php-io-extensions/qt/actions/workflows/ci.yml) [![CI](https://img.shields.io/github/actions/workflow/status/php-io-extensions/qt/ci.yml?style=flat-square&label=CI)](https://github.com/php-io-extensions/qt/actions/workflows/ci.yml) +[![Qt API](https://img.shields.io/badge/Qt_API-5.1%25_%28578%2F11435%29-blue?style=flat-square)](https://github.com/php-io-extensions/qt/blob/v0.10.5/SUPPORT.md) 1:1 PHP bindings of Qt 6, written directly in C++ against the Zend API. Each PHP class is its C++ counterpart (`QApplication`, `QTimer`, `QSocketNotifier`, @@ -33,25 +34,26 @@ style them, drive the common controls and play video: | `QAbstractEventDispatcher` | `instance`, `processEvents`, `wakeUp`, `interrupt` | | `QMetaObject\Connection` | `operator bool` as `isValid()` | | `QMetaObject` | `invokeMethod` (object, member name, `Qt\ConnectionType`, arguments converted to its parameter types: calls a slot or emits a signal) | -| `QWidget` | constructor, `createWindowContainer` (static; the container owns the window), `show`, `hide`, `close`, `isVisible`, `setVisible`, `isWindow`, `isActiveWindow`, `activateWindow`, `setFocus`, `hasFocus`, `raise`, `windowTitle`, `setWindowTitle`, `resize`, `width`, `height`, `size`, `sizeHint`, `pos`, `geometry`, `setGeometry`, `move`, `setMinimumSize`, `minimumWidth`, `minimumHeight`, `setFixedSize`, `setSizePolicy`, `setLayout`, `layout`, `isEnabled`, `setEnabled`, `styleSheet`, `setStyleSheet`, `font`, `setFont`, `setParent` (widgets only), `parentWidget`, `setAttribute`, `testAttribute` | +| `QWidget` | constructor, `createWindowContainer` (static; the container owns the window), `show`, `hide`, `close`, `isVisible`, `setVisible`, `isWindow`, `window`, `windowFlags`, `setWindowFlags`, `windowModality`, `setWindowModality`, `isActiveWindow`, `activateWindow`, `setFocus`, `clearFocus`, `hasFocus`, `focusPolicy`, `setFocusPolicy` (`Qt\FocusPolicy`), `setTabOrder` (static), `raise`, `windowTitle`, `setWindowTitle`, `resize`, `width`, `height`, `size`, `sizeHint`, `pos`, `geometry`, `setGeometry`, `move`, `setMinimumSize`, `minimumWidth`, `minimumHeight`, `setFixedSize`, `setSizePolicy`, `setLayout`, `layout`, `isEnabled`, `setEnabled`, `styleSheet`, `setStyleSheet`, `font`, `setFont`, `setParent` (widgets only), `parentWidget`, `setAttribute`, `testAttribute` | | `QWindow`, `QSurface\SurfaceType` | constructor (needs a `QGuiApplication`), `setSurfaceType`, `surfaceType`, `winId` (the native handle: an `NSView` address on macOS; creates the platform window when it has none), `create`, `show`, `hide`, `destroy`, `isExposed`, `resize`, `close`, `width`, `height`, `devicePixelRatio`, `setVulkanInstance`, `vulkanInstance`; the seven surface types | | `QVulkanInstance` | constructor, `setVkInstance` (adopts a `VkInstance` address, e.g. ext-vulkan's `VkInstance::pointer()`), `create`, `isValid`, `errorCode`, `vkInstance`, `destroy`, static `surfaceForWindow` (a `VkSurfaceKHR` address). Present when Qt was built with Vulkan. On macOS set `QT_VULKAN_LIB` to the loader (`$(pkg-config --variable=libdir vulkan)/libvulkan.1.dylib`): Qt loads it by name and Homebrew's is outside dyld's search path | | `QMainWindow` | constructor, `menuBar`, `setMenuBar`, `centralWidget`, `setCentralWidget` | | `QMenuBar`, `QMenu` | constructors, `addMenu(title)`, `addAction`, `clear`, `isNativeMenuBar`, `setNativeMenuBar`; `title`, `setTitle`, `addSeparator`, `isEmpty`, `menuAction`, `popup(QPoint, ?QAction)` | | `QAction` | constructor, `text`, `setText`, `isCheckable`, `setCheckable`, `isChecked`, `setChecked`, `isEnabled`, `setEnabled`, `isSeparator`, `setSeparator`, `menuRole`, `setMenuRole`, `shortcut`, `setShortcut` (`QKeySequence`), `trigger`, `toggle` | -| `QDialog`, `QMessageBox` | constructors, `open`, `isModal`, `setModal`, `result`, `done`, `accept`, `reject`; `text`, `setText`, `informativeText`, `setInformativeText` | +| `QDialog`, `QMessageBox`, `QFileDialog` | constructors, `open`, `isModal`, `setModal`, `result`, `done`, `accept`, `reject`; `text`, `setText`, `informativeText`, `setInformativeText`, `addButton` (`QMessageBox\ButtonRole`), `setDefaultButton`, `setEscapeButton`, `clickedButton`; `setFileMode`, `setAcceptMode`, `setOption` (`QFileDialog\FileMode`, `AcceptMode`, `Option`), `setDirectory`, `selectFile`, `selectedFiles` | +| `QClipboard` | from `QGuiApplication::clipboard()` (null, with Qt's warning, before a `QGuiApplication`): `setText`, `text` (`QClipboard\Mode`) | | `QObject` (filters) | `installEventFilter`, `removeEventFilter` | | `QOpenGLWidget`, `QOpenGLPainter`, `QSurfaceFormat` | constructor, `makeCurrent`, `doneCurrent`, `isValid`, `defaultFramebufferObject`, `update`, `setFormat`, `format`; `QSurfaceFormat` version, profile and renderable type (`defaultFormat`, `setDefaultFormat`); trampoline: `new QOpenGLPainter(callable, ?QWidget)` runs PHP from `paintGL()` | | `QEventFilter` | trampoline: `new QEventFilter(callable)`; hands every event of each watched object to PHP as `($watched, QEvent $event)` and returns PHP's bool to Qt, the event alive only during the call | -| `QEvent`, `QInputEvent`, `QMouseEvent`, `QContextMenuEvent` | `type`, `accept`, `ignore`, `isAccepted`, `spontaneous`; `modifiers`, `timestamp`, `deviceType` (`QInputDevice\DeviceType`); mouse `button`, `buttons`, `position`, `globalPosition`; context menu `reason` (`QContextMenuEvent\Reason`), `pos`, `globalPos`; constructors for the mouse and context-menu events, and a plain `QEvent` of a type Qt sends as one (window activation, the change notices, …; other types are a ValueError), sent with `QCoreApplication::sendEvent` | -| Hit tests, hints | `QApplication::widgetAt`, `QApplication::focusWidget`, `QWidget::mapFromGlobal`, `QGuiApplication::styleHints()` → `QStyleHints` `mousePressAndHoldInterval`, `startDragDistance` | +| `QEvent`, `QInputEvent`, `QMouseEvent`, `QKeyEvent`, `QWheelEvent`, `QContextMenuEvent` | `type`, `accept`, `ignore`, `isAccepted`, `spontaneous`; `modifiers`, `timestamp`, `deviceType` (`QInputDevice\DeviceType`); mouse `button`, `buttons`, `position`, `globalPosition`; key `key` (a `Qt\Key` value), `text`, `isAutoRepeat`, `nativeScanCode`, `nativeVirtualKey`; wheel `pixelDelta`, `angleDelta`, `position`, `globalPosition`, `source` (`Qt\MouseEventSource`); context menu `reason` (`QContextMenuEvent\Reason`), `pos`, `globalPos`; constructors for the mouse, key, wheel and context-menu events, and a plain `QEvent` of a type Qt sends as one (window activation, the change notices, …; other types are a ValueError), sent with `QCoreApplication::sendEvent` | +| Hit tests, hints | `QApplication::widgetAt`, `QApplication::focusWidget`, `QApplication::activeModalWidget`, `QWidget::mapFromGlobal`, `QGuiApplication::styleHints()` → `QStyleHints` `mousePressAndHoldInterval`, `startDragDistance`, `mouseDoubleClickInterval`, `mouseDoubleClickDistance` | | `QLayout`, `QLayoutItem` | `setSpacing`, `spacing`, `setContentsMargins`, `count`, `removeWidget`, `indexOf`, `itemAt` (a `QLayoutItem`: `widget`, `layout`, `spacerItem`) | | `QBoxLayout`, `QVBoxLayout`, `QHBoxLayout` | constructors (optional parent widget), `addWidget(widget, stretch, alignment)`, `insertWidget`, `addLayout`, `addStretch`, `setStretch`, `setAlignment`; widgets added before the layout is installed are held until it is | | `QGridLayout` | constructor, `addWidget(widget, row, column, rowSpan, columnSpan, alignment)`, `addItem(QSpacerItem, …)`, `rowCount`, `columnCount`, `setRowStretch`, `setColumnStretch`, `rowStretch`, `columnStretch`, `setHorizontalSpacing`, `setVerticalSpacing`, `itemAtPosition` (a `QLayoutItem`) | | `QSpacerItem` | constructor (w, h, policies), `changeSize`, `sizeHint` | | `QLabel` | constructor, `text`, `setText`, `alignment`, `setAlignment`, `wordWrap`, `setWordWrap`, `setScaledContents`, `pixmap`, `setPixmap`, `textFormat`, `setTextFormat`, `openExternalLinks`, `setOpenExternalLinks`, `textInteractionFlags`, `setTextInteractionFlags`; signals `linkActivated(QString)`, `linkHovered(QString)` | | `QDesktopServices` | `openUrl(QUrl)` | -| `QAbstractButton`, `QPushButton`, `QCheckBox` | constructors (text, parent), `text`, `setText`, `isCheckable`, `setCheckable`, `isChecked`, `setChecked`, `click`, `toggle`; signals `clicked(bool)`, `toggled(bool)` | +| `QAbstractButton`, `QPushButton`, `QCheckBox` | constructors (text, parent), `text`, `setText`, `isCheckable`, `setCheckable`, `isChecked`, `setChecked`, `click`, `toggle`, push button `isDefault`, `setDefault`; signals `clicked(bool)`, `toggled(bool)` | | `QAbstractSlider`, `QSlider`, `QScrollBar` | QScrollBar has no public constructor (`verticalScrollBar`, `horizontalScrollBar`); QSlider constructor (orientation, vertical by default as in Qt, parent), `minimum`, `maximum`, `setRange`, `value`, `setValue`, `orientation`, `setOrientation`; signal `valueChanged(int)` | | `QComboBox` | constructor, `addItem`, `addItems`, `clear`, `count`, `currentIndex`, `setCurrentIndex`, `currentText`, `itemText`; signal `currentIndexChanged(int)` | | `QLineEdit` | constructor (text, parent), `text`, `setText`, `placeholderText`, `setPlaceholderText`, `echoMode`, `setEchoMode`, `isReadOnly`, `setReadOnly`, `cut`, `copy`, `paste`, `selectAll`, `undo`, `redo`, `selectedText`; signals `textChanged(QString)`, `returnPressed()` | @@ -64,6 +66,7 @@ style them, drive the common controls and play video: | `QFrame`, `QScrollArea` | constructors, `frameShape`, `setFrameShape`, `setFrameShadow`; `setWidget`, `widget`, `setWidgetResizable`, `setHorizontalScrollBarPolicy`, `setVerticalScrollBarPolicy` | | `QSplitter`, `QSplitterHandle`, `QTabWidget` | `QSplitter(Qt\Orientation)`, `orientation`/`setOrientation`, `addWidget`, `insertWidget` (outside 0..count appends), `count`, `widget`, `indexOf`, `setSizes`/`sizes`, `handleWidth`/`setHandleWidth`, `opaqueResize`/`setOpaqueResize`, `childrenCollapsible`/`setChildrenCollapsible`, `setStretchFactor`, `handle`; handle `splitter`; signal `splitterMoved(int,int)`. `addTab`, `removeTab`, `indexOf`, `count`, `currentIndex`/`setCurrentIndex`, `currentWidget`, `setTabText`/`tabText`, `widget`, `documentMode`/`setDocumentMode` (Qt's own index handling: a bad tab index does nothing, `tabText` gives ""); signal `currentChanged(int)` | | `QAbstractItemView`, `QTableWidget`, `QTableWidgetItem` | `setSelectionBehavior`, `setSelectionMode`, `setEditTriggers`, `NO_EDIT_TRIGGERS`; constructor (rows, columns, parent), `rowCount`, `setRowCount`, `columnCount`, `setColumnCount`, `setHorizontalHeaderLabels`, `horizontalHeaderItem`, `selectedItems`, `clearSelection`, `setItem`, `item`, `clearContents`, `currentRow`, `selectRow`; item constructor, `text`, `setText`, `row`; signals `itemSelectionChanged()`, `cellClicked(int,int)` | +| `QTreeWidget`, `QTreeWidgetItem` | constructor (parent), `columnCount`, `setColumnCount`, `setHeaderLabels`, `addTopLevelItem`, `clear`, `currentItem`, `setCurrentItem`, `scrollToItem` (`QAbstractItemView\ScrollHint`), `expandItem`, `collapseItem`, `selectedItems`; item constructor (type), `text`/`setText`, `foreground`/`setForeground` (`QBrush`), `data`/`setData` (column, role, value as QVariant holds it), `addChild`, `childCount`, `child`, `parent`, `isExpanded`/`setExpanded`, `childIndicatorPolicy`/`setChildIndicatorPolicy`; signals `currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)`, `itemSelectionChanged()`, `itemActivated(QTreeWidgetItem*,int)`, `itemExpanded(QTreeWidgetItem*)`, `itemCollapsed(QTreeWidgetItem*)`, their items as the wrappers PHP holds | | `QFont`, `QPixmap` | values: constructor (family, point size, weight, italic), `family`, `setFamily`, `pointSizeF`, `setPointSizeF`, `weight`, `setWeight`; constructor, `load`, `isNull`, `width`, `height`, `scaled`, `devicePixelRatio`, `setDevicePixelRatio` | | `QImage`, `QByteArray` | `QImage` borrows the buffer at an address (a `QByteArray`'s or a framebuffer's `pointer()`, trusted), with Qt's cleanup function and info: constructor (data, width, height, bytes per line, `QImage\Format`, cleanup, info), `isNull`, `width`, `height`, `format`, `copy`; `QPixmap::fromImage` turns it into a pixmap. `QByteArray(data, size)`, `size`, `pointer` | | `QPoint`, `QPointF`, `QSize`, `QRect`, `QDate`, `QKeySequence` | values, as Qt's own: points, sizes and rects in and out of the widget, event and layout calls; `QDate` (`toString`, `fromString` by `Qt\DateFormat`); `QKeySequence` (`toString`, `fromString` by `QKeySequence\SequenceFormat`) | @@ -77,10 +80,12 @@ Enums: `QEventLoop\ProcessEventsFlag`, `Qt\TimerType`, `QSocketNotifier\Type`, `QSizePolicy\Policy`, `QLineEdit\EchoMode`, `QFont\Weight`, `QTextCursor\MoveMode`, `QTextCursor\MoveOperation`, `QPlainTextEdit\LineWrapMode`, `QFontDatabase\SystemFont`, `Qt\TextInteractionFlag`, `QFrame\Shape`, `QFrame\Shadow`, `QAbstractItemView\SelectionBehavior`, -`QAbstractItemView\SelectionMode`, `QMediaPlayer\PlaybackState`, +`QAbstractItemView\SelectionMode`, `QAbstractItemView\ScrollHint`, +`QTreeWidgetItem\ChildIndicatorPolicy`, `QMediaPlayer\PlaybackState`, `QMediaPlayer\MediaStatus`, `QMediaPlayer\Error`, and, generated from the Qt -headers by `scripts/gen-enum.php`, `QEvent\Type`, `Qt\WidgetAttribute` and -`Qt\AlignmentFlag`. QFlags parameters (`alignment`) take the enum or an OR'd int. +headers by `scripts/gen-enum.php`, `QEvent\Type`, `Qt\WidgetAttribute`, +`Qt\AlignmentFlag`, `Qt\ItemDataRole`, `Qt\ScrollPhase`, `Qt\MouseEventSource` and `Qt\Key` (`KEY_0` … `KEY_9` keep the +prefix; `DEAD_a` / `DEAD_A` keep the case that tells them apart). QFlags parameters (`alignment`) take the enum or an OR'd int. Function: `qVersion()`. The stubs in `stubs/` are the full declaration. Signals connect by the signature `SIGNAL()` spells, and the callable receives @@ -91,6 +96,18 @@ QObject::connect($timer, 'timeout()', fn () => …); QObject::connect($notifier, 'activated(QSocketDescriptor,QSocketNotifier::Type)', fn (int $fd, int $type) => …); ``` +## Coverage + +[SUPPORT.md](https://github.com/php-io-extensions/qt/blob/v0.10.5/SUPPORT.md) lists every public member function name of the classes QtCore, QtGui and +QtWidgets declare (signals, operators, deprecated members and the value classes the extension +marshals as PHP strings and arrays left out; overloads count once), each marked bound or not; the +API badge is computed from it. Regenerate both after a binding change, and check them: + +```bash +php84 scripts/support.php # writes SUPPORT.md and the badge +php84 scripts/support.php --check # exits 1 when either is stale +``` + ## Example ```php diff --git a/ecosystem/standalone-extensions/qt/runbooks/generating-enums.md b/ecosystem/standalone-extensions/qt/runbooks/generating-enums.md index f90bf0f..3ac75bd 100644 --- a/ecosystem/standalone-extensions/qt/runbooks/generating-enums.md +++ b/ecosystem/standalone-extensions/qt/runbooks/generating-enums.md @@ -11,6 +11,9 @@ php scripts/gen-enum.php
[strip-prefi php scripts/gen-enum.php $QT/QtCore/qcoreevent.h Type QEvent 'QEvent\Type' \ --since=6.6:DevicePixelRatioChange --since=6.7:ChildWindowAdded,ChildWindowRemoved,ParentWindowAboutToChange,ParentWindowChange php scripts/gen-enum.php $QT/QtCore/qnamespace.h WidgetAttribute Qt 'Qt\WidgetAttribute' WA_ +php scripts/gen-enum.php $QT/QtCore/qnamespace.h ItemDataRole Qt 'Qt\ItemDataRole' \ + --since=6.10:RangeModelDataRole,StandardItemFlagsRole,FileInfoRole --since=6.11:RangeModelAdapterRole +php scripts/gen-enum.php $QT/QtCore/qnamespace.h Key Qt 'Qt\Key' Key_ --since=6.7:Key_micro --since=6.10:Key_Keyboard ``` Output: @@ -20,11 +23,11 @@ Output: Rules the generator applies: -* Names: prefix stripped, CamelCase → SCREAMING_SNAKE. +* Names: prefix stripped, CamelCase → SCREAMING_SNAKE. A name the strip would start with a digit keeps its prefix (`Key_0` → `KEY_0`). Names that differ only in case keep the case of their one-letter word (`Key_Dead_a` → `DEAD_a`, `Key_Dead_A` → `DEAD_A`). * Values: numbers, hex, earlier enumerators, `| + - <<` evaluated. * Repeated value → later name dropped (backed PHP enum holds each value once). * Preprocessor follows a default Qt build: `#ifndef` / `#if` blocks kept, `#ifdef` blocks skipped, `#else` flips. -* `Q_DECL_ENUMERATOR_DEPRECATED` names kept. +* `Q_DECL_ENUMERATOR_DEPRECATED` names kept; the macro and its message (which may hold a comma, as `Key_mu`'s does) are dropped before the body is split. * `--since` wraps the named checks in `#if QT_VERSION >= QT_VERSION_CHECK(major, minor, 0)`: values newer than the floor (Qt 6.4) are proven on a Qt that has them, skipped on one that does not. Repeat per version. Guards in force (`QEvent_Type.inc`, generated from Qt 6.8.2's `qcoreevent.h`; Qt 6.11 adds nothing the stub carries): @@ -34,5 +37,13 @@ Guards in force (`QEvent_Type.inc`, generated from Qt 6.8.2's `qcoreevent.h`; Qt | `DevicePixelRatioChange` | 6.6 | | `ChildWindowAdded`, `ChildWindowRemoved`, `ParentWindowAboutToChange`, `ParentWindowChange` | 6.7 | +`Qt_ItemDataRole.inc` and `Qt_Key.inc` were generated from Qt 6.11.2's `qnamespace.h`. Each was diffed against Qt 6.4.2's, and the Pi's 6.8.2 and the qtbase 6.6/6.7 and 6.9/6.10 sources fixed each version: + +| Enumerator | Since | +|---|---| +| `Key_micro` (6.4 spells 0xb5 `Key_mu`) | 6.7 | +| `Key_Keyboard`, `RangeModelDataRole`, `StandardItemFlagsRole`, `FileInfoRole` | 6.10 | +| `RangeModelAdapterRole` | 6.11 | + [^gen]: scripts/gen-enum.php [^module]: src/qt.cpp