//=========================================================
//  MusE
//  Linux Music Editor
//  scoreedit.cpp
//  (C) Copyright 2011 Florian Jung (flo93@users.sourceforge.net)
//
//  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; 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 <QLayout>
//#include <QSizeGrip>
#include <QLabel>
#include <QPushButton>
#include <QToolTip>
#include <QMenu>
#include <QMenuBar>
#include <QApplication>
#include <QClipboard>
#include <QDir>
#include <QKeySequence>
#include <QMimeData>
#include <QScrollArea>
#include <QSettings>
#include <QImage>
#include <QInputDialog>
#include <QMessageBox>
#include <QTimer>
#include <QPainterPath>

#include <stdio.h>
#include "muse_math.h"

#include <iostream>
#include <sstream>
using std::cout, std::endl, std::cerr, std::ostringstream;

#include "app.h"
#include "xml.h"
#include "sig.h"
#include "scoreedit.h"
#include "ttoolbar.h"
//#include "tb1.h"
#include "globals.h"
#include "gconfig.h"
#include "icons.h"
#include "audio.h"
#include "functions.h"
#include "helper.h"
//#include "cmd.h"
#include "song.h"
#include "shortcuts.h"
#include "menutitleitem.h"

// Forwards from header:
//#include <QCloseEvent>
//#include <QResizeEvent>
#include <QKeyEvent>
#include <QPainter>
#include <QPixmap>
#include <QScrollBar>
#include <QComboBox>
//#include <QAction>
#include <QActionGroup>
//#include <QGridLayout>
#include <QToolButton>
#include "part.h"
#include "mtscale_flo.h"
#include "steprec.h"
#include "spinbox.h"
#include "tools.h"

using MusEGlobal::debugMsg;
using MusEGlobal::heavyDebugMsg;
using MusECore::UndoOp;
using MusECore::Undo;

namespace MusEGui {

string IntToStr(int i);
QString IntToQStr(int i);


#define SPLIT_NOTE 60



//PIXELS_PER_NOTEPOS must be greater or equal to 3*NOTE_XLEN + 2*NOTE_SHIFT
//because if tick 0 is at x=0: the notes can be shifted by NOTE_SHIFT.
//additionally, they can be moved by NOTE_XLEN (collision avoiding)
//then, they have their own width, which is NOTE_XLEN/2 into the x>0-area
//the same thing applies to the x<0-area

//  OOO
//   |
//   ^actual calculated x, without shifting or moving
//  ^ and
//    ^   : moved note (by XLEN)
// additionally, a shift is possible
// total_width = shift + move + note_xlen + move + shift, where move==note_xlen
// total_width = 2*shift + 3*note_xlen
// if total_width is greater than px_per_notepos, there will be collisions!





//do NOT put parentheses around this! and always right-multiply it,
//that is: foo * PAGESTEP, never PAGESTEP * foo!
#define PAGESTEP 3/4


#define SCROLL_MARGIN 10
#define SCROLL_SPEED 5
//SCROLL_SPEED is in (scroll_pixels per second) per mouse-move-pixel
#define SCROLL_SPEED_MAX 500
//SCROLL_SPEED_MAX is in scroll_pixels_per_second



#define AKKOLADE_WIDTH 8
#define AKKOLADE_LEFTMARGIN 0
#define AKKOLADE_RIGHTMARGIN 2
#define STAFF_DISTANCE (10*YLEN)
#define GRANDSTAFF_DISTANCE (8*YLEN)
#define NOTE_YDIST 20
//NOTE_YDIST is the number of pixels which are between two notes
//which exceed their staves' y-boundaries, so that these boundaries
//must be expanded.



#define MAX_QUANT_POWER 5



QString create_random_string(int len=8)
{
    string result;

    for (int i=0;i<len;i++)
        result+=char((rand() % 26) + 'A');

    return QString(result.c_str());
}






QPixmap *pix_whole, *pix_half, *pix_quarter; // arrays [NUM_MYCOLORS]
QPixmap *pix_dot, *pix_b, *pix_sharp, *pix_noacc; // arrays [NUM_MYCOLORS]
QPixmap *pix_r1, *pix_r2, *pix_r4, *pix_r8, *pix_r16, *pix_r32; // pointers
QPixmap *pix_flag_up, *pix_flag_down; // arrays [4]
QPixmap *pix_num; // array [10]
QPixmap *pix_clef_violin, *pix_clef_bass; //pointers
bool pixmaps_initalized=false;
QColor* mycolors; // array [NUM_MYCOLORS]


static const int scoreTools = PointerTool | PencilTool | RubberTool;

set<QString> ScoreEdit::names;

int ScoreCanvas::_quant_power2_init=3;
int ScoreCanvas::_pixels_per_whole_init=300;
int ScoreCanvas::note_velo_init=64;
int ScoreCanvas::note_velo_off_init=64;
int ScoreCanvas::new_len_init=0;
ScoreCanvas::coloring_mode_t ScoreCanvas::coloring_mode_init=COLOR_MODE_BLACK;
bool ScoreCanvas::preamble_contains_timesig_init=true;
bool ScoreCanvas::preamble_contains_keysig_init=true;


//---------------------------------------------------------
//   ScoreEdit
//---------------------------------------------------------

ScoreEdit::ScoreEdit(QWidget* parent, const char* name, unsigned initPos)
   : TopWin(TopWin::SCORE, parent, name)
{
    //setAttribute(Qt::WA_DeleteOnClose);
    setFocusPolicy(Qt::NoFocus);

    mainw    = new QWidget(this);

    mainGrid = new QGridLayout();
    mainw->setLayout(mainGrid);

    mainGrid->setContentsMargins(0, 0, 0, 0);
    mainGrid->setSpacing(0);
    setCentralWidget(mainw);


    apply_velo=false;


    score_canvas=new ScoreCanvas(this, mainw);
    xscroll = new QScrollBar(Qt::Horizontal, mainw);
    yscroll = new QScrollBar(Qt::Vertical, mainw);
    time_bar = new MusEGui::MTScaleFlo(score_canvas, mainw);
    // When closing/(re)loading, we must wait for this to delete to avoid crashes
    //  due to still active external connections like heartBeatTimer.
    MusEGlobal::muse->addPendingObjectDestruction(time_bar);

    connect(xscroll, SIGNAL(valueChanged(int)), score_canvas,   SLOT(x_scroll_event(int)));
    connect(score_canvas, SIGNAL(xscroll_changed(int)), xscroll,   SLOT(setValue(int)));

    connect(yscroll, SIGNAL(valueChanged(int)), score_canvas,   SLOT(y_scroll_event(int)));
    connect(score_canvas, SIGNAL(yscroll_changed(int)), yscroll,   SLOT(setValue(int)));

    connect(score_canvas, SIGNAL(canvas_width_changed(int)), SLOT(canvas_width_changed(int)));
    connect(score_canvas, SIGNAL(viewport_width_changed(int)), SLOT(viewport_width_changed(int)));
    connect(score_canvas, SIGNAL(canvas_height_changed(int)), SLOT(canvas_height_changed(int)));
    connect(score_canvas, SIGNAL(viewport_height_changed(int)), SLOT(viewport_height_changed(int)));

    connect(MusEGlobal::song, SIGNAL(songChanged(MusECore::SongChangedStruct_t)), score_canvas, SLOT(song_changed(MusECore::SongChangedStruct_t)));

    connect(xscroll, SIGNAL(valueChanged(int)), time_bar, SLOT(set_xpos(int)));
    connect(score_canvas, SIGNAL(pos_add_changed()), time_bar, SLOT(pos_add_changed()));
    connect(score_canvas, SIGNAL(preamble_width_changed(int)), time_bar, SLOT(set_xoffset(int)));

    mainGrid->addWidget(time_bar, 0,0);
    mainGrid->addWidget(score_canvas, 1,0);
    mainGrid->addWidget(xscroll,2,0);
    mainGrid->addWidget(yscroll,1,1);

    xscroll->setMinimum(0);
    yscroll->setMinimum(0);
    xscroll->setValue(0);
    yscroll->setValue(0);


    // Toolbars ---------------------------------------------------------

      // NOTICE: Please ensure that any tool bar object names here match the names assigned
      //          to identical or similar toolbars in class MusE or other TopWin classes.
      //         This allows MusE::setCurrentMenuSharingTopwin() to do some magic
      //          to retain the original toolbar layout. If it finds an existing
      //          toolbar with the same object name, it /replaces/ it using insertToolBar(),
      //          instead of /appending/ with addToolBar().

    addToolBarBreak();

    edit_tools = new MusEGui::EditToolBar(this, scoreTools);
    addToolBar(edit_tools);
    edit_tools->set(MusEGui::PointerTool);
    score_canvas->set_tool(MusEGui::PointerTool);
    connect(edit_tools, SIGNAL(toolChanged(int)), score_canvas,   SLOT(set_tool(int)));
    _configChangedEditToolsMetaConn = connect(MusEGlobal::muse, &MusE::configChanged, edit_tools, &EditToolBar::configChanged);

    QToolBar* steprec_tools=addToolBar(tr("Step recording tools"));
    steprec_tools->setObjectName("Score tools");
    srec  = new QToolButton();
    srec->setToolTip(tr("Step record"));
    srec->setIcon(*steprecSVGIcon);
    srec->setCheckable(true);
    srec->setFocusPolicy(Qt::NoFocus);
    steprec_tools->addWidget(srec);
    connect(srec, SIGNAL(toggled(bool)), score_canvas, SLOT(set_steprec(bool)));

    QToolBar* quant_toolbar = addToolBar(tr("Quantisation settings"));
    quant_toolbar->setObjectName("Score quantisation toolbar");
    quant_toolbar->addWidget(new QLabel(tr("Quantisation"), quant_toolbar));
    quant_combobox = new QComboBox(this);
    quant_combobox->addItem("2");  // if you add or remove items from
    quant_combobox->addItem("4");  // here, also change all code regarding
    quant_combobox->addItem("8");  // _quant_power2 and _quant_power2_init
    quant_combobox->addItem("16"); // and MAX_QUANT_POWER (must be log2(largest_value))
    quant_combobox->addItem("32");
    quant_combobox->setFocusPolicy(Qt::TabFocus);
    quant_combobox->setCurrentIndex(score_canvas->quant_power2()-1);
    // the above is intendedly executed BEFORE connecting. otherwise this would
    // destroy pixels_per_whole_init!
    //connect(quant_combobox, SIGNAL(currentIndexChanged(int)), score_canvas, SLOT(set_quant(int)));
    connect(quant_combobox, SIGNAL(activated(int)), SLOT(quant_combobox_changed(int)));      // Tim
    quant_toolbar->addWidget(quant_combobox);


    QLabel* label = new QLabel(tr("Pixels per whole"));
    label->setIndent(3);
    quant_toolbar->addWidget(label);
    px_per_whole_spinbox = new SpinBox(this);
    px_per_whole_spinbox->setFocusPolicy(Qt::StrongFocus);
    px_per_whole_spinbox->setRange(10, 1200);
    px_per_whole_spinbox->setSingleStep(50);
    connect(px_per_whole_spinbox, SIGNAL(valueChanged(int)), score_canvas, SLOT(set_pixels_per_whole(int)));
    connect(score_canvas, SIGNAL(pixels_per_whole_changed(int)), px_per_whole_spinbox, SLOT(setValue(int)));
    connect(px_per_whole_spinbox, SIGNAL(returnPressed()), SLOT(focusCanvas()));
    connect(px_per_whole_spinbox, SIGNAL(escapePressed()), SLOT(focusCanvas()));
    quant_toolbar->addWidget(px_per_whole_spinbox);
    px_per_whole_spinbox->setValue(ScoreCanvas::_pixels_per_whole_init);

    addToolBarBreak();

    QToolBar* note_settings_toolbar = addToolBar(tr("Note settings"));
    //don't change that name, or you will lose toolbar settings
    note_settings_toolbar->setObjectName("New note settings");
    note_settings_toolbar->addWidget(new QLabel(tr("Note length"), note_settings_toolbar));
    len_actions=new QActionGroup(this);
    n1_action = note_settings_toolbar->addAction("1");
    n2_action = note_settings_toolbar->addAction("2");
    n4_action = note_settings_toolbar->addAction("4");
    n8_action = note_settings_toolbar->addAction("8");
    n16_action = note_settings_toolbar->addAction("16");
    n32_action = note_settings_toolbar->addAction("32");
    nlast_action = note_settings_toolbar->addAction(tr("last"));
    connect(n1_action,    &QAction::triggered, [this]() { menu_command(CMD_NOTELEN_1); } );
    connect(n2_action,    &QAction::triggered, [this]() { menu_command(CMD_NOTELEN_2); } );
    connect(n4_action,    &QAction::triggered, [this]() { menu_command(CMD_NOTELEN_4); } );
    connect(n8_action,    &QAction::triggered, [this]() { menu_command(CMD_NOTELEN_8); } );
    connect(n16_action,   &QAction::triggered, [this]() { menu_command(CMD_NOTELEN_16); } );
    connect(n32_action,   &QAction::triggered, [this]() { menu_command(CMD_NOTELEN_32); } );
    connect(nlast_action, &QAction::triggered, [this]() { menu_command(CMD_NOTELEN_LAST); } );
    n1_action->setCheckable(true);
    n2_action->setCheckable(true);
    n4_action->setCheckable(true);
    n8_action->setCheckable(true);
    n16_action->setCheckable(true);
    n32_action->setCheckable(true);
    nlast_action->setCheckable(true);
    len_actions->addAction(n1_action);
    len_actions->addAction(n2_action);
    len_actions->addAction(n4_action);
    len_actions->addAction(n8_action);
    len_actions->addAction(n16_action);
    len_actions->addAction(n32_action);
    len_actions->addAction(nlast_action);

    switch (ScoreCanvas::new_len_init)
    {
        case 0: nlast_action->setChecked(true); menu_command(CMD_NOTELEN_LAST); break;
        case 1: n1_action->setChecked(true); menu_command(CMD_NOTELEN_1); break;
        case 2: n2_action->setChecked(true); menu_command(CMD_NOTELEN_2); break;
        case 4: n4_action->setChecked(true); menu_command(CMD_NOTELEN_4); break;
        case 8: n8_action->setChecked(true); menu_command(CMD_NOTELEN_8); break;
        case 16: n16_action->setChecked(true); menu_command(CMD_NOTELEN_16); break;
        case 32: n32_action->setChecked(true); menu_command(CMD_NOTELEN_32); break;
        default:
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN. newLen is invalid in ScoreEdit::ScoreEdit.\n" <<
                            "       (newLen="<<ScoreCanvas::new_len_init<<"; the only valid values are 0,1,2,4,8,16 and 32)\n" <<
                            "       however, don't worry, this is no major problem, using 0 instead" << endl;
            nlast_action->setChecked(true);
            menu_command(CMD_NOTELEN_LAST);
    }

    label = new QLabel(tr("Velocity"));
    label->setIndent(3);
    note_settings_toolbar->addWidget(label);
    velo_spinbox = new SpinBox(this);
    velo_spinbox->setRange(0, 127);
    velo_spinbox->setSingleStep(1);
    velo_spinbox->setToolTip(tr("Apply to selected notes, or new notes if none is selected"));
    //we do not use the valueChanged signal, because that would generate
    //many many undos when using the spin buttons.
    connect(velo_spinbox, SIGNAL(editingFinished()), SLOT(velo_box_changed()));
    connect(this,SIGNAL(velo_changed(int)), score_canvas, SLOT(set_velo(int)));
    connect(velo_spinbox, SIGNAL(returnPressed()), SLOT(focusCanvas()));
    connect(velo_spinbox, SIGNAL(escapePressed()), SLOT(focusCanvas()));
    note_settings_toolbar->addWidget(velo_spinbox);
    velo_spinbox->setValue(ScoreCanvas::note_velo_init);

    label = new QLabel(tr("Off-Velocity"));
    label->setIndent(3);
    note_settings_toolbar->addWidget(label);
    velo_off_spinbox = new SpinBox(this);
    velo_off_spinbox->setRange(0, 127);
    velo_off_spinbox->setSingleStep(1);
    velo_off_spinbox->setToolTip(tr("Apply to selected notes, or new notes if none is selected"));
    //we do not use the valueChanged signal, because that would generate
    //many many undos when using the spin buttons.
    connect(velo_off_spinbox, SIGNAL(editingFinished()), SLOT(velo_off_box_changed()));
    connect(this,SIGNAL(velo_off_changed(int)), score_canvas, SLOT(set_velo_off(int)));
    connect(velo_off_spinbox, SIGNAL(returnPressed()), SLOT(focusCanvas()));
    connect(velo_off_spinbox, SIGNAL(escapePressed()), SLOT(focusCanvas()));
    note_settings_toolbar->addWidget(velo_off_spinbox);
    velo_off_spinbox->setValue(ScoreCanvas::note_velo_off_init);


    QMenu* edit_menu = menuBar()->addMenu(tr("&Edit"));

        edit_menu->addActions(MusEGlobal::undoRedo->actions());
        edit_menu->addSeparator();

        cut_action = edit_menu->addAction(*cutSVGIcon, tr("C&ut"));
        connect(cut_action, &QAction::triggered, [this]() { menu_command(CMD_CUT); } );

        copy_action = edit_menu->addAction(*copySVGIcon, tr("&Copy"));
        connect(copy_action, &QAction::triggered, [this]() { menu_command(CMD_COPY); } );

        copy_range_action = edit_menu->addAction(*copyRangeSVGIcon, tr("Copy Events in Range"));
        connect(copy_range_action, &QAction::triggered, [this]() { menu_command(CMD_COPY_RANGE); } );

        paste_action = edit_menu->addAction(*pasteSVGIcon, tr("&Paste"));
        connect(paste_action, &QAction::triggered, [this]() { menu_command(CMD_PASTE); } );

        paste_dialog_action = edit_menu->addAction(*pasteDialogSVGIcon, tr("Paste (With Dialog)..."));
        connect(paste_dialog_action, &QAction::triggered, [this]() { menu_command(CMD_PASTE_DIALOG); } );

        edit_menu->addSeparator();

        del_action = edit_menu->addAction(*deleteSVGIcon, tr("Delete &Events"));
        connect(del_action, &QAction::triggered, [this]() { menu_command(CMD_DEL); } );

        edit_menu->addSeparator();

        QMenu* select_menu = edit_menu->addMenu(tr("&Select"));

            select_all_action = select_menu->addAction(*selectAllSVGIcon, tr("Select &All"));
            connect(select_all_action, &QAction::triggered, [this]() { menu_command(CMD_SELECT_ALL); } );

            select_none_action = select_menu->addAction(*deselectAllSVGIcon, tr("&Deselect All"));
            connect(select_none_action, &QAction::triggered, [this]() { menu_command(CMD_SELECT_NONE); } );

            select_invert_action = select_menu->addAction(*selectInvertSVGIcon, tr("Invert &Selection"));
            connect(select_invert_action, &QAction::triggered, [this]() { menu_command(CMD_SELECT_INVERT); } );

            select_menu->addSeparator();

            select_iloop_action = select_menu->addAction(*selectInsideLoopSVGIcon, tr("&Inside Loop"));
            connect(select_iloop_action, &QAction::triggered, [this]() { menu_command(CMD_SELECT_ILOOP); } );

            select_oloop_action = select_menu->addAction(*selectOutsideLoopSVGIcon, tr("&Outside Loop"));
            connect(select_oloop_action, &QAction::triggered, [this]() { menu_command(CMD_SELECT_OLOOP); } );


    QMenu* functions_menu = menuBar()->addMenu(tr("Fu&nctions"));

        func_quantize_action = functions_menu->addAction(*quantizeSVGIcon, tr("&Quantize..."));
        func_velocity_action = functions_menu->addAction(tr("Change Note &Velocity..."));
        func_cresc_action = functions_menu->addAction(tr("Crescendo/Decrescendo..."));
        func_move_action = functions_menu->addAction(tr("Move Notes..."));
        func_del_overlaps_action = functions_menu->addAction(tr("Delete Overlaps..."));
        func_erase_action = functions_menu->addAction(tr("Erase Events..."));

        functions_menu->addSeparator();

        func_notelen_action = functions_menu->addAction(tr("Change Note &Length..."));
        func_fixed_len_action = functions_menu->addAction(tr("Set Fixed Length..."));
        func_transpose_action = functions_menu->addAction(tr("Transpose..."));
        func_legato_action = functions_menu->addAction(tr("Legato..."));

        connect(func_quantize_action,     &QAction::triggered, [this]() { menu_command(CMD_QUANTIZE); } );
        connect(func_notelen_action,      &QAction::triggered, [this]() { menu_command(CMD_NOTELEN); } );
        connect(func_velocity_action,     &QAction::triggered, [this]() { menu_command(CMD_VELOCITY); } );
        connect(func_cresc_action,        &QAction::triggered, [this]() { menu_command(CMD_CRESCENDO); } );
        connect(func_transpose_action,    &QAction::triggered, [this]() { menu_command(CMD_TRANSPOSE); } );
        connect(func_erase_action,        &QAction::triggered, [this]() { menu_command(CMD_ERASE); } );
        connect(func_move_action,         &QAction::triggered, [this]() { menu_command(CMD_MOVE); } );
        connect(func_fixed_len_action,    &QAction::triggered, [this]() { menu_command(CMD_FIXED_LEN); } );
        connect(func_del_overlaps_action, &QAction::triggered, [this]() { menu_command(CMD_DELETE_OVERLAPS); } );
        connect(func_legato_action,       &QAction::triggered, [this]() { menu_command(CMD_LEGATO); } );

    QMenu* settings_menu = menuBar()->addMenu(tr("&Display"));
    settings_menu->menuAction()->setStatusTip(tr("Display menu: Display options specific to current editor."));

    settings_menu->addAction(subwinAction);
//    settings_menu->addAction(shareAction);
    settings_menu->addAction(fullscreenAction);

    settings_menu->addSeparator();

    color_menu = settings_menu->addMenu(tr("Note Head &Colors"));
            color_actions = new QActionGroup(this);
            color_actions->setObjectName("CheckmarkOnly");
            color_black_action = color_menu->addAction(tr("&Default"));
            color_velo_action =  color_menu->addAction(tr("&Velocity"));
            color_part_action =  color_menu->addAction(tr("&Part"));
            color_black_action->setCheckable(true);
            color_velo_action->setCheckable(true);
            color_part_action->setCheckable(true);
            color_actions->addAction(color_black_action);
            color_actions->addAction(color_velo_action);
            color_actions->addAction(color_part_action);
            connect(color_black_action, &QAction::triggered, [this]() { menu_command(CMD_COLOR_BLACK); } );
            connect(color_velo_action,  &QAction::triggered, [this]() { menu_command(CMD_COLOR_VELO); } );
            connect(color_part_action,  &QAction::triggered, [this]() { menu_command(CMD_COLOR_PART); } );

            switch (ScoreCanvas::coloring_mode_init)
            {
                case 0: color_black_action->setChecked(true); menu_command(CMD_COLOR_BLACK); break;
                case 1: color_velo_action->setChecked(true); menu_command(CMD_COLOR_VELO); break;
                case 2: color_part_action->setChecked(true); menu_command(CMD_COLOR_PART); break;
                default:
                    cerr << "ERROR: THIS SHOULD NEVER HAPPEN. noteColor is invalid in ScoreEdit::ScoreEdit.\n" <<
                                    "       (noteColor="<<ScoreCanvas::coloring_mode_init<<"; the only valid values are 0,1 and 2)\n" <<
                                    "       however, don't worry, this is no major problem, using 0 instead" << endl;
                    color_black_action->setChecked(true);
                    menu_command(CMD_COLOR_BLACK);
            }

        QMenu* preamble_menu = settings_menu->addMenu(tr("Set Up &Preamble"));
        preamble_menu->setObjectName("CheckmarkOnly");

            preamble_keysig_action = preamble_menu->addAction(tr("Display &Key Signature"));
            preamble_timesig_action =  preamble_menu->addAction(tr("Display &Time Signature"));
            connect(preamble_keysig_action, SIGNAL(toggled(bool)), score_canvas, SLOT(preamble_keysig_slot(bool)));
            connect(preamble_timesig_action, SIGNAL(toggled(bool)), score_canvas, SLOT(preamble_timesig_slot(bool)));

            preamble_keysig_action->setCheckable(true);
            preamble_timesig_action->setCheckable(true);

            preamble_keysig_action->setChecked(ScoreCanvas::preamble_contains_keysig_init);
            preamble_timesig_action->setChecked(ScoreCanvas::preamble_contains_timesig_init);

        QAction* set_name_action = settings_menu->addAction(*dummySVGIcon, tr("Set Score &Name..."));
        connect(set_name_action, &QAction::triggered, [this]() { menu_command(CMD_SET_NAME); } );

    config_changed();  // set configuration values, initialize shortcuts
    connect(MusEGlobal::muse, SIGNAL(configChanged()), SLOT(config_changed()));

    QClipboard* cb = QApplication::clipboard();
    connect(cb, SIGNAL(dataChanged()), SLOT(clipboard_changed()));

    clipboard_changed();
    selection_changed();

    connect(MusEGlobal::song, SIGNAL(songChanged(MusECore::SongChangedStruct_t)), SLOT(song_changed(MusECore::SongChangedStruct_t)));
    connect(MusEGlobal::song, SIGNAL(newPartsCreated(const std::map< const MusECore::Part*, std::set<const MusECore::Part*> >&)), score_canvas, SLOT(add_new_parts(const std::map< const MusECore::Part*, std::set<const MusECore::Part*> >&)));

    score_canvas->fully_recalculate();
    score_canvas->goto_tick(initPos,true);
    score_canvas->setFocus();

    if (name!=nullptr)
        set_name(name, false, true);
    else
        init_name();


    apply_velo=true;

    initTopwinState();
    finalizeInit();
}

void ScoreEdit::config_changed()
{
    if(score_canvas)
      score_canvas->config_changed();
    
    init_shortcuts();
}

void ScoreEdit::init_shortcuts()
{
    cut_action->setShortcut(shortcuts[SHRT_CUT].key);
    copy_action->setShortcut(shortcuts[SHRT_COPY].key);
    copy_range_action->setShortcut(shortcuts[SHRT_COPY_RANGE].key);
    paste_action->setShortcut(shortcuts[SHRT_PASTE].key);
    paste_dialog_action->setShortcut(shortcuts[SHRT_PASTE_DIALOG].key);
    del_action->setShortcut(shortcuts[SHRT_DELETE].key);

    select_all_action->setShortcut(shortcuts[SHRT_SELECT_ALL].key);
    select_none_action->setShortcut(shortcuts[SHRT_SELECT_NONE].key);
    select_invert_action->setShortcut(shortcuts[SHRT_SELECT_INVERT].key);
    select_iloop_action->setShortcut(shortcuts[SHRT_SELECT_ILOOP].key);
    select_oloop_action->setShortcut(shortcuts[SHRT_SELECT_OLOOP].key);

//    color_menu->menuAction()->setShortcut(shortcuts[SHRT_EVENT_COLOR].key);

    func_quantize_action->setShortcut(shortcuts[SHRT_QUANTIZE].key);
    func_notelen_action->setShortcut(shortcuts[SHRT_MODIFY_GATE_TIME].key);
    func_velocity_action->setShortcut(shortcuts[SHRT_MODIFY_VELOCITY].key);
    func_transpose_action->setShortcut(shortcuts[SHRT_TRANSPOSE].key);
    func_erase_action->setShortcut(shortcuts[SHRT_ERASE_EVENT].key);
    func_move_action->setShortcut(shortcuts[SHRT_NOTE_SHIFT].key);
    func_fixed_len_action->setShortcut(shortcuts[SHRT_FIXED_LEN].key);
    func_del_overlaps_action->setShortcut(shortcuts[SHRT_DELETE_OVERLAPS].key);
}


void ScoreEdit::add_parts(MusECore::PartList* pl, bool all_in_one)
{
    score_canvas->add_staves(pl, all_in_one);
}

//---------------------------------------------------------
//   itemsAreSelected
//---------------------------------------------------------

bool ScoreEdit::itemsAreSelected() const
{
  if(score_canvas)
    return score_canvas->itemsAreSelected();
  return false;
}


void ScoreEdit::setEditTool(int tool)
{
    edit_tools->set(tool);
}


//---------------------------------------------------------
//   tagItems
//---------------------------------------------------------

void ScoreEdit::tagItems(MusECore::TagEventList* tag_list, const MusECore::EventTagOptionsStruct& options) const
{
  if(score_canvas)
    score_canvas->tagItems(tag_list, options);
}

void ScoreEdit::init_name()
{
    int no=1;
    QString temp;

    while (true)
    {
        temp="Score "+IntToQStr(no);
        if (set_name(temp, false, false))
            break;
        else
            no++;
    }
}

bool ScoreEdit::set_name(QString newname, bool emit_signal, bool emergency_name)
{
    if (names.find(newname)==names.end())
    {
        names.erase(name);
        names.insert(newname);

        name=newname;

        isMdiWin() ? setWindowTitle(name) : setWindowTitle("MusE: " + name);

        if (emit_signal)
            emit name_changed();

        return true;
    }
    else
    {
        if (emergency_name)
        {
            while (set_name(create_random_string(), emit_signal, false) == false) ;
            return true;
        }
        else
            return false;
    }
}

//---------------------------------------------------------
//   ~ScoreEdit
//---------------------------------------------------------

ScoreEdit::~ScoreEdit()
{
    disconnect(_configChangedEditToolsMetaConn);
    names.erase(name);
}

void ScoreEdit::focusCanvas()
{
    if(MusEGlobal::config.smartFocus)
    {
      score_canvas->setFocus();
      score_canvas->activateWindow();
    }
}

void ScoreEdit::velo_box_changed()
{
    emit velo_changed(velo_spinbox->value());
}

void ScoreEdit::velo_off_box_changed()
{
    emit velo_off_changed(velo_off_spinbox->value());
}

void ScoreEdit::quant_combobox_changed(int idx)
{
    score_canvas->set_quant(idx);
    focusCanvas();
}

void ScoreEdit::song_changed(MusECore::SongChangedStruct_t flags)
{
    if(_isDeleting)  // Ignore while while deleting to prevent crash.
        return;

    if (flags & (SC_SELECTION | SC_EVENT_MODIFIED | SC_EVENT_REMOVED))
    {
        map<const MusECore::Event*,
          const MusECore::Part*> selection=get_events(score_canvas->get_all_parts(),1, MusECore::AllEventsRelevant);
        if (!selection.empty()) {
            int velo=-1;
            int velo_off=-1;
            for (map<const MusECore::Event*, const MusECore::Part*>::iterator it=selection.begin(); it!=selection.end(); it++)
                if (it->first->type()==MusECore::Note)
                {
                    if (velo==-1) velo=it->first->velo();
                    else if ((velo>=0) && (velo!=it->first->velo())) velo=-2;

                    if (velo_off==-1) velo_off=it->first->veloOff();
                    else if ((velo_off>=0) && (velo_off!=it->first->veloOff())) velo_off=-2;
                }

            if (velo>=0) velo_spinbox->setValue(velo);
            if (velo_off>=0) velo_off_spinbox->setValue(velo_off);
        }

        selection_changed();
    }
}

void ScoreEdit::canvas_width_changed(int width)
{
    xscroll->setMaximum(width);
}
void ScoreEdit::viewport_width_changed(int width)
{
    xscroll->setPageStep(width * PAGESTEP);
}

void ScoreEdit::canvas_height_changed(int height)
{
    int val=height - score_canvas->viewport_height();
    if (val<=0) val=0;

    yscroll->setMaximum(val);

    if (val==0)
        yscroll->hide();
    else
        yscroll->show();
}
void ScoreEdit::viewport_height_changed(int height)
{
    int val=score_canvas->canvas_height() - height;
    // FINDMICHJETZT canvas_height() is uninitialized!
    if (val<0) val=0;
    yscroll->setPageStep(height * PAGESTEP);
    yscroll->setMaximum(val);

    if (val==0)
        yscroll->hide();
    else
        yscroll->show();
}

void ScoreEdit::closeEvent(QCloseEvent* e)
{
    _isDeleting = true;  // Set flag so certain signals like songChanged, which may cause crash during delete, can be ignored.
    names.erase(name);

    storeSettings();

    emit isDeleting(static_cast<TopWin*>(this));
    e->accept();
}

void ScoreEdit::storeSettings() {

    QSettings settings;
    //settings.setValue("ScoreEdit/geometry", saveGeometry());
    settings.setValue("ScoreEdit/windowState", saveState());
}

void ScoreEdit::menu_command(int cmd)
{
    MusECore::TagEventList tag_list;
    
    const FunctionDialogElements_t fn_element_dflt =
      FunctionAllEventsButton |
      FunctionSelectedEventsButton |
      FunctionLoopedButton |
      FunctionSelectedLoopedButton;
      // | FunctionAllPartsButton |
      // FunctionSelectedPartsButton
      
    switch (cmd)
    {
        case CMD_SET_NAME:
        {
            bool ok;
            QString newname = QInputDialog::getText(this, tr("Set Score Name"),
                                                tr("Enter the new score title"), QLineEdit::Normal,
                                                name, &ok);
            if (ok)
            {
                if (!set_name(newname))
                    QMessageBox::warning(this, tr("Error"), tr("Changing score title failed:\nthe selected title is not unique"));
            }
        }
        break;

        case CMD_SELECT_ALL: select_all(score_canvas->get_all_parts()); break;
        case CMD_SELECT_NONE: select_none(score_canvas->get_all_parts()); break;
        case CMD_SELECT_INVERT: select_invert(score_canvas->get_all_parts()); break;
        case CMD_SELECT_ILOOP: select_in_loop(score_canvas->get_all_parts()); break;
        case CMD_SELECT_OLOOP: select_not_in_loop(score_canvas->get_all_parts()); break;

        case CMD_CUT:
              tagItems(&tag_list, MusECore::EventTagOptionsStruct(MusECore::TagSelected | MusECore::TagAllParts));
              MusECore::cut_items(&tag_list);
            break;
            
        case CMD_COPY:
              tagItems(&tag_list, MusECore::EventTagOptionsStruct(MusECore::TagSelected | MusECore::TagAllParts));
              MusECore::copy_items(&tag_list);
              break;
        case CMD_COPY_RANGE:
              //tagItems(!itemsAreSelected(), false, true, MusEGlobal::song->lPos(), MusEGlobal::song->rPos());
              // For now, unlike the other editors, just tag all parts.
              tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      !itemsAreSelected(), true, true, MusEGlobal::song->lPos(), MusEGlobal::song->rPos()));
              MusECore::copy_items(&tag_list);
              break;
        case CMD_PASTE:
            menu_command(CMD_SELECT_NONE);
            MusECore::paste_items(score_canvas->get_all_parts(), 3072, MusECore::FunctionOptionsStruct(
                                  MusECore::FunctionEraseItemsDefault | MusECore::FunctionPasteNeverNewPart));
            break;
        case CMD_PASTE_DIALOG:
            menu_command(CMD_SELECT_NONE);
            MusECore::paste_items(score_canvas->get_all_parts(), score_canvas->get_selected_part());
            break;
        case CMD_QUANTIZE:
        {
          FunctionDialogReturnQuantize ret =
            quantize_items_dialog(FunctionDialogMode(fn_element_dflt));
          if(ret._valid)
          {
            tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
            MusECore::quantize_items(&tag_list, ret._raster_index,
                                    ret._quant_len,
                                    ret._strength,
                                    ret._swing,
                                    ret._threshold);
          }
        break;
        }

