Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign upGUI: Implement an "Unknown game dialog" #1160
Conversation
|
Because buttons are only added when the underlying feature is supported this means that depending on the platform and libraries used the "Report game" button might show while the "Copy to clipboard" button in the middle might be missing which is a bit ugly. One solution would be to always add the buttons and have them greyed-out when not available, but that's not much better (or maybe worse) from a user's PoV. Best would be if the coordinates of said buttons would be dynamic to avoid the potential gap. |
| @@ -27,6 +27,7 @@ | |||
|
|
|||
| bool hasTextInClipboardMacOSX(); | |||
| Common::String getTextFromClipboardMacOSX(); | |||
| bool setTextInClipboardMacOSX(const Common::String&); | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
criezy
Apr 16, 2018
Member
We indeed seem to do it, at least in the common code. In the backend code this is probably less important. And as you can see since I wrote this part of the code, personally I tend not to name parameters when their meaning is obvious, and unless needed (for example to document them), but this might be a bad habit. I can fix that code when merging this PR.
| @@ -72,6 +72,7 @@ class OSystem_SDL : public ModularBackend { | |||
| // Clipboard | |||
| virtual bool hasTextInClipboard(); | |||
| virtual Common::String getTextFromClipboard(); | |||
| virtual bool setTextInClipboard(const Common::String&); | |||
This comment has been minimized.
This comment has been minimized.
| "its version, language, etc.:", | ||
| path.getPath().c_str(), getName(), "https://bugs.scummvm.org/"); | ||
|
|
||
| Common::String reportTranslated = Common::String::format( |
This comment has been minimized.
This comment has been minimized.
bonki
Apr 16, 2018
Member
You can avoid duplication by having the format string in a separate variable.
This comment has been minimized.
This comment has been minimized.
lotharsm
Apr 16, 2018
Author
Member
I don't know if this could be avoided since the message contains strings that could not be translated if I simply tried to do something like reportTranslated = _(report)...
This comment has been minimized.
This comment has been minimized.
bonki
Apr 16, 2018
•
Member
I am talking about the format string only, something along the lines of:
const char *reportFormat = "The game in '%s' ...";
Common::String report = Common::String::format( reportFormat, ...);
Common::String reportTranslated = Common::String::format(_(reportFormat), ...);
This comment has been minimized.
This comment has been minimized.
| } | ||
| case kOpenBugtrackerURL: | ||
| { | ||
| g_system->openUrl("https://bugs.scummvm.org/newticket?summary=[UNK] Unknown game for engine " + _bugtrackerAffectedEngine + ": &description=" + _bugtrackerGameData + "&component=Engine%3A+"+_bugtrackerAffectedEngine+"&type=enhancement"+"&keywords=unknown-game "+_bugtrackerAffectedEngine); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
|
||
| path.getPath().c_str(), getName(), "https://bugs.scummvm.org/"); | ||
|
|
||
| Common::String bugtrackerAffectedEngine = Common::String::format(getName()); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@bonki You are right about the button positions, this was an oversight be me. What do you think about the following?
|
|
|
||
| //Check if we have clipboard functionality | ||
| if (g_system->hasFeature(OSystem::kFeatureClipboardSupport)) { | ||
| new ButtonWidget(this, copyToClipboardButtonPos, _h - buttonHeight - 8, copyToClipboardButtonWidth, buttonHeight, _("Copy to clipboard"), 0, kCopyToClipboard); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@RootFather Not particularly pretty but it gets the job done as long as no new buttons are added to the dialog :) |
|
|
||
| // Show the UnknownGameDialog and print the translated unknown game information | ||
| GUI::UnknownGameDialog dialog(report, reportTranslated, bugtrackerAffectedEngine); | ||
| dialog.runModal(); |
This comment has been minimized.
This comment has been minimized.
bgK
Apr 16, 2018
Member
I wonder what happens when invoking the detector from the command line using say --detect.
This comment has been minimized.
This comment has been minimized.
bonki
Apr 16, 2018
Member
Hm, apparently it quits without printing anything. Unless I'm doing something wrong that's not quite ideal.
This comment has been minimized.
This comment has been minimized.
f083d6d
to
b711c1e
| @@ -27,6 +27,7 @@ | |||
|
|
|||
| bool hasTextInClipboardMacOSX(); | |||
| Common::String getTextFromClipboardMacOSX(); | |||
| bool setTextInClipboardMacOSX(const Common::String&); | |||
This comment has been minimized.
This comment has been minimized.
criezy
Apr 16, 2018
Member
We indeed seem to do it, at least in the common code. In the backend code this is probably less important. And as you can see since I wrote this part of the code, personally I tend not to name parameters when their meaning is obvious, and unless needed (for example to document them), but this might be a bad habit. I can fix that code when merging this PR.
| "team at %s along with the name of the game you tried to add and " | ||
| "its version, language, etc.:"), | ||
| path.getPath().c_str(), getName(), "https://bugs.scummvm.org/"); | ||
| const char *reportCommon = "The game in '%s' seems to be an unknown %s engine game " \ |
This comment has been minimized.
This comment has been minimized.
criezy
Apr 16, 2018
Member
Why the backslashes at the end of this line (and the next two lines)? They are not necessary.
| g_system->logMessage(LogMessageType::kInfo, report.c_str()); | ||
| // Write the original message about the unknown game to the log file | ||
| Common::String reportLog = report; | ||
| reportLog.wordWrap(80); |
This comment has been minimized.
This comment has been minimized.
criezy
Apr 16, 2018
Member
I understand why you moved the wordWrap(80) here as you don't want to wrap the text that goes to the clipboard or the bug tracker. But this ends up wrapping the unknown game file descriptions {name, MD5, size} while they were not wrapped before, and I don't think they should be wrapped.
Also we probably don't want to add the \n to the report and reportTranslated here, and only add it to the reportLog.
So I would suggest something like this:
reportTranslated.wordWrap(65);
Common::String reportLog = report;
reportLog.wordWrap(80);
Common::String unknownFiles;
for (ADFilePropertiesMap::const_iterator file = filesProps.begin(); file != filesProps.end(); ++file)
unknownFiles += Common::String::format(" {\"%s\", 0, \"%s\", %d},\n", file->_key.c_str(), file->_value.md5.c_str(), file->_value.size);
report += unknownFiles;
reportTranslated += unknownFiles;
reportLog += unknownFiles + "\n";
| @@ -1,6 +1,7 @@ | |||
| MODULE := engines | |||
|
|
|||
| MODULE_OBJS := \ | |||
| unknown-game-dialog.o \ | |||
This comment has been minimized.
This comment has been minimized.
criezy
Apr 16, 2018
Member
I think we tend to list files alphabetically in those module.mk, so it was not inserted in the correct place.
|
|
||
| //Check if we have clipboard functionality and expand the reportTranslated message if needed... | ||
| if (g_system->hasFeature(OSystem::kFeatureClipboardSupport)) { | ||
| _reportTranslated += _("Use the button below to copy the required game information into your clipboard. \n"); |
This comment has been minimized.
This comment has been minimized.
criezy
Apr 16, 2018
Member
If the \n was not added at the end of the reportTranslated (see my related comment for advancedDetector.cpp), then here it should be added at the start and not the end. And also I would not include it in the translated string:
if (g_system->hasFeature(OSystem::kFeatureClipboardSupport)) {
_reportTranslated += "\n";
_reportTranslated += _("Use the button below to copy the required game information into your clipboard.");
}
And do the same for the Open URL text below.
That way the end of lines would actually be consistent in all cases, whatever feature is available. As it is currently if the kFeatureOpenUrl is not available the _reportTranslated ends with a new line, which means you probably have an empty line at the end of the list of lines after the call to wordWrapText.
| Common::Array<Common::String> lines; | ||
| int maxlineWidth = g_gui.getFont().wordWrapText(_reportTranslated, screenW - 2 * 20, lines); | ||
|
|
||
| _w = MAX(MAX(maxlineWidth, 0), (2 * buttonWidth) + 10) + 20; |
This comment has been minimized.
This comment has been minimized.
criezy
Apr 16, 2018
Member
You probably want to use 3 * buttonWidth since you can have up to three buttons here. Or you could adapt it depending on the available features to not force having space for a button that will not be created.
Also you probably want to move the button widths computation above so that you can use it here since it can be wider than buttonWidth. This would end up looking like this:
int closeButtonWidth = MAX(buttonWidth, g_gui.getFont().getStringWidth(_("Close")) + 10);
int copyToClipboardButtonWidth = MAX(buttonWidth, g_gui.getFont().getStringWidth(_("Copy to clipboard")) + 10);
int openBugtrackerURLButtonWidth = MAX(buttonWidth, g_gui.getFont().getStringWidth(_("Report game")) + 10);
int totalButtonWidth = closeButtonWidth;
if (g_system->hasFeature(OSystem::kFeatureClipboardSupport))
totalButtonWidth += 10 + copyToClipboardButtonWidth;
if (g_system->hasFeature(OSystem::kFeatureOpenUrl))
totalButtonWidth += 10 + openBugtrackerURLButtonWidth;
_w = MAX(MAX(maxlineWidth, 0), totalButtonWidth) + 20;
| #include "engines/unknown-game-dialog.h" | ||
| #include "backends/platform/sdl/sdl.h" | ||
|
|
||
| namespace GUI { |
This comment has been minimized.
This comment has been minimized.
criezy
Apr 16, 2018
Member
I know you initially added the file to gui/, and it made sense to use the GUI namespace then, but since now the file is in engines/ I don't think you should use the GUI namespace. Looking at the other dialogs in engines/dialogs.h they are in the global namespace.
So I would suggest to do the same and remove this line, then remove the closing bracket at the end of this file, and do the same in unknown-game-dialog.h.
| //Formatting the reportData for bugtracker submission [replace line breaks]... | ||
| _bugtrackerGameData = _reportData; | ||
| while (_bugtrackerGameData.contains("\n")) { | ||
| replace(_bugtrackerGameData, "\n", "%0A"); |
This comment has been minimized.
This comment has been minimized.
criezy
Apr 16, 2018
Member
I am confused as I would not expect this to compile (unless there are using namespace Common; directives scattered around). Shouldn't this line be:
Common::replace(_bugtrackerGameData, "\n", "%0A");
This comment has been minimized.
This comment has been minimized.
|
Since I created the ticket on our bug tracker, you can guess that I think this is a good idea :-P In addition to the comments I made in the review, I am also wondering if we should use a ScrollContainerWidget in the dialog in case the report message is too big to be displayed on the screen. |
|
There are a few problems with the feature:
|
|
I would very much like this feature to happen as not properly informing the user when unknown game versions are encountered is a great usability problem. However I have some concerns which are probably out of the scope of this PR:
That sound like a good idea. |
| @@ -502,17 +503,42 @@ Common::String OSystem_SDL::getTextFromClipboard() { | |||
|
|
|||
| #if SDL_VERSION_ATLEAST(2, 0, 0) | |||
| char *text = SDL_GetClipboardText(); | |||
| #ifdef USE_TRANSLATION | |||
This comment has been minimized.
This comment has been minimized.
bgK
Apr 17, 2018
Member
It is my understanding we use ISO-8859-1 when USE_TRANSLATION is not defined. It's probably better to convert to that rather than using raw UTF-8 data which is bound to fail to render properly.
(the same applies to setTextInClipboard)
This comment has been minimized.
This comment has been minimized.
criezy
Apr 17, 2018
Member
Right. And that would be a very simple change to do (here, and in the macOS backend).
| } | ||
| case kOpenBugtrackerURL: | ||
| { | ||
| Common::String bugtrackerURL = Common::String::format(( |
This comment has been minimized.
This comment has been minimized.
bgK
Apr 17, 2018
Member
This code will make its way into ResidualVM which does not use a Trac bugtracker. I would appreciate if the code used to build the bugtracker URL was moved to a separate function so it can be overridden easily.
This comment has been minimized.
This comment has been minimized.
lotharsm
Apr 17, 2018
Author
Member
That can be done. Simply moving the URL building part into a separate function and then calling this function in case of kOpenBugtrackerURL?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
lotharsm
Apr 17, 2018
Author
Member
Does https://gist.github.com/rootfather/222ff1079263ea8073d39b5ac4659a8a matches with what you proposed?
This comment has been minimized.
This comment has been minimized.
|
|
||
| // Center the dialog | ||
| _x = (screenW - _w) / 2; | ||
| _y = (screenH - _h) / 2; |
This comment has been minimized.
This comment has been minimized.
bgK
Apr 17, 2018
Member
Centering the dialog needs to happen in the reflowLayout method so the dialog stays centered when resizing the window.
| int closeButtonPos = _w - closeButtonWidth - 10; | ||
| int copyToClipboardButtonPos = _w - copyToClipboardButtonWidth - closeButtonWidth - 15; | ||
| int openBugtrackerURLButtonPos = _w - copyToClipboardButtonWidth - closeButtonWidth - openBugtrackerURLButtonWidth - 20; | ||
| _closeButton = new ButtonWidget(this, closeButtonPos, _h - buttonHeight - 8, buttonWidth, buttonHeight, _("Close"), 0, kClose); |
This comment has been minimized.
This comment has been minimized.
bgK
Apr 17, 2018
Member
There's a bunch of manual GUI widget placement in this method. Did you consider describing the dialog in a layout file to automate this?
This comment has been minimized.
This comment has been minimized.
| case kCopyToClipboard: | ||
| { | ||
| g_system->setTextInClipboard(_reportData); | ||
| MessageDialog dialog(_("All necessary information about your game has been copied into the clipboard"), _("OK")); |
This comment has been minimized.
This comment has been minimized.
bgK
Apr 17, 2018
Member
Maybe use an OSD message so the user does not have to click to confirm the operation was successful.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I see your point, so we should keep the requirement for the user to be logged in into the Trac instance. This ensures that we have a proper way to contact the submitter. This also means that just the /login redirect needs to be fixed.
Yep, that's true - maintaining this feature requires to change the URL building method if required.
I don't know why, but at the moment, the dialog is only shown when adding the games, not when starting them. After starting a game from the launcher, the game information is shown in the log window, but the dialog is not invoked...
That's true, this should be refined.
This means that some of the changes made to AdvancedDetector have to be applied to the engines that rely on own detectors instead of AdvancedDetector?
This might actually be out of scope for me know... |
Thanks to the great help of @criezy, here's my implementation of an GUI dialog that appears when an unknown game is detected. Features: - Allows copying the data collected by game detector to the clipboard - Allows opening the bug tracker and pre-filling the form fiels This closes https://bugs.scummvm.org/ticket/10435.
…a) confirmation
| case kCopyToClipboard: | ||
| { | ||
| g_system->setTextInClipboard(_reportData); | ||
| g_system->displayMessageOnOSD(_("All necessary information about your game has been copied into the clipboard")); |
This comment has been minimized.
This comment has been minimized.
bgK
Apr 17, 2018
•
Member
setTextInClipboard returns a boolean to indicate success or failure. It would be better to only show the success message if putting the text in the clipboard actually worked (And probably add a failure message).
|
I recommend to limit this functionality only when 'Add game' button is pressed. Not even Mass Add. Then it will make sense even creating tickets. For creating tickets, however, I would add another field: username, or e-mail address, or both, so we could trace the report back. |
|
@Kirben @sev- There are some good points there.
|
|
@criezy: regarding 3: I know nothing about websites neither, but this seems like a good approach. Then, we won't render the current version of ScummVM unable to send bugreports and only would have to change the website redirect. |
… was *not* launched by the Mass Add feature
|
There are various indentation issues that crept in with your last commit and need to be fixed. Otherwise the changes in that commit look good. |
| @@ -524,7 +524,7 @@ GameList EngineManager::detectGames(const Common::FSList &fslist) const { | |||
| // Iterate over all known games and for each check if it might be | |||
| // the game in the presented directory. | |||
| for (iter = plugins.begin(); iter != plugins.end(); ++iter) { | |||
| candidates.push_back((*iter)->get<MetaEngine>().detectGames(fslist)); | |||
| candidates.push_back((*iter)->get<MetaEngine>().detectGames(fslist, useUnknownGameDialog)); | |||
This comment has been minimized.
This comment has been minimized.
criezy
Apr 25, 2018
Member
It looks like you mistakenly replaced tab indentation with spaces on this line.
| @@ -373,7 +373,7 @@ void AdvancedMetaEngine::reportUnknown(const Common::FSNode &path, const ADFileP | |||
| g_system->logMessage(LogMessageType::kInfo, reportLog.c_str()); | |||
|
|
|||
| // Check if the GUI is running, show the UnknownGameDialog and print the translated unknown game information | |||
| if (GUI::GuiManager::hasInstance() && g_gui.isActive()) { | |||
| if (GUI::GuiManager::hasInstance() && g_gui.isActive() && useUnknownGameDialog == true) { | |||
| UnknownGameDialog dialog(report, reportTranslated, bugtrackerAffectedEngine); | |||
This comment has been minimized.
This comment has been minimized.
criezy
Apr 25, 2018
Member
Here as well the indentation seems to have been changed to spaces.
Also the test on GUI::GuiManager::hasInstance() && g_gui.isActive() is no longer necessary since I don't see a case where they would fail while useUnknownGameDialog is true. But I don't see any issue with keeping those to be extra-safe.
Note that the == true is not necessary here either, but since our code formatting conventions don't say anything about this, I guess this is fine.
| @@ -574,7 +574,7 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons | |||
| // We didn't find a match | |||
| if (matched.empty()) { | |||
| if (!filesProps.empty() && gotAnyMatchesWithAllFiles) { | |||
| reportUnknown(parent, filesProps, matchedGameIds); | |||
| reportUnknown(parent, filesProps, matchedGameIds, useUnknownGameDialog); | |||
This comment has been minimized.
This comment has been minimized.
| @@ -278,7 +278,7 @@ class AdvancedMetaEngine : public MetaEngine { | |||
|
|
|||
| virtual GameDescriptor findGame(const char *gameId) const; | |||
|
|
|||
| virtual GameList detectGames(const Common::FSList &fslist) const; | |||
| virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; | |||
This comment has been minimized.
This comment has been minimized.
| @@ -313,7 +313,7 @@ class AdvancedMetaEngine : public MetaEngine { | |||
| * @param extra restrict results to specified extra string (only if kADFlagUseExtraAsHint is set) | |||
| * @return list of ADGameDescription pointers corresponding to matched games | |||
| */ | |||
| virtual ADGameDescList detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const; | |||
| virtual ADGameDescList detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog = false) const; | |||
This comment has been minimized.
This comment has been minimized.
| @@ -961,7 +961,7 @@ class ScummMetaEngine : public MetaEngine { | |||
| virtual bool hasFeature(MetaEngineFeature f) const; | |||
| virtual GameList getSupportedGames() const; | |||
| virtual GameDescriptor findGame(const char *gameid) const; | |||
| virtual GameList detectGames(const Common::FSList &fslist) const; | |||
| virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; | |||
This comment has been minimized.
This comment has been minimized.
| @@ -79,7 +79,7 @@ class SkyMetaEngine : public MetaEngine { | |||
| virtual GameList getSupportedGames() const; | |||
| virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; | |||
| virtual GameDescriptor findGame(const char *gameid) const; | |||
| virtual GameList detectGames(const Common::FSList &fslist) const; | |||
| virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; | |||
This comment has been minimized.
This comment has been minimized.
| @@ -89,7 +89,7 @@ class SwordMetaEngine : public MetaEngine { | |||
| virtual bool hasFeature(MetaEngineFeature f) const; | |||
| virtual GameList getSupportedGames() const; | |||
| virtual GameDescriptor findGame(const char *gameid) const; | |||
| virtual GameList detectGames(const Common::FSList &fslist) const; | |||
| virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; | |||
This comment has been minimized.
This comment has been minimized.
| @@ -95,7 +95,7 @@ class Sword2MetaEngine : public MetaEngine { | |||
| virtual GameList getSupportedGames() const; | |||
| virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; | |||
| virtual GameDescriptor findGame(const char *gameid) const; | |||
| virtual GameList detectGames(const Common::FSList &fslist) const; | |||
| virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; | |||
This comment has been minimized.
This comment has been minimized.
| @@ -573,7 +573,7 @@ bool LauncherDialog::doGameDetection(const Common::String &path) { | |||
|
|
|||
| // ...so let's determine a list of candidates, games that | |||
| // could be contained in the specified directory. | |||
| GameList candidates(EngineMan.detectGames(files)); | |||
| GameList candidates(EngineMan.detectGames(files, true)); | |||
This comment has been minimized.
This comment has been minimized.
|
I will try to summarise ideas and issues that were discussed above. The short version of it is that it seems to me that once the incorrect indentation have been fixed, and maybe the creation of the
I have cleaned the clipboard-related commits in my fork (you can see them at https://github.com/criezy/scummvm/commits/unknown-game-dialog). I am planning to use those to replace the ones in this pull request.
With the latest changes from @RootFather the dialog only appears when using the We could consider using the dialog for Mass Add as well if we can get it to only popup once at the end with all the unknown version found. But I think this is out of scope of this PR.
As previously noted, the bug tracker forces users to be logged in to create a ticket, so we do get contact details. I am wondering however if we should
Yes, I think it would be a good idea to return unknown version information to the caller and not just detected games, and let the caller handle this information as it sees fit instead of reporting those from the detection code. However I think this is out of scope of this PR.
I propose to work on that once this PR has been merged. |
I'm leaning a bit towards being against that since this list could potentially be very long and I'm not convinced that a ScrollContainerWidget would be a proper workaround from a UX angle in this case (that being said, using a ScrollContainerWidget sounds like a very good idea nonetheless).
+1 |
|
I have now merged manually those changes after cleaning some commits and squashing most of them. Thank you for your work on this. |
lotharsm commentedApr 16, 2018
•
edited
Thanks to the great help of @criezy, here's my implementation of an GUI dialog that appears when an unknown game is detected.
Features:
Feedback is greatly appreciated! As I'm still learning how to C++ and have little experience, don't hesitate to tell me if I did something awfully stupid. :)
@wjp: The option to automatically report the collected data unfortunately will fail ungracefully if the user is not logged in to ScummVM's Trac instance. There's also something wrong with https://bugs.scummvm.org/login which to my understanding would handle this case by a redirect.
This closes https://bugs.scummvm.org/ticket/10435.