1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 3 /* 4 Rosegarden 5 A MIDI and audio sequencer and musical notation editor. 6 Copyright 2000-2021 the Rosegarden development team. 7 8 Other copyrights also apply to some parts of this work. Please 9 see the AUTHORS file and individual file headers for details. 10 11 This program is free software; you can redistribute it and/or 12 modify it under the terms of the GNU General Public License as 13 published by the Free Software Foundation; either version 2 of the 14 License, or (at your option) any later version. See the file 15 COPYING included with this distribution for more information. 16 */ 17 18 19 #include "TupletDialog.h" 20 #include <QLayout> 21 22 #include "base/NotationTypes.h" 23 #include "gui/editors/notation/NotationStrings.h" 24 #include "gui/editors/notation/NotePixmapFactory.h" 25 #include <QComboBox> 26 #include <QDialog> 27 #include <QDialogButtonBox> 28 #include <QCheckBox> 29 #include <QFrame> 30 #include <QGridLayout> 31 #include <QGroupBox> 32 #include <QLabel> 33 #include <QObject> 34 #include <QString> 35 #include <QWidget> 36 #include <QVBoxLayout> 37 #include <QUrl> 38 #include <QDesktopServices> 39 40 41 namespace Rosegarden 42 { 43 44 TupletDialog::TupletDialog(QWidget *parent, Note::Type defaultUnitType, 45 timeT maxDuration) : 46 QDialog(parent), 47 m_maxDuration(maxDuration) 48 { 49 //setHelp("nv-tuplets"); 50 setModal(true); 51 setWindowTitle(tr("Tuplet")); 52 53 QGridLayout *metagrid = new QGridLayout; 54 setLayout(metagrid); 55 QWidget *vbox = new QWidget(this); 56 QVBoxLayout *vboxLayout = new QVBoxLayout; 57 metagrid->addWidget(vbox, 0, 0); 58 59 60 QGroupBox *timingBox = new QGroupBox( tr("New timing for tuplet group"), vbox ); 61 timingBox->setContentsMargins(5, 5, 5, 5); 62 QGridLayout *timingLayout = new QGridLayout(timingBox); 63 timingLayout->setSpacing(5); 64 vboxLayout->addWidget(timingBox); 65 66 if (m_maxDuration > 0) { 67 68 // bit of a sanity check 69 if (maxDuration < Note(Note::Semiquaver).getDuration()) { 70 maxDuration = Note(Note::Semiquaver).getDuration(); 71 } 72 73 Note::Type maxUnitType = 74 Note::getNearestNote(maxDuration / 2, 0).getNoteType(); 75 if (defaultUnitType > maxUnitType) 76 defaultUnitType = maxUnitType; 77 } 78 79 int timingRow = 0; 80 81 m_hasTimingAlready = new QCheckBox 82 (tr("Timing is already correct: update display only"), timingBox); 83 m_hasTimingAlready->setChecked(false); 84 timingLayout->addWidget(m_hasTimingAlready, timingRow, 0, 1, 3); 85 86 timingLayout->addWidget(new QLabel(tr("Play "), timingBox), ++timingRow, 0); 87 88 m_untupledCombo = new QComboBox(timingBox); 89 timingLayout->addWidget(m_untupledCombo, timingRow, 1); 90 91 m_unitCombo = new QComboBox(timingBox); 92 timingLayout->addWidget(m_unitCombo, timingRow, 2); 93 94 for (Note::Type t = Note::Shortest; t <= Note::Longest; ++t) { 95 Note note(t); 96 timeT duration(note.getDuration()); 97 if (maxDuration > 0 && (2 * duration > maxDuration)) 98 break; 99 timeT e; // error factor, ignore 100 m_unitCombo->addItem(NotePixmapFactory::makeNoteMenuPixmap(duration, e), 101 NotationStrings::makeNoteMenuLabel(duration, false, e, true)); 102 if (defaultUnitType == t) { 103 m_unitCombo->setCurrentIndex(m_unitCombo->count() - 1); 104 } 105 } 106 107 timingLayout->addWidget(new QLabel(tr("in the time of "), timingBox), ++timingRow, 0); 108 109 m_tupledCombo = new QComboBox(timingBox); 110 timingLayout->addWidget(m_tupledCombo, timingRow, 1); 111 112 timingBox->setLayout(timingLayout); 113 114 connect(m_hasTimingAlready, &QAbstractButton::clicked, this, &TupletDialog::slotHasTimingChanged); 115 116 updateUntupledCombo(); 117 updateTupledCombo(); 118 119 m_timingDisplayGrid = new QGroupBox( tr("Timing calculations"), vbox ); 120 QGridLayout *timingDisplayLayout = new QGridLayout(m_timingDisplayGrid); 121 vboxLayout->addWidget(m_timingDisplayGrid); 122 123 int row = 0; 124 125 if (maxDuration > 0) { 126 127 timingDisplayLayout->addWidget(new QLabel(tr("Selected region:")), 128 row, 0); 129 timingDisplayLayout->addWidget(new QLabel(""), row, 1); 130 m_selectionDurationDisplay = new QLabel("x"); 131 timingDisplayLayout->addWidget(m_selectionDurationDisplay, row, 2); 132 m_selectionDurationDisplay->setAlignment(Qt::AlignVCenter | 133 Qt::AlignRight); 134 row++; 135 } else { 136 m_selectionDurationDisplay = nullptr; 137 } 138 139 timingDisplayLayout->addWidget(new QLabel(tr("Group with current timing:")), 140 row, 0); 141 m_untupledDurationCalculationDisplay = new QLabel("x"); 142 timingDisplayLayout->addWidget(m_untupledDurationCalculationDisplay, 143 row, 1); 144 m_untupledDurationDisplay = new QLabel("x"); 145 m_untupledDurationDisplay->setAlignment(Qt::AlignVCenter | 146 Qt::AlignRight); 147 timingDisplayLayout->addWidget(m_untupledDurationDisplay, row, 2); 148 row++; 149 150 timingDisplayLayout->addWidget(new QLabel(tr("Group with new timing:")), 151 row, 0); 152 m_tupledDurationCalculationDisplay = new QLabel("x"); 153 timingDisplayLayout->addWidget(m_tupledDurationCalculationDisplay, 154 row, 1); 155 m_tupledDurationDisplay = new QLabel("x"); 156 m_tupledDurationDisplay->setAlignment(Qt::AlignVCenter | 157 Qt::AlignRight); 158 timingDisplayLayout->addWidget(m_tupledDurationDisplay, row, 2); 159 row++; 160 161 timingDisplayLayout->addWidget(new QLabel(tr("Gap created by timing change:")), 162 row, 0); 163 m_newGapDurationCalculationDisplay = new QLabel("x"); 164 timingDisplayLayout->addWidget(m_newGapDurationCalculationDisplay, 165 row, 1); 166 m_newGapDurationDisplay = new QLabel("x"); 167 m_newGapDurationDisplay->setAlignment(Qt::AlignVCenter | 168 Qt::AlignRight); 169 timingDisplayLayout->addWidget(m_newGapDurationDisplay, row, 2); 170 row++; 171 172 if (maxDuration > 0) { 173 174 timingDisplayLayout->addWidget(new QLabel(tr("Unchanged at end of selection:")), 175 row, 0); 176 m_unchangedDurationCalculationDisplay = new QLabel("x"); 177 timingDisplayLayout->addWidget(m_unchangedDurationCalculationDisplay, 178 row, 1); 179 m_unchangedDurationDisplay = new QLabel("x"); 180 m_unchangedDurationDisplay->setAlignment(Qt::AlignVCenter | 181 Qt::AlignRight); 182 timingDisplayLayout->addWidget(m_unchangedDurationDisplay, row, 2); 183 184 } else { 185 m_unchangedDurationDisplay = nullptr; 186 } 187 188 m_timingDisplayGrid->setLayout(timingDisplayLayout); 189 190 vbox->setLayout(vboxLayout); 191 192 updateTimingDisplays(); 193 194 QObject::connect(m_unitCombo, SIGNAL(activated(const QString &)), 195 this, SLOT(slotUnitChanged(const QString &))); 196 197 QObject::connect(m_untupledCombo, SIGNAL(activated(const QString &)), 198 this, SLOT(slotUntupledChanged(const QString &))); 199 QObject::connect(m_untupledCombo, SIGNAL(textChanged(const QString &)), 200 this, SLOT(slotUntupledChanged(const QString &))); 201 202 QObject::connect(m_tupledCombo, SIGNAL(activated(const QString &)), 203 this, SLOT(slotTupledChanged(const QString &))); 204 QObject::connect(m_tupledCombo, SIGNAL(textChanged(const QString &)), 205 this, SLOT(slotTupledChanged(const QString &))); 206 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help); 207 metagrid->addWidget(buttonBox, 1, 0); 208 metagrid->setRowStretch(0, 10); 209 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 210 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 211 connect(buttonBox, &QDialogButtonBox::helpRequested, this, &TupletDialog::slotHelpRequested); 212 } 213 214 void 215 TupletDialog::slotHasTimingChanged() 216 { 217 updateUntupledCombo(); 218 updateTupledCombo(); 219 m_timingDisplayGrid->setEnabled(!m_hasTimingAlready->isChecked()); 220 } 221 222 Note::Type 223 TupletDialog::getUnitType() const 224 { 225 return Note::Shortest + m_unitCombo->currentIndex(); 226 } 227 228 int 229 TupletDialog::getUntupledCount() const 230 { 231 bool isNumeric = true; 232 int count = m_untupledCombo->currentText().toInt(&isNumeric); 233 if (count == 0 || !isNumeric) 234 return 1; 235 else 236 return count; 237 } 238 239 int 240 TupletDialog::getTupledCount() const 241 { 242 bool isNumeric = true; 243 int count = m_tupledCombo->currentText().toInt(&isNumeric); 244 if (count == 0 || !isNumeric) 245 return 1; 246 else 247 return count; 248 } 249 250 bool 251 TupletDialog::hasTimingAlready() const 252 { 253 return m_hasTimingAlready->isChecked(); 254 } 255 256 void 257 TupletDialog::updateUntupledCombo() 258 { 259 // Untupled combo can contain numbers up to the maximum 260 // duration divided by the unit duration. If there's no 261 // maximum, we'll have to put in some likely values and 262 // allow the user to edit it. Both the numerical combos 263 // should possibly be spinboxes, except I think I like 264 // being able to "suggest" a few values 265 266 int maximum = 12; 267 268 if (m_maxDuration) { 269 if (m_hasTimingAlready->isChecked()) { 270 maximum = (m_maxDuration * 2) / Note(getUnitType()).getDuration(); 271 } else { 272 maximum = m_maxDuration / Note(getUnitType()).getDuration(); 273 } 274 } 275 276 QString previousText = m_untupledCombo->currentText(); 277 if (previousText.toInt() == 0) { 278 if (maximum < 3) 279 previousText = QString("%1").arg(maximum); 280 else 281 previousText = "3"; 282 } 283 284 m_untupledCombo->clear(); 285 bool setText = false; 286 287 for (int i = 1; i <= maximum; ++i) { 288 QString text = QString("%1").arg(i); 289 m_untupledCombo->addItem(text); 290 if (m_hasTimingAlready->isChecked()) { 291 if (i == (m_maxDuration * 3) / (Note(getUnitType()).getDuration()*2)) { 292 m_untupledCombo->setCurrentIndex(m_untupledCombo->count() - 1); 293 } 294 } else if (text == previousText) { 295 m_untupledCombo->setCurrentIndex(m_untupledCombo->count() - 1); 296 setText = true; 297 } 298 } 299 300 if (!setText) { 301 m_untupledCombo->setEditText(previousText); 302 } 303 } 304 305 void 306 TupletDialog::updateTupledCombo() 307 { 308 // should contain all positive integers less than the 309 // largest value in the untupled combo. In principle 310 // we can support values larger, but we can't quite 311 // do the tupleting transformation yet 312 313 int untupled = getUntupledCount(); 314 315 QString previousText = m_tupledCombo->currentText(); 316 if (previousText.toInt() == 0 || 317 previousText.toInt() > untupled) { 318 if (untupled < 2) 319 previousText = QString("%1").arg(untupled); 320 else 321 previousText = "2"; 322 } 323 324 m_tupledCombo->clear(); 325 326 for (int i = 1; i < untupled; ++i) { 327 QString text = QString("%1").arg(i); 328 m_tupledCombo->addItem(text); 329 if (m_hasTimingAlready->isChecked()) { 330 if (i == m_maxDuration / Note(getUnitType()).getDuration()) { 331 m_tupledCombo->setCurrentIndex(m_tupledCombo->count() - 1); 332 } 333 } else if (text == previousText) { 334 m_tupledCombo->setCurrentIndex(m_tupledCombo->count() - 1); 335 } 336 } 337 } 338 339 void 340 TupletDialog::updateTimingDisplays() 341 { 342 timeT unitDuration = Note(getUnitType()).getDuration(); 343 344 int untupledCount = getUntupledCount(); 345 int tupledCount = getTupledCount(); 346 347 timeT untupledDuration = unitDuration * untupledCount; 348 timeT tupledDuration = unitDuration * tupledCount; 349 350 if (m_selectionDurationDisplay) { 351 m_selectionDurationDisplay->setText(QString("%1").arg(m_maxDuration)); 352 } 353 354 m_untupledDurationCalculationDisplay->setText 355 (QString(" %1 x %2 = ").arg(untupledCount).arg(unitDuration)); 356 m_untupledDurationDisplay->setText 357 (QString("%1").arg(untupledDuration)); 358 359 m_tupledDurationCalculationDisplay->setText 360 (QString(" %1 x %2 = ").arg(tupledCount).arg(unitDuration)); 361 m_tupledDurationDisplay->setText 362 (QString("%1").arg(tupledDuration)); 363 364 m_newGapDurationCalculationDisplay->setText 365 (QString(" %1 - %2 = ").arg(untupledDuration).arg(tupledDuration)); 366 m_newGapDurationDisplay->setText 367 (QString("%1").arg(untupledDuration - tupledDuration)); 368 369 if (m_selectionDurationDisplay && m_unchangedDurationDisplay) { 370 if (m_maxDuration != untupledDuration) { 371 m_unchangedDurationCalculationDisplay->setText 372 (QString(" %1 - %2 = ").arg(m_maxDuration).arg(untupledDuration)); 373 } else { 374 m_unchangedDurationCalculationDisplay->setText(""); 375 } 376 m_unchangedDurationDisplay->setText 377 (QString("%1").arg(m_maxDuration - untupledDuration)); 378 } 379 } 380 381 void 382 TupletDialog::slotUnitChanged(const QString &) 383 { 384 updateUntupledCombo(); 385 updateTupledCombo(); 386 updateTimingDisplays(); 387 } 388 389 void 390 TupletDialog::slotUntupledChanged(const QString &) 391 { 392 updateTupledCombo(); 393 updateTimingDisplays(); 394 } 395 396 void 397 TupletDialog::slotTupledChanged(const QString &) 398 { 399 updateTimingDisplays(); 400 } 401 402 403 void 404 TupletDialog::slotHelpRequested() 405 { 406 // TRANSLATORS: if the manual is translated into your language, you can 407 // change the two-letter language code in this URL to point to your language 408 // version, eg. "http://rosegardenmusic.com/wiki/doc:tupletDialog-es" for the 409 // Spanish version. If your language doesn't yet have a translation, feel 410 // free to create one. 411 QString helpURL = tr("http://rosegardenmusic.com/wiki/doc:tupletDialog-en"); 412 QDesktopServices::openUrl(QUrl(helpURL)); 413 } 414 } 415