Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • videolan/vlc
  • chouquette/vlc
  • bakiewicz.marek122/vlc
  • devnexen/vlc
  • rohanrajpal/vlc
  • blurrrb/vlc
  • gsoc/gsoc2019/darkapex/vlc
  • b1ue/vlc
  • fkuehne/vlc
  • magsoft/vlc
  • chub/vlc
  • cramiro9/vlc
  • robUx4/vlc
  • rom1v/vlc
  • akshayaky/vlc
  • tmk907/vlc
  • akymaster/vlc
  • govind.sharma/vlc
  • psilokos/vlc
  • xjbeta/vlc
  • jahan/vlc
  • 1480c1/vlc
  • amanchande/vlc
  • aaqib/vlc
  • rist/vlc
  • apol/vlc
  • mindfreeze/vlc
  • alexandre-janniaux/vlc
  • sandsmark/vlc
  • jagannatharjun/vlc
  • gsoc/gsoc2020/matiaslgonzalez/vlc
  • gsoc/gsoc2020/jagannatharjun/vlc
  • mstorsjo/vlc
  • gsoc/gsoc2020/vedenta/vlc
  • gsoc/gsoc2020/arnav-ishaan/vlc
  • gsoc/gsoc2020/andreduong/vlc
  • fuzun/vlc
  • gsoc/gsoc2020/vatsin/vlc
  • gsoc/gsoc2020/sagid/vlc
  • yaron/vlc
  • Phoenix/vlc
  • Garf/vlc
  • ePiratWorkarounds/vlc
  • tguillem/vlc
  • jnqnfe/vlc
  • mdc/vlc
  • Vedaa/vlc
  • rasa/vlc
  • quink/vlc
  • yealo/vlc
  • aleksey_ak/vlc
  • ePirat/vlc
  • ilya.yanok/vlc
  • asenat/vlc
  • m/vlc
  • bunjee/vlc
  • BLumia/vlc
  • sagudev/vlc
  • hamedmonji30/vlc
  • nullgemm/vlc
  • DivyamAhuja/vlc
  • thesamesam/vlc
  • dag7/vlc
  • snehil101/vlc
  • haasn/vlc
  • jbk/vlc
  • ValZapod/vlc
  • mfkl/vlc
  • WangChuan/vlc
  • core1024/vlc
  • GhostVaibhav/vlc
  • dfuhrmann/vlc
  • davide.prade/vlc
  • tmatth/vlc
  • Courmisch/vlc
  • zouya/vlc
  • hpi/vlc
  • EwoutH/vlc
  • aleung27/vlc
  • hengwu0/vlc
  • saladin/vlc
  • ashuio/vlc
  • richselwood/vlc
  • verma16Ayush/vlc
  • chemicalflash/vlc
  • PoignardAzur/vlc
  • huangjieNT/vlc
  • Blake-Haydon/vlc
  • AnuthaDev/vlc
  • gsoc/gsoc2021/mpd/vlc
  • nicolas_lequec/vlc
  • sambassaly/vlc
  • thresh/vlc
  • bonniegong/vlc
  • myaashish/vlc
  • stavros.vagionitis/vlc
  • ileoo/vlc
  • louis-santucci/vlc
  • cchristiansen/vlc
  • sabyasachi07/vlc
  • AbduAmeen/vlc
  • ashishb0410/vlc
  • urbanhusky/vlc
  • davidepietrasanta/vlc
  • riksleutelstad/vlc
  • jeremyVignelles/vlc
  • komh/vlc
  • iamjithinjohn/vlc
  • JohannesKauffmann/vlc2
  • kunglao/vlc
  • natzberg/vlc
  • jill/vlc
  • cwendling/vlc
  • adufou/vlc
  • ErwanAirone/vlc
  • HasinduDilshan10/vlc
  • vagrantc/vlc
  • rafiv/macos-bigsur-icon
  • Aymeriic/vlc
  • saranshg20/vlc
  • metzlove24/vlc
  • linkfanel/vlc
  • Ds886/vlc
  • metehan-arslan/vlc
  • Skantes/vlc
  • kgsandundananjaya96/vlc
  • mitchcapper/vlc
  • advaitgupta/vlc
  • StefanBruens/vlc
  • ratajs/vlc
  • T.M.F.B.3761/vlc
  • m222059/vlc
  • casemerrick/vlc
  • joshuaword2alt/vlc
  • sjwaddy/vlc
  • dima/vlc
  • Ybalrid/vlc
  • umxprime/vlc
  • eschmidt/vlc
  • vannieuwenhuysenmichelle/vlc
  • badcf00d/vlc
  • wesinator/vlc
  • louis/vlc
  • xqq/vlc
  • EmperorYP7/vlc
  • NicoLiam/vlc
  • loveleen/vlc
  • rofferom/vlc
  • rbultje/vlc
  • TheUnamed/vlc
  • pratiksharma341/vlc
  • Saurab17/vlc
  • purist.coder/vlc
  • Shuicheng/vlc
  • mdrrubel292/vlc
  • silverbleu00/vlc
  • metif12/vlc
  • asher-m/vlc
  • jeffk/vlc
  • Brandonbr1/vlc
  • beautyyuyanli/vlc
  • rego21/vlc
  • muyangren907/vlc
  • collectionbylawrencejason/vlc
  • evelez/vlc
  • GSMgeeth/vlc
  • Oneric/vlc
  • TJ5/vlc
  • XuanTung95/vlc
  • darrenjenny21/vlc
  • Trenly/vlc
  • RockyTDR/vlc
  • mjakubowski/vlc
  • caprica/vlc
  • ForteFrankie/vlc
  • seannamiller19/vlc
  • junlon2006/vlc
  • kiwiren6666/vlc
  • iuseiphonexs/vlc
  • fenngtun/vlc
  • Rajdutt999/vlc
  • typx/vlc
  • leon.vitanos/vlc
  • robertogarci0938/vlc
  • gsoc/gsoc2022/luc65r/vlc-mpd
  • skeller/vlc
  • MCJack123/vlc
  • luc65r/vlc-mpd
  • popov895/vlc
  • claucambra/vlc
  • brad/vlc
  • matthewmurua88/vlc
  • Tomas8874/vlc
  • philenotfound/vlc
  • makita-do3/vlc
  • LZXCorp/vlc
  • mar0x/vlc
  • senojetkennedy0102/vlc
  • shaneb243/vlc
  • ahmadbader/vlc
  • rajduttcse26/vlc-audio-filters
  • Juniorzito8415/vlc
  • achernyakov/vlc
  • lucasjetgroup/vlc
  • pupdoggy666/vlc
  • gmde9363/vlc
  • alexnwayne/vlc
  • bahareebrahimi781/vlc
  • hamad633666/vlc
  • umghof3112/vlc
  • joe0199771874/vlc
  • Octocats66666666/vlc
  • jjm_223/vlc
  • btech10110.19/vlc
  • sunnykfc028/vlc-audio-filters
  • loic/vlc
  • nguyenminhducmx1/vlc
  • JanekKrueger/vlc
  • bstubbington2/vlc
  • rcombs/vlc
  • Ordissimo/vlc
  • king7532/vlc
  • noobsauce101/vlc
  • schong0525/vlc
  • myQwil/vlc
  • apisbg91/vlc
  • geeboy0101017/vlc
  • kim.faughey/vlc
  • nurupo/vlc
  • yyusea/vlc
  • 0711235879.khco/vlc
  • ialo/vlc
  • iloveyeye2/vlc
  • gdtdftdqtd/vlc
  • leandroconsiglio/vlc
  • AndyHTML2012/vlc
  • ncz/vlc
  • lucenticus/vlc
  • knr1931/vlc
  • kjoonlee/vlc
  • chandrakant100/vlc-qt
  • johge42/vlc
  • polter/vlc
  • hexchain/vlc
  • Tushwrld/vlc
  • mztea928/vlc
  • jbelloncastro/vlc
  • alvinhochun/vlc
  • ghostpiratecrow/vlc
  • ujjwaltwitx/vlc
  • alexsonarin06/vlc
  • adrianbon76/vlc
  • altsod/vlc
  • damien.lucas44/vlc
  • dmytrivtaisa/vlc
  • utk202/vlc
  • aaxhrj/vlc
  • thomas.hermes/vlc
  • structurenewworldorder/vlc
  • slomo/vlc
  • wantlamy/vlc
  • musc.o3cminc/vlc
  • thebarshablog/vlc
  • kerrick/vlc
  • kratos142518/vlc
  • leogps/vlc
  • vacantron/vlc
  • luna_koly/vlc
  • Ratio2/vlc
  • anuoshemohammad/vlc
  • apsun/vlc
  • aaa1115910/vlc
  • alimotmoyo/vlc
  • Ambossmann/vlc
  • Sam-LearnsToCode/vlc
  • Chilledheart/vlc
  • Labnann/vlc
  • ktcoooot1/vlc
  • mohit-marathe/vlc
  • johnddx/vlc
  • manstabuk/vlc
  • Omar-ahmed314/vlc
  • vineethkm/vlc
  • 9Enemi86/vlc
  • radoslav.m.panteleev/vlc
  • ashishami2002/vlc
  • Corbax/vlc
  • firnasahmed/vlc
  • pelayarmalam4/vlc
  • c0ff330k/vlc
  • shikhindahikar/vlc
  • l342723951/vlc
  • christianschwandner/vlc
  • douniwan5788/vlc
  • 7damian7/vlc
  • ferdnyc/vlc
  • f.ales1/vlc
  • pandagby/vlc
  • BaaBaa/vlc
  • jewe37/vlc
  • w00drow/vlc
  • russelltg/vlc
  • ironicallygod/vlc
  • soumyaDghosh/vlc
  • linzihao1999/vlc
  • deyayush6/vlc
  • mibi88/vlc
  • newabdallah10/vlc
  • jhorbincolombia/vlc
  • rimvihaqueshupto/vlc
  • andrewkhon98/vlc
  • fab78/vlc
  • lapaz17/vlc
  • amanna13/vlc
  • mdakram28/vlc
  • 07jw1980/vlc
  • sohamgupta/vlc
  • Eson-Jia1/vlc
  • Sumou/vlc
  • vikram-kangotra/vlc
  • chalice191/vlc
  • olivercalder/vlc
  • aaasg4001/vlc
  • zipdox/vlc
  • kwizart/vlc
  • Dragon-S/vlc
  • jdemeule/vlc
  • gabriel_lt/vlc
  • locutusofborg/vlc
  • sammirata/vlc-librist
  • another/vlc
  • Benjamin_Loison/vlc
  • ahmedmoselhi/vlc
  • petergaal/vlc
  • huynhsontung/vlc
  • dariusmihut/vlc
  • tvermaashutosh/vlc
  • buti/vlc
  • Niram7777/vlc
  • rohan-here/vlc
  • balaji-sivasakthi/vlc
  • rlindner81/vlc
  • Kakadus/vlc
  • djain/vlc
  • ABBurmeister/vlc
  • craighuggins/vlc
  • orbea/vlc
  • maxos/vlc
  • aakarshmj/vlc
  • kblaschke/vlc
  • ankitm/vlc
  • advait-0/vlc
  • mohak2003/vlc
  • yselkowitz/vlc
  • AZM999/vlc-azm
  • andrey.turkin/vlc
  • Disha-Baghel/vlc
  • nowrep/vlc
  • Apeng/vlc
  • Choucroute_melba/vlc
  • autra/vlc
  • eclipseo/vlc
  • fhuber/vlc
  • olafhering/vlc
  • sdasda7777/vlc
  • 1div0/vlc
  • skosnits/vlc-extended-playlist-support
  • dnicolson/vlc
  • Timshel/vlc
  • octopols/vlc
  • MangalK/vlc
  • nima64/vlc
  • misawai/vlc
  • Alexander-Wilms/vlc
  • Maxime2/vlc-fork-for-visualizer
  • ww/vlc
  • jeske/vlc
  • sgross-emlix/vlc
  • morenonatural/vlc
  • freakingLovesVLC/vlc
  • borisgolovnev/vlc
  • mpromonet/vlc
  • diogo.simao-marques/vlc
  • masstock/vlc
  • pratikpatel8982/vlc
  • hugok79/vlc
  • longervision/vlc
  • abhiudaysurya/vlc
  • rishabhgarg/vlc
  • tumic/vlc
  • cart/vlc
  • shubham442/vlc
  • Aditya692005/vlc
  • sammirata/vlc4
  • syrykh/vlc
  • Vvorcun/macos-new-icon
  • AyaanshC/vlc
  • nasso/vlc
  • Quark/vlc
  • sebastinas/vlc
  • rhstone/vlc
  • talregev/vlc
  • Managor/vlc
  • abdsaber000/vlc
  • falbrechtskirchinger/vlc
