| 1 | #include "utils.h" |
| 2 | #include "systemconsts.h" |
| 3 | #include <QDate> |
| 4 | #include <QDateTime> |
| 5 | #include <QApplication> |
| 6 | #include "editor.h" |
| 7 | #include "editorlist.h" |
| 8 | #include "settings.h" |
| 9 | #include "mainwindow.h" |
| 10 | #include "project.h" |
| 11 | #include "parser/cppparser.h" |
| 12 | #include "compiler/executablerunner.h" |
| 13 | #include <QMimeDatabase> |
| 14 | #ifdef Q_OS_WIN |
| 15 | #include <windows.h> |
| 16 | #endif |
| 17 | |
| 18 | QStringList splitProcessCommand(const QString &cmd) |
| 19 | { |
| 20 | QStringList result; |
| 21 | SplitProcessCommandQuoteType quoteType = SplitProcessCommandQuoteType::None; |
| 22 | int i=0; |
| 23 | QString current; |
| 24 | while (i<cmd.length()) { |
| 25 | switch (cmd[i].unicode()) { |
| 26 | case ' ': |
| 27 | case '\t': |
| 28 | case '\r': |
| 29 | case '\n': |
| 30 | if (quoteType == SplitProcessCommandQuoteType::None) { |
| 31 | if (!current.isEmpty()) { |
| 32 | result.append(current); |
| 33 | } |
| 34 | current = "" ; |
| 35 | } else { |
| 36 | current += cmd[i]; |
| 37 | } |
| 38 | i++; |
| 39 | break; |
| 40 | case '\"': |
| 41 | switch(quoteType) { |
| 42 | case SplitProcessCommandQuoteType::None: |
| 43 | quoteType = SplitProcessCommandQuoteType::Double; |
| 44 | break; |
| 45 | case SplitProcessCommandQuoteType::Double: |
| 46 | quoteType = SplitProcessCommandQuoteType::None; |
| 47 | break; |
| 48 | default: |
| 49 | current+=cmd[i]; |
| 50 | } |
| 51 | i++; |
| 52 | break; |
| 53 | case '\'': |
| 54 | switch(quoteType) { |
| 55 | case SplitProcessCommandQuoteType::None: |
| 56 | quoteType = SplitProcessCommandQuoteType::Single; |
| 57 | break; |
| 58 | case SplitProcessCommandQuoteType::Single: |
| 59 | quoteType = SplitProcessCommandQuoteType::None; |
| 60 | break; |
| 61 | default: |
| 62 | current+=cmd[i]; |
| 63 | } |
| 64 | i++; |
| 65 | break; |
| 66 | case '\\': |
| 67 | current += cmd[i]; |
| 68 | i++; |
| 69 | if (i<cmd.length()) { |
| 70 | current += cmd[i]; |
| 71 | i++; |
| 72 | } |
| 73 | break; |
| 74 | default: |
| 75 | current += cmd[i]; |
| 76 | i++; |
| 77 | } |
| 78 | } |
| 79 | if (!current.isEmpty()) |
| 80 | result.append(current); |
| 81 | return result; |
| 82 | } |
| 83 | |
| 84 | FileType getFileType(const QString &filename) |
| 85 | { |
| 86 | if (filename.endsWith(".dev" ,PATH_SENSITIVITY)) { |
| 87 | return FileType::Project; |
| 88 | } |
| 89 | if (filename.endsWith(".C" )) { |
| 90 | return FileType::CppSource; |
| 91 | } |
| 92 | if (filename.endsWith(".CPP" )) { |
| 93 | return FileType::CppSource; |
| 94 | } |
| 95 | if (filename.endsWith(".c" ,PATH_SENSITIVITY)) { |
| 96 | return FileType::CSource; |
| 97 | } |
| 98 | if (filename.endsWith(".cpp" ,PATH_SENSITIVITY)) { |
| 99 | return FileType::CppSource; |
| 100 | } |
| 101 | if (filename.endsWith(".cc" ,PATH_SENSITIVITY)) { |
| 102 | return FileType::CppSource; |
| 103 | } |
| 104 | if (filename.endsWith(".cxx" ,PATH_SENSITIVITY)) { |
| 105 | return FileType::CppSource; |
| 106 | } |
| 107 | if (filename.endsWith(".c++" ,PATH_SENSITIVITY)) { |
| 108 | return FileType::CppSource; |
| 109 | } |
| 110 | if (filename.endsWith(".H" )) { |
| 111 | return FileType::CHeader; |
| 112 | } |
| 113 | if (filename.endsWith(".h" ,PATH_SENSITIVITY)) { |
| 114 | return FileType::CHeader; |
| 115 | } |
| 116 | if (filename.endsWith(".hpp" ,PATH_SENSITIVITY)) { |
| 117 | return FileType::CppHeader; |
| 118 | } |
| 119 | if (filename.endsWith(".hh" ,PATH_SENSITIVITY)) { |
| 120 | return FileType::CppHeader; |
| 121 | } |
| 122 | if (filename.endsWith(".hxx" ,PATH_SENSITIVITY)) { |
| 123 | return FileType::CppHeader; |
| 124 | } |
| 125 | if (filename.endsWith(".inl" ,PATH_SENSITIVITY)) { |
| 126 | return FileType::CppHeader; |
| 127 | } |
| 128 | if (filename.endsWith(".rc" ,PATH_SENSITIVITY)) { |
| 129 | return FileType::WindowsResourceSource; |
| 130 | } |
| 131 | if (filename.endsWith(".in" ,PATH_SENSITIVITY)) { |
| 132 | return FileType::Text; |
| 133 | } |
| 134 | if (filename.endsWith(".out" ,PATH_SENSITIVITY)) { |
| 135 | return FileType::Text; |
| 136 | } |
| 137 | if (filename.endsWith(".txt" ,PATH_SENSITIVITY)) { |
| 138 | return FileType::Text; |
| 139 | } |
| 140 | if (filename.endsWith(".dat" ,PATH_SENSITIVITY)) { |
| 141 | return FileType::Text; |
| 142 | } |
| 143 | QMimeDatabase db; |
| 144 | QMimeType mimeType=db.mimeTypeForFile(filename); |
| 145 | if (mimeType.isValid() && mimeType.name().startsWith("text/" )) { |
| 146 | return FileType::Text; |
| 147 | } |
| 148 | return FileType::Other; |
| 149 | } |
| 150 | |
| 151 | QString genMakePath(const QString &fileName, bool escapeSpaces, bool encloseInQuotes) |
| 152 | { |
| 153 | QString result = fileName; |
| 154 | |
| 155 | // Convert backslashes to slashes |
| 156 | result.replace('\\','/'); |
| 157 | if (escapeSpaces) { |
| 158 | result.replace(' ',"\\ " ); |
| 159 | } |
| 160 | if (encloseInQuotes) |
| 161 | if (result.contains(' ')) |
| 162 | result = '"'+result+'"'; |
| 163 | return result; |
| 164 | } |
| 165 | |
| 166 | QString genMakePath1(const QString &fileName) |
| 167 | { |
| 168 | return genMakePath(fileName, false, true); |
| 169 | } |
| 170 | |
| 171 | QString genMakePath2(const QString &fileName) |
| 172 | { |
| 173 | return genMakePath(fileName, true, false); |
| 174 | } |
| 175 | |
| 176 | QString getCompiledExecutableName(const QString& filename) |
| 177 | { |
| 178 | return changeFileExt(filename,EXECUTABLE_EXT); |
| 179 | } |
| 180 | |
| 181 | bool programHasConsole(const QString & filename) |
| 182 | { |
| 183 | #ifdef Q_OS_WIN |
| 184 | bool result = false; |
| 185 | HANDLE handle = CreateFile(filename.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); |
| 186 | if (handle != INVALID_HANDLE_VALUE) { |
| 187 | IMAGE_DOS_HEADER dos_header; |
| 188 | DWORD signature; |
| 189 | DWORD bytesread; |
| 190 | IMAGE_FILE_HEADER pe_header; |
| 191 | IMAGE_OPTIONAL_HEADER opt_header; |
| 192 | |
| 193 | ReadFile(handle, &dos_header, sizeof(dos_header), &bytesread, NULL); |
| 194 | SetFilePointer(handle, dos_header.e_lfanew, NULL, 0); |
| 195 | ReadFile(handle, &signature, sizeof(signature), &bytesread, NULL); |
| 196 | ReadFile(handle, &pe_header, sizeof(pe_header), &bytesread, NULL); |
| 197 | ReadFile(handle, &opt_header, sizeof(opt_header), &bytesread, NULL); |
| 198 | |
| 199 | result = (opt_header.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI); |
| 200 | } |
| 201 | CloseHandle(handle); |
| 202 | return result; |
| 203 | #else |
| 204 | return true; |
| 205 | #endif |
| 206 | } |
| 207 | |
| 208 | QString parseMacros(const QString &s) |
| 209 | { |
| 210 | QString result = s; |
| 211 | Editor *e = pMainWindow->editorList()->getEditor(); |
| 212 | |
| 213 | result.replace("<DEFAULT>" , localizePath(QDir::currentPath())); |
| 214 | result.replace("<DEVCPP>" , localizePath(pSettings->dirs().executable())); |
| 215 | result.replace("<DEVCPPVERSION>" , REDPANDA_CPP_VERSION); |
| 216 | result.replace("<EXECPATH>" , localizePath(pSettings->dirs().appDir())); |
| 217 | QDate today = QDate::currentDate(); |
| 218 | QDateTime now = QDateTime::currentDateTime(); |
| 219 | |
| 220 | result.replace("<DATE>" , today.toString("yyyy-MM-dd" )); |
| 221 | result.replace("<DATETIME>" , now.toString("yyyy-MM-dd hh:mm:ss" )); |
| 222 | |
| 223 | Settings::PCompilerSet compilerSet = pSettings->compilerSets().defaultSet(); |
| 224 | if (compilerSet) { |
| 225 | // Only provide the first cpp include dir |
| 226 | if (compilerSet->defaultCppIncludeDirs().count()>0) |
| 227 | result.replace("<INCLUDE>" , localizePath(compilerSet->defaultCppIncludeDirs().front())); |
| 228 | else |
| 229 | result.replace("<INCLUDE>" ,"" ); |
| 230 | |
| 231 | // Only provide the first lib dir |
| 232 | if (compilerSet->defaultLibDirs().count()>0) |
| 233 | result.replace("<LIB>" , localizePath(compilerSet->defaultLibDirs().front())); |
| 234 | else |
| 235 | result.replace("<LIB>" ,"" ); |
| 236 | } |
| 237 | |
| 238 | if (e!=nullptr && !e->inProject()) { // Non-project editor macros |
| 239 | result.replace("<EXENAME>" , extractFileName(changeFileExt(e->filename(),EXECUTABLE_EXT))); |
| 240 | result.replace("<EXEFILE>" , localizePath(changeFileExt(e->filename(),EXECUTABLE_EXT))); |
| 241 | result.replace("<PROJECTNAME>" , extractFileName(e->filename())); |
| 242 | result.replace("<PROJECTFILE>" , localizePath(e->filename())); |
| 243 | result.replace("<PROJECTFILENAME>" , extractFileName(e->filename())); |
| 244 | result.replace("<PROJECTPATH>" , localizePath(extractFileDir(e->filename()))); |
| 245 | } else if (pMainWindow->project()) { |
| 246 | result.replace("<EXENAME>" , extractFileName(pMainWindow->project()->executable())); |
| 247 | result.replace("<EXEFILE>" , localizePath(pMainWindow->project()->executable())); |
| 248 | result.replace("<PROJECTNAME>" , pMainWindow->project()->name()); |
| 249 | result.replace("<PROJECTFILE>" , localizePath(pMainWindow->project()->filename())); |
| 250 | result.replace("<PROJECTFILENAME>" , extractFileName(pMainWindow->project()->filename())); |
| 251 | result.replace("<PROJECTPATH>" , localizePath(pMainWindow->project()->directory())); |
| 252 | } else { |
| 253 | result.replace("<EXENAME>" , "" ); |
| 254 | result.replace("<EXEFILE>" , "" ); |
| 255 | result.replace("<PROJECTNAME>" , "" ); |
| 256 | result.replace("<PROJECTFILE>" , "" ); |
| 257 | result.replace("<PROJECTFILENAME>" , "" ); |
| 258 | result.replace("<PROJECTPATH>" , "" ); |
| 259 | } |
| 260 | |
| 261 | // Editor macros |
| 262 | if (e!=nullptr) { |
| 263 | result.replace("<SOURCENAME>" , extractFileName(e->filename())); |
| 264 | result.replace("<SOURCEFILE>" , localizePath(e->filename())); |
| 265 | result.replace("<SOURCEPATH>" , localizePath(extractFileDir(e->filename()))); |
| 266 | result.replace("<WORDXY>" , e->wordAtCursor()); |
| 267 | } else { |
| 268 | result.replace("<SOURCENAME>" , "" ); |
| 269 | result.replace("<SOURCEFILE>" , "" ); |
| 270 | result.replace("<SOURCEPATH>" , "" ); |
| 271 | result.replace("<WORDXY>" , "" ); |
| 272 | } |
| 273 | return result; |
| 274 | } |
| 275 | |
| 276 | void resetCppParser(std::shared_ptr<CppParser> parser, int compilerSetIndex) |
| 277 | { |
| 278 | if (!parser) |
| 279 | return; |
| 280 | // Configure parser |
| 281 | parser->reset(); |
| 282 | //paser->enabled = pSettings-> devCodeCompletion.Enabled; |
| 283 | // CppParser.ParseLocalHeaders := devCodeCompletion.ParseLocalHeaders; |
| 284 | // CppParser.ParseGlobalHeaders := devCodeCompletion.ParseGlobalHeaders; |
| 285 | parser->setEnabled(true); |
| 286 | parser->setParseGlobalHeaders(true); |
| 287 | parser->setParseLocalHeaders(true); |
| 288 | // Set options depending on the current compiler set |
| 289 | // TODO: do this every time OnCompilerSetChanged |
| 290 | if (compilerSetIndex<0) { |
| 291 | compilerSetIndex=pSettings->compilerSets().defaultIndex(); |
| 292 | } |
| 293 | Settings::PCompilerSet compilerSet = pSettings->compilerSets().getSet(compilerSetIndex); |
| 294 | parser->clearIncludePaths(); |
| 295 | if (compilerSet) { |
| 296 | foreach (const QString& file,compilerSet->CppIncludeDirs()) { |
| 297 | parser->addIncludePath(file); |
| 298 | } |
| 299 | foreach (const QString& file,compilerSet->CIncludeDirs()) { |
| 300 | parser->addIncludePath(file); |
| 301 | } |
| 302 | foreach (const QString& file,compilerSet->defaultCppIncludeDirs()) { |
| 303 | parser->addIncludePath(file); |
| 304 | } |
| 305 | foreach (const QString& file,compilerSet->defaultCIncludeDirs()) { |
| 306 | parser->addIncludePath(file); |
| 307 | } |
| 308 | //TODO: Add default include dirs last, just like gcc does |
| 309 | // Set defines |
| 310 | if (parser->language()==ParserLanguage::C) { |
| 311 | for (QString define:compilerSet->CDefines()) { |
| 312 | parser->addHardDefineByLine(define); // predefined constants from -dM -E |
| 313 | } |
| 314 | } else { |
| 315 | for (QString define:compilerSet->CppDefines()) { |
| 316 | parser->addHardDefineByLine(define); // predefined constants from -dM -E |
| 317 | } |
| 318 | } |
| 319 | // add a Red Pand C++ 's own macro |
| 320 | parser->addHardDefineByLine("#define EGE_FOR_AUTO_CODE_COMPLETETION_ONLY" ); |
| 321 | // add C/C++ default macro |
| 322 | parser->addHardDefineByLine("#define __FILE__ 1" ); |
| 323 | parser->addHardDefineByLine("#define __LINE__ 1" ); |
| 324 | parser->addHardDefineByLine("#define __DATE__ 1" ); |
| 325 | parser->addHardDefineByLine("#define __TIME__ 1" ); |
| 326 | } |
| 327 | parser->parseHardDefines(); |
| 328 | pMainWindow->disconnect(parser.get(), |
| 329 | &CppParser::onStartParsing, |
| 330 | pMainWindow, |
| 331 | &MainWindow::onStartParsing); |
| 332 | pMainWindow->disconnect(parser.get(), |
| 333 | &CppParser::onProgress, |
| 334 | pMainWindow, |
| 335 | &MainWindow::onParserProgress); |
| 336 | pMainWindow->disconnect(parser.get(), |
| 337 | &CppParser::onEndParsing, |
| 338 | pMainWindow, |
| 339 | &MainWindow::onEndParsing); |
| 340 | pMainWindow->connect(parser.get(), |
| 341 | &CppParser::onStartParsing, |
| 342 | pMainWindow, |
| 343 | &MainWindow::onStartParsing); |
| 344 | pMainWindow->connect(parser.get(), |
| 345 | &CppParser::onProgress, |
| 346 | pMainWindow, |
| 347 | &MainWindow::onParserProgress); |
| 348 | pMainWindow->connect(parser.get(), |
| 349 | &CppParser::onEndParsing, |
| 350 | pMainWindow, |
| 351 | &MainWindow::onEndParsing); |
| 352 | } |
| 353 | |
| 354 | int getNewFileNumber() |
| 355 | { |
| 356 | static int count = 0; |
| 357 | count++; |
| 358 | return count; |
| 359 | } |
| 360 | |
| 361 | #ifdef Q_OS_WIN |
| 362 | static bool gIsGreenEdition = true; |
| 363 | static bool gIsGreenEditionInited = false; |
| 364 | #endif |
| 365 | bool isGreenEdition() |
| 366 | { |
| 367 | #ifdef Q_OS_WIN |
| 368 | if (!gIsGreenEditionInited) { |
| 369 | QString keyString = QString("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\RedPanda-C++" ); |
| 370 | QString value; |
| 371 | if (!readRegistry(HKEY_LOCAL_MACHINE,keyString.toLocal8Bit(),"UninstallString" ,value)) { |
| 372 | keyString = "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\RedPanda-C++" ; |
| 373 | if (!readRegistry(HKEY_LOCAL_MACHINE,keyString.toLocal8Bit(),"UninstallString" ,value)) { |
| 374 | value="" ; |
| 375 | } |
| 376 | } |
| 377 | if (!value.isEmpty()) { |
| 378 | QString regPath = extractFileDir(value); |
| 379 | |
| 380 | QString appPath = QApplication::instance()->applicationDirPath(); |
| 381 | gIsGreenEdition = excludeTrailingPathDelimiter(regPath).compare(excludeTrailingPathDelimiter(appPath), |
| 382 | Qt::CaseInsensitive)!=0; |
| 383 | } |
| 384 | gIsGreenEditionInited = true; |
| 385 | } |
| 386 | return gIsGreenEdition; |
| 387 | #else |
| 388 | return false; |
| 389 | #endif |
| 390 | } |
| 391 | |
| 392 | QByteArray runAndGetOutput(const QString &cmd, const QString& workingDir, const QStringList& arguments, |
| 393 | const QByteArray &inputContent, bool inheritEnvironment, |
| 394 | const QProcessEnvironment& env) |
| 395 | { |
| 396 | QProcess process; |
| 397 | QByteArray result; |
| 398 | if (env.isEmpty()) { |
| 399 | if (inheritEnvironment) { |
| 400 | process.setProcessEnvironment(QProcessEnvironment::systemEnvironment()); |
| 401 | } else { |
| 402 | process.setProcessEnvironment(QProcessEnvironment()); |
| 403 | } |
| 404 | } else { |
| 405 | process.setProcessEnvironment(env); |
| 406 | } |
| 407 | process.setWorkingDirectory(workingDir); |
| 408 | process.connect(&process,&QProcess::readyReadStandardError, |
| 409 | [&](){ |
| 410 | result.append(process.readAllStandardError()); |
| 411 | }); |
| 412 | process.connect(&process,&QProcess::readyReadStandardOutput, |
| 413 | [&](){ |
| 414 | result.append(process.readAllStandardOutput()); |
| 415 | }); |
| 416 | process.start(cmd,arguments); |
| 417 | if (!inputContent.isEmpty()) { |
| 418 | process.write(inputContent); |
| 419 | } |
| 420 | process.closeWriteChannel(); |
| 421 | process.waitForFinished(); |
| 422 | return result; |
| 423 | } |
| 424 | |
| 425 | void executeFile(const QString &fileName, const QString ¶ms, const QString &workingDir, const QString &tempFile) |
| 426 | { |
| 427 | ExecutableRunner* runner=new ExecutableRunner( |
| 428 | fileName, |
| 429 | params, |
| 430 | workingDir); |
| 431 | runner->connect(runner, &QThread::finished, |
| 432 | [runner,tempFile](){ |
| 433 | if (!tempFile.isEmpty()) { |
| 434 | QFile::remove(tempFile); |
| 435 | } |
| 436 | runner->deleteLater(); |
| 437 | }); |
| 438 | runner->connect(runner, &Runner::runErrorOccurred, |
| 439 | [](const QString&){ |
| 440 | //todo |
| 441 | }); |
| 442 | runner->setStartConsole(true); |
| 443 | runner->start(); |
| 444 | } |
| 445 | |
| 446 | #ifdef Q_OS_WIN |
| 447 | bool readRegistry(HKEY key,const QByteArray& subKey, const QByteArray& name, QString& value) { |
| 448 | DWORD dataSize; |
| 449 | LONG result; |
| 450 | result = RegGetValueA(key,subKey, |
| 451 | name, RRF_RT_REG_SZ | RRF_RT_REG_MULTI_SZ, |
| 452 | NULL, |
| 453 | NULL, |
| 454 | &dataSize); |
| 455 | if (result!=ERROR_SUCCESS) |
| 456 | return false; |
| 457 | char * buffer = new char[dataSize+10]; |
| 458 | result = RegGetValueA(key,subKey, |
| 459 | name, RRF_RT_REG_SZ | RRF_RT_REG_MULTI_SZ, |
| 460 | NULL, |
| 461 | buffer, |
| 462 | &dataSize); |
| 463 | if (result!=ERROR_SUCCESS) { |
| 464 | delete[] buffer; |
| 465 | return false; |
| 466 | } |
| 467 | value=QString::fromLocal8Bit(buffer); |
| 468 | delete [] buffer; |
| 469 | return true; |
| 470 | } |
| 471 | #endif |
| 472 | |
| 473 | qulonglong stringToHex(const QString &str, bool &isOk) |
| 474 | { |
| 475 | qulonglong value = str.toULongLong(&isOk,16); |
| 476 | return value; |
| 477 | } |
| 478 | |
| 479 | bool findComplement(const QString &s, const QChar &fromToken, const QChar &toToken, int &curPos, int increment) |
| 480 | { |
| 481 | int curPosBackup = curPos; |
| 482 | int level = 0; |
| 483 | //todo: skip comment, char and strings |
| 484 | while ((curPos < s.length()) && (curPos >= 0)) { |
| 485 | if (s[curPos] == fromToken) { |
| 486 | level++; |
| 487 | } else if (s[curPos] == toToken) { |
| 488 | level--; |
| 489 | if (level == 0) |
| 490 | return true; |
| 491 | } |
| 492 | curPos += increment; |
| 493 | } |
| 494 | curPos = curPosBackup; |
| 495 | return false; |
| 496 | } |
| 497 | |
| 498 | bool haveGoodContrast(const QColor& c1, const QColor &c2) { |
| 499 | int lightness1 = qGray(c1.rgb()); |
| 500 | int lightness2 = qGray(c2.rgb()); |
| 501 | return std::abs(lightness1 - lightness2)>=120; |
| 502 | } |
| 503 | |
| 504 | QByteArray getHTTPBody(const QByteArray& content) { |
| 505 | int i= content.indexOf("\r\n\r\n" ); |
| 506 | if (i>=0) { |
| 507 | return content.mid(i+4); |
| 508 | } |
| 509 | return "" ; |
| 510 | } |
| 511 | |
| 512 | QString getSizeString(int size) |
| 513 | { |
| 514 | if (size < 1024) { |
| 515 | return QString("%1 " ).arg(size)+QObject::tr("bytes" ); |
| 516 | } else if (size < 1024 * 1024) { |
| 517 | return QString("%1 " ).arg(size / 1024.0)+QObject::tr("KB" ); |
| 518 | } else if (size < 1024 * 1024 * 1024) { |
| 519 | return QString("%1 " ).arg(size / 1024.0 / 1024.0)+QObject::tr("MB" ); |
| 520 | } else { |
| 521 | return QString("%1 " ).arg(size / 1024.0 / 1024.0 / 1024.0)+QObject::tr("GB" ); |
| 522 | } |
| 523 | } |
| 524 | |