        case CMD_VELOCITY:
              {
              FunctionDialogReturnVelocity ret =
                velocity_items_dialog(FunctionDialogMode(fn_element_dflt));
              if(ret._valid)
              {
                tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
                MusECore::modify_velocity_items(&tag_list, ret._rateVal, ret._offsetVal);
              }
              break;
              }
        case CMD_CRESCENDO:
              {
              FunctionDialogReturnCrescendo ret =
                crescendo_items_dialog(FunctionDialogMode(
                  FunctionLoopedButton |
                  FunctionSelectedLoopedButton
                  // | FunctionAllPartsButton | 
                  // FunctionSelectedPartsButton
                ));
              if(ret._valid)
              {
                tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
                MusECore::crescendo_items(&tag_list, ret._start_val, ret._end_val, ret._absolute);
              }
              break;
              }
        case CMD_NOTELEN:
              {
              FunctionDialogReturnGateTime ret =
                gatetime_items_dialog(FunctionDialogMode(fn_element_dflt));
              if(ret._valid)
              {
                tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
                MusECore::modify_notelen_items(&tag_list, ret._rateVal, ret._offsetVal);
              }
              break;
              }
        case CMD_TRANSPOSE:
              {
              FunctionDialogReturnTranspose ret =
                transpose_items_dialog(FunctionDialogMode(fn_element_dflt));
              if(ret._valid)
              {
                tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
                MusECore::transpose_items(&tag_list, ret._amount);
              }
              break;
              }
        case CMD_ERASE:
            {
              FunctionDialogReturnErase ret =
                erase_items_dialog(FunctionDialogMode(fn_element_dflt));
              if(ret._valid)
              {
                tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
                MusECore::erase_items(&tag_list, ret._veloThreshold, ret._veloThresUsed, ret._lenThreshold, ret._lenThresUsed);
              }
            }
            break;
        
        case CMD_DEL:
              tagItems(&tag_list, MusECore::EventTagOptionsStruct(MusECore::TagSelected | MusECore::TagAllParts));
              MusECore::erase_items(&tag_list);
            break;

        case CMD_MOVE:
              {
              FunctionDialogReturnMove ret =
                move_items_dialog(FunctionDialogMode(fn_element_dflt));
              if(ret._valid)
              {
                tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
                MusECore::move_items(&tag_list, ret._amount);
              }
              break;
              }
        case CMD_FIXED_LEN:
              {
              FunctionDialogReturnSetLen ret =
                setlen_items_dialog(FunctionDialogMode(fn_element_dflt));
              if(ret._valid)
              {
                tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
                MusECore::set_notelen_items(&tag_list, ret._len);
              }
              break;
              }
        case CMD_DELETE_OVERLAPS:
              {
              FunctionDialogReturnDelOverlaps ret =
                deloverlaps_items_dialog(FunctionDialogMode(fn_element_dflt));
              if(ret._valid)
              {
                tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
                MusECore::delete_overlaps_items(&tag_list);
              }
              break;
              }
        case CMD_LEGATO:
              {
              FunctionDialogReturnLegato ret =
                legato_items_dialog(FunctionDialogMode(fn_element_dflt));
              if(ret._valid)
              {
                tagItems(&tag_list, MusECore::EventTagOptionsStruct::fromOptions(
                      ret._allEvents, ret._allParts, ret._range, ret._pos0, ret._pos1));
                MusECore::legato_items(&tag_list, ret._min_len, !ret._allow_shortening);
              }
              break;
              }

        default:
            score_canvas->menu_command(cmd);
    }
}

void ScoreEdit::clipboard_changed()
{
    paste_action->setEnabled(QApplication::clipboard()->mimeData()->hasFormat(QString("text/x-muse-groupedeventlists")));
    paste_dialog_action->setEnabled(QApplication::clipboard()->mimeData()->hasFormat(QString("text/x-muse-groupedeventlists")));
}

void ScoreEdit::selection_changed()
{
    bool flag = !get_events(score_canvas->get_all_parts(),1).empty();
    cut_action->setEnabled(flag);
    copy_action->setEnabled(flag);
    del_action->setEnabled(flag);
}


//duplicated from songfile.cpp's MusE::readPart(); the only differences:
//"none" is supported and tag_name is settable
MusECore::Part* read_part(MusECore::Xml& xml, QString tag_name="part")
{
    MusECore::Part* part = nullptr;

    for (;;)
    {
        MusECore::Xml::Token token = xml.parse();
        const QString& tag = xml.s1();

        switch (token)
        {
            case MusECore::Xml::Error:
            case MusECore::Xml::End:
                return part;

            case MusECore::Xml::Text:
                {
                    int trackIdx, partIdx;
                    if (tag=="none")
                        part=nullptr;
                    else
                    {
                        sscanf(tag.toLatin1().constData(), "%d:%d", &trackIdx, &partIdx);
                        if (debugMsg) cout << "read_part: trackIdx="<<trackIdx<<", partIdx="<<partIdx;
                        MusECore::Track* track = MusEGlobal::song->tracks()->index(trackIdx);
                        if (track)
                            part = track->parts()->find(partIdx);
                        if (debugMsg) cout << ", track="<<track<<", part="<<part<<endl;
                    }
                }
                break;

            case MusECore::Xml::TagStart:
                xml.unknown("read_part");
                break;

            case MusECore::Xml::TagEnd:
                if (tag == tag_name)
                    return part;

            default:
                break;
        }
    }
}



void staff_t::read_status(MusECore::Xml& xml)
{
    for (;;)
    {
        MusECore::Xml::Token token = xml.parse();
        if (token == MusECore::Xml::Error || token == MusECore::Xml::End)
            break;
        const QString& tag = xml.s1();
        switch (token)
        {
            case MusECore::Xml::TagStart:
                if (tag == "type")
                    type = staff_type_t(xml.parseInt());
                else if (tag == "clef")
                    clef = clef_t(xml.parseInt());
                else if (tag == "part")
                {
                    MusECore::Part* part=read_part(xml);
                    if (part)
                        parts.insert(part);
                    else
                        cerr << "ERROR: THIS SHOULD NEVER HAPPEN: part is NULL while reading from xml" << endl;
                }
                else
                    xml.unknown("staff");
                break;

            case MusECore::Xml::TagEnd:
                if (tag == "staff")
                    goto staff_readstatus_end;

            default:
                break;
        }
    }

    staff_readstatus_end:
    update_part_indices();
}


void staff_t::write_status(int level, MusECore::Xml& xml) const
{
    xml.tag(level++, "staff");
    xml.intTag(level, "type", type);
    xml.intTag(level, "clef", clef);
    for (set<const MusECore::Part*>::iterator part=parts.begin(); part!=parts.end(); part++)
    {
        MusECore::Track* track = (*part)->track();
        int trkIdx   = MusEGlobal::song->tracks()->index(track);
        int partIdx  = track->parts()->index(*part);

        if((trkIdx == -1) || (partIdx == -1))
            cerr << "ERROR: staff_t::write_status: trkIdx:"<<trkIdx<<", partIdx:"<<partIdx<<endl;

        xml.put(level, "<part>%d:%d</part>", trkIdx, partIdx);
    }
    xml.tag(level, "/staff");
}

//---------------------------------------------------------
//   writeStatus
//---------------------------------------------------------

void ScoreEdit::writeStatus(int level, MusECore::Xml& xml) const
{
    xml.tag(level++, "scoreedit");
    TopWin::writeStatus(level, xml);

    xml.strTag(level, "name", name);
    xml.intTag(level, "tool", edit_tools->curTool());
    xml.intTag(level, "steprec", srec->isChecked());
    xml.intTag(level, "quantPower", score_canvas->quant_power2());
    xml.intTag(level, "pxPerWhole", score_canvas->pixels_per_whole());
    xml.intTag(level, "newNoteVelo", velo_spinbox->value());
    xml.intTag(level, "newNoteVeloOff", velo_off_spinbox->value());
    xml.intTag(level, "lastLen", score_canvas->get_last_len());

    int len=0;
    if (n1_action->isChecked())
        len=1;
    else if (n2_action->isChecked())
        len=2;
    else if (n4_action->isChecked())
        len=4;
    else if (n8_action->isChecked())
        len=8;
    else if (n16_action->isChecked())
        len=16;
    else if (n32_action->isChecked())
        len=32;
    else if (nlast_action->isChecked())
        len=0; // means "last"

    xml.intTag(level, "newLen", len);

    int color=0;
    if (color_black_action->isChecked())
        color=0;
    else if (color_velo_action->isChecked())
        color=1;
    else if (color_part_action->isChecked())
        color=2;

    xml.intTag(level, "noteColor", color);

    xml.intTag(level, "xscroll", xscroll->value());
    xml.intTag(level, "yscroll", yscroll->value());
    xml.intTag(level, "preambleContainsKeysig", preamble_keysig_action->isChecked());
    xml.intTag(level, "preambleContainsTimesig", preamble_timesig_action->isChecked());

    const MusECore::Part* selected_part=score_canvas->get_selected_part();
    if (selected_part==nullptr)
    {
        xml.put(level, "<selectedPart>none</selectedPart>");
    }
    else
    {
        MusECore::Track* track = selected_part->track();
        int trkIdx   = MusEGlobal::song->tracks()->index(track);
        int partIdx  = track->parts()->index(selected_part);

        if((trkIdx == -1) || (partIdx == -1))
            cerr << "ERROR: ScoreEdit::write_status: trkIdx:"<<trkIdx<<", partIdx:"<<partIdx<<endl;

        xml.put(level, "<selectedPart>%d:%d</selectedPart>", trkIdx, partIdx);
    }


    score_canvas->write_staves(level,xml);

    xml.tag(level, "/scoreedit");
}

void ScoreCanvas::write_staves(int level, MusECore::Xml& xml) const
{
    for (list<staff_t>::const_iterator staff=staves.begin(); staff!=staves.end(); staff++)
        staff->write_status(level, xml);
}


//---------------------------------------------------------
//   readStatus
//---------------------------------------------------------

