Boilerplates, snippets, use cases and QML notes

Updated 3 months ago

To help beginners kickstart their plugin project, this section gathers boilerplates ,tiny reusable code snippets or parts which are too trivial to be used as an individual plugin, along with several specific use cases and notes that demonstrate how to achieve particular tasks in plugin code.

What goes in here? and why?

This place centralizes clever code bits which would otherwise be left hidden under waves of forum posts. Anyone can edit. Feel free to rearrange, correct or add anything, provide a source if possible. Only share code that is taken from other's plugin after you make sure you have permission. Musicians who wish to learn to create plugins usually visit here first, it'd be counter-productive to fragmentize MuseScore resource on the web, plus sharing here saves you the trouble of learning git commands and signing up for github. Codebase large or functional enough to be used as an individual plugin could be posted as a project on musescore.org without github knowledge. Tips on QML are also put in here and as subpage, notes on API write in community notes on API

Knock knock, who's there?

Wong. Spot a typo? Found codes need fixing? Want to share yours? Please go ahead. Click the ⠇ to the right of the title, choose "Edit".
Not a musescore.org member yet? Register an account, it's free. Start editing right away, you really do not need to report and wait for permission. In fact, paid employee from MuseScore BVBA tends to concentrate on improving the main program and providing forum support, it is up to fellow passionate musicians like yourself to share findings and correct errors in the plugin section. Discussion about this page itself visit this forum thread.
capture_001_29082022_145502.jpg

Marked Default are copied from plugins bundled with MuseScore 3 installation, open them to learn more.

QML notes on WebEngine

QML notes on keyboard and mouse input

use case: Add Fingering Text

use case: Changing an Existing Note's Pitch

use case: Element Explorer

--- BOILERPLATES AND SNIPPETS: ---

QML boilerplates

Contains auto layout templates.

Run this script once. then upon clicking on a element, print all its properties and func on logger

also try the buildin debugger
Object Explorer plugin exhaustively print out element info
see also: use case: Element Explorer

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug20"
    onScoreStateChanged: {
      if (state.selectionChanged && curScore){
        var es=curScore.selection.elements
        for (var i = 0; i < es.length; i++) {
          console.log('\n'+es[i].name)
          for(var p in es[i]){
            console.log(es[i].name+"."+p+" : "+es[i][p])
          }
        }
     }
  }
}

run this script once to sync/bind the time position of the Cursor to first note of current selection

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug2"
  id:pluginscope
  property var c
  onRun: {
    pluginscope.c=curScore.newCursor()
    pluginscope.c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
  }
  onScoreStateChanged: {
    if (state.selectionChanged && curScore) console.log( pluginscope.c.tick )
  }
}

select everything on the score, then filter by element type

Note: selectRange() parameter endTick and endStaff are excluded from selection

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug3"
  onRun: {
    curScore.startCmd()
    curScore.selection.selectRange(0,curScore.lastSegment.tick + 1,0,curScore.nstaves);    //cmd("select-all")
    curScore.endCmd()
    var es=curScore.selection.elements
    for (var i = 0; i < es.length; i++) {
      if(es[i].type==Element.NOTE) console.log(es[i].pitch)
    }
  }
}

move the Cursor to the beginning of score

more info: move cursor to start or end of your current selection ,or tick
see also Default walk.qml

import MuseScore 3.0
import QtQuick 2.9
MuseScore { 
      menuPath: "Plugins.debug5"
      onRun: {
        var c= curScore.newCursor();
        c.voice    = 0;
        c.staffIdx = 0;
        c.rewind(Cursor.SCORE_START);
        console.log(c.tick)    
    }
}

log type enum string of one selected element

import MuseScore 3.0
MuseScore {
  menuPath: "Plugins.debug55"
  property var reverseEnumElement:(function(){ var x={}; for(var i in Element){  if(typeof Element[i]!=='function')  x[Element[i]]=i  }; return x })()
  onRun:{
    console.log(reverseEnumElement[curScore.selection.elements[0].type])
  }
}

log selected note's pitch and next note's pitch

