From 507894eb50775a8aaf944b4916e03fbf5bfb43c5 Mon Sep 17 00:00:00 2001 From: DiyunZ Date: Thu, 6 Aug 2026 23:31:44 -0500 Subject: [PATCH 1/2] Normalize executable names and combo box scrolling --- BUILDING_WINDOWS.md | 16 +- CMOD/CMakeLists.txt | 10 +- ...xperimental.cpp => Piece-experimental.cpp} | 0 CMOD/src/Piece.cpp | 1202 ----------------- LASSIE/CMakeLists.txt | 5 + LASSIE/src/core/Updater.cpp | 2 +- LASSIE/src/main.cpp | 2 + LASSIE/src/widgets/ComboBoxWheelGuard.cpp | 66 + LASSIE/src/widgets/ComboBoxWheelGuard.hpp | 24 + LASSIE/src/windows/MainWindow.cpp | 11 +- Make-Portable-for-Windows.ps1 | 28 +- cmake-modules/FindSndFile.cmake | 2 +- packaging/Packaging.cmake | 22 +- packaging/windows/dissco-association.reg | 10 +- 14 files changed, 154 insertions(+), 1246 deletions(-) rename CMOD/src/{piece-experimental.cpp => Piece-experimental.cpp} (100%) delete mode 100644 CMOD/src/Piece.cpp create mode 100644 LASSIE/src/widgets/ComboBoxWheelGuard.cpp create mode 100644 LASSIE/src/widgets/ComboBoxWheelGuard.hpp diff --git a/BUILDING_WINDOWS.md b/BUILDING_WINDOWS.md index 77af4628..13c58e2c 100644 --- a/BUILDING_WINDOWS.md +++ b/BUILDING_WINDOWS.md @@ -280,15 +280,15 @@ cmake --build build --parallel A successful build should produce: ```text -%DISSCO_ROOT%\build\CMOD\CMOD.exe -%DISSCO_ROOT%\build\LASSIE\LASSIE.exe +%DISSCO_ROOT%\build\CMOD\cmod.exe +%DISSCO_ROOT%\build\LASSIE\lassie.exe ``` Verify: ```cmd -dir "%DISSCO_ROOT%\build\CMOD\CMOD.exe" -dir "%DISSCO_ROOT%\build\LASSIE\LASSIE.exe" +dir "%DISSCO_ROOT%\build\CMOD\cmod.exe" +dir "%DISSCO_ROOT%\build\LASSIE\lassie.exe" ``` Run LASSIE @@ -297,7 +297,7 @@ Run LASSIE Run: ```cmd -"%DISSCO_ROOT%\build\LASSIE\LASSIE.exe" +"%DISSCO_ROOT%\build\LASSIE\lassie.exe" ``` If LASSIE opens, the GUI build is working. @@ -305,7 +305,7 @@ If LASSIE opens, the GUI build is working. If Windows reports missing Qt DLLs, run: ```cmd -"%QT_ROOT%\bin\windeployqt.exe" "%DISSCO_ROOT%\build\LASSIE\LASSIE.exe" +"%QT_ROOT%\bin\windeployqt.exe" "%DISSCO_ROOT%\build\LASSIE\lassie.exe" ``` Then run LASSIE again. @@ -316,7 +316,7 @@ Run CMOD Manually CMOD can be run directly with a `.dissco` file: ```cmd -"%DISSCO_ROOT%\build\CMOD\CMOD.exe" "\your_file.dissco" +"%DISSCO_ROOT%\build\CMOD\cmod.exe" "\your_file.dissco" ``` Generate Score Output @@ -374,7 +374,7 @@ The script automatically: The target computer only needs 64-bit Windows 10 or Windows 11. It does not need Qt, LilyPond, Visual Studio, the Visual C++ Redistributable, or -administrator access. Extract the complete ZIP and double-click `LASSIE.exe`; +administrator access. Extract the complete ZIP and double-click `lassie.exe`; `Run-DISSCO.bat` remains available as a compatibility launcher. For non-default paths or automation, run the PowerShell entry point directly: diff --git a/CMOD/CMakeLists.txt b/CMOD/CMakeLists.txt index 4ff88c38..75d4332a 100644 --- a/CMOD/CMakeLists.txt +++ b/CMOD/CMakeLists.txt @@ -27,6 +27,11 @@ target_include_directories(ModifierUsage PUBLIC target_compile_features(ModifierUsage PUBLIC cxx_std_20) add_executable(CMOD ${CMOD_SRC}) +set(CMOD_EXECUTABLE_BASENAME "CMOD") +if(WIN32) + set(CMOD_EXECUTABLE_BASENAME "cmod") + set_target_properties(CMOD PROPERTIES OUTPUT_NAME "${CMOD_EXECUTABLE_BASENAME}") +endif() # CMOD is consumed by LASSIE as a standalone subprocess (QProcess::start), # not linked into it. No ENABLE_EXPORTS / WINDOWS_EXPORT_ALL_SYMBOLS is # needed — and adding them just to "make the link work" backfires on @@ -63,7 +68,10 @@ endif() # set "default" location of the CMOD binary (directory) for use in LASSIE if(NOT CMOD_BINARY) - set(CMOD_BINARY "${CMAKE_CURRENT_BINARY_DIR}/CMOD" PARENT_SCOPE) + set(CMOD_BINARY + "${CMAKE_CURRENT_BINARY_DIR}/${CMOD_EXECUTABLE_BASENAME}" + PARENT_SCOPE + ) message(STATUS "CMOD_BINARY set") endif() message(STATUS "CMOD_BINARY set to ${CMOD_BINARY}") diff --git a/CMOD/src/piece-experimental.cpp b/CMOD/src/Piece-experimental.cpp similarity index 100% rename from CMOD/src/piece-experimental.cpp rename to CMOD/src/Piece-experimental.cpp diff --git a/CMOD/src/Piece.cpp b/CMOD/src/Piece.cpp deleted file mode 100644 index 449f8b7a..00000000 --- a/CMOD/src/Piece.cpp +++ /dev/null @@ -1,1202 +0,0 @@ -// /* -// CMOD (composition module) -// Copyright (C) 2005 Sever Tipei (s-tipei@uiuc.edu) -// Modified by Ming-ching Chiu 2013 - -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// */ - -// //----------------------------------------------------------------------------// -// // -// // Piece.cpp -// // -// //----------------------------------------------------------------------------// - -// #include "Piece.h" - -// #include "Libraries.h" -// #include "Define.h" -// #include "FileValue.h" -// #include "Note.h" -// #include "Output.h" -// #include "Random.h" -// #include "Utilities.h" -// #include - - - - -// //----------------------------------------------------------------------------// - -// int PieceHelper::getDirectoryList(string dir, vector &files) { -// DIR *dp; -// struct dirent *dirp; -// if((dp = opendir(dir.c_str())) == NULL) { -// cout << "Error(" << errno << ") opening " << dir << endl; -// return errno; -// } - -// while ((dirp = readdir(dp)) != NULL) { -// files.push_back(string(dirp->d_name)); -// } -// closedir(dp); -// return 0; - -// } - -// //----------------------------------------------------------------------------// - -// string PieceHelper::getFixedPath(string path) { -// if(path == "") -// return "./"; -// if(path.c_str()[path.length() - 1] != '/') -// path = path + "/"; -// return path; -// } - -// //----------------------------------------------------------------------------// - -// string PieceHelper::getProjectName(string path) { -// string dir = string(path); -// vector files = vector(); -// getDirectoryList(dir, files); -// string g = ""; -// for(unsigned int i = 0; i < files.size(); i++) { -// string f = files[i]; -// if(f.length() >= 5 && f.substr(f.length() - 3, 3) == "dat") { -// g = f.erase(f.length() - 4); -// break; -// } -// } - -// if(g == "") { -// cout << endl; -// cout << "=========================================================" << endl; -// cout << "Warning: there is no CMOD project file in this directory." << endl; -// cout << "=========================================================" << endl; -// cout << "Files in '" << path << "':" << endl; - -// for(unsigned int i = 0; i < files.size(); i++) -// cout << files[i] << endl; -// cout << "=========================================================" << endl; -// cout << endl; -// } - -// return g; - -// } - - -// //----------------------------------------------------------------------------// - -// int PieceHelper::getSeedNumber(string seed) { -// if(seed == "time") { -// int now = (int)time(0); -// cout << "Using current time " << now << " as seed." << endl; -// return now; -// } - -// const char* seed_c = seed.c_str(); -// int seedNumber = 0; -// int digits = 1; -// for(int i = 0; i < seed.length(); i++) -// digits *= 10; -// for(int i = 0; seed_c[i] != 0; i++) { -// digits /= 10; -// seedNumber += digits * (int)(seed_c[i] - '0'); -// } -// if(seedNumber < 0) seedNumber = -seedNumber; -// return seedNumber; -// } - -// //----------------------------------------------------------------------------// - -// void PieceHelper::createSoundFilesDirectory(string path) { -// string dir = string(path); -// vector files = vector(); -// getDirectoryList(dir, files); -// string g = ""; -// for(unsigned int i = 0; i < files.size(); i++) { -// if(files[i] == "SoundFiles") -// return; -// } - -// string h = "mkdir " + path + "SoundFiles"; -// system(h.c_str()); - -// } - -// //----------------------------------------------------------------------------// - -// void PieceHelper::createScoreFilesDirectory(string path) { - -// string dir = string(path); -// vector files = vector(); -// getDirectoryList(dir, files); -// string g = ""; -// bool dirExists = false; -// for(unsigned int i = 0; i < files.size(); i++) { -// if(files[i] == "ScoreFiles") { -// dirExists = true; -// break; -// } -// } - -// string h = "mkdir " + path + "ScoreFiles"; -// if(!dirExists) -// system(h.c_str()); -// h = "rm -f " + path + "ScoreFiles/*.fms"; -// system(h.c_str()); - -// } - -// //----------------------------------------------------------------------------// - -// bool PieceHelper::doesFileExist(string path, string filename) { -// string dir = string(path); -// vector files = vector(); -// getDirectoryList(dir, files); -// string g = ""; -// for(unsigned int i = 0; i < files.size(); i++) { -// if(files[i] == filename) -// return true; -// } -// return false; -// } - -// //----------------------------------------------------------------------------// - -// string Piece::getNextSoundFile() { - -// string soundPath = "SoundFiles/"; -// //cout<<"sound Path:"<parse(disscoFile.c_str()); - -// //get the parsed DOM Document and read the configuration -// DOMDocument* xmlDocument = parser->getDocument(); -// DOMElement* root = xmlDocument->getDocumentElement(); -// DOMElement* configurations = root->GFEC(); -// DOMElement* element = configurations->GFEC(); - -// title = XMLTC(element); -// element = element->GNES(); -// fileFlags = XMLTC(element); -// element = element->GNES(); -// fileList = XMLTC(element); -// element = element->GNES(); -// pieceStartTime = XMLTC(element); -// element = element->GNES(); -// pieceDuration = XMLTC(element); -// element = element->GNES(); -// soundSynthesis = (XMLTC(element).compare("True")==0)?true:false; -// element = element->GNES(); -// scorePrinting = (XMLTC(element).compare("True")==0)?true:false; -// element = element->GNES(); -// cout << "Here-0.0" << endl; -// // multistaffs -// grandStaff = (XMLTC(element).compare("True")==0)?true:false; -// element = element->GNES(); -// // get the staffs number -// numberOfStaff = atoi(XMLTC(element).c_str()); -// element = element->GNES(); -// numChannels = atoi(XMLTC(element).c_str()); -// element = element->GNES(); -// sampleRate = atoi(XMLTC(element).c_str()); -// element = element->GNES(); -// sampleSize = atoi(XMLTC(element).c_str()); -// element = element->GNES(); -// numThreads = atoi(XMLTC(element).c_str()); -// cout << numThreads << endl; -// element = element->GNES(); -// bool outputParticel = (XMLTC(element).compare("True")==0)?true:false; - -// //check if seed exists -// string seed; -// element = element->GNES(); -// if(element->getFirstChild()){ -// seed = XMLTC(element); -// } -// else{ -// cout<<"Please key in the Random Seed:"<>seed; -// } - -// //Convert seed string to seed number and seed the random number generator -// int seedNumber = PieceHelper::getSeedNumber(seed); -// Random::Seed((unsigned int)seedNumber); - -// //construct the utilities object -// utilities = new Utilities(root, -// _workingPath, -// soundSynthesis, -// outputParticel, -// numThreads, -// numChannels, -// sampleRate, -// this); - -// // setup TimeSpan and Tempo -// TimeSpan pieceSpan; -// pieceSpan.start = utilities->evaluate(pieceStartTime, NULL); -// pieceSpan.duration = utilities->evaluate(pieceDuration, NULL); -// Tempo mainTempo; //Though we supply this, "Top" will provide its own tempo. - -// // multistaffs -// // Initialize the output score -// // Output::notation_score_ = NotationScore(_projectTitle); -// Output::notation_score_ = NotationScore(_projectTitle,grandStaff,numberOfStaff); - -// //Initialize the output class. -// if (utilities->getOutputParticel()){ -// string particelFilename = _projectTitle + ".particel"; -// Output::initialize(particelFilename); -// Output::beginSubLevel("Piece"); -// Print(); -// } - -// /*//Modify the XML file - Eperimental -// string evName = utilities->topEventnames.at(0); -// EventType evType = (EventType)0; -// utilities->currChild = 0; - -// DOMElement* topEvEl = utilities->getEventElement(evType, evName); -// vector children = calculateAesthetic(topEvEl); - -// for(int i = 0; i < children.size(); i++){ -// vector currChildren = calculateAesthetic(children[i]); - -// for(int j = 0; j < currChildren.size(); j++){ -// children.push_back(currChildren[j]); -// } -// //cout<<"Child No: "< bottoms; -// std::vector names; - -// for(std::map::iterator it = utilities->eventValues.begin(); it != utilities->eventValues.end(); ++it){ -// names.push_back(it->first); -// if(it->second != 0.0){ -// pieceAesthetic += it->second; -// numUniqueEvents++; -// bottoms.insert(std::pair(it->second, it->first)); -// cout<first<<", "<second<second; -// } -// } - -// //pieceAesthetic /= numUniqueEvents; -// //cout<<"Piece aesthetic: "<getEventElement(eventTop, fileList); -// utilities->currChild = 0; -// Event* topEvent = new Event(topElement, -// pieceSpan,0, mainTempo, utilities, NULL,NULL,NULL,NULL); -// topEvent->buildChildren(); - -// //get the final MultiTrack object and write it to disk -// if (soundSynthesis){ -// cout << "Piece::Piece: " << "soundSynthesis " << endl; -// MultiTrack* renderedScore = utilities->doneCMOD(); -// string soundFilename = getNextSoundFile(); -// //Write to file. -// AuWriter::write(*renderedScore, soundFilename); -// delete renderedScore; - -// } else { -// cout << "Piece::Piece: " << "Score output " << endl; -// /* for score file */ - -// // output score to lilypond file -// // output_score(projectName); - -// Output::notation_score_.Build(); -// ofstream score_file; -// score_file.open((projectName + ".ly").c_str()); -// score_file << Output::notation_score_; -// score_file.close(); - -// // execute lilypond to create pdf file -// system(("lilypond " + projectName + ".ly").c_str()); - -// } - -// if (outputParticel){ -// //Finish particel output and free up the Output class members. -// Output::endSubLevel(); -// Output::free(); -// } - -// cout << endl; -// cout << "-----------------------------------------------------------" << -// endl; -// cout << "Build complete." << endl; -// cout << "-----------------------------------------------------------" << -// endl << endl; -// cout.flush(); - - -// //clean up -// delete utilities; -// delete parser; -// delete topEvent; //wait till the thread join -// XMLPlatformUtils::Terminate(); - -// } -// } - -// Piece::~Piece(){ -// //do nothing -// } - -// //Experiment 2 -// vector Piece::calcEventM(DOMElement* eventElement){ - -// vector childElements; -// DOMElement* thisEventElement = eventElement->GFEC(); //type -// string typeString = XMLTC(thisEventElement); -// int type = atoi(typeString.c_str()); -// double mVal = 0.0; - -// thisEventElement = thisEventElement->GNES(); //name -// string name = XMLTC(thisEventElement); - -// if(type <= 4){ //Top, High, Medium, Low, Bottom - -// thisEventElement = thisEventElement->GNES(); //maxChildDur -// float maxChildDur = (float)utilities->evaluate(XMLTC(thisEventElement), (void*)this); - -// thisEventElement = thisEventElement->GNES(); //newEDUPerBeat -// int newEDUPerBeat = (int) utilities->evaluate(XMLTC(thisEventElement),(void*)this); - -// thisEventElement = thisEventElement->GNES(); //Time Signature element - -// thisEventElement = thisEventElement->GNES(); //Tempo element - -// DOMElement* numChildrenElement = thisEventElement->GNES(); //Num children element - -// DOMElement* childEventDefElement = numChildrenElement->GNES(); -// DOMElement* childStartTimeElement = childEventDefElement->GFEC(); -// DOMElement* childTypeElement = childStartTimeElement->GNES(); -// DOMElement* childDurationElement = childTypeElement->GNES(); -// DOMElement* AttackSieveElement = childDurationElement->GNES(); -// DOMElement* DurationSieveElement = AttackSieveElement->GNES(); -// DOMElement* methodFlagElement = DurationSieveElement->GNES(); -// DOMElement* childStartTypeFlag = methodFlagElement->GNES(); -// DOMElement* childDurationTypeFlag = childStartTypeFlag->GNES(); - -// //Read Flag values (Needed for modification) -// string defFlag = XMLTC(methodFlagElement); -// int definitionVal = atoi(defFlag.c_str()); - -// if(definitionVal == 0){ //Only Continuum - -// //Calculating start time orignality -// string startFlag = XMLTC(childStartTypeFlag); -// int startFlagVal = atoi(startFlag.c_str()); - -// //layers, initialize child names -// thisEventElement = childEventDefElement->GNES(); -// DOMElement* layerElement = thisEventElement->GFEC(); -// vector layerElements; -// vector childTypeElements; - -// while (layerElement){ - -// layerElements.push_back(layerElement); -// DOMElement* childPackage = layerElement->GFEC()->GNES()->GFEC(); - -// while(childPackage){ -// childTypeElements.push_back(childPackage); -// childPackage = childPackage->GNES(); -// } -// layerElement = layerElement->GNES(); -// } - -// int numChildren; -// DOMElement* flagElement = numChildrenElement->GFEC(); -// if (XMLTC(flagElement) =="0"){ // Continuum -// DOMElement* entry1Element = flagElement->GNES(); -// if (XMLTC(entry1Element)==""){ -// numChildren = childTypeElements.size(); -// } -// else { -// numChildren =(int) utilities->evaluate(XMLTC(entry1Element), (void*)this); -// } -// } -// else if (XMLTC(flagElement) == "1"){ // Density -// DOMElement* densityElement = numChildrenElement->GFEC()->GNES(); -// DOMElement* areaElement = densityElement->GNES(); -// DOMElement* underOneElement = areaElement->GNES(); -// double density = utilities->evaluate( XMLTC(densityElement),(void*)this); -// double area = utilities->evaluate( XMLTC(areaElement),(void*)this); -// double underOne = utilities->evaluate( XMLTC(underOneElement),(void*)this); -// double soundsPsec = pow(2, density * area - underOne); //this can't be right.. - -// numChildren = (int)(soundsPsec * utilities->evaluate(pieceDuration, NULL) + underOne/area); -// } -// else {// by layer -// numChildren = 0; -// for (int i = 0; i < layerElements.size(); i ++){ -// numChildren +=utilities->evaluate(XMLTC(layerElements[i]->GFEC()),(void*)this); -// } -// } - -// if(type == 4){ //Bottom -// XMLCh* extraInfoString = XMLString::transcode("ExtraInfo"); -// DOMNodeList* extraInfoList = eventElement->getElementsByTagName(extraInfoString); -// DOMElement* extraInfo = (DOMElement*) extraInfoList->item(0); -// XMLString::release(&extraInfoString); - -// //Frequency Entropy -// DOMElement* frequencyElement = extraInfo->GFEC(); -// DOMElement* frequencyFlagElement = frequencyElement->GFEC(); -// string flagNum = XMLTC(frequencyFlagElement); -// int flagVal = atoi(flagNum.c_str()); - -// if(flagVal == 0){ //Equal Temperament -// mVal += EQUAL_TEMP * (log(1 / EQUAL_TEMP)/log(2)); -// DOMElement* freqEntry1 = frequencyFlagElement->GNES()->GNES(); -// } - -// else if(flagVal == 1){//Fundamental -// mVal += FUNDAMENTAL * (log(1 / FUNDAMENTAL)/log(2)); -// DOMElement* freqEntry1 = frequencyFlagElement->GNES()->GNES(); -// DOMElement* freqEntry2 = freqEntry1->GNES(); -// } - -// else if(flagVal == 2){//Continuum -// mVal += CONTINUUM * (log(1 / CONTINUUM)/log(2)); -// DOMElement* freqEntry1 = frequencyFlagElement->GNES()->GNES(); -// DOMElement* continuumFlagElement = frequencyFlagElement->GNES(); - -// if (utilities->evaluate(XMLTC(continuumFlagElement), NULL)==0) { //Hertz -// mVal += HZ * (log(1 / HZ)/log(2)); -// /* 3rd arg is a float (baseFreq in Hz) */ -// } - -// else { -// mVal += POW2 * (log(1 / POW2)/log(2)); -// /* 3rd arg is a float (power of 2) */ -// float step = utilities->evaluate(XMLTC(freqEntry1), (void*)this); -// double range = log10(CEILING / MINFREQ) / log10(2.); // change log base -// double baseFreqResult = pow(2, step * range) * MINFREQ; // equal chance for all 8vs -// } -// } - -// /*entropy = calculateEntropyRatio(samples, "Pow2", 0, 15000); //Freq in Hz -// currentEntropy = utilities->eventValues[name]; -// utilities->eventValues.erase(name); -// newEntropy = (currentEntropy + entropy); // Mean of ratios -// utilities->eventValues.insert(std::pair (name, newEntropy)); -// //cout<<"Entropy Ratio:"<evaluate(XMLTC(childTypeElement),(void*)this); -// string childName = XMLTC(childTypeElements[childType]->GFEC()); -// EventType childEventType = (EventType) utilities->evaluate(XMLTC(childTypeElements[childType]->GFEC()->GNES()),(void*)this); - -// DOMElement* childElement = utilities->getEventElement(childEventType, childName); -// childElements.push_back(childElement); -// } - -// return childElements; -// } -// } -// } -// } - -// //Experimental - -// vector Piece::modifyPiece(DOMElement* eventElement){ - -// //Read certain properties of this event/ Navigate the XML -// vector childElements; -// DOMElement* thisEventElement = eventElement->GFEC(); //type -// string typeString = XMLTC(thisEventElement); -// int type = atoi(typeString.c_str()); - -// thisEventElement = thisEventElement->GNES(); //name -// string name = XMLTC(thisEventElement); - -// if(type == 4){ //Bottom. (only doing bottom for now) -// XMLCh* extraInfoString = XMLString::transcode("ExtraInfo"); -// DOMNodeList* extraInfoList = eventElement->getElementsByTagName(extraInfoString); -// DOMElement* extraInfo = (DOMElement*) extraInfoList->item(0); -// XMLString::release(&extraInfoString); - -// //Modifying Frequency -// DOMElement* frequencyElement = extraInfo->GFEC(); -// DOMElement* frequencyFlagElement = frequencyElement->GFEC(); -// string flagNum = XMLTC(frequencyFlagElement); -// int flagVal = atoi(flagNum.c_str()); - -// if(flagVal == 0){ //Equal Tempered -// DOMElement* freqEntry1 = frequencyFlagElement->GNES()->GNES(); - -// if(freqEntry1->GFEC() != NULL){ -// DOMElement* funcElement = freqEntry1->GFEC(); -// functionModifier(funcElement, 80); -// } - -// else{ - -// string freqNum = XMLTC(freqEntry1); -// int freqVal = atoi(freqNum.c_str()); -// freqVal = Random::RandInt(8, 80); - -// char lval[33]; -// sprintf(lval, "%d", freqVal); - -// XMLCh *freq; -// freq = XMLString::transcode(lval); -// freqEntry1->getFirstChild()->setNodeValue(freq); -// XMLString::release(&freq); - -// } -// } - -// else if(flagVal == 1){ //Didnt change Partial Number -// DOMElement* freqEntry1 = frequencyFlagElement->GNES()->GNES(); - -// if(freqEntry1->GFEC() != NULL){ -// DOMElement* funcElement = freqEntry1->GFEC(); -// //functionModifier(funcElement, 100); -// } - -// else{ - -// string freqNum = XMLTC(freqEntry1); -// int freqVal = atoi(freqNum.c_str()); -// //freqVal = (freqVal + Random::RandInt(0, 10)) % 100; - -// char lval[33]; -// sprintf(lval, "%d", freqVal); - -// XMLCh *freq; -// freq = XMLString::transcode(lval); -// freqEntry1->getFirstChild()->setNodeValue(freq); -// XMLString::release(&freq); - -// } -// } - -// else if(flagVal == 2){ -// DOMElement* continuumFlagElement = frequencyFlagElement->GNES(); -// string contflagNum = XMLTC(continuumFlagElement); -// int contflagVal = atoi(contflagNum.c_str()); -// DOMElement* freqEntry1 = frequencyFlagElement->GNES()->GNES(); - -// if(freqEntry1->GFEC() != NULL){ -// DOMElement* funcElement = freqEntry1->GFEC(); -// if(contflagVal == 0){ -// functionModifier(funcElement, 15000); -// } -// else{ -// functionModifier(funcElement, 14); -// } -// } - -// else{ - -// string freqNum = XMLTC(freqEntry1); -// int freqVal = atoi(freqNum.c_str()); -// if(contflagVal == 0){ -// freqVal = Random::RandInt(0, 15000); -// } -// else{ -// freqVal = Random::RandInt(0, 14); -// } - -// char lval[33]; -// sprintf(lval, "%d", freqVal); - -// XMLCh *freq; -// freq = XMLString::transcode(lval); -// freqEntry1->getFirstChild()->setNodeValue(freq); -// XMLString::release(&freq); - -// } -// } - -// //Modifying Loudness -// DOMElement* loudnessElement = frequencyElement->GNES(); - -// if(loudnessElement->GFEC() != NULL){ -// DOMElement* funcElement = loudnessElement->GFEC(); -// functionModifier(funcElement, 225); -// } - -// else{ -// //char *loudnessvalue = XMLString::transcode(loudnessElement->getFirstChild()->getNodeValue()); -// //cout<<"hey:"<getFirstChild()->setNodeValue(loud); -// XMLString::release(&loud); - -// } - -// loudnessElement->GNES(); // Spatialization element -// loudnessElement->GNES()->GNES(); // Reverb element -// loudnessElement->GNES()->GNES()->GNES(); // Filters element -// loudnessElement->GNES()->GNES()->GNES()->GNES(); // Modifiers element -// } - -// /*for(int i = 0; i < numChildren; i++){ - -// double childType = utilities->evaluate(XMLTC(childTypeElement),(void*)this); -// string childName = XMLTC(childTypeElements[childType]->GFEC()); -// EventType childEventType = (EventType) utilities->evaluate(XMLTC(childTypeElements[childType]->GFEC()->GNES()),(void*)this); - -// DOMElement* childElement = utilities->getEventElement(childEventType, childName); -// childElements.push_back(childElement); - -// //cout<GFEC()); - -// if(functionName.compare("RandomInt") == 0){ - -// DOMElement* lowBoundElement = functionElement->GFEC()->GNES(); -// DOMElement* highBoundElement = lowBoundElement->GNES(); - -// if(lowBoundElement->GFEC() != NULL){ -// functionModifier(lowBoundElement->GFEC(), maxValue); -// } - -// else{ -// int lowBound = atoi(XMLTC(lowBoundElement).c_str()); -// lowBound = (lowBound + Random::RandInt(0, maxValue/2)) % (maxValue/2); -// char val[33]; -// sprintf(val, "%d", lowBound); -// XMLCh *low; -// low = XMLString::transcode(val); -// lowBoundElement->getFirstChild()->setNodeValue(low); -// XMLString::release(&low); - -// } - -// if(highBoundElement->GFEC() != NULL){ -// functionModifier(highBoundElement->GFEC(), maxValue); -// } - -// else{ -// int highBound = atoi(XMLTC(highBoundElement).c_str()); -// highBound = (highBound + Random::RandInt(0, maxValue/2)) % (maxValue/2) + maxValue/2; -// char val[33]; -// sprintf(val, "%d", highBound); -// XMLCh *high; -// high = XMLString::transcode(val); -// highBoundElement->getFirstChild()->setNodeValue(high); -// XMLString::release(&high); - -// } -// } - -// else if (functionName.compare("Random") == 0){ - -// DOMElement* lowBoundElement = functionElement->GFEC()->GNES(); -// DOMElement* highBoundElement = lowBoundElement->GNES(); - -// if(lowBoundElement->GFEC() != NULL){ -// functionModifier(lowBoundElement->GFEC(), maxValue); -// } - -// else{ -// double lowBound = atof(XMLTC(lowBoundElement).c_str()); -// lowBound = fmod((lowBound + Random::Rand(0, maxValue/2)), maxValue/2); -// char val[33]; -// sprintf(val, "%f", lowBound); -// XMLCh *low; -// low = XMLString::transcode(val); -// lowBoundElement->getFirstChild()->setNodeValue(low); -// XMLString::release(&low); - -// } - -// if(highBoundElement->GFEC() != NULL){ -// functionModifier(highBoundElement->GFEC(), maxValue); -// } - -// else{ -// double highBound = atof(XMLTC(highBoundElement).c_str()); -// highBound = fmod((highBound + Random::Rand(0, maxValue/2)), maxValue/2) + maxValue/2; -// char val[33]; -// sprintf(val, "%f", highBound); -// XMLCh *high; -// high = XMLString::transcode(val); -// highBoundElement->getFirstChild()->setNodeValue(high); -// XMLString::release(&high); - -// } - -// } - -// else if (functionName.compare("Select") == 0){ -// DOMElement* listElement = functionElement->GFEC()->GNES(); -// DOMElement* indexElement = listElement->GNES(); - -// std::vector list = utilities->listElementToStringVector(listElement); - -// if(indexElement->GFEC() != NULL){ -// functionModifier(indexElement->GFEC(), list.size() - 1); -// } - -// else{ -// int indexVal = atoi(XMLTC(indexElement).c_str()); -// indexVal = (indexVal + 1)%(list.size() - 1); -// char val[33]; -// sprintf(val, "%d", indexVal); -// XMLCh *index; -// index = XMLString::transcode(val); -// indexElement->getFirstChild()->setNodeValue(index); -// XMLString::release(&index); -// } -// } - -// else if (functionName.compare("GetPattern") == 0){ -// //Do Later - -// } -// else if (functionName.compare("Randomizer") == 0){ - -// DOMElement* baseValElement = functionElement->GFEC()->GNES(); -// DOMElement* percDevElement = baseValElement->GNES(); - -// if(baseValElement->GFEC() != NULL){ -// functionModifier(baseValElement->GFEC(), maxValue); -// } - -// else{ -// double baseVal = atof(XMLTC(baseValElement).c_str()); -// baseVal = fmod(baseVal + Random::Rand(0, baseVal/10), maxValue/2); -// char val[33]; -// sprintf(val, "%f", baseVal); -// XMLCh *base; -// base = XMLString::transcode(val); -// baseValElement->getFirstChild()->setNodeValue(base); -// XMLString::release(&base); - -// } - -// if(percDevElement->GFEC() != NULL){ -// functionModifier(percDevElement->GFEC(), maxValue); -// } - -// else{ -// double percDev = atof(XMLTC(percDevElement).c_str()); -// percDev = fmod((percDev + Random::Rand(0, percDev/10)), 1); -// char val[33]; -// sprintf(val, "%f", percDev); -// XMLCh *dev; -// dev = XMLString::transcode(val); -// percDevElement->getFirstChild()->setNodeValue(dev); -// XMLString::release(&dev); - -// } - -// } -// else if (functionName.compare("RandomDensity") == 0) { - -// DOMElement* envelopeNumberElement = functionElement->GFEC()->GNES(); -// DOMElement* lowBoundElement = envelopeNumberElement->GNES(); -// DOMElement* highBoundElement = lowBoundElement->GNES(); - -// if(lowBoundElement->GFEC() != NULL){ -// functionModifier(lowBoundElement->GFEC(), maxValue); -// } - -// else{ -// double lowBound = atof(XMLTC(lowBoundElement).c_str()); -// lowBound = fmod((lowBound + Random::Rand(0, maxValue/2)), maxValue/2); -// char val[33]; -// sprintf(val, "%f", lowBound); -// XMLCh *low; -// low = XMLString::transcode(val); -// lowBoundElement->getFirstChild()->setNodeValue(low); -// XMLString::release(&low); - -// } - -// if(highBoundElement->GFEC() != NULL){ -// functionModifier(highBoundElement->GFEC(), maxValue); -// } - -// else{ -// double highBound = atof(XMLTC(highBoundElement).c_str()); -// highBound = fmod((highBound + Random::Rand(0, maxValue/2)), maxValue/2) + maxValue/2; -// char val[33]; -// sprintf(val, "%f", highBound); -// XMLCh *high; -// high = XMLString::transcode(val); -// highBoundElement->getFirstChild()->setNodeValue(high); -// XMLString::release(&high); - -// } - -// } -// else if (functionName.compare("ChooseL") == 0){ -// //Do Later - -// } - -// else if (functionName.compare("ValuePick") == 0){ -// //Do Later - -// } - -// else if (functionName.compare("Stochos") == 0){ - -// } -// else if (functionName.compare("Decay") == 0){ - -// } - -// else if (functionName.compare("Fibonacci") == 0){ - -// } - -// else if (functionName.compare("LN") == 0){ - -// } - -// else if (functionName.compare("Inverse") == 0){ - -// } -// } - -// vector Piece::calculateAesthetic(DOMElement* eventElement){ - -// vector childElements; -// const int NUM_SAMPLES = 100; -// std::vector samples; -// double entropy = 0.0; -// double currentEntropy, newEntropy; -// DOMElement* thisEventElement = eventElement->GFEC(); //type -// string typeString = XMLTC(thisEventElement); -// int type = atoi(typeString.c_str()); - -// thisEventElement = thisEventElement->GNES(); //name -// string name = XMLTC(thisEventElement); - -// if(type <= 4){ //Top, High, Medium, Low, Bottom - -// thisEventElement = thisEventElement->GNES(); //maxChildDur -// float maxChildDur = (float)utilities->evaluate(XMLTC(thisEventElement), (void*)this); - -// thisEventElement = thisEventElement->GNES(); //newEDUPerBeat -// int newEDUPerBeat = (int) utilities->evaluate(XMLTC(thisEventElement),(void*)this); - -// thisEventElement = thisEventElement->GNES(); //Time Signature element - -// thisEventElement = thisEventElement->GNES(); //Tempo element - -// DOMElement* numChildrenElement = thisEventElement->GNES(); //Num children element - -// DOMElement* childEventDefElement = numChildrenElement->GNES(); -// DOMElement* childStartTimeElement = childEventDefElement->GFEC(); -// DOMElement* childTypeElement = childStartTimeElement->GNES(); -// DOMElement* childDurationElement = childTypeElement->GNES(); -// DOMElement* AttackSieveElement = childDurationElement->GNES(); -// DOMElement* DurationSieveElement = AttackSieveElement->GNES(); -// DOMElement* methodFlagElement = DurationSieveElement->GNES(); -// DOMElement* childStartTypeFlag = methodFlagElement->GNES(); -// DOMElement* childDurationTypeFlag = childStartTypeFlag->GNES(); - -// //Read Flag values (Needed for modification) -// string defFlag = XMLTC(methodFlagElement); -// int definitionVal = atoi(defFlag.c_str()); - -// if(definitionVal == 0){ //Only Continuum - -// //Calculating start time orignality -// string startFlag = XMLTC(childStartTypeFlag); -// int startFlagVal = atoi(startFlag.c_str()); - -// //layers, initialize child names -// thisEventElement = childEventDefElement->GNES(); -// DOMElement* layerElement = thisEventElement->GFEC(); -// vector layerElements; -// vector childTypeElements; - -// while (layerElement){ - -// layerElements.push_back(layerElement); -// DOMElement* childPackage = layerElement->GFEC()->GNES()->GFEC(); - -// while(childPackage){ -// childTypeElements.push_back(childPackage); -// childPackage = childPackage->GNES(); -// } -// layerElement = layerElement->GNES(); -// } - -// int numChildren; -// DOMElement* flagElement = numChildrenElement->GFEC(); -// if (XMLTC(flagElement) =="0"){ // Continuum -// DOMElement* entry1Element = flagElement->GNES(); -// if (XMLTC(entry1Element)==""){ -// numChildren = childTypeElements.size(); -// } -// else { -// numChildren =(int) utilities->evaluate(XMLTC(entry1Element), (void*)this); -// } -// } -// else if (XMLTC(flagElement) == "1"){ // Density -// DOMElement* densityElement = numChildrenElement->GFEC()->GNES(); -// DOMElement* areaElement = densityElement->GNES(); -// DOMElement* underOneElement = areaElement->GNES(); -// double density = utilities->evaluate( XMLTC(densityElement),(void*)this); -// double area = utilities->evaluate( XMLTC(areaElement),(void*)this); -// double underOne = utilities->evaluate( XMLTC(underOneElement),(void*)this); -// double soundsPsec = pow(2, density * area - underOne); //this can't be right.. - -// numChildren = (int)(soundsPsec * utilities->evaluate(pieceDuration, NULL) + underOne/area); - -// } -// else {// by layer -// numChildren = 0; -// for (int i = 0; i < layerElements.size(); i ++){ -// numChildren +=utilities->evaluate(XMLTC(layerElements[i]->GFEC()),(void*)this); -// } -// } - -// if(type == 4){ //Bottom -// XMLCh* extraInfoString = XMLString::transcode("ExtraInfo"); -// DOMNodeList* extraInfoList = eventElement->getElementsByTagName(extraInfoString); -// DOMElement* extraInfo = (DOMElement*) extraInfoList->item(0); -// XMLString::release(&extraInfoString); - -// //Frequency Entropy -// samples.clear(); -// DOMElement* frequencyElement = extraInfo->GFEC(); -// DOMElement* frequencyFlagElement = frequencyElement->GFEC(); -// string flagNum = XMLTC(frequencyFlagElement); -// int flagVal = atoi(flagNum.c_str()); - -// if(flagVal == 0){ -// DOMElement* freqEntry1 = frequencyFlagElement->GNES()->GNES(); - -// for(int i = 0; i < NUM_SAMPLES; i++){ -// double wellTempPitch = utilities->evaluate(XMLTC(freqEntry1), (void*)this); -// double baseFreqResult = C0 * pow(WELL_TEMP_INCR, wellTempPitch); -// samples.push_back(baseFreqResult); -// } -// } - -// else if(flagVal == 1){ -// DOMElement* freqEntry1 = frequencyFlagElement->GNES()->GNES(); -// DOMElement* freqEntry2 = freqEntry1->GNES(); - -// for(int i = 0; i < NUM_SAMPLES; i++){ -// float fund_freq = utilities->evaluate(XMLTC(freqEntry1), (void*)this); -// int overtone_step = utilities->evaluate(XMLTC(freqEntry2), (void*)this); -// double baseFreqResult = fund_freq * overtone_step; -// samples.push_back(baseFreqResult); -// } -// } - -// else if(flagVal == 2){ -// DOMElement* freqEntry1 = frequencyFlagElement->GNES()->GNES(); -// DOMElement* continuumFlagElement = frequencyFlagElement->GNES(); - -// for(int i = 0; i < NUM_SAMPLES; i++){ -// if (utilities->evaluate(XMLTC(continuumFlagElement), NULL)==0) { //Hertz -// samples.push_back(utilities->evaluate(XMLTC(freqEntry1), (void*)this)); -// /* 3rd arg is a float (baseFreq in Hz) */ -// } -// else { -// /* 3rd arg is a float (power of 2) */ -// float step = utilities->evaluate(XMLTC(freqEntry1), (void*)this); -// double range = log10(CEILING / MINFREQ) / log10(2.); // change log base -// double baseFreqResult = pow(2, step * range) * MINFREQ; // equal chance for all 8vs -// samples.push_back(baseFreqResult); -// } -// } -// } - -// entropy = calculateEntropyRatio(samples, "Pow2", 0, 15000); //Freq in Hz -// currentEntropy = utilities->eventValues[name]; -// utilities->eventValues.erase(name); -// newEntropy = (currentEntropy + entropy); // Mean of ratios -// utilities->eventValues.insert(std::pair (name, newEntropy)); -// //cout<<"Entropy Ratio:"<GNES(); -// samples.clear(); - -// for(int i = 0; i < NUM_SAMPLES;){ -// double loudval = utilities->evaluate(XMLTC(loudnessElement), (void*)this); -// if(loudval >= 1.0){ -// samples.push_back(loudval); -// i++; -// } -// } - -// entropy = calculateEntropyRatio(samples, "Pow2", 0, 256); //Loudness -// currentEntropy = utilities->eventValues[name]; -// utilities->eventValues.erase(name); -// newEntropy = (currentEntropy + entropy); // Mean of ratios -// utilities->eventValues.insert(std::pair (name, newEntropy)); -// //cout<<"Entropy Ratio:"<evaluate(XMLTC(childTypeElement),(void*)this); -// string childName = XMLTC(childTypeElements[childType]->GFEC()); -// EventType childEventType = (EventType) utilities->evaluate(XMLTC(childTypeElements[childType]->GFEC()->GNES()),(void*)this); - -// DOMElement* childElement = utilities->getEventElement(childEventType, childName); -// childElements.push_back(childElement); -// } - -// return childElements; -// } -// } -// } - -// double Piece::calculateEntropyRatio(vector sampleData, string partitionMethod, double min, double max){ - -// if(min == 0 && partitionMethod.compare("Pow2") == 0){ -// min++; //For log to work -// } -// int numPartitions = 100; //Arbitrary -// vector probs(numPartitions, 0.0); -// double shannonEntropy = 0.0, maxEntropy, redundancy; - -// std::sort(sampleData.begin(), sampleData.end()); - -// double sampleRange = sampleData[sampleData.size() - 1] - sampleData[0]; - -// for(int i = 0; i < sampleData.size(); i++){ -// if(partitionMethod.compare("Pow2") == 0){ -// probs[(int)(log(sampleData[i])/log(2))] += 1.0/sampleData.size(); -// } -// else if(partitionMethod.compare("Unit") == 0){ -// probs[(int)sampleData[i]] += 1.0/sampleData.size(); -// } -// } - -// for(int i = 0; i < probs.size(); i++){ - -// if(probs[i] > 0.0){ -// shannonEntropy += -1 * probs[i] * log(probs[i])/log(2); -// } -// } - -// if(shannonEntropy < 0.0){ // C++ log stuff error? -// shannonEntropy = 0.0; -// } - -// //cout<<"Shannon: "< #include "windows/EnvelopeLibraryWindow.hpp" #endif +#include "widgets/ComboBoxWheelGuard.hpp" #include "widgets/TextOverflowDisplayPolicy.hpp" #include "windows/MainWindow.hpp" @@ -23,6 +24,7 @@ int main(int argc, char *argv[]) QCoreApplication::setApplicationName("LASSIE"); QApplication a(argc, argv); + ComboBoxWheelGuard comboBoxWheelGuard(a); TextOverflowDisplayPolicy textOverflowDisplayPolicy(a); #ifdef DISSCO_ENABLE_UI_LAYOUT_TESTS diff --git a/LASSIE/src/widgets/ComboBoxWheelGuard.cpp b/LASSIE/src/widgets/ComboBoxWheelGuard.cpp new file mode 100644 index 00000000..8b8562c6 --- /dev/null +++ b/LASSIE/src/widgets/ComboBoxWheelGuard.cpp @@ -0,0 +1,66 @@ +#include "ComboBoxWheelGuard.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +QAbstractScrollArea* containingScrollArea(QWidget* widget) +{ + for (QWidget* parent = widget->parentWidget(); parent; + parent = parent->parentWidget()) { + if (auto* scrollArea = qobject_cast(parent)) + return scrollArea; + } + + return nullptr; +} + +void forwardToScrollArea(QWheelEvent* sourceEvent, + QAbstractScrollArea* scrollArea) +{ + QWidget* viewport = scrollArea->viewport(); + const QPointF globalPosition = sourceEvent->globalPosition(); + const QPoint viewportPosition = viewport->mapFromGlobal( + globalPosition.toPoint()); + QWheelEvent forwardedEvent( + QPointF(viewportPosition), + globalPosition, + sourceEvent->pixelDelta(), + sourceEvent->angleDelta(), + sourceEvent->buttons(), + sourceEvent->modifiers(), + sourceEvent->phase(), + sourceEvent->inverted(), + sourceEvent->source(), + sourceEvent->pointingDevice()); + QCoreApplication::sendEvent(viewport, &forwardedEvent); +} + +} // namespace + +ComboBoxWheelGuard::ComboBoxWheelGuard(QApplication& application) + : QObject(&application) +{ + application.installEventFilter(this); +} + +bool ComboBoxWheelGuard::eventFilter(QObject* watched, QEvent* event) +{ + if (event->type() != QEvent::Wheel) + return false; + + auto* combo = qobject_cast(watched); + if (!combo) + return false; + + auto* wheelEvent = static_cast(event); + if (QAbstractScrollArea* scrollArea = containingScrollArea(combo)) + forwardToScrollArea(wheelEvent, scrollArea); + wheelEvent->accept(); + return true; +} diff --git a/LASSIE/src/widgets/ComboBoxWheelGuard.hpp b/LASSIE/src/widgets/ComboBoxWheelGuard.hpp new file mode 100644 index 00000000..7f9889b9 --- /dev/null +++ b/LASSIE/src/widgets/ComboBoxWheelGuard.hpp @@ -0,0 +1,24 @@ +#ifndef COMBO_BOX_WHEEL_GUARD_HPP +#define COMBO_BOX_WHEEL_GUARD_HPP + +#include + +class QApplication; +class QEvent; + +// Prevents a closed combo box from changing selection when the user scrolls +// past it. If the combo is inside a scrolling view, the wheel input continues +// to that view so scrolling the surrounding page is uninterrupted. +class ComboBoxWheelGuard final : public QObject +{ +public: + explicit ComboBoxWheelGuard(QApplication& application); + +protected: + bool eventFilter(QObject* watched, QEvent* event) override; + +private: + Q_DISABLE_COPY_MOVE(ComboBoxWheelGuard) +}; + +#endif diff --git a/LASSIE/src/windows/MainWindow.cpp b/LASSIE/src/windows/MainWindow.cpp index 1e84727d..dfbbfd32 100644 --- a/LASSIE/src/windows/MainWindow.cpp +++ b/LASSIE/src/windows/MainWindow.cpp @@ -54,11 +54,16 @@ static QString resolveCmodBinary() { // Prefer a CMOD sitting next to the LASSIE binary (packaged builds: // LASSIE.app/Contents/MacOS/CMOD on macOS, the AppDir's usr/bin/CMOD on - // Linux, CMOD.exe on Windows). QStandardPaths adds the platform-specific + // Linux, cmod.exe on Windows). QStandardPaths adds the platform-specific // executable suffix, which is required for a portable Windows package. const QString appDir = QCoreApplication::applicationDirPath(); +#ifdef Q_OS_WIN + const QString executableName = QStringLiteral("cmod"); +#else + const QString executableName = QStringLiteral("CMOD"); +#endif const QString packagedCmod = QStandardPaths::findExecutable( - QStringLiteral("CMOD"), + executableName, {appDir, appDir + QStringLiteral("/../Resources")}); if (!packagedCmod.isEmpty()) { return QDir::cleanPath(packagedCmod); @@ -72,7 +77,7 @@ static void configureCmodEnvironment(QProcess *cmod) { // Portable Windows packages carry LilyPond next to LASSIE. Add only the // bundled bin directory to CMOD's child environment, so launching - // LASSIE.exe directly works without a machine-wide LilyPond install. + // lassie.exe directly works without a machine-wide LilyPond install. const QString appDir = QCoreApplication::applicationDirPath(); const QString lilyPond = QStandardPaths::findExecutable( QStringLiteral("lilypond"), diff --git a/Make-Portable-for-Windows.ps1 b/Make-Portable-for-Windows.ps1 index 0d6dccb1..52aa39ef 100644 --- a/Make-Portable-for-Windows.ps1 +++ b/Make-Portable-for-Windows.ps1 @@ -364,7 +364,7 @@ function Invoke-PortableSmokeTests { [Environment]::SetEnvironmentVariable("QT_PLUGIN_PATH", $null, "Process") [Environment]::SetEnvironmentVariable("QML2_IMPORT_PATH", $null, "Process") - $cmodOutput = & (Join-Path $PackageRoot "CMOD.exe") --help 2>&1 + $cmodOutput = & (Join-Path $PackageRoot "cmod.exe") --help 2>&1 if ($LASTEXITCODE -ne 0 -or ($cmodOutput -join "`n") -notmatch "Usage:") { throw "CMOD smoke test failed with exit code $LASTEXITCODE." } @@ -375,7 +375,7 @@ function Invoke-PortableSmokeTests { } $guiProcess = Start-Process ` - -FilePath (Join-Path $PackageRoot "LASSIE.exe") ` + -FilePath (Join-Path $PackageRoot "lassie.exe") ` -WorkingDirectory $PackageRoot ` -WindowStyle Hidden ` -PassThru @@ -480,12 +480,12 @@ try { $LassieExe = Find-BuiltExecutable ` -BuildRoot $BuildRoot ` -Component "LASSIE" ` - -Name "LASSIE.exe" ` + -Name "lassie.exe" ` -Configuration $Configuration $CmodExe = Find-BuiltExecutable ` -BuildRoot $BuildRoot ` -Component "CMOD" ` - -Name "CMOD.exe" ` + -Name "cmod.exe" ` -Configuration $Configuration Write-Host "LASSIE: $($LassieExe.FullName)" Write-Host "CMOD: $($CmodExe.FullName)" @@ -507,7 +507,7 @@ try { --no-translations ` --no-quick-import ` --dir $StageRoot ` - (Join-Path $StageRoot "LASSIE.exe") + (Join-Path $StageRoot "lassie.exe") if ($LASTEXITCODE -ne 0) { throw "windeployqt failed." } @@ -553,7 +553,7 @@ try { setlocal cd /d "%~dp0" set "PATH=%~dp0$launcherLilyBin;%PATH%" -start "" "%~dp0LASSIE.exe" %* +start "" "%~dp0lassie.exe" %* endlocal "@ Set-Content ` @@ -582,14 +582,14 @@ endlocal # UTF-8-without-BOM source files using the active Windows code page. $chineseInstructions = [Text.Encoding]::UTF8.GetString( [Convert]::FromBase64String( - "5Lit5paH6K+05piOCiAg6Kej5Y6L5pW05LiqIFpJUCDlkI7vvIznm7TmjqXlj4zlh7sgTEFTU0lFLmV4ZeOAggogIOS4jemcgOimgeWuieijhSBRdOOAgVZpc3VhbCBDKysg6L+Q6KGM5bqT5oiWIExpbHlQb25k77yM5Lmf5LiN6ZyA6KaB566h55CG5ZGY5p2D6ZmQ44CCCiAg6K+35Yu/5Y+q5aSN5Yi25Y2V5LiqIEVYRe+8m3Rvb2xz44CBcGxhdGZvcm1zIOWSjCBETEwg5paH5Lu26YO95piv56iL5bqP55qE5LiA6YOo5YiG44CC")) + "5Lit5paH6K+05piOCiAg6Kej5Y6L5pW05LiqIFpJUCDlkI7vvIznm7TmjqXlj4zlh7sgbGFzc2llLmV4ZeOAggogIOS4jemcgOimgeWuieijhSBRdOOAgVZpc3VhbCBDKysg6L+Q6KGM5bqT5oiWIExpbHlQb25k77yM5Lmf5LiN6ZyA6KaB566h55CG5ZGY5p2D6ZmQ44CCCiAg6K+35Yu/5Y+q5aSN5Yi25Y2V5LiqIEVYRe+8m3Rvb2xz44CBcGxhdGZvcm1zIOWSjCBETEwg5paH5Lu26YO95piv56iL5bqP55qE5LiA6YOo5YiG44CC")) $readme = @" DISSCO $version Portable for Windows x64 ======================================== RUN - Double-click LASSIE.exe. + Double-click lassie.exe. Run-DISSCO.bat is also provided as a compatibility launcher. REQUIREMENTS @@ -613,8 +613,8 @@ Build: $commit$dirtySuffix Write-Host "[8/10] Validating package contents and architecture..." -ForegroundColor Cyan $requiredPaths = @( - "LASSIE.exe", - "CMOD.exe", + "lassie.exe", + "cmod.exe", "Qt6Core.dll", "Qt6Gui.dll", "Qt6Widgets.dll", @@ -638,8 +638,8 @@ Build: $commit$dirtySuffix } foreach ($peFile in @( - (Join-Path $StageRoot "LASSIE.exe"), - (Join-Path $StageRoot "CMOD.exe"), + (Join-Path $StageRoot "lassie.exe"), + (Join-Path $StageRoot "cmod.exe"), (Join-Path $StageRoot "Qt6Core.dll") )) { if ((Get-PeMachine -Path $peFile) -ne 0x8664) { @@ -648,8 +648,8 @@ Build: $commit$dirtySuffix } foreach ($sourceAndPackaged in @( - @($LassieExe.FullName, (Join-Path $StageRoot "LASSIE.exe")), - @($CmodExe.FullName, (Join-Path $StageRoot "CMOD.exe")) + @($LassieExe.FullName, (Join-Path $StageRoot "lassie.exe")), + @($CmodExe.FullName, (Join-Path $StageRoot "cmod.exe")) )) { $sourceHash = (Get-FileHash -LiteralPath $sourceAndPackaged[0] -Algorithm SHA256).Hash $packagedHash = (Get-FileHash -LiteralPath $sourceAndPackaged[1] -Algorithm SHA256).Hash diff --git a/cmake-modules/FindSndFile.cmake b/cmake-modules/FindSndFile.cmake index f627d0a4..689c930a 100644 --- a/cmake-modules/FindSndFile.cmake +++ b/cmake-modules/FindSndFile.cmake @@ -157,7 +157,7 @@ if(NOT SNDFILE_FOUND AND WIN32 AND DISSCO_SNDFILE_FETCH_PREBUILT) find_package(SndFile CONFIG QUIET) if(SndFile_FOUND) _dissco_sndfile_extract_from_target() - # Expose the DLL so install() rules can ship it next to LASSIE.exe. + # Expose the DLL so install() rules can ship it next to lassie.exe. find_file(SNDFILE_DLL NAMES sndfile.dll libsndfile-1.dll HINTS "${_root}/bin" diff --git a/packaging/Packaging.cmake b/packaging/Packaging.cmake index ed49ac34..93f23d69 100644 --- a/packaging/Packaging.cmake +++ b/packaging/Packaging.cmake @@ -4,7 +4,7 @@ # - macOS: a .dmg containing LASSIE.app (with CMOD embedded in # Contents/MacOS/) via CPack's DragNDrop generator. Qt frameworks are # bundled by an install(CODE) step that invokes macdeployqt. -# - Windows: an NSIS installer .exe (LASSIE.exe + CMOD.exe + Qt DLLs) +# - Windows: an NSIS installer .exe (lassie.exe + cmod.exe + Qt DLLs) # via CPack's NSIS generator. Qt DLLs are bundled by an install(CODE) # step that invokes windeployqt. The installer writes registry entries # under HKLM\Software\Classes for the .dissco extension; these @@ -77,7 +77,7 @@ if(APPLE) endif() if(WIN32) - # windeployqt: walks LASSIE.exe's PE imports and copies the matching Qt + # windeployqt: walks lassie.exe's PE imports and copies the matching Qt # DLLs (and platform plugins, styles, etc.) into the same directory. # We pull the Qt6::windeployqt imported target's path so the path # tracks whatever Qt the build is configured against, not whatever @@ -90,13 +90,13 @@ if(WIN32) REQUIRED ) - # Run windeployqt against the installed LASSIE.exe. Crucially this is a + # Run windeployqt against the installed lassie.exe. Crucially this is a # separate invocation from any build-time windeployqt (e.g. the one in # LASSIE/CMakeLists.txt's POST_BUILD command, which targets the build # dir for dev convenience): the installer needs DLLs in CMAKE_INSTALL_PREFIX, # not the build dir. install(CODE " - message(STATUS \"Running windeployqt on \${CMAKE_INSTALL_PREFIX}/bin/LASSIE.exe\") + message(STATUS \"Running windeployqt on \${CMAKE_INSTALL_PREFIX}/bin/lassie.exe\") execute_process( COMMAND \"${WINDEPLOYQT_EXECUTABLE}\" --verbose 0 @@ -104,7 +104,7 @@ if(WIN32) --no-translations --no-system-d3d-compiler --no-quick-import - \"\${CMAKE_INSTALL_PREFIX}/bin/LASSIE.exe\" + \"\${CMAKE_INSTALL_PREFIX}/bin/lassie.exe\" RESULT_VARIABLE _wdq_result ) if(NOT _wdq_result EQUAL 0) @@ -123,9 +123,9 @@ if(WIN32) set(CPACK_PACKAGE_INSTALL_DIRECTORY "DISSCO ${DISSCO_VERSION}") set(CPACK_NSIS_EXECUTABLES_DIRECTORY "bin") - # Start Menu and desktop shortcuts pointing at LASSIE.exe. - set(CPACK_PACKAGE_EXECUTABLES "LASSIE;LASSIE") - set(CPACK_CREATE_DESKTOP_LINKS "LASSIE") + # Start Menu and desktop shortcuts pointing at lassie.exe. + set(CPACK_PACKAGE_EXECUTABLES "lassie;LASSIE") + set(CPACK_CREATE_DESKTOP_LINKS "lassie") if(EXISTS "${CMAKE_SOURCE_DIR}/packaging/windows/LASSIE.ico") # NSIS wants Windows-native backslashes in these paths. @@ -133,7 +133,7 @@ if(WIN32) string(REPLACE "\\" "\\\\" _nsis_icon "${_nsis_icon}") set(CPACK_NSIS_MUI_ICON "${_nsis_icon}") set(CPACK_NSIS_MUI_UNIICON "${_nsis_icon}") - set(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\LASSIE.exe") + set(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\lassie.exe") endif() # File-association registry entries. @@ -160,8 +160,8 @@ if(WIN32) WriteRegStr HKLM 'Software\\\\Classes\\\\.dissco' '' 'DISSCO.Project' WriteRegStr HKLM 'Software\\\\Classes\\\\DISSCO.Project' '' 'DISSCO Project' WriteRegStr HKLM 'Software\\\\Classes\\\\DISSCO.Project' 'FriendlyTypeName' 'DISSCO Project' - WriteRegStr HKLM 'Software\\\\Classes\\\\DISSCO.Project\\\\DefaultIcon' '' '$INSTDIR\\\\bin\\\\LASSIE.exe,0' - WriteRegStr HKLM 'Software\\\\Classes\\\\DISSCO.Project\\\\shell\\\\open\\\\command' '' '\\\"$INSTDIR\\\\bin\\\\LASSIE.exe\\\" \\\"%1\\\"' + WriteRegStr HKLM 'Software\\\\Classes\\\\DISSCO.Project\\\\DefaultIcon' '' '$INSTDIR\\\\bin\\\\lassie.exe,0' + WriteRegStr HKLM 'Software\\\\Classes\\\\DISSCO.Project\\\\shell\\\\open\\\\command' '' '\\\"$INSTDIR\\\\bin\\\\lassie.exe\\\" \\\"%1\\\"' System::Call 'shell32::SHChangeNotify(i 0x08000000, i 0, p 0, p 0)' ") set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " diff --git a/packaging/windows/dissco-association.reg b/packaging/windows/dissco-association.reg index 00eb11a5..9b704be3 100644 --- a/packaging/windows/dissco-association.reg +++ b/packaging/windows/dissco-association.reg @@ -2,7 +2,7 @@ Windows Registry Editor Version 5.00 ; .dissco file-association — manual install fallback. ; -; LASSIE.exe normally registers itself on first launch (see +; lassie.exe normally registers itself on first launch (see ; LASSIE/src/win/file_association.cpp), so this file isn't needed in the ; common case. It exists as: ; 1. on-disk documentation of the exact registry layout the @@ -10,9 +10,9 @@ Windows Registry Editor Version 5.00 ; 2. a manual fallback for offline / scripted setups where you don't want ; to launch LASSIE first. ; -; To use: replace REPLACE_WITH_FULL_PATH_TO_LASSIE.EXE below with the actual -; path to LASSIE.exe (backslashes must be escaped, e.g. -; C:\\Program Files\\LASSIE\\LASSIE.exe), save, and double-click to import. +; To use: replace REPLACE_WITH_FULL_PATH_TO_LASSIE below with the actual +; path to lassie.exe (backslashes must be escaped, e.g. +; C:\\Program Files\\LASSIE\\lassie.exe), save, and double-click to import. ; ; Per-user (HKCU) — no admin required. @@ -24,4 +24,4 @@ Windows Registry Editor Version 5.00 "FriendlyTypeName"="DISSCO Project" [HKEY_CURRENT_USER\Software\Classes\DISSCO.Project\shell\open\command] -@="\"REPLACE_WITH_FULL_PATH_TO_LASSIE.EXE\" \"%1\"" +@="\"REPLACE_WITH_FULL_PATH_TO_LASSIE\" \"%1\"" From cdc09276eda7d173b6a11a36873cafcb5cb364e4 Mon Sep 17 00:00:00 2001 From: DiyunZ Date: Thu, 6 Aug 2026 23:45:58 -0500 Subject: [PATCH 2/2] Apply lowercase executable names across platforms --- BUILDING_LINUX.md | 2 +- BUILDING_MACOS.md | 2 +- CMOD/CMakeLists.txt | 11 +++++------ LASSIE/CMakeLists.txt | 5 +---- LASSIE/src/core/Updater.cpp | 2 +- LASSIE/src/windows/MainWindow.cpp | 6 +----- packaging/Packaging.cmake | 12 ++++++------ packaging/linux/LASSIE.desktop | 2 +- 8 files changed, 17 insertions(+), 25 deletions(-) diff --git a/BUILDING_LINUX.md b/BUILDING_LINUX.md index 5a3a2a2a..af20d3cf 100644 --- a/BUILDING_LINUX.md +++ b/BUILDING_LINUX.md @@ -86,6 +86,6 @@ From the project root: cmake --build build --parallel QMAKE=/usr/lib/qt6/bin/qmake6 cmake --build build --target appimage -This produces `build/DISSCO--Linux-.AppImage` — a self-contained executable with Qt, libsndfile, and CMOD bundled in. It runs on most modern Linux distros without further installation. +This produces `build/DISSCO--Linux-.AppImage` — a self-contained executable with Qt, libsndfile, and the `cmod` executable bundled in. Its desktop launcher runs `lassie`, and the AppImage works on most modern Linux distros without further installation. The first invocation downloads `linuxdeploy` and `linuxdeploy-plugin-qt` into `build/.linuxdeploy/`. The icon (`packaging/linux/LASSIE.png`) is a placeholder; replace it with real artwork before cutting a release. diff --git a/BUILDING_MACOS.md b/BUILDING_MACOS.md index 471f8b5f..eb7935c0 100644 --- a/BUILDING_MACOS.md +++ b/BUILDING_MACOS.md @@ -93,7 +93,7 @@ From the project root: cmake --build build --parallel cmake --build build --target package -This produces `build/DISSCO--Darwin.dmg`. The DMG contains `LASSIE.app` with the CMOD binary embedded at `Contents/MacOS/CMOD` and Qt frameworks bundled in via `macdeployqt`. +This produces `build/DISSCO--Darwin.dmg`. The DMG contains `lassie.app` with the `cmod` binary embedded at `Contents/MacOS/cmod` and Qt frameworks bundled in via `macdeployqt`. The build's icon (`packaging/macos/LASSIE.icns`) is a placeholder; regenerate it from updated artwork via `packaging/macos/make-icns.sh`. diff --git a/CMOD/CMakeLists.txt b/CMOD/CMakeLists.txt index 75d4332a..5d5d66bf 100644 --- a/CMOD/CMakeLists.txt +++ b/CMOD/CMakeLists.txt @@ -27,11 +27,10 @@ target_include_directories(ModifierUsage PUBLIC target_compile_features(ModifierUsage PUBLIC cxx_std_20) add_executable(CMOD ${CMOD_SRC}) -set(CMOD_EXECUTABLE_BASENAME "CMOD") -if(WIN32) - set(CMOD_EXECUTABLE_BASENAME "cmod") - set_target_properties(CMOD PROPERTIES OUTPUT_NAME "${CMOD_EXECUTABLE_BASENAME}") -endif() +set(CMOD_EXECUTABLE_BASENAME "cmod") +set_target_properties(CMOD PROPERTIES + OUTPUT_NAME "${CMOD_EXECUTABLE_BASENAME}" +) # CMOD is consumed by LASSIE as a standalone subprocess (QProcess::start), # not linked into it. No ENABLE_EXPORTS / WINDOWS_EXPORT_ALL_SYMBOLS is # needed — and adding them just to "make the link work" backfires on @@ -79,7 +78,7 @@ message(STATUS "CMOD_BINARY set to ${CMOD_BINARY}") include(GNUInstallDirs) if(APPLE) install(TARGETS CMOD - RUNTIME DESTINATION LASSIE.app/Contents/MacOS + RUNTIME DESTINATION lassie.app/Contents/MacOS COMPONENT Runtime ) else() diff --git a/LASSIE/CMakeLists.txt b/LASSIE/CMakeLists.txt index e07f2616..9ab8c689 100644 --- a/LASSIE/CMakeLists.txt +++ b/LASSIE/CMakeLists.txt @@ -113,6 +113,7 @@ if(APPLE AND EXISTS "${LASSIE_ICON_MACOS}") endif() set_target_properties(LASSIE PROPERTIES + OUTPUT_NAME "lassie" WIN32_EXECUTABLE ON MACOSX_BUNDLE ON MACOSX_BUNDLE_BUNDLE_NAME "LASSIE" @@ -124,10 +125,6 @@ set_target_properties(LASSIE PROPERTIES MACOSX_BUNDLE_ICON_FILE "LASSIE.icns" ) -if(WIN32) - set_target_properties(LASSIE PROPERTIES OUTPUT_NAME "lassie") -endif() - install(TARGETS LASSIE BUNDLE DESTINATION . COMPONENT Runtime LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Runtime diff --git a/LASSIE/src/core/Updater.cpp b/LASSIE/src/core/Updater.cpp index 78ad3589..7ecd992a 100644 --- a/LASSIE/src/core/Updater.cpp +++ b/LASSIE/src/core/Updater.cpp @@ -300,7 +300,7 @@ void Updater::downloadAndInstall(const ReleaseInfo &info) { void Updater::launchInstaller(const QString &localPath) { #if defined(Q_OS_MACOS) - // Mount the DMG in Finder; user drags LASSIE.app into Applications. + // Mount the DMG in Finder; user drags lassie.app into Applications. if (!QDesktopServices::openUrl(QUrl::fromLocalFile(localPath))) { reportError(tr("Couldn't open downloaded installer at %1").arg(localPath)); return; diff --git a/LASSIE/src/windows/MainWindow.cpp b/LASSIE/src/windows/MainWindow.cpp index dfbbfd32..2d3b407a 100644 --- a/LASSIE/src/windows/MainWindow.cpp +++ b/LASSIE/src/windows/MainWindow.cpp @@ -53,15 +53,11 @@ namespace { static QString resolveCmodBinary() { // Prefer a CMOD sitting next to the LASSIE binary (packaged builds: - // LASSIE.app/Contents/MacOS/CMOD on macOS, the AppDir's usr/bin/CMOD on + // lassie.app/Contents/MacOS/cmod on macOS, the AppDir's usr/bin/cmod on // Linux, cmod.exe on Windows). QStandardPaths adds the platform-specific // executable suffix, which is required for a portable Windows package. const QString appDir = QCoreApplication::applicationDirPath(); -#ifdef Q_OS_WIN const QString executableName = QStringLiteral("cmod"); -#else - const QString executableName = QStringLiteral("CMOD"); -#endif const QString packagedCmod = QStandardPaths::findExecutable( executableName, {appDir, appDir + QStringLiteral("/../Resources")}); diff --git a/packaging/Packaging.cmake b/packaging/Packaging.cmake index 93f23d69..b3d2ffe3 100644 --- a/packaging/Packaging.cmake +++ b/packaging/Packaging.cmake @@ -1,7 +1,7 @@ # Release packaging for DISSCO (LASSIE GUI + CMOD CLI). # # Produces: -# - macOS: a .dmg containing LASSIE.app (with CMOD embedded in +# - macOS: a .dmg containing lassie.app (with cmod embedded in # Contents/MacOS/) via CPack's DragNDrop generator. Qt frameworks are # bundled by an install(CODE) step that invokes macdeployqt. # - Windows: an NSIS installer .exe (lassie.exe + cmod.exe + Qt DLLs) @@ -37,9 +37,9 @@ if(APPLE) get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) set(MACDEPLOYQT_EXECUTABLE "${_qt_bin_dir}/macdeployqt") - # Bundle Qt frameworks into the installed LASSIE.app. Runs at `cmake - # --install` time (and therefore during CPack), after the executable and - # CMOD have been copied in. + # Bundle Qt frameworks into the installed lassie.app. Runs at `cmake + # --install` time (and therefore during CPack), after lassie and cmod + # have been copied in. # # macdeployqt prints alarming-looking "ERROR: Cannot resolve rpath" lines # for weak-linked Qt modules (QtPdf, QtSvg, QtVirtualKeyboard*) that @@ -48,10 +48,10 @@ if(APPLE) # valid. We capture output and drop those known-benign lines so real # problems remain visible. install(CODE " - message(STATUS \"Running macdeployqt on \${CMAKE_INSTALL_PREFIX}/LASSIE.app\") + message(STATUS \"Running macdeployqt on \${CMAKE_INSTALL_PREFIX}/lassie.app\") execute_process( COMMAND \"${MACDEPLOYQT_EXECUTABLE}\" - \"\${CMAKE_INSTALL_PREFIX}/LASSIE.app\" + \"\${CMAKE_INSTALL_PREFIX}/lassie.app\" -always-overwrite RESULT_VARIABLE _mdq_result OUTPUT_VARIABLE _mdq_out diff --git a/packaging/linux/LASSIE.desktop b/packaging/linux/LASSIE.desktop index be4a1c4d..d7f396dd 100644 --- a/packaging/linux/LASSIE.desktop +++ b/packaging/linux/LASSIE.desktop @@ -3,7 +3,7 @@ Type=Application Name=LASSIE GenericName=DISSCO Composition Environment Comment=Graphical front-end for the DISSCO composition system -Exec=LASSIE %F +Exec=lassie %F Icon=LASSIE Terminal=false Categories=AudioVideo;Audio;Music;