void ScoreEdit::readStatus(MusECore::Xml& xml)
{
    // never "return;" inside that function.
    // instead, goto end_of_scoreedit_read_status;
    // (there is a command which must be executed!)

    bool apply_velo_temp=apply_velo;
    apply_velo=false;

    for (;;)
    {
        MusECore::Xml::Token token = xml.parse();
        if (token == MusECore::Xml::Error || token == MusECore::Xml::End)
            break;

        const QString& tag = xml.s1();
        switch (token)
        {
            case MusECore::Xml::TagStart:
                if (tag == "name")
                    set_name(xml.parse1());
                else if (tag == "tool")
                    edit_tools->set(xml.parseInt());
                else if (tag == "steprec")
                    srec->setChecked(xml.parseInt());
                else if (tag == "quantPower")
                    quant_combobox->setCurrentIndex(xml.parseInt()-1);
                else if (tag == "pxPerWhole")
                    px_per_whole_spinbox->setValue(xml.parseInt());
                else if (tag == "newNoteVelo")
                    velo_spinbox->setValue(xml.parseInt());
                else if (tag == "newNoteVeloOff")
                    velo_off_spinbox->setValue(xml.parseInt());
                else if (tag == "lastLen")
                    score_canvas->set_last_len(xml.parseInt());
                else if (tag == "newLen")
                {
                    int val=xml.parseInt();
                    switch (val)
                    {
                        case 0: nlast_action->setChecked(true); break;
                        case 1: n1_action->setChecked(true); break;
                        case 2: n2_action->setChecked(true); break;
                        case 4: n4_action->setChecked(true); break;
                        case 8: n8_action->setChecked(true); break;
                        case 16: n16_action->setChecked(true); break;
                        case 32: n32_action->setChecked(true); break;
                        default:
                            cerr << "ERROR: THIS SHOULD NEVER HAPPEN. newLen is invalid in ScoreEdit::readStatus.\n" <<
                                    "       (newLen="<<val<<"; the only valid values are 0,1,2,4,8,16 and 32)\n" <<
                                    "       however, don't worry, this is no major problem, using 0 instead" << endl;
                            nlast_action->setChecked(true);
                    }
                }
                else if (tag == "noteColor")
                {
                    int val=xml.parseInt();
                    switch (val)
                    {
                        case 0: color_black_action->setChecked(true); break;
                        case 1: color_velo_action->setChecked(true); break;
                        case 2: color_part_action->setChecked(true); break;
                        default:
                            cerr << "ERROR: THIS SHOULD NEVER HAPPEN. noteColor is invalid in ScoreEdit::readStatus.\n" <<
                                    "       (noteColor="<<val<<"; the only valid values are 0,1 and 2)\n" <<
                                    "       however, don't worry, this is no major problem, using 0 instead" << endl;
                            color_black_action->setChecked(true);
                    }
                }
                else if (tag == "xscroll")
                    xscroll->setValue(xml.parseInt());
                else if (tag == "yscroll")
                    yscroll->setValue(xml.parseInt());
                else if (tag == "preambleContainsKeysig")
                    preamble_keysig_action->setChecked(xml.parseInt());
                else if (tag == "preambleContainsTimesig")
                    preamble_timesig_action->setChecked(xml.parseInt());
                else if (tag == "topwin")
                    TopWin::readStatus(xml);
                else if (tag == "selectedPart")
                    score_canvas->set_selected_part(read_part(xml, "selectedPart"));
                else if (tag == "staff")
                {
                    staff_t staff(score_canvas);
                    staff.read_status(xml);
                    score_canvas->push_back_staff(staff);
                }
                else
                    xml.unknown("ScoreEdit");
                break;

            case MusECore::Xml::TagEnd:
                if (tag == "scoreedit")
                    goto end_of_scoreedit_read_status;

            default:
                break;
        }
    }

end_of_scoreedit_read_status:

    apply_velo=apply_velo_temp;
}

void ScoreEdit::read_configuration(MusECore::Xml& xml)
{
    for (;;)
    {
        MusECore::Xml::Token token = xml.parse();
        if (token == MusECore::Xml::Error || token == MusECore::Xml::End)
            break;

        const QString& tag = xml.s1();
        switch (token)
        {
            case MusECore::Xml::TagStart:
                if (tag=="quantPowerInit")
                    ScoreCanvas::_quant_power2_init=xml.parseInt();
                else if (tag=="pxPerWholeInit")
                    ScoreCanvas::_pixels_per_whole_init=xml.parseInt();
                else if (tag=="newNoteVeloInit")
                    ScoreCanvas::note_velo_init=xml.parseInt();
                else if (tag=="newNoteVeloOffInit")
                    ScoreCanvas::note_velo_off_init=xml.parseInt();
                else if (tag=="newLenInit")
                    ScoreCanvas::new_len_init=xml.parseInt();
                else if (tag=="noteColorInit")
                    ScoreCanvas::coloring_mode_init=(ScoreCanvas::coloring_mode_t)xml.parseInt();
                else if (tag=="preambleContainsKeysig")
                    ScoreCanvas::preamble_contains_keysig_init=xml.parseInt();
                else if (tag=="preambleContainsTimesig")
                    ScoreCanvas::preamble_contains_timesig_init=xml.parseInt();
                else if (tag == "topwin")
                    TopWin::readConfiguration(SCORE, xml);
                else
                    xml.unknown("ScoreEdit");
                break;

            case MusECore::Xml::TagEnd:
                if (tag == "scoreedit")
                    return;

            default:
                break;
        }
    }
}


void ScoreEdit::write_configuration(int level, MusECore::Xml& xml)
{
    xml.tag(level++, "scoreedit");

    xml.intTag(level, "quantPowerInit", ScoreCanvas::_quant_power2_init);
    xml.intTag(level, "pxPerWholeInit", ScoreCanvas::_pixels_per_whole_init);
    xml.intTag(level, "newNoteVeloInit", ScoreCanvas::note_velo_init);
    xml.intTag(level, "newNoteVeloOffInit", ScoreCanvas::note_velo_off_init);
    xml.intTag(level, "newLenInit", ScoreCanvas::new_len_init);
    xml.intTag(level, "noteColorInit", ScoreCanvas::coloring_mode_init);
    xml.intTag(level, "preambleContainsKeysig", ScoreCanvas::preamble_contains_keysig_init);
    xml.intTag(level, "preambleContainsTimesig", ScoreCanvas::preamble_contains_timesig_init);

    TopWin::writeConfiguration(SCORE, level, xml);

    xml.etag(level, "scoreedit");
}



//---------------------------------------------------------
//   itemsAreSelected
//---------------------------------------------------------

bool ScoreCanvas::itemsAreSelected() const
{
  FloItem fi;
  for(list<staff_t>::const_iterator is = staves.begin(); is != staves.end(); ++is)
  {
    const staff_t& staff = *is;
    const ScoreItemList& sil = staff.itemlist;
    for(ScoreItemList::const_iterator isi = sil.begin(); isi != sil.end(); ++isi)
    {
      const set<FloItem, floComp>& si = isi->second;
      for(set<FloItem, floComp>::const_iterator efi = si.begin(); efi != si.end(); ++efi)
      {
        fi = *efi;
        if(fi.source_event && (*fi.source_event).selected())
          return true;
      }
    }
  }   
  return false;
}

//---------------------------------------------------------
//   tagItems
//---------------------------------------------------------

void ScoreCanvas::tagItems(MusECore::TagEventList* tag_list, const MusECore::EventTagOptionsStruct& options) const
{
  const bool tagSelected = options._flags & MusECore::TagSelected;
  //const bool tagMoving   = options._flags & MusECore::TagMoving;
  const bool tagAllItems = options._flags & MusECore::TagAllItems;
  const bool tagAllParts = options._flags & MusECore::TagAllParts;
  const bool range       = options._flags & MusECore::TagRange;
  const MusECore::Pos& p0 = options._p0;
  const MusECore::Pos& p1 = options._p1;
  
  // Here we can choose between using tagAllParts for parts or staves.
  // If we choose parts, it's kind of difficult for the user to tell which part is selected.
  // Whereas, if we choose staves, it's much easier to see that a staff is currently selected.
  // TODO: Possibly add 'staves' radio buttons to the function dialogs instead.
  const bool use_staves_for_tag_all_parts = true;
  // For now we don't use the tagAllParts flag.
  const bool use_tag_all_parts = false;
  
  const MusECore::Part* part;
  MusECore::Pos pos;
  FloItem fi;
  for(list<staff_t>::const_iterator is = staves.begin(); is != staves.end(); ++is)
  {
    const staff_t& staff = *is;

    if(use_tag_all_parts && use_staves_for_tag_all_parts && !tagAllParts && (&staff != &(*current_staff)))
      continue;
    
    const ScoreItemList& sil = staff.itemlist;
    for(ScoreItemList::const_iterator isi = sil.begin(); isi != sil.end(); ++isi)
    {
      const set<FloItem, floComp>& si = isi->second;
      for(set<FloItem, floComp>::const_iterator efi = si.begin(); efi != si.end(); ++efi)
      {
        fi = *efi;
        
        // Make sure the event and part pointers are valid.
        if(!fi.source_event || !fi.source_part)
          continue;
        
        const MusECore::Event& e = *fi.source_event;
        
        part = fi.source_part;
        
        if(use_tag_all_parts && !use_staves_for_tag_all_parts && !tagAllParts && part != selected_part)
          continue;
        
        if(range)
        {
          // Don't forget to add the part's position.
          pos = e.pos() + *part;
          // p1 should be considered outside (one past) the very last position in the range.
          if(pos < p0 || pos >= p1)
            continue;
        }
        
        if(tagAllItems
           || (tagSelected && e.selected())
           //|| (tagMoving && e.isMoving()) //TODO
          )
        {
          tag_list->add(part, e);
        }
      }
    }
  }
}


void ScoreCanvas::add_staves(MusECore::PartList* pl, bool all_in_one)
{
    if (!pl->empty())
    {
        staff_t staff(this);

        if (all_in_one)
        {
            clefTypes clef=((MusECore::MidiTrack*)pl->begin()->second->track())->getClef();

            staff.parts.clear();
            for (MusECore::ciPart part_it=pl->begin(); part_it!=pl->end(); part_it++)
            {
                if (((MusECore::MidiTrack*)part_it->second->track())->getClef() != clef)
                    clef=grandStaff;

                staff.parts.insert(part_it->second);
            }
            staff.cleanup_parts();
            staff.update_part_indices();

            switch (clef)
            {
                case trebleClef:
                    staff.type=NORMAL;
                    staff.clef=VIOLIN;
                    staves.push_back(staff);
                    break;

                case bassClef:
                    staff.type=NORMAL;
                    staff.clef=BASS;
                    staves.push_back(staff);
                    break;

                case grandStaff:
                    staff.type=GRAND_TOP;
                    staff.clef=VIOLIN;
                    staves.push_back(staff);

                    staff.type=GRAND_BOTTOM;
                    staff.clef=BASS;
                    staves.push_back(staff);
                    break;
            }
        }
        else
        {
            set<MusECore::Track*> tracks;
            for (MusECore::ciPart it=pl->begin(); it!=pl->end(); it++)
                tracks.insert(it->second->track());

            MusECore::TrackList* tracklist = MusEGlobal::song->tracks();
            // this loop is used for inserting track-staves in the
            // correct order. simply iterating through tracks's contents
            // would sort after the pointer values, i.e. randomly
            for (MusECore::ciTrack track_it=tracklist->begin(); track_it!=tracklist->end(); track_it++)
                if (tracks.find(*track_it)!=tracks.end())
                {
                    staff.parts.clear();
                    for (MusECore::ciPart part_it=pl->begin(); part_it!=pl->end(); part_it++)
                        if (part_it->second->track() == *track_it)
                            staff.parts.insert(part_it->second);
                    staff.cleanup_parts();
                    staff.update_part_indices();

                    switch (((MusECore::MidiTrack*)(*track_it))->getClef())
                    {
                        case trebleClef:
                            staff.type=NORMAL;
                            staff.clef=VIOLIN;
                            staves.push_back(staff);
                            break;

                        case bassClef:
                            staff.type=NORMAL;
                            staff.clef=BASS;
                            staves.push_back(staff);
                            break;

                        case grandStaff:
                            staff.type=GRAND_TOP;
                            staff.clef=VIOLIN;
                            staves.push_back(staff);

                            staff.type=GRAND_BOTTOM;
                            staff.clef=BASS;
                            staves.push_back(staff);
                            break;
                    }
                }
        }

        cleanup_staves();
        fully_recalculate();
        recalc_staff_pos();
    }
}


ScoreCanvas::ScoreCanvas(ScoreEdit* pr, QWidget* parent_widget) : View(parent_widget, 1, 1)
{
    parent      = pr;
    setFocusPolicy(Qt::StrongFocus);
    setBg(MusEGlobal::config.midiCanvasBg);

    setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);

    srec=false;
    for (int i=0;i<128;i++) held_notes[i]=false;
    steprec=new MusECore::StepRec(held_notes);
    connect(MusEGlobal::song, SIGNAL(midiNote(int, int)), SLOT(midi_note(int,int)));

    x_pos=0;
    x_left=0;
    y_pos=0;
    have_lasso=false;
    inserting=false;
    dragging=false;
    drag_cursor_changed=false;
    mouse_erases_notes=false;
    mouse_inserts_notes=true;
    undo_started=false;

    selected_part=nullptr;
    dragged_event_part=nullptr;

    last_len=384;
    new_len=-1; // will be initialized with new_len_init by ScoreEdit::ScoreEdit();

    _quant_power2=_quant_power2_init; // ScoreEdit relies on this to be done!
    _pixels_per_whole = _pixels_per_whole_init;

    note_velo=note_velo_init;
    note_velo_off=note_velo_off_init;

    dragging_staff=false;


    coloring_mode=coloring_mode_init;
    preamble_contains_keysig=preamble_contains_keysig_init;
    preamble_contains_timesig=preamble_contains_timesig_init;


    x_scroll_speed=0;
    x_scroll_pos=0;
    y_scroll_speed=0;
    y_scroll_pos=0;
    connect (MusEGlobal::heartBeatTimer, SIGNAL(timeout()), SLOT(heartbeat_timer_event()));

    connect(MusEGlobal::song, SIGNAL(posChanged(int, unsigned, bool)), SLOT(pos_changed(int,unsigned,bool)));
    connect(MusEGlobal::song, SIGNAL(playChanged(bool)), SLOT(play_changed(bool)));

    staff_menu=new QMenu(this);

    staffmode_treble_action = staff_menu->addAction(tr("Treble"));
    connect(staffmode_treble_action, SIGNAL(triggered()), SLOT(staffmode_treble_slot()));

    staffmode_bass_action = staff_menu->addAction(tr("Bass"));
    connect(staffmode_bass_action, SIGNAL(triggered()), SLOT(staffmode_bass_slot()));

    staffmode_both_action = staff_menu->addAction(tr("Grand Staff"));
    connect(staffmode_both_action, SIGNAL(triggered()), SLOT(staffmode_both_slot()));

    remove_staff_action = staff_menu->addAction(tr("Remove staff"));
    connect(remove_staff_action, SIGNAL(triggered()), SLOT(remove_staff_slot()));

    unsetCursor();

    ensurePolished();
    init_pixmaps();

    // When closing/(re)loading, we must wait for this to delete to avoid crashes
    //  due to still active external connections like heartBeatTimer.
    MusEGlobal::muse->addPendingObjectDestruction(this);
}

ScoreCanvas::~ScoreCanvas()
{
    delete steprec;
}

void ScoreCanvas::staffmode_treble_slot()
{
    set_staffmode(current_staff, MODE_TREBLE);
}

void ScoreCanvas::staffmode_bass_slot()
{
    set_staffmode(current_staff, MODE_BASS);
}

void ScoreCanvas::staffmode_both_slot()
{
    set_staffmode(current_staff, MODE_BOTH);
}

void ScoreCanvas::remove_staff_slot()
{
    remove_staff(current_staff);
}

void ScoreCanvas::set_staffmode(list<staff_t>::iterator it, staff_mode_t mode)
{
    if (it->type == GRAND_BOTTOM)
    {
        it--;
        if (it->type!=GRAND_TOP)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_bottom without top!"<<endl;
    }

    if (it->type==GRAND_TOP)
    {
        list<staff_t>::iterator tmp=it;
        tmp++;
        if (tmp->type!=GRAND_BOTTOM)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_top without bottom!"<<endl;
        staves.erase(tmp);
    }

    switch (mode)
    {
        case MODE_TREBLE:
            it->type=NORMAL;
            it->clef=VIOLIN;
            break;

        case MODE_BASS:
            it->type=NORMAL;
            it->clef=BASS;
            break;

        case MODE_BOTH:
            it->type=GRAND_BOTTOM;
            it->clef=BASS;

            staves.insert(it, staff_t(this, GRAND_TOP, VIOLIN, it->parts));
            break;

        default:
            cerr << "ERROR: ILLEGAL FUNCTION CALL: invalid mode in set_staffmode" << endl;
    }

    fully_recalculate();
    recalc_staff_pos();
}

void ScoreCanvas::remove_staff(list<staff_t>::iterator it)
{
    if (it->type == GRAND_BOTTOM)
    {
        it--;
        if (it->type!=GRAND_TOP)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_bottom without top!"<<endl;
    }

    if (it->type == NORMAL)
    {
        staves.erase(it);
    }
    else if (it->type == GRAND_TOP)
    {
        staves.erase(it++);
        if (it->type!=GRAND_BOTTOM)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_top without bottom!"<<endl;
        staves.erase(it);
    }

    maybe_close_if_empty();
    fully_recalculate();
    recalc_staff_pos();
}

void ScoreCanvas::merge_staves(list<staff_t>::iterator dest, list<staff_t>::iterator src)
{
    if (dest->type == GRAND_BOTTOM)
    {
        dest--;
        if (dest->type!=GRAND_TOP)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_bottom without top!"<<endl;
    }

    if (src->type == GRAND_BOTTOM)
    {
        src--;
        if (src->type!=GRAND_TOP)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_bottom without top!"<<endl;
    }

    if (dest==src) //dragged to itself?
        return;


    dest->parts.insert(src->parts.begin(), src->parts.end());

    if (dest->type == GRAND_TOP)
    {
        dest++;
        if (dest->type != GRAND_BOTTOM)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_top without bottom!"<<endl;
        dest->parts.insert(src->parts.begin(), src->parts.end());
    }

    dest->update_part_indices();

    remove_staff(src);

    fully_recalculate();
    recalc_staff_pos();
}

void ScoreCanvas::move_staff_above(list<staff_t>::iterator dest, list<staff_t>::iterator src)
{
    if (dest->type == GRAND_BOTTOM)
    {
        dest--;
        if (dest->type!=GRAND_TOP)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_bottom without top!"<<endl;
    }

    if (src->type == GRAND_BOTTOM)
    {
        src--;
        if (src->type!=GRAND_TOP)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_bottom without top!"<<endl;
    }

    if (dest==src) //dragged to itself?
        return;


    list<staff_t>::iterator src_end=src;
    src_end++; //point _after_ src
    if (src->type==GRAND_TOP) //if this is a grand staff, we need to splice two list-entries
        src_end++;

    staves.splice(dest, staves, src, src_end);

    fully_recalculate();
    recalc_staff_pos();
}

void ScoreCanvas::move_staff_below(list<staff_t>::iterator dest, list<staff_t>::iterator src)
{
    if (dest->type == GRAND_TOP)
    {
        dest++;
        if (dest->type!=GRAND_BOTTOM)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: grand_top without bottom!"<<endl;
    }
    dest++; //now dest points past the destination staff.
            //if dest was a grand staff, it now points past the bottom staff

    move_staff_above(dest, src);
}

void ScoreCanvas::set_selected_part(const MusECore::Part* p)
  {selected_part=p; if (selected_part) selected_part_index=selected_part->uuid();}
void ScoreCanvas::set_dragged_event_part(const MusECore::Part* p)
  {dragged_event_part=p; if (dragged_event_part) dragged_event_part_index=dragged_event_part->uuid();}

set<const MusECore::Part*> ScoreCanvas::get_all_parts()
{
    set<const MusECore::Part*> result;

    for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
        result.insert(it->parts.begin(), it->parts.end());

    return result;
}

void ScoreCanvas::fully_recalculate()
{
    song_changed(SC_EVENT_MODIFIED);
}

void ScoreCanvas::song_changed(MusECore::SongChangedStruct_t flags)
{
    if(parent && parent->deleting())  // Ignore while while deleting to prevent crash.
        return;

    if (flags & (SC_PART_MODIFIED | SC_PART_REMOVED | SC_PART_INSERTED | SC_TRACK_REMOVED))
    {
        update_parts();

        if (flags & (SC_PART_REMOVED | SC_TRACK_REMOVED))
        {
            for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
                it->cleanup_parts();

            cleanup_staves();

            for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
                it->recalculate();

            recalc_staff_pos();

            redraw();
        }
    }

    if (flags & (SC_PART_MODIFIED |
                 SC_EVENT_INSERTED | SC_EVENT_MODIFIED | SC_EVENT_REMOVED |
                 SC_SIG  | SC_KEY) )
    {
        calc_pos_add_list();

        for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
            it->recalculate();

        recalc_staff_pos();

        redraw();
        emit canvas_width_changed(canvas_width());
    }

    if (flags & SC_SELECTION)
    {
        redraw();
    }
}

int ScoreCanvas::canvas_width()
{
    //return tick_to_x(staves.begin()->itemlist.rbegin()->first);
    return tick_to_x(SONG_LENGTH);
}

int ScoreCanvas::canvas_height()
{
    return staves.empty() ? 0 : staves.rbegin()->y_bottom;
}

int ScoreCanvas::viewport_width()
{
    return (width() - x_left);
}

int ScoreCanvas::viewport_height()
{
    return height();
}

string IntToStr(int i)
{
    ostringstream s;
    s<<i;
    return s.str();
}

QString IntToQStr(int i)
{
    return QString(IntToStr(i).c_str());
}

void color_image(QImage& img, const QColor& color)
{
    uchar* ptr=img.bits();
    int bytes=img.bytesPerLine() * img.height();   // By Tim. For older Qt versions. Tested OK on Qt4.5.
    int r,g,b;
    color.getRgb(&r,&g,&b);

    for (int i=0; i<bytes/4; i++)
    {
        QRgb* rgb=((QRgb*)ptr);
        (*rgb) = qRgba(r,g,b,qAlpha(*rgb));

        ptr+=4;
    }
}

void load_colored_pixmaps(QString file, QPixmap* array, bool all_colors = false)
{
    QImage img(file);

    if (all_colors) {
        for (int color_index=0;color_index<NUM_MYCOLORS; color_index++)
        {
            color_image(img, mycolors[color_index]);
            array[color_index]=QPixmap::fromImage(img);
        }
    } else {
        color_image(img, mycolors[DEFAULT]);
        array[0] = QPixmap::fromImage(img);
    }
}