import MuseScore 3.0
import QtQuick 2.9
MuseScore { 
  menuPath: "Plugins.debug4"
  onRun: {
    var e=curScore.selection.elements[0]
    if(e.type==Element.NOTE){
      console.log( "This pitch "+e.pitch )
      var track=e.track
      var seg=e.parent
      while(seg&&seg.type!=Element.SEGMENT){ seg=seg.parent }
      if(seg) var tick=seg.tick
      var c=curScore.newCursor()
      c.track=track  //set track first, setting track will reset tick
      c.rewindToTick(tick)
      var segnext=c.segment.next
      if(segnext
        &&segnext.elementAt(track).type==Element.CHORD  // 1 or more notes
        &&segnext.elementAt(track).notes
      ) console.log("Next pitch "+ segnext.elementAt(track).notes[0].pitch )  //0=first entered note
    }
  }
}

log selected chord's pitches

import MuseScore 3.0
import QtQuick 2.9
MuseScore { 
  menuPath: "Plugins.debug41"
  onRun: {
    var e=curScore.selection.elements[0]
    var track=e.track
    var seg=e.parent
    while(seg&&seg.type!=Element.SEGMENT){ seg=seg.parent }
    if(seg) var tick=seg.tick
    var c=curScore.newCursor()
    c.track=track  //set track first, setting track will reset tick
    c.rewindToTick(tick)
    if(c.segment.elementAt(track).type==Element.CHORD){
      var notesarray=c.segment.elementAt(track).notes
      if(notesarray&&notesarray.length>0){
        for (var i = 0; i < notesarray.length; i++){ 
          console.log('note # '+i+' pitch '+notesarray[i].pitch) 
        }
      }
    }
  } 
}

create new note, assign pitch using addNote( )

  • addNote( ) and addRest( ) advance the cursor whenever possible ,see api reference and notes.
  • They do not return falsy value, using cursor advancement logic as loop conditional statement may leads to infinite loop error.
  • Chord inserted incorrectly when spams multiple measures, see kamilio141414's post
  • see also related snippets below.
import MuseScore 3.0
import QtQuick 2.9
MuseScore { 
  menuPath: "Plugins.debug7"
  onRun:{
    var c=curScore.newCursor()
    c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
    curScore.startCmd()
    c.addNote(60) // c.addNote(60,true) to create double stops or chord, or insert note into one //addNote( ) advances the cursor
    curScore.endCmd()
  }
}

create new chord using add( ) to designate accidental

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug8gpl"
  //
  // create and return a new Note element with given pitch, tpc
  function createNote(pitch, tpc){
    var note = newElement(Element.NOTE);
    note.pitch = pitch;
    note.tpc = tpc;
    note.tpc1 = tpc;
    note.tpc2 = tpc;
    return note;
  }// end CreateNote
// Use cursor.rewindToTick(cur_time) instead
  function setCursorToTime(cursor, time){
    cursor.rewind(0);
    while (cursor.segment) { 
      var current_time = cursor.tick;
      if(current_time>=time) return true
      cursor.next();
    }
    cursor.rewind(0);
    return false;
  }// end setcursor To Time
  //adds chord at current position. chord_notes is an array with pitch of notes.
  function addChord(cursor, chord_notes, duration){
    if(chord_notes.length==0) return -1;
    var cur_time=cursor.tick;
    cursor.setDuration(duration, 1920);
    cursor.addNote(chord_notes[0]); //add 1st note //addNote( ) advances the cursor
    var next_time=cursor.tick;
    setCursorToTime(cursor, cur_time); //rewind to this note
    var note = cursor.element.notes[0];
    note.tpc = chord_notes[4];
    var chord = cursor.element; //get the chord created when 1st note was inserted
    for(var i=1; i<4; i++){
      var note = createNote(chord_notes[i], chord_notes[i+4]);
      note.tpc = chord_notes[i+4];
      chord.add(note); //add notes to the chord //add( ) does not advance the cursor
      chord.notes[i].tpc = chord_notes[i+4];
    }
    setCursorToTime(cursor, next_time);
    return 0;
  }//end AddChord
  onRun: {
    var cursor = curScore.newCursor(), startStaff, endStaff, endTick, fullScore = false;
    cursor.rewind(1);
    if (!cursor.segment) { // no selection
      fullScore = true;
      startStaff = 0; // start with 1st staff
      endStaff = curScore.nstaves - 1; // and end with last
    } else {
      startStaff = cursor.staffIdx;
      cursor.rewind(2);
      if (cursor.tick === 0) { // this happens when the selection includes the last measure of the score.
        endTick = curScore.lastSegment.tick + 1;
      } else {
        endTick = cursor.tick;
      }
      endStaff = cursor.staffIdx;
    }
    cursor.rewind(1); // beginning of selection
    if (fullScore) { // no selection
      cursor.rewind(0); // beginning of score
    }
    cursor.voice = 0;
    cursor.staffIdx = 1;
    // chords[0]: name
    // chords[1]: pitches lo-hi & tpc lo-hi
    // chords[2]: occurences of this voicing
    // chords[3]: instances of this chord name
    var chords=
    [
      ["C7b13",[48,52,56,58,14,18,10,12],1,1], //c,e,ab,bb
      ["C7b13",[52,56,58,62,18,10,12,16],1,1], //e,ab,bb,d
    ];
    for (var c = 0; c < chords.length; c++){
      cursor.staffIdx = 0;
      var harmony = newElement(Element.HARMONY);
      harmony.text = chords[c][0];
      cursor.add(harmony);
      cursor.staffIdx = 1;
      var chord = chords[c][1];
      var cur_time=cursor.tick;
      addChord(cursor, chord, 1920); // 480 OK for quarter note trips; 240 for eight notes etc
    }
  }
}

