diff --git a/qml/Main.qml b/qml/Main.qml index 41424f8..561972b 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -1,8 +1,107 @@ import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import PrimeNumbers -Window { - width: 640 - height: 480 +ApplicationWindow { visible: true - title: qsTr("Hello World") + width: 400 + height: 500 + title: "Factorization" + + FactorizationController { + id: factorizationController + } + + ColumnLayout { + anchors.fill: parent + spacing: 10 + + Text { + text: "Enter a number to factorize:" + font.pixelSize: 16 + Layout.alignment: Qt.AlignHCenter + } + + TextField { + id: numberInput + placeholderText: "Enter number" + // NOTE: We can't use the IntValidator here, since it doesn't support 64-bit values (long long) + Layout.fillWidth: true + onTextChanged: { + // If the number changes, reset + factorizationController.reset(); + } + } + + RowLayout { + Layout.fillWidth: true + spacing: 10 + + Button { + text: { + if (factorizationController.isPaused) { + return "Resume"; + } else if (factorizationController.isRunning) { + return "..."; + } else { + return "Start"; + } + } + Layout.fillWidth: true + enabled: !factorizationController.isRunning + onClicked: { + if (factorizationController.isPaused) { + factorizationController.resume(); + } else { + factorizationController.start(parseInt(numberInput.text, 10)); + } + } + } + + Button { + text: "Pause" + Layout.fillWidth: true + enabled: factorizationController.isRunning + onClicked: factorizationController.stop() + } + } + + ProgressBar { + Layout.fillWidth: true + from: 0 + to: 100 + value: factorizationController.progress + } + + Text { + text: "Current Factor: " + factorizationController.currentFactor + font.pixelSize: 14 + Layout.alignment: Qt.AlignHCenter + } + + Text { + text: "Factors Found:" + font.pixelSize: 14 + Layout.alignment: Qt.AlignHCenter + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + model: factorizationController.factors + delegate: Text { + text: modelData + font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter + width: parent.width + } + } + + Text { + text: "Warning: This only shows factors below sqrt(num)" + font.pixelSize: 12 + Layout.alignment: Qt.AlignHCenter + } + } } diff --git a/src/FactorizationController.cpp b/src/FactorizationController.cpp new file mode 100644 index 0000000..089ac7a --- /dev/null +++ b/src/FactorizationController.cpp @@ -0,0 +1,142 @@ +#include "FactorizationController.h" +#include +#include + +FactorizationController::FactorizationController(QObject* parent) : QObject{parent}, m_isRunning(false), m_currentFactNumber(0), m_currentFactor(2) { + assert(connect(&m_timer, &QTimer::timeout, this, &FactorizationController::onTimerTick)); +} + +long long FactorizationController::number() const { + return m_originalNumber; +} + +int FactorizationController::progress() const { + // If current factor is at or below 2 already, we must be done, + // stopFactor can never be less than 2, that makes no sense. + if (m_currentFactNumber == 1 || m_currentFactor <= 2 || m_currentFactor > m_stopFactor) + return 100; + + // Determine the progress based on the current factor, in comparison to stop factor. + // since factor being <= 2 means 100%, we sub 2 here. + double progress = static_cast(m_currentFactor - 2) / static_cast(m_stopFactor - 2); + + // Return the value as int percentage + return static_cast(std::clamp(progress * 100, 0.0, 100.0)); +} + +bool FactorizationController::isFinished() const { + return m_isFinished; +} + +bool FactorizationController::isRunning() const { + return m_isRunning; +} + +bool FactorizationController::isPaused() const { + return m_isPaused; +} + +long long FactorizationController::currentFactor() const { + return m_currentFactor; +} + +QList FactorizationController::factors() const { + return m_factors; +} + +void FactorizationController::start(long long number) { + m_originalNumber = number; + m_currentFactNumber = number; + m_currentFactor = 2; + // we could also just compute this every time, but sqrt is pretty expensive + m_stopFactor = static_cast(std::sqrt(m_currentFactNumber)); + m_isRunning = true; + m_isFinished = false; + m_factors.clear(); + emit isRunningChanged(); + emit isFinishedChanged(); + emit numberChanged(); + emit currentFactorChanged(); + emit progressChanged(); + emit factorsChanged(); + + m_timer.start(0); +} + +void FactorizationController::resume() { + if (!m_isPaused) + return; + + m_isRunning = true; + m_isPaused = false; + emit isRunningChanged(); + emit isPausedChanged(); + + m_timer.start(0); +} + +void FactorizationController::stop() { + if (!m_isRunning) + return; + + m_isRunning = false; + m_isPaused = true; + emit isRunningChanged(); + emit isPausedChanged(); + + m_timer.stop(); +} + +void FactorizationController::reset() { + if (m_isRunning) + stop(); + + // Only continue if we were in a paused state, or running state + // (since we called stop before, that would put is into a paused state now) + // Otherwise, it means start wasn't yet called, or reset was called already. + if (!m_isPaused) + return; + + m_isPaused = false; + m_currentFactNumber = m_originalNumber; + m_currentFactor = 2; + m_factors.clear(); + emit isPausedChanged(); + emit currentFactorChanged(); + emit progressChanged(); + emit factorsChanged(); +} + +void FactorizationController::onTimerTick() { + factorize(); +} + +void FactorizationController::factorize() { + if (!m_isRunning) + return; + + if (m_currentFactNumber == 1 || m_currentFactor > m_stopFactor) { + // Computation done + m_timer.stop(); + m_isRunning = false; + m_isFinished = true; + emit isRunningChanged(); + emit isFinishedChanged(); + emit progressChanged(); + return; + } + + // The factorization process (very basic factorization example) + if (m_currentFactNumber % m_currentFactor == 0) { + m_currentFactNumber /= m_currentFactor; + + m_factors.append(m_currentFactor); + emit factorsChanged(); + + // Don't increase current factor, keep dividing by it until no longer divisible + } else { + m_currentFactor++; + emit currentFactorChanged(); + emit progressChanged(); + } +} diff --git a/src/FactorizationController.h b/src/FactorizationController.h new file mode 100644 index 0000000..803f8c6 --- /dev/null +++ b/src/FactorizationController.h @@ -0,0 +1,164 @@ +#ifndef FACTORIZATIONCONTROLLER_H +#define FACTORIZATIONCONTROLLER_H + +#include +#include +#include +#include + +/** + * @class FactorizationController + * @brief A class that performs integer factorization in a step-by-step manner. + * + * This class uses a timer to progressively factorize a given number, allowing + * for real-time updates on progress and discovered factors. It is designed + * to be used in a Qt-based QML application. + */ +class FactorizationController : public QObject { + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(bool isRunning READ isRunning NOTIFY isRunningChanged) + Q_PROPERTY(bool isFinished READ isFinished NOTIFY isFinishedChanged) + Q_PROPERTY(bool isPaused READ isPaused NOTIFY isPausedChanged) + Q_PROPERTY(long long number READ number NOTIFY numberChanged) + Q_PROPERTY(long long currentFactor READ currentFactor NOTIFY currentFactorChanged) + Q_PROPERTY(int progress READ progress NOTIFY progressChanged) + Q_PROPERTY(QList factors READ factors NOTIFY factorsChanged) + + public: + explicit FactorizationController(QObject* parent = nullptr); + + // region: Getters + + /** + * @brief Gets the current progress of factorization. + * @return The progress percentage (0-100). + */ + int progress() const; + + /** + * @brief Checks if the factorization process is running. + * @return True if the factorization is in progress, otherwise false. + * + * This just informs whether the task is active (scheduled to run), not whether it's + * running at this very moment, as this implementation doesn't use threads. + */ + bool isRunning() const; + + /** + * @brief Checks if the factorization process is done. + * @return True if the factorization is completed, otherwise false. + * + * This will only be true if the factorization process finished without being + * explicitly stopped ahead of time (e.g. when the computation is done and we + * have all the factors). + */ + bool isFinished() const; + + /** + * @brief Checks if the factorization process is currently paused. + * @return True if the factorization process is paused, otherwise false. + * + * This function differs from just !isRunning, as this will only be set when + * the computation is currently paused, but can still be resumed. E.g. the + * computation was already started at some point, and stopped (paused) later, + * without having been reset. + * + * This being true is a prerequisite to resuming. + */ + bool isPaused() const; + + /** + * @brief Gets the original number being factorized. + * @return The number being factorized. + */ + long long number() const; + + /** + * @brief Gets the current factor being tested. + * @return The current factor. + * + * The factorization process is implemented in a way that simply checks whether a number + * is a factor, going one by one. This returns the current factor (to be checked in the + * next iteration). + */ + long long currentFactor() const; + + /** + * @brief Gets the list of discovered factors (may not be complete). + * @return A QList of extracted prime factors. + * + * This returns the factors that were already discovered Note that this list might not + * be complete (if the computation is still running). If you need to know whether you + * can trust this result, check isFinished first. + */ + QList factors() const; + + // endregion + // region: Functions + + /** + * @brief Starts the factorization process for a given number. + * @param number The number to be factorized. + * + * This will start a 0-tick timer, which triggers a partial computation on each run, + * to let the event loop cycle in between. This implementation does NOT rely on threads. + */ + Q_INVOKABLE void start(long long number); + + /** + * @brief Stops the ongoing factorization process. + * + * This will stop the timer, meaning the computation will no longer get triggered + * by the event loop, essentially pausing it. Note that this does not reset the already + * computed details, which means the computation can be resumed later, allowing for + * inspection of the currently computed values. + */ + Q_INVOKABLE void stop(); + + /** + * @brief Resumes a previously stopped factorization process. + */ + Q_INVOKABLE void resume(); + + /** + * @brief Resets the factorization process data. + * + * This will clear the current progress (if any). If the factorization process is running, + * this will stop it. If the factorization process is paused, this will reset isPaused, + * forcing a restart. + */ + Q_INVOKABLE void reset(); + + // endregion + + signals: + void isRunningChanged(); + void isFinishedChanged(); + void numberChanged(); + void currentFactorChanged(); + void factorsChanged(); + void progressChanged(); + void isPausedChanged(); + + private slots: + void onTimerTick(); + + private: + bool m_isRunning; ///< Indicates whether factorization is active. + bool m_isFinished; ///< Indicates whether the factorization process is done. + bool m_isPaused; ///< Indicates whether the factorization process is paused (can be resumed). + long long m_currentFactNumber; ///< The number currently being factorized. + long long m_originalNumber; ///< The original input number. + long long m_currentFactor; ///< The current divisor being checked. + long long m_stopFactor; ///< The stopping limit (square root of original number, saved for efficiency). + QList m_factors; ///< List of discovered prime factors. + QTimer m_timer; ///< Timer for stepwise execution. + + /** + * @brief Performs one step of the factorization process. + */ + void factorize(); +}; + +#endif // FACTORIZATIONCONTROLLER_H diff --git a/src/main.cpp b/src/main.cpp index d833c28..b7f7531 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,15 @@ +#include "FactorizationController.h" #include #include +#include -int main(int argc, char *argv[]) { - QGuiApplication app(argc, argv); +int main(int argc, char* argv[]) { + QGuiApplication app(argc, argv); - QQmlApplicationEngine engine; - QObject::connect( - &engine, &QQmlApplicationEngine::objectCreationFailed, &app, - []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + QQmlApplicationEngine engine; + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); - engine.load(QStringLiteral("qrc:/qml/Main.qml")); + engine.load(QStringLiteral("qrc:/qml/Main.qml")); - return app.exec(); + return app.exec(); }