void ScoreCanvas::init_pixmaps()
{
    if (!pixmaps_initalized)
    {
        if (debugMsg) cout << "initializing colors..." << endl;

        mycolors = new QColor[NUM_MYCOLORS];

        mycolors[DEFAULT] = palette().color(QPalette::WindowText);
        for (int i = 0; i < NUM_PARTCOLORS; i++)
            mycolors[i] = MusEGlobal::config.partColors[i];
        mycolors[HIGHLIGHTED_PIXMAP] = Qt::red;
        mycolors[SELECTED_PIXMAP] = QColor(255,160,0);

        for (int i=0; i<64; i++)
            mycolors[i+VELO_PIXMAP_BEGIN]=QColor(i*4,0,0xff);
        for (int i=64; i<128; i++)
            mycolors[i+VELO_PIXMAP_BEGIN]=QColor(0xff,0,(127-i)*4);


        if (debugMsg) cout << "loading pixmaps..." << endl;

        pix_whole=new QPixmap[NUM_MYCOLORS];
        pix_half=new QPixmap[NUM_MYCOLORS];
        pix_quarter=new QPixmap[NUM_MYCOLORS];
        pix_dot=new QPixmap[NUM_MYCOLORS];
        pix_b=new QPixmap[NUM_MYCOLORS];
        pix_sharp=new QPixmap[NUM_MYCOLORS];
        pix_noacc=new QPixmap[NUM_MYCOLORS];
        pix_num=new QPixmap[10];

        pix_r1=new QPixmap;
        pix_r2=new QPixmap;
        pix_r4=new QPixmap;
        pix_r8=new QPixmap;
        pix_r16=new QPixmap;
        pix_r32=new QPixmap;

        pix_clef_violin=new QPixmap;
        pix_clef_bass=new QPixmap;

        pix_flag_up=new QPixmap[4];
        pix_flag_down=new QPixmap[4];


        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/whole.png", pix_whole, true);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/half.png", pix_half, true);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/quarter.png", pix_quarter, true);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/dot.png", pix_dot, true);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/acc_none.png", pix_noacc, true);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/acc_sharp.png", pix_sharp, true);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/acc_b.png", pix_b, true);

        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/rest1.png", pix_r1);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/rest2.png", pix_r2);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/rest4.png", pix_r4);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/rest8.png", pix_r8);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/rest16.png", pix_r16);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/rest32.png", pix_r32);

        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/flags8u.png", &pix_flag_up[0]);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/flags16u.png", &pix_flag_up[1]);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/flags32u.png", &pix_flag_up[2]);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/flags64u.png", &pix_flag_up[3]);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/flags8d.png", &pix_flag_down[0]);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/flags16d.png", &pix_flag_down[1]);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/flags32d.png", &pix_flag_down[2]);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/flags64d.png", &pix_flag_down[3]);

        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/clef_violin_big.png", pix_clef_violin);
        load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/clef_bass_big.png", pix_clef_bass);

        for (int i = 0; i < 10; i++)
            load_colored_pixmaps(MusEGlobal::museGlobalShare + "/scoreglyphs/" + IntToQStr(i) + ".png", &pix_num[i]);


        pixmaps_initalized=true;

        if (debugMsg) cout << "done" << endl;
    }
}



int modulo(int a, int b) // similar to a % b
{
    return (((a%b)+b)%b);
}

int divide_floor(int a, int b) // similar to a / b
{
    return int(floor(float(a)/float(b)));
}

#define DEFAULT_REST_HEIGHT 6


bool operator< (const note_pos_t& a, const note_pos_t& b)
{
    if (a.height<b.height) return true;
    if (a.height>b.height) return false;
    return a.vorzeichen<b.vorzeichen;
}



int flo_quantize(int tick, int quant_ticks)
{
    return MusEGlobal::sigmap.raster(tick, quant_ticks);
}

int flo_quantize_floor(int tick, int quant_ticks)
{
    return MusEGlobal::sigmap.raster1(tick, quant_ticks);
}


/* builds the event list used by the score editor.
 * that list contains only note-on and -off, time-sig- and
 * key-change events.
 * it stores them sorted by their time (quantized); if more
 * events with the same time occur, the NOTE-OFFs are
 * put before the NOTE-ONs
 * it only operates on parts which were selected in the
 * arranger when the score viewer was started
 *
 * this abstracts the rest of the renderer from muse's internal
 * data structures, making this easy to port to another application
 */
void staff_t::create_appropriate_eventlist()
{
    using MusEGlobal::sigmap;
    using MusECore::iSigEvent;

    eventlist.clear();

    // phase one: fill the list -----------------------------------------

    //insert note on events
    for (set<const MusECore::Part*>::const_iterator part_it=parts.begin(); part_it!=parts.end(); part_it++)
    {
        const MusECore::Part* part=*part_it;

        for (MusECore::ciEvent it=part->events().begin(); it!=part->events().end(); it++)
        {
            const MusECore::Event& event=it->second;

            if ( ( event.isNote() && !event.isNoteOff() &&
                   // (event.endTick() <= part->lenTick()) ) &&
                   (event.tick() <= part->lenTick()) ) && // changed to accord to prcanvas.cpp and others (flo93)
                 ( ((type==GRAND_TOP) && (event.pitch() >= SPLIT_NOTE)) ||
                   ((type==GRAND_BOTTOM) && (event.pitch() < SPLIT_NOTE)) ||
                   (type==NORMAL) )                          )
            {
                unsigned begin, end;
                begin=flo_quantize(event.tick()+part->tick(), parent->quant_ticks());
                end=flo_quantize(event.endTick()+part->tick(), parent->quant_ticks());
                if (end==begin)
                {
                    if (heavyDebugMsg) cout << "note len would be quantized to zero. using minimal possible length" << endl;
                    end=begin+parent->quant_ticks();
                }

                if (heavyDebugMsg) cout << "inserting note on at "<<begin<<" with pitch="<<event.pitch()<<" and len="<<end-begin<<endl;
                eventlist.insert(pair<unsigned, FloEvent>(begin, FloEvent(begin,event.pitch(), event.velo(),end-begin,FloEvent::NOTE_ON,part,&it->second)));
            }
            //else ignore it
        }
    }

    //insert bars and time signatures
    for (iSigEvent it=MusEGlobal::sigmap.begin(); it!=MusEGlobal::sigmap.end(); it++)
    {
        unsigned from=it->second->tick;
        unsigned to=it->first;
        unsigned ticks_per_measure=MusEGlobal::sigmap.ticksMeasure(it->second->tick);

        if (to > unsigned(SONG_LENGTH))
            to=SONG_LENGTH;

        if (heavyDebugMsg) cout << "new signature from tick "<<from<<" to " << to << ": "<<it->second->sig.z<<"/"<<it->second->sig.n<<"; ticks per measure = "<<ticks_per_measure<<endl;
        eventlist.insert(pair<unsigned, FloEvent>(from,  FloEvent(from, FloEvent::TIME_SIG, it->second->sig.z, it->second->sig.n) ) );
        for (unsigned t=from; t<to; t+=ticks_per_measure)
            eventlist.insert(pair<unsigned, FloEvent>(t,  FloEvent(t,0,0,ticks_per_measure,FloEvent::BAR) ) );
    }

    //insert key changes
    for (MusECore::iKeyEvent it=MusEGlobal::keymap.begin(); it!=MusEGlobal::keymap.end(); it++)
        eventlist.insert(pair<unsigned, FloEvent>(
          it->second.tick, FloEvent(it->second.tick,FloEvent::KEY_CHANGE, it->second.key, it->second.minor ) ) );

    // phase two: deal with overlapping notes ---------------------------
    ScoreEventList::iterator it, it2;

    //iterate through all note_on - events
    for (it=eventlist.begin(); it!=eventlist.end(); it++) {
        if (it->second.type==FloEvent::NOTE_ON)
        {
            unsigned end_tick=it->first + it->second.len;

            //iterate though all (relevant) later note_ons which are
            //at the same pitch. if there's a collision, shorten it's len
            for (it2=it, it2++; it2!=eventlist.end() && it2->first < end_tick; it2++)
                if ((it2->second.type==FloEvent::NOTE_ON) && (it2->second.pitch == it->second.pitch))
                    it->second.len=it2->first - it->first;
        }
    }


        // phase three: eliminate zero-length-notes -------------------------
        for (it=eventlist.begin(); it!=eventlist.end();) {
            if ((it->second.type==FloEvent::NOTE_ON) && (it->second.len<=0))
                eventlist.erase(it++);
            else
                it++;
        }
}


bool is_sharp_key(MusECore::key_enum t)
{
    return ((t>=MusECore::KEY_SHARP_BEGIN) && (t<=MusECore::KEY_SHARP_END));
}
bool is_b_key(MusECore::key_enum t)
{
    return ((t>=MusECore::KEY_B_BEGIN) && (t<=MusECore::KEY_B_END));
}

int n_accidentials(MusECore::key_enum t)
{
    if (is_sharp_key(t))
        return t-MusECore::KEY_SHARP_BEGIN-1;
    else
        return t-MusECore::KEY_B_BEGIN-1;
}


//note needs to be 0..11
//always assumes violin clef
//only for internal use
note_pos_t note_pos_(int note, MusECore::key_enum key)
{
    note_pos_t result;
               //C CIS D DIS E F FIS G GIS A AIS H
    int foo[12]={0,-1, 1,-1, 2,3,-1, 4,-1, 5, -1,6};

    if ((note<0) || (note>=12))
        cerr << "ERROR: ILLEGAL FUNCTION CALL (note_pos, note out of range)" << endl;

    if (foo[note]!=-1)
    {
        result.height=foo[note];
        result.vorzeichen=NONE;
    }
    else
    {
        if (is_sharp_key(key))
        {
            result.height=foo[note-1];
            result.vorzeichen=SHARP;
        }
        else // if is_b_key
        {
            result.height=foo[note+1];
            result.vorzeichen=B;
        }
    }

    // Special cases for GES / FIS keys
    if (key==MusECore::KEY_GES)
    {
        // convert a H to a Ces
        if (note==11)
        {
            result.height=12;
            result.vorzeichen=B;
        }
    }
    else if (key==MusECore::KEY_FIS)
    {
        // convert a F to an Eis
        if (note==5)
        {
            result.height=2;
            result.vorzeichen=SHARP;
        }
    }

    return result;
}


//  V   --------------------------  <-- height=10
//  I C --------------------------  <-- height=8
//  O L --------------------------  <-- height=6
//  L E --------------------------  <-- height=4
//  I F --------------------------  <-- height=2
//  N    --o--                      <-- this is C4. height=0

// the "spaces" in between the lines have odd numbers.
// that is, the space between line 2 and 4 is numbered 3.

// these numbers do not change when clef changes. line 2
// is always the "bottom line" of the system.
// in violin clef, line 2 is E4
// in bass clef, line 2 is G2

note_pos_t note_pos (unsigned note, MusECore::key_enum key, clef_t clef)
{
    int octave=(note/12)-1; //integer division. note is unsigned
    note=note%12;

    //now octave contains the octave the note is in
    //(A4 is the 440Hz tone. C4 is the "low C" in the violin clef
    //and the "high C" in the bass clef.
    //note contains 0 for C, 1 for Cis, ..., 11 for H (or B if you're not german)

    note_pos_t pos=note_pos_(note,key);

    switch (clef) //CLEF_MARKER
    {
        case VIOLIN:
            pos.height=pos.height + (octave-4)*7;
            break;

        case BASS:
            pos.height=pos.height + (octave-3)*7 + 5;
            break;
    }

    return pos;
}


int calc_len(int l, int d)
{
    // l=0,1,2 -> whole, half, quarter (think of 2^0, 2^1, 2^2)
    // d=number of dots

    int tmp=0;
    for (int i=0;i<=d;i++)
        tmp+=TICKS_PER_WHOLE / (1 << (l+i));

    return tmp;
}


int calc_measure_len(const list<int>& nums, int denom)
{
    int sum=0;

    for (list<int>::const_iterator it=nums.begin(); it!=nums.end(); it++)
        sum+=*it;

    return 64* sum/denom;
}

vector<int> create_emphasize_list(const list<int>& nums, int denom)
{
    if (heavyDebugMsg)
    {
        cout << "creating emphasize list for ";
        for (list<int>::const_iterator it=nums.begin(); it!=nums.end(); it++)
            cout << *it << " ";
        cout << "/ "<<denom;
    }

    //        |----- 8th -----|
    int foo[]={4,7,6,7,5,7,6,7}; //if 64 changes, this also must change
    int pos=0;
    int len=calc_measure_len(nums, denom);

    vector<int> result(len);

    for (int i=0;i<len;i++)
        result[i]=foo[i%8];

    for (list<int>::const_iterator it=nums.begin(); it!=nums.end(); it++)
    {
        result[pos]=1;
        for (int i=1;i<*it;i++)
            result[pos + i*64/denom]=2;
        pos+= *it * 64 / denom;
    }

    result[0]=0;

    if (heavyDebugMsg)
    {
        for (int i=0;i<len;i++)
        {
            if (i%8==0)
                cout << endl<<i<<":\t";
            cout << result[i]<<" ";
        }
        cout << endl;
    }

    return result;
}

vector<int> create_emphasize_list(int num, int denom)
{
    list<int> nums;

    if (num%3 ==0)
    {
        for (int i=0;i<num/3;i++)
            nums.push_back(3);
    }
    else if (num%2 ==0)
    {
        for (int i=0;i<num/2;i++)
            nums.push_back(2);
    }
    else // num is odd
    {
        for (int i=0;i<(num-3)/2;i++)
            nums.push_back(2);

        nums.push_back(3);
    }

    return create_emphasize_list(nums, denom);
}

//quant_power2 must be in log(len), that is
//whole, half, quarter, eighth = 0,1,2,3
//NOT:  1,2,4,8! (think of 2^foo)
//len is in ticks
list<note_len_t> parse_note_len(int len_ticks, int begin_tick, vector<int>& foo, bool allow_dots, bool allow_normal)
{
    list<note_len_t> retval;

    if (len_ticks<0)
        cerr << "ERROR: ILLEGAL FUNCTION CALL in parse_note_len: len_ticks < 0" << endl;
    if (begin_tick<0)
        cerr << "ERROR: ILLEGAL FUNCTION CALL in parse_note_len: begin_tick < 0" << endl;

    if (allow_normal)
    {
        int dot_max = allow_dots ? MAX_QUANT_POWER : 0;

        for (int i=0;i<=MAX_QUANT_POWER;i++)
            for (int j=0;j<=dot_max-i;j++)
                if (calc_len(i,j) == len_ticks)
                {
                    retval.push_back(note_len_t (i,j));
                    return retval;
                }
    }

    //if !allow_normal or if the above failed

    int begin=begin_tick * 64 / TICKS_PER_WHOLE;
    int len=len_ticks * 64 / TICKS_PER_WHOLE;

    unsigned pos=begin;
    int len_done=0;

    while (len_done<len)
    {
        int len_now=0;
        int last_number=foo[pos];

        do {pos++;len_done++;len_now++;} while (! ((pos==foo.size()) || (foo[pos]<=last_number) || (len_done==len)) );

        len_now=len_now*TICKS_PER_WHOLE/64;

        if (heavyDebugMsg) cout << "add " << len_now << " ticks" << endl;
        if (allow_dots)
        {
            for (int i=0;i<=MAX_QUANT_POWER;i++)
                for (int j=0;j<=MAX_QUANT_POWER-i;j++)
                    if (calc_len(i,j) == len_now)
                    {
                        retval.push_back(note_len_t (i,j));
                        len_now=0;
                    }
        }

        if (len_now) //the above failed or allow_dots=false
        {
            for (int i=0; i<=MAX_QUANT_POWER; i++)
            {
                int tmp=calc_len(i,0);
                if (tmp <= len_now)
                {
                    retval.push_back(note_len_t(i));
                    len_now-=tmp;
                    if (len_now==0) break;
                }
            }
        }

        if (len_now!=0)
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN. wasn't able to split note len properly; len_now="<<len_now << endl;

        if (pos==foo.size()) //we cross measure boundaries?
            pos=0;
    }


    return retval;
}


#define YLEN 10
#define NOTE_SHIFT 3

#define REST_AUSWEICH_X 10
#define DOT_XDIST 6
#define DOT_XBEGIN 10
#define DOT_XBEGIN_REST 10

//#define NUMBER_HEIGHT (pix_num[0].height())

// kann 0 oder 1 sein:
// bei notenkollisionen mit ungerader anzahl von kollidierenden
// wird immer so ausgewichen, dass möglichst wenige ausweichen müssen
// wenn die anzahl aber gerade ist, gibt es keine "bessere" lösung
// in dem fall werden immer die geraden (0) bzw. ungeraden (1)
// ausweichen.
// ROUGH TRANSLATION:
// can be 0 or 1:
// when there are note head collisions with an odd number of colliding
// heads there's an unique solution for "stepping aside", so that
// fewer notes must "step aside". but when the number of colliding
// heads is even, there is no "better" solution. this constant
// specifies whether the "odd" (1) or the "even" (0) heads will move.
#define AUSWEICHEN_BEVORZUGT 0

#define STEM_LEN 30

#define DOTTED_RESTS true
#define UNSPLIT_RESTS false

#define AUX_LINE_LEN 1.5

#define ACCIDENTIAL_DIST 11
#define KEYCHANGE_ACC_DIST 9
#define KEYCHANGE_ACC_LEFTDIST 9
#define KEYCHANGE_ACC_RIGHTDIST 0


#define no_notepos note_pos_t()

#define TIE_DIST 5
#define TIE_HEIGHT 6
#define TIE_THICKNESS 3

void ScoreCanvas::draw_tie (QPainter& p, int x1, int x4, int yo, bool up, QColor color)
{
    QPainterPath path;

    int y1, y2, y3;

    if (up)
    {
        y1 = yo - TIE_DIST;
        y2 = y1 - TIE_HEIGHT;
        y3=y2-TIE_THICKNESS;
    }
    else
    {
        y1 = yo + TIE_DIST;
        y2 = y1 + TIE_HEIGHT;
        y3=y2+TIE_THICKNESS;
    }

    int x2 = x1 + (x4-x1)/4;
    int x3 = x4 - (x4-x1)/4;

    path.moveTo(x1,y1);
    path.cubicTo( x2,y2  ,  x3,y2  ,  x4,y1 );
    path.cubicTo( x3,y3  ,  x2,y3  ,  x1,y1 );

    p.setPen(color);
    p.setBrush(color);

    p.drawPath(path);
}

void ScoreCanvas::draw_akkolade (QPainter& p, int x, int y_)
{
    QPainterPath path;

    qreal h = (2*2*YLEN+GRANDSTAFF_DISTANCE) /2.0  +3; //this is only the half height
    qreal w         = AKKOLADE_WIDTH;
    int y = y_ -h;

    const double X1 =  2.0 * w;
    const double X2 = -0.7096 * w;
    const double X3 = -1.234 * w;
    const double X4 =  1.734 * w;

    path.moveTo(x+ 0, y+ h);
    path.cubicTo(x+ X1,  y+ h + h * .3359, x+ X2,  y+ h + h * .5089, x+ w, y+ 2 * h);
    path.cubicTo(x+ X3,  y+ h + h * .5025, x+ X4,  y+ h + h * .2413, x+ 0, y+ h);
    path.cubicTo(x+ X1,  y+ h - h * .3359, x+ X2,  y+ h - h * .5089, x+ w, y+ 0);
    path.cubicTo(x+ X3,  y+ h - h * .5025, x+ X4,  y+ h - h * .2413, x+ 0, y+ h);

    p.drawPath(path);
}

void ScoreCanvas::draw_accidentials(QPainter& p, int x, int y_offset, const list<int>& acc_list, const QPixmap& pix)
{
    int n_acc_drawn=0;

    for (list<int>::const_iterator acc_it=acc_list.begin(); acc_it!=acc_list.end(); acc_it++)
    {
        int y_coord=2*YLEN  -  ( *acc_it -2)*YLEN/2;
        draw_pixmap(p,x + n_acc_drawn*KEYCHANGE_ACC_DIST,y_offset + y_coord,pix);
        n_acc_drawn++;
    }
}

void staff_t::create_itemlist()
{
    MusECore::key_enum tmp_key=MusECore::KEY_C;
    int lastevent=0;
    int next_measure=-1;
    int last_measure=-1;
    vector<int> emphasize_list=create_emphasize_list(4,4); //actually unnecessary, for safety

    itemlist.clear();

    for (ScoreEventList::iterator it=eventlist.begin(); it!=eventlist.end(); it++)
    {
        int t, pitch, len, velo, actual_tick;
        FloEvent::typeEnum typ;
        t=it->first;
        pitch=it->second.pitch;
        velo=it->second.vel;
        len=it->second.len;
        typ=it->second.type;
        actual_tick=it->second.tick;
        if (actual_tick==-1) actual_tick=t;

        note_pos_t notepos=note_pos(pitch,tmp_key,clef);

        if (heavyDebugMsg)
        {
            printf("FLO: t=%i\ttyp=%i\tpitch=%i\tvel=%i\tlen=%i\n",it->first, it->second.type, it->second.pitch, it->second.vel, it->second.len);
            cout << "\tline="<<notepos.height<<"\tvorzeichen="<<notepos.vorzeichen << endl;
        }

        if (typ == FloEvent::BAR)
        {
            if (last_measure!=-1) //i.e.: "this is NOT the first bar"
            {
                if (lastevent==last_measure) //there was no note?
                {
                    unsigned tmppos=(last_measure+t-parent->quant_ticks())/2;
                    if (heavyDebugMsg) cout << "\tend-of-measure: this was an empty measure. inserting rest in between at t="<<tmppos << endl;
                    itemlist[tmppos].insert( FloItem(FloItem::REST,notepos,0,0) );
                    itemlist[t].insert( FloItem(FloItem::REST_END,notepos,0,0) );
                }
                else
                {
                    // if necessary, insert rest at between last note and end-of-measure
                    int rest=t-lastevent;
                    if (rest)
                    {
                        if (heavyDebugMsg) printf("\tend-of-measure: set rest at %i with len %i\n",lastevent,rest);

                        list<note_len_t> lens=parse_note_len(rest,lastevent-last_measure,emphasize_list,DOTTED_RESTS,UNSPLIT_RESTS);
                        unsigned tmppos=lastevent;
                        for (list<note_len_t>::iterator x=lens.begin(); x!=lens.end(); x++)
                        {
                            if (heavyDebugMsg) cout << "\t\tpartial rest with len="<<x->len<<", dots="<<x->dots<<endl;
                            itemlist[tmppos].insert( FloItem(FloItem::REST,notepos,x->len,x->dots) );
                            tmppos+=calc_len(x->len,x->dots);
                            itemlist[tmppos].insert( FloItem(FloItem::REST_END,notepos,0,0) );
                        }
                    }
                }
            }

            lastevent=t;
            last_measure=t;
            next_measure=t+len;

            itemlist[t].insert( FloItem(FloItem::BAR,no_notepos,0,0) );
        }
        else if (typ == FloEvent::NOTE_ON)
        {
            int rest=t-lastevent;
            if (rest)
            {
                if (heavyDebugMsg) printf("\tset rest at %i with len %i\n",lastevent,rest);
                // no need to check if the rest crosses measure boundaries;
                // it can't.

                list<note_len_t> lens=parse_note_len(rest,lastevent-last_measure,emphasize_list,DOTTED_RESTS,UNSPLIT_RESTS);
                unsigned tmppos=lastevent;
                for (list<note_len_t>::iterator x=lens.begin(); x!=lens.end(); x++)
                {
                    if (heavyDebugMsg) cout << "\t\tpartial rest with len="<<x->len<<", dots="<<x->dots<<endl;
                    itemlist[tmppos].insert( FloItem(FloItem::REST,notepos,x->len,x->dots) );
                    tmppos+=calc_len(x->len,x->dots);
                    itemlist[tmppos].insert( FloItem(FloItem::REST_END,notepos,0,0) );
                }
            }



            if (heavyDebugMsg) printf("\tset note at %i with len=%i\n", t, len);

            int tmplen;
            bool tied_note;

            // if the note exceeds the current measure, split it.
            if (t+len>next_measure)
            {
                tmplen=next_measure-t;
                tied_note=true;

                //append the "remainder" of the note to our EventList, so that
                //it gets processed again when entering the new measure
                int newlen=len-tmplen;
                eventlist.insert(pair<unsigned, FloEvent>(next_measure, FloEvent(actual_tick,pitch, velo,0,FloEvent::NOTE_OFF, it->second.source_part, it->second.source_event)));
                eventlist.insert(pair<unsigned, FloEvent>(next_measure, FloEvent(actual_tick,pitch, velo,newlen,FloEvent::NOTE_ON, it->second.source_part, it->second.source_event)));

                if (heavyDebugMsg) cout << "\t\tnote was split to length "<<tmplen<<" + " << newlen<<endl;
            }
            else
            {
                tmplen=len;
                tied_note=false;

                if (heavyDebugMsg) cout << "\t\tinserting NOTE OFF at "<<t+len<<endl;
                eventlist.insert(pair<unsigned, FloEvent>(t+len,   FloEvent(t+len,pitch, velo,0,FloEvent::NOTE_OFF,it->second.source_part, it->second.source_event)));
            }

            list<note_len_t> lens=parse_note_len(tmplen,t-last_measure,emphasize_list,true,true);
            unsigned tmppos=t;
            int n_lens=lens.size();
            int count=0;
            for (list<note_len_t>::iterator x=lens.begin(); x!=lens.end(); x++)
            {
                if (heavyDebugMsg) cout << "\t\tpartial note with len="<<x->len<<", dots="<<x->dots<<endl;
                count++;

                bool tie;

                if (count<n_lens)
                    tie=true;      // all notes except the last are always tied
                else
                    tie=tied_note; // only the last respects tied_note

                itemlist[tmppos].insert( FloItem(FloItem::NOTE,notepos,x->len,x->dots, tie, actual_tick, it->second.source_part, it->second.source_event) );
                tmppos+=calc_len(x->len,x->dots);
                itemlist[tmppos].insert( FloItem(FloItem::NOTE_END,notepos,0,0) );
            }
        }
        else if (typ == FloEvent::NOTE_OFF)
        {
            lastevent=t;
        }
        else if (typ == FloEvent::TIME_SIG)
        {
            if (heavyDebugMsg) cout << "inserting TIME SIGNATURE "<<it->second.num<<"/"<<it->second.denom<<" at "<<t<<endl;
            itemlist[t].insert( FloItem(FloItem::TIME_SIG, it->second.num, it->second.denom) );

            emphasize_list=create_emphasize_list(it->second.num, it->second.denom);
        }
        else if (typ == FloEvent::KEY_CHANGE)
        {
            if (heavyDebugMsg) cout << "inserting KEY CHANGE ("<<it->second.key<<") at "<<t<<endl;
            itemlist[t].insert( FloItem(FloItem::KEY_CHANGE, it->second.key, it->second.minor) );
            tmp_key=it->second.key;
        }
    }
}