Change the accidental of currently selected note

Changing note's accidentalType changes pitch and tpc automatically.
Creating a new Element.ACCIDENTAL with accidentalType and then adding it to a note does not always work as expected.
For cosmetic purpose accidental without pitch change, and microtonal symbols, use Element.SYMBOL instead, see dmitrio95's post, also see Tuning systems, microtonal notation system, and playback.
Source: dmitrio95's post and XiaoMigros's post.
For possible accidentals, see the AccidentalType enum

import MuseScore 3.0
import QtQuick 2.9
MuseScore { 
  menuPath: "Plugins.debug71"
  onRun:{
    var note=curScore.selection.elements[0]
    curScore.startCmd()
    note.accidentalType = Accidental.SHARP2 // the 'x' double sharp
    curScore.endCmd()
    curScore.startCmd()
    note.accidental.color = "red"
    curScore.endCmd()
  }
}

update note's pitch

see also the create note snippet above
see also use case: Changing an Existing Note's Pitch
Two scenarios
* in Fretted eg guitar TAB: must set 5 values: .pitch, .string, .fret, .tpc1, .tpc2
* in Fretless eg piano: must set 3 values: .pitch, .tpc1, .tpc2
source notes

import MuseScore 3.0
import QtQuick 2.9
MuseScore { 
  menuPath: "Plugins.debug8"
 //WIP
}

add lyrics at selected element and subsequent non rest elements

import MuseScore 3.0
import QtQuick 2.9
MuseScore { 
  menuPath: "Plugins.debug9"
  onRun: {
    var e=curScore.selection.elements[0]
    var seg=e.parent
    while(seg&&seg.type!=Element.SEGMENT){ seg=seg.parent }
    if(seg) var tick=seg.tick
    var c=curScore.newCursor()
    c.rewindToTick(tick)
    c.track=e.track
    var lyr = newElement(Element.LYRICS)
    lyr.text = "muse"
    lyr.verse = 0
    curScore.startCmd()
    c.add(lyr)
    curScore.endCmd()
 
    var lyricsarray=['score','is','awesome']
    c.next()
    while(c&&c.element&&lyricsarray.length>0){
      if(
        c.element.type==Element.CHORD  // 1 or more notes
        // &&c.element.type!=Element.REST  //redundant
      ){
        var lyr = newElement(Element.LYRICS)
        lyr.text = lyricsarray[0]
        lyr.verse = 0
        curScore.startCmd()
        c.add(lyr)
        curScore.endCmd()
        lyricsarray.shift()
      }
      c.next()
    }
  } 
}

log the shown key signature at selected position

see notes on which methods seem not working
Note: Musescore 3 interpret key signature's shape and flat symbols to provide tonality logic, it does not understand tonality really. The following code returns the key signature shown on screen, it does not always mean the real key in musical sense . Caution when using an open/atonal keysig and/or on transposing instruments; transposing instruments' return value also varies with user's current "Concert Pitch" display status.
key signature enum are not exposed as API yet, use this hardcoded schema, ref:
Num of Sharps = positive number
No symbols = 0
Num of Flats = negative number

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug29" 
  onRun:{
    var  c=curScore.newCursor()
    c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
     console.log(c.keySignature)
  }
}

get to the title, composer etc frame at start of page

Did you find a way get to its existing content text eg read title, composer text string ? src ref
also see metatag snippet which contains score properties set as data field only , which may not be the current visual text

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug12"
  onRun: {
    var c=curScore.newCursor()
    c.rewind(Cursor.SCORE_START)
    curScore.selection.select(c) //tackle weird range select problem
    cmd('prev-element')
    while( curScore.selection.elements[0].type!=Element.VBOX ){
      cmd('prev-element')
    }
    var e=curScore.selection.elements[0]
    console.log(e.name)
  }
}

