2018-11-19 15:57:54 -05:00
# include "mainwindow.h"
# include "ui_mainwindow.h"
2022-03-18 22:59:50 -04:00
# include "settingsdialog.h"
2018-11-28 05:19:10 -05:00
using Xybrid : : MainWindow ;
2018-11-19 15:57:54 -05:00
2018-11-28 05:19:10 -05:00
# include <QDebug>
# include <QKeyEvent>
2018-12-04 17:04:45 -05:00
# include <QShortcut>
2018-11-28 05:19:10 -05:00
# include <QTabWidget>
2018-12-01 10:41:14 -05:00
# include <QFileDialog>
2018-12-04 17:04:45 -05:00
# include <QInputDialog>
# include <QMessageBox>
2019-01-09 03:02:58 -05:00
# include <QDialogButtonBox>
# include <QSpinBox>
2018-12-17 14:09:44 -05:00
# include <QWindow>
2018-12-06 14:27:22 -05:00
# include <QUndoStack>
2018-12-22 21:03:51 -05:00
# include <QTimer>
# include <QOpenGLWidget>
# include <QScroller>
# include <QGraphicsTextItem>
2018-12-25 01:54:23 -05:00
# include <QJsonObject>
2018-12-22 21:03:51 -05:00
# include "data/graph.h"
2018-11-22 06:36:27 -05:00
2018-12-04 17:04:45 -05:00
# include "util/strings.h"
2018-12-22 21:03:51 -05:00
# include "util/lambdaeventfilter.h"
2018-12-06 07:25:57 -05:00
# include "fileops.h"
2018-12-04 17:04:45 -05:00
# include "ui/patternlistmodel.h"
# include "ui/patternsequencermodel.h"
2018-11-22 06:36:27 -05:00
# include "ui/patterneditoritemdelegate.h"
2018-12-22 21:03:51 -05:00
# include "ui/patchboard/patchboardscene.h"
2019-06-21 17:50:42 -04:00
# include "ui/patchboard/nodeuiscene.h"
2018-12-22 21:03:51 -05:00
2019-06-16 14:40:09 -04:00
# include "ui/samplelistmodel.h"
2019-01-09 03:02:58 -05:00
# include "editing/compositecommand.h"
2018-12-07 13:45:32 -05:00
# include "editing/projectcommands.h"
2019-01-09 03:02:58 -05:00
# include "editing/patterncommands.h"
2018-12-07 13:45:32 -05:00
2022-03-13 06:47:17 -04:00
# include "config/uistate.h"
2018-12-22 21:03:51 -05:00
# include "config/pluginregistry.h"
2018-12-17 14:09:44 -05:00
# include "audio/audioengine.h"
2018-12-01 10:41:14 -05:00
using Xybrid : : Data : : Project ;
using Xybrid : : Data : : Pattern ;
2019-07-08 04:50:48 -04:00
using Xybrid : : Data : : SequenceEntry ;
2018-12-22 21:03:51 -05:00
using Xybrid : : Data : : Graph ;
using Xybrid : : Data : : Node ;
using Xybrid : : Data : : Port ;
2018-12-01 10:41:14 -05:00
2018-12-04 17:04:45 -05:00
using Xybrid : : UI : : PatternListModel ;
using Xybrid : : UI : : PatternSequencerModel ;
2018-11-22 06:36:27 -05:00
using Xybrid : : UI : : PatternEditorModel ;
using Xybrid : : UI : : PatternEditorItemDelegate ;
2018-12-22 21:03:51 -05:00
using Xybrid : : UI : : PatchboardScene ;
2019-06-21 17:50:42 -04:00
using Xybrid : : UI : : NodeUIScene ;
2018-12-22 21:03:51 -05:00
2019-06-16 14:40:09 -04:00
using Xybrid : : UI : : SampleListModel ;
2018-12-07 13:45:32 -05:00
using namespace Xybrid : : Editing ;
2018-12-22 21:03:51 -05:00
using namespace Xybrid : : Config ;
2018-12-18 19:33:41 -05:00
using namespace Xybrid : : Audio ;
2018-12-07 13:45:32 -05:00
2018-11-23 09:03:35 -05:00
namespace {
2019-01-24 14:26:20 -05:00
//
2018-11-23 09:03:35 -05:00
}
2022-03-13 16:34:07 -04:00
std : : unordered_set < MainWindow * > MainWindow : : openWindows ;
2022-03-13 17:51:06 -04:00
MainWindow : : MainWindow ( QWidget * parent , const QString & fileName ) :
2018-11-19 15:57:54 -05:00
QMainWindow ( parent ) ,
2018-11-22 06:36:27 -05:00
ui ( new Ui : : MainWindow ) {
2019-06-23 03:57:40 -04:00
socket = new UISocket ( ) ; // create this first
2018-11-19 15:57:54 -05:00
ui - > setupUi ( this ) ;
2018-11-22 06:36:27 -05:00
2022-03-15 05:43:04 -04:00
// remove tabs containing system widgets
2018-12-17 14:09:44 -05:00
ui - > tabWidget - > removeTab ( ui - > tabWidget - > indexOf ( ui - > extra_ ) ) ;
2022-03-15 05:43:04 -04:00
ui - > tabWidget - > removeTab ( ui - > tabWidget - > indexOf ( ui - > extra_2 ) ) ;
2018-12-17 14:09:44 -05:00
2018-12-06 14:27:22 -05:00
undoStack = new QUndoStack ( this ) ;
//undoStack->setUndoLimit(256);
2018-12-19 10:18:32 -05:00
connect ( undoStack , & QUndoStack : : cleanChanged , this , [ this ] ( bool ) {
2018-12-06 14:27:22 -05:00
updateTitle ( ) ;
} ) ;
2018-11-22 06:36:27 -05:00
2022-03-18 22:59:50 -04:00
auto efa = ui - > menuEdit - > actions ( ) . at ( 0 ) ;
2018-12-06 14:27:22 -05:00
auto * undoAction = undoStack - > createUndoAction ( this , tr ( " &Undo " ) ) ;
undoAction - > setShortcuts ( QKeySequence : : Undo ) ;
2022-03-18 22:59:50 -04:00
ui - > menuEdit - > insertAction ( efa , undoAction ) ;
2018-12-06 14:27:22 -05:00
auto * redoAction = undoStack - > createRedoAction ( this , tr ( " &Redo " ) ) ;
redoAction - > setShortcuts ( QKeySequence : : Redo ) ;
2022-03-18 22:59:50 -04:00
ui - > menuEdit - > insertAction ( efa , redoAction ) ;
2018-12-06 14:27:22 -05:00
2018-12-01 10:41:14 -05:00
// prevent right pane of pattern view from being collapsed
ui - > patternViewSplitter - > setCollapsible ( 1 , false ) ;
2018-12-19 10:18:32 -05:00
connect ( ui - > patternViewSplitter , & QSplitter : : splitterMoved , this , [ this ] ( int , int ) {
2018-12-04 17:04:45 -05:00
// and when the list is collapsed, make sure header size is updated
ui - > patternEditor - > updateHeader ( ) ;
} ) ;
2019-06-20 20:16:48 -04:00
{ /* Set up toolbar */ } {
auto * t = ui - > tabWidget ;
t - > setCurrentWidget ( ui - > pattern ) ; // set default regardless of what was edited last in the designer :|
t - > setCornerWidget ( ui - > logo , Qt : : TopLeftCorner ) ;
auto * tb = ui - > toolBar ;
t - > setCornerWidget ( tb ) ;
tb - > layout ( ) - > addWidget ( ui - > menuBar ) ;
ui - > playButton - > setIcon ( ui - > playButton - > style ( ) - > standardIcon ( QStyle : : SP_MediaPlay ) ) ;
// play/stop
auto play = new QAction ( this ) ;
play - > setShortcuts ( { QKeySequence ( " Ctrl+P " ) , QKeySequence ( " Ctrl+Shift+P " ) } ) ;
ui - > playButton - > setDefaultAction ( play ) ;
connect ( play , & QAction : : triggered , this , [ this ] {
if ( audioEngine - > playbackMode ( ) = = AudioEngine : : Playing & & audioEngine - > playingProject ( ) = = project ) audioEngine - > stop ( ) ;
else {
bool shift = QGuiApplication : : keyboardModifiers ( ) . testFlag ( Qt : : KeyboardModifier : : ShiftModifier ) ;
if ( shift ) audioEngine - > play ( project , ui - > patternSequencer - > currentIndex ( ) . column ( ) ) ;
else audioEngine - > play ( project ) ;
}
} ) ;
2019-06-26 16:44:55 -04:00
2019-06-20 20:16:48 -04:00
//ui->menuBar->setStyleSheet("QMenuBar { background: transparent; vertical-align: center; } QMenuBar::item { } QMenuBar::item:!pressed { background: transparent; }");
}
2022-03-15 05:43:04 -04:00
{ /* Set up floater container */ } {
auto fc = ui - > floaterContainer ;
fc - > setParent ( this ) ;
fc - > setAttribute ( Qt : : WA_TransparentForMouseEvents ) ; // click through
fc - > setFocusPolicy ( Qt : : NoFocus ) ; // can't be focused
fc - > move ( 0 , 0 ) ;
fc - > setFixedSize ( this - > size ( ) ) ;
setFloater ( ) ;
}
2022-03-13 06:47:17 -04:00
{ /* Set up recent file entries */ } {
auto fm = ui - > menuFile ;
auto bfr = ui - > actionNew_Window ;
for ( size_t i = 0 ; i < UIState : : MAX_RECENTS ; i + + ) {
auto ac = new QAction ( fm ) ;
ac - > setVisible ( false ) ;
fm - > insertAction ( bfr , ac ) ;
recentFileActions . push_back ( ac ) ;
QObject : : connect ( ac , & QAction : : triggered , ac , [ this , i ] ( ) { openRecentProject ( i ) ; } ) ;
}
fm - > insertSeparator ( bfr ) ;
// update list every time we show this menu
QObject : : connect ( fm , & QMenu : : aboutToShow , fm , [ this ] ( ) {
auto ri = UIState : : recentFiles . begin ( ) ;
auto sz = UIState : : recentFiles . size ( ) ;
for ( size_t i = 0 ; i < UIState : : MAX_RECENTS ; i + + ) {
auto ac = recentFileActions [ i ] ;
if ( i < sz ) {
QFileInfo fi ( * ri ) ;
QString ix = i = = 9 ? qs ( " 1&0 " ) : qs ( " &%1 " ) . arg ( i + 1 ) ;
ac - > setText ( qs ( " %1 %2 " ) . arg ( ix , fi . fileName ( ) ) ) ;
ac - > setVisible ( true ) ;
ri + + ;
} else {
ac - > setVisible ( false ) ;
ac - > setText ( QString ( ) ) ;
}
}
} ) ;
}
2018-12-04 17:04:45 -05:00
{ /* Set up pattern list */ } {
// model
ui - > patternList - > setModel ( new PatternListModel ( ui - > patternList , this ) ) ;
// events
// on selection change
2018-12-19 10:18:32 -05:00
connect ( ui - > patternList - > selectionModel ( ) , & QItemSelectionModel : : currentChanged , this , [ this ] ( const QModelIndex & index , const QModelIndex & old ) {
2018-12-04 17:04:45 -05:00
if ( index = = old ) return ; // no actual change
size_t idx = static_cast < size_t > ( index . row ( ) ) ;
if ( idx > = project - > patterns . size ( ) ) return ;
this - > selectPatternForEditing ( project - > patterns [ idx ] . get ( ) ) ;
} ) ;
// on click
2019-01-09 03:02:58 -05:00
connect ( ui - > patternList , & QListView : : clicked , this , [ this ] { // deselect on sequencer when list clicked
2018-12-04 17:04:45 -05:00
ui - > patternSequencer - > setCurrentIndex ( ui - > patternSequencer - > model ( ) - > index ( 0 , - 1 ) ) ;
} ) ;
// rightclick menu
2018-12-19 10:18:32 -05:00
connect ( ui - > patternList , & QListView : : customContextMenuRequested , this , [ this ] ( const QPoint & pt ) {
2019-06-25 16:58:53 -04:00
auto ind = ui - > patternList - > indexAt ( pt ) ;
size_t idx = static_cast < size_t > ( ind . row ( ) ) ;
2018-12-04 17:04:45 -05:00
std : : shared_ptr < Pattern > p = nullptr ;
if ( idx < project - > patterns . size ( ) ) p = project - > patterns [ idx ] ;
QMenu * menu = new QMenu ( this ) ;
2019-06-25 16:58:53 -04:00
if ( ind . isValid ( ) ) menu - > addAction ( " Rename... " , this , [ this , ind ] { ui - > patternList - > edit ( ind ) ; } ) ;
2019-01-09 03:02:58 -05:00
menu - > addAction ( " New Pattern " , this , [ this , idx ] {
2018-12-07 13:45:32 -05:00
( new ProjectPatternAddCommand ( project , static_cast < int > ( idx ) ) ) - > commit ( ) ;
2018-12-04 17:04:45 -05:00
} ) ;
if ( p ) {
2019-01-09 03:02:58 -05:00
menu - > addAction ( " Duplicate Pattern " , this , [ this , p , idx ] {
2018-12-07 13:45:32 -05:00
( new ProjectPatternAddCommand ( project , static_cast < int > ( idx ) + 1 , - 1 , p ) ) - > commit ( ) ;
2018-12-04 17:04:45 -05:00
} ) ;
menu - > addSeparator ( ) ;
2019-01-09 03:02:58 -05:00
menu - > addAction ( " Properties... " , this , [ this , p ] { openPatternProperties ( p ) ; } ) ;
menu - > addSeparator ( ) ;
menu - > addAction ( " Delete Pattern " , this , [ this , p ] {
2018-12-04 17:04:45 -05:00
if ( QMessageBox : : warning ( this , " Are you sure? " , QString ( " Remove pattern %1? " ) . arg ( Util : : numAndName ( p - > index , p - > name ) ) , QMessageBox : : Yes | QMessageBox : : No , QMessageBox : : No ) ! = QMessageBox : : Yes ) return ;
2018-12-07 13:45:32 -05:00
( new ProjectPatternDeleteCommand ( project , p ) ) - > commit ( ) ;
2018-12-04 17:04:45 -05:00
} ) ;
}
2018-12-25 01:54:23 -05:00
menu - > setAttribute ( Qt : : WA_DeleteOnClose ) ;
2018-12-04 17:04:45 -05:00
menu - > popup ( ui - > patternList - > mapToGlobal ( pt ) ) ;
2018-12-17 14:09:44 -05:00
} ) ; //*/
2018-12-04 17:04:45 -05:00
}
2019-06-20 05:17:30 -04:00
{ /* Set up song info pane */ } {
ui - > expandSongInfo - > setIcon ( ui - > expandSongInfo - > style ( ) - > standardIcon ( QStyle : : SP_ToolBarVerticalExtensionButton ) ) ;
ui - > collapseSongInfo - > setIcon ( ui - > expandSongInfo - > style ( ) - > standardIcon ( QStyle : : SP_ToolBarVerticalExtensionButton ) ) ;
connect ( ui - > expandSongInfo , & QPushButton : : pressed , this , [ this ] { setSongInfoPaneExpanded ( true ) ; } ) ;
connect ( ui - > collapseSongInfo , & QPushButton : : pressed , this , [ this ] { setSongInfoPaneExpanded ( false ) ; } ) ;
connect ( ui - > editArtist , & QLineEdit : : textEdited , this , [ this ] ( const QString & s ) { project - > artist = s ; updateTitle ( ) ; } ) ;
connect ( ui - > editTitle , & QLineEdit : : textEdited , this , [ this ] ( const QString & s ) { project - > title = s ; updateTitle ( ) ; } ) ;
2019-06-20 14:52:30 -04:00
connect ( ui - > editComment , & QPlainTextEdit : : modificationChanged , this , [ this ] ( bool b ) { if ( b ) { project - > comment = ui - > editComment - > document ( ) - > toPlainText ( ) ; ui - > editComment - > document ( ) - > setModified ( false ) ; } } ) ;
2019-06-20 05:17:30 -04:00
connect ( ui - > editTempo , qOverload < double > ( & QDoubleSpinBox : : valueChanged ) , this , [ this ] ( double t ) { project - > tempo = t ; } ) ;
}
2018-12-04 17:04:45 -05:00
{ /* Set up sequencer */ } {
// model
ui - > patternSequencer - > setModel ( new PatternSequencerModel ( ui - > patternSequencer , this ) ) ;
// some metrics that the designer doesn't seem to like doing
ui - > patternSequencer - > horizontalHeader ( ) - > setSectionResizeMode ( QHeaderView : : Fixed ) ;
ui - > patternSequencer - > horizontalHeader ( ) - > setDefaultSectionSize ( 24 ) ;
ui - > patternSequencer - > verticalHeader ( ) - > setSectionResizeMode ( QHeaderView : : Fixed ) ;
ui - > patternSequencer - > verticalHeader ( ) - > setDefaultSectionSize ( 24 ) ;
// events
// on selection change
2018-12-19 10:18:32 -05:00
connect ( ui - > patternSequencer - > selectionModel ( ) , & QItemSelectionModel : : currentChanged , this , [ this ] ( const QModelIndex & index , const QModelIndex & ) {
2018-12-04 17:04:45 -05:00
size_t idx = static_cast < size_t > ( index . column ( ) ) ;
if ( idx > = project - > sequence . size ( ) ) return ;
2019-07-08 04:50:48 -04:00
this - > selectPatternForEditing ( project - > sequence [ idx ] . pattern ( ) . get ( ) ) ;
2018-12-04 17:04:45 -05:00
} ) ;
2019-07-12 03:19:56 -04:00
// middle click
auto mouselmb = [ this , seq = ui - > patternSequencer ] ( QObject * , QEvent * e ) {
if ( e - > type ( ) = = QEvent : : MouseButtonRelease ) {
auto me = static_cast < QMouseEvent * > ( e ) ;
if ( me - > button ( ) = = Qt : : MouseButton : : MiddleButton ) {
auto idx = static_cast < size_t > ( seq - > indexAt ( me - > pos ( ) ) . column ( ) ) ;
if ( idx > = project - > sequence . size ( ) ) return false ; // nothing to remove
auto * c = new ProjectSequencerDeltaCommand ( project ) ;
c - > seq . erase ( c - > seq . begin ( ) + static_cast < ptrdiff_t > ( idx ) ) ;
c - > seqSel = static_cast < int > ( idx ) - 1 ;
return c - > commit ( ) ;
}
}
return false ;
} ;
ui - > patternSequencer - > viewport ( ) - > installEventFilter ( new LambdaEventFilter ( this , mouselmb ) ) ;
2018-12-04 17:04:45 -05:00
// rightclick menu
2018-12-19 10:18:32 -05:00
connect ( ui - > patternSequencer , & QTableView : : customContextMenuRequested , this , [ this ] ( const QPoint & pt ) {
2018-12-04 17:04:45 -05:00
size_t idx = static_cast < size_t > ( ui - > patternSequencer - > indexAt ( pt ) . column ( ) ) ;
2018-11-22 06:36:27 -05:00
2018-12-04 17:04:45 -05:00
QMenu * menu = new QMenu ( this ) ;
2019-01-09 03:02:58 -05:00
menu - > addAction ( " Insert Pattern " , this , [ this , idx ] {
2018-12-04 17:04:45 -05:00
if ( ! editingPattern - > validFor ( project ) ) return ; // nope
int si = static_cast < int > ( std : : min ( idx , project - > sequence . size ( ) ) ) ;
2018-12-07 13:45:32 -05:00
auto * c = new ProjectSequencerDeltaCommand ( project ) ;
2019-07-08 04:50:48 -04:00
c - > seq . insert ( c - > seq . begin ( ) + si , editingPattern ) ;
2018-12-07 13:45:32 -05:00
c - > seqSel = si + 1 ;
c - > commit ( ) ;
2018-12-04 17:04:45 -05:00
} ) ;
2019-01-09 03:02:58 -05:00
menu - > addAction ( " Insert Separator " , this , [ this , idx ] {
2018-12-04 17:04:45 -05:00
int si = static_cast < int > ( std : : min ( idx , project - > sequence . size ( ) ) ) ;
2018-12-07 13:45:32 -05:00
auto * c = new ProjectSequencerDeltaCommand ( project ) ;
2019-07-12 03:19:56 -04:00
c - > seq . insert ( c - > seq . begin ( ) + si , SequenceEntry : : Separator ) ;
2018-12-07 13:45:32 -05:00
c - > seqSel = si + 1 ;
c - > commit ( ) ;
2018-12-04 17:04:45 -05:00
} ) ;
2019-07-08 05:04:12 -04:00
menu - > addAction ( " Insert Loop Point " , this , [ this , idx ] {
int si = static_cast < int > ( std : : min ( idx , project - > sequence . size ( ) ) ) ;
auto * c = new ProjectSequencerDeltaCommand ( project ) ;
c - > seq . insert ( c - > seq . begin ( ) + si , SequenceEntry : : LoopStart ) ;
c - > seqSel = si + 1 ;
c - > commit ( ) ;
} ) ;
menu - > addAction ( " Insert Loop Trigger " , this , [ this , idx ] {
int si = static_cast < int > ( std : : min ( idx , project - > sequence . size ( ) ) ) ;
auto * c = new ProjectSequencerDeltaCommand ( project ) ;
c - > seq . insert ( c - > seq . begin ( ) + si , SequenceEntry : : LoopTrigger ) ;
c - > seqSel = si + 1 ;
c - > commit ( ) ;
} ) ;
2019-01-09 03:02:58 -05:00
if ( idx < project - > sequence . size ( ) ) menu - > addAction ( " Remove " , this , [ this , idx ] {
2018-12-07 13:45:32 -05:00
auto * c = new ProjectSequencerDeltaCommand ( project ) ;
c - > seq . erase ( c - > seq . begin ( ) + static_cast < ptrdiff_t > ( idx ) ) ;
c - > seqSel = static_cast < int > ( idx ) - 1 ;
c - > commit ( ) ;
2018-12-04 17:04:45 -05:00
} ) ;
menu - > addSeparator ( ) ;
2019-01-09 03:02:58 -05:00
menu - > addAction ( " Create New Pattern " , this , [ this , idx ] {
2018-12-04 17:04:45 -05:00
int si = static_cast < int > ( std : : min ( idx , project - > sequence . size ( ) ) ) ;
2018-12-07 13:45:32 -05:00
( new ProjectPatternAddCommand ( project , - 1 , si ) ) - > commit ( ) ;
2018-12-04 17:04:45 -05:00
} ) ;
2019-07-08 04:50:48 -04:00
if ( idx < project - > sequence . size ( ) ) if ( auto p = project - > sequence [ idx ] . pattern ( ) ; p ) {
menu - > addAction ( " Duplicate Pattern " , this , [ this , idx , p ] {
int si = static_cast < int > ( std : : min ( idx + 1 , project - > sequence . size ( ) ) ) ;
( new ProjectPatternAddCommand ( project , static_cast < int > ( p - > index ) + 1 , si , p ) ) - > commit ( ) ;
} ) ;
menu - > addSeparator ( ) ;
menu - > addAction ( " Properties... " , this , [ this , p ] { openPatternProperties ( p ) ; } ) ;
}
2018-12-04 17:04:45 -05:00
2018-12-25 01:54:23 -05:00
menu - > setAttribute ( Qt : : WA_DeleteOnClose ) ;
2018-12-04 17:04:45 -05:00
menu - > popup ( ui - > patternSequencer - > mapToGlobal ( pt ) ) ;
2019-07-12 03:19:56 -04:00
} ) ;
2018-12-04 17:04:45 -05:00
}
{ /* Set up keyboard shortcuts for pattern view */ } {
// Ctrl+PgUp/Down - previous or next pattern in sequencer
2019-07-15 23:51:32 -04:00
auto nxPt = [ this ] ( int off ) {
auto ix = ui - > patternSequencer - > currentIndex ( ) ;
int i = ix . column ( ) ;
int ss = static_cast < int > ( project - > sequence . size ( ) ) ;
if ( ! ix . isValid ( ) ) i = off > 0 ? - 1 : 0 ;
for ( int c = 0 ; c < ss ; c + + ) {
i = ( ss + i + off ) % ss ;
auto & se = project - > sequence [ static_cast < size_t > ( i ) ] ;
if ( auto p = se . pattern ( ) ; p ) {
ui - > patternSequencer - > setCurrentIndex ( ui - > patternSequencer - > model ( ) - > index ( 0 , i ) ) ;
return ;
}
2018-12-04 17:04:45 -05:00
}
2019-07-15 23:51:32 -04:00
} ;
connect ( new QShortcut ( QKeySequence ( " Ctrl+PgUp " ) , ui - > pattern ) , & QShortcut : : activated , this , [ nxPt ] { nxPt ( - 1 ) ; } ) ;
connect ( new QShortcut ( QKeySequence ( " Ctrl+PgDown " ) , ui - > pattern ) , & QShortcut : : activated , this , [ nxPt ] { nxPt ( 1 ) ; } ) ;
2018-12-17 14:09:44 -05:00
/* tmp test
2019-01-09 03:02:58 -05:00
connect ( new QShortcut ( QKeySequence ( " Ctrl+F1 " ) , ui - > patchboard ) , & QShortcut : : activated , this , [ this ] {
2018-12-17 14:09:44 -05:00
auto inp = QInputDialog : : getText ( this , " yes " , " yes " ) ;
WId id = inp . toULongLong ( ) ;
auto * w = QWindow : : fromWinId ( id ) ;
auto * w2 = new QWindow ( ) ;
auto * wc = QWidget : : createWindowContainer ( w2 ) ;
w - > setParent ( w2 ) ;
ui - > patchboard - > layout ( ) - > addWidget ( wc ) ;
} ) ;
*/
2018-12-04 17:04:45 -05:00
}
2018-12-22 21:03:51 -05:00
{ /* Set up patchboard view */ } {
//ui->patchboardView->setDragMode(QGraphicsView::DragMode::RubberBandDrag);
auto * view = ui - > patchboardView ;
2018-12-25 01:54:23 -05:00
bool enableHWAccel = false ; // disabled because QOpenGLWidget has some huge lag issues in this context
if ( enableHWAccel ) {
auto * vp = new QOpenGLWidget ( ) ;
view - > setViewport ( vp ) ; // enable hardware acceleration
}
2020-02-20 04:09:49 -05:00
view - > setRenderHints ( QPainter : : Antialiasing | QPainter : : SmoothPixmapTransform ) ;
2019-07-18 05:11:43 -04:00
// Under OSX these cause Xybrid to crash.
2020-02-20 04:09:49 -05:00
# ifndef __APPLE__
2018-12-25 01:54:23 -05:00
glEnable ( GL_MULTISAMPLE ) ;
glEnable ( GL_LINE_SMOOTH ) ;
2020-02-20 04:09:49 -05:00
# endif
2018-12-25 01:54:23 -05:00
//QGL::FormatOption::Rgba
2018-12-22 21:03:51 -05:00
view - > setAlignment ( Qt : : AlignTop | Qt : : AlignLeft ) ;
view - > setHorizontalScrollBarPolicy ( Qt : : ScrollBarAlwaysOff ) ;
view - > setVerticalScrollBarPolicy ( Qt : : ScrollBarAlwaysOff ) ;
view - > setAttribute ( Qt : : WA_AcceptTouchEvents , true ) ;
QScroller : : grabGesture ( view , QScroller : : MiddleMouseButtonGesture ) ;
{
auto prop = QScroller : : scroller ( view ) - > scrollerProperties ( ) ;
prop . setScrollMetric ( QScrollerProperties : : HorizontalOvershootPolicy , QScrollerProperties : : OvershootAlwaysOff ) ;
prop . setScrollMetric ( QScrollerProperties : : VerticalOvershootPolicy , QScrollerProperties : : OvershootAlwaysOff ) ;
prop . setScrollMetric ( QScrollerProperties : : AxisLockThreshold , 1 ) ;
QScroller : : scroller ( view ) - > setScrollerProperties ( prop ) ;
QScroller : : scroller ( view ) - > setSnapPositionsX ( { } ) ;
QScroller : : scroller ( view ) - > setSnapPositionsY ( { } ) ;
}
// event filter to make drag-to-select only happen on left click
view - > viewport ( ) - > installEventFilter ( new LambdaEventFilter ( view , [ view ] ( QObject * w , QEvent * e ) {
if ( e - > type ( ) = = QEvent : : MouseButtonPress ) {
auto * me = static_cast < QMouseEvent * > ( e ) ;
// initiate drag
2018-12-25 01:54:23 -05:00
if ( me - > button ( ) = = Qt : : LeftButton ) {
view - > setDragMode ( QGraphicsView : : RubberBandDrag ) ;
}
2018-12-22 21:03:51 -05:00
} else if ( e - > type ( ) = = QEvent : : MouseButtonRelease ) { // disable drag after end
2022-03-18 22:59:50 -04:00
QTimer : : singleShot ( 1 , view , [ view ] {
2018-12-25 01:54:23 -05:00
view - > setDragMode ( QGraphicsView : : NoDrag ) ;
} ) ;
2018-12-22 21:03:51 -05:00
}
return w - > QObject : : eventFilter ( w , e ) ;
} ) ) ;
2019-06-23 02:26:52 -04:00
connect ( new QShortcut ( QKeySequence ( " Esc " ) , view ) , & QShortcut : : activated , this , [ this ] { ui - > patchboardBreadcrumbs - > up ( ) ; } ) ;
2018-12-22 21:03:51 -05:00
}
2019-06-16 14:40:09 -04:00
{ /* Set up sample list */ } {
// model
2019-06-16 15:14:46 -04:00
auto mdl = new SampleListModel ( ui - > sampleList , this ) ;
ui - > sampleList - > setModel ( mdl ) ;
connect ( ui - > sampleList - > selectionModel ( ) , & QItemSelectionModel : : currentChanged , this , [ this , mdl ] ( const QModelIndex & ind , const QModelIndex & old [[maybe_unused]] ) {
selectSampleForEditing ( mdl - > itemAt ( ind ) ) ;
} ) ;
2021-11-10 02:13:51 -05:00
// edit pane
connect ( ui - > groupSampleLoop , & QGroupBox : : toggled , this , [ this ] ( bool on ) {
if ( editingSample ) {
if ( on ) {
editingSample - > loopStart = ui - > spinSampleLoopStart - > value ( ) ;
editingSample - > loopEnd = ui - > spinSampleLoopEnd - > value ( ) ;
} else {
editingSample - > loopStart = - 1 ;
editingSample - > loopEnd = - 1 ;
}
2021-11-10 02:42:13 -05:00
ui - > waveformPreview - > update ( ) ;
2021-11-10 02:13:51 -05:00
}
} ) ;
connect ( ui - > spinSampleLoopStart , qOverload < int > ( & QSpinBox : : valueChanged ) , this , [ this ] ( int v ) {
if ( editingSample & & ui - > groupSampleLoop - > isChecked ( ) ) {
editingSample - > loopStart = v ;
2021-11-10 02:42:13 -05:00
ui - > waveformPreview - > update ( ) ;
2021-11-10 02:13:51 -05:00
}
} ) ;
connect ( ui - > spinSampleLoopEnd , qOverload < int > ( & QSpinBox : : valueChanged ) , this , [ this ] ( int v ) {
if ( editingSample & & ui - > groupSampleLoop - > isChecked ( ) ) {
editingSample - > loopEnd = v ;
2021-11-10 02:42:13 -05:00
ui - > waveformPreview - > update ( ) ;
2021-11-10 02:13:51 -05:00
}
} ) ;
2021-11-11 00:47:37 -05:00
connect ( ui - > spinSampleNote , qOverload < int > ( & QSpinBox : : valueChanged ) , this , [ this ] ( int v ) {
auto sp = ui - > spinSampleNote ;
sp - > setSuffix ( " ) " ) ;
sp - > setPrefix ( qs ( " %1 ( " ) . arg ( Util : : noteName ( static_cast < int16_t > ( v ) ) ) ) ;
if ( editingSample ) editingSample - > baseNote = v ;
} ) ;
connect ( ui - > spinSampleNoteSub , qOverload < double > ( & QDoubleSpinBox : : valueChanged ) , this , [ this ] ( double v ) {
auto sp = ui - > spinSampleNoteSub ;
sp - > setPrefix ( v > = 0.0 ? qs ( " + " ) : QString ( ) ) ;
if ( editingSample ) editingSample - > subNote = v ;
} ) ;
emit ui - > spinSampleNoteSub - > valueChanged ( 0.0 ) ; // force refresh
2019-06-16 14:40:09 -04:00
}
2022-03-22 05:15:02 -04:00
// force fonts to display properly
updateFont ( ) ;
2018-12-04 17:04:45 -05:00
// Set up signaling from project to UI
2018-12-17 14:09:44 -05:00
socket - > setParent ( this ) ;
2018-12-07 13:45:32 -05:00
socket - > window = this ;
2018-12-06 14:27:22 -05:00
socket - > undoStack = undoStack ;
2018-12-17 14:09:44 -05:00
connect ( socket , & UISocket : : updatePatternLists , this , & MainWindow : : updatePatternLists ) ;
2018-12-19 10:18:32 -05:00
connect ( socket , & UISocket : : patternUpdated , this , [ this ] ( Pattern * p ) {
2018-12-06 14:27:22 -05:00
if ( editingPattern . get ( ) ! = p ) return ;
ui - > patternEditor - > refresh ( ) ;
} ) ;
2018-12-19 10:18:32 -05:00
connect ( socket , & UISocket : : rowUpdated , this , [ this ] ( Pattern * p , int ch , int r ) {
2018-12-06 14:27:22 -05:00
if ( editingPattern . get ( ) ! = p ) return ;
const auto cpc = PatternEditorModel : : colsPerChannel ;
auto ind = ui - > patternEditor - > model ( ) - > index ( r , ch * cpc ) ;
emit ui - > patternEditor - > model ( ) - > dataChanged ( ind , ind . siblingAtColumn ( ( ch + 1 ) * cpc - 1 ) ) ;
static_cast < PatternEditorModel * > ( ui - > patternEditor - > model ( ) ) - > updateColumnDisplay ( ) ;
} ) ;
2018-12-25 01:54:23 -05:00
connect ( socket , & UISocket : : openGraph , this , [ this ] ( Graph * g ) {
if ( ! g ) return ;
auto gg = std : : static_pointer_cast < Graph > ( g - > shared_from_this ( ) ) ;
2019-06-20 05:35:49 -04:00
QString name = gg - > name ;
if ( name . isEmpty ( ) ) name = gg - > pluginName ( ) ;
2018-12-25 01:54:23 -05:00
ui - > patchboardBreadcrumbs - > push ( name , this , [ this , gg ] {
openGraph ( gg ) ;
} ) ;
} ) ;
2019-06-21 17:50:42 -04:00
connect ( socket , & UISocket : : openNodeUI , this , [ this ] ( Node * n ) {
if ( ! n ) return ;
auto nn = n - > shared_from_this ( ) ;
QString name = nn - > name ;
if ( name . isEmpty ( ) ) name = nn - > pluginName ( ) ;
ui - > patchboardBreadcrumbs - > push ( name , this , [ this , nn ] {
openNodeUI ( nn ) ;
} ) ;
} ) ;
2018-11-23 09:03:35 -05:00
2018-12-19 10:18:32 -05:00
// and from audio engine
2019-01-09 03:02:58 -05:00
connect ( audioEngine , & AudioEngine : : playbackModeChanged , this , [ this , undoAction , redoAction ] {
2018-12-19 10:18:32 -05:00
bool locked = project - > editingLocked ( ) ;
undoAction - > setEnabled ( ! locked ) ;
redoAction - > setEnabled ( ! locked ) ;
2019-06-20 20:16:48 -04:00
bool playingThis = audioEngine - > playbackMode ( ) = = AudioEngine : : Playing & & audioEngine - > playingProject ( ) = = project ;
if ( playingThis ) ui - > playButton - > setIcon ( ui - > playButton - > style ( ) - > standardIcon ( QStyle : : SP_MediaStop ) ) ;
else ui - > playButton - > setIcon ( ui - > playButton - > style ( ) - > standardIcon ( QStyle : : SP_MediaPlay ) ) ;
2018-12-19 10:18:32 -05:00
} ) ;
2019-06-16 15:14:46 -04:00
selectSampleForEditing ( nullptr ) ; // init blank
2022-03-13 22:32:12 -04:00
bool isFirst = false ; //openWindows.empty();
2022-03-13 16:18:35 -04:00
openWindows . insert ( this ) ;
2022-03-13 17:51:06 -04:00
if ( fileName . isEmpty ( ) ) {
// start with a new project
menuFileNew ( ) ;
} else {
openProject ( fileName , isFirst ) ;
if ( ! project ) {
if ( ! isFirst ) close ( ) ;
else menuFileNew ( ) ;
}
}
2018-11-19 15:57:54 -05:00
}
2018-11-22 06:36:27 -05:00
MainWindow : : ~ MainWindow ( ) {
2019-06-26 05:50:40 -04:00
if ( audioEngine - > playingProject ( ) = = project ) audioEngine - > stop ( ) ;
2018-11-19 15:57:54 -05:00
delete ui ;
}
2018-11-28 05:19:10 -05:00
2022-03-13 18:00:02 -04:00
MainWindow * MainWindow : : projectWindow ( const QString & fileName ) {
2022-03-13 20:26:00 -04:00
if ( fileName . isEmpty ( ) ) return nullptr ;
2022-03-13 18:00:02 -04:00
for ( auto w : openWindows ) if ( w - > project & & w - > project - > fileName = = fileName ) return w ;
return nullptr ;
}
2022-03-15 05:43:04 -04:00
void MainWindow : : resizeEvent ( QResizeEvent * e ) {
this - > QMainWindow : : resizeEvent ( e ) ;
ui - > floaterContainer - > setFixedSize ( this - > size ( ) ) ;
}
2022-03-13 07:24:28 -04:00
void MainWindow : : closeEvent ( QCloseEvent * e ) {
if ( promptSave ( ) ) {
e - > ignore ( ) ;
return ;
}
2019-06-14 05:00:01 -04:00
undoStack - > clear ( ) ;
setAttribute ( Qt : : WA_DeleteOnClose ) ; // delete when done
2022-03-13 16:18:35 -04:00
openWindows . erase ( this ) ; // and remove from list now
2022-03-18 22:59:50 -04:00
if ( openWindows . size ( ) = = 0 & & SettingsDialog : : instance ) SettingsDialog : : instance - > reject ( ) ;
2019-06-14 05:00:01 -04:00
}
2018-11-28 05:19:10 -05:00
bool MainWindow : : eventFilter ( QObject * obj [[maybe_unused]] , QEvent * event ) {
if ( event - > type ( ) = = QEvent : : KeyPress ) {
auto ke = static_cast < QKeyEvent * > ( event ) ;
qDebug ( ) < < QString ( " key pressed: %1 " ) . arg ( ( ke - > key ( ) ) ) ;
if ( ke - > key ( ) = = Qt : : Key_Tab ) {
qDebug ( ) < < QString ( " tab " ) ;
auto * t = this - > ui - > tabWidget ;
t - > setTabPosition ( QTabWidget : : TabPosition : : South ) ;
t - > setCurrentIndex ( t - > currentIndex ( ) + 1 ) ;
return true ;
}
}
return false ;
}
2018-12-01 10:41:14 -05:00
2022-03-13 18:38:10 -04:00
void MainWindow : : tryFocus ( ) {
raise ( ) ;
activateWindow ( ) ;
}
2022-03-13 18:00:02 -04:00
2022-03-13 17:51:06 -04:00
void MainWindow : : openProject ( const QString & fileName , bool failSilent ) {
2022-03-13 18:38:10 -04:00
auto pw = projectWindow ( fileName ) ;
if ( pw & & pw ! = this ) {
pw - > tryFocus ( ) ;
return ;
}
2022-03-13 06:47:17 -04:00
auto np = FileOps : : loadProject ( fileName ) ;
if ( ! np ) {
2022-03-13 17:53:48 -04:00
if ( ! failSilent ) QMessageBox : : critical ( this , qs ( " Error " ) , qs ( " Error loading project \" %1 \" . " ) . arg ( QFileInfo ( fileName ) . fileName ( ) ) ) ;
2022-03-13 06:47:17 -04:00
return ;
2018-12-06 07:25:57 -05:00
}
2022-03-13 06:47:17 -04:00
if ( audioEngine - > playingProject ( ) = = project ) audioEngine - > stop ( ) ;
project = np ;
UIState : : addRecentFile ( fileName ) ;
onNewProjectLoaded ( ) ;
}
void MainWindow : : openRecentProject ( size_t idx ) {
2022-03-13 07:24:28 -04:00
if ( promptSave ( ) ) return ;
2022-03-13 06:47:17 -04:00
if ( idx > UIState : : recentFiles . size ( ) ) return ;
auto it = UIState : : recentFiles . begin ( ) ;
for ( size_t i = 0 ; i < idx ; i + + ) it + + ;
openProject ( QString ( * it ) ) ; // need to copy string before opening
2018-12-01 10:41:14 -05:00
}
2022-03-13 07:24:28 -04:00
bool MainWindow : : promptSave ( ) {
2022-03-13 17:51:06 -04:00
if ( ! project ) return false ; // window closing on open
2022-03-13 07:24:28 -04:00
if ( ! undoStack - > isClean ( ) ) {
2022-03-13 07:28:29 -04:00
auto ftxt = project - > fileName . isEmpty ( ) ? qs ( " unsaved project " ) : qs ( " project \" %1 \" " ) . arg ( QFileInfo ( project - > fileName ) . fileName ( ) ) ;
auto r = QMessageBox : : warning ( this , qs ( " Unsaved changes " ) , qs ( " Save changes to %1? " ) . arg ( ftxt ) ,
2022-03-19 18:20:05 -04:00
static_cast < QMessageBox : : StandardButtons > ( QMessageBox : : Yes | QMessageBox : : No | QMessageBox : : Cancel ) ) ;
2022-03-13 07:24:28 -04:00
if ( r = = QMessageBox : : Cancel | | r = = QMessageBox : : Escape ) return true ; // signal abort
if ( r = = QMessageBox : : Yes ) {
menuFileSave ( ) ;
if ( project - > fileName . isEmpty ( ) ) return true ; // save-as-new canceled
}
}
return false ;
}
void MainWindow : : menuFileNew ( ) {
if ( promptSave ( ) ) return ;
auto hold = project ; // keep alive until done
if ( audioEngine - > playingProject ( ) = = project ) audioEngine - > stop ( ) ;
project = FileOps : : newProject ( ) ;
onNewProjectLoaded ( ) ;
}
void MainWindow : : menuFileOpen ( ) {
if ( promptSave ( ) ) return ;
if ( auto fileName = FileOps : : showOpenDialog ( this , " Open project... " , Config : : Directories : : projects , FileOps : : Filter : : project ) ; ! fileName . isEmpty ( ) ) {
openProject ( fileName ) ;
}
}
2018-12-01 10:41:14 -05:00
void MainWindow : : menuFileSave ( ) {
2018-12-06 07:25:57 -05:00
if ( project - > fileName . isEmpty ( ) ) menuFileSaveAs ( ) ;
else {
FileOps : : saveProject ( project ) ;
2018-12-25 01:54:23 -05:00
undoStack - > setClean ( ) ;
2018-12-06 07:25:57 -05:00
}
2018-12-01 10:41:14 -05:00
}
void MainWindow : : menuFileSaveAs ( ) {
2019-07-20 15:25:35 -04:00
QString saveDir = Config : : Directories : : projects ;
if ( ! project - > fileName . isEmpty ( ) ) {
QFileInfo f ( project - > fileName ) ;
saveDir = f . dir ( ) . filePath ( f . baseName ( ) ) ;
}
if ( auto fileName = FileOps : : showSaveAsDialog ( this , " Save project as... " , saveDir , FileOps : : Filter : : project , " xyp " ) ; ! fileName . isEmpty ( ) ) {
2019-01-24 14:14:52 -05:00
FileOps : : saveProject ( project , fileName ) ;
2022-03-13 06:47:17 -04:00
UIState : : addRecentFile ( fileName ) ;
2019-01-24 14:14:52 -05:00
undoStack - > setClean ( ) ;
updateTitle ( ) ;
}
2018-12-01 10:41:14 -05:00
}
2019-07-20 15:25:35 -04:00
void MainWindow : : menuFileExport ( ) {
if ( project - > exportFileName . isEmpty ( ) ) menuFileExportAs ( ) ;
2022-03-15 05:43:04 -04:00
else render ( ) ;
2019-07-20 15:25:35 -04:00
}
void MainWindow : : menuFileExportAs ( ) {
QString saveDir = Config : : Directories : : projects ;
if ( ! project - > fileName . isEmpty ( ) ) {
QFileInfo f ( project - > fileName ) ;
2022-03-15 03:18:20 -04:00
saveDir = f . dir ( ) . filePath ( f . baseName ( ) ) . append ( " .mp3 " ) ;
} else saveDir = saveDir . append ( " /untitled.mp3 " ) ;
2019-07-20 15:28:04 -04:00
if ( auto fileName = FileOps : : showSaveAsDialog ( this , " Save project as... " , saveDir , FileOps : : Filter : : audioOut , " mp3 " ) ; ! fileName . isEmpty ( ) ) {
2019-07-20 15:25:35 -04:00
project - > exportFileName = fileName ;
2022-03-15 05:43:04 -04:00
render ( ) ;
2019-07-20 15:25:35 -04:00
}
}
2022-03-15 05:43:04 -04:00
void MainWindow : : render ( ) {
std : : vector < QWidget * > dis {
ui - > pattern , ui - > patchboard , ui - > samples ,
ui - > menuBar , ui - > playButton
} ;
for ( auto w : dis ) w - > setEnabled ( false ) ;
setFloater ( ui - > floaterRendering ) ;
audioEngine - > render ( project , project - > exportFileName ) ;
if ( openWindows . find ( this ) = = openWindows . end ( ) ) return ; // don't try to update UI if the window has been disposed
setFloater ( ) ;
for ( auto w : dis ) w - > setEnabled ( true ) ;
}
2019-06-20 05:17:30 -04:00
void MainWindow : : menuFileNewWindow ( ) {
auto w = new MainWindow ( ) ;
w - > show ( ) ;
}
2022-03-18 22:59:50 -04:00
void MainWindow : : menuSettings ( ) {
SettingsDialog : : tryOpen ( ) ;
}
2022-03-13 16:34:07 -04:00
void MainWindow : : menuQuit ( ) {
auto c = openWindows . size ( ) ;
if ( c > 1 ) { // prompt if more than just this window
auto r = QMessageBox : : warning ( this , qs ( " Quit " ) , qs ( " Close %1 open projects? " ) . arg ( c ) ,
2022-03-19 18:20:05 -04:00
static_cast < QMessageBox : : StandardButtons > ( QMessageBox : : Yes | QMessageBox : : No ) ) ;
2022-03-13 16:34:07 -04:00
if ( r = = QMessageBox : : No | | r = = QMessageBox : : Escape ) return ;
}
// assemble list
std : : vector < MainWindow * > cl ;
cl . push_back ( this ) ;
for ( auto w : openWindows ) if ( w ! = this ) cl . push_back ( w ) ;
// and close
for ( auto w : cl ) w - > close ( ) ;
}
2018-12-01 10:41:14 -05:00
void MainWindow : : onNewProjectLoaded ( ) {
2018-12-06 14:27:22 -05:00
undoStack - > clear ( ) ;
2018-12-17 14:09:44 -05:00
project - > socket = socket ;
2018-12-08 07:01:45 -05:00
updatePatternLists ( ) ;
patternSelection ( 0 ) ;
sequenceSelection ( - 1 ) ;
2019-07-08 04:50:48 -04:00
for ( size_t i = 0 ; i < project - > sequence . size ( ) ; i + + ) { // find first actual pattern in sequence, else fall back to first pattern numerically
if ( ! project - > sequence [ i ] . pattern ( ) ) continue ;
2018-12-08 07:01:45 -05:00
sequenceSelection ( static_cast < int > ( i ) ) ;
2018-12-01 10:41:14 -05:00
break ;
}
2018-12-06 14:27:22 -05:00
2018-12-25 01:54:23 -05:00
//openGraph(project->rootGraph);
ui - > patchboardBreadcrumbs - > clear ( ) ;
ui - > patchboardBreadcrumbs - > push ( " / " , this , [ this , gg = project - > rootGraph ] {
openGraph ( gg ) ;
} ) ;
2018-12-22 21:03:51 -05:00
2019-06-20 05:17:30 -04:00
ui - > editTempo - > setValue ( project - > tempo ) ;
ui - > editArtist - > setText ( project - > artist ) ;
ui - > editTitle - > setText ( project - > title ) ;
2019-06-20 14:52:30 -04:00
ui - > editComment - > document ( ) - > setPlainText ( project - > comment ) ;
2019-06-20 05:17:30 -04:00
2018-12-06 14:27:22 -05:00
updateTitle ( ) ;
2019-06-20 05:17:30 -04:00
setSongInfoPaneExpanded ( false ) ;
2019-06-16 14:40:09 -04:00
2022-03-16 04:13:18 -04:00
if ( ui - > tabWidget - > currentWidget ( ) = = ui - > patchboard ) ui - > patchboardView - > setFocus ( ) ;
2019-06-16 14:40:09 -04:00
emit projectLoaded ( ) ;
2018-12-01 10:41:14 -05:00
}
2018-12-07 13:45:32 -05:00
int MainWindow : : patternSelection ( int n ) {
auto i = ui - > patternList - > currentIndex ( ) ;
if ( n > = - 1 ) ui - > patternList - > setCurrentIndex ( ui - > patternList - > model ( ) - > index ( n , 0 ) ) ;
return i . isValid ( ) ? i . row ( ) : - 1 ;
}
int MainWindow : : sequenceSelection ( int n ) {
auto i = ui - > patternSequencer - > currentIndex ( ) ;
if ( n > = - 1 ) ui - > patternSequencer - > setCurrentIndex ( ui - > patternSequencer - > model ( ) - > index ( 0 , n ) ) ;
return i . isValid ( ) ? i . column ( ) : - 1 ;
}
2018-12-19 10:18:32 -05:00
void MainWindow : : playbackPosition ( int seq , int row ) {
sequenceSelection ( seq ) ;
2019-06-14 05:00:01 -04:00
if ( ui - > patternEditor - > isFolded ( ) & & editingPattern - > fold > 1 ) row - = row % editingPattern - > fold ;
2019-07-20 01:09:59 -04:00
auto mi = ui - > patternEditor - > currentIndex ( ) . siblingAtRow ( row + 1 ) ;
2019-06-14 05:00:01 -04:00
2019-07-20 01:09:59 -04:00
if ( ! mi . isValid ( ) ) mi = ui - > patternEditor - > model ( ) - > index ( row + 1 , 0 ) ;
2018-12-19 10:18:32 -05:00
ui - > patternEditor - > setCurrentIndex ( mi ) ;
ui - > patternEditor - > selectionModel ( ) - > select ( QItemSelection ( mi . siblingAtColumn ( 0 ) , mi . siblingAtColumn ( ui - > patternEditor - > horizontalHeader ( ) - > count ( ) - 1 ) ) , QItemSelectionModel : : SelectionFlag : : ClearAndSelect ) ;
ui - > patternEditor - > scrollTo ( mi , QAbstractItemView : : PositionAtCenter ) ;
}
2018-12-04 17:04:45 -05:00
void MainWindow : : updatePatternLists ( ) {
emit ui - > patternList - > model ( ) - > layoutChanged ( ) ;
emit ui - > patternSequencer - > model ( ) - > layoutChanged ( ) ;
2018-12-07 13:45:32 -05:00
if ( auto i = ui - > patternList - > currentIndex ( ) ; i . isValid ( ) & & ! ui - > patternSequencer - > currentIndex ( ) . isValid ( ) )
selectPatternForEditing ( project - > patterns [ static_cast < size_t > ( i . row ( ) ) ] . get ( ) ) ; // make sure pattern editor matches selection
2018-12-04 17:04:45 -05:00
if ( editingPattern & & ! editingPattern - > validFor ( project ) ) // if current pattern invalidated, select new one
selectPatternForEditing ( project - > patterns [ std : : min ( editingPattern - > index , project - > patterns . size ( ) - 1 ) ] . get ( ) ) ;
}
2018-12-06 14:27:22 -05:00
void MainWindow : : updateTitle ( ) {
2019-06-14 05:00:01 -04:00
if ( ! project ) return ;
2019-06-20 05:17:30 -04:00
QString songTitle ;
if ( project - > title . isEmpty ( ) ) {
if ( project - > fileName . isEmpty ( ) ) songTitle = qs ( " (new project) " ) ;
else songTitle = QFileInfo ( project - > fileName ) . baseName ( ) ;
} else {
2022-03-18 22:59:50 -04:00
if ( ! project - > artist . isEmpty ( ) ) songTitle = qs ( " %1 - %2 " ) . arg ( project - > artist , project - > title ) ;
2019-06-20 05:17:30 -04:00
else songTitle = project - > title ;
}
2019-06-26 00:24:29 -04:00
if ( ! undoStack - > isClean ( ) ) songTitle . append ( " (modified) " ) ;
2019-06-20 05:17:30 -04:00
ui - > labelSongInfo - > setText ( songTitle ) ;
2019-06-26 05:50:40 -04:00
setWindowTitle ( qs ( " Xybrid - " ) % songTitle ) ;
2019-06-20 05:17:30 -04:00
}
2022-03-22 05:15:02 -04:00
void MainWindow : : updateFont ( ) {
QString font = qs ( " Iosevka Term Light " ) ;
double pt = 10.0 ;
QString fstr = qs ( " font: %2pt \" %1 \" ; " ) . arg ( font ) . arg ( pt ) ;
QString tfstr = qs ( " QTableView { %1 } " ) . arg ( fstr ) ;
QString hfstr = qs ( " QHeaderView { %1 } " ) . arg ( fstr ) ;
ui - > patternSequencer - > setStyleSheet ( tfstr ) ;
ui - > patternEditor - > setStyleSheet ( tfstr ) ;
ui - > patternEditor - > verticalHeader ( ) - > setStyleSheet ( hfstr ) ;
}
2019-06-20 05:17:30 -04:00
void MainWindow : : setSongInfoPaneExpanded ( bool open ) {
if ( open ) {
ui - > songInfoPane - > setCurrentIndex ( 1 ) ;
} else {
ui - > songInfoPane - > setCurrentIndex ( 0 ) ;
2019-06-20 14:52:30 -04:00
ui - > patternEditor - > setFocus ( Qt : : FocusReason : : ShortcutFocusReason ) ;
2019-06-20 05:17:30 -04:00
}
auto s = ui - > songInfoPane - > currentWidget ( ) - > minimumHeight ( ) ;
ui - > songInfoPane - > setMinimumHeight ( s ) ;
ui - > songInfoPane - > setMaximumHeight ( s ) ;
2018-12-06 14:27:22 -05:00
}
2022-03-15 05:43:04 -04:00
void MainWindow : : setFloater ( QWidget * w ) {
auto idx = ui - > floaterContainer - > indexOf ( w ) ;
if ( idx < 0 ) idx = 0 ;
ui - > floaterContainer - > setCurrentIndex ( idx ) ;
}
2018-12-01 10:41:14 -05:00
bool MainWindow : : selectPatternForEditing ( Pattern * pattern ) {
2018-12-04 17:04:45 -05:00
if ( ! pattern | | pattern = = editingPattern . get ( ) ) return false ; // no u
2018-12-01 10:41:14 -05:00
if ( pattern - > project ! = project . get ( ) ) return false ; // wrong project
if ( project - > patterns . size ( ) < = pattern - > index ) return false ; // invalid id
auto sp = project - > patterns [ pattern - > index ] ;
if ( sp . get ( ) ! = pattern ) return false ; // wrong index
auto hold = editingPattern ; // keep alive until done
editingPattern = sp ;
2018-12-04 17:04:45 -05:00
ui - > patternEditor - > setPattern ( editingPattern ) ;
ui - > patternList - > setCurrentIndex ( ui - > patternList - > model ( ) - > index ( static_cast < int > ( editingPattern - > index ) , 0 ) ) ;
2018-12-01 10:41:14 -05:00
return true ;
}
2018-12-22 21:03:51 -05:00
2019-06-16 15:14:46 -04:00
void MainWindow : : selectSampleForEditing ( std : : shared_ptr < Xybrid : : Data : : Sample > smp ) {
if ( ! smp | | smp - > project ! = project . get ( ) ) { // no valid sample selected
2021-11-10 02:13:51 -05:00
editingSample = nullptr ;
2019-06-16 15:14:46 -04:00
ui - > sampleViewPane - > setEnabled ( false ) ;
2019-06-17 02:23:45 -04:00
ui - > sampleInfo - > setText ( qs ( " (no sample selected) " ) ) ;
2021-11-10 02:13:51 -05:00
ui - > groupSampleLoop - > setChecked ( false ) ;
ui - > spinSampleLoopStart - > setValue ( 0 ) ;
ui - > spinSampleLoopEnd - > setValue ( 0 ) ;
2021-11-11 00:47:37 -05:00
ui - > spinSampleNote - > setValue ( 60 ) ;
ui - > spinSampleNoteSub - > setValue ( 0.0 ) ;
2019-06-16 15:14:46 -04:00
} else {
2021-11-10 02:42:13 -05:00
editingSample = nullptr ;
2019-06-16 15:14:46 -04:00
ui - > sampleViewPane - > setEnabled ( true ) ;
2019-06-17 02:23:45 -04:00
ui - > sampleInfo - > setText (
qs ( " %1 // %2 \n %3 %4Hz, %5 frames (%6) " )
2021-11-10 02:13:51 -05:00
. arg ( smp - > name . section ( ' / ' , - 1 , - 1 ) ,
smp - > uuid . toString ( ) ,
smp - > numChannels ( ) = = 2 ? qs ( " Stereo " ) : qs ( " Mono " ) )
2019-06-17 02:23:45 -04:00
. arg ( smp - > sampleRate )
. arg ( smp - > length ( ) )
. arg ( Util : : sampleLength ( smp - > sampleRate , smp - > length ( ) ) )
) ;
2021-11-10 02:13:51 -05:00
ui - > spinSampleLoopStart - > setRange ( 0 , smp - > length ( ) ) ;
ui - > spinSampleLoopEnd - > setRange ( 0 , smp - > length ( ) ) ;
if ( smp - > loopStart < 0 ) { // loop disabled
ui - > groupSampleLoop - > setChecked ( false ) ;
ui - > spinSampleLoopStart - > setValue ( 0 ) ;
ui - > spinSampleLoopEnd - > setValue ( smp - > length ( ) ) ;
} else {
ui - > groupSampleLoop - > setChecked ( true ) ;
ui - > spinSampleLoopStart - > setValue ( smp - > loopStart ) ;
ui - > spinSampleLoopEnd - > setValue ( smp - > loopEnd ) ;
}
2021-11-10 02:42:13 -05:00
2021-11-11 00:47:37 -05:00
ui - > spinSampleNote - > setValue ( smp - > baseNote ) ;
ui - > spinSampleNoteSub - > setValue ( smp - > subNote ) ;
2021-11-10 02:42:13 -05:00
editingSample = smp ;
2019-06-16 15:14:46 -04:00
}
2021-11-10 02:13:51 -05:00
2019-06-17 02:23:45 -04:00
ui - > waveformPreview - > setSample ( smp ) ;
2019-06-16 15:14:46 -04:00
}
2018-12-22 21:03:51 -05:00
void MainWindow : : openGraph ( const std : : shared_ptr < Data : : Graph > & g ) {
if ( ! g ) return ; // invalid
2018-12-25 01:54:23 -05:00
QPointF scrollPt ( g - > viewX , g - > viewY ) ;
2019-06-26 05:50:40 -04:00
//delete ui->patchboardView->scene();
if ( auto s = ui - > patchboardView - > scene ( ) ; s ) s - > deleteLater ( ) ;
2019-01-25 20:07:04 -05:00
//ui->patchboardView->setScene(nullptr);
2018-12-22 21:03:51 -05:00
ui - > patchboardView - > setScene ( new PatchboardScene ( ui - > patchboardView , g ) ) ;
2019-06-23 02:26:52 -04:00
auto sz = ui - > patchboardView - > viewport ( ) - > visibleRegion ( ) . boundingRect ( ) . size ( ) ;
ui - > patchboardView - > centerOn ( scrollPt + QPointF ( sz . width ( ) / 2 , sz . height ( ) / 2 ) ) ;
2018-12-22 21:03:51 -05:00
}
2019-01-09 03:02:58 -05:00
2019-06-21 17:50:42 -04:00
void MainWindow : : openNodeUI ( const std : : shared_ptr < Data : : Node > & n ) {
if ( ! n ) return ;
2019-06-26 05:50:40 -04:00
//delete ui->patchboardView->scene();
if ( auto s = ui - > patchboardView - > scene ( ) ; s ) s - > deleteLater ( ) ;
2019-06-21 17:50:42 -04:00
ui - > patchboardView - > setScene ( new NodeUIScene ( ui - > patchboardView , n ) ) ;
2019-06-23 13:24:57 -04:00
// scene handles initial scroll; don't need to do it here
2019-06-21 17:50:42 -04:00
}
2019-01-09 03:02:58 -05:00
void MainWindow : : openPatternProperties ( const std : : shared_ptr < Xybrid : : Data : : Pattern > & p ) {
if ( ! p | | p - > project ! = project . get ( ) ) return ;
auto dlg = new QDialog ( this ) ;
dlg - > setWindowTitle ( " Pattern Properties " ) ;
dlg - > setModal ( true ) ;
dlg - > setAttribute ( Qt : : WA_DeleteOnClose ) ;
dlg - > setSizePolicy ( QSizePolicy ( QSizePolicy : : Fixed , QSizePolicy : : Fixed ) ) ;
//dlg->resize(320, 200);
auto dl = new QVBoxLayout ( ) ;
dlg - > setLayout ( dl ) ;
//dlg->layout()->setMargin(3);
2019-06-20 05:17:30 -04:00
auto eName = new QLineEdit ( p - > name ) ;
2019-01-09 03:02:58 -05:00
dl - > addWidget ( eName ) ;
//dlg->layout()->setAlignment(eName, Qt::AlignTop);
auto gLength = new QHBoxLayout ( ) ;
dl - > addLayout ( gLength ) ;
gLength - > addWidget ( new QLabel ( " Length " ) ) ;
auto gLengthBox = new QSpinBox ( ) ;
gLengthBox - > setRange ( 1 , 1024 ) ;
gLengthBox - > setValue ( p - > rows ) ;
gLengthBox - > setSizePolicy ( QSizePolicy : : Expanding , QSizePolicy : : Fixed ) ;
gLength - > addWidget ( gLengthBox ) ;
2019-06-14 05:00:01 -04:00
gLength - > addWidget ( new QLabel ( " Fold " ) ) ;
auto gFoldBox = new QSpinBox ( ) ;
gFoldBox - > setRange ( 0 , 128 ) ;
gFoldBox - > setValue ( p - > fold ) ;
gFoldBox - > setSizePolicy ( QSizePolicy : : Fixed , QSizePolicy : : Fixed ) ;
gLength - > addWidget ( gFoldBox ) ;
2019-01-09 03:02:58 -05:00
auto gTime = new QHBoxLayout ( ) ;
dl - > addLayout ( gTime ) ;
auto gTimeBoxW = 60 ;
auto gTimeBoxMax = 32 ;
gTime - > addWidget ( new QLabel ( " Beats " ) ) ;
auto gTimeBeats = new QSpinBox ( ) ;
gTimeBeats - > setRange ( 1 , gTimeBoxMax ) ;
gTimeBeats - > setValue ( p - > time . beatsPerMeasure ) ;
gTimeBeats - > setMaximumWidth ( gTimeBoxW ) ;
gTime - > addWidget ( gTimeBeats ) ;
gTime - > addWidget ( new QLabel ( " Rows " ) ) ;
auto gTimeRows = new QSpinBox ( ) ;
gTimeRows - > setRange ( 1 , gTimeBoxMax ) ;
gTimeRows - > setValue ( p - > time . rowsPerBeat ) ;
gTimeRows - > setMaximumWidth ( gTimeBoxW ) ;
gTime - > addWidget ( gTimeRows ) ;
gTime - > addWidget ( new QLabel ( " Ticks " ) ) ;
auto gTimeTicks = new QSpinBox ( ) ;
gTimeTicks - > setRange ( 1 , gTimeBoxMax ) ;
gTimeTicks - > setValue ( p - > time . ticksPerRow ) ;
gTimeTicks - > setMaximumWidth ( gTimeBoxW ) ;
gTime - > addWidget ( gTimeTicks ) ;
auto bbox = new QDialogButtonBox ( QDialogButtonBox : : Ok | QDialogButtonBox : : Cancel ) ;
dl - > addWidget ( bbox ) ;
connect ( bbox , & QDialogButtonBox : : rejected , dlg , & QDialog : : reject ) ;
connect ( bbox , & QDialogButtonBox : : accepted , dlg , [ = ] {
auto cc = new CompositeCommand ( ) ;
2019-06-20 05:17:30 -04:00
if ( auto n = eName - > text ( ) ; n ! = p - > name ) cc - > compose ( new PatternRenameCommand ( p , n ) ) ;
2019-01-09 03:02:58 -05:00
if ( auto t = Data : : TimeSignature ( gTimeBeats - > value ( ) , gTimeRows - > value ( ) , gTimeTicks - > value ( ) ) ; t ! = p - > time )
cc - > compose ( new PatternTimeSignatureCommand ( p , t ) ) ;
if ( auto nr = gLengthBox - > value ( ) ; nr ! = p - > rows ) {
if ( nr < p - > rows ) { // preserve contents beyond cutoff
for ( auto r = nr ; r < p - > rows ; r + + ) {
for ( auto c = 0 ; c < static_cast < int > ( p - > numChannels ( ) ) ; c + + ) {
cc - > compose ( new PatternDeltaCommand ( p , c , r ) ) ;
}
}
}
cc - > compose ( new PatternLengthCommand ( p , nr ) ) ;
}
2019-06-14 05:00:01 -04:00
if ( auto f = gFoldBox - > value ( ) ; f ! = p - > fold ) cc - > compose ( new PatternFoldCommand ( p , f ) ) ;
2019-01-09 03:02:58 -05:00
cc - > commit ( " edit pattern properties " ) ;
dlg - > accept ( ) ;
} ) ;
dlg - > show ( ) ;
}