void staff_t::process_itemlist()
{
    map<int,int> occupied;
    int last_measure=0;
    vector<int> emphasize_list=create_emphasize_list(4,4); //unnecessary, only for safety

    //iterate through all times with items
    for (ScoreItemList::iterator it2=itemlist.begin(); it2!=itemlist.end(); it2++)
    {
        set<FloItem, floComp>& curr_items=it2->second;

        if (heavyDebugMsg) cout << "at t="<<it2->first<<endl;

        // phase 0: keep track of active notes, rests -------------------
        //          (and occupied lines) and the last measure
        //          and the current time signature
        for (set<FloItem, floComp>::iterator it=curr_items.begin(); it!=curr_items.end(); it++)
        {
            if ((it->type==FloItem::NOTE) || (it->type==FloItem::REST))
                occupied[it->pos.height]++;
            else if ((it->type==FloItem::NOTE_END) || (it->type==FloItem::REST_END))
                occupied[it->pos.height]--;
            else if (it->type==FloItem::BAR)
                last_measure=it2->first;
            else if (it->type==FloItem::TIME_SIG)
                emphasize_list=create_emphasize_list(it->num, it->denom);
        }

        if (heavyDebugMsg)
        {
            cout << "occupied: ";
            for (map<int,int>::iterator i=occupied.begin(); i!=occupied.end(); i++)
                if (i->second) cout << i->first << "("<<i->second<<")   ";
            cout << endl;
        }





        // phase 1: group rests together -----------------------------------
        int n_groups=0;
        bool dont_group=false;

        //iterate through all rests R at that time
        //  iterate through all rests X at that time below R
        //    if something is between X and R ("barrier"), stop
        //    else: group them together
        for (set<FloItem, floComp>::iterator it=curr_items.begin(); it!=curr_items.end();)
        {
            //only operate on rests; ignore rests which are created by this code
            //(can be seen on already_grouped)
            if ((it->type==FloItem::REST) && (it->already_grouped==false))
            {
                if (heavyDebugMsg) cout << "trying to group" << endl;

                int lastheight;
                int height_cumulative=0;
                int counter=0;

                lastheight=it->pos.height;

                set<FloItem, floComp>::iterator tmp;
                for (tmp=it; tmp!=curr_items.end();)
                {
                    if (heavyDebugMsg) cout << "checking if we can proceed with an item at height="<<tmp->pos.height<<endl;

                    for (int i=lastheight+1; i<=tmp->pos.height-1; i++)
                        if (occupied[i]!=0)
                        {
                            if (heavyDebugMsg) cout << "we can NOT, because occ["<<i<<"] != 0" << endl;
                            //stop grouping that rest
                            goto get_out_here;
                        }

                    lastheight=tmp->pos.height;

                    // the current item is a rest with equal len? cool!
                    if (tmp->type==FloItem::REST && tmp->len==it->len && tmp->dots==it->dots)
                    {
                        // füge diese pause zur gruppe dazu und entferne sie von diesem set hier
                        // entfernen aber nur, wenn sie nicht it, also die erste pause ist, die brauchen wir noch!
                        if (heavyDebugMsg) cout << "\tgrouping rest at height="<<tmp->pos.height<<endl;
                        height_cumulative+=tmp->pos.height;
                        counter++;
                        if (tmp!=it)
                            curr_items.erase(tmp++);
                        else
                            tmp++;
                    }
                    else //it's something else? well, we can stop grouping that rest then
                    {
                        if (heavyDebugMsg) cout << "we can NOT, because that item is not a rest" << endl;
                        //stop grouping that rest
                        goto get_out_here;
                    }
                }
                if (heavyDebugMsg) cout << "no items to proceed on left, continuing" << endl;
                get_out_here:

                n_groups++;

                // entferne it vom set und
                // füge eine pause mit dem "mittelwert" ein.
                // occupied und die "_END"-events bleiben unberührt

                FloItem temp=*it;
                temp.already_grouped=true;

                // have we grouped all available rests into one single?
                if ( (n_groups==1) && (tmp==curr_items.end()) && !dont_group)
                {
                    if (heavyDebugMsg) cout << "wow, we were able to group all rests into one single" << endl;
                    if (temp.len==0) //the whole rest is shifted one line (one space and one line)
                        temp.pos.height=DEFAULT_REST_HEIGHT+2;
                    else
                        temp.pos.height=DEFAULT_REST_HEIGHT;
                }
                else
                {
                    if (heavyDebugMsg) cout << "creating group #"<<n_groups<<endl;
                    temp.pos.height=nearbyint((float)height_cumulative/counter);
                }

                // do NOT first insert, then erase, because if temp.height ==
                // it->height, the set considers temp and it equal (it doesn't
                // take already_grouped into account)
                // the result of this: insert does nothing, and erase erases
                // the item. effect: you don't have the rest at all
                curr_items.erase(it++);

                if (heavyDebugMsg) cout << "replacing all grouped rests with a rest at height="<<temp.pos.height<<endl;

                curr_items.insert(temp);
            }
            else
            {
                if (it->type==FloItem::NOTE)
                    dont_group=true;

                it++;
            }
        }





        // phase 2: avoid collisions of items ------------------------------
        set<FloItem, floComp>::iterator lastit, groupbegin, invalid;
        invalid=curr_items.end();
        lastit=invalid;
        groupbegin=invalid;
  int count = 0;

        //TODO: is "grouping" notes and rests together okay?
        //      or is it better to ignore rests when grouping?
        for (set<FloItem, floComp>::iterator it=curr_items.begin(); it!=curr_items.end(); it++) {
            if ( (it->type==FloItem::NOTE) || (it->type==FloItem::REST) )
            {
                if (lastit != invalid)
                {
                    if (it->pos.height == lastit->pos.height+1) // they would collide?
                    {
                        if (groupbegin==invalid) // we have no group atm?
                        {
                            groupbegin=lastit;     // start a new group
                            count=1; // because lastit has to be taken into account.
                                     // for "it", there's a count++ later
                        }

                        // the following will work even on start-new-group,
                        // because lastit will be "untouched", and that's why
                        // still be initialized to "false"
                        it->ausweich=!lastit->ausweich;

                        count++;
                    }
                    else
                    {
                        if (groupbegin!=invalid) //this is the first item which
                        {												 //doesn't belong to the previous group any more
                            if (count%2 == 0) //count is even?
                                if (modulo(groupbegin->pos.height, 2) == AUSWEICHEN_BEVORZUGT)
                                    for (set<FloItem, floComp>::iterator tmp=groupbegin; tmp!=it; tmp++)
                                        tmp->ausweich=!tmp->ausweich;

                            groupbegin=invalid;
                        }
                        // else: everything is ok :)
                    }
                }

                lastit=it;
            }
        }

            // this could be the case if the last processed item before end()
            // still belonged to a group. finalize this last group as well:
            if (groupbegin!=invalid)
            {
                if (count%2 == 0) //count is even?
                    if (modulo(groupbegin->pos.height, 2) == AUSWEICHEN_BEVORZUGT)
                        for (set<FloItem, floComp>::iterator tmp=groupbegin; tmp!=curr_items.end(); tmp++)
                            tmp->ausweich=!tmp->ausweich;
            }
            // else: everything is ok :)





        // phase 3: group notes by their length and ------------------------
        //          find out appropriate stem directions
group_them_again:
        map<int, cumulative_t> lengths;
        bool has_whole=false;

        // find out which note lengths are present at that time
        for (set<FloItem, floComp>::iterator it=curr_items.begin(); it!=curr_items.end(); it++)
            if (it->type==FloItem::NOTE)
                lengths[it->len].add(it->pos.height);

        if (heavyDebugMsg)
        {
            cout << "note lengths at that time are:";
            for (map<int, cumulative_t>::iterator it=lengths.begin(); it!=lengths.end(); it++)
                cout << it->first << "("<< it->second.mean() <<")  ";
            cout << endl;
        }

        if (lengths.erase(0)) // in case "0" is in the set, erase it
            has_whole=true;     // but remember there were whole notes

        if (lengths.size()==0)
        {
            if (heavyDebugMsg) cout << "no notes other than wholes, or no notes at all. we can relax" << endl;
        }
        else if (lengths.size()==1)
        {
            pair<const int, cumulative_t>& group=*(lengths.begin());
            stem_t stem;
            int shift=0;
            if (heavyDebugMsg) cout << "only one non-whole note group (len="<<group.first<<") at height="<<group.second.mean()<< endl;

            if (group.second.mean()>=6)
            {
                stem=DOWNWARDS;
                if (has_whole)
                    shift=-1;
            }
            else
            {
                stem=UPWARDS;
                if (has_whole)
                    shift=1;
            }

            // for each note in that group
            for (set<FloItem, floComp>::iterator it=curr_items.begin(); it!=curr_items.end(); it++)
                if ( (it->type==FloItem::NOTE) && (it->len==group.first) )
                {
                    it->stem=stem;
                    it->shift=shift;
                }
        }
        else if (lengths.size()==2)
        {
            map<int, cumulative_t>::iterator it=lengths.begin();
            pair<const int, cumulative_t>& group1=*it;
            it++;
            pair<const int, cumulative_t>& group2=*it;
            stem_t stem1, stem2;
            int shift1=0, shift2=0;
            if (heavyDebugMsg) cout << "two non-whole note group: len="<<group1.first<<" at height="<<group1.second.mean()<<"  and len="<<group2.first<<" at height="<<group2.second.mean()<< endl;

            if (group1.second.mean()<group2.second.mean())
            {
                stem1=DOWNWARDS;
                stem2=UPWARDS;
                shift1=-1;
                if (has_whole)
                    shift2=1;
            }
            else
            {
                stem1=UPWARDS;
                stem2=DOWNWARDS;
                shift2=-1;
                if (has_whole)
                    shift1=1;
            }

            // for each note in group1
            for (set<FloItem, floComp>::iterator itt=curr_items.begin(); itt != curr_items.end(); itt++)
                if ((itt->type == FloItem::NOTE) && (itt->len == group1.first) )
                {
                    itt->stem=stem1;
                    itt->shift=shift1;
                }

            // for each note in group2
            for (set<FloItem, floComp>::iterator itt=curr_items.begin(); itt != curr_items.end(); itt++)
                if ((itt->type == FloItem::NOTE) && (itt->len == group2.first) )
                {
                    itt->stem=stem2;
                    itt->shift=shift2;
                }
        }
        else //more than 2 groups
        {
            //at this time, there are no iterators pointing to curr_items.
            //this means, we can erase and insert safely into curr_items here.

            //group1 contains the longer notes, group2 the shorter

            int group1_n=lengths.size()/2; //round down
            int group2_n=lengths.size()-group1_n;

            int group1_len, group2_len;
            int group1_len_ticks, group2_len_ticks;


            map<int, cumulative_t>::iterator lit=lengths.begin();
            for (int i=0;i<group1_n-1;i++) lit++; //go to the group1_n-th entry
            group1_len=lit->first;
            for (int i=0;i<group2_n;i++) lit++;  //go to the (group1_n+group2_n)-th entry (i.e., the last before end() )
            group2_len=lit->first;

            group1_len_ticks=calc_len(group1_len,0);
            group2_len_ticks=calc_len(group2_len,0);

            if (heavyDebugMsg) cout << "we have "<<lengths.size()<<" groups. putting the "<<group1_n<<" longest and the "<<group2_n<<"shortest groups together"<<endl <<
                                       "\tgroup1 will have len="<<group1_len<<" ("<<group1_len_ticks<<" ticks), group2 will have len="<<group2_len<<" ("<<group2_len_ticks<<" ticks)"<<endl;

            for (set<FloItem, floComp>::iterator it=curr_items.begin(); it!=curr_items.end();)
                if (it->type==FloItem::NOTE)
                {
                    //if *it belongs to group1 and has not already its destination length
                    if (heavyDebugMsg) cout << "\tprocessing note-item with len="<<it->len<<endl;
                    if (it->len<group1_len)
                    {
                        if (heavyDebugMsg) cout << "\t\thas to be changed to fit into group 1" << endl;
                        FloItem tmp=*it;
                        curr_items.erase(it++);

                        int len_ticks_remaining=calc_len(tmp.len, tmp.dots)-group1_len_ticks;
                        bool tied_note=tmp.tied;


                        //shorten the current item to it's group's length
                        tmp.len=group1_len;
                        tmp.dots=0;
                        tmp.tied=true;
                        curr_items.insert(tmp);

                        //create items for the remaining lengths (and a note_END for the just created shortened note)
                        int t=it2->first+group1_len_ticks;

                        itemlist[t].insert( FloItem(FloItem::NOTE_END,tmp.pos,0,0) );

                        list<note_len_t> lens=parse_note_len(len_ticks_remaining,t-last_measure,emphasize_list,true,true);
                        unsigned tmppos=t;
                        int n_lens=lens.size();
                        int cnt=0;
                        for (list<note_len_t>::iterator x=lens.begin(); x!=lens.end(); x++)
                        {
                            if (heavyDebugMsg) cout << "\t\twhile regrouping: partial note with len="<<x->len<<", dots="<<x->dots<<endl;
                            cnt++;

                            bool tie;

                            if (cnt < n_lens)
                                tie=true;      // all notes except the last are always tied
                            else
                                tie=tied_note; // only the last respects tied_note

                            itemlist[tmppos].insert( FloItem(FloItem::NOTE, tmp.pos,x->len,x->dots, tie, tmp.begin_tick, tmp.source_part, tmp.source_event) );
                            tmppos+=calc_len(x->len,x->dots);
                            itemlist[tmppos].insert( FloItem(FloItem::NOTE_END, tmp.pos,0,0) );
                        }

                    }
                    //else if *it belongs to group2 and has not already its destination length
                    else if ((it->len<group2_len) && (it->len>group1_len))
                    {
                        if (heavyDebugMsg) cout << "\t\thas to be changed to fit into group 2" << endl;

                        FloItem tmp=*it;
                        curr_items.erase(it++);

                        int len_ticks_remaining=calc_len(tmp.len, tmp.dots)-group2_len_ticks;
                        bool tied_note=tmp.tied;


                        //shorten the current item to it's group's length
                        tmp.len=group2_len;
                        tmp.dots=0;
                        tmp.tied=true;
                        curr_items.insert(tmp);

                        //create items for the remaining lengths (and a note_END for the just created shortened note)
                        int t=it2->first+group2_len_ticks;

                        itemlist[t].insert( FloItem(FloItem::NOTE_END,tmp.pos,0,0) );

                        list<note_len_t> lens=parse_note_len(len_ticks_remaining,t-last_measure,emphasize_list,true,true);
                        unsigned tmppos=t;
                        int n_lens=lens.size();
                        int cnt=0;
                        for (list<note_len_t>::iterator x=lens.begin(); x!=lens.end(); x++)
                        {
                            if (heavyDebugMsg) cout << "\t\twhile regrouping: partial note with len="<<x->len<<", dots="<<x->dots<<endl;
                            cnt++;

                            bool tie;

                            if (cnt < n_lens)
                                tie=true;      // all notes except the last are always tied
                            else
                                tie=tied_note; // only the last respects tied_note

                            itemlist[tmppos].insert( FloItem(FloItem::NOTE,tmp.pos,x->len,x->dots, tie, tmp.begin_tick, tmp.source_part, tmp.source_event) );
                            tmppos+=calc_len(x->len,x->dots);
                            itemlist[tmppos].insert( FloItem(FloItem::NOTE_END,tmp.pos,0,0) );
                        }

                    }
                    else //nothing to do?
                    {
                        if (heavyDebugMsg) cout << "\t\tnothing to do" << endl;
                        it++;
                    }
                }
                else
                    it++;

            goto group_them_again; //do it again
        }

    }
}

//draw a pixmap centered
void ScoreCanvas::draw_pixmap(QPainter& p, int x, int y, const QPixmap& pm)
{
    if (heavyDebugMsg) cout << "drawing pixmap with size="<<pm.width()<<"/"<<pm.height()<<" at "<<x<<"/"<<y<<endl;
    p.drawPixmap(x-pm.width()/2,y-pm.height()/2,pm);
}

QRect bbox_center(int x, int y, const QSize& size)
{
    //why x-foo/2+foo? because due to integer divisions,
    // x-foo/2+foo can be smaller than x+foo/2!
    return QRect(x-size.width()/2,y-size.height()/2,size.width(),size.height());
}

QRect FloItem::bbox() const
{
    return bbox_center(x,y,pix->size());
}

void ScoreCanvas::draw_note_lines(QPainter& p, int y, bool reserve_akkolade_space)
{
    int xbegin = reserve_akkolade_space ? AKKOLADE_LEFTMARGIN+AKKOLADE_WIDTH+AKKOLADE_RIGHTMARGIN : 0;
    int xend=width();
    // FINDMICHJETZT y is uninitialized!

    p.setPen(mycolors[DEFAULT]);

    for (int i=0;i<5;i++)
        p.drawLine(xbegin, y + i*YLEN - 2*YLEN, xend, y + i*YLEN - 2*YLEN);
}


void staff_t::calc_item_pos()
{
    //this has to be KEY_C or KEY_C_B and nothing else,
    //because only with these two keys the next (initial)
    //key signature is properly drawn.
    MusECore::key_enum curr_key=MusECore::KEY_C;

    int pos_add=0;

    max_y_coord=0;
    min_y_coord=0;

    for (ScoreItemList::iterator it2=itemlist.begin(); it2!=itemlist.end(); it2++)
    {
        for (set<FloItem, floComp>::iterator it=it2->second.begin(); it!=it2->second.end();it++)
        {
            it->x=it2->first * parent->pixels_per_whole()/TICKS_PER_WHOLE  +pos_add;
            //if this changes, also change the line(s) with YLEN (but not all). don't change it.
            it->y=2*YLEN  -  (it->pos.height-2)*YLEN/2;

            if (it->type==FloItem::NOTE)
            {
                if (it->y > max_y_coord) max_y_coord=it->y;
                if (it->y < min_y_coord) min_y_coord=it->y;

                it->x+=parent->note_x_indent() + it->shift*NOTE_SHIFT;

                switch (it->len)
                {
                    case 0: it->pix=pix_whole; break;
                    case 1: it->pix=pix_half; break;
                    default: it->pix=pix_quarter; break;
                }

                it->stem_x=it->x;

                if (it->ausweich)
                {
                    if ((it->stem==UPWARDS) || (it->len==0))
                        it->x += it->pix->width()-1; //AUSWEICH_X
                    else
                        it->x -= it->pix->width()-1; //AUSWEICH_X
                }

                //if there's a tie, try to find the tie's destination and set is_tie_dest
                if (it->tied)
                {
                    set<FloItem, floComp>::iterator dest;
                    set<FloItem, floComp>& desttime = itemlist[it2->first+calc_len(it->len,it->dots)];
                    for (dest=desttime.begin(); dest!=desttime.end();dest++)
                        if ((dest->type==FloItem::NOTE) && (dest->pos==it->pos))
                        {
                            dest->is_tie_dest=true;
                            dest->tie_from_x=it->x;
                            break;
                        }

                    if (dest==desttime.end())
                        cerr << "ERROR: THIS SHOULD NEVER HAPPEN: did not find destination note for tie!" << endl;
                }
            }
            else if (it->type==FloItem::REST)
            {
                switch (it->len)
                {
                    case 0: it->pix=pix_r1; break;
                    case 1: it->pix=pix_r2; break;
                    case 2: it->pix=pix_r4; break;
                    case 3: it->pix=pix_r8; break;
                    case 4: it->pix=pix_r16; break;
                    case 5: it->pix=pix_r32; break;
                }

                it->x+=parent->note_x_indent() + (it->ausweich ? REST_AUSWEICH_X : 0); //AUSWEICH_X
            }
            else if (it->type==FloItem::BAR)
            {
                //nothing to do :)
            }
            else if (it->type==FloItem::TIME_SIG)
            {
                int add=calc_timesig_width(it->num, it->denom);
                pos_add+=add;
            }
            else if (it->type==FloItem::KEY_CHANGE)
            {
                MusECore::key_enum new_key=it->key;

                list<int> aufloes_list=calc_accidentials(curr_key, clef, new_key);
                list<int> new_acc_list=calc_accidentials(new_key, clef);

                int n_acc_drawn=aufloes_list.size() + new_acc_list.size();
                pos_add+=n_acc_drawn*KEYCHANGE_ACC_DIST+ KEYCHANGE_ACC_LEFTDIST+ KEYCHANGE_ACC_RIGHTDIST;

                curr_key=new_key;
            }
        }
    }

    max_y_coord+= (pix_quarter->height()/2 +NOTE_YDIST/2);
    min_y_coord-= (pix_quarter->height()/2 +NOTE_YDIST/2);
}