Alternative to modifying existing text: Delete the existing frame, use the addText function:
curScore.addText("title", "The Park, Op. 44")

log time signature then create new one at selected position

some values won't work, always check actual timesig : right click > measure prop see handbook
See Time Signature Enforcer for a plugin that uses the cmd interface to manipulate the timesigActual of a measure.
see notes for methods currently not working

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug28"
  onRun: {
    var c=curScore.newCursor()
    c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
    //
    var m=c.measure.timesigActual  //replace with timesigNominal for displayed symbol
    console.log("timesigActual : "+m.numerator)
    console.log("timesigActual : "+m.denominator)
    //
    var ts=newElement(Element.TIMESIG)
    ts.timesig=fraction(12,8) //some values won't work, always check timesigActual
    curScore.startCmd()
    c.add(ts)
    curScore.endCmd()
  }
}

log score information entered in File>Properties or new score wizard

'name' of default score properties

import MuseScore 3.0
MuseScore {
  menuPath: "Plugins.debug31"
  onRun:{
    console.log(curScore.metaTag("workTitle")) //https://github.com/fp22june/MuseScoreTag362/blob/master/share/templates/My_First_Score.mscx#L19-L29
  }
}

Add text, symbols, and articulation to selected note

see also TempoChanges Plugin
sym, SymId enum: see Master palatte > symbols or lookup
MS3 fontfamily
lookup codepoints
everything in musescore.qrc
only icon image
source post

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug30"
  pluginType:"dock"  
  onRun: {
    var c=curScore.newCursor()
    c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
    //
    var s = newElement(Element.ARTICULATION); 
    s.symbol = "articAccentAbove" // see SymId enumeration values
    curScore.startCmd()
    c.add(s)
    curScore.endCmd()
    //
    var sym = newElement(Element.SYMBOL);
    sym.symbol = "miscEyeglasses"
    curScore.startCmd()
    c.add(t)
    curScore.endCmd()
    //
    var t = newElement(Element.STAFF_TEXT); 
    t.fontSize= 20
    t.text = "<sym>miscEyeglasses</sym>" 
    curScore.startCmd()
    c.add(t)
    curScore.endCmd()
  }
  Column{
    Text{
      font.family: 'MScore Text' //also  Bravura  Leland  //https://github.com/musescore/MuseScore/tree/master/fonts
      font.pointSize: 20
      text:"\uE4E5" //https://github.com/w3c/smufl/blob/gh-pages/metadata/glyphnames.json
    }
    Image{
      source:"qrc:///data/icons/note-longa.svg" //https://github.com/musescore/MuseScore/blob/3.x/mscore/icons.cpp#L40
    }
  }
}

Save custom data

The following snippet saves a string as tag property in a score, to save object use JSON.stringify() and parse()
Also:
Save data to a time position: add invisible Staff Text, more info study the MuseScore Navigation plugin
Save data across sessions, use Settings { }, study the Daily Log plugin

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
MuseScore { 
  menuPath: "Plugins.debug24"
  pluginType: "dock"
  anchors.fill: parent
  onRun:{
    curScore.setMetaTag('customdataname', 'initemptyvalue')
  }
  Column{
    Button{
      text:"load"
      onClicked: console.log( curScore.metaTag('customdataname') )
    }
    Button{
      text:"save"
      onClicked: curScore.setMetaTag('customdataname', 'customdatavalue')
    }
  }
}

workaround to gate onScoreStateChanged's cpu-heavy code to the last plugin instance

Following limits cpu-heavy, blocking synchronous code to last plugin instance (because every onScoreStateChanged continues to run even after its invocation plugin window closed)
Remember to use unique string in metaTag('PluginLastInstance')
To test, comment out the comparison line then reopen plugin few times

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug43"
  property string instanceStamp:Date.now()
  Component.onCompleted: curScore.setMetaTag('PluginLastInstance', instanceStamp) //save to score, overwritten by latest plugin
  function blockingSynchronousCode(){
    var x=0
    for(var i=0;i<1e7;i++) x+=i //edit 1e7 according to cpu
  }
  onScoreStateChanged:{
    if(curScore.metaTag('PluginLastInstance')==instanceStamp){   //filter out old instances
      blockingSynchronousCode()
    }
  }
}

workaround to relink onScoreStateChanged to current score only