405 results
Show changes
Commits on Source (4)
......@@ -3951,6 +3951,7 @@ AC_ARG_ENABLE([qt],
])
have_qt5_x11="no"
have_qt5_gtk="no"
have_qt5_quick_test="no"
AS_IF([test "${enable_qt}" != "no"], [
PKG_CHECK_MODULES([QT], [Qt5Core >= 5.12.0 Qt5Widgets Qt5Gui Qt5Quick Qt5QuickWidgets Qt5QuickControls2 Qt5Svg], [
PKG_CHECK_MODULES([QT5_X11], [Qt5X11Extras], [
......@@ -3965,6 +3966,11 @@ AS_IF([test "${enable_qt}" != "no"], [
AC_MSG_WARN([Not building Qt Interface with wayland support.])
])
PKG_CHECK_MODULES([QT5_QUICK_TEST], [Qt5QuickTest], [
have_qt5_quick_test="yes"
],[
])
QT_PATH="$(eval $PKG_CONFIG --variable=exec_prefix Qt5Core)"
QT_HOST_PATH="$(eval $PKG_CONFIG --variable=host_bins Qt5Core)"
QT_INCLUDE_DIRECTORY="$(eval $PKG_CONFIG --variable=includedir Qt5Core)"
......@@ -4069,6 +4075,7 @@ AM_CONDITIONAL([HAVE_QT5_X11], [test "${have_qt5_x11}" = "yes"])
AM_CONDITIONAL([HAVE_QT5_WAYLAND], [test "${have_qt5_wayland}" = "yes"])
AM_CONDITIONAL([HAVE_QT5_GTK], [test "${have_qt5_gtk}" = "yes"])
AM_CONDITIONAL([HAVE_QT5_DECLARATIVE_PRIVATE], [test "${have_declarative_private}" = "yes"])
AM_CONDITIONAL([HAVE_QT5_QUICK_TEST], [test "${have_qt5_quick_test}" = "yes"])
dnl
dnl detect kde4-config patch (used for kde solids).
......
......@@ -982,6 +982,8 @@ libqt_plugin_la_QML = \
gui/qt/util/qml/ViewDragAutoScrollHandler.qml \
gui/qt/util/qml/BindingRev8.qml \
gui/qt/util/qml/BindingRev14.qml \
gui/qt/util/qml/FSM.qml \
gui/qt/util/qml/FSMState.qml \
gui/qt/util/qml/VanillaObject.qml \
gui/qt/util/qml/NativeMenu.qml \
gui/qt/util/qml/MLContextMenu.qml \
......@@ -1138,4 +1140,21 @@ if !HAVE_OS2
pkglibexec_PROGRAMS += vlc-qt-check
endif
endif
if HAVE_QT5_QUICK_TEST
qml_test_SOURCES = gui/qt/tests/qml_test.cpp
nodist_qml_test_SOURCES = gui/qt/resources.cpp
if HAVE_QMLCACHE
nodist_qml_test_SOURCES += gui/qt/qmlcache_loader.cpp $(libqt_plugin_la_QML)
endif
qml_test_CXXFLAGS = $(AM_CXXFLAGS) $(QT_CFLAGS) -fPIC $(CXXFLAGS_qt) ${QT5_QUICK_TEST_CFLAGS} -DQUICK_TEST_SOURCE_DIR="\"${srcdir}/gui/qt/tests\""
qml_test_LDADD = $(QT_LIBS) $(LIBS_qt) $(QT5_PLUGINS_LIBS) ${QT5_QUICK_TEST_LIBS}
check_PROGRAMS += qml_test
EXTRA_DIST += gui/qt/tests/tst_FSM.qml
TESTS += qml_test
endif
endif
......@@ -539,9 +539,14 @@ if host_system == 'windows'
endif
if qt5_dep.found()
qt5pre_qrc = qt5.preprocess(
qresources: qrc_files,
include_directories: qt_include_dir,
dependencies: qt5_dep)
qt5pre_files = qt5.preprocess(ui_files: ui_sources,
moc_headers: moc_headers,
qresources: qrc_files,
include_directories: qt_include_dir,
dependencies: qt5_dep)
......@@ -681,11 +686,23 @@ if qt5_dep.found()
vlc_modules += {
'name' : 'qt',
'sources' : [qt5pre_files, qt_sources, some_sources],
'sources' : [qt5pre_files, qt5pre_qrc, qt_sources, some_sources],
'dependencies' : [qt5_dep, qt_extra_deps],
'include_directories' : qt_include_dir,
'c_args' : qt_extra_flags,
'cpp_args' : [qt_extra_flags, qt_cppargs]
}
test_qt5_dep = dependency('qt5', modules: ['QuickTest'], required: false)
if test_qt5_dep.found()
qml_test = executable(
'qml_test',
files('tests/qml_test.cpp'),
qt5pre_qrc,
build_by_default: false,
dependencies: [test_qt5_dep],
cpp_args: ['-DQUICK_TEST_SOURCE_DIR="' + meson.current_source_dir() + '/tests"']
)
test('qml_test', qml_test, suite:'qt')
endif
endif
......@@ -152,12 +152,10 @@ FocusScope {
id: playlistVisibility
onShowPlaylist: {
rootPlayer.lockUnlockAutoHide(true)
MainCtx.playlistVisible = true
}
onHidePlaylist: {
rootPlayer.lockUnlockAutoHide(false)
MainCtx.playlistVisible = false
}
}
......
......@@ -18,6 +18,8 @@
import QtQuick 2.12
import org.videolan.vlc 0.1
import "qrc:///util/" as Util
/**
* playlist visibility state machine
*
......@@ -37,7 +39,7 @@ import org.videolan.vlc 0.1
* @enduml
*
*/
Item {
Util.FSM {
id: fsm
//incoming signals
......@@ -51,180 +53,101 @@ Item {
signal hidePlaylist()
//exposed internal states
property alias isPlaylistVisible: fsmVisible.enabled
property var _substate: undefined
function setState(state, substate) {
//callLater is used to avoid Connections on a signal to be immediatly
//re-trigered on the new state
Qt.callLater(function() {
if (state._substate === substate)
return;
if (state._substate !== undefined) {
if (state._substate.exited) {
state._substate.exited()
}
state._substate.enabled = false
}
property alias isPlaylistVisible: fsmVisible.active
state._substate = substate
initialState: MainCtx.playlistDocked ? fsmDocked : fsmFloating
if (state._substate !== undefined) {
state._substate.enabled = true
if (state._substate.entered) {
state._substate.entered()
}
}
})
}
//initial state
Component.onCompleted: {
if (MainCtx.playlistDocked)
fsm.setState(fsm, fsmDocked)
else
fsm.setState(fsm, fsmFloating)
}
signalMap: ({
togglePlaylistVisibility: fsm.togglePlaylistVisibility,
updatePlaylistVisible: fsm.updatePlaylistVisible,
updatePlaylistDocked: fsm.updatePlaylistDocked,
updateVideoEmbed: fsm.updateVideoEmbed
})
Item {
Util.FSMState {
id: fsmFloating
enabled: false
Connections {
target: fsm
//explicitly bind on parent enabled, as Connections doens't behave as Item
//regarding enabled propagation on children, ditto bellow
enabled: fsmFloating.enabled
onTogglePlaylistVisibility: {
MainCtx.playlistVisible = !MainCtx.playlistVisible
}
onUpdatePlaylistDocked: {
if (MainCtx.playlistDocked) {
fsm.setState(fsm, fsmDocked)
transitions: ({
togglePlaylistVisibility: {
action: () => {
MainCtx.playlistVisible = !MainCtx.playlistVisible
}
},
updatePlaylistDocked: {
guard: () => MainCtx.playlistDocked,
target: fsmDocked
}
}
})
}
Item {
Util.FSMState {
id: fsmDocked
enabled: false
property var _substate: undefined
function entered() {
if(MainCtx.hasEmbededVideo) {
fsm.setState(fsmDocked, fsmForceHidden)
} else {
fsm.setState(fsmDocked, fsmFollowVisible)
}
}
function exited() {
fsm.setState(fsmDocked, undefined)
}
initialState: MainCtx.hasEmbededVideo ? fsmForceHidden : fsmFollowVisible
Item {
Util.FSMState {
id: fsmFollowVisible
enabled: false
property var _substate: undefined
function entered() {
if(MainCtx.playlistVisible)
fsm.setState(this, fsmVisible)
else
fsm.setState(this, fsmHidden)
}
initialState: MainCtx.playlistVisible ? fsmVisible : fsmHidden
function exited() {
fsm.setState(this, undefined)
}
Connections {
target: fsm
enabled: fsmFollowVisible.enabled
onUpdatePlaylistDocked: {
if (!MainCtx.playlistDocked) {
fsm.setState(fsm, fsmFloating)
}
}
onUpdateVideoEmbed: {
if (MainCtx.hasEmbededVideo) {
fsm.setState(fsmDocked, fsmForceHidden)
}
transitions: ({
updatePlaylistDocked: {
guard: () => !MainCtx.playlistDocked,
target: fsmFloating
},
updateVideoEmbed: {
guard: () => MainCtx.hasEmbededVideo,
target: fsmForceHidden
}
}
})
Item {
Util.FSMState {
id: fsmVisible
enabled: false
function entered() {
fsm.showPlaylist()
}
function exited() {
fsm.hidePlaylist()
}
Connections {
target: fsm
enabled: fsmVisible.enabled
onUpdatePlaylistVisible: {
if (!MainCtx.playlistVisible)
fsm.setState(fsmFollowVisible, fsmHidden)
transitions: ({
updatePlaylistVisible: {
guard: ()=> !MainCtx.playlistVisible,
target: fsmHidden
},
togglePlaylistVisibility: {
action: () => { MainCtx.playlistVisible = false },
target: fsmHidden
}
onTogglePlaylistVisibility: fsm.setState(fsmFollowVisible, fsmHidden)
}
})
}
Item {
Util.FSMState {
id: fsmHidden
enabled: false
Connections {
target: fsm
enabled: fsmHidden.enabled
onUpdatePlaylistVisible: {
if (MainCtx.playlistVisible)
fsm.setState(fsmFollowVisible, fsmVisible)
transitions: ({
updatePlaylistVisible: {
guard: () => MainCtx.playlistVisible,
target: fsmVisible
},
togglePlaylistVisibility: {
action: () => { MainCtx.playlistVisible = true },
target: fsmVisible
}
onTogglePlaylistVisibility: fsm.setState(fsmFollowVisible, fsmVisible)
}
})
}
}
Item {
Util.FSMState {
id: fsmForceHidden
enabled: false
Connections {
target: fsm
enabled: fsmForceHidden.enabled
onUpdateVideoEmbed: {
if (!MainCtx.hasEmbededVideo) {
fsm.setState(fsmDocked, fsmFollowVisible)
}
transitions: ({
updateVideoEmbed: {
guard: () => !MainCtx.hasEmbededVideo,
target: fsmFollowVisible
},
togglePlaylistVisibility: {
action: () => {
MainCtx.playlistVisible = true
},
target: fsmFollowVisible
}
onTogglePlaylistVisibility: {
MainCtx.playlistVisible = true
fsm.setState(fsmDocked, fsmFollowVisible)
}
}
})
}
}
}
......@@ -25,6 +25,7 @@ import org.videolan.vlc 0.1
import "qrc:///widgets/" as Widgets
import "qrc:///style/"
import "qrc:///util/Helpers.js" as Helpers
import "qrc:///util/" as Util
Slider {
id: control
......@@ -86,7 +87,7 @@ Slider {
pos: Qt.point(sliderRectMouseArea.mouseX, 0)
}
QtObject {
Util.FSM {
id: fsm
signal playerUpdatePosition(real position)
signal pressControl(real position, bool forcePrecise)
......@@ -95,83 +96,14 @@ Slider {
//each signal is associated to a key, when a signal is received,
//transitions of active state for the given key are evaluated
property var signalMap: ({
playerUpdatePosition: fsm.playerUpdatePosition,
pressControl: fsm.pressControl,
releaseControl: fsm.releaseControl,
moveControl: fsm.moveControl
signalMap: ({
"playerUpdatePosition": fsm.playerUpdatePosition,
"pressControl": fsm.pressControl,
"releaseControl": fsm.releaseControl,
"moveControl": fsm.moveControl
})
property var initialState: fsmReleased
property var _state: null
function _evaluateTransition(state, event, t, ...args) {
if ("guard" in t) {
if (!(t.guard instanceof Function)) {
console.error(`guard property of ${state}::${event} is not a function`)
}
if (!t.guard(...args))
return false
}
if ("action" in t) {
if (!(t.action instanceof Function))
console.error(`action property of ${state}::${event} is not a function`)
t.action(...args)
}
if ("target" in t)
changeState(t.target)
return true
}
function handleSignal(event, state, ...args) {
if (!state)
return
if (!(event in _state.transitions))
return
const transitions = state.transitions[event]
if (Array.isArray(transitions)) {
for (const t of transitions) {
//stop at the first accepted transition
if (_evaluateTransition(state, event, t, ...args))
return
}
} else {
_evaluateTransition(state, event, transitions, ...args)
}
}
function changeState(state) {
if (_state) {
if (_state.exit instanceof Function)
_state.exit()
}
_state = state
if (_state) {
if (_state.enter instanceof Function)
_state.enter()
}
}
Component.onCompleted: {
for (const signalName of Object.keys(signalMap)) {
signalMap[signalName].connect((...args) => {
//use callLater to ensure transitions are ordered.
//signal are not queued by default, this is an issue
//if an action/enter/exit function raise another signal
Qt.callLater(() => {
handleSignal(signalName, fsm._state, ...args)
})
})
}
changeState(initialState)
}
initialState: fsmReleased
function _seekToPosition(position, threshold, forcePrecise) {
position = Helpers.clamp(position, 0., 1.)
......@@ -186,38 +118,39 @@ Slider {
Player.position = position
}
property list<QtObject> subStates: [
QtObject {
id: fsmReleased
property var transitions: ({
playerUpdatePosition: {
action: (position) => {
control.value = position
}
},
pressControl: {
action: (position, forcePrecise) => {
control.forceActiveFocus()
fsm._seekToPosition(position, VLCStyle.dp(4) / control.width, forcePrecise)
},
target: fsmHeld
Util.FSMState {
id: fsmReleased
transitions: ({
"playerUpdatePosition": {
action: (position) => {
control.value = position
}
})
},
QtObject {
id: fsmHeld
property var transitions: ({
moveControl: {
action: (position, forcePrecise) => {
fsm._seekToPosition(position, VLCStyle.dp(2) / control.width, forcePrecise)
}
},
"pressControl": {
action: (position, forcePrecise) => {
control.forceActiveFocus()
fsm._seekToPosition(position, VLCStyle.dp(4) / control.width, forcePrecise)
},
releaseControl: {
target: fsmReleased
target: fsmHeld
}
})
}
Util.FSMState {
id: fsmHeld
transitions: ({
"moveControl": {
action: (position, forcePrecise) => {
fsm._seekToPosition(position, VLCStyle.dp(2) / control.width, forcePrecise)
}
})
}
]
},
"releaseControl": {
target: fsmReleased
}
})
}
}
Connections {
......
/*****************************************************************************
* Copyright (C) 2023 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <QtQuickTest>
// not much right now, type registration & initialisation may be required later on
// https://doc.qt.io/qt-5/qtquicktest-index.html#executing-c-before-qml-tests
int main(int argc, char **argv)
{
QTEST_SET_MAIN_SOURCE_PATH
//run tests offscreen as the CI doesn't have a desktop environment
qputenv("QT_QPA_PLATFORM", "offscreen");
Q_INIT_RESOURCE(vlc);
return quick_test_main(argc, argv, "qml_test", QUICK_TEST_SOURCE_DIR);
}
/*****************************************************************************
* Copyright (C) 2023 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* ( at your option ) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
import QtQuick 2.12
import QtTest 1.12
import "qrc:///util/" as Util
TestCase {
id: root
name: "FSM"
property var events: []
function recEvent(e) {
events.push(e)
}
Util.FSM {
id: fsm
initialState: fsmA
signal signalSelfInternal()
signal signalSelfExternal()
signal signalToParent()
signal signalToSibiling()
signal signalToBA()
signal signalToC()
signal signalInParent()
signalMap: ({
signalSelfInternal: signalSelfInternal,
signalSelfExternal: signalSelfExternal,
signalToParent: signalToParent,
signalToSibiling: signalToSibiling,
signalToBA: signalToBA,
signalToC: signalToC,
signalInParent: signalInParent,
})
property bool selfTransitionDone: false
Util.FSMState {
id: fsmA
objectName: "fsmA"
initialState: fsmAA
function enter() {
recEvent("+A")
}
function exit() {
recEvent("-A")
}
Util.FSMState {
id: fsmAA
objectName: "fsmAA"
initialState: fsmAAA
function enter() {
recEvent("+AA")
}
function exit() {
recEvent("-AA")
}
transitions: ({
signalInParent: fsmC,
})
Util.FSMState {
id: fsmAAA
objectName: "fsmAAA"
function enter() {
recEvent("+AAA")
}
function exit() {
recEvent("-AAA")
}
transitions: ({
signalSelfInternal: {
action: () => { fsm.selfTransitionDone = true },
},
signalSelfExternal: {
action: () => { fsm.selfTransitionDone = true },
target: fsmAAA,
},
signalToParent: {
action: () => { fsm.selfTransitionDone = true },
target: fsmAA
},
signalToSibiling: fsmAAB,
signalToBA: fsmBA,
signalToC: fsmC,
})
}
Util.FSMState {
id: fsmAAB
objectName: "fsmAAB"
function enter() {
recEvent("+AAB")
}
function exit() {
recEvent("-AAB")
}
transitions: ({
signalToParent: {
action: () => { fsm.selfTransitionDone = true },
target: fsmAA
}
})
}
}
}
Util.FSMState {
id: fsmB
objectName: "fsmB"
//no initial state
function enter() { recEvent("+B") }
function exit() { recEvent("-B") }
Util.FSMState {
id: fsmBA
objectName: "fsmBA"
function enter() { recEvent("+BA") }
function exit() { recEvent("-BA") }
}
}
Util.FSMState {
id: fsmC
objectName: "fsmC"
function enter() {
recEvent("+C")
}
function exit() {
recEvent("-C")
}
}
}
function check_active_inactive(active, inactive)
{
for (const s of inactive)
verify(!s.active, `${s} should be inactive`)
for (const s of active)
verify(s.active, `${s} should be active`)
}
function check_events(expected)
{
verify(
expected.every((e, idx) => root.events[idx] === e),
`expected transitions are ${expected}, got "${root.events}"`)
}
function init() {
fsm.reset()
fsmSeq.reset()
fsmGuard.reset()
fsmAction.reset()
events = []
}
function test_initial_state() {
check_active_inactive([fsmA, fsmAA, fsmAAA], [fsmAAB, fsmB, fsmBA, fsmC])
}
function test_reset() {
fsm.reset()
tryCompare(fsmAAA, "active", true)
check_events(["+A", "+AA", "+AAA"])
check_active_inactive([fsmA, fsmAA, fsmAAA], [fsmAAB, fsmB, fsmBA, fsmC])
}
function test_transitions_data() {
return [
{tag: "signalToSibiling", transition: fsm.signalToSibiling, events: ["-AAA", "+AAB"],
active: [fsmAAB, fsmA, fsmAA], inactive: [fsmB, fsmBA, fsmC, fsmAAA]},
{tag: "signalToBA", transition: fsm.signalToBA, events: ["-AAA", "-AA", "-A", "+B", "+BA"],
active: [fsmBA, fsmB], inactive: [fsmC, fsmA, fsmAA, fsmAAB, fsmAAA]},
{tag:"signalInParent", transition: fsm.signalInParent, events: ["-AAA", "-AA", "-A", "+C"],
active: [fsmC], inactive: [fsmB, fsmBA, fsmA, fsmAA, fsmAAB, fsmAAA]},
]
}
function test_transitions(data) {
data.transition()
tryCompare(data.active[0], "active", true)
check_events(data.events)
check_active_inactive(data.active, data.inactive)
}
function test_self_transitions_data() {
return [
{tag: "SelfInternal", transition: fsm.signalSelfInternal, events: [],
active: [fsmA, fsmAA, fsmAAA], inactive: [fsmB, fsmBA, fsmC, fsmAAB]},
{tag: "SelfExternal", transition: fsm.signalSelfExternal, events: ["-AAA", "+AAA"],
active: [fsmA, fsmAA, fsmAAA], inactive: [fsmB, fsmBA, fsmC, fsmAAB]},
{tag: "signalToParent", transition: fsm.signalToParent, events: ["-AAA", "-AA", "+AA", "+AAA"],
active: [fsmA, fsmAA, fsmAAA], inactive: [fsmB, fsmBA, fsmC, fsmAAB]},
]
}
function test_self_transitions(data) {
fsm.selfTransitionDone = false
data.transition()
//state doesn't change, can't wait for state activation
tryCompare(fsm, "selfTransitionDone", true)
check_events(data.events)
check_active_inactive(data.active, data.inactive)
}
function test_self_transitions_reset_initial_state() {
//as a setup, move to AAB
fsm.signalToSibiling()
tryCompare(fsmAAB, "active", true)
root.events = []
//move to the parent state
fsm.signalToParent()
tryCompare(fsmAAA, "active", true)
check_events(["-AAB", "-AA", "+AA", "+AAA"])
check_active_inactive([fsmA, fsmAA, fsmAAA], [fsmAAB])
}
Util.FSM {
id: fsmSeq
signal atob()
signal atoc()
signal ctoa()
signalMap: ({
atob: atob,
atoc: atoc,
ctod: ctoa,
})
initialState: seqA
Util.FSMState {
id: seqA
objectName: "seqA"
function enter() { recEvent("+A") }
function exit() { recEvent("-A") }
transitions: ({
atob: seqB,
atoc: seqC,
})
}
Util.FSMState {
id: seqB
objectName: "seqB"
function enter() { recEvent("+B") }
function exit() { recEvent("-B") }
transitions: ({
atob: seqA,
})
}
Util.FSMState {
id: seqC
objectName: "seqC"
function enter() {
recEvent("+C")
fsmSeq.ctoa()
}
function exit() { recEvent("-C") }
transitions: ({
ctod: seqD,
})
}
Util.FSMState {
id: seqD
objectName: "seqD"
function enter() { recEvent("+D") }
function exit() { recEvent("-D") }
}
}
function test_sequential_transitions_data() {
return [
{tag: "non-recursive", transition: fsmSeq.atob, events: ["-A", "+B"],
active: [seqB], inactive: [seqA, seqC]},
//C auto transition to A
{tag: "recursive", transition: fsmSeq.atoc, events: ["-A", "+C", "-C", "+D"],
active: [seqD], inactive: [seqB, seqC]},
]
}
function test_sequential_transitions(data) {
data.transition()
tryCompare(data.active[0], "active", true)
check_events(data.events)
check_active_inactive(data.active, data.inactive)
}
Util.FSM {
id: fsmGuard
signal success()
signal fail()
signal multiple()
signal multipleDefault()
signal multipleFail()
signal paramBool(var a)
signal paramMultiple(var a, var b, var c, var d, var e)
signalMap: ({
success: fsmGuard.success,
fail: fsmGuard.fail,
multiple: fsmGuard.multiple,
multipleDefault: fsmGuard.multipleDefault,
multipleFail: fsmGuard.multipleFail,
paramBool: fsmGuard.paramBool,
paramMultiple: fsmGuard.paramMultiple,
})
initialState: guardInit
Util.FSMState {
id: guardInit
objectName: "guardInit"
transitions: ({
success: {
guard: () => true,
target: guardOK
},
fail: {
guard: () => false,
target: guardFail
},
multiple: [{
guard: () => false,
target: guardFail
}, {
guard: () => true,
target: guardOK
}, {
//will not be evaluated
target: guardFail
}],
multipleFail: [{
guard: () => false,
target: guardFail
}, {
guard: () => false,
target: guardFail
}],
multipleDefault: [{
guard: () => false,
target: guardFail
}, {
target: guardOK
}, {
//will not be evaluated
target: guardFail
}],
paramBool: {
guard: (v) => v,
target: guardOK
},
paramMultiple: {
guard: (a, b, c, d, e) => a === true && b === 42 && c === "test" && d(e),
target: guardOK
},
})
}
Util.FSMState {
id: guardOK
objectName: "guardOK"
}
Util.FSMState {
id: guardFail
objectName: "guardFail"
}
}
function test_guard_transitions_data() {
return [
{tag: "success", transition: fsmGuard.success,
active: [guardOK], inactive: [guardInit, guardFail]},
{tag: "fail", transition: fsmGuard.fail,
active: [guardInit], inactive: [guardOK, guardFail]},
{tag: "multiple", transition: fsmGuard.multiple,
active: [guardOK], inactive: [guardInit, guardFail]},
{tag: "multipleDefault", transition: fsmGuard.multipleDefault,
active: [guardOK], inactive: [guardInit, guardFail]},
{tag: "multipleFail", transition: fsmGuard.multipleFail,
active: [guardInit], inactive: [guardOK, guardFail]},
{tag: "param(true)", transition: function() { fsmGuard.paramBool(true) },
active: [guardOK], inactive: [guardInit, guardFail]},
{tag: "param(false)", transition: function() { fsmGuard.paramBool(false) },
active: [guardInit], inactive: [guardOK, guardFail]},
{tag: "param(mutiple)", transition: function() { fsmGuard.paramMultiple(true, 42, "test", (bar) => bar.a === 51, { a: 51}) },
active: [guardOK], inactive: [guardInit, guardFail]},
{tag: "param(mutiple fail)", transition: function() { fsmGuard.paramMultiple(false, -1, "nope", (bar) => bar.a === 51, { a: 2}) },
active: [guardInit], inactive: [guardOK, guardFail]},
]
}
function test_guard_transitions(data) {
data.transition()
tryCompare(data.active[0], "active", true)
check_active_inactive(data.active, data.inactive)
}
Util.FSM {
id: fsmAction
signal simple()
signal redefinedInChild()
signal guardedFalse()
signal guardedTrue()
signal guardedMultiple()
signal signalInAction1()
signal signalInAction2()
signal withParam(var a, var b, var c)
signalMap: ({
simple: simple,
redefinedInChild: redefinedInChild,
guardedFalse: guardedFalse,
guardedTrue: guardedTrue,
guardedMultiple: guardedMultiple,
signalInAction1: signalInAction1,
signalInAction2: signalInAction2,
withParam: withParam,
})
initialState: actionA
Util.FSMState {
id: actionA
objectName: "actionA"
initialState: actionAA
function enter() { recEvent("+A") }
function exit() { recEvent("-A") }
transitions: ({
redefinedInChild: {
//child will transition before us, this won't be triggered
action: () => recEvent("KO"),
target: actionKO
}
})
Util.FSMState {
id: actionAA
objectName: "actionAA"
function enter() { recEvent("+AA") }
function exit() { recEvent("-AA") }
transitions: ({
simple: {
action: () => recEvent("OK"),
target: actionOK
},
guardedFalse: {
guard: () => false,
action: () => recEvent("KO"),
target: actionOK
},
guardedTrue: {
guard: () => true,
action: () => recEvent("OK"),
target: actionOK
},
guardedMultiple: [{
guard: () => false,
action: () => recEvent("KO"),
target: actionKO
}, {
guard: () => true,
action: () => recEvent("OK"),
target: actionOK
},{
//this should not be triggered
guard: () => true,
action: () => recEvent("KO"),
target: actionKO
}],
redefinedInChild: {
action: () => recEvent("OK"),
target: actionOK
},
withParam: {
action: (a,b,c) => {
if (a === 42 && b === "p1" && c.a === 51)
recEvent("OK")
},
target: actionOK
},
signalInAction1: {
action: () => { fsmAction.signalInAction2() },
target: actionTransient
},
signalInAction2: {
//this should not be triggered
action: () => { recEvent("KO") },
target: actionOK
},
})
}
}
Util.FSMState {
id: actionOK
objectName: "actionOK"
function enter() { recEvent("+OK") }
function exit() { recEvent("-OK") }
}
Util.FSMState {
id: actionKO
objectName: "actionKO"
function enter() { recEvent("+KO") }
function exit() { recEvent("-KO") }
}
Util.FSMState {
id: actionTransient
objectName: "actionTransient"
function enter() { recEvent("+T") }
function exit() { recEvent("-T") }
transitions: ({
signalInAction2: actionOK
})
}
}
function test_action_transitions_data() {
return [
{tag: "simple", transition: fsmAction.simple,
events: ["OK", "-AA", "-A", "+OK"], active: [actionOK], inactive: [actionA, actionAA]},
{tag: "guardedFalse", transition: fsmAction.guardedFalse,
events: [], active: [actionA, actionAA], inactive: [actionOK]},
{tag: "guardedTrue", transition: fsmAction.guardedTrue,
events: ["OK", "-AA", "-A", "+OK"], active: [actionOK], inactive: [actionA, actionAA]},
{tag: "guardedMultiple", transition: fsmAction.guardedMultiple,
events: ["OK", "-AA", "-A", "+OK"], active: [actionOK], inactive: [actionA, actionAA]},
{tag: "redefinedInChild", transition: fsmAction.redefinedInChild,
events: ["OK", "-AA", "-A", "+OK"], active: [actionOK], inactive: [actionA, actionAA]},
{tag: "withParam", transition: function() { fsmAction.withParam(42, "p1", {a: 51}) },
events: ["OK", "-AA", "-A", "+OK"], active: [actionOK], inactive: [actionA, actionAA]},
{tag: "signalInAction", transition: fsmAction.signalInAction1,
events: ["-AA", "-A", "+T", "-T", "+OK"], active: [actionOK], inactive: [actionA, actionAA]},
]
}
function test_action_transitions(data) {
data.transition()
tryCompare(data.active[0], "active", true)
check_events(data.events)
check_active_inactive(data.active, data.inactive)
}
//check that the FSM hierarchy may contain other object than FSMState nodes
Util.FSM {
id: fsmMixed
initialState: mixedA
signal atob()
signal btoc()
signalMap: ({
atob: atob,
btoc: btoc,
})
Util.FSMState {
id: mixedA
objectName: "mixedA"
//whatever
Rectangle {}
transitions: ({
atob: mixedB
})
}
Util.FSMState {
id: mixedB
objectName: "mixedB"
Timer {
interval: 20
running: mixedB.active
onTriggered: fsmMixed.btoc()
}
transitions: ({
btoc: mixedC
})
}
Util.FSMState {
id: mixedC
objectName: "mixedC"
}
}
function test_mixed_children() {
fsmMixed.atob()
tryCompare(mixedB, "active", true)
wait(30)
verify(mixedC.active, true)
}
}
/*****************************************************************************
* Copyright (C) 2023 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* ( at your option ) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
import QtQuick 2.12
/**
* @brief a pure QML hierarchical Finite State Machine implementation
*
* FSM {
* signal aSignal()
* signal anotherSignal(a, b, c)
*
* //map signals to a key
* signalMap: ({
* "aSignal": aSignal,
* "anotherSignal": anotherSignal
* })
*
* //define what is the initial sub state
* initialState: firstState
*
* FSMState {
* id: firstState
* //transitions defintions for this state
* transitions: ({
* "aSignal": finalState, //transition to finalState when receiving aSignal
* "anotherSignal": [{
* action: (a,b,c) => {
* //action is executed if transition is taken (anotherSignal is received and
* //guard returns true)
* },
* guard: (a,b,c) => a + b > c, //transition is taken if guard returns true
* target: subStateA //target state
* }, {
* target: subStateB
* }]
* })
* }
* FSMState {
* id: anotherState
*
* initialState: subStateA
* FSMState {
* id: subStateA
* //states may be nested
* }
* FSMState {
* id: subStateB
* }
* }
* FSMState {
* id: finalState
* }
* }
*/
FSMState {
id: fsm
//each signal is associated to a key, when a signal is received,
//transitions of active state for the given key are evaluated
property var signalMap: ({
})
property bool running: true
/**
* @param {FSMState} state state handling the event
* @param {string} event name of the event
* @param {...*} args event arguments
* @param {Object} t transition definition
* @return {boolean} true if the state has handled the event
*/
function _evaluateTransition(state, event, t, ...args) {
if ("guard" in t) {
if (!(t.guard instanceof Function)) {
console.error(`guard property of ${state}::${event} is not a function`)
}
if (!t.guard(...args))
return false
}
if ("action" in t) {
if (!(t.action instanceof Function))
console.error(`action property of ${state}::${event} is not a function`)
t.action(...args)
}
if ("target" in t)
_changeState(t.target)
return true
}
/**
* @param {FSMState} state state handling the event
* @param {string} event name of the event
* @param {...*} args event arguments
* @return {boolean} true if the state has handled the event
*/
function handleSignal(state, event, ...args) {
if (!running)
return false
if (!state)
return false
if (state._state) {
if (handleSignal(state._state, event, ...args))
return true
}
if (!(event in state.transitions)) {
return false
}
const transitions = state.transitions[event]
if (transitions === undefined) {
console.warn(`undefined transition for ${state}::${event}`)
//FIXME: comparing object to QML type with instanceof fails with 5.12
} else if (transitions === null || transitions.toString().startsWith("FSMState")) {
_changeState(transitions)
return true
} else if (Array.isArray(transitions)) {
for (const t of transitions) {
//stop at the first accepted transition
if (_evaluateTransition(state, event, t, ...args))
return true
}
return false
} else {
return _evaluateTransition(state, event, transitions, ...args)
}
}
/**
* @param {FSMState} state
*/
function _exitState(state) {
if (!state)
return
//exit sub states
if (state._state)
_exitState(state._state)
state._state = null
state.active = false
if (state.exit instanceof Function)
state.exit()
}
/**
* @brief mark the state as active, enter handler is evaluated
* @param {FSMState} state
*/
function _activateState(state) {
if (!state)
return
if (!state.active) {
state.active = true
if (state.enter instanceof Function)
state.enter()
}
}
/**
* @brief enter the target state, enter handler are evaluated
* inital sub-states are entered recursively
* @param {FSMState} state
*/
function _enterState(state) {
if (!state)
return
_activateState(state)
if (state.initialState) {
state._state = state.initialState
_enterState(state._state)
}
}
/**
* @param {FSMState} state
* @param {FSMState[]} parentStates
*/
function _resolveStatesHierarchy(state, parentStates) {
if (!state)
return
state._parentStates = parentStates
for (let i in state._children) {
const child = state._children[i]
if (child instanceof FSMState) {
state._subStates.push(child)
}
}
for (const s of state._subStates) {
_resolveStatesHierarchy(s, [...parentStates, state])
}
}
/**
* @param {FSMState} state
*/
function _validateFSM(state) {
if (!state)
return
if (!(state instanceof FSMState)) {
console.warn(`invalid state machine: ${state} is not an FSMState node`)
}
for (const key of Object.keys(state.transitions)) {
if (!Object.keys(fsm.signalMap).includes(key)) {
console.warn(`transition ${key} ${state} match no signal`, Object.keys(fsm.signalMap))
}
}
for (const s of state._subStates) {
_validateFSM(s)
}
}
/**
* @param {FSMState[]} a state list
* @param {FSMState} a root state
* @return {FSMState} the common ancestor
*/
function _findCommonAncestorState(a, b) {
if (!a || !b)
return null
if (!b._parentStates.includes(a))
return null
const node = _findCommonAncestorState(a._state, b)
if (node !== null)
return node
return a
}
/**
* @param {FSMState} state target state
*/
function _changeState(state) {
const ancestor = _findCommonAncestorState(fsm, state)
//exit uncommon states
if (ancestor) {
_exitState(ancestor._state)
}
if (!state) {
return
}
//activate parent state, but do not enter their initialState
let parentState = fsm
for (let i in state._parentStates) {
_activateState(state._parentStates[i])
parentState._state = state._parentStates[i]
parentState = state._parentStates[i]
}
//enter target state, then enter initial sub-states
parentState._state = state
_enterState(state)
}
/**
* reset the FSM to its initial state, exit handlers of the current state are not
* evaluated. enter hander of initial state will be evaluated
*/
function reset() {
function reset_rec(state) {
if (!state)
return
if (state._state) {
reset_rec(state._state)
state._state = null
}
state.active = false
}
reset_rec(fsm)
_changeState(initialState)
}
Component.onCompleted: {
_resolveStatesHierarchy(fsm, [])
_validateFSM(fsm)
for (const signalName of Object.keys(signalMap)) {
signalMap[signalName].connect((...args) => {
//use callLater to ensure transitions are ordered.
//signal are not queued by default, this is an issue
//if an action/enter/exit function raise another signal
Qt.callLater(() => {
handleSignal(fsm, signalName, ...args)
})
})
}
_changeState(fsm)
}
}
/*****************************************************************************
* Copyright (C) 2023 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* ( at your option ) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
import QtQuick 2.12
QtObject {
property bool active: false
///initial sub state
property var initialState: null
/**
* @typedef {Object} TransitionDefinition
* @property {Function} [guard] transition is only evaluated if this function returns true
* @property {Function} [action] action executed on transition
* @property {FSMState} [target] target state, if not defined, FSM will stay in
* current state
*/
/**
* dictionnary containing definition of transitions
* key is the signal name as defined in
* @type {Object<string:null|FSMState|TransitionDefinition|TransitionDefinition[]>}
*/
property var transitions: ({})
default property list<QtObject> _children
//subStates
property QtObject _state: null
property var _parentStates: []
property var _subStates: []
}
......@@ -82,6 +82,8 @@
<file alias="MLContextMenu.qml">util/qml/MLContextMenu.qml</file>
<file alias="ListViewRev15.qml">util/qml/ListViewRev15.qml</file>
<file alias="ListViewRev11.qml">util/qml/ListViewRev11.qml</file>
<file alias="FSM.qml">util/qml/FSM.qml</file>
<file alias="FSMState.qml">util/qml/FSMState.qml</file>
</qresource>
<qresource prefix="/sd">
<file alias="capture-card.svg">pixmaps/sd/capture-card.svg</file>
......