void ScoreCanvas::calc_pos_add_list()
{
    using MusEGlobal::sigmap;
    using MusECore::iSigEvent;


    pos_add_list.clear();

    //process time signatures
    for (iSigEvent it=MusEGlobal::sigmap.begin(); it!=MusEGlobal::sigmap.end(); it++)
        pos_add_list[it->second->tick]+=calc_timesig_width(it->second->sig.z, it->second->sig.n);


    //process key changes

    //this has to be KEY_C or KEY_C_B and nothing else,
    //because only with these two keys the next (initial)
    //key signature is properly calculated.
    MusECore::key_enum curr_key=MusECore::KEY_C;


    for (MusECore::iKeyEvent it=MusEGlobal::keymap.begin(); it!=MusEGlobal::keymap.end(); it++)
    {
        MusECore::key_enum new_key=it->second.key;
        list<int> aufloes_list=calc_accidentials(curr_key, VIOLIN, new_key); //clef argument is unnecessary
        list<int> new_acc_list=calc_accidentials(new_key, VIOLIN);           //in this case
        int n_acc_drawn=aufloes_list.size() + new_acc_list.size();
        pos_add_list[it->second.tick]+=n_acc_drawn*KEYCHANGE_ACC_DIST+ KEYCHANGE_ACC_LEFTDIST+ KEYCHANGE_ACC_RIGHTDIST;

        curr_key=new_key;
    }

    emit pos_add_changed();
}

void ScoreCanvas::draw_items(QPainter& p, int y, staff_t& staff, int x1, int x2)
{
    int from_tick, to_tick;
    ScoreItemList::iterator from_it, to_it;

    //drawing too much isn't bad. drawing too few is.

    from_tick=x_to_tick(x1);
    from_it=staff.itemlist.lower_bound(from_tick);
    //from_it now contains the first time which is fully drawn
    //however, the previous beat could still be relevant, when it's
    //partly drawn. so we decrement from_it
    if (from_it!=staff.itemlist.begin()) from_it--;

    //decrement until we're at a time with a bar
    //otherwise, drawing accidentials will be broken
    while (from_it!=staff.itemlist.begin() && from_it->second.find(FloItem(FloItem::BAR))==from_it->second.end())
        from_it--;


    to_tick=x_to_tick(x2);
    to_it=staff.itemlist.upper_bound(to_tick);
    //to_it now contains the first time which is not drawn at all any more
    //however, a tie from 1:04 to 2:01 is stored in 2:01, not in 1:04,
    //so for drawing ties, we need to increment to_it, so that the
    //"first time not drawn at all any more" is the last which gets
    //actually drawn.
    if (to_it!=staff.itemlist.end()) to_it++; //do one tick more than necessary. this will draw ties

    draw_items(p,y, staff, from_it, to_it);
}

void ScoreCanvas::draw_items(QPainter& p, int y, staff_t& staff)
{
    draw_items(p,y, staff,x_pos,x_pos+width()-x_left);
}

void ScoreCanvas::draw_items(QPainter& p, int y_offset, staff_t& staff, ScoreItemList::iterator from_it, ScoreItemList::iterator to_it)
{
    // init accidentials properly
    vorzeichen_t curr_accidential[7];
    vorzeichen_t default_accidential[7];
    MusECore::key_enum curr_key;

    curr_key=key_at_tick(from_it->first).key;
    list<int> new_acc_list=calc_accidentials(curr_key, staff.clef);
    vorzeichen_t new_accidential = is_sharp_key(curr_key) ? SHARP : B;

    for (int i=0;i<7;i++)
        curr_accidential[i]=default_accidential[i]=NONE;

    for (list<int>::iterator acc_it=new_acc_list.begin(); acc_it!=new_acc_list.end(); acc_it++)
        default_accidential[*acc_it % 7]=curr_accidential[*acc_it % 7]=new_accidential;



    for (ScoreItemList::iterator it2=from_it; it2!=to_it; it2++)
    {
        if (heavyDebugMsg) cout << "at t="<<it2->first << endl;

        int upstem_y1 = -1, upstem_y2=-1, upstem_x=-1, upflag=-1;
        int downstem_y1 = -1, downstem_y2=-1, downstem_x=-1, downflag=-1;

        for (set<FloItem, floComp>::iterator it=it2->second.begin(); it!=it2->second.end();it++)
        {
            if (it->type==FloItem::NOTE)
            {
                if (heavyDebugMsg)
                {
                    cout << "\tNOTE at line"<<it->pos.height<<" with acc.="<<it->pos.vorzeichen<<", len="<<pow(2,it->len);
                    for (int i=0;i<it->dots;i++) cout << ".";
                    cout << " , stem=";
                    if (it->stem==UPWARDS)
                        cout << "UPWARDS";
                    else
                        cout << "DOWNWARDS";

                    cout << " , shift="<<it->shift<<", ausweich="<<it->ausweich<<", ";
                    if (!it->tied)	cout << "un";
                    cout << "tied, is_tie_dest="<<it->is_tie_dest<<endl;
                }

                if (it->len!=0) //only for non-whole notes the stems are relevant!
                {
                    if (it->stem==UPWARDS)
                    {
                        if (upstem_y1 == -1)
                            upstem_y1=it->y;

                        upstem_y2=it->y;


                        if ((upflag!=-1) && (upflag!=it->len))
                            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: upflag != this->flag" << endl;
                        upflag=it->len;

                        if ((upstem_x!=-1) && (upstem_x!=it->stem_x ))
                            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: upstem_x != x_result" << endl;
                        upstem_x=it->stem_x;
                    }
                    else
                    {
                        if (downstem_y1 == -1)
                            downstem_y1=it->y;

                        downstem_y2=it->y;


                        if ((downflag!=-1) && (downflag!=it->len))
                            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: downflag != this->flag" << endl;
                        downflag=it->len;

                        if ((downstem_x!=-1) && (downstem_x!=it->stem_x))
                            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: downstem_x != x_result" << endl;
                        downstem_x=it->stem_x; //important: before the below calculation!
                    }
                }


                if (it->pos.height <= 0) //we need auxiliary lines on the bottom?
                {
                    p.setPen(mycolors[DEFAULT]);
                    for (int i=0; i>=it->pos.height; i-=2)
                        p.drawLine(it->x-it->pix->width()*AUX_LINE_LEN/2 -x_pos+x_left,y_offset + 2*YLEN  -  (i-2)*YLEN/2,it->x+it->pix->width()*AUX_LINE_LEN/2-x_pos+x_left,y_offset + 2*YLEN  -  (i-2)*YLEN/2);
                }
                else if (it->pos.height >= 12) //we need auxiliary lines on the top?
                {
                    p.setPen(mycolors[DEFAULT]);
                    for (int i=12; i<=it->pos.height; i+=2)
                        p.drawLine(it->x-it->pix->width()*AUX_LINE_LEN/2 -x_pos+x_left,y_offset + 2*YLEN  -  (i-2)*YLEN/2,it->x+it->pix->width()*AUX_LINE_LEN/2-x_pos+x_left,y_offset + 2*YLEN  -  (i-2)*YLEN/2);
                }

                it->is_active= ( (MusEGlobal::song->cpos() >= it->source_event->tick() + it->source_part->tick()) &&
                                       (MusEGlobal::song->cpos() < it->source_event->endTick() + it->source_part->tick()) );


                int color_index;
                switch (coloring_mode)
                {
                    case COLOR_MODE_BLACK:
                        color_index=DEFAULT;
                        break;

                    case COLOR_MODE_PART:
                        color_index=it->source_part->colorIndex();
                        break;

                    case COLOR_MODE_VELO:
                        color_index=VELO_PIXMAP_BEGIN + it->source_event->velo();
                        break;

                    default:
                        cerr << "ERROR: THIS CANNOT HAPPEN: coloring_mode (="<<coloring_mode<<") is invalid! defaulting to black." << endl;
                        color_index=DEFAULT;
                }

                if (it->source_event->selected())
                    color_index=SELECTED_PIXMAP;

                if (MusEGlobal::audio->isPlaying() && it->is_active)
                    color_index=HIGHLIGHTED_PIXMAP;


                draw_pixmap(p,it->x -x_pos+x_left,y_offset + it->y,it->pix[color_index]);

                //draw dots

                int x_dot=DOT_XBEGIN;
                int y_dot;
                if (modulo(it->pos.height, 2) == 0) //note is on a line?
                    y_dot=YLEN * 0.33; // actually 0.5, but that would be _exactly_ in the space
                else //note is between two lines?
                    y_dot=YLEN * 0.1;

                if (it->stem==DOWNWARDS)
                    y_dot=-y_dot;
                //else y_dot=y_dot;

                for (int i=0;i<it->dots;i++)
                {
                    draw_pixmap(p,it->x+x_dot -x_pos+x_left,y_offset + it->y+y_dot,pix_dot[color_index]);
                    x_dot+=DOT_XDIST;
                }



                //draw accidentials
                if (it->pos.vorzeichen != curr_accidential[modulo(it->pos.height,7)])
                {
                    QPixmap* acc_pix;
                    switch (it->pos.vorzeichen)
                    {
                        case NONE: acc_pix=pix_noacc; break;
                        case SHARP: acc_pix=pix_sharp; break;
                        case B: acc_pix=pix_b; break;
                        default: cerr << "ERROR: THIS CANNOT HAPPEN: it->pos.vorzeichen (="<<it->pos.vorzeichen<<") is invalid! defaulting to NONE." << endl;
                                 acc_pix=pix_noacc; break;
                    }
                    draw_pixmap(p,it->x-ACCIDENTIAL_DIST -x_pos+x_left,y_offset + it->y, acc_pix[color_index]);

                    curr_accidential[modulo(it->pos.height,7)]=it->pos.vorzeichen;
                }


                //if needed, draw tie
                if (it->is_tie_dest)
                {
                    if (heavyDebugMsg) cout << "drawing tie" << endl;
                    draw_tie(p,it->tie_from_x-x_pos+x_left,it->x -x_pos+x_left,y_offset + it->y, (it->len==0) ? true : (it->stem==DOWNWARDS) , mycolors[DEFAULT]);
                    // in english: "if it's a whole note, tie is upwards (true). if not, tie is upwards if
                    //              stem is downwards and vice versa"
                }
            }
            else if (it->type==FloItem::REST)
            {
                if (heavyDebugMsg)
                {
                    cout << "\tREST at line"<<it->pos.height<<" with len="<<pow(2,it->len);
                    for (int i=0;i<it->dots;i++) cout << ".";
                    cout << " , ausweich="<<it->ausweich<<endl;
                }

                draw_pixmap(p,it->x -x_pos+x_left,y_offset + it->y,*it->pix);


                //draw dots

                int x_dot=DOT_XBEGIN_REST;
                int y_dot;
                if (modulo(it->pos.height, 2) == 0) //rest is on a line?
                    y_dot=YLEN * 0.33; // actually 0.5, but that would be _exactly_ in the space
                else //note is between two lines?
                    y_dot=YLEN * 0.1;

                if (it->len!=0) // all rests except the whole are treated as
                    y_dot=-y_dot; // if they had a downwards stem

                for (int i=0;i<it->dots;i++)
                {
                    draw_pixmap(p,it->x+x_dot -x_pos+x_left,y_offset + it->y+y_dot,pix_dot[DEFAULT]);
                    x_dot+=DOT_XDIST;
                }
            }
            else if (it->type==FloItem::BAR)
            {
                if (heavyDebugMsg) cout << "\tBAR" << endl;

                p.setPen(mycolors[DEFAULT]);
                p.drawLine(it->x -x_pos+x_left,y_offset  -2*YLEN,it->x -x_pos+x_left,y_offset +2*YLEN);

                for (int i=0;i<7;i++)
                    curr_accidential[i]=default_accidential[i];
            }
            else if (it->type==FloItem::TIME_SIG)
            {
                if (heavyDebugMsg) cout << "\tTIME SIGNATURE: "<<it->num<<"/"<<it->denom<<endl;

                draw_timesig(p,  it->x - x_pos+x_left, y_offset, it->num, it->denom);
            }
            else if (it->type==FloItem::KEY_CHANGE)
            {
                MusECore::key_enum new_key=it->key;
                if (heavyDebugMsg) cout << "\tKEY CHANGE: from "<<curr_key<<" to "<<new_key<<endl;

                const bool is_minor = it->minor;
                const int str_y_coord = -4 * YLEN + 2 /* margin */;
                const QString kstr = MusECore::KeyEvent::keyToString(new_key, is_minor);
                const int x_1 = -x_pos + x_left;
                const int kstrx = x_1 + it->x + KEYCHANGE_ACC_LEFTDIST;
                // Don't bother drawing the string if it would begin just before the left edge
                //  because the preamble will already change to show the new key.
                if(!preamble_contains_timesig || kstrx >= x_1)
                  p.drawText(kstrx, y_offset + str_y_coord, kstr);

                list<int> aufloes_list=calc_accidentials(curr_key, staff.clef, new_key);
                list<int> new_acci_list=calc_accidentials(new_key, staff.clef);

                // cancel accidentials from curr_key
                draw_accidentials(p, it->x + KEYCHANGE_ACC_LEFTDIST - x_pos+x_left, y_offset, aufloes_list, pix_noacc[DEFAULT]);

                // draw all accidentials from new_key
                QPixmap* pix = is_sharp_key(new_key) ? &pix_sharp[DEFAULT] : &pix_b[DEFAULT];
                vorzeichen_t new_accident = is_sharp_key(new_key) ? SHARP : B;

                draw_accidentials(p, it->x + aufloes_list.size()*KEYCHANGE_ACC_DIST + KEYCHANGE_ACC_LEFTDIST - x_pos+x_left, y_offset, new_acci_list, *pix);

                for (int i=0;i<7;i++)
                    curr_accidential[i]=default_accidential[i]=NONE;

                for (list<int>::iterator acc_it=new_acci_list.begin(); acc_it != new_acci_list.end(); acc_it++)
                    default_accidential[*acc_it % 7]=curr_accidential[*acc_it % 7]=new_accident;

                curr_key=new_key;
            }
        }

        p.setPen(mycolors[DEFAULT]);
        //note: y1 is bottom, y2 is top!
        if (upstem_x!=-1)
        {
            upstem_x=upstem_x-pix_quarter[0].width()/2 +pix_quarter[0].width() -1;
            p.drawLine(upstem_x -x_pos+x_left, y_offset + upstem_y1, upstem_x -x_pos+x_left, y_offset + upstem_y2-STEM_LEN);

            if (upflag>=3) //if the note needs a flag
                p.drawPixmap(upstem_x -x_pos+x_left,y_offset + upstem_y2-STEM_LEN,pix_flag_up[upflag-3]);
        }
        if (downstem_x!=-1)
        {
            downstem_x=downstem_x-pix_quarter[0].width()/2;
            p.drawLine(downstem_x -x_pos+x_left, y_offset + downstem_y1+STEM_LEN, downstem_x -x_pos+x_left, y_offset + downstem_y2);

            if (downflag>=3) //if the note needs a flag
                p.drawPixmap(downstem_x -x_pos+x_left,y_offset + downstem_y1+STEM_LEN-pix_flag_down[downflag-3].height(),pix_flag_down[downflag-3]);
        }
    }
}

bool ScoreCanvas::need_redraw_for_hilighting()
{
    for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
        if (need_redraw_for_hilighting(it->itemlist)) return true;

    return false;
}

bool ScoreCanvas::need_redraw_for_hilighting(ScoreItemList& itemlist)
{
    return need_redraw_for_hilighting(itemlist, x_pos,x_pos+width()-x_left);
}

bool ScoreCanvas::need_redraw_for_hilighting(ScoreItemList& itemlist, int x1, int x2)
{
    int from_tick, to_tick;
    ScoreItemList::iterator from_it, to_it;

    from_tick=x_to_tick(x1);
    from_it=itemlist.lower_bound(from_tick);
    //from_it now contains the first time which is fully drawn
    //however, the previous beat could still be relevant, when it's
    //partly drawn. so we decrement from_it
    if (from_it!=itemlist.begin()) from_it--;

    to_tick=x_to_tick(x2);
    to_it=itemlist.upper_bound(to_tick);
    //to_it now contains the first time which is not drawn at all any more

    return need_redraw_for_hilighting(from_it, to_it);
}

bool ScoreCanvas::need_redraw_for_hilighting(ScoreItemList::iterator from_it, ScoreItemList::iterator to_it)
{
    //if we aren't playing, there will never be a need for redrawing due to highlighting things
    if (MusEGlobal::audio->isPlaying()==false)
        return false;

    for (ScoreItemList::iterator it2=from_it; it2!=to_it; it2++)
        for (set<FloItem, floComp>::iterator it=it2->second.begin(); it!=it2->second.end();it++)
            if (it->type==FloItem::NOTE)
            {
                bool is_active= ( (MusEGlobal::song->cpos() >= it->source_event->tick() + it->source_part->tick()) &&
                                  (MusEGlobal::song->cpos() < it->source_event->endTick() + it->source_part->tick()) );
                if (it->is_active != is_active)
                    return true;
            }

    return false;
}

int clef_height(clef_t clef)
{
    switch (clef) //CLEF_MARKER
    {
        case VIOLIN: return 4;
        case BASS: return 8;
        default:
            cerr << "ERROR: ILLEGAL FUNCTION CALL in clef_height()" << endl;
            return 6;
    }
}

#define TIMESIG_LEFTMARGIN 5
#define TIMESIG_RIGHTMARGIN 5
#define DIGIT_YDIST 9
#define DIGIT_WIDTH 12

#define CLEF_LEFTMARGIN 5
#define CLEF_RIGHTMARGIN 5

void ScoreCanvas::draw_preamble(QPainter& p, int y_offset, clef_t clef, bool reserve_akkolade_space, bool with_akkolade)
{
    int x_left_old=x_left;
    int tick=x_to_tick(x_pos);

    // maybe draw akkolade ----------------------------------------------
    if (reserve_akkolade_space)
    {
        if (with_akkolade) {
            p.setBrush(mycolors[DEFAULT]);
            draw_akkolade(p, AKKOLADE_LEFTMARGIN, y_offset+GRANDSTAFF_DISTANCE/2);
        }

        x_left= AKKOLADE_LEFTMARGIN + AKKOLADE_WIDTH + AKKOLADE_RIGHTMARGIN;
    }
    else
        x_left=0;


    // draw clef --------------------------------------------------------
    QPixmap* pix_clef= (clef==BASS) ? pix_clef_bass : pix_clef_violin;
    int y_coord=2*YLEN  -  ( clef_height(clef) -2)*YLEN/2;

    draw_pixmap(p,x_left + CLEF_LEFTMARGIN + pix_clef->width()/2,y_offset + y_coord,*pix_clef);

    x_left+= CLEF_LEFTMARGIN + pix_clef->width() + CLEF_RIGHTMARGIN;


    // draw accidentials ------------------------------------------------
    if (preamble_contains_keysig)
    {
        MusECore::KeyEvent key=key_at_tick(tick);
        QPixmap* pix_acc=is_sharp_key(key.key) ? &pix_sharp[DEFAULT] : &pix_b[DEFAULT];
        list<int> acclist=calc_accidentials(key.key,clef);

        const int str_y_coord = -4 * YLEN + 2 /* margin */;
        const QString kstr = key.keyString();
// Width() is obsolete. Qt >= 5.11 use horizontalAdvance().
#if QT_VERSION >= 0x050b00
        const int ksw = fontMetrics().horizontalAdvance(kstr);
#else
        const int ksw = fontMetrics().width(kstr);
#endif
        int kstrx = x_left - ksw / 2;
        if(kstrx < 0)
          kstrx = 0;
        p.drawText(kstrx, y_offset + str_y_coord, kstr);
        x_left+=KEYCHANGE_ACC_LEFTDIST;

        draw_accidentials(p,x_left, y_offset, acclist ,*pix_acc);

        x_left+=acclist.size()*KEYCHANGE_ACC_DIST + KEYCHANGE_ACC_RIGHTDIST;
    }


    // draw time signature ----------------------------------------------
    if (preamble_contains_timesig)
    {
        x_left+=TIMESIG_LEFTMARGIN;

        timesig_t timesig=timesig_at_tick(tick);

        draw_timesig(p, x_left, y_offset, timesig.num, timesig.denom);

        x_left+=calc_timesig_width(timesig.num, timesig.denom)+TIMESIG_RIGHTMARGIN;
    }

    // draw bar ---------------------------------------------------------
    p.setPen(mycolors[DEFAULT]);
    p.drawLine(x_left,y_offset  -2*YLEN,x_left,y_offset +2*YLEN);


    if (x_left_old!=x_left)
    {
        emit viewport_width_changed(viewport_width());
        emit preamble_width_changed(x_left);
    }
}


void ScoreCanvas::draw_timesig(QPainter& p, int x, int y_offset, int num, int denom)
{
    int num_width=calc_number_width(num);
    int denom_width=calc_number_width(denom);
    int width=((num_width > denom_width) ? num_width : denom_width);
    int num_indent=(width-num_width)/2 + TIMESIG_LEFTMARGIN;
    int denom_indent=(width-denom_width)/2 + TIMESIG_LEFTMARGIN;

    draw_number(p, x+num_indent, y_offset -DIGIT_YDIST, num);
    draw_number(p, x+denom_indent, y_offset +DIGIT_YDIST, denom);
}

int calc_timesig_width(int num, int denom)
{
    int num_width=calc_number_width(num);
    int denom_width=calc_number_width(denom);
    int width=((num_width > denom_width) ? num_width : denom_width);
    return width+TIMESIG_LEFTMARGIN+TIMESIG_RIGHTMARGIN;
}

int calc_number_width(int n)
{
    string str=IntToStr(n);
    return (str.length()*DIGIT_WIDTH);
}

void ScoreCanvas::draw_number(QPainter& p, int x, int y, int n)
{
    string str=IntToStr(n);
    int curr_x=x+DIGIT_WIDTH/2;

    for (size_t i=0;i<str.length(); i++)
    {
        draw_pixmap(p, curr_x, y, pix_num[str[i]-'0']);
        curr_x+=DIGIT_WIDTH;
    }
}


void ScoreCanvas::draw(QPainter& p, const QRect&, const QRegion&)
{
    if (debugMsg) cout <<"now in ScoreCanvas::draw"<<endl;


    p.setPen(mycolors[DEFAULT]);

    bool reserve_akkolade_space=false;
    for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
        if (it->type==GRAND_TOP)
        {
            reserve_akkolade_space=true;
            break;
        }

    for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
    {
        //TODO: maybe only draw visible staves?
        draw_note_lines(p,it->y_draw - y_pos, reserve_akkolade_space);
        draw_preamble(p,it->y_draw - y_pos, it->clef, reserve_akkolade_space, (it->type==GRAND_TOP));
        p.setClipRect(x_left+1,0,p.device()->width(),p.device()->height());
        draw_items(p,it->y_draw - y_pos, *it);
        p.setClipping(false);
    }

    if (have_lasso)
    {
        p.setPen(Qt::blue);
        p.setBrush(Qt::NoBrush);
        p.drawRect(lasso);
    }

    if (debugMsg) cout << "drawing done." << endl;
}