Using the gate method above, if the plugin window stays open, switching to another score will invalidate onScoreStateChanged because instanceStamp doesn't exist or doesn't equal to plugin's stored value. Click button to reapply stamp so that plugin is affected by current score's onScoreStateChanged
Remember to use unique string in metaTag('PluginLastInstance')

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
MuseScore {
  menuPath: "Plugins.debug44"
  pluginType: "dock"
  anchors.fill:parent
  property string instanceStamp:Date.now()
  function maincode(){
    txt.text=instanceStamp+' score called '+Date.now()
  }
  function ownInstance(){
    instanceStamp=Date.now()
    curScore.setMetaTag('PluginLastInstance', instanceStamp)
    maincode()
  }
  Component.onCompleted: ownInstance()
  onScoreStateChanged:{
    if(curScore.metaTag('PluginLastInstance')==instanceStamp){ 
      maincode()
    }
  }
  Column{
    Button{
      text:"relink to current score"
      onClicked:ownInstance()
    }
    Text{
      id: txt
      anchors.fill:parent
    }
  }
}

Listview with unidirectional dataflow

qt ref

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
MuseScore {
  menuPath: "Plugins.debug22"
  pluginType: "dock"
  anchors.fill: parent
  id:pluginscope
  property var db:[
      { nm : "name1" , btn : true }
    , { nm : "name2" , btn : false }
    , { nm : "name3" , btn : false }
    , { nm : "name4" , btn : true }
  ]
  function gen(payload){
    if(payload && payload.deletenm){
      for (var i=0; i<pluginscope.db.length; i++){
        if(pluginscope.db[i].nm==payload.deletenm){
          pluginscope.db[i].removed=true
        }
      }
    }
    if(payload && payload.renewall){
      pluginscope.db=pluginscope.db.map(function _(r){
        return ({ nm: r.nm, btn: r.btn, removed: false})
      })
    }
    md1.clear()
    pluginscope.db.map(function _(r){
      if(!r.removed) md1.append(r)
    })
    console.log("gen")
  }
  Item{
    anchors.fill: parent
    ListModel { id: md1 }    //ref from delegate use getview1.ListView.view.model
    ListView {
      anchors.fill: parent
      id: listview1         //ref from delegate use getview1.ListView.view
      model: md1  
      delegate: Component {
        Row{
          id: getview1
          width: parent.width
          Text{
            clip:true
            text: nm
          }
          Button { 
            text: btn ? "logmynm" : "deleteme"
            onClicked:{ 
              if(btn){ 
                console.log(nm)
              } else {
                gen({ deletenm : nm })
              }
            }
          }
        }
      }
    }
  }
  Button { 
    anchors.bottom: parent.bottom
    text: "renewall"
    onClicked: gen({ renewall : true })
  }
}

QML keyboard handler

more info see this QML notes
Keys enum list
focus:true may not mean want you think

import MuseScore 3.0
import QtQuick 2.9
MuseScore { 
  menuPath: "Plugins.debug10"
  pluginType: "dock"
  Item {
    focus:true
    Keys.onPressed: {
      console.log("event.key: "+event.key)
      if (event.key == Qt.Key_Space) cmd("play")
    }
  }
}

Add components dynamically eg add clickable buttons

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
MuseScore { 
  menuPath: "Plugins.debug50"
  pluginType: "dock"
  anchors.fill: parent
  Column{
    anchors.fill: parent
    id:btnscontainer 
  }
  Component { id: btntemplate 
    Button { 
      width: parent.width
    }
  }
  Component.onCompleted:{
    btntemplate.createObject(btnscontainer,{text:'log1'})
      .clicked.connect(function(){console.log('logged1')})
    btntemplate.createObject(btnscontainer,{text:'log2'})
      .clicked.connect(function(){console.log('logged2')})
  }
}

Add components dynamically with non static var (data binding)

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
MuseScore {
  menuPath: "Plugins.debug54"
  pluginType: "dock"
  anchors.fill: parent
  implicitHeight: 9999
  Column{
    id:maincontainer
    anchors.fill:parent ;    padding:5;
    Row{ id:rowtoftext }
  }
  Component { id: texttemplate 
    Text{ 
      color:'green'
      topPadding:5
      property var fs   // to dynamically overwrite font.pointSize
      font.pointSize: fs||this.font.pointSize
      font.weight:Font.Medium
    }
  }
  Text{ id:normaltext }   //reference
  Component.onCompleted:{
    texttemplate.createObject(rowtoftext,{
       text:'Extend'
      ,rightPadding:10
      ,leftPadding:Qt.binding(function(){return parent.width*.3 }) // use Qt.binding for non-static var , otherwise assigned at createObject( ) invocation
    })
    texttemplate.createObject(rowtoftext,{
       text:'Musescore'
      ,color:'blue'
      ,topPadding:0
      ,fs: normaltext.font.pointSize*1.5
    })
  }
}

