From 1da09acdf7d7bf3ecdd9d42e9803fba7a28ef7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= Date: Fri, 13 Jan 2017 20:04:15 +0200 Subject: [PATCH] UI|Home: Adding packages to profiles using the package info dialog --- ...ckagepopupwidget.h => packageinfodialog.h} | 16 +- doomsday/apps/client/src/clientapp.cpp | 2 +- doomsday/apps/client/src/dd_main.cpp | 2 +- .../src/ui/dialogs/createprofiledialog.cpp | 2 +- .../ui/dialogs/packagecompatibilitydialog.cpp | 2 +- .../client/src/ui/dialogs/packagesdialog.cpp | 6 +- .../client/src/ui/home/gamecolumnwidget.cpp | 8 +- .../src/ui/home/gamepanelbuttonwidget.cpp | 12 +- .../src/ui/home/packagescolumnwidget.cpp | 4 +- ...epopupwidget.cpp => packageinfodialog.cpp} | 163 +++++++++++++----- .../src/ui/widgets/packagesbuttonwidget.cpp | 2 +- .../src/ui/widgets/packagessidebarwidget.cpp | 2 +- .../client/src/ui/widgets/packageswidget.cpp | 2 +- 13 files changed, 155 insertions(+), 68 deletions(-) rename doomsday/apps/client/include/ui/widgets/{packagepopupwidget.h => packageinfodialog.h} (74%) rename doomsday/apps/client/src/ui/widgets/{packagepopupwidget.cpp => packageinfodialog.cpp} (69%) diff --git a/doomsday/apps/client/include/ui/widgets/packagepopupwidget.h b/doomsday/apps/client/include/ui/widgets/packageinfodialog.h similarity index 74% rename from doomsday/apps/client/include/ui/widgets/packagepopupwidget.h rename to doomsday/apps/client/include/ui/widgets/packageinfodialog.h index c932cb9d46..e5cb725953 100644 --- a/doomsday/apps/client/include/ui/widgets/packagepopupwidget.h +++ b/doomsday/apps/client/include/ui/widgets/packageinfodialog.h @@ -16,30 +16,30 @@ * http://www.gnu.org/licenses */ -#ifndef DENG_CLIENT_UI_PACKAGEPOPUPWIDGET_H -#define DENG_CLIENT_UI_PACKAGEPOPUPWIDGET_H +#ifndef DENG_CLIENT_UI_PACKAGEINFODIALOG_H +#define DENG_CLIENT_UI_PACKAGEINFODIALOG_H #include /** - * Popup showing information about a package. + * Dialog showing information about a package. */ -class PackagePopupWidget : public de::DialogWidget +class PackageInfoDialog : public de::DialogWidget { Q_OBJECT public: - PackagePopupWidget(de::String const &packageId); - PackagePopupWidget(de::File const *packageFile); + PackageInfoDialog(de::String const &packageId); + PackageInfoDialog(de::File const *packageFile); public slots: void playInGame(); void addToProfile(); void configure(); - void uninstall(); + void showFile(); private: DENG2_PRIVATE(d) }; -#endif // DENG_CLIENT_UI_PACKAGEPOPUPWIDGET_H +#endif // DENG_CLIENT_UI_PACKAGEINFODIALOG_H diff --git a/doomsday/apps/client/src/clientapp.cpp b/doomsday/apps/client/src/clientapp.cpp index 8b038322fd..11d29b91b1 100644 --- a/doomsday/apps/client/src/clientapp.cpp +++ b/doomsday/apps/client/src/clientapp.cpp @@ -897,7 +897,7 @@ void ClientApp::unloadGame(GameProfile const &upcomingGame) // Game has been set to null, update window. ClientWindow::main().setTitle(DD_ComposeMainWindowTitle()); - if (!upcomingGame.game().isEmpty()) + if (!upcomingGame.gameId().isEmpty()) { ClientWindow &mainWin = ClientWindow::main(); mainWin.taskBar().close(); diff --git a/doomsday/apps/client/src/dd_main.cpp b/doomsday/apps/client/src/dd_main.cpp index d3d1d28143..f35a3592b0 100644 --- a/doomsday/apps/client/src/dd_main.cpp +++ b/doomsday/apps/client/src/dd_main.cpp @@ -1093,7 +1093,7 @@ static void initialize() else { StringList ids; - foreach (GameProfile const *prof, playable) ids << prof->game(); + foreach (GameProfile const *prof, playable) ids << prof->gameId(); msg += "The following games are playable: " + String::join(ids, ", "); } App_Error(msg.toLatin1()); diff --git a/doomsday/apps/client/src/ui/dialogs/createprofiledialog.cpp b/doomsday/apps/client/src/ui/dialogs/createprofiledialog.cpp index 82edcad777..c0f3b76f30 100644 --- a/doomsday/apps/client/src/ui/dialogs/createprofiledialog.cpp +++ b/doomsday/apps/client/src/ui/dialogs/createprofiledialog.cpp @@ -141,7 +141,7 @@ GameProfile *CreateProfileDialog::makeProfile() const void CreateProfileDialog::fetchFrom(GameProfile const &profile) { editor().setText(profile.name()); - d->gameChoice->setSelected(d->gameChoice->items().findData(profile.game())); + d->gameChoice->setSelected(d->gameChoice->items().findData(profile.gameId())); d->packages->setPackages(profile.packages()); } diff --git a/doomsday/apps/client/src/ui/dialogs/packagecompatibilitydialog.cpp b/doomsday/apps/client/src/ui/dialogs/packagecompatibilitydialog.cpp index d0037f37e8..93bb0a3ecf 100644 --- a/doomsday/apps/client/src/ui/dialogs/packagecompatibilitydialog.cpp +++ b/doomsday/apps/client/src/ui/dialogs/packagecompatibilitydialog.cpp @@ -93,7 +93,7 @@ DENG2_PIMPL(PackageCompatibilityDialog) // The only action on the packages is to view information. actions << new ui::SubwidgetItem(tr("..."), ui::Up, [this] () -> PopupWidget * { - return new PackagePopupWidget(list->actionPackage()); + return new PackageInfoDialog(list->actionPackage()); }); self().area().add(list = new PackagesWidget(wanted)); diff --git a/doomsday/apps/client/src/ui/dialogs/packagesdialog.cpp b/doomsday/apps/client/src/ui/dialogs/packagesdialog.cpp index be89db70fa..a7f04e5e89 100644 --- a/doomsday/apps/client/src/ui/dialogs/packagesdialog.cpp +++ b/doomsday/apps/client/src/ui/dialogs/packagesdialog.cpp @@ -150,7 +150,7 @@ DENG_GUI_PIMPL(PackagesDialog) PopupWidget *makeInfoPopup() const { - return new PackagePopupWidget(_item->packageFile()); + return new PackageInfoDialog(_item->packageFile()); } private: @@ -227,7 +227,7 @@ DENG_GUI_PIMPL(PackagesDialog) menu->items() << new ui::SubwidgetItem(tr("Info"), ui::Up, [this] () -> PopupWidget * { - return new PackagePopupWidget(browser->actionPackage()); + return new PackageInfoDialog(browser->actionPackage()); }) << new ui::ActionItem(style().images().image("gear"), tr("Select Packages"), new CallbackAction([this] () { @@ -237,7 +237,7 @@ DENG_GUI_PIMPL(PackagesDialog) } else*/ { - return new PackagePopupWidget(id); + return new PackageInfoDialog(id); } }); diff --git a/doomsday/apps/client/src/ui/home/gamecolumnwidget.cpp b/doomsday/apps/client/src/ui/home/gamecolumnwidget.cpp index 1993ec5fc5..aa0347543b 100644 --- a/doomsday/apps/client/src/ui/home/gamecolumnwidget.cpp +++ b/doomsday/apps/client/src/ui/home/gamecolumnwidget.cpp @@ -65,7 +65,7 @@ DENG_GUI_PIMPL(GameColumnWidget) Game const &game() const { DENG2_ASSERT(profile != nullptr); - return DoomsdayApp::games()[profile->game()]; + return profile->game(); } //String gameId() const { return data().toString(); } @@ -186,9 +186,9 @@ DENG_GUI_PIMPL(GameColumnWidget) bool addItemForProfile(GameProfile &profile) { auto const &games = DoomsdayApp::games(); - if (games.contains(profile.game())) + if (games.contains(profile.gameId())) { - if (games[profile.game()].family() == gameFamily) + if (profile.game().family() == gameFamily) { menu->items() << new ProfileItem(this, profile); addOrRemoveSubheading(); @@ -296,7 +296,7 @@ DENG_GUI_PIMPL(GameColumnWidget) if (!prof1.isPlayable() && prof2.isPlayable()) return false; // Finally, based on identifier. - return prof1.game().compareWithoutCase(prof2.game()) < 0; + return prof1.gameId().compareWithoutCase(prof2.gameId()) < 0; } return year < 0; }); diff --git a/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp b/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp index ee9ac333e3..1297f0fbf2 100644 --- a/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp +++ b/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp @@ -41,6 +41,7 @@ using namespace de; DENG_GUI_PIMPL(GamePanelButtonWidget) +, DENG2_OBSERVES(Profiles::AbstractProfile, Change) { GameProfile &gameProfile; ui::FilteredDataT savedItems; @@ -62,7 +63,7 @@ DENG_GUI_PIMPL(GamePanelButtonWidget) { // Only saved sessions for this game are to be included. auto const &item = it.as(); - if (item.gameId() != gameProfile.game()) + if (item.gameId() != gameProfile.gameId()) { return false; } @@ -169,7 +170,7 @@ DENG_GUI_PIMPL(GamePanelButtonWidget) Game const &game() const { - return DoomsdayApp::games()[gameProfile.game()]; + return gameProfile.game(); } void updatePackagesIndicator() @@ -239,6 +240,11 @@ DENG_GUI_PIMPL(GamePanelButtonWidget) { self().icon().setImage(self().makeGameLogo(game(), catalog)); } + + void profileChanged(Profiles::AbstractProfile &) + { + self().updateContent(); + } }; GamePanelButtonWidget::GamePanelButtonWidget(GameProfile &game, SaveListData const &savedItems) @@ -247,6 +253,8 @@ GamePanelButtonWidget::GamePanelButtonWidget(GameProfile &game, SaveListData con connect(d->saves, SIGNAL(selectionChanged(de::ui::DataPos)), this, SLOT(saveSelected(de::ui::DataPos))); connect(d->saves, SIGNAL(doubleClicked(de::ui::DataPos)), this, SLOT(saveDoubleClicked(de::ui::DataPos))); connect(this, SIGNAL(doubleClicked()), this, SLOT(play())); + + game.audienceForChange() += d; } void GamePanelButtonWidget::setSelected(bool selected) diff --git a/doomsday/apps/client/src/ui/home/packagescolumnwidget.cpp b/doomsday/apps/client/src/ui/home/packagescolumnwidget.cpp index 3978a20d6c..1236d81240 100644 --- a/doomsday/apps/client/src/ui/home/packagescolumnwidget.cpp +++ b/doomsday/apps/client/src/ui/home/packagescolumnwidget.cpp @@ -66,13 +66,13 @@ DENG_GUI_PIMPL(PackagesColumnWidget) { actions << new ui::SubwidgetItem(tr("..."), ui::Left, [this] () -> PopupWidget * { - return new PackagePopupWidget(packages->actionPackage()); + return new PackageInfoDialog(packages->actionPackage()); /*auto *popMenu = new PopupMenuWidget; popMenu->setColorTheme(Inverted); popMenu->items() << new ui::SubwidgetItem(tr("Info"), ui::Down, [this, packageId] () -> PopupWidget * { - return new PackagePopupWidget(packageId); + return new PackageInfoDialog(packageId); }); if (Package::hasOptionalContent(packageId)) diff --git a/doomsday/apps/client/src/ui/widgets/packagepopupwidget.cpp b/doomsday/apps/client/src/ui/widgets/packageinfodialog.cpp similarity index 69% rename from doomsday/apps/client/src/ui/widgets/packagepopupwidget.cpp rename to doomsday/apps/client/src/ui/widgets/packageinfodialog.cpp index 2de5809da5..f6f7f13eea 100644 --- a/doomsday/apps/client/src/ui/widgets/packagepopupwidget.cpp +++ b/doomsday/apps/client/src/ui/widgets/packageinfodialog.cpp @@ -20,19 +20,28 @@ #include "ui/widgets/packagecontentoptionswidget.h" #include +#include +#include #include +#include +#include +#include +#include #include #include #include -#include +#include #include +#include #include #include +#include + using namespace de; -DENG_GUI_PIMPL(PackagePopupWidget) +DENG_GUI_PIMPL(PackageInfoDialog) { LabelWidget *title; LabelWidget *path; @@ -41,7 +50,11 @@ DENG_GUI_PIMPL(PackagePopupWidget) LabelWidget *metaInfo; IndirectRule *targetHeight; String packageId; + NativePath nativePath; SafeWidgetPtr configurePopup; + SafeWidgetPtr profileMenu; + enum MenuMode { AddToProfile, PlayInProfile }; + MenuMode menuMode; Impl(Public *i) : Base(i) { @@ -142,7 +155,7 @@ DENG_GUI_PIMPL(PackagePopupWidget) Image iconImage = img->image(); if (iconImage.width() > 512 || iconImage.height() > 512) { - throw Error("PackagePopupWidget::useIconFile", + throw Error("PackageInfoDialog::useIconFile", "Icon file " + img->description() + " is too large (max 512x512)"); } icon->setImage(iconImage); @@ -163,20 +176,30 @@ DENG_GUI_PIMPL(PackagePopupWidget) bool setup(File const *file) { + if (!file) return false; // Not a package? + + // Look up the package metadata. Record const &names = file->objectNamespace(); - if (!file || !names.has(Package::VAR_PACKAGE)) + if (!names.has(Package::VAR_PACKAGE)) { return false; } Record const &meta = names.subrecord(Package::VAR_PACKAGE); packageId = meta.gets(Package::VAR_ID); + nativePath = file->correspondingNativePath(); + String fileDesc = file->source()->description(); String format; if (DataBundle const *bundle = file->target().maybeAs()) { format = bundle->formatAsText().upperFirstChar(); useDefaultIcon(); + + if (bundle->format() == DataBundle::Collection) + { + fileDesc = file->target().description(); + } } else { @@ -184,10 +207,16 @@ DENG_GUI_PIMPL(PackagePopupWidget) useIconFile(file->path()); } + if (file->source()->is()) + { + // The file itself makes for a better description. + fileDesc = file->description(); + } + title->setText(meta.gets(Package::VAR_TITLE)); path->setText(String(_E(b) "%1" _E(.) "\n%2") .arg(format) - .arg(file->source()->description().upperFirstChar())); + .arg(fileDesc.upperFirstChar())); String metaMsg = String(_E(Ta)_E(l) "Version: " _E(.)_E(Tb) "%1\n" _E(Ta)_E(l) "Tags: " _E(.)_E(Tb) "%2\n" @@ -247,18 +276,22 @@ DENG_GUI_PIMPL(PackagePopupWidget) // - uninstall (user packages) self().buttons() - << new DialogButtonItem(Action, + << new DialogButtonItem(Action | Id2, style().images().image("play"), tr("Play in..."), new SignalAction(thisPublic, SLOT(playInGame()))) - << new DialogButtonItem(Action, + << new DialogButtonItem(Action | Id3, style().images().image("create"), tr("Add to..."), - new SignalAction(thisPublic, SLOT(addToProfile()))) - << new DialogButtonItem(Action, - tr("Show File"), - new SignalAction(thisPublic, SLOT(uninstall()))); + new SignalAction(thisPublic, SLOT(addToProfile()))); + if (!nativePath.isEmpty()) + { + self().buttons() + << new DialogButtonItem(Action, + tr("Show File"), + new SignalAction(thisPublic, SLOT(showFile()))); + } if (Package::hasOptionalContent(*file)) { self().buttons() @@ -269,9 +302,68 @@ DENG_GUI_PIMPL(PackagePopupWidget) } return true; } + + static String visibleFamily(String const &family) + { + if (family.isEmpty()) return tr("Other"); + return family.upperFirstChar(); + } + + void openProfileMenu(RuleRectangle const &anchor, bool playableOnly) + { + if (profileMenu) return; + + profileMenu.reset(new PopupMenuWidget); + profileMenu->setDeleteAfterDismissed(true); + profileMenu->setAnchorAndOpeningDirection(anchor, ui::Left); + + QList profs = DoomsdayApp::gameProfiles().profilesSortedByFamily(); + + String lastFamily; + for (GameProfile *prof : profs) + { + if (playableOnly && !prof->isPlayable()) continue; + + if (lastFamily != prof->game().family()) + { + if (!profileMenu->items().isEmpty()) + { + profileMenu->items() << new ui::Item(ui::Item::Separator); + } + profileMenu->items() + << new ui::Item(ui::Item::ShownAsLabel | ui::Item::Separator, + visibleFamily(prof->game().family())); + lastFamily = prof->game().family(); + } + + profileMenu->items() + << new ui::ActionItem(prof->name(), new CallbackAction([this, prof] () + { + profileSelectedFromMenu(*prof); + })); + } + + self().add(profileMenu); + profileMenu->open(); + } + + void profileSelectedFromMenu(GameProfile &profile) + { + switch (menuMode) + { + case AddToProfile: + StringList pkgs = profile.packages(); + if (!pkgs.contains(packageId)) + { + pkgs << packageId; + profile.setPackages(pkgs); + } + break; + } + } }; -PackagePopupWidget::PackagePopupWidget(String const &packageId) +PackageInfoDialog::PackageInfoDialog(String const &packageId) : DialogWidget("packagepopup") , d(new Impl(this)) { @@ -281,7 +373,7 @@ PackagePopupWidget::PackagePopupWidget(String const &packageId) } } -PackagePopupWidget::PackagePopupWidget(File const *packageFile) +PackageInfoDialog::PackageInfoDialog(File const *packageFile) : DialogWidget("packagepopup") , d(new Impl(this)) { @@ -291,53 +383,40 @@ PackagePopupWidget::PackagePopupWidget(File const *packageFile) } } -void PackagePopupWidget::playInGame() +void PackageInfoDialog::playInGame() { - + d->menuMode = Impl::PlayInProfile; + d->openProfileMenu(buttonWidget(Id2)->rule(), true); } -void PackagePopupWidget::addToProfile() +void PackageInfoDialog::addToProfile() { - + d->menuMode = Impl::AddToProfile; + d->openProfileMenu(buttonWidget(Id3)->rule(), false); } -void PackagePopupWidget::configure() +void PackageInfoDialog::configure() { - /*_optionsPopup.reset(new PopupWidget); - _optionsPopup->setDeleteAfterDismissed(true); - _optionsPopup->setAnchorAndOpeningDirection(rule(), ui::Left); - _optionsPopup->closeButton().setActionFn([this] () - { - root().setFocus(this); - _optionsPopup->close(); - }); + if (d->configurePopup) return; // Let it close itself. - auto *opts = new PackageContentOptionsWidget(packageId(), root().viewHeight()); - opts->rule().setInput(Rule::Width, rule().width()); - _optionsPopup->setContent(opts);*/ - - if (d->configurePopup) return; - - PopupWidget *pop = PackageContentOptionsWidget::makePopup - (d->packageId, rule("dialog.packages.width"), root().viewHeight()); + PopupWidget *pop = PackageContentOptionsWidget::makePopup( + d->packageId, rule("dialog.packages.width"), root().viewHeight()); d->configurePopup.reset(pop); pop->setAnchorAndOpeningDirection(buttonWidget(Id1)->rule(), ui::Left); pop->closeButton().setActionFn([pop] () { - //root().setFocus(this) pop->close(); }); - /*_optionsPopup->closeButton().setActionFn([this] () - { - root().setFocus(this); - _optionsPopup->close(); - });*/ add(pop); pop->open(); } -void PackagePopupWidget::uninstall() +void PackageInfoDialog::showFile() { - + if (!d->nativePath.isEmpty()) + { + QDesktopServices::openUrl(QUrl::fromLocalFile( + d->nativePath.isDirectory()? d->nativePath : d->nativePath.fileNamePath())); + } } diff --git a/doomsday/apps/client/src/ui/widgets/packagesbuttonwidget.cpp b/doomsday/apps/client/src/ui/widgets/packagesbuttonwidget.cpp index 24dbabad68..11c5195161 100644 --- a/doomsday/apps/client/src/ui/widgets/packagesbuttonwidget.cpp +++ b/doomsday/apps/client/src/ui/widgets/packagesbuttonwidget.cpp @@ -61,7 +61,7 @@ DENG_GUI_PIMPL(PackagesButtonWidget) auto *dlg = new PackagesDialog(dialogTitle); if (profile) { - dlg->setGame(profile->game()); + dlg->setGame(profile->gameId()); } dlg->setDeleteAfterDismissed(true); dlg->setSelectedPackages(packages); diff --git a/doomsday/apps/client/src/ui/widgets/packagessidebarwidget.cpp b/doomsday/apps/client/src/ui/widgets/packagessidebarwidget.cpp index ae7412b5c6..790fac6131 100644 --- a/doomsday/apps/client/src/ui/widgets/packagessidebarwidget.cpp +++ b/doomsday/apps/client/src/ui/widgets/packagessidebarwidget.cpp @@ -41,7 +41,7 @@ DENG_GUI_PIMPL(PackagesSidebarWidget) // Action for showing information about the package. browser->actionItems().insert(0, new ui::ActionItem(tr("..."), new CallbackAction([this] () { - auto *pop = new PackagePopupWidget(browser->actionPackage()); + auto *pop = new PackageInfoDialog(browser->actionPackage()); root().addOnTop(pop); pop->setDeleteAfterDismissed(true); pop->setAnchorAndOpeningDirection(browser->actionWidget()->as() diff --git a/doomsday/apps/client/src/ui/widgets/packageswidget.cpp b/doomsday/apps/client/src/ui/widgets/packageswidget.cpp index e9989b3ff8..818e47a174 100644 --- a/doomsday/apps/client/src/ui/widgets/packageswidget.cpp +++ b/doomsday/apps/client/src/ui/widgets/packageswidget.cpp @@ -316,7 +316,7 @@ DENG_GUI_PIMPL(PackagesWidget) PopupWidget *makeInfoPopup() const { - return new PackagePopupWidget(_item->file); + return new PackageInfoDialog(_item->file); } float estimatedHeight() const override