list<int> calc_accidentials(MusECore::key_enum key, clef_t clef, MusECore::key_enum next_key)
{
    list<int> result;

    int violin_sharp_pos[]={10,7,11,8,5,9,6}; //CLEF_MARKER
    int violin_b_pos[]={6,9,5,8,4,7,3};
    int bass_sharp_pos[]={8,5,9,6,3,7,4};
    int bass_b_pos[]={4,7,3,6,2,5,1};

 int* accidential_pos = nullptr;

    switch (clef)
    {
        case VIOLIN: accidential_pos = is_sharp_key(key) ? violin_sharp_pos : violin_b_pos; break;
        case BASS: accidential_pos = is_sharp_key(key) ? bass_sharp_pos : bass_b_pos; break;
    }

    int begin=0;

    if (is_sharp_key(key)==is_sharp_key(next_key)) //same kind of key (both b or both #)?
        begin=n_accidentials(next_key);
    else
        begin=0;


    int end=n_accidentials(key);

    for (int i=begin; i<end; i++)
        result.push_back(accidential_pos[i]);

    return result;
}




int ScoreCanvas::tick_to_x(int t)
{
    int x=t*pixels_per_whole()/TICKS_PER_WHOLE;

    for (std::map<int,int>::iterator it=pos_add_list.begin(); it!=pos_add_list.end() && it->first<=t; it++)
        x+=it->second;

    return x;
}

int ScoreCanvas::delta_tick_to_delta_x(int t)
{
    return t*pixels_per_whole()/TICKS_PER_WHOLE;
}

int ScoreCanvas::calc_posadd(int t)
{
    int result=0;

    for (std::map<int,int>::iterator it=pos_add_list.begin(); it!=pos_add_list.end() && it->first<t; it++)
        result+=it->second;

    return result;
}

//doesn't round mathematically correct, but i don't think this
//will be a problem, because a tick is pretty small
int ScoreCanvas::x_to_tick(int x)
{
    int t=TICKS_PER_WHOLE * x/pixels_per_whole();
    int min_t=0;

    for (std::map<int,int>::iterator it=pos_add_list.begin(); it!=pos_add_list.end() && it->first<t; it++)
    {
        min_t=it->first;
        x-=it->second;
        t=TICKS_PER_WHOLE * x/pixels_per_whole();
    }

    return t > min_t ? t : min_t;
}

MusECore::KeyEvent ScoreCanvas::key_at_tick(int t_)
{
    unsigned int t= (t_>=0) ? t_ : 0;

    return MusEGlobal::keymap.keyAtTick(t);
}

timesig_t ScoreCanvas::timesig_at_tick(int t_)
{
    timesig_t tmp;
    unsigned int t= (t_>=0) ? t_ : 0;

    MusEGlobal::sigmap.timesig(t, tmp.num, tmp.denom);

    return tmp;
}

int ScoreCanvas::height_to_pitch(int h, clef_t clef)
{
    int foo[]={0,2,4,5,7,9,11};

    switch(clef) //CLEF_MARKER
    {
        case VIOLIN:	return foo[modulo(h,7)] + ( divide_floor(h,7)*12 ) + 60;
        case BASS:		return foo[modulo((h-5),7)] + ( divide_floor(h-5,7)*12 ) + 48;
        default:
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: unknown clef in height_to_pitch" << endl;
            return 60;
    }
}

int ScoreCanvas::height_to_pitch(int h, clef_t clef, MusECore::key_enum key)
{
    int add=0;

    list<int> accs=calc_accidentials(key,clef);

    for (list<int>::iterator it=accs.begin(); it!=accs.end(); it++)
    {
        if (modulo(*it,7) == modulo(h,7))
        {
            add=is_sharp_key(key) ? 1 : -1;
            break;
        }
    }

    return height_to_pitch(h,clef)+add;
}

int ScoreCanvas::y_to_height(int y)
{
    return int(nearbyint(float(2*YLEN  - y)*2.0/YLEN))+2 ;
}

int ScoreCanvas::y_to_pitch(int y, int t, clef_t clef)
{
    return height_to_pitch(y_to_height(y), clef, key_at_tick(t).key);
}


#define DRAG_INIT_DISTANCE 5

void ScoreCanvas::mousePressEvent (QMouseEvent* event)
{
    keystate=event->modifiers();
    bool ctrl=keystate & Qt::ControlModifier;

    // always round DOWN.
    // because the "area" of a beat goes from "beat_begin" to "nextbeat_begin-1",
    // but notes are drawn in the middle of that area!

    list<staff_t>::iterator staff_it=staff_at_y(event->y() + y_pos);

    int y=event->y() + y_pos - staff_it->y_draw;
    int x=event->x()+x_pos-x_left;
    int tick=flo_quantize_floor(x_to_tick(x), quant_ticks());

    if (staff_it!=staves.end())
    {
        if (event->x() <= x_left) //clicked in the preamble?
        {
            if (event->button() == Qt::RightButton) //right-click?
            {
                current_staff=staff_it;
                staff_menu->popup(event->globalPos());
            }
            else if (event->button() == Qt::MiddleButton) //middle click?
            {
                remove_staff(staff_it);
            }
            else if (event->button() == Qt::LeftButton) //left click?
            {
                current_staff=staff_it;
                setCursor(Qt::SizeAllCursor);
                dragging_staff=true;
            }
        }
        else
        {
            ScoreItemList& itemlist=staff_it->itemlist;

            if (debugMsg) cout << "mousePressEvent at "<<x<<"/"<<y<<"; tick="<<tick<<endl;
            set<FloItem, floComp>::iterator set_it;
            for (set_it=itemlist[tick].begin(); set_it!=itemlist[tick].end(); set_it++)
                if (set_it->type==FloItem::NOTE)
                    if (set_it->bbox().contains(x,y))
                        break;

            if (set_it!=itemlist[tick].end()) //we found something?
            {
                mouse_down_pos=event->pos();
                mouse_operation=NO_OP;

                int t=tick;

                set<FloItem, floComp>::iterator found;
                do
                {
                    found=itemlist[t].find(FloItem(FloItem::NOTE, set_it->pos));
                    if (found == itemlist[t].end())
                    {
                        cerr << "ERROR: THIS SHOULD NEVER HAPPEN: could not find the note's tie-destination" << endl;
                        break;
                    }
                    else
                    {
                        t+=calc_len(found->len, found->dots);
                    }
                } while (found->tied);

                int total_begin=set_it->begin_tick;
                int total_end=t;

                int this_begin=tick;
                int this_end=this_begin+calc_len(set_it->len, set_it->dots);

                set_selected_part(set_it->source_part);

                //that's the only note corresponding to the event?
                if (this_begin==total_begin && this_end==total_end)
                {
                    if (x < set_it->x)
                        mouse_x_drag_operation=BEGIN;
                    else
                        mouse_x_drag_operation=LENGTH;
                }
                //that's NOT the only note?
                else
                {
                    if (this_begin==total_begin)
                        mouse_x_drag_operation=BEGIN;
                    else if (this_end==total_end)
                        mouse_x_drag_operation=LENGTH;
                    else
                        mouse_x_drag_operation=NO_OP;
                }

                if (debugMsg)
                    cout << "you clicked at a note with begin at "<<set_it->begin_tick<<" and end at "<<t<<endl
                             << "x-drag-operation will be "<<mouse_x_drag_operation<<endl
                             << "pointer to part is "<<set_it->source_part << endl;

                if (set_it->source_part == nullptr) cerr << "ERROR: THIS SHOULD NEVER HAPPEN: set_it->source_part is NULL!" << endl;



                clicked_event_ptr=set_it->source_event;
                dragged_event=*set_it->source_event;
                original_dragged_event=dragged_event.clone();
                set_dragged_event_part(set_it->source_part);

                if ((mouse_erases_notes) || (event->button()==Qt::MiddleButton)) //erase?
                {
                    MusEGlobal::song->applyOperation(UndoOp(UndoOp::DeleteEvent,dragged_event, dragged_event_part,  false, false));
                }
                else if (event->button()==Qt::LeftButton) //edit?
                {
                    setMouseTracking(true);
                    dragging=true;
                    drag_cursor_changed=false;
                }
            }
            else //we found nothing?
                if (event->button()==Qt::LeftButton)
                {
                    if (mouse_inserts_notes)
                    {
                        const MusECore::Part* curr_part = nullptr;
                        set<const MusECore::Part*> possible_dests=staff_it->parts_at_tick(tick);

                        if (!possible_dests.empty())
                        {
                            if (possible_dests.size()==1)
                                curr_part=*possible_dests.begin();
                            else
                            {
                                if (possible_dests.find(selected_part)!=possible_dests.end())
                                    curr_part=selected_part;
                                else
                                    QMessageBox::information(this, tr("Ambiguous part"), tr("There are two or more possible parts you could add the note to, but none matches the selected part. Please select the destination part by clicking on any note belonging to it and try again, or add a new stave containing only the destination part."));
                            }
                        }
                        else
                            QMessageBox::information(this, tr("No part"), tr("There are no parts you could add the note to."));

                        if (curr_part!=nullptr)
                        {
                            signed int relative_tick=(signed) tick - curr_part->tick();
                            if (relative_tick<0)
                                cerr << "ERROR: THIS SHOULD NEVER HAPPEN: relative_tick is negative!" << endl;

                            if (!ctrl)
                                deselect_all();

                            MusECore::Event newevent(MusECore::Note);
                            newevent.setPitch(y_to_pitch(y,tick, staff_it->clef));
                            newevent.setVelo(note_velo);
                            newevent.setVeloOff(note_velo_off);
                            newevent.setTick(relative_tick);
                            newevent.setLenTick((new_len>0)?new_len:last_len);
                            newevent.setSelected(true); // No need to select clones, AddEvent operation below will take care of that.

                            if (flo_quantize(newevent.lenTick(), quant_ticks()) <= 0)
                            {
                                newevent.setLenTick(quant_ticks());
                                if (debugMsg) cout << "inserted note's length would be invisible after quantisation (too short)." << endl <<
                                                                            "       setting it to " << newevent.lenTick() << endl;
                            }

                            if (newevent.endTick() > curr_part->lenTick())
                            {
                                if (debugMsg) cout << "clipping inserted note from len="<<newevent.endTick()<<" to len="<<(curr_part->lenTick() - newevent.tick())<<endl;
                                newevent.setLenTick(curr_part->lenTick() - newevent.tick());
                            }

                            MusEGlobal::song->applyOperation(UndoOp(UndoOp::AddEvent, newevent, curr_part,false, false));

                            set_dragged_event_part(curr_part);
                            dragged_event=newevent;
                            original_dragged_event=dragged_event.clone();

                            mouse_down_pos=event->pos();
                            mouse_operation=NO_OP;
                            mouse_x_drag_operation=LENGTH;

                            fully_recalculate();

                            setMouseTracking(true);
                            dragging=true;
                            inserting=true;
                            drag_cursor_changed=true;
                            setCursor(Qt::SizeAllCursor);

                            MusEGlobal::song->update(SC_SELECTION);
                        }
                    }
                    else // !mouse_inserts_notes. open a lasso
                    {
                        have_lasso=true;
                        lasso_start=event->pos();
                        lasso=QRect(lasso_start, lasso_start);

                        setMouseTracking(true);
                    }

                } else if (event->button() == Qt::RightButton)
                    callContextMenu();
        }

    } else if (event->button() == Qt::RightButton)
        callContextMenu();
}

void ScoreCanvas::mouseReleaseEvent (QMouseEvent* event)
{
    keystate=event->modifiers();
    bool ctrl=keystate & Qt::ControlModifier;

    if (dragging && event->button()==Qt::LeftButton)
    {
        if (mouse_operation==LENGTH)
        {
            if (flo_quantize(dragged_event.lenTick(), quant_ticks()) <= 0)
            {
                if (debugMsg) cout << "new length <= 0, erasing item" << endl;
                if (undo_started) MusEGlobal::song->undo();
                MusEGlobal::song->applyOperation(UndoOp(UndoOp::DeleteEvent,dragged_event, dragged_event_part, false, false));
            }
            else
            {
                last_len=flo_quantize(dragged_event.lenTick(), quant_ticks());
            }
        }


        if (mouse_operation==NO_OP && !inserting)
        {
            if (event->button()==Qt::LeftButton)
                if (!ctrl)
                    deselect_all();

            MusEGlobal::song->applyOperation(
                UndoOp(UndoOp::SelectEvent, *clicked_event_ptr, selected_part,
                       !clicked_event_ptr->selected(), clicked_event_ptr->selected()),
              MusECore::Song::OperationExecuteUpdate);
        }

        setMouseTracking(false);
        setCursor(active_tool_cursor);
        inserting=false;
        dragging=false;
        drag_cursor_changed=false;
        undo_started=false;

        x_scroll_speed=0; x_scroll_pos=0;
    }

    if (dragging_staff && event->button()==Qt::LeftButton)
    {
        int y=event->y()+y_pos;
        list<staff_t>::iterator mouse_staff=staff_at_y(y);

        if (mouse_staff!=staves.end())
        {
            if ( ((mouse_staff->type==NORMAL) && (y >= mouse_staff->y_draw-2*YLEN) && (y <= mouse_staff->y_draw+2*YLEN)) ||
                 ((mouse_staff->type==GRAND_TOP) && (y >= mouse_staff->y_draw-2*YLEN)) ||
                 ((mouse_staff->type==GRAND_BOTTOM) && (y <= mouse_staff->y_draw+2*YLEN)) )
                merge_staves(mouse_staff, current_staff);
            else if (y >= mouse_staff->y_draw+2*YLEN) //will never happen when mouse_staff->type==GRAND_TOP
                move_staff_below(mouse_staff, current_staff);
            else if (y <= mouse_staff->y_draw-2*YLEN) //will never happen when mouse_staff->type==GRAND_BOTTOM
                move_staff_above(mouse_staff, current_staff);
        }

        dragging_staff=false;
        setCursor(active_tool_cursor);

        y_scroll_speed=0; y_scroll_pos=0;
    }

    if (have_lasso && event->button()==Qt::LeftButton)
    {
        if (!ctrl)
            deselect_all();

        set<const MusECore::Event*> already_processed;

        for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
            it->apply_lasso(lasso.translated(x_pos-x_left, y_pos - it->y_draw), already_processed);

        MusEGlobal::song->update(SC_SELECTION);

        have_lasso=false;
        redraw();
    }
}

#define PITCH_DELTA 5


void ScoreCanvas::mouseMoveEvent (QMouseEvent* event)
{
    keystate=event->modifiers();
    bool ctrl=keystate & Qt::ControlModifier;

    if (dragging)
    {
        int dx=event->x()-mouse_down_pos.x();
        int dy=event->y()-mouse_down_pos.y();

        int x=event->x()+x_pos-x_left;

        int tick=flo_quantize_floor(x_to_tick(x), quant_ticks());


        if ((drag_cursor_changed==false) && ((dx!=0) || (dy!=0)))
        {
            setCursor(Qt::SizeAllCursor);
            drag_cursor_changed=true;
        }

        if (mouse_operation==NO_OP)
        {
            if ((abs(dx)>DRAG_INIT_DISTANCE) && (mouse_x_drag_operation!=NO_OP))
            {
                if (debugMsg) cout << "mouse-operation is now "<<mouse_x_drag_operation<<endl;
                mouse_operation=mouse_x_drag_operation;
                setCursor(Qt::SizeHorCursor);
            }
            else if (abs(dy)>DRAG_INIT_DISTANCE)
            {
                if (debugMsg) cout << "mouse-operation is now PITCH" << endl;
                mouse_operation=PITCH;
                setCursor(Qt::SizeVerCursor);
            }

            if (mouse_operation!=NO_OP)
            {
                if (!inserting && clicked_event_ptr->selected()==false)
                {
                    if (!ctrl)
                        deselect_all();

                    MusEGlobal::song->applyOperation(
                        UndoOp(UndoOp::SelectEvent, *clicked_event_ptr, selected_part,
                               true, clicked_event_ptr->selected()),
                      MusECore::Song::OperationExecuteUpdate);
                }

                old_pitch=-1;
                old_dest_tick=INT_MAX;
                old_len=-1;
            }
        }

        int new_pitch;

        switch (mouse_operation)
        {
            case NO_OP:
                break;

            case PITCH:
                if (heavyDebugMsg) cout << "trying to change pitch, delta="<<-nearbyint((float)dy/PITCH_DELTA)<<endl;
                new_pitch=original_dragged_event.pitch() - nearbyint((float)dy/PITCH_DELTA);

                if (new_pitch < 0) new_pitch=0;
                if (new_pitch > 127) new_pitch=127;

                if (new_pitch != old_pitch)
                {
                    if (debugMsg) cout << "changing pitch, delta="<<new_pitch-original_dragged_event.pitch()<<endl;
                    if (undo_started) MusEGlobal::song->undo();
                    undo_started=transpose_notes(part_to_set(dragged_event_part),1, new_pitch-original_dragged_event.pitch());
                    old_pitch=new_pitch;
                }

                break;

            case BEGIN:
                {
                    signed relative_tick=tick-signed(dragged_event_part->tick());
                    unsigned dest_tick;

                    if (relative_tick >= 0)
                        dest_tick=relative_tick;
                    else
                    {
                        dest_tick=0;
                        if (debugMsg) cout << "not moving note before begin of part; setting it directly to the begin" << endl;
                    }

                    if (dest_tick != old_dest_tick)
                    {
                        if (undo_started) MusEGlobal::song->undo(); //FINDMICH EXTEND
                        undo_started=move_notes(part_to_set(dragged_event_part),1, (signed)dest_tick-original_dragged_event.tick());
                        old_dest_tick=dest_tick;
                    }
                }

                break;

            case LENGTH:
                tick+=quant_ticks();
                if (dragged_event.tick()+old_len + dragged_event_part->tick() != unsigned(tick))
                {
                    MusECore::Event tmp=dragged_event.clone();
                    signed relative_tick=tick-signed(dragged_event_part->tick());
                    signed newLen= relative_tick - dragged_event.tick();

                    if (newLen >= 0)
                        tmp.setLenTick(newLen);
                    else
                    {
                        tmp.setLenTick(0);
                        if (debugMsg) cout << "not setting len to a negative value. using 0 instead" << endl;
                    }

                    unsigned newpartlen=dragged_event_part->lenTick();
                    if (tmp.endTick() > dragged_event_part->lenTick())
                    {
                        if (dragged_event_part->hasHiddenEvents() & MusECore::Part::RightEventsHidden) // do not allow autoexpand
                        {
                            tmp.setLenTick(dragged_event_part->lenTick() - tmp.tick());
                            if (debugMsg) cout << "resized note would exceed its part; limiting length to " << tmp.lenTick() << endl;
                        }
                        else
                        {
                            newpartlen=tmp.endTick();
                            if (debugMsg) cout << "resized note would exceeds its part; expanding the part..." << endl;
                        }
                    }

                    if (undo_started) MusEGlobal::song->undo();
                    MusECore::Undo operations;
                    operations.push_back(MusECore::UndoOp(MusECore::UndoOp::ModifyEvent, tmp, dragged_event, dragged_event_part, false, false));
                    if (newpartlen != dragged_event_part->lenTick())
                        schedule_resize_all_same_len_clone_parts(dragged_event_part, newpartlen, operations);
                    undo_started=MusEGlobal::song->applyOperationGroup(operations);

                    old_len=newLen;
                }

                break;
        }


        if ((mouse_operation==LENGTH) || (mouse_operation==BEGIN)) //x-scrolling enabled?
        {
            int win_x=event->x();

            if (win_x < x_left + SCROLL_MARGIN)
            {
                x_scroll_speed=(win_x - (x_left + SCROLL_MARGIN)) * SCROLL_SPEED;
                if (x_scroll_speed < -SCROLL_SPEED_MAX) x_scroll_speed=-SCROLL_SPEED_MAX;
            }
            else if (win_x > width() - SCROLL_MARGIN)
            {
                x_scroll_speed=(win_x - (width() - SCROLL_MARGIN)) * SCROLL_SPEED;
                if (x_scroll_speed > SCROLL_SPEED_MAX) x_scroll_speed=SCROLL_SPEED_MAX;
            }
            else
                x_scroll_speed=0;
        }
        else
        {
            x_scroll_speed=0;
        }
    }

    if (dragging_staff) //y-scrolling enabled?
    {
        int win_y=event->y();

        if (win_y < SCROLL_MARGIN)
        {
            y_scroll_speed=(win_y - SCROLL_MARGIN) * SCROLL_SPEED;
            if (y_scroll_speed < -SCROLL_SPEED_MAX) y_scroll_speed=-SCROLL_SPEED_MAX;
        }
        else if (win_y > height() - SCROLL_MARGIN)
        {
            y_scroll_speed=(win_y - (height() - SCROLL_MARGIN)) * SCROLL_SPEED;
            if (y_scroll_speed > SCROLL_SPEED_MAX) y_scroll_speed=SCROLL_SPEED_MAX;
        }
        else
            y_scroll_speed=0;
    }
    else
    {
        y_scroll_speed=0;
    }

    if (have_lasso)
    {
        lasso=QRect(lasso_start, event->pos()).normalized();
        redraw();
    }
}

void ScoreCanvas::heartbeat_timer_event()
{
    if (x_scroll_speed)
    {
        int old_xpos=x_pos;

        x_scroll_pos+=x_scroll_speed*MusEGlobal::heartBeatTimer->interval()/1000.0;
        int tmp=int(x_scroll_pos);
        if (tmp!=0)
        x_pos+=tmp;
        x_scroll_pos-=tmp;

        if (x_pos<0) x_pos=0;
        if (x_pos>canvas_width()) x_pos=canvas_width();

        if (old_xpos!=x_pos) emit xscroll_changed(x_pos);
    }

    if (y_scroll_speed)
    {
        int old_ypos=y_pos;

        y_scroll_pos+=y_scroll_speed*MusEGlobal::heartBeatTimer->interval()/1000.0;
        int tmp=int(y_scroll_pos);
        if (tmp!=0)
        y_pos+=tmp;
        y_scroll_pos-=tmp;

        if (y_pos<0) y_pos=0;
        if (y_pos>canvas_height()) y_pos=canvas_height();

        if (old_ypos!=y_pos) emit yscroll_changed(y_pos);
    }
}

void ScoreCanvas::x_scroll_event(int x)
{
    if (debugMsg) cout << "SCROLL EVENT: x="<<x<<endl;
    x_pos=x;
    redraw();
}

void ScoreCanvas::y_scroll_event(int y)
{
    if (debugMsg) cout << "SCROLL EVENT: y="<<y<<endl;
    y_pos=y;
    redraw();
}


