#include "main_window.h" #include "ui_main_window.h" #include #include #include #include #include // icon_utils.h pulls in , so it is included after all Qt // headers to keep the Windows include ordering that Qt expects. #include "icon_utils.h" #include namespace qwfassoc { // region: Helpers namespace { // Convert our internal TargetScope enum to the wfassocpp::Scope value used by // the program APIs (register / unregister / link / unlink / is_registered). wfassocpp::Scope toWfassocScope(TargetScope scope) { return scope == TargetScope::User ? wfassocpp::Scope::User : wfassocpp::Scope::System; } // Decide which CellState a queried ExtStatus corresponds to, given the // resolved "self" name for this extension. We treat the cell as Self when the // resolved display name matches our own; otherwise it is treated as Other. CellState classifyCell(const QString& selfName, const QString& observedName) { return observedName == selfName ? CellState::Self : CellState::Other; } // Effective icon to draw for a cell, based on its state. QPixmap effectiveCellIcon(const ExtRow& row, const CellData& cell) { switch (cell.state) { case CellState::Blank: return QPixmap(); case CellState::Self: return row.selfIcon; case CellState::Other: return cell.icon; } return QPixmap(); } } // namespace // endregion // region: Construction MainWindow::MainWindow(wfassocpp::Program program, TargetScope scope, QWidget* parent) : QDialog(parent), ui_(new Ui::MainWindow), program_(std::move(program)), scope_(scope) { ui_->setupUi(this); // Resolve program metadata that several labels depend on. These calls may // throw std::runtime_error on failure; the caller (main.cpp) is expected // to handle that and present an error dialog before exiting. programName_ = QString::fromUtf8(program_.ResolveName()); { auto iconRc = program_.ResolveIcon(); auto handle = iconRc.GetIcon(); programIcon_ = icon_utils::fromHicon(handle); } // Fetch the current user name for the second column header. The USERNAME // environment variable is good enough on Windows; fall back to a static // translatable placeholder if it is unset for any reason. userName_ = QProcessEnvironment::systemEnvironment().value( QStringLiteral("USERNAME"), tr("User")); // Give the table as much vertical room as possible inside its layout. ui_->associationsLayout->setStretch(2, 1); // Reasonable default column widths so dotted extensions and ProgId names // stay readable in the 480px dialog. ui_->assocTable->setColumnWidth(0, 90); ui_->assocTable->setColumnWidth(1, 175); ui_->assocTable->setColumnWidth(2, 175); ui_->assocTable->verticalHeader()->setVisible(false); ui_->assocTable->setShowGrid(true); // Wire signals. connect(ui_->installButton, &QPushButton::clicked, this, &MainWindow::onInstallClicked); connect(ui_->uninstallButton, &QPushButton::clicked, this, &MainWindow::onUninstallClicked); connect(ui_->selectUserButton, &QPushButton::clicked, this, &MainWindow::onSelectUserClicked); connect(ui_->selectSystemButton, &QPushButton::clicked, this, &MainWindow::onSelectSystemClicked); connect(ui_->assocTable, &QTableWidget::cellClicked, this, &MainWindow::onCellClicked); connect(ui_->okButton, &QPushButton::clicked, this, &MainWindow::onOkClicked); connect(ui_->cancelButton, &QPushButton::clicked, this, &MainWindow::onCancelClicked); connect(ui_->applyButton, &QPushButton::clicked, this, &MainWindow::onApplyClicked); retranslateUi(); refreshProgramState(); } MainWindow::~MainWindow() = default; // endregion // region: UI Text void MainWindow::retranslateUi() { // The window title embeds the program name, so it is composed at runtime // through tr() + QString::arg to stay translatable. setWindowTitle(tr("%1 Options").arg(programName_)); // Application tab. if (!programIcon_.isNull()) { ui_->appIconLabel->setPixmap( programIcon_.scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } ui_->appDescLabel->setText( tr("Install or uninstall %1 here.").arg(programName_)); // File association tab. ui_->assocHeaderLabel->setText( tr("File types associated with %1:").arg(programName_)); QStringList headers; headers << tr("Type") << userName_ << tr("All Users"); ui_->assocTable->setHorizontalHeaderLabels(headers); } // endregion // region: State Refresh void MainWindow::refreshProgramState() { const bool registered = program_.IsRegistered(toWfassocScope(scope_)); ui_->installButton->setEnabled(!registered); ui_->uninstallButton->setEnabled(registered); // The whole file-association tab is disabled until the application has // been registered in the active scope. Additionally, the system column is // permanently disabled when running in user mode. const bool userColumnActive = registered; const bool systemColumnActive = registered && isSystemColumnEnabled(); ui_->selectUserButton->setEnabled(userColumnActive); ui_->selectSystemButton->setEnabled(systemColumnActive); ui_->assocTable->setEnabled(registered); rebuildTable(); } void MainWindow::rebuildTable() { refreshing_ = true; rows_.clear(); const size_t count = program_.ExtsLen(); rows_.reserve(count); ui_->assocTable->setRowCount(static_cast(count)); for (size_t i = 0; i < count; ++i) { ExtRow row; row.index = i; // Self extension info: dotted body, display name and cached icon. auto selfExt = program_.ResolveExt(i); row.extBody = QString::fromUtf8(selfExt.GetExt()); row.dottedExt = QString::fromUtf8(selfExt.GetDottedExt()); row.selfName = QString::fromUtf8(selfExt.GetName()); row.selfIcon = icon_utils::fromHicon(selfExt.GetIcon()); // Query the user-view and system-view states. None means blank; // a match against our self name means Self; anything else is Other // and we keep the original name/icon around for display. auto userStatus = program_.QueryExt(wfassocpp::View::User, i); if (userStatus) { const QString observedName = QString::fromUtf8(userStatus->GetName()); row.initialUser.state = classifyCell(row.selfName, observedName); row.initialUser.name = observedName; row.initialUser.icon = icon_utils::fromHicon(userStatus->GetIcon()); } auto systemStatus = program_.QueryExt(wfassocpp::View::System, i); if (systemStatus) { const QString observedName = QString::fromUtf8(systemStatus->GetName()); row.initialSystem.state = classifyCell(row.selfName, observedName); row.initialSystem.name = observedName; row.initialSystem.icon = icon_utils::fromHicon(systemStatus->GetIcon()); } row.pendingUser = row.initialUser; row.pendingSystem = row.initialSystem; rows_.push_back(std::move(row)); // Create the QTableWidgetItem cells once; subsequent refreshes only // update their text/icon and flags. const int rowIdx = static_cast(i); auto* typeItem = new QTableWidgetItem(row.dottedExt); ui_->assocTable->setItem(rowIdx, 0, typeItem); auto* userItem = new QTableWidgetItem; userItem->setTextAlignment(Qt::AlignCenter); ui_->assocTable->setItem(rowIdx, 1, userItem); auto* systemItem = new QTableWidgetItem; systemItem->setTextAlignment(Qt::AlignCenter); ui_->assocTable->setItem(rowIdx, 2, systemItem); refreshRowDisplay(rowIdx); } refreshing_ = false; updateApplyButtonEnabled(); } void MainWindow::refreshRowDisplay(int row) { if (row < 0 || row >= static_cast(rows_.size())) { return; } const ExtRow& r = rows_[row]; // Column 0: hybrid icon (user-preferred) + dotted extension. QPixmap hybridIcon; if (r.pendingUser.state != CellState::Blank) { hybridIcon = effectiveCellIcon(r, r.pendingUser); } else if (r.pendingSystem.state != CellState::Blank) { hybridIcon = effectiveCellIcon(r, r.pendingSystem); } QTableWidgetItem* typeItem = ui_->assocTable->item(row, 0); if (typeItem) { typeItem->setIcon(QIcon(hybridIcon)); typeItem->setText(r.dottedExt); } // Column 1: user scope display name. QTableWidgetItem* userItem = ui_->assocTable->item(row, 1); if (userItem) { userItem->setText(r.pendingUser.state == CellState::Blank ? QString() : r.pendingUser.name); } // Column 2: system scope display name. When the system column is // inactive (user-only run), the cells are flagged as disabled so that // clicks are ignored and the rendering is greyed out. QTableWidgetItem* systemItem = ui_->assocTable->item(row, 2); if (systemItem) { systemItem->setText(r.pendingSystem.state == CellState::Blank ? QString() : r.pendingSystem.name); const Qt::ItemFlags enabledFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; // Without Qt::ItemIsEnabled the cell renders disabled (greyed out) // and cellClicked is not emitted, so clicks are silently ignored. const Qt::ItemFlags disabledFlags = Qt::ItemIsSelectable; if (isSystemColumnEnabled()) { systemItem->setFlags(enabledFlags); } else { systemItem->setFlags(disabledFlags); } } } void MainWindow::updateApplyButtonEnabled() { bool dirty = false; for (const ExtRow& r : rows_) { if (r.pendingUser.state != r.initialUser.state || r.pendingSystem.state != r.initialSystem.state) { dirty = true; break; } } ui_->applyButton->setEnabled(dirty); } // endregion // region: Cell Interaction void MainWindow::toggleCell(int row, int column) { if (refreshing_) { return; } if (row < 0 || row >= static_cast(rows_.size())) { return; } CellData* cell = nullptr; const ExtRow* rowPtr = &rows_[row]; if (column == 1) { cell = &rows_[row].pendingUser; } else if (column == 2) { if (!isSystemColumnEnabled()) { return; } cell = &rows_[row].pendingSystem; } else { return; } // Toggle: Self -> Blank, anything else -> Self. if (cell->state == CellState::Self) { cell->state = CellState::Blank; cell->name.clear(); cell->icon = QPixmap(); } else { cell->state = CellState::Self; cell->name = rowPtr->selfName; cell->icon = rowPtr->selfIcon; } refreshRowDisplay(row); updateApplyButtonEnabled(); } void MainWindow::selectAllInScope(bool isUser) { if (!isUser && !isSystemColumnEnabled()) { return; } // Progressively select more. If there is at least one blank cell, the // first click only fills blanks; otherwise the click overrides cells // pointing at other handlers as well. bool hasBlank = false; for (ExtRow& r : rows_) { const CellData& cell = isUser ? r.pendingUser : r.pendingSystem; if (cell.state == CellState::Blank) { hasBlank = true; break; } } for (ExtRow& r : rows_) { CellData& cell = isUser ? r.pendingUser : r.pendingSystem; if (hasBlank) { if (cell.state == CellState::Blank) { cell.state = CellState::Self; cell.name = r.selfName; cell.icon = r.selfIcon; } } else if (cell.state != CellState::Self) { cell.state = CellState::Self; cell.name = r.selfName; cell.icon = r.selfIcon; } } for (size_t i = 0; i < rows_.size(); ++i) { refreshRowDisplay(static_cast(i)); } updateApplyButtonEnabled(); } // endregion // region: Apply void MainWindow::applyAllChanges() { // Walk through every row and commit any cell whose pending state differs // from the initial snapshot. wfassoc's link/unlink take an index rather // than a scope/view, so we map columns back to (scope, index) pairs. for (const ExtRow& r : rows_) { if (r.pendingUser.state != r.initialUser.state) { if (r.pendingUser.state == CellState::Self) { program_.LinkExt(wfassocpp::Scope::User, r.index); } else { program_.UnlinkExt(wfassocpp::Scope::User, r.index); } } if (r.pendingSystem.state != r.initialSystem.state) { if (r.pendingSystem.state == CellState::Self) { program_.LinkExt(wfassocpp::Scope::System, r.index); } else { program_.UnlinkExt(wfassocpp::Scope::System, r.index); } } } // Re-query and rebuild the table so the UI reflects the live registry. rebuildTable(); } bool MainWindow::isSystemColumnEnabled() const { return scope_ == TargetScope::System; } // endregion // region: Slots void MainWindow::onInstallClicked() { try { program_.Register(toWfassocScope(scope_)); } catch (const std::exception& e) { QMessageBox::critical(this, windowTitle(), QString::fromUtf8(e.what())); return; } QMessageBox::information(this, windowTitle(), tr("Application installed successfully.")); refreshProgramState(); } void MainWindow::onUninstallClicked() { try { program_.Unregister(toWfassocScope(scope_)); } catch (const std::exception& e) { QMessageBox::critical(this, windowTitle(), QString::fromUtf8(e.what())); return; } QMessageBox::information(this, windowTitle(), tr("Application uninstalled successfully.")); refreshProgramState(); } void MainWindow::onSelectUserClicked() { selectAllInScope(/*isUser=*/true); } void MainWindow::onSelectSystemClicked() { selectAllInScope(/*isUser=*/false); } void MainWindow::onCellClicked(int row, int column) { toggleCell(row, column); } void MainWindow::onOkClicked() { try { applyAllChanges(); } catch (const std::exception& e) { QMessageBox::critical(this, windowTitle(), QString::fromUtf8(e.what())); // Sync the table with the live registry, since some changes may have // been committed before the failure. refreshProgramState(); return; } accept(); } void MainWindow::onCancelClicked() { reject(); } void MainWindow::onApplyClicked() { try { applyAllChanges(); } catch (const std::exception& e) { QMessageBox::critical(this, windowTitle(), QString::fromUtf8(e.what())); refreshProgramState(); return; } } // endregion } // namespace qwfassoc