2023-01-26 18:24:34 +02:00
/*
* firstlaunch_moc . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "firstlaunch_moc.h"
# include "ui_firstlaunch_moc.h"
2023-03-08 02:01:56 +02:00
# include "mainwindow_moc.h"
2023-03-11 00:57:55 +02:00
# include "modManager/cmodlistview_moc.h"
2023-03-08 02:01:56 +02:00
# include "../../lib/CConfigHandler.h"
2023-03-11 00:57:55 +02:00
# include "../../lib/CGeneralTextHandler.h"
# include "../../lib/Languages.h"
# include "../../lib/VCMIDirs.h"
# include "../../lib/filesystem/Filesystem.h"
# include "../languages.h"
2023-03-08 02:01:56 +02:00
2023-01-26 18:24:34 +02:00
FirstLaunchView : : FirstLaunchView ( QWidget * parent )
2023-03-11 00:57:55 +02:00
: QWidget ( parent )
, ui ( new Ui : : FirstLaunchView )
2023-01-26 18:24:34 +02:00
{
ui - > setupUi ( this ) ;
2023-03-08 02:01:56 +02:00
2023-03-11 00:57:55 +02:00
enterSetup ( ) ;
activateTabLanguage ( ) ;
2023-03-12 18:33:29 +02:00
ui - > lineEditDataSystem - > setText ( pathToQString ( boost : : filesystem : : absolute ( VCMIDirs : : get ( ) . dataPaths ( ) . front ( ) ) ) ) ;
ui - > lineEditDataUser - > setText ( pathToQString ( boost : : filesystem : : absolute ( VCMIDirs : : get ( ) . userDataPath ( ) ) ) ) ;
2023-03-11 00:57:55 +02:00
}
void FirstLaunchView : : on_buttonTabLanguage_clicked ( )
{
activateTabLanguage ( ) ;
}
void FirstLaunchView : : on_buttonTabHeroesData_clicked ( )
{
activateTabHeroesData ( ) ;
}
void FirstLaunchView : : on_buttonTabModPreset_clicked ( )
{
activateTabModPreset ( ) ;
}
void FirstLaunchView : : on_listWidgetLanguage_currentRowChanged ( int currentRow )
{
languageSelected ( ui - > listWidgetLanguage - > item ( currentRow ) - > data ( Qt : : UserRole ) . toString ( ) ) ;
}
void FirstLaunchView : : changeEvent ( QEvent * event )
{
if ( event - > type ( ) = = QEvent : : LanguageChange )
{
ui - > retranslateUi ( this ) ;
2023-03-12 23:33:38 +02:00
Languages : : fillLanguages ( ui - > listWidgetLanguage , false ) ;
2023-03-11 00:57:55 +02:00
}
QWidget : : changeEvent ( event ) ;
}
void FirstLaunchView : : on_pushButtonLanguageNext_clicked ( )
{
activateTabHeroesData ( ) ;
}
void FirstLaunchView : : on_pushButtonDataNext_clicked ( )
{
activateTabModPreset ( ) ;
}
void FirstLaunchView : : on_pushButtonDataBack_clicked ( )
{
activateTabLanguage ( ) ;
}
void FirstLaunchView : : on_pushButtonDataSearch_clicked ( )
{
heroesDataUpdate ( ) ;
}
void FirstLaunchView : : on_pushButtonDataCopy_clicked ( )
{
copyHeroesData ( ) ;
}
void FirstLaunchView : : on_pushButtonDataHelp_clicked ( )
{
2023-09-07 11:57:03 +02:00
static const QUrl vcmibuilderWiki ( " https://github.com/vcmi/vcmi/blob/master/docs/players/Installation_Linux.md#install-data-using-vcmibuilder-script " ) ;
2023-03-11 00:57:55 +02:00
QDesktopServices : : openUrl ( vcmibuilderWiki ) ;
}
void FirstLaunchView : : on_comboBoxLanguage_currentIndexChanged ( int index )
{
forceHeroesLanguage ( ui - > comboBoxLanguage - > itemData ( index ) . toString ( ) ) ;
}
void FirstLaunchView : : enterSetup ( )
{
2023-03-12 23:33:38 +02:00
Languages : : fillLanguages ( ui - > listWidgetLanguage , false ) ;
2023-01-26 18:24:34 +02:00
}
2023-03-11 00:57:55 +02:00
void FirstLaunchView : : setSetupProgress ( int progress )
2023-01-26 18:24:34 +02:00
{
2023-06-29 18:09:47 +02:00
ui - > buttonTabLanguage - > setDisabled ( progress < 1 ) ;
ui - > buttonTabHeroesData - > setDisabled ( progress < 2 ) ;
ui - > buttonTabModPreset - > setDisabled ( progress < 3 ) ;
2023-01-26 18:24:34 +02:00
}
2023-03-11 00:57:55 +02:00
void FirstLaunchView : : activateTabLanguage ( )
2023-01-26 18:24:34 +02:00
{
2023-03-11 00:57:55 +02:00
setSetupProgress ( 1 ) ;
2023-01-26 18:24:34 +02:00
ui - > installerTabs - > setCurrentIndex ( 0 ) ;
2023-03-11 00:57:55 +02:00
ui - > buttonTabLanguage - > setChecked ( true ) ;
ui - > buttonTabHeroesData - > setChecked ( false ) ;
ui - > buttonTabModPreset - > setChecked ( false ) ;
2023-01-26 18:24:34 +02:00
}
2023-03-11 00:57:55 +02:00
void FirstLaunchView : : activateTabHeroesData ( )
2023-01-26 18:24:34 +02:00
{
2023-03-11 00:57:55 +02:00
setSetupProgress ( 2 ) ;
2023-01-26 18:24:34 +02:00
ui - > installerTabs - > setCurrentIndex ( 1 ) ;
2023-03-11 00:57:55 +02:00
ui - > buttonTabLanguage - > setChecked ( false ) ;
ui - > buttonTabHeroesData - > setChecked ( true ) ;
ui - > buttonTabModPreset - > setChecked ( false ) ;
if ( ! hasVCMIBuilderScript )
{
ui - > pushButtonDataHelp - > hide ( ) ;
ui - > labelDataHelp - > hide ( ) ;
}
2024-01-05 23:03:22 +02:00
if ( heroesDataUpdate ( ) )
return ;
QString installPath = getHeroesInstallDir ( ) ;
if ( ! installPath . isEmpty ( ) )
2024-01-06 15:50:00 +02:00
{
2024-01-06 23:34:02 +02:00
auto reply = QMessageBox : : question ( this , tr ( " Heroes III installation found! " ) , tr ( " Copy data to VCMI folder? " ) , QMessageBox : : Yes | QMessageBox : : No ) ;
2024-01-06 15:50:00 +02:00
if ( reply = = QMessageBox : : Yes )
copyHeroesData ( installPath ) ;
}
2023-01-26 18:24:34 +02:00
}
2023-03-11 00:57:55 +02:00
void FirstLaunchView : : activateTabModPreset ( )
2023-01-26 18:24:34 +02:00
{
2023-03-11 00:57:55 +02:00
setSetupProgress ( 3 ) ;
2023-01-26 18:24:34 +02:00
ui - > installerTabs - > setCurrentIndex ( 2 ) ;
2023-03-11 00:57:55 +02:00
ui - > buttonTabLanguage - > setChecked ( false ) ;
ui - > buttonTabHeroesData - > setChecked ( false ) ;
ui - > buttonTabModPreset - > setChecked ( true ) ;
modPresetUpdate ( ) ;
2023-01-26 18:24:34 +02:00
}
2023-03-11 00:57:55 +02:00
void FirstLaunchView : : exitSetup ( )
{
2023-10-28 17:44:17 +02:00
if ( auto * mainWindow = dynamic_cast < MainWindow * > ( QApplication : : activeWindow ( ) ) )
2023-03-12 18:33:29 +02:00
mainWindow - > exitSetup ( ) ;
2023-03-11 00:57:55 +02:00
}
// Tab Language
void FirstLaunchView : : languageSelected ( const QString & selectedLanguage )
2023-03-08 02:01:56 +02:00
{
Settings node = settings . write [ " general " ] [ " language " ] ;
node - > String ( ) = selectedLanguage . toStdString ( ) ;
2023-10-28 17:44:17 +02:00
if ( auto * mainWindow = dynamic_cast < MainWindow * > ( QApplication : : activeWindow ( ) ) )
2023-03-08 02:01:56 +02:00
mainWindow - > updateTranslation ( ) ;
}
2024-01-05 23:03:22 +02:00
bool FirstLaunchView : : heroesDataUpdate ( )
2023-03-08 02:01:56 +02:00
{
2024-01-05 23:03:22 +02:00
bool detected = heroesDataDetect ( ) ;
if ( detected )
2023-03-11 00:57:55 +02:00
heroesDataDetected ( ) ;
else
heroesDataMissing ( ) ;
2024-01-05 23:03:22 +02:00
return detected ;
2023-03-11 00:57:55 +02:00
}
void FirstLaunchView : : heroesDataMissing ( )
{
QPalette newPalette = palette ( ) ;
newPalette . setColor ( QPalette : : Base , QColor ( 200 , 50 , 50 ) ) ;
ui - > lineEditDataSystem - > setPalette ( newPalette ) ;
ui - > lineEditDataUser - > setPalette ( newPalette ) ;
ui - > pushButtonDataSearch - > setVisible ( true ) ;
ui - > pushButtonDataCopy - > setVisible ( true ) ;
ui - > labelDataSearch - > setVisible ( true ) ;
ui - > labelDataCopy - > setVisible ( true ) ;
ui - > labelDataFound - > setVisible ( false ) ;
2023-04-19 22:10:34 +02:00
ui - > pushButtonDataNext - > setEnabled ( false ) ;
2023-03-11 00:57:55 +02:00
if ( hasVCMIBuilderScript )
2023-03-08 02:01:56 +02:00
{
2023-03-11 00:57:55 +02:00
ui - > pushButtonDataHelp - > setVisible ( true ) ;
ui - > labelDataHelp - > setVisible ( true ) ;
2023-03-08 02:01:56 +02:00
}
}
2023-03-11 00:57:55 +02:00
void FirstLaunchView : : heroesDataDetected ( )
{
QPalette newPalette = palette ( ) ;
newPalette . setColor ( QPalette : : Base , QColor ( 50 , 200 , 50 ) ) ;
ui - > lineEditDataSystem - > setPalette ( newPalette ) ;
ui - > lineEditDataUser - > setPalette ( newPalette ) ;
ui - > pushButtonDataSearch - > setVisible ( false ) ;
ui - > pushButtonDataCopy - > setVisible ( false ) ;
ui - > labelDataSearch - > setVisible ( false ) ;
ui - > labelDataCopy - > setVisible ( false ) ;
if ( hasVCMIBuilderScript )
{
ui - > pushButtonDataHelp - > setVisible ( false ) ;
ui - > labelDataHelp - > setVisible ( false ) ;
}
ui - > labelDataFound - > setVisible ( true ) ;
2023-04-15 19:04:00 +02:00
ui - > pushButtonDataNext - > setEnabled ( true ) ;
2023-03-11 00:57:55 +02:00
heroesLanguageUpdate ( ) ;
}
// Tab Heroes III Data
bool FirstLaunchView : : heroesDataDetect ( )
{
// user might have copied files to one of our data path.
// perform full reinitialization of virtual filesystem
CResourceHandler : : destroy ( ) ;
CResourceHandler : : initialize ( ) ;
CResourceHandler : : load ( " config/filesystem.json " ) ;
// use file from lod archive to check presence of H3 data. Very rough estimate, but will work in majority of cases
2023-08-23 14:07:50 +02:00
bool heroesDataFoundROE = CResourceHandler : : get ( ) - > existsResource ( ResourcePath ( " DATA/GENRLTXT.TXT " ) ) ;
bool heroesDataFoundSOD = CResourceHandler : : get ( ) - > existsResource ( ResourcePath ( " DATA/TENTCOLR.TXT " ) ) ;
2023-03-11 00:57:55 +02:00
2023-03-26 22:21:29 +02:00
return heroesDataFoundROE & & heroesDataFoundSOD ;
2023-03-11 00:57:55 +02:00
}
void FirstLaunchView : : heroesLanguageUpdate ( )
{
2023-03-12 23:33:38 +02:00
Languages : : fillLanguages ( ui - > comboBoxLanguage , true ) ;
2023-03-14 13:37:22 +02:00
QString language = Languages : : getHeroesDataLanguage ( ) ;
2023-03-11 00:57:55 +02:00
bool success = ! language . isEmpty ( ) ;
ui - > labelDataFailure - > setVisible ( ! success ) ;
ui - > labelDataSuccess - > setVisible ( success ) ;
}
void FirstLaunchView : : forceHeroesLanguage ( const QString & language )
{
Settings node = settings . write [ " general " ] [ " gameDataLanguage " ] ;
node - > String ( ) = language . toStdString ( ) ;
}
2024-01-05 23:03:22 +02:00
QString FirstLaunchView : : getHeroesInstallDir ( )
{
# ifdef VCMI_WINDOWS
QString gogPath = QSettings ( " HKEY_LOCAL_MACHINE \\ SOFTWARE \\ GOG.com \\ Games \\ 1207658787 " , QSettings : : NativeFormat ) . value ( " path " ) . toString ( ) ;
if ( ! gogPath . isEmpty ( ) )
return gogPath ;
2024-01-06 23:34:02 +02:00
2024-01-05 23:03:22 +02:00
QString cdPath = QSettings ( " HKEY_LOCAL_MACHINE \\ SOFTWARE \\ New World Computing \\ Heroes of Might and Magic® III \\ 1.0 " , QSettings : : NativeFormat ) . value ( " AppPath " ) . toString ( ) ;
if ( ! cdPath . isEmpty ( ) )
return cdPath ;
# endif
2024-01-06 15:50:00 +02:00
return QString { } ;
2024-01-05 23:03:22 +02:00
}
void FirstLaunchView : : copyHeroesData ( const QString & path )
2023-03-11 00:57:55 +02:00
{
2024-01-05 23:03:22 +02:00
QDir sourceRoot = QDir ( path ) ;
if ( path . isEmpty ( ) )
2024-01-06 23:34:02 +02:00
sourceRoot . setPath ( QFileDialog : : getExistingDirectory ( this , { } , { } , QFileDialog : : ShowDirsOnly | QFileDialog : : DontResolveSymlinks ) ) ;
2023-03-11 00:57:55 +02:00
2023-03-12 18:33:29 +02:00
if ( ! sourceRoot . exists ( ) )
2023-03-11 00:57:55 +02:00
return ;
2023-04-15 19:02:59 +02:00
if ( sourceRoot . dirName ( ) . compare ( " data " , Qt : : CaseInsensitive ) = = 0 )
{
// We got Data folder. Possibly user selected "Data" folder of Heroes III install. Check whether valid data might exist 1 level above
QStringList dirData = sourceRoot . entryList ( { " data " } , QDir : : Filter : : Dirs ) ;
if ( dirData . empty ( ) )
{
// This is "Data" folder without any "Data" folders inside. Try to check for data 1 level above
sourceRoot . cdUp ( ) ;
}
}
2023-03-12 18:33:29 +02:00
QStringList dirData = sourceRoot . entryList ( { " data " } , QDir : : Filter : : Dirs ) ;
QStringList dirMaps = sourceRoot . entryList ( { " maps " } , QDir : : Filter : : Dirs ) ;
QStringList dirMp3 = sourceRoot . entryList ( { " mp3 " } , QDir : : Filter : : Dirs ) ;
2023-03-11 00:57:55 +02:00
2023-03-26 22:21:29 +02:00
if ( dirData . empty ( ) )
{
QMessageBox : : critical ( this , " Heroes III data not found! " , " Failed to detect valid Heroes III data in chosen directory. \n Please select directory with installed Heroes III data. " ) ;
2023-03-11 00:57:55 +02:00
return ;
2023-03-26 22:21:29 +02:00
}
2023-03-11 00:57:55 +02:00
2023-03-12 18:33:29 +02:00
QDir sourceData = sourceRoot . filePath ( dirData . front ( ) ) ;
2023-03-26 22:21:29 +02:00
QStringList roeFiles = sourceData . entryList ( { " *.lod " } , QDir : : Filter : : Files ) ;
QStringList sodFiles = sourceData . entryList ( { " H3ab*.lod " } , QDir : : Filter : : Files ) ;
QStringList hdFiles = sourceData . entryList ( { " *.pak " } , QDir : : Filter : : Files ) ;
2023-03-11 00:57:55 +02:00
2023-03-26 22:21:29 +02:00
if ( sodFiles . empty ( ) )
{
if ( roeFiles . empty ( ) )
{
// Directory structure is correct (Data/Maps/Mp3) but no .lod archives that should be present in any install
QMessageBox : : critical ( this , " Heroes III data not found! " , " Failed to detect valid Heroes III data in chosen directory. \n Please select directory with installed Heroes III data. " ) ;
return ;
}
if ( ! hdFiles . empty ( ) )
{
// HD Edition contains only RoE data so we can't use even unmodified files from it
QMessageBox : : critical ( this , " Heroes III data not found! " , " Heroes III: HD Edition files are not supported by VCMI. \n Please select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death. " ) ;
return ;
}
// RoE or some other unsupported edition. Demo version?
QMessageBox : : critical ( this , " Heroes III data not found! " , " Unknown or unsupported Heroes III version found. \n Please select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death. " ) ;
2023-03-11 00:57:55 +02:00
return ;
2023-03-26 22:21:29 +02:00
}
QStringList copyDirectories ;
copyDirectories + = dirData . front ( ) ;
if ( ! dirMaps . empty ( ) )
copyDirectories + = dirMaps . front ( ) ;
2023-03-11 00:57:55 +02:00
2023-03-26 22:21:29 +02:00
if ( ! dirMp3 . empty ( ) )
copyDirectories + = dirMp3 . front ( ) ;
2023-03-11 00:57:55 +02:00
2023-03-12 18:33:29 +02:00
QDir targetRoot = pathToQString ( VCMIDirs : : get ( ) . userDataPath ( ) ) ;
2023-03-11 00:57:55 +02:00
2023-03-12 18:33:29 +02:00
for ( const QString & dirName : copyDirectories )
2023-03-11 00:57:55 +02:00
{
2023-03-12 18:33:29 +02:00
QDir sourceDir = sourceRoot . filePath ( dirName ) ;
2023-03-11 00:57:55 +02:00
QDir targetDir = targetRoot . filePath ( dirName ) ;
if ( ! targetRoot . exists ( dirName ) )
targetRoot . mkdir ( dirName ) ;
2023-03-12 18:33:29 +02:00
for ( const QString & filename : sourceDir . entryList ( QDir : : Filter : : Files ) )
2023-03-11 00:57:55 +02:00
{
QFile sourceFile ( sourceDir . filePath ( filename ) ) ;
sourceFile . copy ( targetDir . filePath ( filename ) ) ;
}
}
heroesDataUpdate ( ) ;
}
// Tab Mod Preset
void FirstLaunchView : : modPresetUpdate ( )
{
bool translationExists = ! findTranslationModName ( ) . isEmpty ( ) ;
ui - > labelPresetLanguage - > setVisible ( translationExists ) ;
2023-04-03 00:46:26 +02:00
ui - > labelPresetLanguageDescr - > setVisible ( translationExists ) ;
2023-03-12 18:33:29 +02:00
ui - > checkBoxPresetLanguage - > setVisible ( translationExists ) ;
2023-03-11 00:57:55 +02:00
2023-03-12 18:33:29 +02:00
ui - > checkBoxPresetLanguage - > setEnabled ( checkCanInstallTranslation ( ) ) ;
ui - > checkBoxPresetExtras - > setEnabled ( checkCanInstallExtras ( ) ) ;
ui - > checkBoxPresetHota - > setEnabled ( checkCanInstallHota ( ) ) ;
ui - > checkBoxPresetWog - > setEnabled ( checkCanInstallWog ( ) ) ;
2023-03-26 19:11:59 +02:00
// we can't install anything - either repository checkout is off or all recommended mods are already installed
if ( ! checkCanInstallTranslation ( ) & & ! checkCanInstallExtras ( ) & & ! checkCanInstallHota ( ) & & ! checkCanInstallWog ( ) )
exitSetup ( ) ;
2023-03-11 00:57:55 +02:00
}
QString FirstLaunchView : : findTranslationModName ( )
{
if ( ! getModView ( ) )
return QString ( ) ;
QString preferredlanguage = QString : : fromStdString ( settings [ " general " ] [ " language " ] . String ( ) ) ;
QString installedlanguage = QString : : fromStdString ( settings [ " session " ] [ " language " ] . String ( ) ) ;
if ( preferredlanguage = = installedlanguage )
return QString ( ) ;
return getModView ( ) - > getTranslationModName ( preferredlanguage ) ;
}
bool FirstLaunchView : : checkCanInstallTranslation ( )
{
QString modName = findTranslationModName ( ) ;
if ( modName . isEmpty ( ) )
return false ;
return checkCanInstallMod ( modName ) ;
}
bool FirstLaunchView : : checkCanInstallWog ( )
{
return checkCanInstallMod ( " wake-of-gods " ) ;
}
bool FirstLaunchView : : checkCanInstallHota ( )
{
return checkCanInstallMod ( " hota " ) ;
}
bool FirstLaunchView : : checkCanInstallExtras ( )
{
return checkCanInstallMod ( " vcmi-extras " ) ;
}
CModListView * FirstLaunchView : : getModView ( )
{
2023-10-28 17:44:17 +02:00
auto * mainWindow = dynamic_cast < MainWindow * > ( QApplication : : activeWindow ( ) ) ;
2023-03-11 00:57:55 +02:00
assert ( mainWindow ) ;
if ( ! mainWindow )
return nullptr ;
return mainWindow - > getModView ( ) ;
}
bool FirstLaunchView : : checkCanInstallMod ( const QString & modID )
{
2023-03-26 19:11:59 +02:00
return getModView ( ) & & getModView ( ) - > isModAvailable ( modID ) ;
2023-03-11 00:57:55 +02:00
}
2023-03-12 18:33:29 +02:00
void FirstLaunchView : : on_pushButtonPresetBack_clicked ( )
2023-03-11 00:57:55 +02:00
{
2023-03-12 18:33:29 +02:00
activateTabHeroesData ( ) ;
2023-03-11 00:57:55 +02:00
}
2023-03-12 18:33:29 +02:00
void FirstLaunchView : : on_pushButtonPresetNext_clicked ( )
2023-03-11 00:57:55 +02:00
{
2023-03-12 18:33:29 +02:00
QStringList modsToInstall ;
2023-03-11 00:57:55 +02:00
2023-03-26 19:11:59 +02:00
if ( ui - > checkBoxPresetLanguage - > isChecked ( ) & & checkCanInstallTranslation ( ) )
2023-03-12 18:33:29 +02:00
modsToInstall . push_back ( findTranslationModName ( ) ) ;
2023-03-11 00:57:55 +02:00
2023-03-26 19:11:59 +02:00
if ( ui - > checkBoxPresetExtras - > isChecked ( ) & & checkCanInstallExtras ( ) )
2023-03-12 18:33:29 +02:00
modsToInstall . push_back ( " vcmi-extras " ) ;
2023-03-11 00:57:55 +02:00
2023-03-26 19:11:59 +02:00
if ( ui - > checkBoxPresetWog - > isChecked ( ) & & checkCanInstallWog ( ) )
2023-03-12 18:33:29 +02:00
modsToInstall . push_back ( " wake-of-gods " ) ;
2023-03-11 00:57:55 +02:00
2023-03-26 19:11:59 +02:00
if ( ui - > checkBoxPresetHota - > isChecked ( ) & & checkCanInstallHota ( ) )
2023-03-12 18:33:29 +02:00
modsToInstall . push_back ( " hota " ) ;
2023-03-11 00:57:55 +02:00
2023-03-12 13:28:09 +02:00
exitSetup ( ) ;
2023-03-12 18:33:29 +02:00
for ( auto const & modName : modsToInstall )
getModView ( ) - > doInstallMod ( modName ) ;
2023-03-11 00:57:55 +02:00
}
2023-03-27 18:35:42 +02:00
void FirstLaunchView : : on_pushButtonDiscord_clicked ( )
{
QDesktopServices : : openUrl ( QUrl ( " https://discord.gg/chBT42V " ) ) ;
}
void FirstLaunchView : : on_pushButtonSlack_clicked ( )
{
QDesktopServices : : openUrl ( QUrl ( " https://slack.vcmi.eu/ " ) ) ;
}
void FirstLaunchView : : on_pushButtonGithub_clicked ( )
{
QDesktopServices : : openUrl ( QUrl ( " https://github.com/vcmi/vcmi " ) ) ;
}