//if force is true, it will always happen something
//if force is false, it will only happen something, if
//tick isn't visible at all. if it's visible, but not at
//the position goto would set it to, nothing happens
void ScoreCanvas::goto_tick(int tick, bool force)
{
    if (!force)
    {
        if (tick < x_to_tick(x_pos))
        {
            x_pos=tick_to_x(tick) - x_left;
            if (x_pos<0) x_pos=0;
            if (x_pos>canvas_width()) x_pos=canvas_width();

            emit xscroll_changed(x_pos);
        }
        else if (tick > x_to_tick(x_pos+viewport_width()*PAGESTEP))
        {
            x_pos=tick_to_x(tick);
            if (x_pos<0) x_pos=0;
            if (x_pos>canvas_width()) x_pos=canvas_width();

            emit xscroll_changed(x_pos);
        }
    }
    else
    {
        x_pos=tick_to_x(tick)-viewport_width()/2;
        if (x_pos<0) x_pos=0;
        if (x_pos>canvas_width()) x_pos=canvas_width();

        emit xscroll_changed(x_pos);
    }
}

//---------------------------------------------------------
//   resizeEvent
//---------------------------------------------------------

void ScoreCanvas::resizeEvent(QResizeEvent* ev)
{
    QWidget::resizeEvent(ev);

    emit viewport_width_changed( viewport_width()  );
    emit viewport_height_changed( viewport_height()  );
}

void ScoreCanvas::pos_changed(int index, unsigned tick, bool scroll)
{
    if (index==0)
    {
        if (scroll) //potential need to scroll?
        {
            switch (MusEGlobal::song->follow())
            {
                case MusECore::Song::NO: break;
                case MusECore::Song::JUMP:  goto_tick(tick,false);  break;
                case MusECore::Song::CONTINUOUS:  goto_tick(tick,true);  break;
            }
        }

        if (need_redraw_for_hilighting())
            redraw();
    }
}


void ScoreCanvas::recalc_staff_pos()
{
    int y=0;

    for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
    {
        it->y_top=y;
        switch (it->type)
        {
            case NORMAL:
                it->y_draw = it->y_top + STAFF_DISTANCE/2;
                if (it->min_y_coord < -STAFF_DISTANCE/2)
                    it->y_draw+= (-it->min_y_coord - STAFF_DISTANCE/2);

                it->y_bottom = it->y_draw + STAFF_DISTANCE/2;
                if (it->max_y_coord > STAFF_DISTANCE/2)
                    it->y_bottom+= (it->max_y_coord - STAFF_DISTANCE/2);

                break;

            case GRAND_TOP:
                it->y_draw = it->y_top + STAFF_DISTANCE/2;
                if (it->min_y_coord < -STAFF_DISTANCE/2)
                    it->y_draw+= (-it->min_y_coord - STAFF_DISTANCE/2);

                it->y_bottom = it->y_draw + GRANDSTAFF_DISTANCE/2;

                break;

            case GRAND_BOTTOM:
                it->y_draw = it->y_top + GRANDSTAFF_DISTANCE/2;

                it->y_bottom = it->y_draw + STAFF_DISTANCE/2;
                if (it->max_y_coord > STAFF_DISTANCE/2)
                    it->y_bottom+= (it->max_y_coord - STAFF_DISTANCE/2);

                break;

            default:
                cerr << "ERROR: THIS SHOULD NEVER HAPPEN: invalid staff type!" << endl;
        }
        y=it->y_bottom;
    }

    emit canvas_height_changed( canvas_height() );
}

list<staff_t>::iterator ScoreCanvas::staff_at_y(int y)
{
    for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
        if ((y >= it->y_top) && (y < it->y_bottom))
            return it;

    return staves.end();
}

void ScoreCanvas::play_changed(bool)
{
    redraw();
}

void ScoreCanvas::config_changed()
{
    if (MusEGlobal::config.canvasBgPixmap.isEmpty()) {
          setBg(MusEGlobal::config.midiCanvasBg);
          setBg(QPixmap());
    }
    else {
          setBg(QPixmap(MusEGlobal::config.canvasBgPixmap));
    }
    redraw();
}

void ScoreCanvas::set_tool(int tool)
{
    switch (tool)
    {
    case MusEGui::PointerTool:
        setCursor(QCursor(Qt::ArrowCursor));
        mouse_erases_notes=false;
        mouse_inserts_notes=false;
        break;
    case MusEGui::RubberTool:
        setCursor(*deleteCursor);
        mouse_erases_notes=true;
        mouse_inserts_notes=false;
        break;
    case MusEGui::PencilTool:
        setCursor(*pencilCursor);
        mouse_erases_notes=false;
        mouse_inserts_notes=true;
        break;
    default:
        cerr << "ERROR: THIS SHOULD NEVER HAPPEN: set_tool called with unknown tool ("<<tool<<")"<<endl;
    }

    active_tool_cursor = cursor();
    active_tool = tool;
}

void ScoreCanvas::menu_command(int cmd)
{
    switch (cmd)
    {
        case CMD_COLOR_BLACK:  coloring_mode_init=coloring_mode=COLOR_MODE_BLACK; redraw(); break;
        case CMD_COLOR_PART:   coloring_mode_init=coloring_mode=COLOR_MODE_PART;  redraw(); break;
        case CMD_COLOR_VELO:   coloring_mode_init=coloring_mode=COLOR_MODE_VELO;  redraw(); break;
        case CMD_NOTELEN_1:    new_len_init= 1; new_len=TICKS_PER_WHOLE/ 1; break;
        case CMD_NOTELEN_2:    new_len_init= 2; new_len=TICKS_PER_WHOLE/ 2; break;
        case CMD_NOTELEN_4:    new_len_init= 4; new_len=TICKS_PER_WHOLE/ 4; break;
        case CMD_NOTELEN_8:    new_len_init= 8; new_len=TICKS_PER_WHOLE/ 8; break;
        case CMD_NOTELEN_16:   new_len_init=16; new_len=TICKS_PER_WHOLE/16; break;
        case CMD_NOTELEN_32:   new_len_init=32; new_len=TICKS_PER_WHOLE/32; break;
        case CMD_NOTELEN_LAST: new_len_init= 0; new_len=-1; break;

        default:
            cerr << "ERROR: ILLEGAL FUNCTION CALL: ScoreCanvas::menu_command called with unknown command ("<<cmd<<")"<<endl;
    }
}

void ScoreCanvas::preamble_keysig_slot(bool state)
{
    preamble_contains_keysig=state;
    preamble_contains_keysig_init=state;
    redraw();
}
void ScoreCanvas::preamble_timesig_slot(bool state)
{
    preamble_contains_timesig=state;
    preamble_contains_timesig_init=state;
    redraw();
}

void ScoreCanvas::set_quant(int val)
{
    if ((val>=0) && (val<5))
    {
        int oldLen=quant_len();

        _quant_power2=val+1;
        _quant_power2_init=_quant_power2;

        set_pixels_per_whole(pixels_per_whole() * quant_len() / oldLen );

        fully_recalculate();
    }
    else
    {
        cerr << "ERROR: ILLEGAL FUNCTION CALL: set_quant called with invalid value of "<<val<<endl;
    }
}

void ScoreCanvas::set_pixels_per_whole(int val)
{
    if (debugMsg) cout << "setting px per whole to " << val << endl;

 int tick = 0;
    int old_xpos=x_pos;
    if (x_pos!=0) tick=x_to_tick(x_pos);
    // the above saves us from a division by zero when initializing
    // ScoreCanvas; then x_pos will be 0 and x_to_tick (causing the
    // division by zero) won't be called. also, when x_pos=0, and the
    // above would not be done, after that function, x_pos will be
    // not zero, but at the position of the first note (which isn't
    // zero!)

    _pixels_per_whole=val;
    _pixels_per_whole_init=val;

    for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
        it->calc_item_pos();

    emit pixels_per_whole_changed(val);

    if (old_xpos!=0)
    {
        x_pos=tick_to_x(tick);
        if (debugMsg) cout << "x_pos was not zero, readjusting to " << x_pos << endl;
        emit xscroll_changed(x_pos);
    }

    redraw();
}

void ScoreCanvas::cleanup_staves()
{
    for (list<staff_t>::iterator it=staves.begin(); it!=staves.end();)
    {
        if (it->parts.empty())
            staves.erase(it++);
        else
            it++;
    }

    maybe_close_if_empty();
}

void ScoreCanvas::maybe_close_if_empty()
{
    if (staves.empty())
    {
        if (!parent->close())
            cerr << "ERROR: THIS SHOULD NEVER HAPPEN: tried to close, but event hasn't been accepted!" << endl;
    }
}

void ScoreCanvas::set_velo(int velo)
{
    note_velo=velo;
    note_velo_init=velo;

    if (parent->get_apply_velo())
    {
        MusECore::TagEventList tag_list;
        tagItems(&tag_list, MusECore::EventTagOptionsStruct(MusECore::TagSelected | MusECore::TagAllParts));
        MusECore::modify_velocity_items(&tag_list, 0, velo);
    }
}

void ScoreCanvas::set_velo_off(int velo)
{
    note_velo_off=velo;
    note_velo_off_init=velo;

    if (parent->get_apply_velo())
    {
        MusECore::TagEventList tag_list;
        tagItems(&tag_list, MusECore::EventTagOptionsStruct(MusECore::TagSelected | MusECore::TagAllParts));
        MusECore::modify_off_velocity_items(&tag_list, 0, velo);
    }
}

void ScoreCanvas::deselect_all()
{
    set<const MusECore::Part*> all_parts=get_all_parts();

    Undo operations;
    operations.combobreaker=true;

    for (set<const MusECore::Part*>::iterator part=all_parts.begin(); part!=all_parts.end(); part++)
        for (MusECore::ciEvent event=(*part)->events().begin(); event!=(*part)->events().end(); event++)
            operations.push_back(UndoOp(UndoOp::SelectEvent, event->second, *part, false, event->second.selected()));

    MusEGlobal::song->applyOperationGroup(operations, MusECore::Song::OperationExecuteUpdate);
}

bool staff_t::cleanup_parts()
{
    bool did_something=false;

    for (set<const MusECore::Part*>::iterator it=parts.begin(); it!=parts.end();)
    {
        bool valid=false;

        for (MusECore::iTrack track=MusEGlobal::song->tracks()->begin(); track!=MusEGlobal::song->tracks()->end(); track++)
            if ((*track)->type() == MusECore::Track::MIDI)
            {
                MusECore::PartList* pl=(*track)->parts();
                for (MusECore::iPart part=pl->begin(); part!=pl->end(); part++)
                    if (*it == part->second)
                    {
                        valid=true;
                        goto get_out_here2;
                    }
            }

        get_out_here2:
        if (!valid)
        {
            parts.erase(it++);

            did_something=true;
        }
        else
            it++;
    }

    if (did_something) update_part_indices();
    return did_something;
}

set<const MusECore::Part*> staff_t::parts_at_tick(unsigned tick)
{
    set<const MusECore::Part*> result;

    for (set<const MusECore::Part*>::iterator it=parts.begin(); it!=parts.end(); it++)
        if ((tick >= (*it)->tick()) && (tick<=(*it)->endTick()))
            result.insert(*it);

    return result;
}

void staff_t::apply_lasso(QRect rect, set<const MusECore::Event*>& already_processed)
{
    Undo operations;
    for (ScoreItemList::iterator it=itemlist.begin(); it!=itemlist.end(); it++)
        for (set<FloItem>::iterator it2=it->second.begin(); it2!=it->second.end(); it2++)
            if (it2->type==FloItem::NOTE)
            {
                if (rect.contains(it2->x, it2->y))
                    if (already_processed.find(it2->source_event)==already_processed.end())
                    {
                        operations.push_back(UndoOp(UndoOp::SelectEvent,*it2->source_event,it2->source_part,
                                                       !it2->source_event->selected(),it2->source_event->selected()));
                        // Here we have a choice of whether to allow undoing of selections.
                        // Disabled for now, it's too tedious in use. Possibly make the choice user settable.
//                         operations.push_back(UndoOp(UndoOp::SelectEvent,*it2->source_event,it2->source_part,
//                                                     !it2->source_event->selected(),it2->source_event->selected(), false));
                        already_processed.insert(it2->source_event);
                    }
            }
    MusEGlobal::song->applyOperationGroup(operations, MusECore::Song::OperationExecuteUpdate);
}

void ScoreCanvas::set_steprec(bool flag)
{
    srec=flag;
}

void ScoreCanvas::midi_note(int pitch, int velo)
{
    // Ignore invalid pitches such as rest notes.
    if(pitch >= 0)
    {
      if (velo)
          held_notes[pitch]=true;
      else
          held_notes[pitch]=false;
    }

    if ( srec && selected_part && !MusEGlobal::audio->isPlaying() && velo )
        steprec->record(
          selected_part,
          pitch,
          quant_ticks(),
          quant_ticks(),
          velo,
          MusEGlobal::globalKeyState&Qt::ControlModifier,
          MusEGlobal::globalKeyState&Qt::ShiftModifier);
}



void ScoreCanvas::update_parts()
{
    if (selected_part!=nullptr) //if it's null, let it be null
        selected_part=MusECore::partFromSerialNumber(selected_part_index);

    if (dragged_event_part!=nullptr) //same thing here
        dragged_event_part=MusECore::partFromSerialNumber(dragged_event_part_index);

    for (list<staff_t>::iterator it=staves.begin(); it!=staves.end(); it++)
        it->update_parts();
}

void staff_t::update_parts()
{
    parts.clear();

    for (set<QUuid>::iterator it=part_indices.begin(); it!=part_indices.end(); it++)
        parts.insert(MusECore::partFromSerialNumber(*it));
}

void staff_t::update_part_indices()
{
    part_indices.clear();

    for (set<const MusECore::Part*>::iterator it=parts.begin(); it!=parts.end(); it++)
        part_indices.insert((*it)->uuid());
}


void ScoreEdit::keyPressEvent(QKeyEvent* event)
{
    int key = event->key();

    if (key == Qt::Key_Escape)
    {
        close();
        return;
    }
    else if (key == shortcuts[SHRT_TOOL_POINTER].key)
    {
        edit_tools->set(MusEGui::PointerTool);
        return;
    }
    else if (key == shortcuts[SHRT_TOOL_PENCIL].key)
    {
        edit_tools->set(MusEGui::PencilTool);
        return;
    }
    else if (key == shortcuts[SHRT_TOOL_RUBBER].key)
    {
        edit_tools->set(MusEGui::RubberTool);
        return;
    }
    else if (key == shortcuts[SHRT_EVENT_COLOR].key)
    {
        if (score_canvas->coloring_mode_init == score_canvas->COLOR_MODE_BLACK)
            color_velo_action->trigger();
        else if (score_canvas->coloring_mode_init == score_canvas->COLOR_MODE_VELO)
            color_part_action->trigger();
        else
            color_black_action->trigger();
        return;
    }
    else //Default:
    {
        event->ignore();
        return;
    }
}


void ScoreCanvas::add_new_parts(const std::map< const MusECore::Part*, std::set<const MusECore::Part*> >& param)
{
    for (list<staff_t>::iterator staff=staves.begin(); staff!=staves.end(); staff++)
    {
        for (std::map< const MusECore::Part*, set<const MusECore::Part*> >::const_iterator it = param.begin(); it!=param.end(); it++)
            if (staff->parts.find(it->first)!=staff->parts.end())
                staff->parts.insert(it->second.begin(), it->second.end());

        //staff->cleanup_parts(); // don't cleanup here, because at this point, the parts might exist only
                                  // in the operation group. cleanup could remove them immediately
        staff->update_part_indices();
    }

    fully_recalculate();
}


QMenu* ScoreCanvas::toolContextMenu()
{
    QMenu* r_menu = new QMenu(this);
    QAction* act0 = nullptr;

    r_menu->addAction(new MenuTitleItem(tr("Tools"), r_menu));

    for (unsigned i = 0; i < static_cast<unsigned>(EditToolBar::toolList.size()); ++i) {
        if ((scoreTools & (1 << i)) == 0)
            continue;
        QAction* act = r_menu->addAction(QIcon(**EditToolBar::toolList[i].icon), tr(EditToolBar::toolList[i].tip));

        if (MusEGui::EditToolBar::toolShortcuts.contains(1 << i)) {
            act->setShortcut(MusEGui::shortcuts[MusEGui::EditToolBar::toolShortcuts[1 << i]].key);
        }

        act->setData(scoreTools & (1 << i));
        act->setCheckable(true);
        act->setChecked((1 << i) == active_tool);
        if (!act0)
            act0 = act;
    }

    r_menu->setActiveAction(act0);
    return r_menu;
}

void ScoreCanvas::callContextMenu()
{
    QMenu * cm = toolContextMenu();
    if (cm) {
        QAction *act = cm->exec(QCursor::pos());
        if (act && act->data().isValid()) {
            int tool = act->data().toInt();
            parent->setEditTool(tool);
        }
        delete cm;
    }
}



} // namespace MusEGui

//the following assertions are made:
//  pix_quarter.width() == pix_half.width()


// pix->width()-1 + 1/2*pix->width() + SHIFT + ADD_SPACE
// 10-1+5+3+3=20 <- um so viel wird der taktstrich verschoben
// um das doppelte (20*2=40) werden die kleinsten schläge gegeneinander versetzt




//note: recalculating event- and itemlists "from zero"
//      could happen in realtime, as it is pretty fast.
//      however, this adds unnecessary cpu usage.
//      it is NO problem to recalc the stuff "from zero"
//      every time something changes.


/* BUGS and potential bugs
 *   o tied notes don't work properly when there's a key-change in
 *     between, for example, when a cis is tied to a des [ will not fix ]
 *         (reason: this actually never happens if dealing with a sane piece)
 * > o when changing toolbarstate when sharing and immediately after that
 *     changing "share" status, the changed state isn't stored
 *     (could be solved by storing the current window when quitting/saving whatever)
 *   ? pasting in editors sometimes fails oO? ( ERROR: reading eventlist
 *     from clipboard failed. ignoring this one... ) [ not reproducible ]
 *   o test drum controllers
 *   o test old- and new drumtrack recording, steprecording
 *   o velo-controller doesn't work in new-style drum tracks
 *
 * CURRENT TODO
 *   o better and visual means for fading etc
 *   o rename E- and A-note to I- and O-
 *   o storing <no_toplevels /> into a template file seems to ignore
 *     the arranger's "MDI-ness", sets is at subwin all the time!
 *
 *   o add controller editor "like search-and-replace":
 *     acts on all specified type's events, and edits the value:
 *      - apply some MIN or MAX on it
 *      - scale it
 *      - offset it
 *      - only act on values in a certain value range
 *      - maybe do curve-mapping
 *   o remove the silly song type!
 *   o remove the song length spinbox
 *   o logical/physical device mapping:
 *     song only contains logical information: not some "synth port",
 *       but a "synth name", like "Yamaha DX7" or "TR-808".
 *     the global config contains physical information, like
 *       "'Yamaha DX7' is on 'USB Midi Bridge, Output two'",
 *       "'TR-808' is on 'jack-midi port foo, connected to bar'"
 *     if loading a song with unknown synth name, ask the user.
 *   o prerecord feature.
 *   o part templates
 *
 *   o find and fix FINDMICHJETZT
 *   o fix valgrind problems (the two "FINDMICHJETZT" lines in scoreedit.cpp)
 *
 * IMPORTANT TODO
 *   o allow steprec-insert-rest-note to be set to "off" / "unused"
 *   o all places where i added doubleclick-edits: only react on left-click double clicks!
 *   o support "new style" reordering with old style drum tracks as well
 *     (not swapping but inserting!)
 *
 *   o canvas editor: create clone via "alt+drag" moves window instead
 *   o controller view in score editor
 *   o solo button
 *   o do partial recalculating; recalculating can take pretty long
 *     (0,5 sec) when displaying a whole song in scores
 *   o transpose etc. must also transpose key-pressure events
 *   o transpose: support in-key-transpose
 *   o thin out: remove unneeded ctrl messages
 *   o support edge-scrolling when opening a lasso
 *   o add "dotted quarter" quantize option (for 6/8 beat)
 *   o ticks-to-quarter spinboxes
 *   o mirror most menus to an additional right-click context menu to avoid the long mouse pointer
 *     journey to the menu bar. try to find a way which does not involve duplicate code!
 *   o implement borland-style maximize: free windows do not cover the main menu, even when maximized
 *   o smart range selection: if range markers have been used recently (that is, a dialog with
 *     "range" setting, or they've been modified), default to "in range" or "selected in range"
 *
 *   o rename stuff with F2 key
 *
 *   o shrink a part from its beginning as well! watch out for clones!
 *
 * less important stuff
 *   o allow "fixating" toolbars?
 *   o quantize-templates (everything is forced into a specified
 *                         rhythm)
 *   o part-templates (you specify some notes and a control-chord;
 *                     the notes are set according to the chord then)
 *   o add functions like set velo, mod/set velo-off
 *   o use bars instead of flags over groups of 8ths / 16ths etc
 *   o support different keys in different tracks at the same time
 *       calc_pos_add_list and calc_item_pos will be affected by this
 *       calc_pos_add_list must be called before calc_item_pos then,
 *       and calc_item_pos must respect the pos_add_list instead of
 *       keeping its own pos_add variable (which is only an optimisation)
 *
 * really unimportant nice-to-haves
 *   o support in-song clef-changes
 *   o draw measure numbers
 *   o use timesig_t in all timesig-stuff
 *   o refuse to resize so that width gets smaller or equal than x_left
 *   o draw a margin around notes which are in a bright color
 *   o support drum tracks in the score editor (x-note-heads etc.)
 *   o drum list: scroll while dragging: probably unnecessary with the "reorder list" function
 *
 *
 * stuff for the other muse developers
 *   o process accurate timesignatures from muse's list (has to be implemented first in muse)
 *      ( (2+2+3)/4 or (3+2+2)/4 instead of 7/4 )
 */


/* R O A D M A P
 * =============
 *
 *   1. finish the score editor, without transposing instruments and
 *      with only a global keymap
 *
 * 			REASON: a score editor with few functions is better than
 *              no score editor at all
 *
 *
 *   2. support transposing by octave-steps
 *
 *      REASON: the main problem with transposing is, that the
 *              editor needs different key signatures and needs
 *              to align them against each other. this problem
 *              doesn't exist when only transposing by octaves
 *
 *
 *   3. support transposing instruments, but only one
 *      transposing-setting per score window. that is, you won't be
 *      able to display your (C-)strings in the same window as your
 *      B-trumpet. this will be very easy to implement
 *
 *      REASON: the above problem still exists, but is circumvented
 *              by simply not having to align them against each other
 *              (because they're in different windows)
 *
 *
 *   4. support different transposing instruments in the same score
 *      window. this will be some hassle, because we need to align
 *      the scores properly. for example, when the C-violin has
 *      C-major (no accidentials), then the B-trumpet need some
 *      accidentials. we now must align the staves so that the
 *      "note-after-keychange"s of both staves are again at the
 *      same x-position
 *
 *      REASON: some solution for that problem must be written.
 *              this is a large step, which atm isn't very important
 *
 *
 *   5. support different keys per track. this wouldn't be that
 *      hard, when 4) is already done; because we then already have
 *      the "align it properly" functionality, and can use it
 *
 * 			REASON: this is only a nice-to-have, which can however be
 *              easily implemented when 4) is done
 */