Popup a modal window

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Dialogs 1.1
MuseScore { 
  menuPath: "Plugins.debug51"
  pluginType: "dock"
  id: pluginscope
  anchors.fill: parent
  //
  Component { id: msg ; MessageDialog { title: "Confirm"; standardButtons: StandardButton.Ok; onAccepted: console.log("ok") } }
  function confirm(t){ msg.createObject(pluginscope,{ text:t, visible: true}) }
  //
  Button { 
    width: parent.width
    text:"MuseScore"
    onClicked: confirm('Rocks')
  }
}

Button color, mouse over color

more prop like disabled, qmlssed, checked, checkable, focused, highlighted, flat, mirrored, hovered etc
for more mouse control like detect button, QML use MouseArea , see this QML notes

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
MuseScore { 
  menuPath: "Plugins.debug6"
  pluginType: "dock"
  anchors.fill: parent
  Button{ 
    text:"clickme"
    onClicked: onbtn1()
    background: Rectangle {
    color: parent.hovered ? "#aaa": "#ddd"
    } 
  }
  function onbtn1(){
    console.log("clicked")
  }
}

mouse button, mouse down, mouse up

Mouse button enum list
also see this QML notes
for complex mouse handler logic, you may want to use standard js mouseevent as in the WebEngine snippet, instead of QML mouseevent shown here

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2 
MuseScore { 
  menuPath: "Plugins.debug90"
  pluginType: "dock"
  anchors.fill: parent
  Row{
    Rectangle { 
      width:  50;
      height: 50;
      color: "red"
      MouseArea{
        id:ma
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton
        onPressed: {
          console.log('MouseArea.pressedButtons: '+ pressedButtons) //pressedButtons is also available ma.pressedButtons
          console.log('mouse.button: '+mouse.button)                //mouse is mouseEvent exist inside onPressed(){} 
        }
      }
    }
    Rectangle { 
      width:  50;
      height: 50;
      color: "blue"
      MouseArea{
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton
        z:2
        onPressed:{
          mouse.accepted=false
          console.log('pressed')
        }
      }
      MouseArea{
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton
        z:-1
        onReleased:{
          console.log('released')
        }
      }
    }
  }
}

Setup a timeout countdown timer for delayed function

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
MuseScore { 
  menuPath: "Plugins.debug52"
  pluginType: "dock"
  id: pluginscope
  anchors.fill: parent
  //
  property var timeout
  Component { id: setTimeout ; Timer {  } }
  //
  Button { 
    width: parent.width
    text:"Delayed"
    onClicked:{
      console.log('promise')
      setTimeout.createObject(pluginscope,{ interval:1000 ,running:true  })
        .triggered.connect(function(){ console.log('delivered') })
    }
  }
}

Debounce

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
MuseScore { 
  menuPath: "Plugins.debug53"
  pluginType: "dock"
  id: pluginscope
  anchors.fill: parent
  //
  property var timeout
  Component { id: setTimeout ; Timer {  } }
  function debounced(curryt,curryf){
    return function(obj){
      if(timeout) timeout.stop()
      timeout=setTimeout.createObject(pluginscope,{
         interval:curryt
         //,onTriggered:function(){ curryf(obj) }   //?cannot curry?
        ,running:true
      })
      timeout.triggered.connect(function(){ curryf(obj) })
    }
  }
  //
  property int heavycount:0
  function heavy(){console.log('heavy'+(heavycount++))}
  property var heavydebounced:debounced(500,heavy)
  //
  property int easycount:0
  Button { 
    width: parent.width
    text:"Smash"
    onClicked:{
      console.log('easy'+(easycount++))
      heavydebounced()
    }
  }
}

WebEngine = fetch online or local page, or write html, run javascript

