diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 857f9d5..108b010 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,6 +42,13 @@ jobs: draft: false prerelease: false + # macOS signing (optional): GitHub repo secrets + # MACOS_CERTIFICATE_BASE64 — base64 of exported .p12 ("Developer ID Application" + private key) + # MACOS_CERTIFICATE_PASSWORD — password for that .p12 + # APPLE_CODESIGN_IDENTITY — full name from Keychain, e.g. "Developer ID Application: Your Name (TEAMID)" + # Notarization (optional, recommended for Gatekeeper): + # APPLE_API_KEY_ID, APPLE_API_ISSUER_ID — from App Store Connect → Users and Access → Integrations → Keys + # APPLE_API_KEY_BASE64 — base64 of the downloaded AuthKey_XXXXXX.p8 file build-macos: runs-on: macos-latest continue-on-error: false @@ -139,17 +146,12 @@ jobs: echo "Found app at: $APP_PATH" - name: Deploy Qt dependencies and create DMG + env: + APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }} run: | APP_PATH="${{ steps.find_app.outputs.app_path }}" - # Deploy Qt dependencies into the app bundle macdeployqt "$APP_PATH" -always-overwrite -verbose=2 - # Create DMG - macdeployqt "$APP_PATH" -dmg -always-overwrite - DMG_PATH="${APP_PATH%.app}.dmg" - echo "DMG created at: $DMG_PATH" - ls -lh "$(dirname $DMG_PATH)" - # Verify Qt libraries are bundled if [ -d "$APP_PATH/Contents/Frameworks" ]; then echo "Qt frameworks bundled:" ls -lh "$APP_PATH/Contents/Frameworks" | head -10 diff --git a/typistprison/popups/prisonerdialog.h b/typistprison/popups/prisonerdialog.h index 81cbac0..93a83db 100644 --- a/typistprison/popups/prisonerdialog.h +++ b/typistprison/popups/prisonerdialog.h @@ -264,10 +264,9 @@ class PrisonerDialog : public QDialog { } )"); } else { - QString numericText = lineEdit->text().remove(QRegularExpression("[^0-9]")); - if (numericText.isEmpty()) { - actionButton->setEnabled(false); - } else { + // When the goal is explicitly set to +∞, treat it as a valid + // word goal and allow launching, even though there are no digits. + if (lineEdit->text() == "+∞") { actionButton->setEnabled(true); actionButton->setIcons(QIcon(":/icons/right_arrow.png"), QIcon(":/icons/right_arrow_hover.png")); actionButton->setSilentBehavior(R"( @@ -288,6 +287,32 @@ class PrisonerDialog : public QDialog { padding: 4px 8px; } )"); + } else { + QString numericText = lineEdit->text().remove(QRegularExpression("[^0-9]")); + if (numericText.isEmpty()) { + actionButton->setEnabled(false); + } else { + actionButton->setEnabled(true); + actionButton->setIcons(QIcon(":/icons/right_arrow.png"), QIcon(":/icons/right_arrow_hover.png")); + actionButton->setSilentBehavior(R"( + QPushButton { + background-color: transparent; + border: 1px solid #5A5A5A; + color: #BDBDBD; + border-radius: 4px; + padding: 4px 8px; + } + )"); + actionButton->setHoverBehavior(R"( + QPushButton { + background-color: transparent; + border: 1px solid #999999; + color: #DEDEDE; + border-radius: 4px; + padding: 4px 8px; + } + )"); + } } } if (ratio == 1.0) { @@ -373,9 +398,15 @@ class PrisonerDialog : public QDialog { actionButton->setLayoutDirection(Qt::RightToLeft); connect(actionButton, &QPushButton::clicked, this, [=]() { + int wordGoal; // Get word goal from lineEdit - QString numericText = lineEdit->text().remove(QRegularExpression("[^0-9]")); - int wordGoal = numericText.toInt(); + if (lineEdit->text() == "+∞") { + wordGoal = -1; + } else { + QString numericText = lineEdit->text().remove(QRegularExpression("[^0-9]")); + wordGoal = numericText.toInt(); + } + // Get time limit from slider double ratio = slider->value() / 100.0; @@ -659,21 +690,45 @@ private slots: int totalWidth = sizes[0] + sizes[1]; // Total width of splitter double spliterRatio = static_cast(sizes[0] - minLeftWidth) / (totalWidth - minLeftWidth); + int numericValue = 0; if (spliterRatio == 1.0) { + // Infinite word goal: show "+∞" and keep the Launch button enabled. lineEdit->setText("+∞"); slider->setValue(100); // Set slider to maximum slider->setEnabled(false); // Disable slider interaction + + actionButton->setEnabled(true); + actionButton->setIcons(QIcon(":/icons/right_arrow.png"), QIcon(":/icons/right_arrow_hover.png")); + actionButton->setSilentBehavior(R"( + QPushButton { + background-color: transparent; + border: 1px solid #5A5A5A; + color: #BDBDBD; + border-radius: 4px; + padding: 4px 8px; + } + )"); + actionButton->setHoverBehavior(R"( + QPushButton { + background-color: transparent; + border: 1px solid #999999; + color: #DEDEDE; + border-radius: 4px; + padding: 4px 8px; + } + )"); return; + } else { + double sliderRatio = static_cast(sizes[0]) / totalWidth; + slider->setValue(sliderRatio * 100); // Set slider value + slider->setEnabled(true); // Enable slider interaction + + // Reverse map to get the numeric value from width + numericValue = reverseMapFromRange(spliterRatio); + + // Update the QLineEdit content + lineEdit->setText(QString::number(numericValue)); } - double sliderRatio = static_cast(sizes[0]) / totalWidth; - slider->setValue(sliderRatio * 100); // Set slider value - slider->setEnabled(true); // Enable slider interaction - - // Reverse map to get the numeric value from width - int numericValue = reverseMapFromRange(spliterRatio); - - // Update the QLineEdit content - lineEdit->setText(QString::number(numericValue)); // Change visibility of actionbutton ⏯️ if (numericValue >= 100) { diff --git a/typistprison/prisonermanager.cpp b/typistprison/prisonermanager.cpp index e187095..dcd828f 100644 --- a/typistprison/prisonermanager.cpp +++ b/typistprison/prisonermanager.cpp @@ -30,6 +30,14 @@ void PrisonerManager::clear() { void PrisonerManager::updateTimerProgress(qreal timerProgressLength) { this->timerProgressLength = timerProgressLength; + + // If wordGoal is -1, we are in "infinite words" mode: + // - Do not track background progress in the editor + // - Do not run the chaser / failure logic + if (wordGoal == -1) { + return; + } + greyWordCount = qRound(wordGoal * timerProgressLength) + baseWordCount; if (isPrisoner) { // if chaser reach the progress, fail the prisoner mode diff --git a/typistprison/progressborderwidget.cpp b/typistprison/progressborderwidget.cpp index ae07a93..2151c7d 100644 --- a/typistprison/progressborderwidget.cpp +++ b/typistprison/progressborderwidget.cpp @@ -72,9 +72,19 @@ void ProgressBorderWidget::deactivatePrisonerMode() { void ProgressBorderWidget::startTimerProgress(int timeLimit, int wordGoal) { timerProgress = 0.0; + targetWordCount = wordGoal; + + // For infinite word goals (wordGoal == -1), we do not start the background + // timer/chaser at all. The visual chaser and timer-driven failure are disabled. + if (wordGoal == -1) { + totalTime = 0.0; + prisonerTimer->stop(); + isTimerRunning = false; + return; + } + // convert minutes time to seconds totalTime = timeLimit * 60 * 10; - targetWordCount = wordGoal; prisonerTimer->start(100); // update interval isTimerRunning = true; } @@ -91,7 +101,9 @@ void ProgressBorderWidget::updateTimerProgress() { timerProgress = timerProgress + 1; requestRepaint(); - if (prisonerManager && totalTime > 0.0) { + // If there is no meaningful total time (including infinite-word mode where + // we disabled the chaser), skip notifying the PrisonerManager. + if (prisonerManager && totalTime > 0.0 && targetWordCount != -1) { qreal normalizedProgress = timerProgress / totalTime; prisonerManager->updateTimerProgress(normalizedProgress); } @@ -313,8 +325,10 @@ void ProgressBorderWidget::paintEvent(QPaintEvent *event) { qreal perimeter = 2 * (width + height) - startDistance - endDistance + 3 * cornerArcLength; qreal timerProgressLength; - if (totalTime == 0.0){ - timerProgressLength = 0; + // If there is no timer (including infinite word goal where we disabled it), + // do not paint the chaser border at all. + if (totalTime <= 0.0 || targetWordCount == -1) { + timerProgressLength = -1; } else { timerProgressLength = (timerProgress / totalTime) * perimeter; }