hydrogen 1.2.6
PatternEditorInstrumentList.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
24
26#include <core/EventQueue.h>
27#include <core/Hydrogen.h>
30#include <core/Basics/Note.h>
31#include <core/Basics/Pattern.h>
33#include <core/Basics/Song.h>
34using namespace H2Core;
35
38#include "CommonStrings.h"
39#include "UndoActions.h"
40#include "PatternEditorPanel.h"
42#include "DrumPatternEditor.h"
43#include "../HydrogenApp.h"
44#include "../MainForm.h"
45#include "../Widgets/Button.h"
46#include "../Skin.h"
47
48#include <QtGui>
49#include <QtWidgets>
50#include <QClipboard>
51
52#include <cassert>
53#include <algorithm> // for std::min
54
55
57 : PixmapWidget(pParent)
59 , m_bIsSelected( false )
60 , m_bEntered( false )
61{
62
64 auto pCommonStrings = HydrogenApp::get_instance()->getCommonStrings();
65
66 int h = pPref->getPatternEditorGridHeight();
67 setFixedSize(181, h);
68
69 QFont nameFont( pPref->getLevel2FontFamily(), getPointSize( pPref->getFontSize() ) );
70
71 m_pNameLbl = new QLabel(this);
72 m_pNameLbl->resize( 145, h );
73 m_pNameLbl->move( 10, 1 );
74 m_pNameLbl->setFont(nameFont);
75
76 /*: Text displayed on the button for muting an instrument. Its
77 size is designed for a single character.*/
78 m_pMuteBtn = new Button( this, QSize( InstrumentLine::m_nButtonWidth, height() - 1 ),
80 pCommonStrings->getSmallMuteButton(),
81 true, QSize(), tr("Mute instrument"),
82 false, true );
83 m_pMuteBtn->move( 145, 0 );
84 m_pMuteBtn->setChecked(false);
85 m_pMuteBtn->setObjectName( "InstrumentLineMuteButton" );
86 connect(m_pMuteBtn, SIGNAL( clicked() ), this, SLOT( muteClicked() ));
87
88 /*: Text displayed on the button for soloing an instrument. Its
89 size is designed for a single character.*/
90 m_pSoloBtn = new Button( this, QSize( InstrumentLine::m_nButtonWidth, height() - 1 ),
92 pCommonStrings->getSmallSoloButton(),
93 false, QSize(), tr("Solo"),
94 false, true );
95 m_pSoloBtn->move( 163, 0 );
96 m_pSoloBtn->setChecked(false);
97 m_pSoloBtn->setObjectName( "InstrumentLineSoloButton" );
98 connect(m_pSoloBtn, SIGNAL( clicked() ), this, SLOT(soloClicked()));
99
100 m_pSampleWarning = new Button( this, QSize( 15, 13 ), Button::Type::Icon,
101 "warning.svg", "", false, QSize(),
102 tr( "Some samples for this instrument failed to load." ),
103 true );
104 m_pSampleWarning->move( 128, 5 );
105 m_pSampleWarning->hide();
106 connect(m_pSampleWarning, SIGNAL( clicked() ), this, SLOT( sampleWarningClicked() ));
107
108
109 // Popup menu
110 m_pFunctionPopup = new QMenu( this );
111 m_pFunctionPopup->addAction( tr( "Delete notes" ), this, SLOT( functionClearNotes() ) );
112
113 m_pFunctionPopupSub = new QMenu( tr( "Fill notes ..." ), m_pFunctionPopup );
114 m_pFunctionPopupSub->addAction( tr( "Fill all notes" ), this, SLOT( functionFillAllNotes() ) );
115 m_pFunctionPopupSub->addAction( tr( "Fill 1/2 notes" ), this, SLOT( functionFillEveryTwoNotes() ) );
116 m_pFunctionPopupSub->addAction( tr( "Fill 1/3 notes" ), this, SLOT( functionFillEveryThreeNotes() ) );
117 m_pFunctionPopupSub->addAction( tr( "Fill 1/4 notes" ), this, SLOT( functionFillEveryFourNotes() ) );
118 m_pFunctionPopupSub->addAction( tr( "Fill 1/6 notes" ), this, SLOT( functionFillEverySixNotes() ) );
119 m_pFunctionPopupSub->addAction( tr( "Fill 1/8 notes" ), this, SLOT( functionFillEveryEightNotes() ) );
120 m_pFunctionPopupSub->addAction( tr( "Fill 1/12 notes" ), this, SLOT( functionFillEveryTwelveNotes() ) );
121 m_pFunctionPopupSub->addAction( tr( "Fill 1/16 notes" ), this, SLOT( functionFillEverySixteenNotes() ) );
123
124 m_pFunctionPopup->addAction( tr( "Randomize velocity" ), this, SLOT( functionRandomizeVelocity() ) );
125 auto selectNotesAction = m_pFunctionPopup->addAction( tr( "Select notes" ) );
126 connect( selectNotesAction, &QAction::triggered, this,
128
129 m_pFunctionPopup->addSection( tr( "Edit all patterns" ) );
130 m_pFunctionPopup->addAction( tr( "Cut notes"), this, SLOT( functionCutNotesAllPatterns() ) );
131 m_pFunctionPopup->addAction( tr( "Copy notes"), this, SLOT( functionCopyAllInstrumentPatterns() ) );
132 m_pFunctionPopup->addAction( tr( "Paste notes" ), this, SLOT( functionPasteAllInstrumentPatterns() ) );
133 m_pFunctionPopup->addAction( tr( "Delete notes" ), this, SLOT( functionDeleteNotesAllPatterns() ) );
134
135 m_pFunctionPopup->addSection( tr( "Instrument" ) );
136 m_pFunctionPopup->addAction( tr( "Rename instrument" ), this, SLOT( functionRenameInstrument() ) );
137 auto deleteAction = m_pFunctionPopup->addAction( tr( "Delete instrument" ) );
138 connect( deleteAction, &QAction::triggered, this, [=](){
140 functionDeleteInstrument( m_nInstrumentNumber );} );
141 m_pFunctionPopup->setObjectName( "PatternEditorFunctionPopup" );
142
143 // Reset the clicked row once the popup is closed by clicking at
144 // any position other than at an action of the popup.
145 connect( m_pFunctionPopup, &QMenu::aboutToHide, [=](){
148 }
149 });
150
152}
153
154
156 if ( m_rowSelection != rowSelection ) {
157 m_rowSelection = rowSelection;
158 update();
159 }
160}
161
162
163void InstrumentLine::setName(const QString& sName)
164{
165 if ( m_pNameLbl->text() != sName ){
166 m_pNameLbl->setText(sName);
167 }
168}
169
170
171
172void InstrumentLine::setSelected( bool bSelected )
173{
174 if ( bSelected == m_bIsSelected ) {
175 return;
176 }
177
178 m_bIsSelected = bSelected;
179
181 update();
182}
183
185
187
188 QColor textColor;
189 if ( m_bIsSelected ) {
190 textColor = pPref->getColorTheme()->m_patternEditor_selectedRowTextColor;
191 } else {
192 textColor = pPref->getColorTheme()->m_patternEditor_textColor;
193 }
194
195 m_pNameLbl->setStyleSheet( QString( "\
196QLabel {\
197 color: %1;\
198 font-weight: bold;\
199 }" ).arg( textColor.name() ) );
200}
201
202#ifdef H2CORE_HAVE_QT6
203void InstrumentLine::enterEvent( QEnterEvent *ev ) {
204#else
205void InstrumentLine::enterEvent( QEvent *ev ) {
206#endif
207 UNUSED( ev );
208 m_bEntered = true;
209 update();
210}
211
212void InstrumentLine::leaveEvent( QEvent* ev ) {
213 UNUSED( ev );
214 m_bEntered = false;
215 update();
216}
217
218void InstrumentLine::paintEvent( QPaintEvent* ev ) {
219 auto pPref = Preferences::get_instance();
220 auto pHydrogenApp = HydrogenApp::get_instance();
221
222 QPainter painter(this);
223
224 QColor backgroundColor;
225 if ( m_bIsSelected ) {
226 backgroundColor = pPref->getColorTheme()->m_patternEditor_selectedRowColor.darker( 114 );
227 } else {
228 if ( m_nInstrumentNumber == 0 ||
229 m_nInstrumentNumber % 2 == 0 ) {
230 backgroundColor = pPref->getColorTheme()->m_patternEditor_backgroundColor.darker( 120 );
231 } else {
232 backgroundColor = pPref->getColorTheme()->m_patternEditor_alternateRowColor.darker( 132 );
233 }
234 }
235
236 // Make the background slightly lighter when hovered.
237 bool bHovered = false;
239 bHovered = true;
240 }
241
242 Skin::drawListBackground( &painter, QRect( 0, 0, width(), height() ),
243 backgroundColor, bHovered );
244
245 // Draw border indicating cursor position
246 if ( ( m_bIsSelected && pHydrogenApp->getPatternEditorPanel() != nullptr &&
247 pHydrogenApp->getPatternEditorPanel()->getDrumPatternEditor()->hasFocus() &&
248 ! pHydrogenApp->hideKeyboardCursor() ) ||
250
251 QPen pen;
252
254 // In case a row was right-clicked, highlight it using a border.
255 pen.setColor( pPref->getColorTheme()->m_highlightColor);
256 } else {
257 pen.setColor( pPref->getColorTheme()->m_cursorColor );
258 }
259
260 pen.setWidth( 2 );
261 painter.setPen( pen );
262 painter.setRenderHint( QPainter::Antialiasing );
263 painter.drawRoundedRect( QRect( 1, 1, width() - 2 * InstrumentLine::m_nButtonWidth - 1,
264 height() - 2 ), 4, 4 );
265 }
266}
267
268
270{
271 if ( m_nInstrumentNumber != nIndex ) {
272 m_nInstrumentNumber = nIndex;
273 update();
274 }
275}
276
277
278
279void InstrumentLine::setMuted(bool isMuted)
280{
281 if ( ! m_pMuteBtn->isDown() &&
282 m_pMuteBtn->isChecked() != isMuted ) {
283 m_pMuteBtn->setChecked(isMuted);
284 }
285}
286
287
288void InstrumentLine::setSoloed( bool soloed )
289{
290 if ( ! m_pSoloBtn->isDown() &&
291 m_pSoloBtn->isChecked() != soloed ) {
292 m_pSoloBtn->setChecked( soloed );
293 }
294}
295
296
297void InstrumentLine::setSamplesMissing( bool bSamplesMissing )
298{
299 if ( bSamplesMissing ) {
300 m_pSampleWarning->show();
301 } else {
302 m_pSampleWarning->hide();
303 }
304}
305
306
307
309{
310 Hydrogen *pHydrogen = Hydrogen::get_instance();
311 std::shared_ptr<Song> pSong = pHydrogen->getSong();
312 if ( pSong == nullptr ) {
313 ERRORLOG( "No song set yet" );
314 return;
315 }
316
317 auto pInstrList = pSong->getInstrumentList();
318 auto pInstr = pInstrList->get( m_nInstrumentNumber );
319 if ( pInstr == nullptr ) {
320 ERRORLOG( QString( "Unable to retrieve instrument [%1]" )
321 .arg( m_nInstrumentNumber ) );
322 return;
323 }
324
326
327 CoreActionController* pCoreActionController = pHydrogen->getCoreActionController();
328 pCoreActionController->setStripIsMuted( m_nInstrumentNumber, !pInstr->is_muted() );
329}
330
331
332
334{
335 Hydrogen *pHydrogen = Hydrogen::get_instance();
336 std::shared_ptr<Song> pSong = pHydrogen->getSong();
337 if ( pSong == nullptr ) {
338 ERRORLOG( "No song set yet" );
339 return;
340 }
341
342 auto pInstrList = pSong->getInstrumentList();
343 auto pInstr = pInstrList->get( m_nInstrumentNumber );
344 if ( pInstr == nullptr ) {
345 ERRORLOG( QString( "Unable to retrieve instrument [%1]" )
346 .arg( m_nInstrumentNumber ) );
347 return;
348 }
349
351
352 CoreActionController* pCoreActionController = pHydrogen->getCoreActionController();
353 pCoreActionController->setStripIsSoloed( m_nInstrumentNumber, !pInstr->is_soloed() );
354}
355
357{
358 QMessageBox::information( this, "Hydrogen",
359 tr( "One or more samples for this instrument failed to load. This may be because the"
360 " songfile uses an older default drumkit. This might be fixed by opening a new "
361 "drumkit." ) );
362}
363
368
370{
371 auto pEv = static_cast<MouseEvent*>( ev );
372
375
376 if ( ev->button() == Qt::LeftButton ) {
377
378 std::shared_ptr<Song> pSong = Hydrogen::get_instance()->getSong();
379 if ( pSong == nullptr ) {
380 ERRORLOG( "No song set yet" );
381 return;
382 }
383 auto pInstr = pSong->getInstrumentList()->get( m_nInstrumentNumber );
384 if ( pInstr != nullptr && pInstr->hasSamples() ) {
385
386 const int nWidth = m_pMuteBtn->x() - 5; // clickable field width
387 const float fVelocity = std::min(
388 (float)pEv->position().x()/(float)nWidth, 1.0f);
389 Note *pNote = new Note( pInstr, 0, fVelocity);
391 }
392
393 } else if (ev->button() == Qt::RightButton ) {
394
396 // There is still a dialog window opened from the last
397 // time. It needs to be closed before the popup will
398 // be shown again.
399 ERRORLOG( "A dialog is still opened. It needs to be closed first." );
400 return;
401 }
402
404
405 m_pFunctionPopup->popup(
406 QPoint( pEv->globalPosition().x(), pEv->globalPosition().y() ) );
407 }
408
409 // propago l'evento al parent: serve per il drag&drop
410 PixmapWidget::mousePressEvent(ev);
411}
412
416
418{
419 Hydrogen *pHydrogen = Hydrogen::get_instance();
420 PatternList *pPatternList = pHydrogen->getSong()->getPatternList();
421 assert( pPatternList != nullptr );
422
423 int nSelectedPatternNumber = pHydrogen->getSelectedPatternNumber();
424 if ( nSelectedPatternNumber != -1 &&
425 nSelectedPatternNumber < pPatternList->size() ) {
426 Pattern* pCurrentPattern = pPatternList->get( nSelectedPatternNumber );
427 return pCurrentPattern;
428 }
429 return nullptr;
430}
431
432
433
434
436{
437 Hydrogen * pHydrogen = Hydrogen::get_instance();
438 int nSelectedPatternNr = pHydrogen->getSelectedPatternNumber();
439 Pattern *pPattern = getCurrentPattern();
440 auto pSelectedInstrument = pHydrogen->getSong()->getInstrumentList()->get( m_nInstrumentNumber );
441 if ( pSelectedInstrument == nullptr ) {
442 ERRORLOG( "No instrument selected" );
443 return;
444 }
445
446 if ( nSelectedPatternNr == -1 ) {
447 // No pattern selected. Nothing to be clear.
448 return;
449 }
450
451 std::list< Note* > noteList;
452 const Pattern::notes_t* notes = pPattern->get_notes();
454 Note *pNote = it->second;
455 assert( pNote );
456 if ( pNote->get_instrument() == pSelectedInstrument ) {
457 noteList.push_back( pNote );
458 }
459 }
460 if( noteList.size() > 0 ){
464 nSelectedPatternNr );
465 HydrogenApp::get_instance()->m_pUndoStack->push( action );
466 }
467}
468
469
471{
472 Hydrogen* pHydrogen = Hydrogen::get_instance();
473 std::shared_ptr<Song> pSong = pHydrogen->getSong();
474 if ( pSong == nullptr ) {
475 assert( pSong );
476 ERRORLOG( "No song present" );
477 return;
478 }
479
480 // Serialize & put to clipboard
481 QString sSerialized = pSong->copyInstrumentLineToString( m_nInstrumentNumber );
482 if ( sSerialized.isEmpty() ) {
483 ERRORLOG( QString( "Unable to serialize instrument line [%1]" )
484 .arg( m_nInstrumentNumber ) );
485 return;
486 }
487
488 QClipboard *clipboard = QApplication::clipboard();
489 clipboard->setText( sSerialized );
490}
491
492
494{
495 Hydrogen* pHydrogen = Hydrogen::get_instance();
496 std::shared_ptr<Song> pSong = pHydrogen->getSong();
497 if ( pSong == nullptr ) {
498 assert( pSong );
499 ERRORLOG( "No song present" );
500 return;
501 }
502
503 // This is a note list for pasted notes collection
504 std::list<Pattern*> patternList;
505
506 // Get from clipboard & deserialize
507 QClipboard *clipboard = QApplication::clipboard();
508 QString sSerialized = clipboard->text();
509 if ( ! pSong->pasteInstrumentLineFromString( sSerialized,
511 patternList ) ) {
512 return;
513 }
514
515 // Ignore empty result
516 if (patternList.size() <= 0) {
517 return;
518 }
519
520 // Create action
523}
524
526{
527 std::shared_ptr<Song> pSong = Hydrogen::get_instance()->getSong();
528 PatternList *pPatternList = pSong->getPatternList();
529 auto pSelectedInstrument = pSong->getInstrumentList()->get( m_nInstrumentNumber );
530 if ( pSelectedInstrument == nullptr ) {
531 ERRORLOG( "No instrument selected" );
532 return;
533 }
534 QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack;
535
536 pUndo->beginMacro( tr( "Delete all notes on %1" ).arg( pSelectedInstrument->get_name() ) );
537 for ( int nPattern = 0; nPattern < pPatternList->size(); nPattern++ ) {
538 std::list< Note* > noteList;
539 Pattern *pPattern = pPatternList->get( nPattern );
540 const Pattern::notes_t* notes = pPattern->get_notes();
542 if ( it->second->get_instrument() == pSelectedInstrument ) {
543 noteList.push_back( it->second );
544 }
545 }
546 if ( noteList.size() > 0 ) {
547 pUndo->push( new SE_clearNotesPatternEditorAction( noteList, m_nInstrumentNumber, nPattern ) );
548 }
549 }
550 pUndo->endMacro();
551}
552
558
559
568
570{
571 Hydrogen *pHydrogen = Hydrogen::get_instance();
572 if ( pHydrogen->getSelectedPatternNumber() == -1 ) {
573 // No pattern selected. Nothing to be filled.
574 return;
575 }
576
578 DrumPatternEditor *pPatternEditor = pPatternEditorPanel->getDrumPatternEditor();
579 int nBase;
580 if ( pPatternEditor->isUsingTriplets() ) {
581 nBase = 3;
582 }
583 else {
584 nBase = 4;
585 }
586 int nResolution = 4 * MAX_NOTES * every / ( nBase * pPatternEditor->getResolution() );
587
588
589 std::shared_ptr<Song> pSong = pHydrogen->getSong();
590
591 QStringList notePositions;
592
593 Pattern* pCurrentPattern = getCurrentPattern();
594 if (pCurrentPattern != nullptr) {
595 int nPatternSize = pCurrentPattern->get_length();
596 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
597 if ( pSelectedInstrument == nullptr ) {
598 ERRORLOG( "No instrument selected" );
599 return;
600 }
601 int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber();
602
603 for (int i = 0; i < nPatternSize; i += nResolution) {
604 bool noteAlreadyPresent = false;
605 const Pattern::notes_t* notes = pCurrentPattern->get_notes();
606 FOREACH_NOTE_CST_IT_BOUND_LENGTH(notes,it,i,pCurrentPattern) {
607 Note *pNote = it->second;
608 if ( pNote->get_instrument() == pSelectedInstrument ) {
609 // note already exists
610 noteAlreadyPresent = true;
611 break;
612 }
613 }
614
615 if ( noteAlreadyPresent == false ) {
616 notePositions << QString("%1").arg(i);
617 }
618 }
619 SE_fillNotesRightClickAction *action = new SE_fillNotesRightClickAction( notePositions, nSelectedInstrument, pHydrogen->getSelectedPatternNumber() );
620 HydrogenApp::get_instance()->m_pUndoStack->push( action );
621 }
622
623}
624
625
626
628{
629 Hydrogen *pHydrogen = Hydrogen::get_instance();
630
631 if ( pHydrogen->getSelectedPatternNumber() == -1 ) {
632 // No pattern selected. Nothing to be randomized.
633 return;
634 }
635
637 DrumPatternEditor *pPatternEditor = pPatternEditorPanel->getDrumPatternEditor();
638
639
640 int nBase;
641 if ( pPatternEditor->isUsingTriplets() ) {
642 nBase = 3;
643 }
644 else {
645 nBase = 4;
646 }
647 int nResolution = 4 * MAX_NOTES / ( nBase * pPatternEditor->getResolution() );
648
649 std::shared_ptr<Song> pSong = pHydrogen->getSong();
650
651 QStringList noteVeloValue;
652 QStringList oldNoteVeloValue;
653
654 Pattern* pCurrentPattern = getCurrentPattern();
655 if (pCurrentPattern != nullptr) {
656 int nPatternSize = pCurrentPattern->get_length();
657 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
658 if ( pSelectedInstrument == nullptr ) {
659 ERRORLOG( "No instrument selected" );
660 return;
661 }
662 int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber();
663
664 for (int i = 0; i < nPatternSize; i += nResolution) {
665 const Pattern::notes_t* notes = pCurrentPattern->get_notes();
666 FOREACH_NOTE_CST_IT_BOUND_LENGTH(notes,it,i,pCurrentPattern) {
667 Note *pNote = it->second;
668 if ( pNote->get_instrument() == pSelectedInstrument ) {
669 float fVal = ( rand() % 100 ) / 100.0;
670 oldNoteVeloValue << QString("%1").arg( pNote->get_velocity() );
671 fVal = pNote->get_velocity() + ( ( fVal - 0.50 ) / 2 );
672 if ( fVal < 0 ) {
673 fVal = 0;
674 }
675 if ( fVal > 1 ) {
676 fVal = 1;
677 }
678 noteVeloValue << QString("%1").arg(fVal);
679 }
680 }
681 }
682 SE_randomVelocityRightClickAction *action = new SE_randomVelocityRightClickAction( noteVeloValue, oldNoteVeloValue, nSelectedInstrument, pHydrogen->getSelectedPatternNumber() );
683 HydrogenApp::get_instance()->m_pUndoStack->push( action );
684 }
685}
686
687
688
690{
692 // This code is pretty much a duplicate of void InstrumentEditor::labelClicked
693 // in InstrumentEditor.cpp
694 Hydrogen * pHydrogen = Hydrogen::get_instance();
695 auto pSelectedInstrument = pHydrogen->getSong()->getInstrumentList()->get( m_nInstrumentNumber );
696 if ( pSelectedInstrument == nullptr ) {
697 ERRORLOG( "No instrument selected" );
698 return;
699 }
700
701 QString sOldName = pSelectedInstrument->get_name();
702 bool bIsOkPressed;
703 QString sNewName = QInputDialog::getText( this, "Hydrogen", tr( "New instrument name" ), QLineEdit::Normal, sOldName, &bIsOkPressed );
704 if ( bIsOkPressed ) {
705 pSelectedInstrument->set_name( sNewName );
706
707 if ( pHydrogen->hasJackAudioDriver() ) {
708 pHydrogen->getAudioEngine()->lock( RIGHT_HERE );
709 pHydrogen->renameJackPorts( pHydrogen->getSong() );
710 pHydrogen->getAudioEngine()->unlock();
711 }
712
713 // this will force an update...
715
716 }
717 else
718 {
719 // user entered nothing or pressed Cancel
720 }
721
723}
724
727
728 if ( changes & H2Core::Preferences::Changes::Font ) {
729
730 m_pNameLbl->setFont( QFont( pPref->getLevel2FontFamily(), getPointSize( pPref->getFontSize() ) ) );
731 }
732
733 if ( changes & H2Core::Preferences::Changes::Colors ) {
735 update();
736 }
737}
738
739
741
743 : QWidget( parent )
744 {
745
747
748 //INFOLOG("INIT");
749 m_pPattern = nullptr;
750 m_pPatternEditorPanel = pPatternEditorPanel;
751
753
754 m_nEditorWidth = 181;
756
758
759 setAcceptDrops(true);
760
761 for ( int i = 0; i < MAX_INSTRUMENTS; ++i) {
762 m_pInstrumentLine[i] = nullptr;
763 }
764
765
767
768 m_pUpdateTimer = new QTimer( this );
769 connect( m_pUpdateTimer, SIGNAL( timeout() ), this, SLOT( updateInstrumentLines() ) );
770 m_pUpdateTimer->start(50);
771
772 QScrollArea *pScrollArea = dynamic_cast< QScrollArea *>( parentWidget()->parentWidget() );
773 assert( pScrollArea );
774 m_pDragScroller = new DragScroller( pScrollArea );
775}
776
777
778
780{
781 //INFOLOG( "DESTROY" );
782 m_pUpdateTimer->stop();
783 delete m_pDragScroller;
784}
785
786
787
788
799
801 if ( nEvent == 0 || nEvent == 1 ) {
803 }
804}
805
809
811 auto pHydrogen = Hydrogen::get_instance();
812 auto pSong = pHydrogen->getSong();
813 auto pInstrList = pSong->getInstrumentList();
814
815 unsigned nInstruments = pInstrList->size();
816 for ( unsigned nInstr = 0; nInstr < MAX_INSTRUMENTS; ++nInstr ) {
817 if ( nInstr < nInstruments &&
818 m_pInstrumentLine[ nInstr ] != nullptr ) {
819 m_pInstrumentLine[ nInstr ]->update();
820 }
821 }
822}
823
825
826 auto pHydrogen = Hydrogen::get_instance();
827 auto pSong = pHydrogen->getSong();
828 auto pInstrList = pSong->getInstrumentList();
829
830 unsigned nSelectedInstr = pHydrogen->getSelectedInstrumentNumber();
831
832 unsigned nInstruments = pInstrList->size();
833 for ( unsigned nInstr = 0; nInstr < MAX_INSTRUMENTS; ++nInstr ) {
834 if ( nInstr < nInstruments &&
835 m_pInstrumentLine[ nInstr ] != nullptr ) {
836
837 InstrumentLine *pLine = m_pInstrumentLine[ nInstr ];
838 pLine->setSelected( nInstr == nSelectedInstr );
839 }
840 }
841}
842
847{
848 Hydrogen *pHydrogen = Hydrogen::get_instance();
849 std::shared_ptr<Song> pSong = pHydrogen->getSong();
850 auto pInstrList = pSong->getInstrumentList();
851
852 unsigned nSelectedInstr = pHydrogen->getSelectedInstrumentNumber();
853
854 unsigned nInstruments = pInstrList->size();
855 for ( unsigned nInstr = 0; nInstr < MAX_INSTRUMENTS; ++nInstr ) {
856 if ( nInstr >= nInstruments ) { // unused instrument! let's hide and destroy the mixerline!
857 if ( m_pInstrumentLine[ nInstr ] ) {
858 delete m_pInstrumentLine[ nInstr ];
859 m_pInstrumentLine[ nInstr ] = nullptr;
860
861 int newHeight = m_nGridHeight * nInstruments + 1;
862 resize( width(), newHeight );
863 }
864 continue;
865 }
866 else {
867 if ( m_pInstrumentLine[ nInstr ] == nullptr ) {
868 // the instrument line doesn't exists..I'll create a new one!
870 m_pInstrumentLine[nInstr]->move( 0, m_nGridHeight * nInstr + 1 );
871 m_pInstrumentLine[nInstr]->show();
872
873 int newHeight = m_nGridHeight * nInstruments;
874 resize( width(), newHeight );
875 }
876 InstrumentLine *pLine = m_pInstrumentLine[ nInstr ];
877 auto pInstr = pInstrList->get(nInstr);
878 assert(pInstr);
879
880 pLine->setNumber(nInstr);
881 pLine->setName( pInstr->get_name() );
882 pLine->setSelected( nInstr == nSelectedInstr );
883 pLine->setMuted( pInstr->is_muted() );
884 pLine->setSoloed( pInstr->is_soloed() );
885
886 pLine->setSamplesMissing( pInstr->has_missing_samples() );
887 }
888 }
889
890}
891
893{
894 event->acceptProposedAction();
895}
896
898{
899 //WARNINGLOG("Drop!");
900 if ( ! event->mimeData()->hasFormat("text/plain") ) {
901 event->ignore();
902 return;
903 }
904
905 auto pHydrogen = Hydrogen::get_instance();
906 std::shared_ptr<Song> pSong = pHydrogen->getSong();
907 auto pInstrumentList = pSong->getInstrumentList();
908 int nInstruments = pInstrumentList->size();
909 if ( nInstruments >= MAX_INSTRUMENTS ) {
910 event->ignore();
911 QMessageBox::critical( this, "Hydrogen", tr( "Unable to insert further instruments. Maximum possible number" ) +
912 QString( ": %1" ).arg( MAX_INSTRUMENTS ) );
913 return;
914 }
915
916 auto pEv = static_cast<DropEvent*>( event );
917
918 QString sText = event->mimeData()->text();
919
920
921 if ( sText.startsWith("Songs:") ||
922 sText.startsWith("Patterns:") ||
923 sText.startsWith("move pattern:") ||
924 sText.startsWith("drag pattern:") ) {
925 return;
926 }
927
928 if (sText.startsWith("move instrument:")) {
929
930 int nSourceInstrument = pHydrogen->getSelectedInstrumentNumber();
931
932 // Starting point for instument list is 50 lower than
933 // on the drum pattern editor
934
935 int pos_y = ( pEv->position().x() >= m_nEditorWidth ) ?
936 pEv->position().y() - 50 : pEv->position().y();
937
938 int nTargetInstrument = pos_y / m_nGridHeight;
939
940 if( nTargetInstrument >= pInstrumentList->size() ){
941 nTargetInstrument = pInstrumentList->size() - 1;
942 }
943
944 if ( nSourceInstrument == nTargetInstrument ) {
945 event->acceptProposedAction();
946 return;
947 }
948
949 SE_moveInstrumentAction *action = new SE_moveInstrumentAction( nSourceInstrument, nTargetInstrument );
950 HydrogenApp::get_instance()->m_pUndoStack->push( action );
951
952 event->acceptProposedAction();
953 }
954 if( sText.startsWith("importInstrument:") ) {
955 //an instrument was dragged from the soundlibrary browser to the patterneditor
956
957 sText = sText.remove(0,QString("importInstrument:").length());
958
959 QStringList tokens = sText.split( "::" );
960 QString sDrumkitPath = tokens.at( 0 );
961 QString sInstrumentName = tokens.at( 1 );
962
963 int nTargetInstrument = pEv->position().y() / m_nGridHeight;
964
965 /*
966 "X > 181": border between the instrument names on the left and the grid
967 Because the right part of the grid starts above the name column, we have to subtract the difference
968 */
969 if ( pEv->position().x() > 181 ) {
970 nTargetInstrument = ( pEv->position().y() - 90 ) / m_nGridHeight ;
971 }
972
973 if( nTargetInstrument > pInstrumentList->size() ){
974 nTargetInstrument = pInstrumentList->size();
975 }
976
977 auto pCommonString = HydrogenApp::get_instance()->getCommonStrings();
978
979 if ( sDrumkitPath.isEmpty() ) {
980 QMessageBox::critical( this, "Hydrogen", pCommonString->getInstrumentLoadError() );
981 return;
982 }
983
984 SE_dragInstrumentAction *action = new SE_dragInstrumentAction( sDrumkitPath, sInstrumentName, nTargetInstrument );
985 HydrogenApp::get_instance()->m_pUndoStack->push( action );
986
987 event->acceptProposedAction();
988 }
989}
990
991
992
994{
995 auto pEv = static_cast<MouseEvent*>( event );
996
997 if (event->button() == Qt::LeftButton) {
998 __drag_start_position = pEv->position().toPoint();
999 }
1000
1001}
1002
1003
1004
1006{
1007 auto pEv = static_cast<MouseEvent*>( event );
1008
1009 if (!(event->buttons() & Qt::LeftButton)) {
1010 return;
1011 }
1012 if ( abs(pEv->position().y() - __drag_start_position.y()) < (int)m_nGridHeight) {
1013 return;
1014 }
1015
1016 Hydrogen *pHydrogen = Hydrogen::get_instance();
1017 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
1018 if ( pSelectedInstrument == nullptr ) {
1019 ERRORLOG( "No instrument selected" );
1020 return;
1021 }
1022
1023 QString sText = QString("move instrument:%1").arg( pSelectedInstrument->get_name() );
1024
1025 QDrag *pDrag = new QDrag(this);
1026 QMimeData *pMimeData = new QMimeData;
1027
1028 pMimeData->setText( sText );
1029 pDrag->setMimeData( pMimeData);
1030
1031 m_pDragScroller->startDrag();
1032 pDrag->exec( Qt::CopyAction | Qt::MoveAction );
1033 m_pDragScroller->endDrag();
1034
1035 // propago l'evento
1036 QWidget::mouseMoveEvent(event);
1037}
1038
1039
1041 auto pInstrumentList = Hydrogen::get_instance()->getSong()->getInstrumentList();
1042
1043 if ( nInstrumentNumber == -1 ) {
1044 // Update all lines.
1045 for ( int ii = 0; ii < MAX_INSTRUMENTS; ++ii ) {
1046 auto pInstrumentLine = m_pInstrumentLine[ ii ];
1047 if ( pInstrumentLine != nullptr ) {
1048 auto pInstrument = pInstrumentList->get( ii );
1049 if ( pInstrument == nullptr ) {
1050 ERRORLOG( QString( "Instrument [%1] associated to InstrumentLine [%1] not found" )
1051 .arg( ii ) );
1052 return;
1053 }
1054
1055 pInstrumentLine->setName( pInstrument->get_name() );
1056 pInstrumentLine->setMuted( pInstrument->is_muted() );
1057 pInstrumentLine->setSoloed( pInstrument->is_soloed() );
1058 }
1059 }
1060 }
1061 else {
1062 // Update a specific line
1063 auto pInstrument = pInstrumentList->get( nInstrumentNumber );
1064 if ( pInstrument == nullptr ) {
1065 ERRORLOG( QString( "Instrument [%1] not found" )
1066 .arg( nInstrumentNumber ) );
1067 return;
1068 }
1069
1070 auto pInstrumentLine = m_pInstrumentLine[ nInstrumentNumber ];
1071 if ( pInstrumentLine == nullptr ) {
1072 ERRORLOG( QString( "No InstrumentLine for instrument [%1] created yet" )
1073 .arg( nInstrumentNumber ) );
1074 return;
1075 }
1076
1077 pInstrumentLine->setName( pInstrument->get_name() );
1078 pInstrumentLine->setMuted( pInstrument->is_muted() );
1079 pInstrumentLine->setSoloed( pInstrument->is_soloed() );
1080 }
1081}
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
Definition AudioEngine.h:61
#define ERRORLOG(x)
Definition Object.h:242
#define FOREACH_NOTE_CST_IT_BOUND_LENGTH(_notes, _it, _bound, _pattern)
Iterate over all notes in column _bound in an immutable way if it is contained in _pattern.
Definition Pattern.h:290
#define FOREACH_NOTE_CST_IT_BEGIN_END(_notes, _it)
Iterate over all provided notes in an immutable way.
Definition Pattern.h:268
Generic Button with SVG icons or text.
Definition Button.h:60
@ Icon
Button is both flat and has a transparent background.
Definition Button.h:75
@ Toggle
Button is set checkable.
Definition Button.h:70
Drag scroller object.
Definition Selection.h:129
Compatibility class to support QDropEvent more esily in Qt5 and Qt6.
Definition DropEvent.h:35
Drum pattern editor.
virtual void updateEditor(bool bPatternOnly=false) override
void unlock()
Mutex unlocking of the AudioEngine.
void lock(const char *file, unsigned int line, const char *function)
Mutex locking of the AudioEngine.
Sampler * getSampler() const
bool setStripIsMuted(int nStrip, bool isMuted)
bool setStripIsSoloed(int nStrip, bool isSoloed)
static EventQueue * get_instance()
Returns a pointer to the current EventQueue singleton stored in __instance.
Definition EventQueue.h:224
void push_event(const EventType type, const int nValue)
Queues the next event into the EventQueue.
Hydrogen Audio Engine.
Definition Hydrogen.h:54
bool hasJackAudioDriver() const
void renameJackPorts(std::shared_ptr< Song > pSong)
Calls audioEngine_renameJackPorts() if Preferences::m_bJackTrackOuts is set to true.
Definition Hydrogen.cpp:957
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
int getSelectedInstrumentNumber() const
Definition Hydrogen.h:678
int getSelectedPatternNumber() const
Definition Hydrogen.h:674
void setSelectedInstrumentNumber(int nInstrument, bool bTriggerEvent=true)
Definition Hydrogen.cpp:944
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:663
std::shared_ptr< Instrument > getSelectedInstrument() const
CoreActionController * getCoreActionController() const
Definition Hydrogen.h:653
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:101
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
Definition Note.h:499
float get_velocity() const
__velocity accessor
Definition Note.h:539
PatternList is a collection of patterns.
Definition PatternList.h:43
int size() const
returns the numbers of patterns
Pattern * get(int idx)
get a pattern from the list
Pattern class is a Note container.
Definition Pattern.h:46
int get_length() const
set the denominator of the pattern
Definition Pattern.h:340
const notes_t * get_notes() const
get the virtual pattern set
Definition Pattern.h:355
std::multimap< int, Note * > notes_t
< multimap note type
Definition Pattern.h:50
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
unsigned getPatternEditorGridHeight()
Changes
Bitwise or-able options showing which part of the Preferences were altered using the PreferencesDialo...
@ Font
Either the font size or font family have changed.
@ Colors
At least one of the colors has changed.
void noteOn(Note *pNote)
Start playing a note.
Definition Sampler.cpp:185
MainForm * getMainForm()
void addEventListener(EventListener *pListener)
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
std::shared_ptr< CommonStrings > getCommonStrings()
QUndoStack * m_pUndoStack
PatternEditorPanel * getPatternEditorPanel()
void preferencesChanged(H2Core::Preferences::Changes changes)
Propagates a change in the Preferences through the GUI.
void setRowSelection(RowSelection rowSelection)
InstrumentLine(QWidget *pParent)
virtual void mousePressEvent(QMouseEvent *ev) override
virtual void mouseDoubleClickEvent(QMouseEvent *ev) override
virtual void leaveEvent(QEvent *ev)
void onPreferencesChanged(H2Core::Preferences::Changes changes)
void setSelected(bool isSelected)
void setSamplesMissing(bool bSamplesMissing)
H2Core::Pattern * getCurrentPattern()
void setName(const QString &sName)
static constexpr int m_nButtonWidth
virtual void paintEvent(QPaintEvent *ev) override
int m_nInstrumentNumber
The related instrument number.
virtual void enterEvent(QEvent *ev) override
bool m_bEntered
Whether the cursor entered the boundary of the widget.
Compatibility class to support QMouseEvent more esily in Qt5 and Qt6.
Definition MouseEvent.h:35
virtual void mouseMoveEvent(QMouseEvent *event) override
void updateInstrumentLines()
Update every InstrumentLine, create or destroy lines if necessary.
InstrumentLine * createInstrumentLine()
Create a new InstrumentLine.
virtual void mousePressEvent(QMouseEvent *event) override
PatternEditorInstrumentList(QWidget *parent, PatternEditorPanel *pPatternEditorPanel)
virtual void instrumentParametersChangedEvent(int) override
virtual void dragEnterEvent(QDragEnterEvent *event) override
virtual void selectedInstrumentChangedEvent() override
virtual void updateSongEvent(int nEvent) override
virtual void dropEvent(QDropEvent *event) override
InstrumentLine * m_pInstrumentLine[MAX_INSTRUMENTS]
Pattern Editor Panel.
void selectInstrumentNotes(int nInstrument)
DrumPatternEditor * getDrumPatternEditor()
bool isUsingTriplets() const
uint getResolution() const
PixmapWidget(QWidget *pParent)
static void drawListBackground(QPainter *p, QRect rect, QColor background, bool bHovered)
Draws the background of a row in both the pattern list of the SongEditor and the instrument list in t...
Definition Skin.cpp:144
RowSelection m_rowSelection
Determines the highlighting of the row associated with m_nRowClicked.
RowSelection
Specifies whether the row corresponding to m_nRowClicked should be highlighted and determines the lif...
@ Popup
The m_nRowClicked row was right-clicked and a popup dialog did open and is still shown.
@ None
No highlighting will be drawn for the row last clicked.
@ Dialog
The popup dialog is already closed but the user clicked an associated action and its dialog is still ...
constexpr int getPointSize(H2Core::FontTheme::FontSize fontSize) const
#define MAX_INSTRUMENTS
Maximum number of instruments allowed in Hydrogen.
Definition config.dox:70
#define MAX_NOTES
Maximum number of notes.
Definition config.dox:79
#define UNUSED(v)
Definition Globals.h:42
@ EVENT_SELECTED_INSTRUMENT_CHANGED
Definition EventQueue.h:77