Windows only, see QML notes
save the following two snippet in the same directory, name the html debug21.html
also see the other advanced boilerplate below

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtWebEngine 1.5
MuseScore { 
  menuPath: "Plugins.debug21"
  pluginType: "dock"
  anchors.fill: parent
  Column{
    anchors.fill: parent
    Button{
      id:b
      height:30
      text:"clickme"
      onClicked:{
        v2.runJavaScript('approot.style.background="red"')
        v3.loadHtml('<div style="background:green;width:100%;height:100%"></div>')
      }
    }
    WebEngineView {
      width: parent.width
      height: (parent.height-b.height)/3
      url: "https://example.com"
    }
    WebEngineView {
      id:v2
      width: parent.width
      height: (parent.height-b.height)/3
      url: "debug21.html"
    }
    WebEngineView {
      id:v3
      width: parent.width
      height: (parent.height-b.height)/3
    }
  }
}

debug21.html

<div id="approot" style="width:100%;height:100%"></div>

WebEngine + WebChannel = hybrid UI html css js

Windows only, see QML notes
save the following two snippet in the same directory, name the html debug19.html
advice: if large project, avoid direct mutations will help prevent nightmare
also see the other simpler boilerplate above

import MuseScore 3.0
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtWebEngine 1.5
import QtWebChannel 1.0
MuseScore{
  menuPath: "Plugins.debug19"
  pluginType: "dock"
  anchors.fill: parent
  QtObject {
    id: backendobj
    WebChannel.id: "backendid"
    property string backp: "nothing"
    signal post(string payload)
    function backf(payload){
      backconsole.text = "From front f: " + payload +'\n'+ backconsole.text
      return (payload+110)
    }
  }
  Column{
    anchors.top: wv.bottom
    spacing: 6
    Button {
      height: 12
      text: "Cast signal backendobj.post"
      onClicked: { backendobj.post(201319) }
    }
    Button {
      height: 12
      text: "Direct javascript"
      onClicked:{wv.runJavaScript(""
        + "   var t=document.querySelector('textarea').value;    "
        + "   document.querySelector('textarea').value = 'Direct javascript: 394' +'\\n'+ t;   "
      )}
    }
    Button {
      height: 12
      text: "Read backendobj.backp"
      onClicked: { backconsole.text = backendobj.backp +'\n'+ backconsole.text }
    }
    Text {
      id: backconsole
      text: "BackConsole"
      //onTextChanged: backendobj.post("BackConsoleCB")
    }
  }
  WebChannel {
    id: channel
    registeredObjects: [backendobj]
  }
  WebEngineView {
    id: wv
    width: parent.width
    height: parent.height/2
    url: "debug19.html"
    webChannel: channel
    onJavaScriptConsoleMessage: function(w,s,l,i){ console.log('Web console line '+l+' : '+s) } //pipe console
  }
}

debug19.html

<button onclick="frontf()">Front f</button>
<button onclick="backendobjread()">Direct backendobj.back read</button>
<button onclick="backendobjwrite()">Direct backendobj.back write</button>
<textarea id="debug" style="width:100%;height:60%"></textarea>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script>
    var backend
    window.onload = function _(){
        new QWebChannel(qt.webChannelTransport, channel=>{
            backend = channel.objects.backendid;
            backend.post.connect(payload=>{
                debug.value = ("Signal from backendobj.post: "+payload) +'\n'+ debug.value
            })
        })
    }
    // async backend.func and callback
    var frontf = _=>{ 
      backend.backf( 721 , r=>{
        debug.value = ("Back f CB: "+r) +'\n'+ debug.value 
      })
    }
    // direct read/write backend.prop 
    var backendobjread  = _=> debug.value = backend.backp +'\n'+ debug.value
    var backendobjwrite = _=> backend.backp = new Date() 
</script>

loop thru all channel, mute all

see also this note

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  menuPath: "Plugins.debug64"
  function iterCh(cb){
    var parts = curScore.parts
    for (var i = 0; i < parts.length; i++) {
        var instrs = parts[i].instruments
        for (var j = 0; j < instrs.length; j++) {
            var channels = instrs[j].channels
            for (var k = 0; k < channels.length; k++) {
                cb(parts[i],instrs[j],channels[k], i,j,k)
            }
        }
    }
  }
  onRun:{
    iterCh(function _(p,i,c, n,m,k){ 
      c.mute=true
    })
  }
}

log OS name, start another program commandline such as python

see the BandInMuseScore plugin and receiving / passing MuseScore command line parameter.
TIPS: use Score's path, see api and Batch Convert

