diff --git a/lib/ipatool-go b/lib/ipatool-go index 60f95e6..6aede7e 160000 --- a/lib/ipatool-go +++ b/lib/ipatool-go @@ -1 +1 @@ -Subproject commit 60f95e6cf8dff33cb735cdd390266f26bcafd3ff +Subproject commit 6aede7e92bc338ff7cb8f9e7672bc1a39c35aaba diff --git a/resources.qrc b/resources.qrc index 1e40c54..2777c10 100644 --- a/resources.qrc +++ b/resources.qrc @@ -48,5 +48,6 @@ resources/airplayer-tutorial.mp4 resources/ipad-mockups/ipad.png resources/DeveloperDiskImages.json + resources/keychain.mp4 \ No newline at end of file diff --git a/resources/keychain.mp4 b/resources/keychain.mp4 new file mode 100644 index 0000000..bd137ec Binary files /dev/null and b/resources/keychain.mp4 differ diff --git a/src/appdownloadbasedialog.cpp b/src/appdownloadbasedialog.cpp index c858d6c..f394d26 100644 --- a/src/appdownloadbasedialog.cpp +++ b/src/appdownloadbasedialog.cpp @@ -117,7 +117,8 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId, // if (promptToOpenDir) QMessageBox::critical( this, "Download Failed", - QString("Failed to download %1. Error code: %2") + QString("Failed to download %1. Try signing out and back " + "in. Error code: %2") .arg(m_appName) .arg(result)); reject(); diff --git a/src/appstoremanager.cpp b/src/appstoremanager.cpp index b97ddd2..102756d 100644 --- a/src/appstoremanager.cpp +++ b/src/appstoremanager.cpp @@ -1,5 +1,6 @@ #include "appstoremanager.h" #include "libipatool-go.h" +#include "settingsmanager.h" #include #include #include @@ -46,7 +47,24 @@ AppStoreManager::AppStoreManager(QObject *parent) bool AppStoreManager::initialize() { - int result = IpaToolInitialize(); + bool useUnsecureBackend = + SettingsManager::sharedInstance()->useUnsecureBackend(); + + QString backends; + + if (useUnsecureBackend) { + backends = "file"; + } else { +#ifdef __APPLE__ + backends = "keychain,file"; +#elif defined(WIN32) + backends = "wincred,file"; +#else + backends = "secret-service,file"; +#endif + } + + int result = IpaToolInitialize(backends.toUtf8().data()); if (result != 0) { qDebug() << "IpaToolInitialize failed with error code:" << result; return false; diff --git a/src/appswidget.cpp b/src/appswidget.cpp index b8e2b55..1672e07 100644 --- a/src/appswidget.cpp +++ b/src/appswidget.cpp @@ -5,7 +5,10 @@ #include "appinstalldialog.h" #include "appstoremanager.h" #include "iDescriptor-ui.h" +#include "keychaindialog.h" #include "logindialog.h" +#include "mainwindow.h" +#include "settingsmanager.h" #include "sponsorwidget.h" #include "zlineedit.h" #include @@ -156,17 +159,6 @@ void AppsWidget::setupUI() // --- Status and Login Button --- m_manager = AppStoreManager::sharedInstance(); - if (!m_manager) { - qDebug() << "AppStoreManager failed to initialize"; - m_statusLabel->setText("Failed to initialize"); - m_loginButton->setText("Failed to initialize"); - m_loginButton->setEnabled(false); - m_loginButton->setStyleSheet( - "background-color: #ccc; color: #666; border: none; border-radius: " - "4px; padding: 8px 16px; font-size: 14px;"); - } else { - onAppStoreInitialized(m_manager->getAccountInfo()); - } m_statusLabel->setStyleSheet("font-size: 14px; color: #666;"); @@ -213,8 +205,10 @@ void AppsWidget::setupUI() &AppsWidget::onAppStoreInitialized); connect(m_manager, &AppStoreManager::loggedOut, this, &AppsWidget::onAppStoreInitialized); +} - // fetch sponsors +void AppsWidget::init() +{ QUrl sponsorsUrl("http://localhost:5173/sponsors.json"); QNetworkRequest request(sponsorsUrl); QNetworkReply *reply = m_networkManager->get(request); @@ -247,8 +241,8 @@ void AppsWidget::setupUI() QJsonObject silverObj = sponsorObj["silver"].toObject(); QJsonObject bronzeObj = sponsorObj["bronze"].toObject(); - // Store the platinum members to be used when populating the - // grid + // Store the platinum members to be used when populating + // the grid m_platinumSponsors = platinumObj["members"].toArray(); m_goldSponsors = goldObj["members"].toArray(); m_silverSponsors = silverObj["members"].toArray(); @@ -259,16 +253,47 @@ void AppsWidget::setupUI() } } qDebug() << "Sponsors fetch completed"; - showDefaultApps(); reply->deleteLater(); + QTimer::singleShot(0, this, &AppsWidget::handleInit); } catch (...) { qDebug() << "Exception occurred while processing sponsors"; - showDefaultApps(); reply->deleteLater(); + QTimer::singleShot(0, this, &AppsWidget::handleInit); } }); } +void AppsWidget::handleInit() +{ + if (!m_manager) { + qDebug() << "AppStoreManager failed to initialize"; + m_statusLabel->setText("Failed to initialize"); + m_loginButton->setText("Failed to initialize"); + m_loginButton->setEnabled(false); + m_loginButton->setStyleSheet( + "background-color: #ccc; color: #666; " + "border: " + "none; border-radius: " + "4px; padding: 8px 16px; font-size: 14px;"); + return; + } + if (!SettingsManager::sharedInstance()->useUnsecureBackend() && + SettingsManager::sharedInstance()->showKeychainDialog()) { +#ifdef __APPLE__ + KeychainDialog dialog(this); + if (dialog.exec() == QDialog::Rejected) { + // pass empty QJsonObject to skip signing in + onAppStoreInitialized(QJsonObject()); + showDefaultApps(); + return; + } +#endif + } + // todo also change in the ipatoolinitialze as the backend is alrady enabled + onAppStoreInitialized(m_manager->getAccountInfo()); + showDefaultApps(); +} + void AppsWidget::onAppStoreInitialized(const QJsonObject &accountInfo) { if (accountInfo.contains("success") && @@ -277,10 +302,13 @@ void AppsWidget::onAppStoreInitialized(const QJsonObject &accountInfo) QString email = accountInfo.value("email").toString(); m_statusLabel->setText("Signed in as " + email); m_isLoggedIn = true; + m_searchEdit->setDisabled(false); } else { m_statusLabel->setText("Not signed in"); + m_searchEdit->setDisabled(true); } } else { + m_searchEdit->setDisabled(true); m_statusLabel->setText("Not signed in"); } @@ -710,6 +738,11 @@ void AppsWidget::createAppCard( void AppsWidget::onDownloadIpaClicked(const QString &name, const QString &bundleId) { + if (!m_isLoggedIn) { + QMessageBox::information(this, "Sign In Required", + "Please sign in to download IPA files."); + return; + } QString description = "Download the IPA file for " + name; AppDownloadDialog dialog(name, bundleId, description, this); dialog.exec(); diff --git a/src/appswidget.h b/src/appswidget.h index b448cbe..fccc6ef 100644 --- a/src/appswidget.h +++ b/src/appswidget.h @@ -66,6 +66,7 @@ public: static AppsWidget *sharedInstance(); void onAppCardClicked(const QString &appName, const QString &bundleId, const QString &description); + void init(); private slots: void onLoginClicked(); void onDownloadIpaClicked(const QString &name, const QString &bundleId); @@ -90,7 +91,7 @@ private: void clearAppGrid(); void populateDefaultApps(); void createSponsorCard(QGridLayout *gridLayout, int row, int col); - + void handleInit(); QStackedWidget *m_stackedWidget; QWidget *m_defaultAppsPage; QWidget *m_loadingPage; diff --git a/src/core/services/dnssd/dnssd_service.cpp b/src/core/services/dnssd/dnssd_service.cpp index 97c049e..0b542a2 100644 --- a/src/core/services/dnssd/dnssd_service.cpp +++ b/src/core/services/dnssd/dnssd_service.cpp @@ -245,11 +245,30 @@ void DNSSD_API DnssdService::addrInfoCallback( inet_ntop(AF_INET, &addr_in->sin_addr, ip, sizeof(ip)); NetworkDevice device; - device.name = pending.name; + // Extract a better device name from hostname or use TXT records + QString friendlyName = pending.hostname; + if (friendlyName.endsWith(".local.")) { + friendlyName = + friendlyName.left(friendlyName.length() - 7); // Remove ".local." + qDebug() << "friendly name:" << friendlyName; + } + + // Try to get device name from TXT records first + if (pending.txt.contains("DvNm")) { + device.name = pending.txt["DvNm"]; + qDebug() << "Device name from DvNm TXT record:" << device.name; + } else if (pending.txt.contains("Name")) { + device.name = pending.txt["Name"]; + qDebug() << "Device name from Name TXT record:" << device.name; + } else { + // Use the cleaned hostname as fallback + qDebug() << "Using hostname as device name:" << friendlyName; + device.name = friendlyName; + } + device.hostname = pending.hostname; device.address = QString::fromUtf8(ip); device.port = pending.port > 0 ? pending.port : 22; // Default to SSH port - // device.txt = pending.txt; qDebug() << "Resolved IP for Apple device:" << device.name << "at" << device.address << ":" << device.port; diff --git a/src/keychaindialog.cpp b/src/keychaindialog.cpp new file mode 100644 index 0000000..5b22123 --- /dev/null +++ b/src/keychaindialog.cpp @@ -0,0 +1,135 @@ +#include "keychaindialog.h" +#include "settingsmanager.h" +#include +#include +#include +#include +#include +#include +#include + +KeychainDialog::KeychainDialog(QWidget *parent) + : QDialog(parent), m_player(nullptr), m_videoWidget(nullptr), + m_mainLayout(nullptr), m_okButton(nullptr), m_titleLabel(nullptr), + m_descriptionLabel(nullptr), m_dontShowAgainCheckbox(nullptr) +{ + setupUI(); + setupVideo(); +} + +KeychainDialog::~KeychainDialog() +{ + if (m_player) { + m_player->stop(); + } +} + +void KeychainDialog::setupUI() +{ + setWindowTitle("Keychain Access Required"); + setModal(true); + setMinimumSize(600, 450); + resize(700, 500); + + m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setContentsMargins(20, 20, 20, 20); + m_mainLayout->setSpacing(15); + + // Title label + m_titleLabel = new QLabel("Keychain Access Required"); + m_titleLabel->setAlignment(Qt::AlignCenter); + m_titleLabel->setStyleSheet( + "font-size: 18px; font-weight: bold; margin-bottom: 10px;"); + m_mainLayout->addWidget(m_titleLabel); + + // Description label + m_descriptionLabel = new QLabel( + "In order to sign in to App Store we use the keychain backend to " + "safely store and retrieve your credentials. Please click on \"Always " + "Allow\" when prompted. " + "This is a security feature to protect your Apple ID credentials. You " + "can disable this in Settings."); + m_descriptionLabel->setAlignment(Qt::AlignCenter); + m_descriptionLabel->setWordWrap(true); + m_mainLayout->addWidget(m_descriptionLabel); + + // Video widget + m_videoWidget = new QVideoWidget(); + m_videoWidget->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding); + m_videoWidget->setAspectRatioMode( + Qt::AspectRatioMode::KeepAspectRatioByExpanding); + m_videoWidget->setStyleSheet( + "QVideoWidget { background-color: transparent; }"); + m_videoWidget->setMinimumHeight(250); + m_mainLayout->addWidget(m_videoWidget, 1); + + m_dontShowAgainCheckbox = new QCheckBox("Don't show this again"); + m_mainLayout->addWidget(m_dontShowAgainCheckbox, 0, Qt::AlignCenter); + + QHBoxLayout *buttonsLayout = new QHBoxLayout(); + m_skipSigningInButton = new QPushButton("Skip For Now"); + m_skipSigningInButton->setFixedHeight(40); + + m_okButton = new QPushButton("OK, I understand"); + m_okButton->setDefault(true); + m_okButton->setFixedHeight(40); + + buttonsLayout->addWidget(m_skipSigningInButton); + buttonsLayout->addWidget(m_okButton); + + m_mainLayout->addLayout(buttonsLayout, Qt::AlignCenter); + + connect(m_okButton, &QPushButton::clicked, this, + &KeychainDialog::onOkClicked); + connect(m_skipSigningInButton, &QPushButton::clicked, this, + &KeychainDialog::onSkipSigningInClicked); +} + +void KeychainDialog::setupVideo() +{ + m_player = new QMediaPlayer(this); + m_player->setVideoOutput(m_videoWidget); + m_player->setSource(QUrl("qrc:/resources/keychain.mp4")); + + // Loop the video + connect(m_player, &QMediaPlayer::mediaStatusChanged, this, + [this](QMediaPlayer::MediaStatus status) { + if (status == QMediaPlayer::EndOfMedia) { + m_player->setPosition(0); + m_player->play(); + } + }); + + // Auto-play when ready + connect(m_player, &QMediaPlayer::mediaStatusChanged, this, + [this](QMediaPlayer::MediaStatus status) { + if (status == QMediaPlayer::LoadedMedia) { + m_player->play(); + } + }); +} + +void KeychainDialog::onOkClicked() +{ + if (m_dontShowAgainCheckbox && m_dontShowAgainCheckbox->isChecked()) { + SettingsManager::sharedInstance()->setShowKeychainDialog(false); + } + + if (m_player) { + m_player->stop(); + } + accept(); +} + +void KeychainDialog::onSkipSigningInClicked() +{ + if (m_dontShowAgainCheckbox && m_dontShowAgainCheckbox->isChecked()) { + SettingsManager::sharedInstance()->setShowKeychainDialog(false); + } + + if (m_player) { + m_player->stop(); + } + reject(); +} \ No newline at end of file diff --git a/src/keychaindialog.h b/src/keychaindialog.h new file mode 100644 index 0000000..a022adb --- /dev/null +++ b/src/keychaindialog.h @@ -0,0 +1,38 @@ +#ifndef KEYCHAIN_DIALOG_H +#define KEYCHAIN_DIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +class KeychainDialog : public QDialog +{ + Q_OBJECT + +public: + explicit KeychainDialog(QWidget *parent = nullptr); + ~KeychainDialog(); + +private slots: + void onOkClicked(); + void onSkipSigningInClicked(); + +private: + void setupUI(); + void setupVideo(); + + QMediaPlayer *m_player; + QVideoWidget *m_videoWidget; + QVBoxLayout *m_mainLayout; + QPushButton *m_okButton; + QPushButton *m_skipSigningInButton; + QLabel *m_titleLabel; + QLabel *m_descriptionLabel; + QCheckBox *m_dontShowAgainCheckbox; +}; + +#endif // KEYCHAIN_DIALOG_H \ No newline at end of file diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f102ac6..ca2480a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -134,21 +134,18 @@ MainWindow::MainWindow(QWidget *parent) this, &MainWindow::updateNoDevicesConnected); m_ZTabWidget->addTab(m_mainStackedWidget, "iDevice"); - m_ZTabWidget->addTab(AppsWidget::sharedInstance(), "Apps"); + auto *appsWidgetTab = + m_ZTabWidget->addTab(AppsWidget::sharedInstance(), "Apps"); m_ZTabWidget->addTab(new ToolboxWidget(this), "Toolbox"); auto *jailbrokenWidget = new JailbrokenWidget(this); m_ZTabWidget->addTab(jailbrokenWidget, "Jailbroken"); m_ZTabWidget->finalizeStyles(); - // connect( - // m_ZTabWidget, &ZTabWidget::currentChanged, this, - // [this, jailbrokenWidget](int index) { - // if (index == 3) { // Jailbroken tab - // jailbrokenWidget->initWidget(); - // } - // }, - // Qt::SingleShotConnection); + connect( + appsWidgetTab, &ZTab::clicked, this, + [this](int index) { AppsWidget::sharedInstance()->init(); }, + Qt::SingleShotConnection); // settings button ZIconWidget *settingsButton = new ZIconWidget( diff --git a/src/settingsmanager.cpp b/src/settingsmanager.cpp index 1d96c54..ab0333d 100644 --- a/src/settingsmanager.cpp +++ b/src/settingsmanager.cpp @@ -106,6 +106,17 @@ void SettingsManager::setUnmountiFuseOnExit(bool enabled) } #endif +bool SettingsManager::useUnsecureBackend() const +{ + return m_settings->value("useUnsecureBackend-ipatool", false).toBool(); +} + +void SettingsManager::setUseUnsecureBackend(bool enabled) +{ + m_settings->setValue("useUnsecureBackend-ipatool", enabled); + m_settings->sync(); +} + QString SettingsManager::theme() const { return m_settings->value("theme", "System Default").toString(); @@ -128,6 +139,17 @@ void SettingsManager::setConnectionTimeout(int seconds) m_settings->sync(); } +bool SettingsManager::showKeychainDialog() const +{ + return m_settings->value("showKeychainDialog", true).toBool(); +} + +void SettingsManager::setShowKeychainDialog(bool show) +{ + m_settings->setValue("showKeychainDialog", show); + m_settings->sync(); +} + void SettingsManager::doIfEnabled(Setting setting, std::function action) { bool shouldExecute = false; @@ -167,8 +189,10 @@ void SettingsManager::resetToDefaults() #ifndef __APPLE__ setUnmountiFuseOnExit(false); #endif + setUseUnsecureBackend(false); setTheme("System Default"); setConnectionTimeout(30); + setShowKeychainDialog(true); } void SettingsManager::saveFavoritePlace(const QString &path, diff --git a/src/settingsmanager.h b/src/settingsmanager.h index 343ddcc..b514819 100644 --- a/src/settingsmanager.h +++ b/src/settingsmanager.h @@ -55,6 +55,8 @@ public: bool unmountiFuseOnExit() const; void setUnmountiFuseOnExit(bool enabled); #endif + bool useUnsecureBackend() const; + void setUseUnsecureBackend(bool enabled); QString theme() const; void setTheme(const QString &theme); @@ -62,6 +64,9 @@ public: int connectionTimeout() const; void setConnectionTimeout(int seconds); + bool showKeychainDialog() const; + void setShowKeychainDialog(bool show); + // Utility method for conditional execution void doIfEnabled(Setting setting, std::function action); diff --git a/src/settingswidget.cpp b/src/settingswidget.cpp index be0f280..dcb389e 100644 --- a/src/settingswidget.cpp +++ b/src/settingswidget.cpp @@ -105,6 +105,18 @@ void SettingsWidget::setupUI() scrollLayout->addWidget(deviceGroup); + // === SECURITY SETTINGS === + auto *securityGroup = new QGroupBox("Security"); + auto *securityLayout = new QVBoxLayout(securityGroup); + + m_useUnsecureBackend = + new QCheckBox("Use unsecure backend for app store (ipatool)"); + m_useUnsecureBackend->setToolTip( + "Enabling this may put your Apple account at risk but you don't have " + "to deal with Apple keychain."); + securityLayout->addWidget(m_useUnsecureBackend); + scrollLayout->addWidget(securityGroup); + // Add stretch to push everything to the top scrollLayout->addStretch(); @@ -158,7 +170,7 @@ void SettingsWidget::loadSettings() } m_connectionTimeout->setValue(sm->connectionTimeout()); - + m_useUnsecureBackend->setChecked(sm->useUnsecureBackend()); // Disable apply button initially m_applyButton->setEnabled(false); } @@ -180,6 +192,27 @@ void SettingsWidget::connectSignals() this, &SettingsWidget::onSettingChanged); connect(m_connectionTimeout, QOverload::of(&QSpinBox::valueChanged), this, &SettingsWidget::onSettingChanged); + + connect(m_useUnsecureBackend, &QCheckBox::toggled, this, [this]() { + // since this is unsafe if its being enabled, show a warning + if (m_useUnsecureBackend->isChecked()) { + auto reply = QMessageBox::warning( + this, "Warning", + "Enabling this will not encrypt your Apple account which is a " + "security risk. Are you sure you want to enable this?", + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + + if (reply == QMessageBox::Yes) { + m_restartRequired = true; + onSettingChanged(); + } else { + m_useUnsecureBackend->setChecked(false); + } + } else { + m_restartRequired = true; + onSettingChanged(); + } + }); } void SettingsWidget::onBrowseButtonClicked() @@ -223,7 +256,13 @@ void SettingsWidget::onResetToDefaultsClicked() void SettingsWidget::onApplyClicked() { saveSettings(); - QMessageBox::information(this, "Settings", "Settings have been applied."); + QMessageBox::information(this, "Settings", + m_restartRequired + ? "Settings applied. Please restart " + "the application for changes to " + "take effect." + : "Settings applied."); + m_restartRequired = false; } void SettingsWidget::onSettingChanged() @@ -244,6 +283,7 @@ void SettingsWidget::saveSettings() #ifndef __APPLE__ sm->setUnmountiFuseOnExit(m_unmount_iFuseDrives->isChecked()); #endif + sm->setUseUnsecureBackend(m_useUnsecureBackend->isChecked()); sm->setTheme(m_themeCombo->currentText()); sm->setConnectionTimeout(m_connectionTimeout->value()); diff --git a/src/settingswidget.h b/src/settingswidget.h index b026ed2..4c77ba7 100644 --- a/src/settingswidget.h +++ b/src/settingswidget.h @@ -40,7 +40,7 @@ private: #ifndef __APPLE__ QCheckBox *m_unmount_iFuseDrives; #endif - + QCheckBox *m_useUnsecureBackend; // Device Connection QSpinBox *m_connectionTimeout; @@ -48,6 +48,8 @@ private: QPushButton *m_checkUpdatesButton; QPushButton *m_resetButton; QPushButton *m_applyButton; + + bool m_restartRequired = false; }; #endif // SETTINGSWIDGET_H diff --git a/src/welcomewidget.cpp b/src/welcomewidget.cpp index 95bd166..910cda8 100644 --- a/src/welcomewidget.cpp +++ b/src/welcomewidget.cpp @@ -29,23 +29,16 @@ void WelcomeWidget::setupUI() m_mainLayout->addSpacing(12); // Subtitle - m_subtitleLabel = createStyledLabel("100% Open-Source & Free", 16, false); + m_subtitleLabel = createStyledLabel("Open-Source & Free", 16, false); m_subtitleLabel->setAlignment(Qt::AlignCenter); QPalette palette = m_subtitleLabel->palette(); - palette.setColor(QPalette::WindowText, - palette.color(QPalette::WindowText).lighter(140)); - m_subtitleLabel->setPalette(palette); m_mainLayout->addWidget(m_subtitleLabel); m_mainLayout->addSpacing(10); m_imageLabel = new ResponsiveQLabel(); m_imageLabel->setPixmap(QPixmap(":/resources/connect.png")); - // Let the pixmap scale while preserving aspect ratio m_imageLabel->setScaledContents(true); - // Prefer centered, not full-width expansion m_imageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - // Cap size so it stays nicely centered on large windows - // m_imageLabel->setMaximumSize(480, 320); m_imageLabel->setStyleSheet("background: transparent; border: none;"); @@ -53,15 +46,9 @@ void WelcomeWidget::setupUI() m_mainLayout->addWidget(m_imageLabel, 0, Qt::AlignHCenter); m_mainLayout->addSpacing(10); - // Instruction text m_instructionLabel = createStyledLabel( "Please connect an iOS device to get started", 14, false); m_instructionLabel->setAlignment(Qt::AlignCenter); - QPalette instructionPalette = m_instructionLabel->palette(); - instructionPalette.setColor( - QPalette::WindowText, - instructionPalette.color(QPalette::WindowText).lighter(120)); - m_instructionLabel->setPalette(instructionPalette); m_mainLayout->addWidget(m_instructionLabel); m_mainLayout->addSpacing(10); diff --git a/src/ztabwidget.cpp b/src/ztabwidget.cpp index 3f85184..21903fe 100644 --- a/src/ztabwidget.cpp +++ b/src/ztabwidget.cpp @@ -72,7 +72,7 @@ void ZTabWidget::setupGlider() m_gliderAnimation->setEasingCurve(QEasingCurve::OutCubic); } -int ZTabWidget::addTab(QWidget *widget, const QString &label) +ZTab *ZTabWidget::addTab(QWidget *widget, const QString &label) { ZTab *tab = new ZTab(label, m_tabBar); connect(tab, &ZTab::clicked, this, &ZTabWidget::onTabClicked); @@ -84,7 +84,7 @@ int ZTabWidget::addTab(QWidget *widget, const QString &label) m_stackedWidget->addWidget(widget); m_buttonGroup->addButton(tab, index); - return index; + return tab; } void ZTabWidget::setCurrentIndex(int index) diff --git a/src/ztabwidget.h b/src/ztabwidget.h index 0e8addc..f10dd54 100644 --- a/src/ztabwidget.h +++ b/src/ztabwidget.h @@ -27,7 +27,7 @@ class ZTabWidget : public QWidget public: explicit ZTabWidget(QWidget *parent = nullptr); void finalizeStyles(); - int addTab(QWidget *widget, const QString &label); + ZTab *addTab(QWidget *widget, const QString &label); void setCurrentIndex(int index); int currentIndex() const; QWidget *widget(int index) const;