import MuseScore 3.0
import QtQuick 2.9
MuseScore {
  anchors.fill: parent
  menuPath: "Plugins.debug42"
  QProcess { id: proc }
  onRun:{
    console.log('os: '+Qt.platform.os)
    var c
    switch (Qt.platform.os){
      case "windows":
        c = 'cmd /c calc'
        break;
      default:
        c = '/bin/sh -c calc'
    }
    proc.start(c)
    var val=proc.waitForFinished(5000)
    // DEBUG
    try {
      var out=proc.readAllStandardOutput()
      console.log("-- Command output: "+out)
      if (val) {
        console.log('terminated correctly.')
      } else {
        console.log('failure')
      }
    } catch (err) {
      console.log("--" + err.message);
    }
 }
}

Identify if the score is displayed in "Concert pitch" or "Score pitch"

This can be achieved by comparing the .tpc1 and .tpc2 value of a note of a transposing instrument.

Rational:

The note.tpc1 is the Concert pitch representation of a note.
The note.tpc2 is the Score pitch representation of a note.
The note.tpc is the representation of the note in the current mode.

For a transposing instrument tpc1 and tpc2 are different. So they can be compared to tpc and the current mode deduced.

For a non transposing instrument tpc1 and tpc2 are equal. So the current mode cannot be deduced.

import QtQuick 2.0
import MuseScore 3.0
 
MuseScore {
      menuPath: "Plugins.pluginName"
      description: "Description goes here"
      version: "1.0"
      onRun: {
            // assuming a note of a transpoing instrument is selected
            var note=curScore.selection.elements[0];
            console.log((note.tpc===note.tpc1)?"Concert Pitch":"Score pitch");
            }
      }

Identify an instrument transposition

This can be achieved by comparing the .tpc and .tpc1 value of a note of a transposing instrument.

Rational

The note.tpc1 is the Concert pitch representation of a note.
The note.tpc2 is the Score pitch representation of a note.

import QtQuick 2.0
import MuseScore 3.0
 
MuseScore {
      menuPath: "Plugins.pluginName"
      description: "Description goes here"
      version: "1.0"
      onRun: {
 
            var note=curScore.selection.elements[0];  // assuming a note is selected
            console.log("Instrument's transposition: %1 semtitones".arg(deltaTpcToPitch(note.tpc1,note.tpc2)));
            }
 
      function deltaTpcToPitch(tpc1, tpc2) {
            var d = ((tpc2 - tpc1) * 5) % 12;
                //if (d < 0) d += 12;
            return d;
            }
      }

setup keyboard shortcut in MuseScore from QML

Better use Plugin Manager's "Define Shortcut" instead, because running the following code more than once will bug out that key until restart.
Source post.
Workaround forum post by matt28.

import MuseScore 3.0
import QtQuick 2.6
import QtQuick.Window 2.2
MuseScore {
    id: plugin
    pluginType: "dock"
    readonly property var window: Window.window
    Item { 
      id: someItem 
      focus:true
    }
    Shortcut {
        sequence: "Ctrl+D"
        context: Qt.ApplicationShortcut
        onActivated: {
            plugin.window.requestActivate();
            someItem.forceActiveFocus();
        }
    }
}

External links

Element Analyser plugin which aims to provide a reusable library

License and credits for this page only

License info are for legal purpose of this webpage only.
These are copied from default plugins and MuseScore files. Some contributor info are missing, please add credits wherever due.
abc_import.qml
Based on ABC Import by Nicolas Froment (lasconic)
Copyright (2013) Stephane Groleau (vgstef)
colornotes.qml
Copyright (C) 2012 Werner Schweer
Copyright (C) 2013-2017 Nicolas Froment, Joachim Schmitz
Copyright (C) 2014 Jörn Eichler
notenames-interactive.qml
Copyright (C) 2012 Werner Schweer
Copyright (C) 2013 - 2019 Joachim Schmitz
Copyright (C) 2014 Jörn Eichler
Copyright (C) 2020 MuseScore BVBA
notenames.qml
Copyright (C) 2012 Werner Schweer
Copyright (C) 2013 - 2020 Joachim Schmitz
Copyright (C) 2014 Jörn Eichler
Copyright (C) 2020 Johan Temmerman
Copyright (C) 2023 Laurent van Roy (parkingb)
Copyright (C) 2012 Werner Schweer and others
walk.qml
jeetee
Copyright (C) 2012-2017 Werner Schweer
MuseScore
Music Composition & Notation
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2as published by the Free Software Foundation. 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., 675 Mass Ave, Cambridge, MA 02139, USA.
These are for the WebEngine WebChannel boilerplate:
msfp
decovar Declaration of VAR
source
GPLv3