diff --git a/krArc/krarc.cpp b/krArc/krarc.cpp index c182c378..2232e673 100644 --- a/krArc/krarc.cpp +++ b/krArc/krarc.cpp @@ -1,1908 +1,1908 @@ /***************************************************************************** * Copyright (C) 2003 Rafi Yanai * * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krarc.h" #include "../krusader/defaults.h" // QtCore #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../krusader/compat.h" #define MAX_IPC_SIZE (1024*32) #define TRIES_WITH_PASSWORDS 3 using namespace KIO; extern "C" { #ifdef KRARC_ENABLED /* This codec is for being able to handle files which encoding differs from the current locale. * * Unfortunately QProcess requires QString parameters for arguments which are encoded to Local8Bit * If we want to use unzip with ISO-8852-2 when the current locale is UTF-8, it will cause problems. * * Workaround: * 1. encode the QString to QByteArray ( according to the selected remote encoding, ISO-8852-2 ) * 2. encode QByteArray to QString again * unicode 0xE000-0xF7FF is for private use * the byte array is mapped to 0xE000-0xE0FF unicodes * 3. KrArcCodec maps 0xE000-0xE0FF to 0x0000-0x00FF, while calls the default encoding routine * for other unicodes. */ class KrArcCodec : public QTextCodec { public: KrArcCodec(QTextCodec * codec) : originalCodec(codec) {} ~KrArcCodec() override = default; QByteArray name() const override { return originalCodec->name(); } QList aliases() const override { return originalCodec->aliases(); } int mibEnum() const override { return originalCodec->mibEnum(); } protected: QString convertToUnicode(const char *in, int length, ConverterState *state) const override { return originalCodec->toUnicode(in, length, state); } QByteArray convertFromUnicode(const QChar *in, int length, ConverterState *state) const override { // the QByteArray is embedded into the unicode charset (QProcess hell) QByteArray result; for (int i = 0; i != length; i++) { if (((in[ i ].unicode()) & 0xFF00) == 0xE000) // map 0xE000-0xE0FF to 0x0000-0x00FF result.append((char)(in[ i ].unicode() & 0xFF)); else result.append(originalCodec->fromUnicode(in + i, 1, state)); } return result; } private: QTextCodec * originalCodec; } *krArcCodec; #define SET_KRCODEC QTextCodec *origCodec = QTextCodec::codecForLocale(); \ QTextCodec::setCodecForLocale( krArcCodec ); #define RESET_KRCODEC QTextCodec::setCodecForLocale( origCodec ); #endif // KRARC_ENABLED class DummySlave : public KIO::SlaveBase { public: DummySlave(const QByteArray &pool_socket, const QByteArray &app_socket) : SlaveBase("kio_krarc", pool_socket, app_socket) { error((int)ERR_SLAVE_DEFINED, QString("krarc is disabled.")); } }; int Q_DECL_EXPORT kdemain(int argc, char **argv) { if (argc != 4) { - qWarning() << "Usage: kio_krarc protocol domain-socket1 domain-socket2" << endl; + qWarning() << "Usage: kio_krarc protocol domain-socket1 domain-socket2" << QT_ENDL; exit(-1); } // At least, that fixes the empty name in the warning that says: Please fix the "" KIO slave // There is more information in https://bugs.kde.org/show_bug.cgi?id=384653 QCoreApplication app(argc, argv); app.setApplicationName(QStringLiteral("kio_krarc")); #ifdef KRARC_ENABLED kio_krarcProtocol slave(argv[2], argv[3]); #else DummySlave slave(argv[2], argv[3]); #endif slave.dispatchLoop(); return 0; } } // extern "C" #ifdef KRARC_ENABLED kio_krarcProtocol::kio_krarcProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) : SlaveBase("kio_krarc", pool_socket, app_socket), archiveChanged(true), arcFile(nullptr), extArcReady(false), password(QString()), codec(nullptr) { KRFUNC; KConfigGroup group(&krConf, "General"); QString tmpDirPath = group.readEntry("Temp Directory", _TempDirectory); QDir tmpDir(tmpDirPath); if(!tmpDir.exists()) { for (int i = 1 ; i != -1 ; i = tmpDirPath.indexOf('/', i + 1)) QDir().mkdir(tmpDirPath.left(i)); QDir().mkdir(tmpDirPath); } arcTempDir = tmpDirPath + DIR_SEPARATOR; QString dirName = "krArc" + QDateTime::currentDateTime().toString(Qt::ISODate); dirName.replace(QRegExp(":"), "_"); tmpDir.mkdir(dirName); arcTempDir = arcTempDir + dirName + DIR_SEPARATOR; krArcCodec = new KrArcCodec(QTextCodec::codecForLocale()); } /* ---------------------------------------------------------------------------------- */ kio_krarcProtocol::~kio_krarcProtocol() { KRFUNC; // delete the temp directory KProcess proc; proc << fullPathName("rm") << "-rf" << arcTempDir; proc.start(); proc.waitForFinished(); } /* ---------------------------------------------------------------------------------- */ bool kio_krarcProtocol::checkWriteSupport() { KRFUNC; krConf.reparseConfiguration(); if (KConfigGroup(&krConf, "kio_krarc").readEntry("EnableWrite", false)) return true; else { error(ERR_UNSUPPORTED_ACTION, i18n("krarc: write support is disabled.\n" "You can enable it on the 'Archives' page in Konfigurator.")); return false; } } void kio_krarcProtocol::receivedData(KProcess *, QByteArray &d) { KRFUNC; const QByteArray& buf(d); data(buf); processedSize(d.length()); decompressedLen += d.length(); } void kio_krarcProtocol::mkdir(const QUrl &url, int permissions) { KRFUNC; const QString path = getPath(url); KRDEBUG(path); if (!checkWriteSupport()) return; // In case of KIO::mkpath call there is a mkdir call for every path element. // Therefore the path all the way up to our archive needs to be checked for existence // and reported as success. if (QDir().exists(path)) { finished(); return; } if (!setArcFile(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, path); return; } if (newArchiveURL && !initDirDict(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, path); return; } if (putCmd.isEmpty()) { error(ERR_UNSUPPORTED_ACTION, i18n("Creating folders is not supported with %1 archives", arcType)); return; } const QString arcFilePath = getPath(arcFile->url()); if (arcType == "arj" || arcType == "lha") { QString arcDir = path.mid(arcFilePath.length()); if (arcDir.right(1) != DIR_SEPARATOR) arcDir = arcDir + DIR_SEPARATOR; if (dirDict.find(arcDir) == dirDict.end()) addNewDir(arcDir); finished(); return; } QString arcDir = findArcDirectory(url); QString tempDir = arcDir.mid(1) + path.mid(path.lastIndexOf(DIR_SEPARATOR) + 1); if (tempDir.right(1) != DIR_SEPARATOR) tempDir = tempDir + DIR_SEPARATOR; if (permissions == -1) permissions = 0777; //set default permissions QByteArray arcTempDirEnc = arcTempDir.toLocal8Bit(); for (int i = 0;i < tempDir.length() && i >= 0; i = tempDir.indexOf(DIR_SEPARATOR, i + 1)) { QByteArray newDirs = encodeString(tempDir.left(i)); newDirs.prepend(arcTempDirEnc); QT_MKDIR(newDirs, permissions); } if (tempDir.endsWith(DIR_SEPARATOR)) tempDir.truncate(tempDir.length() - 1); // pack the directory KrLinecountingProcess proc; proc << putCmd << arcFilePath << localeEncodedString(tempDir); infoMessage(i18n("Creating %1...", url.fileName())); QDir::setCurrent(arcTempDir); SET_KRCODEC proc.start(); RESET_KRCODEC proc.waitForFinished(); // delete the temp directory QDir().rmdir(arcTempDir); if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(proc.exitCode())) { error(ERR_CANNOT_WRITE, path + "\n\n" + proc.getErrorMsg()); return; } // force a refresh of archive information initDirDict(url, true); finished(); } void kio_krarcProtocol::put(const QUrl &url, int permissions, KIO::JobFlags flags) { KRFUNC; KRDEBUG(getPath(url)); if (!checkWriteSupport()) return; bool overwrite = !!(flags & KIO::Overwrite); bool resume = !!(flags & KIO::Resume); if (!setArcFile(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (newArchiveURL && !initDirDict(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (putCmd.isEmpty()) { error(ERR_UNSUPPORTED_ACTION, i18n("Writing to %1 archives is not supported", arcType)); return; } if (!overwrite && findFileEntry(url)) { error(ERR_FILE_ALREADY_EXIST, getPath(url)); return; } QString arcDir = findArcDirectory(url); if (arcDir.isEmpty()) KRDEBUG("arcDir is empty."); QString tempFile = arcDir.mid(1) + getPath(url).mid(getPath(url).lastIndexOf(DIR_SEPARATOR) + 1); QString tempDir = arcDir.mid(1); if (tempDir.right(1) != DIR_SEPARATOR) tempDir = tempDir + DIR_SEPARATOR; if (permissions == -1) permissions = 0777; //set default permissions QByteArray arcTempDirEnc = arcTempDir.toLocal8Bit(); for (int i = 0;i < tempDir.length() && i >= 0; i = tempDir.indexOf(DIR_SEPARATOR, i + 1)) { QByteArray newDirs = encodeString(tempDir.left(i)); newDirs.prepend(arcTempDirEnc); QT_MKDIR(newDirs, 0755); } int fd; if (resume) { QByteArray ba = encodeString(tempFile); ba.prepend(arcTempDirEnc); fd = QT_OPEN(ba, O_RDWR); // append if resuming QT_LSEEK(fd, 0, SEEK_END); // Seek to end } else { // WABA: Make sure that we keep writing permissions ourselves, // otherwise we can be in for a surprise on NFS. mode_t initialMode; if (permissions != -1) initialMode = permissions | S_IWUSR | S_IRUSR; else initialMode = 0666; QByteArray ba = encodeString(tempFile); ba.prepend(arcTempDirEnc); fd = QT_OPEN(ba, O_CREAT | O_TRUNC | O_WRONLY, initialMode); } QByteArray buffer; int readResult; bool isIncomplete = false; do { dataReq(); readResult = readData(buffer); auto bytesWritten = ::write(fd, buffer.data(), buffer.size()); if (bytesWritten < buffer.size()) { isIncomplete = true; break; } } while (readResult > 0); ::close(fd); if (isIncomplete) { error(ERR_CANNOT_WRITE, getPath(url)); return; } // pack the file KrLinecountingProcess proc; proc << putCmd << getPath(arcFile->url()) << localeEncodedString(tempFile); infoMessage(i18n("Packing %1...", url.fileName())); QDir::setCurrent(arcTempDir); SET_KRCODEC proc.start(); RESET_KRCODEC proc.waitForFinished(); // remove the files QDir().rmdir(arcTempDir); if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(proc.exitCode())) { error(ERR_CANNOT_WRITE, getPath(url) + "\n\n" + proc.getErrorMsg()); return; } // force a refresh of archive information initDirDict(url, true); finished(); } void kio_krarcProtocol::get(const QUrl &url) { KRFUNC; get(url, TRIES_WITH_PASSWORDS); } void kio_krarcProtocol::get(const QUrl &url, int tries) { KRFUNC; KRDEBUG(getPath(url)); bool decompressToFile = false; if (!setArcFile(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (newArchiveURL && !initDirDict(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (getCmd.isEmpty()) { error(ERR_UNSUPPORTED_ACTION, i18n("Retrieving data from %1 archives is not supported", arcType)); return; } UDSEntry* entry = findFileEntry(url); if (!entry) { error(KIO::ERR_DOES_NOT_EXIST, getPath(url)); return; } if (KFileItem(*entry, url).isDir()) { error(KIO::ERR_IS_DIRECTORY, getPath(url)); return; } KIO::filesize_t expectedSize = KFileItem(*entry, url).size(); // for RPM files extract the cpio file first if (!extArcReady && arcType == "rpm") { KrLinecountingProcess cpio; cpio << "rpm2cpio" << getPath(arcFile->url(), QUrl::StripTrailingSlash); cpio.setStandardOutputFile(arcTempDir + "contents.cpio"); cpio.start(); cpio.waitForFinished(); if (cpio.exitStatus() != QProcess::NormalExit || !checkStatus(cpio.exitCode())) { error(ERR_CANNOT_READ, getPath(url) + "\n\n" + cpio.getErrorMsg()); return; } extArcReady = true; } // for DEB files extract the tar file first if (!extArcReady && arcType == "deb") { KrLinecountingProcess dpkg; dpkg << cmd << "--fsys-tarfile" << getPath(arcFile->url(), QUrl::StripTrailingSlash); dpkg.setStandardOutputFile(arcTempDir + "contents.cpio"); dpkg.start(); dpkg.waitForFinished(); if (dpkg.exitStatus() != QProcess::NormalExit || !checkStatus(dpkg.exitCode())) { error(ERR_CANNOT_READ, getPath(url) + "\n\n" + dpkg.getErrorMsg()); return; } extArcReady = true; } // Use the external unpacker to unpack the file QString file = getPath(url).mid(getPath(arcFile->url()).length() + 1); KrLinecountingProcess proc; if (extArcReady) { proc << getCmd << arcTempDir + "contents.cpio" << '*' + localeEncodedString(file); } else if (arcType == "arj" || arcType == "ace" || arcType == "7z") { proc << getCmd << getPath(arcFile->url(), QUrl::StripTrailingSlash) << localeEncodedString(file); if (arcType == "ace" && QFile("/dev/ptmx").exists()) // Don't remove, unace crashes if missing!!! proc.setStandardInputFile("/dev/ptmx"); file = url.fileName(); decompressToFile = true; } else { decompressedLen = 0; // Determine the mimetype of the file to be retrieved, and emit it. // This is mandatory in all slaves (for KRun/BrowserRun to work). QMimeDatabase db; QMimeType mt = db.mimeTypeForFile(arcTempDir + file); if (mt.isValid()) emit mimeType(mt.name()); QString escapedFilename = file; if(arcType == "zip") // left bracket needs to be escaped escapedFilename.replace('[', "[[]"); proc << getCmd << getPath(arcFile->url()); if (arcType != "gzip" && arcType != "bzip2" && arcType != "lzma" && arcType != "xz") proc << localeEncodedString(escapedFilename); connect(&proc, &KrLinecountingProcess::newOutputData, this, &kio_krarcProtocol::receivedData); proc.setMerge(false); } infoMessage(i18n("Unpacking %1...", url.fileName())); // change the working directory to our arcTempDir QDir::setCurrent(arcTempDir); SET_KRCODEC proc.setTextModeEnabled(false); proc.start(); RESET_KRCODEC proc.waitForFinished(); if (!extArcReady && !decompressToFile) { if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(proc.exitCode()) || (arcType != "bzip2" && arcType != "lzma" && arcType != "xz" && expectedSize != decompressedLen)) { if (encrypted && tries) { invalidatePassword(); get(url, tries - 1); return; } error(KIO::ERR_ACCESS_DENIED, getPath(url) + "\n\n" + proc.getErrorMsg()); return; } } else { if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(proc.exitCode()) || !QFileInfo(arcTempDir + file).exists()) { if (decompressToFile) QFile(arcTempDir + file).remove(); if (encrypted && tries) { invalidatePassword(); get(url, tries - 1); return; } error(KIO::ERR_ACCESS_DENIED, getPath(url)); return; } // the following block is ripped from KDE file KIO::Slave // $Id: krarc.cpp,v 1.43 2007/01/13 13:39:51 ckarai Exp $ QByteArray _path(QFile::encodeName(arcTempDir + file)); QT_STATBUF buff; if (QT_LSTAT(_path.data(), &buff) == -1) { if (errno == EACCES) error(KIO::ERR_ACCESS_DENIED, getPath(url)); else error(KIO::ERR_DOES_NOT_EXIST, getPath(url)); return; } if (S_ISDIR(buff.st_mode)) { error(KIO::ERR_IS_DIRECTORY, getPath(url)); return; } if (!S_ISREG(buff.st_mode)) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, getPath(url)); return; } int fd = QT_OPEN(_path.data(), O_RDONLY); if (fd < 0) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, getPath(url)); return; } // Determine the mimetype of the file to be retrieved, and emit it. // This is mandatory in all slaves (for KRun/BrowserRun to work). QMimeDatabase db; QMimeType mt = db.mimeTypeForFile(arcTempDir + file); if (mt.isValid()) emit mimeType(mt.name()); KIO::filesize_t processed_size = 0; QString resumeOffset = metaData("resume"); if (!resumeOffset.isEmpty()) { bool ok; KIO::fileoffset_t offset = resumeOffset.toLongLong(&ok); if (ok && (offset > 0) && (offset < buff.st_size)) { if (QT_LSEEK(fd, offset, SEEK_SET) == offset) { canResume(); processed_size = offset; } } } totalSize(buff.st_size); char buffer[ MAX_IPC_SIZE ]; while (1) { int n = ::read(fd, buffer, MAX_IPC_SIZE); if (n == -1) { if (errno == EINTR) continue; error(KIO::ERR_CANNOT_READ, getPath(url)); ::close(fd); return; } if (n == 0) break; // Finished { QByteArray array = QByteArray::fromRawData(buffer, n); data(array); } processed_size += n; } data(QByteArray()); ::close(fd); processedSize(buff.st_size); finished(); if (decompressToFile) QFile(arcTempDir + file).remove(); return; } // send empty buffer to mark EOF data(QByteArray()); finished(); } void kio_krarcProtocol::del(QUrl const & url, bool isFile) { KRFUNC; KRDEBUG(getPath(url)); if (!checkWriteSupport()) return; if (!setArcFile(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (newArchiveURL && !initDirDict(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (delCmd.isEmpty()) { error(ERR_UNSUPPORTED_ACTION, i18n("Deleting files from %1 archives is not supported", arcType)); return; } if (!findFileEntry(url)) { if ((arcType != "arj" && arcType != "lha") || isFile) { error(KIO::ERR_DOES_NOT_EXIST, getPath(url)); return; } } QString file = getPath(url).mid(getPath(arcFile->url()).length() + 1); if (!isFile && file.right(1) != DIR_SEPARATOR) { if (arcType == "zip") file = file + DIR_SEPARATOR; } KrLinecountingProcess proc; proc << delCmd << getPath(arcFile->url()) << localeEncodedString(file); infoMessage(i18n("Deleting %1...", url.fileName())); SET_KRCODEC proc.start(); RESET_KRCODEC proc.waitForFinished(); if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(proc.exitCode())) { error(ERR_CANNOT_WRITE, getPath(url) + "\n\n" + proc.getErrorMsg()); return; } // force a refresh of archive information initDirDict(url, true); finished(); } void kio_krarcProtocol::stat(const QUrl &url) { KRFUNC; KRDEBUG(getPath(url)); if (!setArcFile(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (newArchiveURL && !initDirDict(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (listCmd.isEmpty()) { error(ERR_UNSUPPORTED_ACTION, i18n("Accessing files is not supported with %1 archives", arcType)); return; } QString path = getPath(url, QUrl::StripTrailingSlash); QUrl newUrl = url; // but treat the archive itself as the archive root if (path == getPath(arcFile->url(), QUrl::StripTrailingSlash)) { newUrl.setPath(path + DIR_SEPARATOR); path = getPath(newUrl); } // we might be stating a real file if (QFileInfo(path).exists()) { QT_STATBUF buff; QT_STAT(path.toLocal8Bit(), &buff); QString mime; QMimeDatabase db; QMimeType result = db.mimeTypeForFile(path); if (result.isValid()) mime = result.name(); statEntry(KFileItem(QUrl::fromLocalFile(path), mime, buff.st_mode).entry()); finished(); return; } UDSEntry* entry = findFileEntry(newUrl); if (entry) { statEntry(*entry); finished(); } else error(KIO::ERR_DOES_NOT_EXIST, path); } void kio_krarcProtocol::copy(const QUrl &url, const QUrl &dest, int, KIO::JobFlags flags) { KRDEBUG("source: " << url.path() << " dest: " << dest.path()); if (!checkWriteSupport()) return; bool overwrite = !!(flags & KIO::Overwrite); // KDE HACK: opening the password dlg in copy causes error for the COPY, and further problems // that's why encrypted files are not allowed to copy if (!encrypted && dest.isLocalFile()) do { if (url.fileName() != dest.fileName()) break; if (QTextCodec::codecForLocale()->name() != codec->name()) break; //the file exists and we don't want to overwrite if ((!overwrite) && (QFile(getPath(dest)).exists())) { error((int)ERR_FILE_ALREADY_EXIST, QString(QFile::encodeName(getPath(dest)))); return; }; if (!setArcFile(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (newArchiveURL && !initDirDict(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } UDSEntry* entry = findFileEntry(url); if (copyCmd.isEmpty() || !entry) break; QString file = getPath(url).mid(getPath(arcFile->url()).length() + 1); QString destDir = getPath(dest, QUrl::StripTrailingSlash); if (!QDir(destDir).exists()) { int ndx = destDir.lastIndexOf(DIR_SEPARATOR_CHAR); if (ndx != -1) destDir.truncate(ndx + 1); } QDir::setCurrent(destDir); QString escapedFilename = file; if(arcType == "zip") { // left bracket needs to be escaped escapedFilename.replace('[', "[[]"); } KrLinecountingProcess proc; proc << copyCmd << getPath(arcFile->url(), QUrl::StripTrailingSlash) << escapedFilename; if (arcType == "ace" && QFile("/dev/ptmx").exists()) // Don't remove, unace crashes if missing!!! proc.setStandardInputFile("/dev/ptmx"); proc.setOutputChannelMode(KProcess::SeparateChannels); // without this output redirection has no effect infoMessage(i18n("Unpacking %1...", url.fileName())); proc.start(); proc.waitForFinished(); if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(proc.exitCode())) { error(KIO::ERR_CANNOT_WRITE, getPath(dest, QUrl::StripTrailingSlash) + "\n\n" + proc.getErrorMsg()); return; } if (!QFileInfo(getPath(dest, QUrl::StripTrailingSlash)).exists()) { error(KIO::ERR_CANNOT_WRITE, getPath(dest, QUrl::StripTrailingSlash)); return; } processedSize(KFileItem(*entry, url).size()); finished(); QDir::setCurrent(QDir::rootPath()); /* for being able to umount devices after copying*/ return; } while (0); if (encrypted) KRDEBUG("ERROR: " << url.path() << " is encrypted."); if (!dest.isLocalFile()) KRDEBUG("ERROR: " << url.path() << " is not a local file."); // CMD_COPY is no more in KF5 - replaced with 74 value (as stated in kio/src/core/commands_p.h, which could be found in cgit.kde.org/kio.git/tree) error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(mProtocol, 74)); } void kio_krarcProtocol::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags flags) { Q_UNUSED(flags); KRDEBUG("renaming from: " << src.path() << " to: " << dest.path()); KRDEBUG("command: " << arcPath); if (!checkWriteSupport()) { return; } if (renCmd.isEmpty()) { error(KIO::ERR_CANNOT_RENAME, src.fileName()); return; } if (src.fileName() == dest.fileName()) { return; } KrLinecountingProcess proc; proc << renCmd << arcPath << src.path().remove(arcPath + '/') << dest.path().remove(arcPath + '/'); proc.start(); proc.waitForFinished(); if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(proc.exitCode())) { error(KIO::ERR_CANNOT_RENAME, src.fileName()); return; } finished(); } void kio_krarcProtocol::listDir(const QUrl &url) { KRFUNC; KRDEBUG(getPath(url)); if (!setArcFile(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } if (listCmd.isEmpty()) { error(ERR_UNSUPPORTED_ACTION, i18n("Listing folders is not supported for %1 archives", arcType)); return; } QString path = getPath(url); if (path.right(1) != DIR_SEPARATOR) path = path + DIR_SEPARATOR; // it might be a real dir ! if (QFileInfo(path).exists()) { if (QFileInfo(path).isDir()) { QUrl redir; redir.setPath(getPath(url)); redirection(redir); finished(); } else { // maybe it's an archive ! error(ERR_IS_FILE, path); } return; } if (!initDirDict(url)) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } QString arcDir = path.mid(getPath(arcFile->url()).length()); arcDir.truncate(arcDir.lastIndexOf(DIR_SEPARATOR)); if (arcDir.right(1) != DIR_SEPARATOR) arcDir = arcDir + DIR_SEPARATOR; if (dirDict.find(arcDir) == dirDict.end()) { error(ERR_CANNOT_ENTER_DIRECTORY, getPath(url)); return; } UDSEntryList* dirList = dirDict[ arcDir ]; totalSize(dirList->size()); listEntries(*dirList); finished(); } bool kio_krarcProtocol::setArcFile(const QUrl &url) { KRFUNC; KRDEBUG(url.fileName()); QString path = getPath(url); time_t currTime = time(nullptr); archiveChanged = true; newArchiveURL = true; // is the file already set ? if (arcFile && getPath(arcFile->url(), QUrl::StripTrailingSlash) == path.left(getPath(arcFile->url(), QUrl::StripTrailingSlash).length())) { newArchiveURL = false; // Has it changed ? KFileItem* newArcFile = new KFileItem(arcFile->url(), QString(), arcFile->mode()); if (metaData("Charset") != currentCharset || !newArcFile->cmp(*arcFile)) { currentCharset = metaData("Charset"); codec = QTextCodec::codecForName(currentCharset.toLatin1()); if (codec == nullptr) codec = QTextCodec::codecForMib(4 /* latin-1 */); delete arcFile; password.clear(); extArcReady = false; arcFile = newArcFile; } else { // same old file delete newArcFile; archiveChanged = false; if (encrypted && password.isNull()) initArcParameters(); } } else { // it's a new file... extArcReady = false; // new archive file means new dirDict, too dirDict.clear(); if (arcFile) { delete arcFile; password.clear(); arcFile = nullptr; } QString newPath = path; if (newPath.right(1) != DIR_SEPARATOR) newPath = newPath + DIR_SEPARATOR; for (int pos = 0; pos >= 0; pos = newPath.indexOf(DIR_SEPARATOR, pos + 1)) { QFileInfo qfi(newPath.left(pos)); if (qfi.exists() && !qfi.isDir()) { QT_STATBUF stat_p; QT_LSTAT(newPath.left(pos).toLocal8Bit(), &stat_p); arcFile = new KFileItem(QUrl::fromLocalFile(newPath.left(pos)), QString(), stat_p.st_mode); break; } } if (!arcFile) { // KRDEBUG("ERROR: " << path << " does not exist."); error(ERR_DOES_NOT_EXIST, path); return false; // file not found } currentCharset = metaData("Charset"); codec = QTextCodec::codecForName(currentCharset.toLatin1()); if (codec == nullptr) codec = QTextCodec::codecForMib(4 /* latin-1 */); } /* FIX: file change can only be detected if the timestamp between the two consequent changes is more than 1s. If the archive is continuously changing (check: move files inside the archive), krarc may erroneously think, that the archive file is unchanged, because the timestamp is the same as the previous one. This situation can only occur if the modification time equals with the current time. While this condition is true, we can say, that the archive is changing, so content reread is always necessary during that period. */ if (archiveChanging) archiveChanged = true; archiveChanging = (currTime == (time_t)arcFile->time(KFileItem::ModificationTime).toTime_t()); arcPath = getPath(arcFile->url(), QUrl::StripTrailingSlash); arcType = detectArchive(encrypted, arcPath); if (arcType == "tbz") arcType = "bzip2"; else if (arcType == "tgz") arcType = "gzip"; else if (arcType == "tlz") arcType = "lzma"; else if (arcType == "txz") arcType = "xz"; if (arcType.isEmpty()) { arcType = arcFile->mimetype(); arcType = getShortTypeFromMime(arcType); if (arcType == "jar") arcType = "zip"; } return initArcParameters(); } bool kio_krarcProtocol::initDirDict(const QUrl &url, bool forced) { KRFUNC; KRDEBUG(getPath(url)); // set the archive location //if( !setArcFile(getPath(url)) ) return false; // no need to rescan the archive if it's not changed // KRDEBUG("achiveChanged: " << archiveChanged << " forced: " << forced); if (!archiveChanged && !forced) { // KRDEBUG("doing nothing."); return true; } extArcReady = false; if (!setArcFile(url)) return false; /* if the archive was changed refresh the file information */ // write the temp file KrLinecountingProcess proc; QTemporaryFile temp; // parse the temp file if (!temp.open()) { error(ERR_CANNOT_READ, temp.fileName()); return false; } if (arcType != "bzip2" && arcType != "lzma" && arcType != "xz") { if (arcType == "rpm") { proc << listCmd << arcPath; proc.setStandardOutputFile(temp.fileName()); } else { proc << listCmd << getPath(arcFile->url(), QUrl::StripTrailingSlash); proc.setStandardOutputFile(temp.fileName()); } if (arcType == "ace" && QFile("/dev/ptmx").exists()) // Don't remove, unace crashes if missing!!! proc.setStandardInputFile("/dev/ptmx"); proc.setOutputChannelMode(KProcess::SeparateChannels); // without this output redirection has no effect proc.start(); proc.waitForFinished(); if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(proc.exitCode())) return false; } // clear the dir dictionary QHashIterator< QString, KIO::UDSEntryList *> lit(dirDict); while (lit.hasNext()) delete lit.next().value(); dirDict.clear(); // add the "/" directory auto* root = new UDSEntryList(); dirDict.insert(DIR_SEPARATOR, root); // and the "/" UDSEntry UDSEntry entry; entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_NAME, "."); mode_t mode = parsePermString("drwxr-xr-x"); entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_FILE_TYPE, mode & S_IFMT); // keep file type only entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_ACCESS, mode & 07777); // keep permissions only root->append(entry); if (arcType == "bzip2" || arcType == "lzma" || arcType == "xz") abort(); char buf[1000]; QString line; int lineNo = 0; bool invalidLine = false; // the rar list is started with a ------ line. if (arcType == "rar" || arcType == "arj" || arcType == "lha" || arcType == "7z") { while (temp.readLine(buf, 1000) != -1) { line = decodeString(buf); if (line.startsWith(QLatin1String("----------"))) break; } } while (temp.readLine(buf, 1000) != -1) { line = decodeString(buf); if (arcType == "rar") { // the rar list is ended with a ------ line. if (line.startsWith(QLatin1String("----------"))) { invalidLine = !invalidLine; break; } if (invalidLine) continue; else { if (line[0] == '*') // encrypted archives starts with '*' line[0] = ' '; } } if (arcType == "ace") { // the ace list begins with a number. if (!line[0].isDigit()) continue; } if (arcType == "arj") { // the arj list is ended with a ------ line. if (line.startsWith(QLatin1String("----------"))) { invalidLine = !invalidLine; continue; } if (invalidLine) continue; else { temp.readLine(buf, 1000); line = line + decodeString(buf); temp.readLine(buf, 1000); line = line + decodeString(buf); temp.readLine(buf, 1000); line = line + decodeString(buf); } } if (arcType == "lha" || arcType == "7z") { // the arj list is ended with a ------ line. if (line.startsWith(QLatin1String("----------"))) break; } parseLine(lineNo++, line.trimmed()); } // close and delete our file temp.close(); archiveChanged = false; // KRDEBUG("done."); return true; } QString kio_krarcProtocol::findArcDirectory(const QUrl &url) { KRFUNC; KRDEBUG(url.fileName()); QString path = getPath(url); if (path.right(1) == DIR_SEPARATOR) path.truncate(path.length() - 1); if (!initDirDict(url)) { return QString(); } QString arcDir = path.mid(getPath(arcFile->url()).length()); arcDir.truncate(arcDir.lastIndexOf(DIR_SEPARATOR)); if (arcDir.right(1) != DIR_SEPARATOR) arcDir = arcDir + DIR_SEPARATOR; return arcDir; } UDSEntry* kio_krarcProtocol::findFileEntry(const QUrl &url) { KRFUNC; QString arcDir = findArcDirectory(url); if (arcDir.isEmpty()) return nullptr; QHash::iterator itef = dirDict.find(arcDir); if (itef == dirDict.end()) return nullptr; UDSEntryList* dirList = itef.value(); QString name = getPath(url); if (getPath(arcFile->url(), QUrl::StripTrailingSlash) == getPath(url, QUrl::StripTrailingSlash)) name = '.'; // the '/' case else { if (name.right(1) == DIR_SEPARATOR) name.truncate(name.length() - 1); name = name.mid(name.lastIndexOf(DIR_SEPARATOR) + 1); } UDSEntryList::iterator entry; for (entry = dirList->begin(); entry != dirList->end(); ++entry) { if ((entry->contains(KIO::UDSEntry::UDS_NAME)) && (entry->stringValue(KIO::UDSEntry::UDS_NAME) == name)) return &(*entry); } return nullptr; } QString kio_krarcProtocol::nextWord(QString &s, char d) { // Note: KRFUNC was not used here in order to avoid filling the log with too much information s = s.trimmed(); int j = s.indexOf(d, 0); QString temp = s.left(j); // find the leftmost word. s.remove(0, j); return temp; } mode_t kio_krarcProtocol::parsePermString(QString perm) { KRFUNC; mode_t mode = 0; // file type if (perm[0] == 'd') mode |= S_IFDIR; #ifndef Q_OS_WIN if (perm[0] == 'l') mode |= S_IFLNK; #endif if (perm[0] == '-') mode |= S_IFREG; // owner permissions if (perm[1] != '-') mode |= S_IRUSR; if (perm[2] != '-') mode |= S_IWUSR; if (perm[3] != '-') mode |= S_IXUSR; #ifndef Q_OS_WIN // group permissions if (perm[4] != '-') mode |= S_IRGRP; if (perm[5] != '-') mode |= S_IWGRP; if (perm[6] != '-') mode |= S_IXGRP; // other permissions if (perm[7] != '-') mode |= S_IROTH; if (perm[8] != '-') mode |= S_IWOTH; if (perm[9] != '-') mode |= S_IXOTH; #endif return mode; } UDSEntryList* kio_krarcProtocol::addNewDir(const QString& path) { KRFUNC; UDSEntryList* dir; // check if the current dir exists QHash::iterator itef = dirDict.find(path); if (itef != dirDict.end()) return itef.value(); // set dir to the parent dir dir = addNewDir(path.left(path.lastIndexOf(DIR_SEPARATOR, -2) + 1)); // add a new entry in the parent dir QString name = path.mid(path.lastIndexOf(DIR_SEPARATOR, -2) + 1); name = name.left(name.length() - 1); if (name == "." || name == "..") { // entries with these names wouldn't be displayed // don't translate since this is an internal error QString err = QString("Cannot handle path: ") + path; // KRDEBUG("ERROR: " << err); error(KIO::ERR_INTERNAL, err); exit(); } UDSEntry entry; entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_NAME, name); mode_t mode = parsePermString("drwxr-xr-x"); entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_FILE_TYPE, mode & S_IFMT); // keep file type only entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_ACCESS, mode & 07777); // keep permissions only entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_SIZE, 0); entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_MODIFICATION_TIME, arcFile->time(KFileItem::ModificationTime).toTime_t()); dir->append(entry); // create a new directory entry and add it.. dir = new UDSEntryList(); dirDict.insert(path, dir); return dir; } void kio_krarcProtocol::parseLine(int lineNo, QString line) { KRFUNC; UDSEntryList* dir; UDSEntry entry; QString owner; QString group; QString symlinkDest; QString perm; mode_t mode = 0666; size_t size = 0; time_t time = ::time(nullptr); QString fullName; if (arcType == "zip") { // permissions perm = nextWord(line); // ignore the next 2 fields nextWord(line); nextWord(line); // size size = nextWord(line).toLong(); // ignore the next 2 fields nextWord(line);nextWord(line); // date & time QString d = nextWord(line); QDate qdate(d.mid(0, 4).toInt(), d.mid(4, 2).toInt(), d.mid(6, 2).toInt()); QTime qtime(d.mid(9, 2).toInt(), d.mid(11, 2).toInt(), d.mid(13, 2).toInt()); time = QDateTime(qdate, qtime).toTime_t(); // full name fullName = nextWord(line, '\n'); if (perm.length() != 10) perm = (perm.at(0) == 'd' || fullName.endsWith(DIR_SEPARATOR)) ? "drwxr-xr-x" : "-rw-r--r--" ; mode = parsePermString(perm); } if (arcType == "rar") { // permissions perm = nextWord(line); // size size = nextWord(line).toLong(); // ignore the next 2 fields : packed size and compression ration nextWord(line); nextWord(line); // date & time QString d = nextWord(line); int year = 1900 + d.mid(6, 2).toInt(); if (year < 1930) year += 100; QDate qdate(year, d.mid(3, 2).toInt(), d.mid(0, 2).toInt()); QString t = nextWord(line); QTime qtime(t.mid(0, 2).toInt(), t.mid(3, 2).toInt(), 0); time = QDateTime(qdate, qtime).toTime_t(); // checksum : ignored nextWord(line); // full name fullName = nextWord(line, '\n'); if (perm.length() == 7) { // windows rar permission format bool isDir = (perm.at(1).toLower() == 'd'); bool isReadOnly = (perm.at(2).toLower() == 'r'); perm = isDir ? "drwxr-xr-x" : "-rw-r--r--"; if (isReadOnly) perm[ 2 ] = '-'; } if (perm.length() != 10) perm = (perm.at(0) == 'd') ? "drwxr-xr-x" : "-rw-r--r--" ; mode = parsePermString(perm); } if (arcType == "arj") { nextWord(line); // full name fullName = nextWord(line, '\n'); // ignore the next 2 fields nextWord(line); nextWord(line); // size size = nextWord(line).toLong(); // ignore the next 2 fields nextWord(line); nextWord(line); // date & time QString d = nextWord(line); int year = 1900 + d.mid(0, 2).toInt(); if (year < 1930) year += 100; QDate qdate(year, d.mid(3, 2).toInt(), d.mid(6, 2).toInt()); QString t = nextWord(line); QTime qtime(t.mid(0, 2).toInt(), t.mid(3, 2).toInt(), 0); time = QDateTime(qdate, qtime).toTime_t(); // permissions perm = nextWord(line); if (perm.length() != 10) perm = (perm.at(0) == 'd') ? "drwxr-xr-x" : "-rw-r--r--" ; mode = parsePermString(perm); } if (arcType == "rpm") { // full name fullName = nextWord(line); // size size = nextWord(line).toULong(); // date & time time = nextWord(line).toULong(); // next field is md5sum, ignore it nextWord(line); // permissions mode = nextWord(line).toULong(nullptr, 8); // Owner & Group owner = nextWord(line); group = nextWord(line); // symlink destination #ifndef Q_OS_WIN if (S_ISLNK(mode)) { // ignore the next 3 fields nextWord(line); nextWord(line); nextWord(line); symlinkDest = nextWord(line); } #endif } if (arcType == "gzip") { if (!lineNo) return; //ignore the first line // first field is uncompressed size - ignore it nextWord(line); // size size = nextWord(line).toULong(); // ignore the next field nextWord(line); // full name fullName = nextWord(line); fullName = fullName.mid(fullName.lastIndexOf(DIR_SEPARATOR) + 1); } if (arcType == "lzma") { fullName = arcFile->name(); if (fullName.endsWith(QLatin1String("lzma"))) { fullName.truncate(fullName.length() - 5); } mode = arcFile->mode(); size = arcFile->size(); } if (arcType == "xz") { fullName = arcFile->name(); if (fullName.endsWith(QLatin1String("xz"))) { fullName.truncate(fullName.length() - 3); } mode = arcFile->mode(); size = arcFile->size(); } if (arcType == "bzip2") { // There is no way to list bzip2 files, so we take our information from // the archive itself... fullName = arcFile->name(); if (fullName.endsWith(QLatin1String("bz2"))) { fullName.truncate(fullName.length() - 4); } mode = arcFile->mode(); size = arcFile->size(); } if (arcType == "lha") { // permissions perm = nextWord(line); if (perm.length() != 10) perm = (perm.at(0) == 'd') ? "drwxr-xr-x" : "-rw-r--r--" ; mode = parsePermString(perm); // ignore the next field nextWord(line); // size size = nextWord(line).toLong(); // ignore the next field nextWord(line); // date & time int month = (QString("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec").split(',')).indexOf(nextWord(line)) + 1; int day = nextWord(line).toInt(); int year = QDate::currentDate().year(); QString third = nextWord(line); QTime qtime; if (third.contains(":")) qtime = QTime::fromString(third); else year = third.toInt(); QDate qdate(year, month, day); time = QDateTime(qdate, qtime).toTime_t(); // full name fullName = nextWord(line, '\n'); } if (arcType == "ace") { // date & time QString d = nextWord(line); int year = 1900 + d.mid(6, 2).toInt(); if (year < 1930) year += 100; QDate qdate(year, d.mid(3, 2).toInt(), d.mid(0, 2).toInt()); QString t = nextWord(line); QTime qtime(t.mid(0, 2).toInt(), t.mid(3, 2).toInt(), 0); time = QDateTime(qdate, qtime).toTime_t(); // ignore the next field nextWord(line); // size size = nextWord(line).toLong(); // ignore the next field nextWord(line); // full name fullName = nextWord(line, '\n'); if (fullName[ 0 ] == '*') // encrypted archives starts with '*' fullName = fullName.mid(1); } if (arcType == "deb") { // permissions perm = nextWord(line); mode = parsePermString(perm); // Owner & Group owner = nextWord(line, DIR_SEPARATOR_CHAR); group = nextWord(line).mid(1); // size size = nextWord(line).toLong(); // date & time QString d = nextWord(line); QDate qdate(d.mid(0, 4).toInt(), d.mid(5, 2).toInt(), d.mid(8, 2).toInt()); QString t = nextWord(line); QTime qtime(t.mid(0, 2).toInt(), t.mid(3, 2).toInt(), 0); time = QDateTime(qdate, qtime).toTime_t(); // full name fullName = nextWord(line, '\n').mid(1); //if ( fullName.right( 1 ) == "/" ) return; if (fullName.contains("->")) { symlinkDest = fullName.mid(fullName.indexOf("->") + 2); fullName = fullName.left(fullName.indexOf("->") - 1); } } if (arcType == "7z") { // date & time QString d = nextWord(line); QDate qdate(d.mid(0, 4).toInt(), d.mid(5, 2).toInt(), d.mid(8, 2).toInt()); QString t = nextWord(line); QTime qtime(t.mid(0, 2).toInt(), t.mid(3, 2).toInt(), t.mid(6, 2).toInt()); time = QDateTime(qdate, qtime).toTime_t(); // permissions perm = nextWord(line); bool isDir = (perm.at(0).toLower() == 'd'); bool isReadOnly = (perm.at(1).toLower() == 'r'); perm = isDir ? "drwxr-xr-x" : "-rw-r--r--"; if (isReadOnly) perm[ 2 ] = '-'; mode = parsePermString(perm); // size size = nextWord(line).toLong(); // ignore the next 15 characters line = line.mid(15); // full name fullName = nextWord(line, '\n'); } if (fullName.right(1) == DIR_SEPARATOR) fullName = fullName.left(fullName.length() - 1); if (!fullName.startsWith(DIR_SEPARATOR)) fullName = DIR_SEPARATOR + fullName; QString path = fullName.left(fullName.lastIndexOf(DIR_SEPARATOR) + 1); // set/create the directory UDSEntryList QHash::iterator itef = dirDict.find(path); if (itef == dirDict.end()) dir = addNewDir(path); else dir = itef.value(); QString name = fullName.mid(fullName.lastIndexOf(DIR_SEPARATOR) + 1); // file name entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_NAME, name); // file type entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_FILE_TYPE, mode & S_IFMT); // keep file type only // file permissions entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_ACCESS, mode & 07777); // keep permissions only // file size entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_SIZE, size); // modification time entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_MODIFICATION_TIME, time); // link destination if (!symlinkDest.isEmpty()) { entry.UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_LINK_DEST, symlinkDest); } if (S_ISDIR(mode)) { fullName = fullName + DIR_SEPARATOR; if (dirDict.find(fullName) == dirDict.end()) dirDict.insert(fullName, new UDSEntryList()); else { // try to overwrite an existing entry UDSEntryList::iterator entryIt; for (entryIt = dir->begin(); entryIt != dir->end(); ++entryIt) { if (entryIt->contains(KIO::UDSEntry::UDS_NAME) && entryIt->stringValue(KIO::UDSEntry::UDS_NAME) == name) { entryIt->UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_MODIFICATION_TIME, time); entryIt->UDS_ENTRY_INSERT(KIO::UDSEntry::UDS_ACCESS, mode); return; } } return; // there is already an entry for this directory } } // multi volume archives can add a file twice, use only one UDSEntryList::iterator dirEntryIt; for (dirEntryIt = dir->begin(); dirEntryIt != dir->end(); ++dirEntryIt) if (dirEntryIt->contains(KIO::UDSEntry::UDS_NAME) && dirEntryIt->stringValue(KIO::UDSEntry::UDS_NAME) == name) return; dir->append(entry); } bool kio_krarcProtocol::initArcParameters() { KRFUNC; KRDEBUG("arcType: " << arcType); noencoding = false; cmd.clear(); listCmd = QStringList(); getCmd = QStringList(); copyCmd = QStringList(); delCmd = QStringList(); putCmd = QStringList(); renCmd = QStringList(); if (arcType == "zip") { noencoding = true; cmd = fullPathName("unzip"); listCmd << fullPathName("unzip") << "-ZTs-z-t-h"; getCmd << fullPathName("unzip") << "-p"; copyCmd << fullPathName("unzip") << "-jo"; if (QStandardPaths::findExecutable(QStringLiteral("zip")).isEmpty()) { delCmd = QStringList(); putCmd = QStringList(); } else { delCmd << fullPathName("zip") << "-d"; putCmd << fullPathName("zip") << "-ry"; } QString a7zExecutable = find7zExecutable(); if (!a7zExecutable.isEmpty()) { renCmd << a7zExecutable << "rn"; } if (!getPassword().isEmpty()) { getCmd << "-P" << password; copyCmd << "-P" << password; putCmd << "-P" << password; } } else if (arcType == "rar") { noencoding = true; if (QStandardPaths::findExecutable(QStringLiteral("rar")).isEmpty()) { cmd = fullPathName("unrar"); listCmd << fullPathName("unrar") << "-c-" << "-v" << "v"; getCmd << fullPathName("unrar") << "p" << "-ierr" << "-idp" << "-c-" << "-y"; copyCmd << fullPathName("unrar") << "e" << "-y"; delCmd = QStringList(); putCmd = QStringList(); } else { cmd = fullPathName("rar"); listCmd << fullPathName("rar") << "-c-" << "-v" << "v"; getCmd << fullPathName("rar") << "p" << "-ierr" << "-idp" << "-c-" << "-y"; copyCmd << fullPathName("rar") << "e" << "-y"; delCmd << fullPathName("rar") << "d"; putCmd << fullPathName("rar") << "-r" << "a"; } if (!getPassword().isEmpty()) { getCmd << QString("-p%1").arg(password); listCmd << QString("-p%1").arg(password); copyCmd << QString("-p%1").arg(password); if (!putCmd.isEmpty()) { putCmd << QString("-p%1").arg(password); delCmd << QString("-p%1").arg(password); } } } else if (arcType == "rpm") { cmd = fullPathName("rpm"); listCmd << fullPathName("rpm") << "--dump" << "-lpq"; getCmd << fullPathName("cpio") << "--force-local" << "--no-absolute-filenames" << "-iuvdF"; delCmd = QStringList(); putCmd = QStringList(); copyCmd = QStringList(); } else if (arcType == "gzip") { cmd = fullPathName("gzip"); listCmd << fullPathName("gzip") << "-l"; getCmd << fullPathName("gzip") << "-dc"; copyCmd = QStringList(); delCmd = QStringList(); putCmd = QStringList(); } else if (arcType == "bzip2") { cmd = fullPathName("bzip2"); listCmd << fullPathName("bzip2"); getCmd << fullPathName("bzip2") << "-dc"; copyCmd = QStringList(); delCmd = QStringList(); putCmd = QStringList(); } else if (arcType == "lzma") { cmd = fullPathName("lzma"); listCmd << fullPathName("lzma"); getCmd << fullPathName("lzma") << "-dc"; copyCmd = QStringList(); delCmd = QStringList(); putCmd = QStringList(); } else if (arcType == "xz") { cmd = fullPathName("xz"); listCmd << fullPathName("xz"); getCmd << fullPathName("xz") << "-dc"; copyCmd = QStringList(); delCmd = QStringList(); putCmd = QStringList(); } else if (arcType == "arj") { cmd = fullPathName("arj"); listCmd << fullPathName("arj") << "v" << "-y" << "-v"; getCmd << fullPathName("arj") << "-jyov" << "-v" << "e"; copyCmd << fullPathName("arj") << "-jyov" << "-v" << "e"; delCmd << fullPathName("arj") << "d"; putCmd << fullPathName("arj") << "-r" << "a"; if (!getPassword().isEmpty()) { getCmd << QString("-g%1").arg(password); copyCmd << QString("-g%1").arg(password); putCmd << QString("-g%1").arg(password); } } else if (arcType == "lha") { cmd = fullPathName("lha"); listCmd << fullPathName("lha") << "l"; getCmd << fullPathName("lha") << "pq"; copyCmd << fullPathName("lha") << "eif"; delCmd << fullPathName("lha") << "d"; putCmd << fullPathName("lha") << "a"; } else if (arcType == "ace") { cmd = fullPathName("unace"); listCmd << fullPathName("unace") << "v"; getCmd << fullPathName("unace") << "e" << "-o"; copyCmd << fullPathName("unace") << "e" << "-o"; delCmd = QStringList(); putCmd = QStringList(); if (!getPassword().isEmpty()) { getCmd << QString("-p%1").arg(password); copyCmd << QString("-p%1").arg(password); } } else if (arcType == "deb") { cmd = fullPathName("dpkg"); listCmd << fullPathName("dpkg") << "-c"; getCmd << fullPathName("tar") << "xvf"; copyCmd = QStringList(); delCmd = QStringList(); putCmd = QStringList(); } else if (arcType == "7z") { noencoding = true; cmd = find7zExecutable(); if (cmd.isEmpty()) { return false; } listCmd << cmd << "l" << "-y"; getCmd << cmd << "e" << "-y"; copyCmd << cmd << "e" << "-y"; delCmd << cmd << "d" << "-y"; putCmd << cmd << "a" << "-y"; renCmd << cmd << "rn"; if (!getPassword().isEmpty()) { getCmd << QString("-p%1").arg(password); listCmd << QString("-p%1").arg(password); copyCmd << QString("-p%1").arg(password); if (!putCmd.isEmpty()) { putCmd << QString("-p%1").arg(password); delCmd << QString("-p%1").arg(password); } } } // checking if it's an absolute path #ifdef Q_OS_WIN if (cmd.length() > 2 && cmd[ 0 ].isLetter() && cmd[ 1 ] == ':') return true; #else if (cmd.startsWith(DIR_SEPARATOR)) return true; #endif if (QStandardPaths::findExecutable(cmd).isEmpty()) { error(KIO::ERR_CANNOT_LAUNCH_PROCESS, cmd + i18n("\nMake sure that the %1 binary is installed properly on your system.", cmd)); KRDEBUG("Failed to find cmd: " << cmd); return false; } return true; } bool kio_krarcProtocol::checkStatus(int exitCode) { KRFUNC; KRDEBUG(exitCode); return KrArcBaseManager::checkStatus(arcType, exitCode); } void kio_krarcProtocol::checkIf7zIsEncrypted(bool &encrypted, QString fileName) { // Reminder: If that function is modified, it's important to research if the // changes must also be applied to `KrArcHandler::checkIf7zIsEncrypted()` KRFUNC; if (encryptedArchPath == fileName) encrypted = true; else { // we try to find whether the 7z archive is encrypted // this is hard as the headers are also compressed QString a7zExecutable = find7zExecutable(); if (a7zExecutable.isEmpty()) { return; } lastData = encryptedArchPath = ""; KrLinecountingProcess proc; proc << a7zExecutable << "-y" << "t" << fileName; connect(&proc, &KrLinecountingProcess::newOutputData, this, &kio_krarcProtocol::check7zOutputForPassword); proc.start(); proc.waitForFinished(); encrypted = this->encrypted; if (encrypted) encryptedArchPath = fileName; } } void kio_krarcProtocol::check7zOutputForPassword(KProcess * proc, QByteArray & buf) { // Reminder: If that function is modified, it's important to research if the // changes must also be applied to `Kr7zEncryptionChecker::receivedOutput()` KRFUNC; QString data = QString(buf); QString checkable = lastData + data; QStringList lines = checkable.split('\n'); lastData = lines[ lines.count() - 1 ]; for (int i = 0; i != lines.count(); i++) { QString line = lines[ i ].trimmed().toLower(); int ndx = line.indexOf("testing"); if (ndx >= 0) line.truncate(ndx); if (line.isEmpty()) continue; if (line.contains("password") && line.contains("enter")) { KRDEBUG("Encrypted 7z archive found!"); encrypted = true; proc->kill(); return; } } } void kio_krarcProtocol::invalidatePassword() { KRFUNC; KRDEBUG(getPath(arcFile->url(), QUrl::StripTrailingSlash) + DIR_SEPARATOR); if (!encrypted) return; KIO::AuthInfo authInfo; authInfo.caption = i18n("Krarc Password Dialog"); authInfo.username = "archive"; authInfo.readOnly = true; authInfo.keepPassword = true; authInfo.verifyPath = true; QString fileName = getPath(arcFile->url(), QUrl::StripTrailingSlash); authInfo.url = QUrl::fromLocalFile(ROOT_DIR); authInfo.url.setHost(fileName /*.replace('/','_')*/); authInfo.url.setScheme("krarc"); password.clear(); cacheAuthentication(authInfo); } QString kio_krarcProtocol::getPassword() { KRFUNC; KRDEBUG("Encrypted: " << encrypted); if (!password.isNull()) return password; if (!encrypted) return (password = ""); KIO::AuthInfo authInfo; authInfo.caption = i18n("Krarc Password Dialog"); authInfo.username = "archive"; authInfo.readOnly = true; authInfo.keepPassword = true; authInfo.verifyPath = true; QString fileName = getPath(arcFile->url(), QUrl::StripTrailingSlash); authInfo.url = QUrl::fromLocalFile(ROOT_DIR); authInfo.url.setHost(fileName /*.replace('/','_')*/); authInfo.url.setScheme("krarc"); if (checkCachedAuthentication(authInfo) && !authInfo.password.isNull()) { KRDEBUG(authInfo.password); return (password = authInfo.password); } authInfo.password.clear(); #if KIO_VERSION_MINOR >= 24 int errCode = openPasswordDialogV2(authInfo, i18n("Accessing the file requires a password.")); if (!errCode && !authInfo.password.isNull()) { #else if (openPasswordDialog(authInfo, i18n("Accessing the file requires a password.")) && !authInfo.password.isNull()) { #endif KRDEBUG(authInfo.password); return (password = authInfo.password); #if KIO_VERSION_MINOR >= 24 } else { error(errCode, QString()); #endif } KRDEBUG(password); return password; } QString kio_krarcProtocol::localeEncodedString(QString str) { // Note: KRFUNC was not used here in order to avoid filling the log with too much information if (noencoding) return str; QByteArray array = codec->fromUnicode(str); // encoding the byte array to QString, mapping 0x0000-0x00FF to 0xE000-0xE0FF // see KrArcCodec for more explanation int size = array.size(); QString result; const char *data = array.data(); for (int i = 0; i != size; i++) { unsigned short ch = (((int)data[ i ]) & 0xFF) + 0xE000; // user defined character result.append(QChar(ch)); } return result; } QByteArray kio_krarcProtocol::encodeString(const QString& str) { // Note: KRFUNC was not used here in order to avoid filling the log with too much information if (noencoding) return QTextCodec::codecForLocale()->fromUnicode(str); return codec->fromUnicode(str); } QString kio_krarcProtocol::decodeString(char * buf) { // Note: KRFUNC was not used here in order to avoid filling the log with too much information if (noencoding) return QTextCodec::codecForLocale()->toUnicode(buf); return codec->toUnicode(buf); } QString kio_krarcProtocol::getPath(const QUrl &url, QUrl::FormattingOptions options) { // Note: KRFUNC was not used here in order to avoid filling the log with too much information QString path = url.adjusted(options).path(); REPLACE_DIR_SEP2(path); #ifdef Q_OS_WIN if (path.startsWith(DIR_SEPARATOR)) { int p = 1; while (p < path.length() && path[ p ] == DIR_SEPARATOR_CHAR) p++; /* /C:/Folder */ if (p + 2 <= path.length() && path[ p ].isLetter() && path[ p + 1 ] == ':') { path = path.mid(p); } } #endif return path; } #endif // KRARC_ENABLED diff --git a/krusader/Dialogs/krmaskchoice.cpp b/krusader/Dialogs/krmaskchoice.cpp index 3a810eb1..442e5057 100644 --- a/krusader/Dialogs/krmaskchoice.cpp +++ b/krusader/Dialogs/krmaskchoice.cpp @@ -1,151 +1,152 @@ /***************************************************************************** * Copyright (C) 2000 Shie Erlich * * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krmaskchoice.h" // QtCore #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include "../GUI/krlistwidget.h" +#include "../compat.h" /** * Constructs a KrMaskChoice which is a child of 'parent', with the * name 'name' and widget flags set to 'f' * * The dialog will by default be modeless, unless you set 'modal' to * TRUE to construct a modal dialog. */ KrMaskChoice::KrMaskChoice(QWidget* parent) : QDialog(parent) { setModal(true); resize(401, 314); setWindowTitle(i18n("Choose Files")); auto* MainLayout = new QVBoxLayout(this); auto* HeaderLayout = new QHBoxLayout(); MainLayout->addLayout(HeaderLayout); PixmapLabel1 = new QLabel(this); PixmapLabel1->setScaledContents(true); PixmapLabel1->setMaximumSize(QSize(31, 31)); HeaderLayout->addWidget(PixmapLabel1); label = new QLabel(this); label->setText(i18n("Select the following files:")); HeaderLayout->addWidget(label); selection = new KComboBox(this); selection->setEditable(true); selection->setInsertPolicy(QComboBox::InsertAtTop); selection->setAutoCompletion(true); MainLayout->addWidget(selection); auto* GroupBox1 = new QGroupBox(this); GroupBox1->setTitle(i18n("Predefined Selections")); MainLayout->addWidget(GroupBox1); auto* gbLayout = new QHBoxLayout(GroupBox1); preSelections = new KrListWidget(GroupBox1); preSelections->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); preSelections->setWhatsThis(i18n("A predefined selection is a file-mask which you often use.\nSome examples are: \"*.c, *.h\", \"*.c, *.o\", etc.\nYou can add these masks to the list by typing them and pressing the Add button.\nDelete removes a predefined selection and Clear removes all of them.\nNotice that the line in which you edit the mask has its own history, you can scroll it, if needed.")); gbLayout->addWidget(preSelections); auto* vbox = new QVBoxLayout(); gbLayout->addLayout(vbox); PushButton7 = new QPushButton(GroupBox1); PushButton7->setText(i18n("Add")); PushButton7->setToolTip(i18n("Adds the selection in the line-edit to the list")); vbox->addWidget(PushButton7); PushButton7_2 = new QPushButton(GroupBox1); PushButton7_2->setText(i18n("Delete")); PushButton7_2->setToolTip(i18n("Delete the marked selection from the list")); vbox->addWidget(PushButton7_2); PushButton7_3 = new QPushButton(GroupBox1); PushButton7_3->setText(i18n("Clear")); PushButton7_3->setToolTip(i18n("Clears the entire list of selections")); vbox->addWidget(PushButton7_3); vbox->addItem(new QSpacerItem(5, 5, QSizePolicy::Fixed, QSizePolicy::Expanding)); QDialogButtonBox* ButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); MainLayout->addWidget(ButtonBox); // signals and slots connections connect(ButtonBox, &QDialogButtonBox::rejected, this, &KrMaskChoice::reject); connect(ButtonBox, &QDialogButtonBox::accepted, this, &KrMaskChoice::accept); connect(PushButton7, &QPushButton::clicked, this, &KrMaskChoice::addSelection); connect(PushButton7_2, &QPushButton::clicked, this, &KrMaskChoice::deleteSelection); connect(PushButton7_3, &QPushButton::clicked, this, &KrMaskChoice::clearSelections); - connect(selection, QOverload::of(&KComboBox::activated), selection, &KComboBox::setEditText); + connect(selection, QOverload::of(&KComboBox::QCOMBOBOX_ACTIVATED), selection, &KComboBox::setEditText); connect(selection->lineEdit(), &QLineEdit::returnPressed, this, &KrMaskChoice::accept); connect(preSelections, &KrListWidget::currentItemChanged, this, &KrMaskChoice::currentItemChanged); connect(preSelections, &KrListWidget::itemActivated, this, &KrMaskChoice::acceptFromList); } /* * Destroys the object and frees any allocated resources */ KrMaskChoice::~KrMaskChoice() { // no need to delete child widgets, Qt does it all for us } void KrMaskChoice::addSelection() { qWarning("KrMaskChoice::addSelection(): Not implemented yet!"); } void KrMaskChoice::clearSelections() { qWarning("KrMaskChoice::clearSelections(): Not implemented yet!"); } void KrMaskChoice::deleteSelection() { qWarning("KrMaskChoice::deleteSelection(): Not implemented yet!"); } void KrMaskChoice::acceptFromList(QListWidgetItem *) { qWarning("KrMaskChoice::acceptFromList(QListWidgetItem *): Not implemented yet!"); } void KrMaskChoice::currentItemChanged(QListWidgetItem *) { qWarning("KrMaskChoice::currentItemChanged(QListWidgetItem *): Not implemented yet!"); } diff --git a/krusader/Dialogs/newftpgui.cpp b/krusader/Dialogs/newftpgui.cpp index 294936a3..53af81ba 100644 --- a/krusader/Dialogs/newftpgui.cpp +++ b/krusader/Dialogs/newftpgui.cpp @@ -1,203 +1,204 @@ /***************************************************************************** * Copyright (C) 2002 Shie Erlich * * Copyright (C) 2002 Rafi Yanai * * Copyright (C) 2009 Fathi Boudra * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "newftpgui.h" // QtCore #include #include // QtGui #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "../krglobal.h" #include "../icon.h" +#include "../compat.h" #define SIZE_MINIMUM QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed) const QStringList sProtocols = QStringList() << QStringLiteral("ftp") << QStringLiteral("ftps") << QStringLiteral("sftp") << QStringLiteral("fish") << QStringLiteral("nfs") << QStringLiteral("smb") << QStringLiteral("webdav") << QStringLiteral("svn") << QStringLiteral("svn+file") << QStringLiteral("svn+http") << QStringLiteral("svn+https") << QStringLiteral("svn+ssh"); /** * Constructs a newFTPGUI which is a child of 'parent', * with the name 'name' and widget flags set to 'f' */ newFTPGUI::newFTPGUI(QWidget* parent) : QDialog(parent) { setModal(true); setWindowTitle(i18n("New Network Connection")); resize(500, 240); auto *mainLayout = new QVBoxLayout; setLayout(mainLayout); QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred); policy.setHeightForWidth(sizePolicy().hasHeightForWidth()); setSizePolicy(policy); iconLabel = new QLabel(this); iconLabel->setPixmap(Icon("network-wired").pixmap(32)); iconLabel->setSizePolicy(SIZE_MINIMUM); aboutLabel = new QLabel(i18n("About to connect to..."), this); QFont font(aboutLabel->font()); font.setBold(true); aboutLabel->setFont(font); protocolLabel = new QLabel(i18n("Protocol:"), this); hostLabel = new QLabel(i18n("Host:"), this); portLabel = new QLabel(i18n("Port:"), this); prefix = new KComboBox(this); prefix->setObjectName(QString::fromUtf8("protocol")); prefix->setSizePolicy(SIZE_MINIMUM); url = new KrHistoryComboBox(this); url->setMaxCount(50); url->setMinimumContentsLength(10); const QStringList availableProtocols = KProtocolInfo::protocols(); for (const QString& protocol : sProtocols) { if (availableProtocols.contains(protocol)) prefix->addItem(protocol + QStringLiteral("://")); } // load the history and completion list after creating the history combo KConfigGroup group(krConfig, "Private"); QStringList list = group.readEntry("newFTP Completion list", QStringList()); url->completionObject()->setItems(list); list = group.readEntry("newFTP History list", QStringList()); url->setHistoryItems(list); // Select last used protocol const QString lastUsedProtocol = group.readEntry("newFTP Protocol", QString()); if(!lastUsedProtocol.isEmpty()) { prefix->setCurrentItem(lastUsedProtocol); } port = new QSpinBox(this); port->setMaximum(65535); port->setValue(21); port->setSizePolicy(SIZE_MINIMUM); usernameLabel = new QLabel(i18n("Username:"), this); username = new KLineEdit(this); passwordLabel = new QLabel(i18n("Password:"), this); password = new KLineEdit(this); password->setEchoMode(QLineEdit::Password); auto *horizontalLayout = new QHBoxLayout(); horizontalLayout->addWidget(iconLabel); horizontalLayout->addWidget(aboutLabel); auto *gridLayout = new QGridLayout(); gridLayout->addWidget(protocolLabel, 0, 0, 1, 1); gridLayout->addWidget(hostLabel, 0, 1, 1, 1); gridLayout->addWidget(portLabel, 0, 2, 1, 1); gridLayout->addWidget(prefix, 1, 0, 1, 1); gridLayout->addWidget(url, 1, 1, 1, 1); gridLayout->addWidget(port, 1, 2, 1, 1); gridLayout->addWidget(usernameLabel, 2, 0, 1, 1); gridLayout->addWidget(username, 3, 0, 1, 3); gridLayout->addWidget(passwordLabel, 4, 0, 1, 1); gridLayout->addWidget(password, 5, 0, 1, 3); auto *widgetLayout = new QGridLayout(); widgetLayout->addLayout(horizontalLayout, 0, 0, 1, 1); widgetLayout->addLayout(gridLayout, 1, 0, 1, 1); mainLayout->addLayout(widgetLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setText(i18n("&Connect")); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &newFTPGUI::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &newFTPGUI::reject); - connect(prefix, QOverload::of(&KComboBox::activated), this, &newFTPGUI::slotTextChanged); - connect(url, QOverload::of(&KrHistoryComboBox::activated), url, &KrHistoryComboBox::addToHistory); + connect(prefix, QOverload::of(&KComboBox::QCOMBOBOX_ACTIVATED), this, &newFTPGUI::slotTextChanged); + connect(url, QOverload::of(&KrHistoryComboBox::QCOMBOBOX_ACTIVATED), url, &KrHistoryComboBox::addToHistory); if(!lastUsedProtocol.isEmpty()) { // update the port field slotTextChanged(lastUsedProtocol); } setTabOrder(url, username); setTabOrder(username, password); setTabOrder(password, prefix); } /** * Destroys the object and frees any allocated resources */ newFTPGUI::~newFTPGUI() { // no need to delete child widgets, Qt does it all for us } void newFTPGUI::slotTextChanged(const QString &string) { if (string.startsWith(QLatin1String("ftp")) || string.startsWith(QLatin1String("sftp")) || string.startsWith(QLatin1String("fish"))) { if (port->value() == 21 || port->value() == 22) { port->setValue(string.startsWith(QLatin1String("ftp")) ? 21 : 22); } port->setEnabled(true); } else { port->setEnabled(false); } } /** * Main event handler. Reimplemented to handle application font changes */ bool newFTPGUI::event(QEvent *ev) { bool ret = QDialog::event(ev); if (ev->type() == QEvent::ApplicationFontChange) { QFont font(aboutLabel->font()); font.setBold(true); aboutLabel->setFont(font); } return ret; } diff --git a/krusader/Dialogs/packguibase.cpp b/krusader/Dialogs/packguibase.cpp index 9f4d4a9e..7e5bb1ea 100644 --- a/krusader/Dialogs/packguibase.cpp +++ b/krusader/Dialogs/packguibase.cpp @@ -1,495 +1,496 @@ /***************************************************************************** * Copyright (C) 2000 Shie Erlich * * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "packguibase.h" // QtCore #include // QtGui #include #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../defaults.h" #include "../krglobal.h" #include "../icon.h" +#include "../compat.h" #include "../GUI/krhistorycombobox.h" /* * Constructs a PackGUIBase which is a child of 'parent', with the * name 'name' and widget flags set to 'f' * * The dialog will by default be modeless, unless you set 'modal' to * TRUE to construct a modal dialog. */ PackGUIBase::PackGUIBase(QWidget* parent) : QDialog(parent), expanded(false) { KConfigGroup group(krConfig, "Archives"); setModal(true); resize(430, 140); setWindowTitle(i18n("Pack")); grid = new QGridLayout(this); grid->setSpacing(6); grid->setContentsMargins(11, 11, 11, 11); hbox = new QHBoxLayout; hbox->setSpacing(6); hbox->setContentsMargins(0, 0, 0, 0); TextLabel3 = new QLabel(this); TextLabel3->setText(i18n("To archive")); hbox->addWidget(TextLabel3); nameData = new QLineEdit(this); hbox->addWidget(nameData); typeData = new QComboBox(this); typeData->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); - connect(typeData, QOverload::of(&QComboBox::activated), this, &PackGUIBase::checkConsistency); - connect(typeData, QOverload::of(&QComboBox::highlighted), this, &PackGUIBase::checkConsistency); + connect(typeData, QOverload::of(&QComboBox::QCOMBOBOX_ACTIVATED), this, &PackGUIBase::checkConsistency); + connect(typeData, QOverload::of(&QComboBox::QCOMBOBOX_HIGHLIGHTED), this, &PackGUIBase::checkConsistency); hbox->addWidget(typeData); grid->addLayout(hbox, 1, 0); hbox_2 = new QHBoxLayout; hbox_2->setSpacing(6); hbox_2->setContentsMargins(0, 0, 0, 0); TextLabel5 = new QLabel(this); TextLabel5->setText(i18n("In folder")); hbox_2->addWidget(TextLabel5); dirData = new QLineEdit(this); hbox_2->addWidget(dirData); browseButton = new QToolButton(this); browseButton->setIcon(Icon("document-open")); hbox_2->addWidget(browseButton); auto* spacer = new QSpacerItem(48, 20, QSizePolicy::Fixed, QSizePolicy::Fixed); hbox_2->addItem(spacer); grid->addLayout(hbox_2, 2, 0); hbox_3 = new QHBoxLayout; hbox_3->setSpacing(6); hbox_3->setContentsMargins(0, 0, 0, 0); PixmapLabel1 = new QLabel(this); PixmapLabel1->setPixmap(Icon("package-x-generic").pixmap(32)); PixmapLabel1->setScaledContents(true); PixmapLabel1->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); hbox_3->addWidget(PixmapLabel1); TextLabel1 = new QLabel(this); TextLabel1->setText(i18n("Pack")); hbox_3->addWidget(TextLabel1); grid->addLayout(hbox_3, 0, 0); hbox_4 = new QHBoxLayout; hbox_4->setSpacing(6); hbox_4->setContentsMargins(0, 0, 0, 0); auto* spacer_3 = new QSpacerItem(20, 26, QSizePolicy::Fixed, QSizePolicy::Expanding); hbox_4->addItem(spacer_3); grid->addLayout(hbox_4, 3, 0); advancedWidget = new QWidget(this); hbox_5 = new QGridLayout(advancedWidget); hbox_5->setSpacing(6); hbox_5->setContentsMargins(0, 0, 0, 0); auto *compressLayout = new QVBoxLayout; compressLayout->setSpacing(6); compressLayout->setContentsMargins(0, 0, 0, 0); multipleVolume = new QCheckBox(i18n("Multiple volume archive"), advancedWidget); connect(multipleVolume, &QCheckBox::toggled, this, &PackGUIBase::checkConsistency); - compressLayout->addWidget(multipleVolume, 0, nullptr); + compressLayout->addWidget(multipleVolume, 0, Qt::Alignment()); auto * volumeHbox = new QHBoxLayout; auto* spacer_5 = new QSpacerItem(20, 26, QSizePolicy::Fixed, QSizePolicy::Fixed); volumeHbox->addItem(spacer_5); TextLabel7 = new QLabel(i18n("Size:"), advancedWidget); volumeHbox->addWidget(TextLabel7); volumeSpinBox = new QSpinBox(advancedWidget); volumeSpinBox->setMinimum(1); volumeSpinBox->setMaximum(9999); volumeSpinBox->setValue(1440); volumeHbox->addWidget(volumeSpinBox); volumeUnitCombo = new QComboBox(advancedWidget); volumeUnitCombo->addItem("B"); volumeUnitCombo->addItem("KB"); volumeUnitCombo->addItem("MB"); volumeUnitCombo->setCurrentIndex(1); volumeHbox->addWidget(volumeUnitCombo); compressLayout->addLayout(volumeHbox); int level = group.readEntry("Compression level", _defaultCompressionLevel); setCompressionLevel = new QCheckBox(i18n("Set compression level"), advancedWidget); if (level != _defaultCompressionLevel) setCompressionLevel->setChecked(true); connect(setCompressionLevel, &QCheckBox::toggled, this, &PackGUIBase::checkConsistency); - compressLayout->addWidget(setCompressionLevel, 0, nullptr); + compressLayout->addWidget(setCompressionLevel, 0, Qt::Alignment()); auto * sliderHbox = new QHBoxLayout; auto* spacer_6 = new QSpacerItem(20, 26, QSizePolicy::Fixed, QSizePolicy::Fixed); sliderHbox->addItem(spacer_6); QWidget * sliderVBoxWidget = new QWidget(advancedWidget); auto *sliderVBox = new QVBoxLayout(sliderVBoxWidget); compressionSlider = new QSlider(Qt::Horizontal, sliderVBoxWidget); compressionSlider->setMinimum(1); compressionSlider->setMaximum(9); compressionSlider->setPageStep(1); compressionSlider->setValue(level); compressionSlider->setTickPosition(QSlider::TicksBelow); sliderVBox->addWidget(compressionSlider); QWidget * minmaxWidget = new QWidget(sliderVBoxWidget); sliderVBox->addWidget(minmaxWidget); auto * minmaxHbox = new QHBoxLayout(minmaxWidget); minLabel = new QLabel(i18n("MIN"), minmaxWidget); maxLabel = new QLabel(i18n("MAX"), minmaxWidget); maxLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); minmaxHbox->addWidget(minLabel); minmaxHbox->addWidget(maxLabel); sliderHbox->addWidget(sliderVBoxWidget); compressLayout->addLayout(sliderHbox); compressLayout->addStretch(0); hbox_5->addLayout(compressLayout, 0, 0); QFrame *vline = new QFrame(advancedWidget); vline->setFrameStyle(QFrame::VLine | QFrame::Sunken); vline->setMinimumWidth(20); hbox_5->addWidget(vline, 0, 1); auto * passwordGrid = new QGridLayout; passwordGrid->setSpacing(6); passwordGrid->setContentsMargins(0, 0, 0, 0); TextLabel4 = new QLabel(advancedWidget); TextLabel4->setText(i18n("Password")); passwordGrid->addWidget(TextLabel4, 0, 0); password = new QLineEdit(advancedWidget); password->setEchoMode(QLineEdit::Password); connect(password, &QLineEdit::textChanged, this, &PackGUIBase::checkConsistency); passwordGrid->addWidget(password, 0, 1); TextLabel6 = new QLabel(advancedWidget); TextLabel6->setText(i18n("Again")); passwordGrid->addWidget(TextLabel6, 1, 0); passwordAgain = new QLineEdit(advancedWidget); passwordAgain->setEchoMode(QLineEdit::Password); connect(passwordAgain, &QLineEdit::textChanged, this, &PackGUIBase::checkConsistency); passwordGrid->addWidget(passwordAgain, 1, 1); auto *consistencyHbox = new QHBoxLayout; auto* spacer_cons = new QSpacerItem(48, 20, QSizePolicy::Expanding, QSizePolicy::Fixed); consistencyHbox->addItem(spacer_cons); passwordConsistencyLabel = new QLabel(advancedWidget); consistencyHbox->addWidget(passwordConsistencyLabel); passwordGrid->addLayout(consistencyHbox, 2, 0, 1, 2); encryptHeaders = new QCheckBox(i18n("Encrypt headers"), advancedWidget); passwordGrid->addWidget(encryptHeaders, 3, 0, 1, 2); auto* spacer_psw = new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Expanding); passwordGrid->addItem(spacer_psw, 4, 0); hbox_5->addLayout(passwordGrid, 0, 2); hbox_7 = new QHBoxLayout; hbox_7->setSpacing(6); hbox_7->setContentsMargins(0, 0, 0, 0); TextLabel8 = new QLabel(i18n("Command line switches:"), advancedWidget); TextLabel8->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); hbox_7->addWidget(TextLabel8); commandLineSwitches = new KrHistoryComboBox(advancedWidget); commandLineSwitches->setMaxCount(25); // remember 25 items commandLineSwitches->setDuplicatesEnabled(false); commandLineSwitches->setMinimumContentsLength(10); QStringList list = group.readEntry("Command Line Switches", QStringList()); commandLineSwitches->setHistoryItems(list); hbox_7->addWidget(commandLineSwitches); hbox_5->addLayout(hbox_7, 1, 0, 1, 3); advancedWidget->hide(); checkConsistency(); grid->addWidget(advancedWidget, 4, 0); hbox_6 = new QHBoxLayout; hbox_6->setSpacing(6); hbox_6->setContentsMargins(0, 0, 0, 0); advancedButton = new QPushButton(this); advancedButton->setText(i18n("&Advanced >>")); hbox_6->addWidget(advancedButton); auto* spacer_2 = new QSpacerItem(140, 20, QSizePolicy::Expanding, QSizePolicy::Fixed); hbox_6->addItem(spacer_2); okButton = new QPushButton(this); KStandardGuiItem::assign(okButton, KStandardGuiItem::Ok); okButton->setDefault(true); hbox_6->addWidget(okButton); cancelButton = new QPushButton(this); KStandardGuiItem::assign(cancelButton, KStandardGuiItem::Cancel); hbox_6->addWidget(cancelButton); grid->addLayout(hbox_6, 6, 0); // signals and slots connections connect(okButton, &QPushButton::clicked, this, &PackGUIBase::accept); connect(advancedButton, &QPushButton::clicked, this, &PackGUIBase::expand); connect(cancelButton, &QPushButton::clicked, this, &PackGUIBase::reject); connect(browseButton, &QToolButton::clicked, this, &PackGUIBase::browse); } /* * Destroys the object and frees any allocated resources */ PackGUIBase::~PackGUIBase() { // no need to delete child widgets, Qt does it all for us } void PackGUIBase::browse() { qWarning("PackGUIBase::browse(): Not implemented yet!"); } void PackGUIBase::expand() { expanded = !expanded; advancedButton->setText(expanded ? i18n("&Advanced <<") : i18n("&Advanced >>")); if (expanded) advancedWidget->show(); else { advancedWidget->hide(); layout()->activate(); QSize minSize = minimumSize(); resize(width(), minSize.height()); } show(); } void PackGUIBase::checkConsistency() { QPalette p = QGuiApplication::palette(); QPalette pal = passwordConsistencyLabel->palette(); if (password->text().isEmpty() && passwordAgain->text().isEmpty()) { pal.setColor(passwordConsistencyLabel->foregroundRole(), p.color(QPalette::Active, QPalette::Text)); passwordConsistencyLabel->setText(i18n("No password specified")); } else if (password->text() == passwordAgain->text()) { pal.setColor(passwordConsistencyLabel->foregroundRole(), p.color(QPalette::Active, QPalette::Text)); passwordConsistencyLabel->setText(i18n("The passwords are equal")); } else { pal.setColor(passwordConsistencyLabel->foregroundRole(), Qt::red); passwordConsistencyLabel->setText(i18n("The passwords are different")); } passwordConsistencyLabel->setPalette(pal); QString packer = typeData->currentText(); bool passworded = false; if (packer == "7z" || packer == "rar" || packer == "zip" || packer == "arj") passworded = true; passwordConsistencyLabel->setEnabled(passworded); password->setEnabled(passworded); passwordAgain->setEnabled(passworded); TextLabel4->setEnabled(passworded); TextLabel6->setEnabled(passworded); encryptHeaders->setEnabled(packer == "rar"); multipleVolume->setEnabled(packer == "rar" || packer == "arj"); bool volumeEnabled = multipleVolume->isEnabled() && multipleVolume->isChecked(); volumeSpinBox->setEnabled(volumeEnabled); volumeUnitCombo->setEnabled(volumeEnabled); TextLabel7->setEnabled(volumeEnabled); /* TODO */ setCompressionLevel->setEnabled(packer == "rar" || packer == "arj" || packer == "zip" || packer == "7z"); bool sliderEnabled = setCompressionLevel->isEnabled() && setCompressionLevel->isChecked(); compressionSlider->setEnabled(sliderEnabled); minLabel->setEnabled(sliderEnabled); maxLabel->setEnabled(sliderEnabled); } bool PackGUIBase::extraProperties(QMap & inMap) { inMap.clear(); KConfigGroup group(krConfig, "Archives"); if (password->isEnabled() && passwordAgain->isEnabled()) { if (password->text() != passwordAgain->text()) { KMessageBox::error(this, i18n("Cannot pack, the passwords are different.")); return false; } if (!password->text().isEmpty()) { inMap[ "Password" ] = password->text(); if (encryptHeaders->isEnabled() && encryptHeaders->isChecked()) inMap[ "EncryptHeaders" ] = '1'; } } if (multipleVolume->isEnabled() && multipleVolume->isChecked()) { KIO::filesize_t size = volumeSpinBox->value(); switch (volumeUnitCombo->currentIndex()) { case 2: size *= 1000; #if __GNUC__ >= 7 [[gnu::fallthrough]]; #endif case 1: size *= 1000; default: break; } if (size < 10000) { KMessageBox::error(this, i18n("Invalid volume size.")); return false; } QString sbuffer; sbuffer.asprintf("%llu", size); inMap[ "VolumeSize" ] = sbuffer; } if (setCompressionLevel->isEnabled() && setCompressionLevel->isChecked()) { inMap[ "CompressionLevel" ] = QString("%1").arg(compressionSlider->value()); int level = compressionSlider->value(); group.writeEntry("Compression level", level); } QString cmdArgs = commandLineSwitches->currentText().trimmed(); if (!cmdArgs.isEmpty()) { bool firstChar = true; QChar quote = QChar::Null; for (int i = 0; i < cmdArgs.length(); i++) { QChar ch(cmdArgs[ i ]); if (ch.isSpace()) continue; if (ch == quote) { quote = QChar::Null; continue; } if (firstChar && ch != QLatin1Char('-')) { KMessageBox::error(this, i18n("Invalid command line switch.\nA switch must start with '-'.")); return false; } firstChar = false; if (quote == QLatin1Char('"')) continue; if (quote == QChar::Null && (ch == QLatin1Char('\'') || ch == QLatin1Char('"'))) quote = ch; if (ch == QLatin1Char('\\')) { if (i == cmdArgs.length() - 1) { KMessageBox::error(this, i18n("Invalid command line switch.\nBackslashes cannot be the last character.")); return false; } i++; } } if (quote != QChar::Null) { KMessageBox::error(this, i18n("Invalid command line switch.\nUnclosed quotation mark.")); return false; } commandLineSwitches->addToHistory(cmdArgs); inMap[ "CommandLineSwitches" ] = cmdArgs; } return true; } diff --git a/krusader/Filter/generalfilter.cpp b/krusader/Filter/generalfilter.cpp index e86d4d43..c5431edc 100644 --- a/krusader/Filter/generalfilter.cpp +++ b/krusader/Filter/generalfilter.cpp @@ -1,666 +1,667 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * Copyright (C) 2003 Csaba Karai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "generalfilter.h" #include "filtertabs.h" #include "../krglobal.h" #include "../krservices.h" #include "../FileSystem/filesystem.h" +#include "../compat.h" // QtWidgets #include #include #include #include #include #include #include #include #include #include typedef struct { const char *description; const char *regExp; int cursorAdjustment; } term; static const term items[] = { { I18N_NOOP("Any Character"), ".", 0 }, { I18N_NOOP("Start of Line"), "^", 0 }, { I18N_NOOP("End of Line"), "$", 0 }, { I18N_NOOP("Set of Characters"), "[]", -1 }, { I18N_NOOP("Repeats, Zero or More Times"), "*", 0 }, { I18N_NOOP("Repeats, One or More Times"), "+", 0 }, { I18N_NOOP("Optional"), "?", 0 }, { I18N_NOOP("Escape"), "\\", 0 }, { I18N_NOOP("TAB"), "\\t", 0 }, { I18N_NOOP("Newline"), "\\n", 0 }, { I18N_NOOP("Carriage Return"), "\\r", 0 }, { I18N_NOOP("White Space"), "\\s", 0 }, { I18N_NOOP("Digit"), "\\d", 0 }, }; class RegExpAction : public QAction { public: RegExpAction(QObject *parent, const QString &text, const QString ®Exp, int cursor) : QAction(text, parent), mText(text), mRegExp(regExp), mCursor(cursor) { } QString text() const { return mText; } QString regExp() const { return mRegExp; } int cursor() const { return mCursor; } private: QString mText; QString mRegExp; int mCursor; }; GeneralFilter::GeneralFilter(FilterTabs *tabs, int properties, QWidget *parent, QStringList extraOptions) : QWidget(parent), profileManager(nullptr), fltTabs(tabs) { auto *filterLayout = new QGridLayout(this); filterLayout->setSpacing(6); filterLayout->setContentsMargins(11, 11, 11, 11); this->properties = properties; // Options for name filtering auto *nameGroup = new QGroupBox(this); nameGroup->setTitle(i18n("File Name")); auto *nameGroupLayout = new QGridLayout(nameGroup); nameGroupLayout->setAlignment(Qt::AlignTop); nameGroupLayout->setSpacing(6); nameGroupLayout->setContentsMargins(11, 11, 11, 11); searchForCase = new QCheckBox(nameGroup); searchForCase->setText(i18n("&Case sensitive")); searchForCase->setChecked(false); nameGroupLayout->addWidget(searchForCase, 1, 2); QLabel *searchForLabel = new QLabel(nameGroup); searchForLabel->setText(i18n("Search &for:")); nameGroupLayout->addWidget(searchForLabel, 0, 0); searchFor = new KrHistoryComboBox(false, nameGroup/*, "searchFor"*/); QSizePolicy searchForPolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); searchForPolicy.setHeightForWidth(searchFor->sizePolicy().hasHeightForWidth()); searchFor->setSizePolicy(searchForPolicy); searchFor->setEditable(true); searchFor->setDuplicatesEnabled(false); searchFor->setMaxCount(25); searchFor->setMinimumContentsLength(10); searchForLabel->setBuddy(searchFor); nameGroupLayout->addWidget(searchFor, 0, 1, 1, 2); const QString s = "

" + i18n("

The filename filtering criteria is defined here.

" "

You can make use of wildcards. Multiple patterns are separated by " "space (means logical OR) and patterns are excluded from the search " "using the pipe symbol.

" "

If the pattern is ended with a slash (*pattern*/), " "that means that pattern relates to recursive search of folders." "

  • pattern - means to search those files/folders " "that name is pattern, recursive search goes through all " "subfolders independently of the value of pattern
  • " "
  • pattern/ - means to search all files/folders, but " "recursive search goes through/excludes the folders that name is " "pattern

" "

It is allowed to use quotation marks for names that contain space." " Filter \"Program Files\" searches out those " "files/folders that name is Program Files.

" "

Examples:

    " "
  • *.o
  • " "
  • *.h *.c\?\?
  • " "
  • *.cpp *.h | *.moc.cpp
  • " "
  • * | .svn/ .git/

" "Note: the search term 'text' is equivalent to " "'*text*'.

"); searchFor->setWhatsThis(s); searchForLabel->setWhatsThis(s); QLabel *searchType = new QLabel(nameGroup); searchType->setText(i18n("&Of type:")); nameGroupLayout->addWidget(searchType, 1, 0); ofType = new KComboBox(false, nameGroup); ofType->setObjectName("ofType"); QSizePolicy ofTypePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); ofTypePolicy.setHeightForWidth(ofType->sizePolicy().hasHeightForWidth()); ofType->setSizePolicy(ofTypePolicy); ofType->setEditable(false); searchType->setBuddy(ofType); ofType->addItem(i18n("All Files")); ofType->addItem(i18n("Archives")); ofType->addItem(i18n("Folders")); ofType->addItem(i18n("Image Files")); ofType->addItem(i18n("Text Files")); ofType->addItem(i18n("Video Files")); ofType->addItem(i18n("Audio Files")); connect(ofType, QOverload::of(&KComboBox::currentIndexChanged), this, &GeneralFilter::slotDisable); nameGroupLayout->addWidget(ofType, 1, 1); filterLayout->addWidget(nameGroup, 0, 0); middleLayout = new QHBoxLayout(); middleLayout->setSpacing(6); middleLayout->setContentsMargins(0, 0, 0, 0); if (properties & FilterTabs::HasProfileHandler) { // The profile handler auto *profileHandler = new QGroupBox(this); profileHandler->setTitle(i18n("&Profile handler")); auto *profileLayout = new QGridLayout(profileHandler); profileLayout->setAlignment(Qt::AlignTop); profileLayout->setSpacing(6); profileLayout->setContentsMargins(11, 11, 11, 11); profileListBox = new KrListWidget(profileHandler); profileLayout->addWidget(profileListBox, 0, 0, 4, 1); profileAddBtn = new QPushButton(profileHandler); KStandardGuiItem::assign(profileAddBtn, KStandardGuiItem::Add); profileLayout->addWidget(profileAddBtn, 0, 1); profileLoadBtn = new QPushButton(i18n("&Load"), profileHandler); profileLoadBtn->setEnabled(false); profileLayout->addWidget(profileLoadBtn, 1, 1); profileOverwriteBtn = new QPushButton(profileHandler); profileOverwriteBtn->setEnabled(false); KStandardGuiItem::assign(profileOverwriteBtn, KStandardGuiItem::Overwrite); profileLayout->addWidget(profileOverwriteBtn, 2, 1); profileRemoveBtn = new QPushButton(profileHandler); profileRemoveBtn->setEnabled(false); KStandardGuiItem::assign(profileRemoveBtn, KStandardGuiItem::Remove); profileLayout->addWidget(profileRemoveBtn, 3, 1); profileManager = new ProfileManager("SelectionProfile", this); profileManager->hide(); middleLayout->addWidget(profileHandler); refreshProfileListBox(); } if (properties & FilterTabs::HasSearchIn) { // Options for search in QGroupBox *searchGroupBox = new QGroupBox(i18n("Searc&h in"), this); auto *searchLayout = new QGridLayout(searchGroupBox); searchLayout->setAlignment(Qt::AlignTop); searchLayout->setSpacing(6); searchLayout->setContentsMargins(11, 11, 11, 11); searchIn = new KURLListRequester(KURLListRequester::RequestDirs, searchGroupBox); searchLayout->addWidget(searchIn, 0, 0); connect(searchIn, &KURLListRequester::changed, this, &GeneralFilter::slotDisable); middleLayout->addWidget(searchGroupBox); } if (properties & FilterTabs::HasDontSearchIn) { // Options for don't search in QGroupBox *searchGroupBox = new QGroupBox(i18n("&Do not search in"), this); auto *searchLayout = new QGridLayout(searchGroupBox); searchLayout->setAlignment(Qt::AlignTop); searchLayout->setSpacing(6); searchLayout->setContentsMargins(11, 11, 11, 11); dontSearchIn = new KURLListRequester(KURLListRequester::RequestDirs, searchGroupBox); searchLayout->addWidget(dontSearchIn, 0, 0, 1, 2); if (properties & FilterTabs::HasRecurseOptions) { KConfigGroup group(krConfig, "Search"); useExcludeFolderNames = createExcludeCheckBox(group); searchLayout->addWidget(useExcludeFolderNames, 1, 0, 1, 1); excludeFolderNames = createExcludeComboBox(group); searchLayout->addWidget(excludeFolderNames, 1, 1, 1, 1); if (!useExcludeFolderNames->isChecked()) { excludeFolderNames->setDisabled(true); } connect(useExcludeFolderNames, &QCheckBox::toggled, excludeFolderNames, &KrHistoryComboBox::setEnabled); } middleLayout->addWidget(searchGroupBox); } filterLayout->addLayout(middleLayout, 1, 0); // Options for containing text auto *containsGroup = new QGroupBox(this); containsGroup->setTitle(i18n("Containing text")); auto *containsLayout = new QGridLayout(containsGroup); containsLayout->setAlignment(Qt::AlignTop); containsLayout->setSpacing(6); containsLayout->setContentsMargins(11, 11, 11, 11); auto *containsTextLayout = new QHBoxLayout(); containsTextLayout->setSpacing(6); containsTextLayout->setContentsMargins(0, 0, 0, 0); containsLabel = new QLabel(containsGroup); QSizePolicy containsLabelPolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); containsLabelPolicy.setHeightForWidth(containsLabel->sizePolicy().hasHeightForWidth()); containsLabel->setSizePolicy(containsLabelPolicy); containsLabel->setText(i18n("&Text:")); containsTextLayout->addWidget(containsLabel); containsText = new KrHistoryComboBox(false, containsGroup/*, "containsText"*/); QSizePolicy containsTextPolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); containsTextPolicy.setHeightForWidth(containsText->sizePolicy().hasHeightForWidth()); containsText->setSizePolicy(containsTextPolicy); containsText->setDuplicatesEnabled(false); containsText->setMaxCount(25); containsText->setMinimumContentsLength(10); containsTextLayout->addWidget(containsText); containsLabel->setBuddy(containsText); containsRegExp = new QToolButton(containsGroup); containsRegExp->setPopupMode(QToolButton::MenuButtonPopup); containsRegExp->setCheckable(true); containsRegExp->setText(i18n("RegExp")); // Populate the popup menu. auto *patterns = new QMenu(containsRegExp); for (int i = 0; (unsigned)i < sizeof(items) / sizeof(items[0]); i++) { patterns->addAction(new RegExpAction(patterns, i18n(items[i].description), items[i].regExp, items[i].cursorAdjustment)); } connect(containsRegExp, &QToolButton::toggled, this, &GeneralFilter::slotDisable); connect(containsRegExp, &QToolButton::triggered, this, &GeneralFilter::slotRegExpTriggered); containsRegExp->setMenu(patterns); patterns->setEnabled(false); containsTextLayout->addWidget(containsRegExp); containsLayout->addLayout(containsTextLayout, 0, 0); auto *containsCbsLayout = new QHBoxLayout(); containsCbsLayout->setSpacing(6); containsCbsLayout->setContentsMargins(0, 0, 0, 0); encLabel = new QLabel(i18n("Encoding:"), containsGroup); containsCbsLayout->addWidget(encLabel); contentEncoding = new KComboBox(containsGroup); contentEncoding->setEditable(false); contentEncoding->addItem(i18nc("Default encoding", "Default")); contentEncoding->addItems(KCharsets::charsets()->descriptiveEncodingNames()); containsCbsLayout->addWidget(contentEncoding); auto* cbSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); containsCbsLayout->addItem(cbSpacer); containsWholeWord = new QCheckBox(containsGroup); QSizePolicy containsWholeWordPolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); containsWholeWordPolicy.setHeightForWidth(containsWholeWord->sizePolicy().hasHeightForWidth()); containsWholeWord->setSizePolicy(containsWholeWordPolicy); containsWholeWord->setText(i18n("&Match whole word only")); containsWholeWord->setChecked(false); containsCbsLayout->addWidget(containsWholeWord); containsTextCase = new QCheckBox(containsGroup); QSizePolicy containsTextCasePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); containsTextCasePolicy.setHeightForWidth(containsTextCase->sizePolicy().hasHeightForWidth()); containsTextCase->setSizePolicy(containsTextCasePolicy); containsTextCase->setText(i18n("Cas&e sensitive")); containsTextCase->setChecked(true); containsCbsLayout->addWidget(containsTextCase); containsLayout->addLayout(containsCbsLayout, 1, 0); filterLayout->addWidget(containsGroup, 2, 0); auto *recurseLayout = new QHBoxLayout(); recurseLayout->setSpacing(6); recurseLayout->setContentsMargins(0, 0, 0, 0); auto* recurseSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); recurseLayout->addItem(recurseSpacer); if (properties & FilterTabs::HasRecurseOptions) { // Options for recursive searching searchInDirs = new QCheckBox(this); searchInDirs->setText(i18n("Search in s&ub folders")); searchInDirs->setChecked(true); recurseLayout->addWidget(searchInDirs); searchInArchives = new QCheckBox(this); searchInArchives->setText(i18n("Search in arch&ives")); recurseLayout->addWidget(searchInArchives); followLinks = new QCheckBox(this); followLinks->setText(i18n("Follow &links")); recurseLayout->addWidget(followLinks); } filterLayout->addLayout(recurseLayout, 3, 0); for(int i = 0; i < extraOptions.length(); i++) { auto *option = new QCheckBox(this); option->setText(extraOptions[i]); recurseLayout->addWidget(option); this->extraOptions.insert(extraOptions[i], option); } // Connection table if (properties & FilterTabs::HasProfileHandler) { connect(profileAddBtn, &QPushButton::clicked, this, &GeneralFilter::slotAddBtnClicked); connect(profileLoadBtn, &QPushButton::clicked, this, &GeneralFilter::slotLoadBtnClicked); connect(profileOverwriteBtn, &QPushButton::clicked, this, &GeneralFilter::slotOverwriteBtnClicked); connect(profileRemoveBtn, &QPushButton::clicked, this, &GeneralFilter::slotRemoveBtnClicked); connect(profileListBox, &KrListWidget::itemDoubleClicked, this, &GeneralFilter::slotProfileDoubleClicked); connect(profileManager, &ProfileManager::loadFromProfile, fltTabs, &FilterTabs::loadFromProfile); connect(profileManager, &ProfileManager::saveToProfile, fltTabs, &FilterTabs::saveToProfile); } - connect(searchFor, QOverload::of(&KrHistoryComboBox::activated), searchFor, &KrHistoryComboBox::addToHistory); - connect(containsText, QOverload::of(&KrHistoryComboBox::activated), containsText, &KrHistoryComboBox::addToHistory); + connect(searchFor, QOverload::of(&KrHistoryComboBox::QCOMBOBOX_ACTIVATED), searchFor, &KrHistoryComboBox::addToHistory); + connect(containsText, QOverload::of(&KrHistoryComboBox::QCOMBOBOX_ACTIVATED), containsText, &KrHistoryComboBox::addToHistory); // load the completion and history lists // ==> search for KConfigGroup group(krConfig, "Search"); QStringList list = group.readEntry("SearchFor Completion", QStringList()); searchFor->completionObject()->setItems(list); list = group.readEntry("SearchFor History", QStringList()); searchFor->setHistoryItems(list); // ==> grep list = group.readEntry("ContainsText Completion", QStringList()); containsText->completionObject()->setItems(list); list = group.readEntry("ContainsText History", QStringList()); containsText->setHistoryItems(list); setTabOrder(searchFor, containsText); // search for -> content setTabOrder(containsText, searchType); // content -> search type slotDisable(); } GeneralFilter::~GeneralFilter() { // save the history combos // ==> search for QStringList list = searchFor->completionObject()->items(); KConfigGroup group(krConfig, "Search"); group.writeEntry("SearchFor Completion", list); list = searchFor->historyItems(); group.writeEntry("SearchFor History", list); // ==> grep text list = containsText->completionObject()->items(); group.writeEntry("ContainsText Completion", list); list = containsText->historyItems(); group.writeEntry("ContainsText History", list); if ((properties & FilterTabs::HasDontSearchIn) && (properties & FilterTabs::HasRecurseOptions)) { list = excludeFolderNames->historyItems(); group.writeEntry("ExcludeFolderNamesHistory", list); group.writeEntry("ExcludeFolderNames", excludeFolderNames->currentText()); group.writeEntry("ExcludeFolderNamesUse", static_cast(useExcludeFolderNames->checkState())); } krConfig->sync(); } bool GeneralFilter::isExtraOptionChecked(const QString& name) { QCheckBox *option = extraOptions[name]; return option ? option->isChecked() : false; } void GeneralFilter::checkExtraOption(const QString& name, bool check) { QCheckBox *option = extraOptions[name]; if (option) option->setChecked(check); } void GeneralFilter::queryAccepted() { searchFor->addToHistory(searchFor->currentText()); containsText->addToHistory(containsText->currentText()); if ((properties & FilterTabs::HasDontSearchIn) && (properties & FilterTabs::HasRecurseOptions)) { excludeFolderNames->addToHistory(excludeFolderNames->currentText()); } } void GeneralFilter::refreshProfileListBox() { profileListBox->clear(); profileListBox->addItems(ProfileManager::availableProfiles("SelectionProfile")); if (profileListBox->count() != 0) { profileLoadBtn->setEnabled(true); profileOverwriteBtn->setEnabled(true); profileRemoveBtn->setEnabled(true); } else { profileLoadBtn->setEnabled(false); profileOverwriteBtn->setEnabled(false); profileRemoveBtn->setEnabled(false); } } QCheckBox *GeneralFilter::createExcludeCheckBox(const KConfigGroup &group) { auto *excludeCheckBox = new QCheckBox(this); excludeCheckBox->setText(i18n("Exclude Folder Names")); excludeCheckBox->setToolTip(i18n("Filters out specified folder names from the results.")); excludeCheckBox->setChecked(static_cast(group.readEntry("ExcludeFolderNamesUse", 0))); return excludeCheckBox; } KrHistoryComboBox *GeneralFilter::createExcludeComboBox(const KConfigGroup &group) { auto *excludeComboBox = new KrHistoryComboBox(false, this); QSizePolicy excludeFolderNamesPolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); excludeFolderNamesPolicy.setHeightForWidth(excludeComboBox->sizePolicy().hasHeightForWidth()); excludeComboBox->setSizePolicy(excludeFolderNamesPolicy); excludeComboBox->setEditable(true); excludeComboBox->setDuplicatesEnabled(false); excludeComboBox->setMaxCount(25); excludeComboBox->setMinimumContentsLength(10); excludeComboBox->lineEdit()->setPlaceholderText(i18n("Enter space-separated folder names")); excludeComboBox->lineEdit()->setWhatsThis( i18n("You can insert names with escaped spaces or quoted.\nExample: .git \"target " "build\" build\\ krusader")); excludeComboBox->setHistoryItems(group.readEntry("ExcludeFolderNamesHistory", QStringList())); excludeComboBox->setEditText(group.readEntry("ExcludeFolderNames", "")); return excludeComboBox; } void GeneralFilter::slotAddBtnClicked() { profileManager->newProfile(searchFor->currentText().simplified()); refreshProfileListBox(); } void GeneralFilter::slotOverwriteBtnClicked() { QListWidgetItem *item = profileListBox->currentItem(); if (item != nullptr) profileManager->overwriteProfile(item->text()); } void GeneralFilter::slotRemoveBtnClicked() { QListWidgetItem *item = profileListBox->currentItem(); if (item != nullptr) { profileManager->deleteProfile(item->text()); refreshProfileListBox(); } } void GeneralFilter::slotProfileDoubleClicked(QListWidgetItem *item) { if (item != nullptr) { QString profileName = item->text(); profileManager->loadProfile(profileName); fltTabs->close(true); } } void GeneralFilter::slotLoadBtnClicked() { QListWidgetItem *item = profileListBox->currentItem(); if (item != nullptr) profileManager->loadProfile(item->text()); } void GeneralFilter::slotDisable() { bool state = containsRegExp->isChecked(); bool global = ofType->currentText() != i18n("Folders"); bool remoteOnly = false; if (properties & FilterTabs::HasSearchIn) { QList urlList = searchIn->urlList(); remoteOnly = urlList.count() != 0; foreach(const QUrl &url, urlList) if (url.scheme() == "file") remoteOnly = false; } containsWholeWord->setEnabled(!state && global); containsRegExp->menu()->setEnabled(state && global); encLabel->setEnabled(global); contentEncoding->setEnabled(global); containsTextCase->setEnabled(global); containsRegExp->setEnabled(global); if (properties & FilterTabs::HasRecurseOptions) searchInArchives->setEnabled(global && !remoteOnly); containsLabel->setEnabled(global); containsText->setEnabled(global); } void GeneralFilter::slotRegExpTriggered(QAction * act) { if (act == nullptr) return; auto *regAct = dynamic_cast(act); if (regAct == nullptr) return; containsText->lineEdit()->insert(regAct->regExp()); containsText->lineEdit()->setCursorPosition(containsText->lineEdit()->cursorPosition() + regAct->cursor()); containsText->lineEdit()->setFocus(); } bool GeneralFilter::getSettings(FilterSettings &s) { // check that we have (at least) what to search, and where to search in if (searchFor->currentText().simplified().isEmpty()) { KMessageBox::error(this , i18n("No search criteria entered.")); searchFor->setFocus(); return false; } s.searchFor = searchFor->currentText().trimmed(); s.searchForCase = searchForCase->isChecked(); if (ofType->currentText() != i18n("All Files")) s.mimeType = ofType->currentText(); if (containsText->isEnabled()) { s.containsText = containsText->currentText(); s.containsTextCase = containsTextCase->isChecked(); s.containsWholeWord = containsWholeWord->isChecked(); s.containsRegExp = containsRegExp->isChecked(); } if (contentEncoding->currentIndex() != 0) s.contentEncoding = KCharsets::charsets()->encodingForName(contentEncoding->currentText()); if (properties & FilterTabs::HasRecurseOptions) { s.recursive = searchInDirs->isChecked(); s.searchInArchives = searchInArchives->isChecked(); s.followLinks = followLinks->isChecked(); } if (properties & FilterTabs::HasSearchIn) { s.searchIn = searchIn->urlList(); if (s.searchIn.isEmpty()) { // we need a place to search in KMessageBox::error(this , i18n("Please specify a location to search in.")); searchIn->lineEdit()->setFocus(); return false; } } if (properties & FilterTabs::HasDontSearchIn) { s.dontSearchIn = dontSearchIn->urlList(); if (properties & FilterTabs::HasRecurseOptions) { if (useExcludeFolderNames->isChecked()) { s.excludeFolderNames = KShell::splitArgs(excludeFolderNames->currentText()); } else { s.excludeFolderNames = QStringList(); } } } return true; } void GeneralFilter::applySettings(const FilterSettings &s) { searchFor->setEditText(s.searchFor); searchForCase->setChecked(s.searchForCase); setComboBoxValue(ofType, s.mimeType); containsText->setEditText(s.containsText); containsTextCase->setChecked(s.containsTextCase); containsWholeWord->setChecked(s.containsWholeWord); containsRegExp->setChecked(s.containsRegExp); setComboBoxValue(contentEncoding, KCharsets::charsets()->descriptionForEncoding(s.contentEncoding)); if (properties & FilterTabs::HasRecurseOptions) { searchInDirs->setChecked(s.recursive); searchInArchives->setChecked(s.searchInArchives); followLinks->setChecked(s.followLinks); } if (properties & FilterTabs::HasSearchIn) { searchIn->lineEdit()->clear(); searchIn->listBox()->clear(); searchIn->listBox()->addItems(KrServices::toStringList(s.searchIn)); } if (properties & FilterTabs::HasDontSearchIn) { dontSearchIn->lineEdit()->clear(); dontSearchIn->listBox()->clear(); dontSearchIn->listBox()->addItems(KrServices::toStringList(s.dontSearchIn)); } } diff --git a/krusader/GUI/krremoteencodingmenu.cpp b/krusader/GUI/krremoteencodingmenu.cpp index ed6ba4bb..3f496598 100644 --- a/krusader/GUI/krremoteencodingmenu.cpp +++ b/krusader/GUI/krremoteencodingmenu.cpp @@ -1,226 +1,227 @@ /***************************************************************************** * Copyright (C) 2005 Csaba Karai * * Copyright (C) 2005-2020 Krusader Krew [https://krusader.org] * * * * Based on KRemoteEncodingPlugin from Dawit Alemayehu * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krremoteencodingmenu.h" // QtCore #include // QtWidgets #include #include #include #include #include #include #include #include #include "../krglobal.h" +#include "../compat.h" #include "../icon.h" #include "../Panel/krpanel.h" #include "../Panel/panelfunc.h" #define DATA_KEY QString::fromLatin1("Charset") KrRemoteEncodingMenu::KrRemoteEncodingMenu(const QString &text, const QString &iconName, KActionCollection *parent) : KActionMenu(Icon(iconName), text, parent), settingsLoaded(false) { connect(menu(), &QMenu::aboutToShow, this, &KrRemoteEncodingMenu::slotAboutToShow); parent->addAction("changeremoteencoding", this); } void KrRemoteEncodingMenu::slotAboutToShow() { if (!settingsLoaded) loadSettings(); // uncheck everything QList acts = menu()->actions(); foreach(QAction *act, acts) act->setChecked(false); QString charset = currentCharacterSet(); if (!charset.isEmpty()) { int id = 1; QStringList::Iterator it; for (it = encodingNames.begin(); it != encodingNames.end(); ++it, ++id) if ((*it).indexOf(charset) != -1) break; bool found = false; foreach(QAction *act, acts) { if (act->data().canConvert ()) { int idr = act->data().toInt(); if (idr == id) { act->setChecked(found = true); break; } } } if (!found) qWarning() << Q_FUNC_INFO << "could not find entry for charset=" << charset; } else { foreach(QAction *act, acts) { if (act->data().canConvert ()) { int idr = act->data().toInt(); if (idr == -2) { act->setChecked(true); break; } } } } } QString KrRemoteEncodingMenu::currentCharacterSet() { QUrl currentURL = ACTIVE_PANEL->virtualPath(); return KProtocolManager::charsetFor(currentURL); } void KrRemoteEncodingMenu::loadSettings() { settingsLoaded = true; encodingNames = KCharsets::charsets()->descriptiveEncodingNames(); QMenu *qmenu = menu(); disconnect(qmenu, &QMenu::triggered, this, &KrRemoteEncodingMenu::slotTriggered); connect(qmenu, &QMenu::triggered, this, &KrRemoteEncodingMenu::slotTriggered); qmenu->clear(); QStringList::ConstIterator it; int count = 0; QAction *act; for (it = encodingNames.constBegin(); it != encodingNames.constEnd(); ++it) { act = qmenu->addAction(*it); act->setData(QVariant(++count)); act->setCheckable(true); } qmenu->addSeparator(); act = qmenu->addAction(i18n("Reload")); act->setCheckable(true); act->setData(QVariant(-1)); act = qmenu->addAction(i18nc("Default encoding", "Default")); act->setCheckable(true); act->setData(QVariant(-2)); } void KrRemoteEncodingMenu::slotTriggered(QAction * act) { if (!act || !act->data().canConvert ()) return; int id = act->data().toInt(); switch (id) { case -1: slotReload(); return; case -2: chooseDefault(); return; default: chooseEncoding(encodingNames[id - 1]); } } void KrRemoteEncodingMenu::chooseEncoding(QString encoding) { QUrl currentURL = ACTIVE_PANEL->virtualPath(); KConfig config(("kio_" + currentURL.scheme() + "rc").toLatin1()); QString host = currentURL.host(); QString charset = KCharsets::charsets()->encodingForName(encoding); KConfigGroup group(&config, host); group.writeEntry(DATA_KEY, charset); config.sync(); // Update the io-slaves... updateKIOSlaves(); } void KrRemoteEncodingMenu::slotReload() { loadSettings(); } void KrRemoteEncodingMenu::chooseDefault() { QUrl currentURL = ACTIVE_PANEL->virtualPath(); // We have no choice but delete all higher domain level // settings here since it affects what will be matched. KConfig config(("kio_" + currentURL.scheme() + "rc").toLatin1()); - QStringList partList = currentURL.host().split('.', QString::SkipEmptyParts); + QStringList partList = currentURL.host().split('.', SKIP_EMPTY_PARTS); if (!partList.isEmpty()) { partList.erase(partList.begin()); QStringList domains; // Remove the exact name match... domains << currentURL.host(); while (partList.count()) { if (partList.count() == 2) if (partList[0].length() <= 2 && partList[1].length() == 2) break; if (partList.count() == 1) break; domains << partList.join("."); partList.erase(partList.begin()); } for (auto & domain : domains) { //qDebug() << "Domain to remove: " << *it; if (config.hasGroup(domain)) config.deleteGroup(domain); else if (config.group("").hasKey(domain)) config.group("").deleteEntry(domain); //don't know what group name is supposed to be XXX } } config.sync(); updateKIOSlaves(); } void KrRemoteEncodingMenu::updateKIOSlaves() { KIO::Scheduler::emitReparseSlaveConfiguration(); // Reload the page with the new charset QTimer::singleShot(500, ACTIVE_FUNC, SLOT(refresh())); } diff --git a/krusader/KViewer/krviewer.cpp b/krusader/KViewer/krviewer.cpp index 2d76dbfc..799c55cf 100644 --- a/krusader/KViewer/krviewer.cpp +++ b/krusader/KViewer/krviewer.cpp @@ -1,717 +1,717 @@ /***************************************************************************** * Copyright (C) 2002 Shie Erlich * * Copyright (C) 2002 Rafi Yanai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krviewer.h" // QtCore #include #include #include #include #include // QtGui #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../defaults.h" #include "../icon.h" #include "panelviewer.h" #include "viewertabbar.h" #define VIEW_ICON "document-preview" #define EDIT_ICON "document-edit" #define MODIFIED_ICON "document-save-as" #define CHECK_MODFIED_INTERVAL 500 /* NOTE: Currently the code expects PanelViewer::openUrl() to be called only once in the panel viewer's life time - otherwise unexpected things might happen. */ QList KrViewer::viewers; KrViewer::KrViewer(QWidget *parent) - : KParts::MainWindow(parent, (Qt::WindowFlags)KDE_DEFAULT_WINDOWFLAGS), manager(this, this), + : KParts::MainWindow(parent, Qt::WindowFlags()), manager(this, this), tabWidget(this), sizeX(-1), sizeY(-1) { //setWFlags(Qt::WType_TopLevel | WDestructiveClose); setXMLFile("krviewer.rc"); // kpart-related xml file setHelpMenuEnabled(false); connect(&manager, &KParts::PartManager::activePartChanged, this, &KrViewer::createGUI); connect(&tabWidget, &QTabWidget::currentChanged, this, &KrViewer::tabChanged); connect(&tabWidget, &QTabWidget::tabCloseRequested, this, [=](int index) { tabCloseRequest(index, false); }); tabWidget.setDocumentMode(true); tabWidget.setMovable(true); if (ViewerTabBar *tabBar = tabWidget.tabBar()) connect(tabBar, &ViewerTabBar::closeTabSignal, this, [=](int index) { tabCloseRequest(index, false); }); setCentralWidget(&tabWidget); printAction = KStandardAction::print(this, SLOT(print()), nullptr); copyAction = KStandardAction::copy(this, SLOT(copy()), nullptr); viewerMenu = new QMenu(this); QAction *tempAction; KActionCollection *ac = actionCollection(); #define addCustomMenuAction(name, text, slot, shortcut)\ tempAction = ac->addAction(name, this, slot);\ tempAction->setText(text);\ ac->setDefaultShortcut(tempAction, shortcut);\ viewerMenu->addAction(tempAction); addCustomMenuAction("genericViewer", i18n("&Generic Viewer"), SLOT(viewGeneric()), Qt::CTRL + Qt::SHIFT + Qt::Key_G); addCustomMenuAction("textViewer", i18n("&Text Viewer"), SLOT(viewText()), Qt::CTRL + Qt::SHIFT + Qt::Key_T); addCustomMenuAction("hexViewer", i18n("&Hex Viewer"), SLOT(viewHex()), Qt::CTRL + Qt::SHIFT + Qt::Key_H); addCustomMenuAction("lister", i18n("&Lister"), SLOT(viewLister()), Qt::CTRL + Qt::SHIFT + Qt::Key_L); viewerMenu->addSeparator(); addCustomMenuAction("textEditor", i18n("Text &Editor"), SLOT(editText()), Qt::CTRL + Qt::SHIFT + Qt::Key_E); viewerMenu->addSeparator(); QList actList = menuBar()->actions(); bool hasPrint = false, hasCopy = false; foreach(QAction *a, actList) { if (a->shortcut().matches(printAction->shortcut()) != QKeySequence::NoMatch) hasPrint = true; if (a->shortcut().matches(copyAction->shortcut()) != QKeySequence::NoMatch) hasCopy = true; } QAction *printAct = viewerMenu->addAction(printAction->icon(), printAction->text(), this, SLOT(print())); if (hasPrint) printAct->setShortcut(printAction->shortcut()); QAction *copyAct = viewerMenu->addAction(copyAction->icon(), copyAction->text(), this, SLOT(copy())); if (hasCopy) copyAct->setShortcut(copyAction->shortcut()); viewerMenu->addSeparator(); configKeysAction = ac->addAction(KStandardAction::KeyBindings, this, SLOT(configureShortcuts())); viewerMenu->addAction(configKeysAction); viewerMenu->addSeparator(); detachAction = ac->addAction("detachTab", this, SLOT(detachTab())); detachAction->setText(i18n("&Detach Tab")); //no point in detaching only one tab.. detachAction->setEnabled(false); ac->setDefaultShortcut(detachAction, Qt::META + Qt::Key_D); viewerMenu->addAction(detachAction); quitAction = ac->addAction(KStandardAction::Quit, this, SLOT(close())); viewerMenu->addAction(quitAction); QList shortcuts; tabCloseAction = ac->addAction("closeTab", this, SLOT(tabCloseRequest())); tabCloseAction->setText(i18n("&Close Current Tab")); shortcuts = KStandardShortcut::close(); shortcuts.append(Qt::Key_Escape); ac->setDefaultShortcuts(tabCloseAction, shortcuts); tabNextAction = ac->addAction("nextTab", this, SLOT(nextTab())); tabNextAction->setText(i18n("&Next Tab")); shortcuts = KStandardShortcut::tabNext(); shortcuts.append(Qt::CTRL + Qt::Key_Tab); // reenforce QTabWidget shortcut ac->setDefaultShortcuts(tabNextAction, shortcuts); tabPrevAction = ac->addAction("prevTab", this, SLOT(prevTab())); tabPrevAction->setText(i18n("&Previous Tab")); shortcuts = KStandardShortcut::tabPrev(); shortcuts.append(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab); // reenforce QTabWidget shortcut ac->setDefaultShortcuts(tabPrevAction, shortcuts); tabWidget.setTabsClosable(true); checkModified(); KConfigGroup group(krConfig, "KrViewerWindow"); int sx = group.readEntry("Window Width", -1); int sy = group.readEntry("Window Height", -1); if (sx != -1 && sy != -1) resize(sx, sy); else resize(900, 700); if (group.readEntry("Window Maximized", false)) { setWindowState(windowState() | Qt::WindowMaximized); } // filtering out the key events menuBar() ->installEventFilter(this); } KrViewer::~KrViewer() { disconnect(&manager, SIGNAL(activePartChanged(KParts::Part*)), this, SLOT(createGUI(KParts::Part*))); viewers.removeAll(this); // close tabs before deleting tab bar - this avoids Qt bug 26115 // https://bugreports.qt-project.org/browse/QTBUG-26115 while(tabWidget.count()) tabCloseRequest(tabWidget.currentIndex(), true); delete printAction; delete copyAction; } void KrViewer::configureDeps() { PanelEditor::configureDeps(); } void KrViewer::createGUI(KParts::Part* part) { KParts::MainWindow::createGUI(part); updateActions(); toolBar() ->show(); statusBar() ->show(); // the KParts part may override the viewer shortcuts. We prevent it // by installing an event filter on the menuBar() and the part reservedKeys.clear(); reservedKeyActions.clear(); QList list = viewerMenu->actions(); // also add the actions that are not in the menu... list << tabCloseAction << tabNextAction << tabPrevAction; // getting the key sequences of the viewer menu for (int w = 0; w != list.count(); w++) { QAction *act = list[ w ]; QList sequences = act->shortcuts(); foreach(QKeySequence keySeq, sequences) { for(int i = 0; i < keySeq.count(); ++i) { reservedKeys.push_back(keySeq[i]); reservedKeyActions.push_back(act); //the same action many times in case of multiple shortcuts } } } // and "fix" the menubar QList actList = menuBar()->actions(); viewerMenu->setTitle(i18n("&KrViewer")); QAction * act = menuBar() ->addMenu(viewerMenu); act->setData(QVariant(70)); menuBar() ->show(); } void KrViewer::configureShortcuts() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } bool KrViewer::eventFilter(QObject * /* watched */, QEvent * e) { // TODO: after porting to Qt5/KF5 we never catch *ANY* KeyPress or ShortcutOverride events here anymore. // Should look into if there is any way to fix it. Currently if a KPart has same shortcut as KrViewer then // it causes a conflict, messagebox shown to user and no action triggered. if (e->type() == QEvent::ShortcutOverride) { auto* ke = dynamic_cast( e); if (reservedKeys.contains(ke->key())) { ke->accept(); QAction *act = reservedKeyActions[ reservedKeys.indexOf(ke->key())]; if (act != nullptr) { // don't activate the close functions immediately! // it can cause crash if (act == tabCloseAction || act == quitAction) { QTimer::singleShot(0, act, &QAction::trigger); } else { act->activate(QAction::Trigger); } } return true; } } else if (e->type() == QEvent::KeyPress) { auto* ke = dynamic_cast( e); if (reservedKeys.contains(ke->key())) { ke->accept(); return true; } } return false; } KrViewer* KrViewer::getViewer(bool new_window) { if (!new_window) { if (viewers.isEmpty()) { viewers.prepend(new KrViewer()); // add to first (active) } else { if (viewers.first()->isMinimized()) { // minimized? -> show it again viewers.first()->setWindowState((viewers.first()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); viewers.first()->show(); } viewers.first()->raise(); viewers.first()->activateWindow(); } return viewers.first(); } else { auto *newViewer = new KrViewer(); viewers.prepend(newViewer); return newViewer; } } void KrViewer::view(QUrl url, QWidget * parent) { KConfigGroup group(krConfig, "General"); bool defaultWindow = group.readEntry("View In Separate Window", _ViewInSeparateWindow); view(std::move(url), Default, defaultWindow, parent); } void KrViewer::view(QUrl url, Mode mode, bool new_window, QWidget * parent) { KrViewer* viewer = getViewer(new_window); viewer->viewInternal(std::move(url), mode, parent); viewer->show(); } void KrViewer::edit(QUrl url, QWidget * parent) { edit(std::move(url), Text, -1, parent); } void KrViewer::edit(const QUrl& url, Mode mode, int new_window, QWidget * parent) { KConfigGroup group(krConfig, "General"); QString editor = group.readEntry("Editor", _Editor); if (new_window == -1) new_window = group.readEntry("View In Separate Window", _ViewInSeparateWindow); if (editor != "internal editor" && !editor.isEmpty()) { KProcess proc; QStringList cmdArgs = KShell::splitArgs(editor, KShell::TildeExpand); if (cmdArgs.isEmpty()) { KMessageBox::error(krMainWindow, i18nc("Arg is a string containing the bad quoting.", "Bad quoting in editor command:\n%1", editor)); return; } // if the file is local, pass a normal path and not a url. this solves // the problem for editors that aren't url-aware proc << cmdArgs << url.toDisplayString(QUrl::PreferLocalFile); if (!proc.startDetached()) KMessageBox::sorry(krMainWindow, i18n("Can not open \"%1\"", editor)); return; } KrViewer* viewer = getViewer(new_window); viewer->editInternal(url, mode, parent); viewer->show(); } void KrViewer::addTab(PanelViewerBase *pvb) { int tabIndex = tabWidget.addTab(pvb, makeTabIcon(pvb), makeTabText(pvb)); tabWidget.setCurrentIndex(tabIndex); tabWidget.setTabToolTip(tabIndex, makeTabToolTip(pvb)); updateActions(); // now we can offer the option to detach tabs (we have more than one) if (tabWidget.count() > 1) detachAction->setEnabled(true); tabWidget.adjustViewerTabBarVisibility(); tabWidget.show(); connect(pvb, &PanelViewerBase::openUrlFinished, this, &KrViewer::openUrlFinished); connect(pvb, &PanelViewerBase::urlChanged, this, &KrViewer::tabURLChanged); } void KrViewer::tabURLChanged(PanelViewerBase *pvb, const QUrl &url) { Q_UNUSED(url) refreshTab(pvb); } void KrViewer::openUrlFinished(PanelViewerBase *pvb, bool success) { if (success) { KParts::ReadOnlyPart *part = pvb->part(); if (part) { if (!isPartAdded(part)) addPart(part); if (tabWidget.currentWidget() == pvb) { manager.setActivePart(part); if (part->widget()) part->widget()->setFocus(); } } } else { tabCloseRequest(tabWidget.currentIndex(), false); } } void KrViewer::tabChanged(int index) { QWidget *w = tabWidget.widget(index); if(!w) return; KParts::ReadOnlyPart *part = dynamic_cast(w)->part(); if (part && isPartAdded(part)) { manager.setActivePart(part); if (part->widget()) part->widget()->setFocus(); } else manager.setActivePart(nullptr); // set this viewer to be the main viewer if (viewers.removeAll(this)) viewers.prepend(this); // move to first } void KrViewer::tabCloseRequest(int index, bool force) { // important to save as returnFocusTo will be cleared at removePart QWidget *returnFocusToThisWidget = returnFocusTo; auto *pvb = dynamic_cast(tabWidget.widget(index)); if (!pvb) return; if (!force && !pvb->queryClose()) return; if (pvb->part() && isPartAdded(pvb->part())) removePart(pvb->part()); disconnect(pvb, nullptr, this, nullptr); pvb->closeUrl(); tabWidget.removeTab(index); delete pvb; pvb = nullptr; if (tabWidget.count() <= 0) { if (returnFocusToThisWidget) { returnFocusToThisWidget->raise(); returnFocusToThisWidget->activateWindow(); } else { krMainWindow->raise(); krMainWindow->activateWindow(); } QTimer::singleShot(0, this, &KrViewer::close); } else if (tabWidget.count() == 1) { // no point in detaching only one tab.. detachAction->setEnabled(false); tabWidget.adjustViewerTabBarVisibility(); } } void KrViewer::tabCloseRequest() { tabCloseRequest(tabWidget.currentIndex()); } bool KrViewer::queryClose() { KConfigGroup group(krConfig, "KrViewerWindow"); group.writeEntry("Window Width", sizeX); group.writeEntry("Window Height", sizeY); group.writeEntry("Window Maximized", isMaximized()); for (int i = 0; i != tabWidget.count(); i++) { auto* pvb = dynamic_cast(tabWidget.widget(i)); if (!pvb) continue; tabWidget.setCurrentIndex(i); if (!pvb->queryClose()) return false; } return true; } void KrViewer::viewGeneric() { auto* pvb = dynamic_cast(tabWidget.currentWidget()); if (pvb) viewInternal(pvb->url(), Generic); } void KrViewer::viewText() { auto* pvb = dynamic_cast(tabWidget.currentWidget()); if (pvb) viewInternal(pvb->url(), Text); } void KrViewer::viewLister() { auto* pvb = dynamic_cast(tabWidget.currentWidget()); if (pvb) viewInternal(pvb->url(), Lister); } void KrViewer::viewHex() { auto* pvb = dynamic_cast(tabWidget.currentWidget()); if (pvb) viewInternal(pvb->url(), Hex); } void KrViewer::editText() { auto* pvb = dynamic_cast(tabWidget.currentWidget()); if (pvb) editInternal(pvb->url(), Text); } void KrViewer::checkModified() { QTimer::singleShot(CHECK_MODFIED_INTERVAL, this, &KrViewer::checkModified); auto* pvb = dynamic_cast(tabWidget.currentWidget()); if (pvb) refreshTab(pvb); } void KrViewer::refreshTab(PanelViewerBase* pvb) { int ndx = tabWidget.indexOf(pvb); tabWidget.setTabText(ndx, makeTabText(pvb)); tabWidget.setTabIcon(ndx, makeTabIcon(pvb)); tabWidget.setTabToolTip(ndx, makeTabToolTip(pvb)); } void KrViewer::nextTab() { int index = (tabWidget.currentIndex() + 1) % tabWidget.count(); tabWidget.setCurrentIndex(index); } void KrViewer::prevTab() { int index = (tabWidget.currentIndex() - 1) % tabWidget.count(); while (index < 0) index += tabWidget.count(); tabWidget.setCurrentIndex(index); } void KrViewer::detachTab() { auto* pvb = dynamic_cast(tabWidget.currentWidget()); if (!pvb) return; KrViewer* viewer = getViewer(true); bool wasPartAdded = false; KParts::ReadOnlyPart *part = pvb->part(); if (part && isPartAdded(part)) { wasPartAdded = true; removePart(part); } disconnect(pvb, nullptr, this, nullptr); tabWidget.removeTab(tabWidget.indexOf(pvb)); if (tabWidget.count() == 1) { //no point in detaching only one tab.. detachAction->setEnabled(false); tabWidget.adjustViewerTabBarVisibility(); } pvb->setParent(&viewer->tabWidget); pvb->move(QPoint(0, 0)); viewer->addTab(pvb); if (wasPartAdded) { viewer->addPart(part); if (part->widget()) part->widget()->setFocus(); } viewer->show(); } void KrViewer::changeEvent(QEvent *e) { if (e->type() == QEvent::ActivationChange && isActiveWindow()) if (viewers.removeAll(this)) viewers.prepend(this); // move to first } void KrViewer::print() { auto* pvb = dynamic_cast(tabWidget.currentWidget()); if (!pvb || !pvb->part() || !isPartAdded(pvb->part())) return; KParts::BrowserExtension * ext = KParts::BrowserExtension::childObject(pvb->part()); if (ext && ext->isActionEnabled("print")) Invoker(ext, SLOT(print())).invoke(); } void KrViewer::copy() { auto* pvb = dynamic_cast(tabWidget.currentWidget()); if (!pvb || !pvb->part() || !isPartAdded(pvb->part())) return; KParts::BrowserExtension * ext = KParts::BrowserExtension::childObject(pvb->part()); if (ext && ext->isActionEnabled("copy")) Invoker(ext, SLOT(copy())).invoke(); } void KrViewer::updateActions() { QList actList = toolBar()->actions(); bool hasPrint = false, hasCopy = false; foreach(QAction *a, actList) { if (a->text() == printAction->text()) hasPrint = true; if (a->text() == copyAction->text()) hasCopy = true; } if (!hasPrint) toolBar()->addAction(printAction->icon(), printAction->text(), this, SLOT(print())); if (!hasCopy) toolBar()->addAction(copyAction->icon(), copyAction->text(), this, SLOT(copy())); } bool KrViewer::isPartAdded(KParts::Part* part) { return manager.parts().contains(part); } void KrViewer::resizeEvent(QResizeEvent *e) { if (!isMaximized()) { sizeX = e->size().width(); sizeY = e->size().height(); } KParts::MainWindow::resizeEvent(e); } QString KrViewer::makeTabText(PanelViewerBase *pvb) { QString fileName = pvb->url().fileName(); if (pvb->isModified()) fileName.prepend("*"); return pvb->isEditor() ? i18nc("filename (filestate)", "%1 (Editing)", fileName) : i18nc("filename (filestate)", "%1 (Viewing)", fileName); } QString KrViewer::makeTabToolTip(PanelViewerBase *pvb) { QString url = pvb->url().toDisplayString(QUrl::PreferLocalFile); return pvb->isEditor() ? i18nc("filestate: filename", "Editing: %1", url) : i18nc("filestate: filename", "Viewing: %1", url); } QIcon KrViewer::makeTabIcon(PanelViewerBase *pvb) { QString iconName; if (pvb->isModified()) iconName = MODIFIED_ICON; else if (pvb->isEditor()) iconName = EDIT_ICON; else iconName = VIEW_ICON; return Icon(iconName); } void KrViewer::addPart(KParts::ReadOnlyPart *part) { Q_ASSERT(part); Q_ASSERT(!isPartAdded(part)); if (isPartAdded(part)) { qDebug()<<"part already added:"<installEventFilter(this); manager.addPart(part, false); // don't automatically set active part } void KrViewer::removePart(KParts::ReadOnlyPart *part) { Q_ASSERT(part); Q_ASSERT(isPartAdded(part)); if (isPartAdded(part)) { disconnect(part, nullptr, this, nullptr); part->removeEventFilter(this); manager.removePart(part); } else qDebug()<<"part hasn't been added:"<openUrl(std::move(url)); } void KrViewer::editInternal(QUrl url, Mode mode, QWidget * parent) { returnFocusTo = parent; PanelViewerBase* editWidget = new PanelEditor(&tabWidget, mode); addTab(editWidget); editWidget->openUrl(std::move(url)); } diff --git a/krusader/KViewer/lister.cpp b/krusader/KViewer/lister.cpp index 28e38905..4295a24d 100644 --- a/krusader/KViewer/lister.cpp +++ b/krusader/KViewer/lister.cpp @@ -1,2277 +1,2277 @@ /***************************************************************************** * Copyright (C) 2009 Csaba Karai * * Copyright (C) 2009-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "lister.h" // QtCore #include #include #include #include #include // QtGui #include #include #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include // QtPrintSupport #include #include #include #include #include #include #include #include #include #include #include #include #include "../krglobal.h" #include "../icon.h" #include "../kractions.h" #include "../GUI/krremoteencodingmenu.h" #include "../compat.h" #define SEARCH_CACHE_CHARS 100000 #define SEARCH_MAX_ROW_LEN 4000 #define CONTROL_CHAR 752 #define CACHE_SIZE 1048576 // cache size set to 1MiB ListerTextArea::ListerTextArea(Lister *lister, QWidget *parent) : KTextEdit(parent), _lister(lister) { connect(this, &QTextEdit::cursorPositionChanged, this, &ListerTextArea::slotCursorPositionChanged); _tabWidth = 4; setWordWrapMode(QTextOption::NoWrap); setLineWrapMode(QTextEdit::NoWrap); // zoom shortcuts connect(new QShortcut(QKeySequence("Ctrl++"), this), &QShortcut::activated, this, [=]() { zoomIn(); }); connect(new QShortcut(QKeySequence("Ctrl+-"), this), &QShortcut::activated, this, [=]() { zoomOut(); }); // start cursor blinking connect(&_blinkTimer, &QTimer::timeout, this, [=] { if (!_cursorBlinkMutex.tryLock()) { return; } setCursorWidth(cursorWidth() == 0 ? 2 : 0); _cursorBlinkMutex.unlock(); }); _blinkTimer.start(500); } void ListerTextArea::reset() { _screenStartPos = 0; _cursorPos = 0; _cursorAnchorPos = -1; _cursorAtFirstColumn = true; calculateText(); } void ListerTextArea::sizeChanged() { if (_cursorAnchorPos > _lister->fileSize()) _cursorAnchorPos = -1; if (_cursorPos > _lister->fileSize()) _cursorPos = _lister->fileSize(); redrawTextArea(true); } void ListerTextArea::resizeEvent(QResizeEvent * event) { KTextEdit::resizeEvent(event); redrawTextArea(); } void ListerTextArea::calculateText(const bool forcedUpdate) { const QRect contentRect = viewport()->contentsRect(); const QFontMetrics fm(font()); const int fontHeight = std::max(fm.height(), 1); // This is quite accurate (although not perfect) way of getting // a single character width along with its surrounding space. const float fontWidth = (fm.QFONTMETRICS_WIDTH("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW") - fm.QFONTMETRICS_WIDTH("W")) / 99.0; const int sizeY = contentRect.height() / fontHeight; _pageSize = sizeY; const int textViewportWidth = std::max(contentRect.width() - (int) fontWidth, 0); SET_TAB_STOP_DISTANCE(fontWidth * _tabWidth); const int sizeX = textViewportWidth / fontWidth; _sizeChanged = (_sizeY != sizeY) || (_sizeX != sizeX) || forcedUpdate; _sizeY = sizeY; _sizeX = sizeX; QList rowStarts; QStringList list = readLines(_screenStartPos, _screenEndPos, _sizeY, &rowStarts); if (_sizeChanged) { _averagePageSize = _screenEndPos - _screenStartPos; setUpScrollBar(); } const QStringList listRemn = readLines(_screenEndPos, _screenEndPos, 1); list << listRemn; if (list != _rowContent) { _cursorBlinkMutex.lock(); _blinkTimer.stop(); setCursorWidth(0); setPlainText(list.join("\n")); if (_cursorAnchorPos == -1 || _cursorAnchorPos == _cursorPos) { clearSelection(); _blinkTimer.start(500); } _cursorBlinkMutex.unlock(); _rowContent = list; _rowStarts = rowStarts; if (_rowStarts.size() < _sizeY) { _rowStarts << _screenEndPos; } } } qint64 ListerTextArea::textToFilePositionOnScreen(const int x, const int y, bool &isfirst) { isfirst = (x == 0); if (y >= _rowStarts.count()) { return 0; } const qint64 rowStart = _rowStarts[ y ]; if (x == 0) { return rowStart; } if (_hexMode) { const qint64 pos = rowStart + _lister->hexPositionToIndex(_sizeX, x); if (pos > _lister->fileSize()) { return _lister->fileSize(); } return pos; } // we can't use fromUnicode because of the invalid encoded chars const int maxBytes = 2 * _sizeX * MAX_CHAR_LENGTH; QByteArray chunk = _lister->cacheChunk(rowStart, maxBytes); QTextStream stream(&chunk); stream.setCodec(_lister->codec()); stream.read(x); return rowStart + stream.pos(); } void ListerTextArea::fileToTextPositionOnScreen(const qint64 p, const bool isfirst, int &x, int &y) { // check if cursor is outside of visible area if (p < _screenStartPos || p > _screenEndPos || _rowStarts.count() < 1) { x = -1; y = (p > _screenEndPos) ? -2 : -1; return; } // find row y = 0; while (y < _rowStarts.count() && _rowStarts[ y ] <= p) { y++; } y--; if (y < 0) { x = y = -1; return; } const qint64 rowStart = _rowStarts[ y ]; if (_hexMode) { x = _lister->hexIndexToPosition(_sizeX, (int)(p - rowStart)); return; } // find column const int maxBytes = 2 * _sizeX * MAX_CHAR_LENGTH; x = 0; if (rowStart >= p) { if ((rowStart == p) && !isfirst && y > 0) { const qint64 previousRow = _rowStarts[ y - 1 ]; const QByteArray chunk = _lister->cacheChunk(previousRow, maxBytes); QByteArray cachedBuffer = chunk.left(p - previousRow); QTextStream stream(&cachedBuffer); stream.setCodec(_lister->codec()); stream.read(_rowContent[ y - 1].length()); if (previousRow + stream.pos() == p) { y--; x = _rowContent[ y ].length(); } } return; } const QByteArray chunk = _lister->cacheChunk(rowStart, maxBytes); const QByteArray cachedBuffer = chunk.left(p - rowStart); x = _lister->codec()->toUnicode(cachedBuffer).length(); } void ListerTextArea::getCursorPosition(int &x, int &y) { getScreenPosition(textCursor().position(), x, y); } void ListerTextArea::getScreenPosition(const int position, int &x, int &y) { x = position; y = 0; foreach (const QString &row, _rowContent) { const int rowLen = row.length() + 1; if (x < rowLen) { return; } x -= rowLen; y++; } } void ListerTextArea::setCursorPositionOnScreen(const int x, const int y, const int anchorX, const int anchorY) { setCursorWidth(0); int finalX = x; int finalY = y; if (finalX == -1 || finalY < 0) { if (anchorY == -1) { return; } if (finalY == -2) { finalY = _sizeY; finalX = (_rowContent.count() > _sizeY) ? _rowContent[ _sizeY ].length() : 0; } else finalX = finalY = 0; } const int realSizeY = std::min(_sizeY + 1, _rowContent.count()); const auto setUpCursor = [&] (const int cursorX, const int cursorY, const QTextCursor::MoveMode mode) -> bool { if (cursorY > realSizeY) { return false; } _skipCursorChangedListener = true; moveCursor(QTextCursor::Start, mode); for (int i = 0; i < cursorY; i++) { moveCursor(QTextCursor::Down, mode); } int finalCursorX = cursorX; if (_rowContent.count() > cursorY && finalCursorX > _rowContent[ cursorY ].length()) { finalCursorX = _rowContent[ cursorY ].length(); } for (int i = 0; i < finalCursorX; i++) { moveCursor(QTextCursor::Right, mode); } _skipCursorChangedListener = false; return true; }; QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; // set cursor anchor if (anchorX != -1 && anchorY != -1) { const bool canContinue = setUpCursor(anchorX, anchorY, mode); if (!canContinue) { return; } mode = QTextCursor::KeepAnchor; } // set cursor position setUpCursor(finalX, finalY, mode); } qint64 ListerTextArea::getCursorPosition(bool &isfirst) { if (cursorWidth() == 0) { isfirst = _cursorAtFirstColumn; return _cursorPos; } int x, y; getCursorPosition(x, y); return textToFilePositionOnScreen(x, y, isfirst); } void ListerTextArea::setCursorPositionInDocument(const qint64 p, const bool isfirst) { _cursorPos = p; int x, y; fileToTextPositionOnScreen(p, isfirst, x, y); bool startBlinkTimer = _screenStartPos <= _cursorPos && _cursorPos <= _screenEndPos; int anchorX = -1, anchorY = -1; if (_cursorAnchorPos != -1 && _cursorAnchorPos != p) { int anchPos = _cursorAnchorPos; bool anchorBelow = false, anchorAbove = false; if (anchPos < _screenStartPos) { anchPos = _screenStartPos; anchorY = -2; anchorAbove = true; } if (anchPos > _screenEndPos) { anchPos = _screenEndPos; anchorY = -3; anchorBelow = true; } fileToTextPositionOnScreen(anchPos, isfirst, anchorX, anchorY); if (_hexMode) { if (anchorAbove) { anchorX = 0; } if (anchorBelow && _rowContent.count() > 0) { anchorX = _rowContent[ 0 ].length(); } } startBlinkTimer = startBlinkTimer && !anchorAbove && !anchorBelow; } if (startBlinkTimer) { _blinkTimer.start(500); } setCursorPositionOnScreen(x, y, anchorX, anchorY); _lister->slotUpdate(); } void ListerTextArea::slotCursorPositionChanged() { if (_skipCursorChangedListener) { return; } int cursorX, cursorY; getCursorPosition(cursorX, cursorY); _cursorAtFirstColumn = (cursorX == 0); _cursorPos = textToFilePositionOnScreen(cursorX, cursorY, _cursorAtFirstColumn); _lister->slotUpdate(); } QString ListerTextArea::readSection(const qint64 p1, const qint64 p2) { if (p1 == p2) return QString(); qint64 sel1 = p1; qint64 sel2 = p2; if (sel1 > sel2) { std::swap(sel1, sel2); } QString section; if (_hexMode) { while (sel1 != sel2) { const QStringList list = _lister->readHexLines(sel1, sel2, _sizeX, 1); if (list.isEmpty()) { break; } if (!section.isEmpty()) { section += QChar('\n'); } section += list.at(0); } return section; } qint64 pos = sel1; QScopedPointer decoder(_lister->codec()->makeDecoder()); do { const int maxBytes = std::min(_sizeX * _sizeY * MAX_CHAR_LENGTH, (int) (sel2 - pos)); const QByteArray chunk = _lister->cacheChunk(pos, maxBytes); if (chunk.isEmpty()) break; section += decoder->toUnicode(chunk); pos += chunk.size(); } while (pos < sel2); return section; } QStringList ListerTextArea::readLines(qint64 filePos, qint64 &endPos, const int lines, QList * locs) { QStringList list; if (_hexMode) { endPos = _lister->fileSize(); if (filePos >= endPos) { return list; } const int bytes = _lister->hexBytesPerLine(_sizeX); qint64 startPos = (filePos / bytes) * bytes; qint64 shiftPos = startPos; list = _lister->readHexLines(shiftPos, endPos, _sizeX, lines); endPos = shiftPos; if (locs) { for (int i = 0; i < list.count(); i++) { (*locs) << startPos; startPos += bytes; } } return list; } endPos = filePos; const int maxBytes = _sizeX * _sizeY * MAX_CHAR_LENGTH; const QByteArray chunk = _lister->cacheChunk(filePos, maxBytes); if (chunk.isEmpty()) return list; int byteCounter = 0; QString row = ""; int effLength = 0; if (locs) (*locs) << filePos; bool skipImmediateNewline = false; const auto performNewline = [&] (qint64 nextRowStartOffset) { list << row; effLength = 0; row = ""; if (locs) { (*locs) << (filePos + nextRowStartOffset); } }; QScopedPointer decoder(_lister->codec()->makeDecoder()); while (byteCounter < chunk.size() && list.size() < lines) { const int lastCnt = byteCounter; QString chr = decoder->toUnicode(chunk.mid(byteCounter++, 1)); if (chr.isEmpty()) { continue; } if ((chr[ 0 ] < 32) && (chr[ 0 ] != '\n') && (chr[ 0 ] != '\t')) { chr = QChar(CONTROL_CHAR); } if (chr == "\n") { if (!skipImmediateNewline) { performNewline(byteCounter); } skipImmediateNewline = false; continue; } skipImmediateNewline = false; if (chr == "\t") { effLength += _tabWidth - (effLength % _tabWidth) - 1; if (effLength > _sizeX) { performNewline(lastCnt); } } row += chr; effLength++; if (effLength >= _sizeX) { performNewline(byteCounter); skipImmediateNewline = true; } } if (list.size() < lines) list << row; endPos = filePos + byteCounter; return list; } void ListerTextArea::setUpScrollBar() { if (_averagePageSize == _lister->fileSize()) { _lister->scrollBar()->setPageStep(0); _lister->scrollBar()->setMaximum(0); _lister->scrollBar()->hide(); _lastPageStartPos = 0; } else { const int maxPage = MAX_CHAR_LENGTH * _sizeX * _sizeY; qint64 pageStartPos = _lister->fileSize() - maxPage; qint64 endPos; if (pageStartPos < 0) pageStartPos = 0; QStringList list = readLines(pageStartPos, endPos, maxPage); if (list.count() <= _sizeY) { _lastPageStartPos = 0; } else { readLines(pageStartPos, _lastPageStartPos, list.count() - _sizeY); } const int maximum = (_lastPageStartPos > SLIDER_MAX) ? SLIDER_MAX : _lastPageStartPos; int pageSize = (_lastPageStartPos > SLIDER_MAX) ? SLIDER_MAX * _averagePageSize / _lastPageStartPos : _averagePageSize; if (pageSize == 0) pageSize++; _lister->scrollBar()->setPageStep(pageSize); _lister->scrollBar()->setMaximum(maximum); _lister->scrollBar()->show(); } } void ListerTextArea::keyPressEvent(QKeyEvent * ke) { if (KrGlobal::copyShortcut == QKeySequence(ke->key() | ke->modifiers())) { copySelectedToClipboard(); ke->accept(); return; } if (ke->modifiers() == Qt::NoModifier || ke->modifiers() & Qt::ShiftModifier) { qint64 newAnchor = -1; if (ke->modifiers() & Qt::ShiftModifier) { newAnchor = _cursorAnchorPos; if (_cursorAnchorPos == -1) newAnchor = _cursorPos; } switch (ke->key()) { case Qt::Key_F3: ke->accept(); if (ke->modifiers() == Qt::ShiftModifier) _lister->searchPrev(); else _lister->searchNext(); return; case Qt::Key_Home: case Qt::Key_End: _cursorAnchorPos = newAnchor; break; case Qt::Key_Left: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); int x, y; getCursorPosition(x, y); if (y == 0 && x == 0) slotActionTriggered(QAbstractSlider::SliderSingleStepSub); } break; case Qt::Key_Right: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); if (textCursor().position() == toPlainText().length()) slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); } break; case Qt::Key_Up: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); int x, y; getCursorPosition(x, y); if (y == 0) slotActionTriggered(QAbstractSlider::SliderSingleStepSub); } break; case Qt::Key_Down: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); int x, y; getCursorPosition(x, y); if (y >= _sizeY-1) slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); } break; case Qt::Key_PageDown: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); ke->accept(); int x, y; getCursorPosition(x, y); slotActionTriggered(QAbstractSlider::SliderPageStepAdd); y += _sizeY - _skippedLines; if (y > _rowContent.count()) { y = _rowContent.count() - 1; if (y > 0) x = _rowContent[ y - 1 ].length(); else x = 0; } _cursorPos = textToFilePositionOnScreen(x, y, _cursorAtFirstColumn); setCursorPositionInDocument(_cursorPos, _cursorAtFirstColumn); } return; case Qt::Key_PageUp: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); ke->accept(); int x, y; getCursorPosition(x, y); slotActionTriggered(QAbstractSlider::SliderPageStepSub); y -= _sizeY - _skippedLines; if (y < 0) { y = 0; x = 0; } _cursorPos = textToFilePositionOnScreen(x, y, _cursorAtFirstColumn); setCursorPositionInDocument(_cursorPos, _cursorAtFirstColumn); } return; } } if (ke->modifiers() == Qt::ControlModifier) { switch (ke->key()) { case Qt::Key_G: ke->accept(); _lister->jumpToPosition(); return; case Qt::Key_F: ke->accept(); _lister->enableSearch(true); return; case Qt::Key_Home: _cursorAnchorPos = -1; ke->accept(); slotActionTriggered(QAbstractSlider::SliderToMinimum); setCursorPositionInDocument((qint64)0, true); return; case Qt::Key_A: case Qt::Key_End: { _cursorAnchorPos = (ke->key() == Qt::Key_A) ? 0 : -1; ke->accept(); slotActionTriggered(QAbstractSlider::SliderToMaximum); const qint64 endPos = _lister->fileSize(); setCursorPositionInDocument(endPos, false); return; } case Qt::Key_Down: ke->accept(); slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); return; case Qt::Key_Up: ke->accept(); slotActionTriggered(QAbstractSlider::SliderSingleStepSub); return; case Qt::Key_PageDown: ke->accept(); slotActionTriggered(QAbstractSlider::SliderPageStepAdd); return; case Qt::Key_PageUp: ke->accept(); slotActionTriggered(QAbstractSlider::SliderPageStepSub); return; } } const int oldAnchor = textCursor().anchor(); KTextEdit::keyPressEvent(ke); handleAnchorChange(oldAnchor); } void ListerTextArea::mousePressEvent(QMouseEvent * e) { KTextEdit::mousePressEvent(e); // do change anchor only when shift is not pressed if (!(QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)) { performAnchorChange(textCursor().anchor()); } } void ListerTextArea::mouseDoubleClickEvent(QMouseEvent * e) { _cursorAnchorPos = -1; const int oldAnchor = textCursor().anchor(); KTextEdit::mouseDoubleClickEvent(e); handleAnchorChange(oldAnchor); } void ListerTextArea::mouseMoveEvent(QMouseEvent * e) { if (e->pos().y() < 0) { slotActionTriggered(QAbstractSlider::SliderSingleStepSub); } else if (e->pos().y() > height()) { slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); } KTextEdit::mouseMoveEvent(e); } void ListerTextArea::wheelEvent(QWheelEvent * e) { - int delta = e->delta(); + int delta = e->angleDelta().y(); if (delta) { // zooming if (e->modifiers() & Qt::ControlModifier) { e->accept(); if (delta > 0) { zoomIn(); } else { zoomOut(); } return; } if (delta > 0) { e->accept(); while (delta > 0) { slotActionTriggered(QAbstractSlider::SliderSingleStepSub); slotActionTriggered(QAbstractSlider::SliderSingleStepSub); slotActionTriggered(QAbstractSlider::SliderSingleStepSub); delta -= 120; } } else { e->accept(); while (delta < 0) { slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); delta += 120; } } setCursorPositionInDocument(_cursorPos, false); } } void ListerTextArea::slotActionTriggered(int action) { switch (action) { case QAbstractSlider::SliderSingleStepAdd: { qint64 endPos; readLines(_screenStartPos, endPos, 1); if (endPos <= _lastPageStartPos) { _screenStartPos = endPos; } } break; case QAbstractSlider::SliderSingleStepSub: { if (_screenStartPos == 0) { break; } if (_hexMode) { int bytesPerRow = _lister->hexBytesPerLine(_sizeX); _screenStartPos = (_screenStartPos / bytesPerRow) * bytesPerRow; _screenStartPos -= bytesPerRow; if (_screenStartPos < 0) { _screenStartPos = 0; } break; } int maxSize = _sizeX * _sizeY * MAX_CHAR_LENGTH; const QByteArray encodedEnter = _lister->codec()->fromUnicode(QString("\n")); qint64 readPos = _screenStartPos - maxSize; if (readPos < 0) { readPos = 0; } maxSize = _screenStartPos - readPos; const QByteArray chunk = _lister->cacheChunk(readPos, maxSize); int from = chunk.size(); while (from > 0) { from--; from = chunk.lastIndexOf(encodedEnter, from); if (from == -1) { from = 0; break; } const int backRef = std::max(from - 20, 0); const int size = from - backRef + encodedEnter.size(); const QString decoded = _lister->codec()->toUnicode(chunk.mid(backRef, size)); if (decoded.endsWith(QLatin1String("\n"))) { if (from < (chunk.size() - encodedEnter.size())) { from += encodedEnter.size(); break; } } } readPos += from; qint64 previousPos = readPos; while (readPos < _screenStartPos) { previousPos = readPos; readLines(readPos, readPos, 1); } _screenStartPos = previousPos; } break; case QAbstractSlider::SliderPageStepAdd: { _skippedLines = 0; qint64 endPos; for (int i = 0; i < _sizeY; i++) { readLines(_screenStartPos, endPos, 1); if (endPos <= _lastPageStartPos) { _screenStartPos = endPos; _skippedLines++; } else { break; } } } break; case QAbstractSlider::SliderPageStepSub: { _skippedLines = 0; if (_screenStartPos == 0) { break; } if (_hexMode) { const int bytesPerRow = _lister->hexBytesPerLine(_sizeX); _screenStartPos = (_screenStartPos / bytesPerRow) * bytesPerRow; _screenStartPos -= _sizeY * bytesPerRow; if (_screenStartPos < 0) { _screenStartPos = 0; } break; } // text lister mode int maxSize = 2 * _sizeX * _sizeY * MAX_CHAR_LENGTH; const QByteArray encodedEnter = _lister->codec()->fromUnicode(QString("\n")); qint64 readPos = _screenStartPos - maxSize; if (readPos < 0) readPos = 0; maxSize = _screenStartPos - readPos; const QByteArray chunk = _lister->cacheChunk(readPos, maxSize); maxSize = chunk.size(); int sizeY = _sizeY + 1; int origSizeY = sizeY; int from = maxSize; int lastEnter = maxSize; bool readNext = true; while (readNext) { readNext = false; while (from > 0) { from--; from = chunk.lastIndexOf(encodedEnter, from); if (from == -1) { from = 0; break; } const int backRef = std::max(from - 20, 0); const int size = from - backRef + encodedEnter.size(); QString decoded = _lister->codec()->toUnicode(chunk.mid(backRef, size)); if (decoded.endsWith(QLatin1String("\n"))) { if (from < (maxSize - encodedEnter.size())) { int arrayStart = from + encodedEnter.size(); decoded = _lister->codec()->toUnicode(chunk.mid(arrayStart, lastEnter - arrayStart)); sizeY -= ((decoded.length() / (_sizeX + 1)) + 1); if (sizeY < 0) { from = arrayStart; break; } } lastEnter = from; } } qint64 searchPos = readPos + from; QList locs; while (searchPos < _screenStartPos) { locs << searchPos; readLines(searchPos, searchPos, 1); } if (locs.count() >= _sizeY) { _screenStartPos = locs[ locs.count() - _sizeY ]; } else if (from != 0) { origSizeY += locs.count() + 1; sizeY = origSizeY; readNext = true; } else if (readPos == 0) { _screenStartPos = 0; } } } break; case QAbstractSlider::SliderToMinimum: _screenStartPos = 0; break; case QAbstractSlider::SliderToMaximum: _screenStartPos = _lastPageStartPos; break; case QAbstractSlider::SliderMove: { if (_inSliderOp) // self created call? return; qint64 pos = _lister->scrollBar()->sliderPosition(); if (pos == SLIDER_MAX) { _screenStartPos = _lastPageStartPos; break; } else if (pos == 0) { _screenStartPos = 0; break; } if (_lastPageStartPos > SLIDER_MAX) pos = _lastPageStartPos * pos / SLIDER_MAX; if (pos != 0) { if (_hexMode) { const int bytesPerRow = _lister->hexBytesPerLine(_sizeX); pos = (pos / bytesPerRow) * bytesPerRow; } else { const int maxSize = _sizeX * _sizeY * MAX_CHAR_LENGTH; qint64 readPos = pos - maxSize; if (readPos < 0) readPos = 0; qint64 previousPos = readPos; while (readPos <= pos) { previousPos = readPos; readLines(readPos, readPos, 1); } pos = previousPos; } } _screenStartPos = pos; } break; case QAbstractSlider::SliderNoAction: break; }; _inSliderOp = true; const int value = (_lastPageStartPos > SLIDER_MAX) ? SLIDER_MAX * _screenStartPos / _lastPageStartPos : _screenStartPos; _lister->scrollBar()->setSliderPosition(value); _inSliderOp = false; redrawTextArea(); } void ListerTextArea::redrawTextArea(bool forcedUpdate) { if (_redrawing) { return; } _redrawing = true; bool isfirst; const qint64 pos = getCursorPosition(isfirst); calculateText(forcedUpdate); setCursorPositionInDocument(pos, isfirst); _redrawing = false; } void ListerTextArea::ensureVisibleCursor() { if (_screenStartPos <= _cursorPos && _cursorPos <= _screenEndPos) { return; } int delta = _sizeY / 2; if (delta == 0) delta++; qint64 newScreenStart = _cursorPos; while (delta) { const int maxSize = _sizeX * MAX_CHAR_LENGTH; qint64 readPos = newScreenStart - maxSize; if (readPos < 0) readPos = 0; qint64 previousPos = readPos; while (readPos < newScreenStart) { previousPos = readPos; readLines(readPos, readPos, 1); if (readPos == previousPos) break; } newScreenStart = previousPos; delta--; } if (newScreenStart > _lastPageStartPos) { newScreenStart = _lastPageStartPos; } _screenStartPos = newScreenStart; slotActionTriggered(QAbstractSlider::SliderNoAction); } void ListerTextArea::setAnchorAndCursor(qint64 anchor, qint64 cursor) { _cursorPos = cursor; _cursorAnchorPos = anchor; ensureVisibleCursor(); setCursorPositionInDocument(cursor, false); } QString ListerTextArea::getSelectedText() { if (_cursorAnchorPos != -1 && _cursorAnchorPos != _cursorPos) { return readSection(_cursorAnchorPos, _cursorPos); } return QString(); } void ListerTextArea::copySelectedToClipboard() { const QString selection = getSelectedText(); if (!selection.isEmpty()) { QApplication::clipboard()->setText(selection); } } void ListerTextArea::clearSelection() { QTextCursor cursor = textCursor(); cursor.clearSelection(); setTextCursor(cursor); _cursorAnchorPos = -1; } void ListerTextArea::performAnchorChange(int anchor) { int x, y; bool isfirst; getScreenPosition(anchor, x, y); _cursorAnchorPos = textToFilePositionOnScreen(x, y, isfirst); } void ListerTextArea::handleAnchorChange(int oldAnchor) { const int anchor = textCursor().anchor(); if (oldAnchor != anchor) { performAnchorChange(anchor); } } void ListerTextArea::setHexMode(bool hexMode) { bool isfirst; const qint64 pos = getCursorPosition(isfirst); _hexMode = hexMode; _screenStartPos = 0; calculateText(true); setCursorPositionInDocument(pos, isfirst); ensureVisibleCursor(); } void ListerTextArea::zoomIn(int range) { KTextEdit::zoomIn(range); redrawTextArea(); } void ListerTextArea::zoomOut(int range) { KTextEdit::zoomOut(range); redrawTextArea(); } ListerPane::ListerPane(Lister *lister, QWidget *parent) : QWidget(parent), _lister(lister) { } bool ListerPane::event(QEvent *e) { const bool handled = ListerPane::handleCloseEvent(e); if (!handled) { return QWidget::event(e); } return true; } bool ListerPane::handleCloseEvent(QEvent *e) { if (e->type() == QEvent::ShortcutOverride) { auto *ke = dynamic_cast(e); if (ke->key() == Qt::Key_Escape) { if (_lister->isSearchEnabled()) { _lister->searchDelete(); _lister->enableSearch(false); ke->accept(); return true; } if (!_lister->textArea()->getSelectedText().isEmpty()) { _lister->textArea()->clearSelection(); ke->accept(); return true; } } } return false; } ListerBrowserExtension::ListerBrowserExtension(Lister * lister) : KParts::BrowserExtension(lister) { _lister = lister; emit enableAction("copy", true); emit enableAction("print", true); } void ListerBrowserExtension::copy() { _lister->textArea()->copySelectedToClipboard(); } void ListerBrowserExtension::print() { _lister->print(); } class ListerEncodingMenu : public KrRemoteEncodingMenu { public: ListerEncodingMenu(Lister *lister, const QString &text, const QString &icon, KActionCollection *parent) : KrRemoteEncodingMenu(text, icon, parent), _lister(lister) { } protected: QString currentCharacterSet() override { return _lister->characterSet(); } void chooseDefault() override { _lister->setCharacterSet(QString()); } void chooseEncoding(QString encodingName) override { QString charset = KCharsets::charsets()->encodingForName(encodingName); _lister->setCharacterSet(charset); } Lister * _lister; }; Lister::Lister(QWidget *parent) : KParts::ReadOnlyPart(parent) { setXMLFile("krusaderlisterui.rc"); _actionSaveSelected = new QAction(Icon("document-save"), i18n("Save selection..."), this); connect(_actionSaveSelected, &QAction::triggered, this, &Lister::saveSelected); actionCollection()->addAction("save_selected", _actionSaveSelected); _actionSaveAs = new QAction(Icon("document-save-as"), i18n("Save as..."), this); connect(_actionSaveAs, &QAction::triggered, this, &Lister::saveAs); actionCollection()->addAction("save_as", _actionSaveAs); _actionPrint = new QAction(Icon("document-print"), i18n("Print..."), this); connect(_actionPrint, &QAction::triggered, this, &Lister::print); actionCollection()->addAction("print", _actionPrint); actionCollection()->setDefaultShortcut(_actionPrint, Qt::CTRL + Qt::Key_P); _actionSearch = new QAction(Icon("system-search"), i18n("Search"), this); connect(_actionSearch, &QAction::triggered, this, &Lister::searchAction); actionCollection()->addAction("search", _actionSearch); actionCollection()->setDefaultShortcut(_actionSearch, Qt::CTRL + Qt::Key_F); _actionSearchNext = new QAction(Icon("go-down"), i18n("Search next"), this); connect(_actionSearchNext, &QAction::triggered, this, &Lister::searchNext); actionCollection()->addAction("search_next", _actionSearchNext); actionCollection()->setDefaultShortcut(_actionSearchNext, Qt::Key_F3); _actionSearchPrev = new QAction(Icon("go-up"), i18n("Search previous"), this); connect(_actionSearchPrev, &QAction::triggered, this, &Lister::searchPrev); actionCollection()->addAction("search_prev", _actionSearchPrev); actionCollection()->setDefaultShortcut(_actionSearchPrev, Qt::SHIFT + Qt::Key_F3); _actionJumpToPosition = new QAction(Icon("go-jump"), i18n("Jump to position"), this); connect(_actionJumpToPosition, &QAction::triggered, this, &Lister::jumpToPosition); actionCollection()->addAction("jump_to_position", _actionJumpToPosition); actionCollection()->setDefaultShortcut(_actionJumpToPosition, Qt::CTRL + Qt::Key_G); _actionHexMode = new QAction(Icon("document-preview"), i18n("Hex mode"), this); connect(_actionHexMode, &QAction::triggered, this, &Lister::toggleHexMode); actionCollection()->addAction("hex_mode", _actionHexMode); actionCollection()->setDefaultShortcut(_actionHexMode, Qt::CTRL + Qt::Key_H); new ListerEncodingMenu(this, i18n("Select charset"), "character-set", actionCollection()); QWidget * widget = new ListerPane(this, parent); widget->setFocusPolicy(Qt::StrongFocus); auto *grid = new QGridLayout(widget); _textArea = new ListerTextArea(this, widget); _textArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); _textArea->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); _textArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _textArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); widget->setFocusProxy(_textArea); grid->addWidget(_textArea, 0, 0); _scrollBar = new QScrollBar(Qt::Vertical, widget); grid->addWidget(_scrollBar, 0, 1); _scrollBar->hide(); QWidget * statusWidget = new QWidget(widget); auto *hbox = new QHBoxLayout(statusWidget); _listerLabel = new QLabel(i18n("Lister:"), statusWidget); hbox->addWidget(_listerLabel); _searchProgressBar = new QProgressBar(statusWidget); _searchProgressBar->setMinimum(0); _searchProgressBar->setMaximum(1000); _searchProgressBar->setValue(0); _searchProgressBar->hide(); hbox->addWidget(_searchProgressBar); _searchStopButton = new QToolButton(statusWidget); _searchStopButton->setIcon(Icon("process-stop")); _searchStopButton->setToolTip(i18n("Stop search")); _searchStopButton->hide(); connect(_searchStopButton, &QToolButton::clicked, this, &Lister::searchDelete); hbox->addWidget(_searchStopButton); _searchLineEdit = new KLineEdit(statusWidget); _searchLineEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); _originalBackground = _searchLineEdit->palette().color(QPalette::Base); _originalForeground = _searchLineEdit->palette().color(QPalette::Text); connect(_searchLineEdit, &KLineEdit::returnPressed, this, &Lister::searchNext); connect(_searchLineEdit, &KLineEdit::textChanged, this, &Lister::searchTextChanged); hbox->addWidget(_searchLineEdit); _searchNextButton = new QPushButton(Icon("go-down"), i18n("Next"), statusWidget); _searchNextButton->setToolTip(i18n("Jump to next match")); connect(_searchNextButton, &QPushButton::clicked, this, &Lister::searchNext); hbox->addWidget(_searchNextButton); _searchPrevButton = new QPushButton(Icon("go-up"), i18n("Previous"), statusWidget); _searchPrevButton->setToolTip(i18n("Jump to previous match")); connect(_searchPrevButton, &QPushButton::clicked, this, &Lister::searchPrev); hbox->addWidget(_searchPrevButton); _searchOptions = new QPushButton(i18n("Options"), statusWidget); _searchOptions->setToolTip(i18n("Modify search behavior")); auto * menu = new QMenu(); _fromCursorAction = menu->addAction(i18n("From cursor")); _fromCursorAction->setCheckable(true); _fromCursorAction->setChecked(true); _caseSensitiveAction = menu->addAction(i18n("Case sensitive")); _caseSensitiveAction->setCheckable(true); _matchWholeWordsOnlyAction = menu->addAction(i18n("Match whole words only")); _matchWholeWordsOnlyAction->setCheckable(true); _regExpAction = menu->addAction(i18n("RegExp")); _regExpAction->setCheckable(true); _hexAction = menu->addAction(i18n("Hexadecimal")); _hexAction->setCheckable(true); _searchOptions->setMenu(menu); hbox->addWidget(_searchOptions); hbox->addItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); _statusLabel = new QLabel(statusWidget); hbox->addWidget(_statusLabel); grid->addWidget(statusWidget, 1, 0, 1, 2); setWidget(widget); connect(_scrollBar, &QScrollBar::actionTriggered, _textArea, &ListerTextArea::slotActionTriggered); connect(&_searchUpdateTimer, &QTimer::timeout, this, &Lister::slotUpdate); new ListerBrowserExtension(this); enableSearch(false); _tempFile = new QTemporaryFile(this); _tempFile->setFileTemplate(QDir::tempPath() + QLatin1String("/krusader_lister.XXXXXX")); } bool Lister::openUrl(const QUrl &listerUrl) { _downloading = false; setUrl(listerUrl); _fileSize = 0; if (listerUrl.isLocalFile()) { _filePath = listerUrl.path(); if (!QFile::exists(_filePath)) return false; _fileSize = getFileSize(); } else { if (_tempFile->isOpen()) { _tempFile->close(); } _tempFile->open(); _filePath = _tempFile->fileName(); KIO::TransferJob *downloadJob = KIO::get(listerUrl, KIO::NoReload, KIO::HideProgressInfo); connect(downloadJob, &KIO::TransferJob::data, this, [=](KIO::Job*, QByteArray array) { if (array.size() != 0) { _tempFile->write(array); } }); connect(downloadJob, &KIO::TransferJob::result, this, [=](KJob *job) { _tempFile->flush(); if (job->error()) { /* any error occurred? */ auto *kioJob = qobject_cast(job); KMessageBox::error(_textArea, i18n("Error reading file %1.", kioJob->url().toDisplayString(QUrl::PreferLocalFile))); } _downloading = false; _downloadUpdateTimer.stop(); slotUpdate(); }); connect(&_downloadUpdateTimer, &QTimer::timeout, this, [&]() { slotUpdate(); }); _downloadUpdateTimer.start(500); _downloading = true; } // invalidate cache _cache.clear(); _textArea->reset(); emit started(nullptr); emit setWindowCaption(listerUrl.toDisplayString()); emit completed(); return true; } QByteArray Lister::cacheChunk(const qint64 filePos, const int maxSize) { if (filePos >= _fileSize) { return QByteArray(); } int size = maxSize; if (_fileSize - filePos < size) { size = _fileSize - filePos; } if (!_cache.isEmpty() && (filePos >= _cachePos) && (filePos + size <= _cachePos + _cache.size())) { return _cache.mid(filePos - _cachePos, size); } const int negativeOffset = CACHE_SIZE * 2 / 5; qint64 cachePos = filePos - negativeOffset; if (cachePos < 0) cachePos = 0; QFile sourceFile(_filePath); if (!sourceFile.open(QIODevice::ReadOnly)) { return QByteArray(); } if (!sourceFile.seek(cachePos)) { return QByteArray(); } const QByteArray bytes = sourceFile.read(CACHE_SIZE); if (bytes.isEmpty()) { return bytes; } _cache = bytes; _cachePos = cachePos; const qint64 cacheRefIndex = filePos - _cachePos; int newSize = bytes.size() - cacheRefIndex; if (newSize < size) size = newSize; return _cache.mid(cacheRefIndex, size); } qint64 Lister::getFileSize() { return QFile(_filePath).size(); } void Lister::guiActivateEvent(KParts::GUIActivateEvent * event) { if (event->activated()) { slotUpdate(); _textArea->redrawTextArea(true); } else { enableSearch(false); } KParts::ReadOnlyPart::guiActivateEvent(event); } void Lister::slotUpdate() { const qint64 oldSize = _fileSize; _fileSize = getFileSize(); if (oldSize != _fileSize) _textArea->sizeChanged(); int cursorX = 0, cursorY = 0; _textArea->getCursorPosition(cursorX, cursorY); bool isfirst = false; const qint64 cursor = _textArea->getCursorPosition(isfirst); const int percent = (_fileSize == 0) ? 0 : (int)((201 * cursor) / _fileSize / 2); const QString status = i18n("Column: %1, Position: %2 (%3, %4%)", cursorX, cursor, _fileSize, percent); _statusLabel->setText(status); if (_searchProgressCounter) _searchProgressCounter--; } bool Lister::isSearchEnabled() { return !_searchLineEdit->isHidden() || !_searchProgressBar->isHidden(); } void Lister::enableSearch(const bool enable) { if (enable) { _listerLabel->setText(i18n("Search:")); _searchLineEdit->show(); _searchNextButton->show(); _searchPrevButton->show(); _searchOptions->show(); if (!_searchLineEdit->hasFocus()) { _searchLineEdit->setFocus(); const QString selection = _textArea->getSelectedText(); if (!selection.isEmpty()) { _searchLineEdit->setText(selection); } _searchLineEdit->selectAll(); } } else { _listerLabel->setText(i18n("Lister:")); _searchLineEdit->hide(); _searchNextButton->hide(); _searchPrevButton->hide(); _searchOptions->hide(); _textArea->setFocus(); } } void Lister::searchNext() { search(true); } void Lister::searchPrev() { search(false); } void Lister::search(const bool forward, const bool restart) { _restartFromBeginning = restart; if (_searchInProgress || _searchLineEdit->text().isEmpty()) return; if (_searchLineEdit->isHidden()) enableSearch(true); _searchPosition = forward ? 0 : _fileSize; if (_fromCursorAction->isChecked()) { bool isfirst; qint64 cursor = _textArea->getCursorPosition(isfirst); if (cursor != 0 && !forward) cursor--; if (_searchLastFailedPosition == -1 || _searchLastFailedPosition != cursor) _searchPosition = cursor; } const bool caseSensitive = _caseSensitiveAction->isChecked(); const bool matchWholeWord = _matchWholeWordsOnlyAction->isChecked(); const bool regExp = _regExpAction->isChecked(); const bool hex = _hexAction->isChecked(); if (hex) { QString hexcontent = _searchLineEdit->text(); hexcontent.remove(QLatin1String("0x")); hexcontent.remove(' '); hexcontent.remove('\t'); hexcontent.remove('\n'); hexcontent.remove('\r'); _searchHexQuery = QByteArray(); if (hexcontent.length() & 1) { setColor(false, false); return; } while (!hexcontent.isEmpty()) { const QString hexData = hexcontent.left(2); hexcontent = hexcontent.mid(2); bool ok = true; const int c = hexData.toUInt(&ok, 16); if (!ok) { setColor(false, false); return; } _searchHexQuery.push_back((char) c); } } else { _searchQuery.setContent(_searchLineEdit->text(), caseSensitive, matchWholeWord, codec()->name(), regExp); } _searchIsForward = forward; _searchHexadecimal = hex; QTimer::singleShot(0, this, &Lister::slotSearchMore); _searchInProgress = true; _searchProgressCounter = 3; enableActions(false); } void Lister::enableActions(const bool state) { _actionSearch->setEnabled(state); _actionSearchNext->setEnabled(state); _actionSearchPrev->setEnabled(state); _actionJumpToPosition->setEnabled(state); if (state) { _searchUpdateTimer.stop(); } else { slotUpdate(); } } void Lister::slotSearchMore() { if (!_searchInProgress) return; if (!_searchUpdateTimer.isActive()) { _searchUpdateTimer.start(200); } updateProgressBar(); if (!_searchIsForward) _searchPosition--; if (_searchPosition < 0 || _searchPosition >= _fileSize) { if (_restartFromBeginning) resetSearchPosition(); else { searchFailed(); return; } } int maxCacheSize = SEARCH_CACHE_CHARS; qint64 searchPos = _searchPosition; bool setPosition = true; if (!_searchIsForward) { qint64 origSearchPos = _searchPosition; searchPos -= maxCacheSize; if (searchPos <= 0) { searchPos = 0; _searchPosition = 0; setPosition = false; } qint64 diff = origSearchPos - searchPos; if (diff < maxCacheSize) maxCacheSize = diff; } const QByteArray chunk = cacheChunk(searchPos, maxCacheSize); if (chunk.isEmpty()) { searchFailed(); return; } const int chunkSize = chunk.size(); qint64 foundAnchor = -1; qint64 foundCursor = -1; int byteCounter = 0; if (_searchHexadecimal) { const int ndx = _searchIsForward ? chunk.indexOf(_searchHexQuery) : chunk.lastIndexOf(_searchHexQuery); if (chunkSize > _searchHexQuery.length()) { if (_searchIsForward) { _searchPosition = searchPos + chunkSize; if ((_searchPosition < _fileSize) && (chunkSize > _searchHexQuery.length())) _searchPosition -= _searchHexQuery.length(); byteCounter = _searchPosition - searchPos; } else { if (_searchPosition > 0) _searchPosition += _searchHexQuery.length(); } } if (ndx != -1) { foundAnchor = searchPos + ndx; foundCursor = foundAnchor + _searchHexQuery.length(); } } else { int rowStart = 0; QString row = ""; QScopedPointer decoder(_codec->makeDecoder()); while (byteCounter < chunkSize) { const QString chr = decoder->toUnicode(chunk.mid(byteCounter++, 1)); if (chr.isEmpty() && byteCounter < chunkSize) { continue; } if (chr != "\n") row += chr; if (chr == "\n" || row.length() >= SEARCH_MAX_ROW_LEN || byteCounter >= chunkSize) { if (setPosition) { _searchPosition = searchPos + byteCounter; if (!_searchIsForward) { _searchPosition++; setPosition = false; } } if (_searchQuery.checkLine(row, !_searchIsForward)) { QByteArray cachedBuffer = chunk.mid(rowStart, chunkSize - rowStart); QTextStream stream(&cachedBuffer); stream.setCodec(_codec); stream.read(_searchQuery.matchIndex()); foundAnchor = searchPos + rowStart + stream.pos(); stream.read(_searchQuery.matchLength()); foundCursor = searchPos + rowStart + stream.pos(); if (_searchIsForward) break; } row = ""; rowStart = byteCounter; } } } if (foundAnchor != -1 && foundCursor != -1) { _textArea->setAnchorAndCursor(foundAnchor, foundCursor); searchSucceeded(); return; } if (_searchIsForward && searchPos + byteCounter >= _fileSize) { if (_restartFromBeginning) resetSearchPosition(); else { searchFailed(); return; } } else if (_searchPosition <= 0 || _searchPosition >= _fileSize) { if (_restartFromBeginning) resetSearchPosition(); else { searchFailed(); return; } } QTimer::singleShot(0, this, &Lister::slotSearchMore); } void Lister::resetSearchPosition() { _restartFromBeginning = false; _searchPosition = _searchIsForward ? 0 : _fileSize - 1; } void Lister::searchSucceeded() { _searchInProgress = false; setColor(true, false); hideProgressBar(); _searchLastFailedPosition = -1; enableActions(true); } void Lister::searchFailed() { _searchInProgress = false; setColor(false, false); hideProgressBar(); bool isfirst; _searchLastFailedPosition = _textArea->getCursorPosition(isfirst); if (!_searchIsForward) _searchLastFailedPosition--; enableActions(true); } void Lister::searchDelete() { _searchInProgress = false; setColor(false, true); hideProgressBar(); _searchLastFailedPosition = -1; enableActions(true); } void Lister::searchTextChanged() { searchDelete(); if (_fileSize < 0x10000) { // autosearch files less than 64k if (!_searchLineEdit->text().isEmpty()) { bool isfirst; const qint64 anchor = _textArea->getCursorAnchor(); const qint64 cursor = _textArea->getCursorPosition(isfirst); if (cursor > anchor && anchor != -1) { _textArea->setCursorPositionInDocument(anchor, true); } search(true, true); } } } void Lister::setColor(const bool match, const bool restore) { QColor fore, back; if (!restore) { const KConfigGroup gc(krConfig, "Colors"); QString foreground, background; const QPalette p = QGuiApplication::palette(); if (match) { foreground = "Quicksearch Match Foreground"; background = "Quicksearch Match Background"; fore = Qt::black; back = QColor(192, 255, 192); } else { foreground = "Quicksearch Non-match Foreground"; background = "Quicksearch Non-match Background"; fore = Qt::black; back = QColor(255, 192, 192); } if (gc.readEntry(foreground, QString()) == "KDE default") fore = p.color(QPalette::Active, QPalette::Text); else if (!gc.readEntry(foreground, QString()).isEmpty()) fore = gc.readEntry(foreground, fore); if (gc.readEntry(background, QString()) == "KDE default") back = p.color(QPalette::Active, QPalette::Base); else if (!gc.readEntry(background, QString()).isEmpty()) back = gc.readEntry(background, back); } else { back = _originalBackground; fore = _originalForeground; } QPalette pal = _searchLineEdit->palette(); pal.setColor(QPalette::Base, back); pal.setColor(QPalette::Text, fore); _searchLineEdit->setPalette(pal); } void Lister::hideProgressBar() { if (!_searchProgressBar->isHidden()) { _searchProgressBar->hide(); _searchStopButton->hide(); _searchLineEdit->show(); _searchNextButton->show(); _searchPrevButton->show(); _searchOptions->show(); _listerLabel->setText(i18n("Search:")); } } void Lister::updateProgressBar() { if (_searchProgressCounter) return; if (_searchProgressBar->isHidden()) { _searchProgressBar->show(); _searchStopButton->show(); _searchOptions->hide(); _searchLineEdit->hide(); _searchNextButton->hide(); _searchPrevButton->hide(); _listerLabel->setText(i18n("Search position:")); // otherwise focus is set to document tab _textArea->setFocus(); } const qint64 pcnt = (_fileSize == 0) ? 1000 : (2001 * _searchPosition) / _fileSize / 2; const auto pctInt = (int) pcnt; if (_searchProgressBar->value() != pctInt) _searchProgressBar->setValue(pctInt); } void Lister::jumpToPosition() { bool ok = true; QString res = QInputDialog::getText(_textArea, i18n("Jump to position"), i18n("Text position:"), QLineEdit::Normal, "0", &ok); if (!ok) return; res = res.trimmed(); qint64 pos = -1; if (res.startsWith(QLatin1String("0x"))) { res = res.mid(2); bool ok; const qulonglong upos = res.toULongLong(&ok, 16); if (!ok) { KMessageBox::error(_textArea, i18n("Invalid number."), i18n("Jump to position")); return; } pos = (qint64)upos; } else { bool ok; const qulonglong upos = res.toULongLong(&ok); if (!ok) { KMessageBox::error(_textArea, i18n("Invalid number."), i18n("Jump to position")); return; } pos = (qint64)upos; } if (pos < 0 || pos > _fileSize) { KMessageBox::error(_textArea, i18n("Number out of range."), i18n("Jump to position")); return; } _textArea->deleteAnchor(); _textArea->setCursorPositionInDocument(pos, true); _textArea->ensureVisibleCursor(); } void Lister::saveAs() { const QUrl url = QFileDialog::getSaveFileUrl(_textArea, i18n("Lister")); if (url.isEmpty()) return; QUrl sourceUrl; if (!_downloading) sourceUrl = QUrl::fromLocalFile(_filePath); else sourceUrl = this->url(); QList urlList; urlList << sourceUrl; KIO::Job *job = KIO::copy(urlList, url); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } void Lister::saveSelected() { bool isfirst; const qint64 start = _textArea->getCursorAnchor(); const qint64 end = _textArea->getCursorPosition(isfirst); if (start == -1 || start == end) { KMessageBox::error(_textArea, i18n("Nothing is selected."), i18n("Save selection...")); return; } if (start > end) { _savePosition = end; _saveEnd = start; } else { _savePosition = start; _saveEnd = end; } const QUrl url = QFileDialog::getSaveFileUrl(_textArea, i18n("Lister")); if (url.isEmpty()) return; KIO::TransferJob *saveJob = KIO::put(url, -1, KIO::Overwrite); connect(saveJob, &KIO::TransferJob::dataReq, this, &Lister::slotDataSend); connect(saveJob, &KIO::TransferJob::result, this, &Lister::slotSendFinished); saveJob->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(saveJob); saveJob->uiDelegate()->setAutoErrorHandlingEnabled(true); _actionSaveSelected->setEnabled(false); } void Lister::slotDataSend(KIO::Job *, QByteArray &array) { if (_savePosition >= _saveEnd) { array = QByteArray(); return; } qint64 max = _saveEnd - _savePosition; if (max > 1000) max = 1000; array = cacheChunk(_savePosition, (int) max); _savePosition += array.size(); } void Lister::slotSendFinished(KJob *) { _actionSaveSelected->setEnabled(true); } void Lister::setCharacterSet(const QString& set) { _characterSet = set; if (_characterSet.isEmpty()) { _codec = QTextCodec::codecForLocale(); } else { _codec = KCharsets::charsets()->codecForName(_characterSet); } _textArea->redrawTextArea(true); } void Lister::print() { bool isfirst; const qint64 anchor = _textArea->getCursorAnchor(); const qint64 cursor = _textArea->getCursorPosition(isfirst); const bool hasSelection = (anchor != -1 && anchor != cursor); const QString docName = url().fileName(); QPrinter printer; printer.setDocName(docName); QScopedPointer printDialog(new QPrintDialog(&printer, _textArea)); if (hasSelection) { printDialog->addEnabledOption(QAbstractPrintDialog::PrintSelection); } if (!printDialog->exec()) { return; } if (printer.pageOrder() == QPrinter::LastPageFirst) { switch (KMessageBox::warningContinueCancel(_textArea, i18n("Reverse printing is not supported. Continue with normal printing?"))) { case KMessageBox::Continue : break; default: return; } } QPainter painter; painter.begin(&printer); const QString dateString = QDate::currentDate().toString(Qt::SystemLocaleShortDate); const QRect pageRect = printer.pageRect(); const QRect drawingRect(0, 0, pageRect.width(), pageRect.height()); const QFont normalFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); const QFontMetrics fmNormal(normalFont); const int normalFontHeight = fmNormal.height(); const QFontMetrics fmFixed(fixedFont); const int fixedFontHeight = std::max(fmFixed.height(), 1); const int fixedFontWidth = std::max(fmFixed.QFONTMETRICS_WIDTH("W"), 1); const int effPageSize = drawingRect.height() - normalFontHeight - 1; const int rowsPerPage = std::max(effPageSize / fixedFontHeight, 1); const int columnsPerPage = std::max(drawingRect.width() / fixedFontWidth, 1); bool firstPage = true; qint64 startPos = 0; qint64 endPos = _fileSize; if (printer.printRange() == QPrinter::Selection) { if (anchor > cursor) startPos = cursor, endPos = anchor; else startPos = anchor, endPos = cursor; } int page = 0; while (startPos < endPos) { page++; QStringList rows = readLines(startPos, endPos, columnsPerPage, rowsPerPage); // print since set-up fromPage number if (printer.fromPage() && page < printer.fromPage()) { continue; } // print until set-up toPage number if (printer.toPage() && printer.toPage() >= printer.fromPage() && page > printer.toPage()) break; if (!firstPage) { printer.newPage(); } firstPage = false; // Use the painter to draw on the page. painter.setFont(normalFont); painter.drawText(drawingRect, Qt::AlignLeft, dateString); painter.drawText(drawingRect, Qt::AlignHCenter, docName); painter.drawText(drawingRect, Qt::AlignRight, QString("%1").arg(page)); painter.drawLine(0, normalFontHeight, drawingRect.width(), normalFontHeight); painter.setFont(fixedFont); int yOffset = normalFontHeight + 1; foreach (const QString &row, rows) { painter.drawText(0, yOffset + fixedFontHeight, row); yOffset += fixedFontHeight; } } } QStringList Lister::readLines(qint64 &filePos, const qint64 endPos, const int columns, const int lines) { if (_textArea->hexMode()) { return readHexLines(filePos, endPos, columns, lines); } QStringList list; const int maxBytes = std::min(columns * lines * MAX_CHAR_LENGTH, (int) (endPos - filePos)); if (maxBytes <= 0) { return list; } const QByteArray chunk = cacheChunk(filePos, maxBytes); if (chunk.isEmpty()) { return list; } QScopedPointer decoder(_codec->makeDecoder()); int byteCounter = 0; QString row = ""; bool skipImmediateNewline = false; while (byteCounter < chunk.size() && list.size() < lines) { QString chr = decoder->toUnicode(chunk.mid(byteCounter++, 1)); if (chr.isEmpty()) { continue; } // replace unreadable characters if ((chr[ 0 ] < 32) && (chr[ 0 ] != '\n') && (chr[ 0 ] != '\t')) { chr = QChar(' '); } // handle newline if (chr == "\n") { if (!skipImmediateNewline) { list << row; row = ""; } skipImmediateNewline = false; continue; } skipImmediateNewline = false; // handle tab if (chr == "\t") { const int tabLength = _textArea->tabWidth() - (row.length() % _textArea->tabWidth()); if (row.length() + tabLength > columns) { list << row; row = ""; } row += QString(tabLength, QChar(' ')); } else { // normal printable character row += chr; } if (row.length() >= columns) { list << row; row = ""; skipImmediateNewline = true; } } if (list.size() < lines) { list << row; } filePos += byteCounter; return list; } int Lister::hexPositionDigits() { int positionDigits = 0; qint64 checker = _fileSize; while (checker) { positionDigits++; checker /= 16; } if (positionDigits < 8) { return 8; } return positionDigits; } int Lister::hexBytesPerLine(const int columns) { const int positionDigits = hexPositionDigits(); if (columns >= positionDigits + 5 + 128) { return 32; } if (columns >= positionDigits + 5 + 64) { return 16; } return 8; } QStringList Lister::readHexLines(qint64 &filePos, const qint64 endPos, const int columns, const int lines) { const int positionDigits = hexPositionDigits(); const int bytesPerRow = hexBytesPerLine(columns); QStringList list; const qint64 choppedPos = (filePos / bytesPerRow) * bytesPerRow; const int maxBytes = std::min(bytesPerRow * lines, (int) (endPos - choppedPos)); if (maxBytes <= 0) return list; const QByteArray chunk = cacheChunk(choppedPos, maxBytes); if (chunk.isEmpty()) return list; int cnt = 0; for (int l = 0; l < lines; l++) { if (filePos >= endPos) { break; } const qint64 printPos = (filePos / bytesPerRow) * bytesPerRow; QString pos; pos.setNum(printPos, 16); while (pos.length() < positionDigits) pos = QString("0") + pos; pos = QString("0x") + pos; pos += QString(": "); QString charData; for (int i = 0; i != bytesPerRow; ++i, ++cnt) { const qint64 currentPos = printPos + i; if (currentPos < filePos || currentPos >= endPos) { pos += QString(" "); charData += QString(" "); } else { char c = chunk.at(cnt); auto charCode = (int)c; if (charCode < 0) charCode += 256; QString hex; hex.setNum(charCode, 16); if (hex.length() < 2) hex = QString("0") + hex; pos += hex + QString(" "); if (c < 32) c = '.'; charData += QChar(c); } } pos += QString(" ") + charData; list << pos; filePos = printPos + bytesPerRow; } if (filePos > endPos) { filePos = endPos; } return list; } int Lister::hexIndexToPosition(const int columns, const int index) { const int positionDigits = hexPositionDigits(); const int bytesPerRow = hexBytesPerLine(columns); const int finalIndex = std::min(index, bytesPerRow); return positionDigits + 4 + (3*finalIndex); } int Lister::hexPositionToIndex(const int columns, const int position) { const int positionDigits = hexPositionDigits(); const int bytesPerRow = hexBytesPerLine(columns); int finalPosition = position; finalPosition -= 4 + positionDigits; if (finalPosition <= 0) return 0; finalPosition /= 3; if (finalPosition >= bytesPerRow) return bytesPerRow; return finalPosition; } void Lister::toggleHexMode() { setHexMode(!_textArea->hexMode()); } void Lister::setHexMode(const bool mode) { if (mode) { _textArea->setHexMode(true); _actionHexMode->setText(i18n("Text mode")); } else { _textArea->setHexMode(false); _actionHexMode->setText(i18n("Hex mode")); } } diff --git a/krusader/Konfigurator/kgdependencies.cpp b/krusader/Konfigurator/kgdependencies.cpp index 347969b1..ff36a924 100644 --- a/krusader/Konfigurator/kgdependencies.cpp +++ b/krusader/Konfigurator/kgdependencies.cpp @@ -1,173 +1,174 @@ /***************************************************************************** * Copyright (C) 2004 Csaba Karai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "kgdependencies.h" #include "../krservices.h" #include "../krglobal.h" +#include "../compat.h" // QtCore #include // QtWidgets #include #include #include #include #include #include #define PAGE_GENERAL 0 #define PAGE_PACKERS 1 #define PAGE_CHECKSUM 2 KgDependencies::KgDependencies(bool first, QWidget* parent) : KonfiguratorPage(first, parent) { auto *kgDependenciesLayout = new QGridLayout(this); kgDependenciesLayout->setSpacing(6); // ---------------------------- GENERAL TAB ------------------------------------- tabWidget = new QTabWidget(this); QWidget *general_tab = new QWidget(tabWidget); auto* general_scroll = new QScrollArea(tabWidget); general_scroll->setFrameStyle(QFrame::NoFrame); general_scroll->setWidget(general_tab); // this also sets scrollacrea as the new parent for widget general_scroll->setWidgetResizable(true); // let the widget use every space available tabWidget->addTab(general_scroll, i18n("General")); auto *pathsGrid = new QGridLayout(general_tab); pathsGrid->setSpacing(6); pathsGrid->setContentsMargins(11, 11, 11, 11); pathsGrid->setAlignment(Qt::AlignTop); addApplication("kget", pathsGrid, 0, general_tab, PAGE_GENERAL); addApplication("mailer", pathsGrid, 1, general_tab, PAGE_GENERAL); addApplication("diff utility", pathsGrid, 2, general_tab, PAGE_GENERAL); addApplication("krename", pathsGrid, 3, general_tab, PAGE_GENERAL); addApplication("locate", pathsGrid, 4, general_tab, PAGE_GENERAL); addApplication("mount", pathsGrid, 5, general_tab, PAGE_GENERAL); addApplication("umount", pathsGrid, 6, general_tab, PAGE_GENERAL); addApplication("updatedb", pathsGrid, 7, general_tab, PAGE_GENERAL); // ---------------------------- PACKERS TAB ------------------------------------- QWidget *packers_tab = new QWidget(tabWidget); auto* packers_scroll = new QScrollArea(tabWidget); packers_scroll->setFrameStyle(QFrame::NoFrame); packers_scroll->setWidget(packers_tab); // this also sets scrollacrea as the new parent for widget packers_scroll->setWidgetResizable(true); // let the widget use every space available tabWidget->addTab(packers_scroll, i18n("Packers")); auto *archGrid1 = new QGridLayout(packers_tab); archGrid1->setSpacing(6); archGrid1->setContentsMargins(11, 11, 11, 11); archGrid1->setAlignment(Qt::AlignTop); addApplication("7z", archGrid1, 0, packers_tab, PAGE_PACKERS, "7za"); addApplication("arj", archGrid1, 1, packers_tab, PAGE_PACKERS); addApplication("bzip2", archGrid1, 2, packers_tab, PAGE_PACKERS); addApplication("cpio", archGrid1, 3, packers_tab, PAGE_PACKERS); addApplication("dpkg", archGrid1, 4, packers_tab, PAGE_PACKERS); addApplication("gzip", archGrid1, 5, packers_tab, PAGE_PACKERS); addApplication("lha", archGrid1, 6, packers_tab, PAGE_PACKERS); addApplication("lzma", archGrid1, 7, packers_tab, PAGE_PACKERS); addApplication("rar", archGrid1, 8, packers_tab, PAGE_PACKERS); addApplication("tar", archGrid1, 9, packers_tab, PAGE_PACKERS); addApplication("unace", archGrid1, 10, packers_tab, PAGE_PACKERS); addApplication("unarj", archGrid1, 11, packers_tab, PAGE_PACKERS); addApplication("unrar", archGrid1, 12, packers_tab, PAGE_PACKERS); addApplication("unzip", archGrid1, 13, packers_tab, PAGE_PACKERS); addApplication("zip", archGrid1, 14, packers_tab, PAGE_PACKERS); addApplication("xz", archGrid1, 15, packers_tab, PAGE_PACKERS); // ---------------------------- CHECKSUM TAB ------------------------------------- QWidget *checksum_tab = new QWidget(tabWidget); auto* checksum_scroll = new QScrollArea(tabWidget); checksum_scroll->setFrameStyle(QFrame::NoFrame); checksum_scroll->setWidget(checksum_tab); // this also sets scrollacrea as the new parent for widget checksum_scroll->setWidgetResizable(true); // let the widget use every space available tabWidget->addTab(checksum_scroll, i18n("Checksum Utilities")); auto *archGrid2 = new QGridLayout(checksum_tab); archGrid2->setSpacing(6); archGrid2->setContentsMargins(11, 11, 11, 11); archGrid2->setAlignment(Qt::AlignTop); addApplication("md5sum", archGrid2, 0, checksum_tab, PAGE_CHECKSUM); addApplication("sha1sum", archGrid2, 1, checksum_tab, PAGE_CHECKSUM); addApplication("sha224sum", archGrid2, 2, checksum_tab, PAGE_CHECKSUM); addApplication("sha256sum", archGrid2, 3, checksum_tab, PAGE_CHECKSUM); addApplication("sha384sum", archGrid2, 4, checksum_tab, PAGE_CHECKSUM); addApplication("sha512sum", archGrid2, 5, checksum_tab, PAGE_CHECKSUM); kgDependenciesLayout->addWidget(tabWidget, 0, 0); } void KgDependencies::addApplication(const QString& name, QGridLayout *grid, int row, QWidget *parent, int page, const QString& additionalList) { // try to autodetect the full path name QString defaultValue = KrServices::fullPathName(name); if (defaultValue.isEmpty()) { - QStringList list = additionalList.split(',', QString::SkipEmptyParts); + QStringList list = additionalList.split(',', SKIP_EMPTY_PARTS); for (int i = 0; i != list.count(); i++) if (!KrServices::fullPathName(list[ i ]).isEmpty()) { defaultValue = KrServices::fullPathName(list[ i ]); break; } } QLabel *labelPath = addLabel(grid, row, 0, name, parent); KonfiguratorURLRequester *fullPath = createURLRequester("Dependencies", name, defaultValue, labelPath, parent, false, QString(), page); connect(fullPath->extension(), &KonfiguratorExtension::applyManually, this, &KgDependencies::slotApply); grid->addWidget(fullPath, row, 1); } void KgDependencies::slotApply(QObject *obj, const QString& configGroup, const QString& name) { auto *urlRequester = qobject_cast( obj); KConfigGroup group(krConfig, configGroup); group.writeEntry(name, urlRequester->url().toDisplayString(QUrl::PreferLocalFile)); QString usedPath = KrServices::fullPathName(name); if (urlRequester->url().toDisplayString(QUrl::PreferLocalFile) != usedPath) { group.writeEntry(name, usedPath); if (usedPath.isEmpty()) KMessageBox::error(this, i18n("The %1 path is incorrect, no valid path found.", urlRequester->url().toDisplayString(QUrl::PreferLocalFile))); else KMessageBox::error( this, i18n("The %1 path is incorrect, %2 used instead.", urlRequester->url().toDisplayString(QUrl::PreferLocalFile), usedPath)); urlRequester->setUrl(QUrl::fromLocalFile(usedPath)); } } int KgDependencies::activeSubPage() { return tabWidget->currentIndex(); } diff --git a/krusader/Konfigurator/kgpanel.cpp b/krusader/Konfigurator/kgpanel.cpp index 73a4abb6..461463ed 100644 --- a/krusader/Konfigurator/kgpanel.cpp +++ b/krusader/Konfigurator/kgpanel.cpp @@ -1,845 +1,845 @@ /***************************************************************************** * Copyright (C) 2010 Jan Lepper * * Copyright (C) 2010-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "kgpanel.h" #include "../defaults.h" #include "../krglobal.h" #include "../Dialogs/krdialogs.h" // QtGui #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "../GUI/krtreewidget.h" #include "../Panel/krsearchbar.h" #include "../Panel/PanelView/krselectionmode.h" #include "../Panel/PanelView/krview.h" #include "../Panel/PanelView/krviewfactory.h" #include "../Panel/krlayoutfactory.h" #include "../icon.h" #include "../krglobal.h" enum { PAGE_GENERAL = 0, PAGE_VIEW, PAGE_PANELTOOLBAR, PAGE_MOUSE, PAGE_MEDIA_MENU, PAGE_LAYOUT }; KgPanel::KgPanel(bool first, QWidget* parent) : KonfiguratorPage(first, parent) { tabWidget = new QTabWidget(this); setWidget(tabWidget); setWidgetResizable(true); setupGeneralTab(); setupPanelTab(); setupButtonsTab(); setupMouseModeTab(); setupMediaMenuTab(); setupLayoutTab(); } // --------------------------------------------------------------------------------------- // ---------------------------- General TAB ---------------------------------------------- // --------------------------------------------------------------------------------------- void KgPanel::setupGeneralTab() { auto *scrollArea = new QScrollArea(tabWidget); QWidget *tab = new QWidget(scrollArea); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setWidget(tab); scrollArea->setWidgetResizable(true); tabWidget->addTab(scrollArea, i18n("General")); auto *layout = new QVBoxLayout(tab); layout->setSpacing(6); layout->setContentsMargins(11, 11, 11, 11); // --------------------------------------------------------------------------------------- // ------------------------------- Navigator bar ------------------------------------- // --------------------------------------------------------------------------------------- QGroupBox *groupBox = createFrame(i18n("Navigator bar"), tab); QGridLayout *gridLayout = createGridLayout(groupBox); KONFIGURATOR_CHECKBOX_PARAM navigatorbar_settings[] = { // cfg_class, cfg_name, default, text, restart, tooltip {"Look&Feel", "Navigator Edit Mode", false, i18n("Edit Mode by default"), true, i18n("Show editable path in Navigator bar by default") }, {"Look&Feel", "Navigator Full Path", false, i18n("Show full path by default"), true, i18n("Always show full path in Navigator bar by default.") }, }; KonfiguratorCheckBoxGroup* cbs = createCheckBoxGroup(2, 0, navigatorbar_settings, 2 /*count*/, groupBox, PAGE_GENERAL); gridLayout->addWidget(cbs, 0, 0); layout->addWidget(groupBox); // --------------------------------------------------------------------------------------- // ------------------------------- Operation --------------------------------------------- // --------------------------------------------------------------------------------------- groupBox = createFrame(i18n("Operation"), tab); gridLayout = createGridLayout(groupBox); KONFIGURATOR_CHECKBOX_PARAM operation_settings[] = { // cfg_class, cfg_name, default, text, restart, tooltip {"Look&Feel", "Mark Dirs", _MarkDirs, i18n("Autoselect folders"), false, i18n("When matching the select criteria, not only files will be selected, but also folders.") }, {"Look&Feel", "Rename Selects Extension", true, i18n("Rename selects extension"), false, i18n("When renaming a file, the whole text is selected. If you want Total-Commander like renaming of just the name, without extension, uncheck this option.") }, {"Look&Feel", "UnselectBeforeOperation", _UnselectBeforeOperation, i18n("Unselect files before copy/move"), false, i18n("Unselect files, which are to be copied/moved, before the operation starts.") }, {"Look&Feel", "FilterDialogRemembersSettings", _FilterDialogRemembersSettings, i18n("Filter dialog remembers settings"), false, i18n("The filter dialog is opened with the last filter settings that where applied to the panel.") }, }; cbs = createCheckBoxGroup(2, 0, operation_settings, 4 /*count*/, groupBox, PAGE_GENERAL); gridLayout->addWidget(cbs, 0, 0); layout->addWidget(groupBox); // --------------------------------------------------------------------------------------- // ------------------------------ Tabs --------------------------------------------------- // --------------------------------------------------------------------------------------- groupBox = createFrame(i18n("Tabs"), tab); gridLayout = createGridLayout(groupBox); KONFIGURATOR_CHECKBOX_PARAM tabbar_settings[] = { // cfg_class cfg_name default text restart tooltip {"Look&Feel", "Fullpath Tab Names", _FullPathTabNames, i18n("Use full path tab names"), true , i18n("Display the full path in the folder tabs. By default only the last part of the path is displayed.") }, {"Look&Feel", "Show Tab Buttons", true, i18n("Show new/close tab buttons"), true , i18n("Show the new/close tab buttons.") }, }; cbs = createCheckBoxGroup(2, 0, tabbar_settings, 2 /*count*/, groupBox, PAGE_GENERAL); gridLayout->addWidget(cbs, 0, 0, 1, 2); // ----------------- Tab Bar position ---------------------------------- auto *hbox = new QHBoxLayout(); QLabel *labelTabBar = new QLabel(i18n("Tab Bar position:"), groupBox); hbox->addWidget(labelTabBar); KONFIGURATOR_NAME_VALUE_PAIR positions[] = { { i18n("Top"), "top" }, { i18n("Bottom"), "bottom" } }; KonfiguratorComboBox *cmb = createComboBox("Look&Feel", "Tab Bar Position", "bottom", positions, 2, labelTabBar, groupBox, true, false, QString(), PAGE_GENERAL); hbox->addWidget(cmb); gridLayout->addLayout(hbox, 1, 0, Qt::AlignLeft); // ----------------- Show Tab bar ---------------------------------- KonfiguratorCheckBox *checkBox = createCheckBox("Look&Feel", "Show Tab Bar On Single Tab", true, i18n("Show Tab Bar on single tab"), groupBox, true, i18n("Show the tab bar with only one tab.")); gridLayout->addWidget(checkBox, 1, 1, Qt::AlignLeft); layout->addWidget(groupBox); // --------------------------------------------------------------------------------------- // ----------------------------- Search bar -------------------------------------------- // --------------------------------------------------------------------------------------- groupBox = createFrame(i18n("Search bar"), tab); gridLayout = createGridLayout(groupBox); KONFIGURATOR_CHECKBOX_PARAM quicksearch[] = { // cfg_class cfg_name default text restart tooltip {"Look&Feel", "New Style Quicksearch", _NewStyleQuicksearch, i18n("Start by typing"), false, i18n("Open search bar and start searching by typing in panel.") }, {"Look&Feel", "Case Sensitive Quicksearch", _CaseSensitiveQuicksearch, i18n("Case sensitive"), false, i18n("Search must match case.") }, {"Look&Feel", "Up/Down Cancels Quicksearch", false, i18n("Up/Down cancels search"), false, i18n("Pressing the Up/Down buttons closes the search bar (only in search mode).") }, {"Look&Feel", "Navigation with Right Arrow Quicksearch", _NavigationWithRightArrowQuicksearch, i18n("Folder navigation with Right Arrow"), false, i18n("Pressing the Right button enters folder if no search text editing intention is captured.") }, }; cbs = createCheckBoxGroup(2, 0, quicksearch, 4 /*count*/, groupBox, PAGE_GENERAL); gridLayout->addWidget(cbs, 0, 0, 1, -1); // -------------- Search bar position ----------------------- hbox = new QHBoxLayout(); QLabel *labelPosit = new QLabel(i18n("Position:"), groupBox); hbox->addWidget(labelPosit); cmb = createComboBox("Look&Feel", "Quicksearch Position", "bottom", positions, 2, labelPosit, groupBox, true, false, QString(), PAGE_GENERAL); hbox->addWidget(cmb); hbox->addWidget(createSpacer(groupBox)); gridLayout->addLayout(hbox, 1, 0); // -------------- Default search mode ----------------------- hbox = new QHBoxLayout(); QLabel *labelMode = new QLabel(i18n("Default mode:"), groupBox); hbox->addWidget(labelMode); KONFIGURATOR_NAME_VALUE_PAIR modes[] = { {i18n("Search"), QString::number(KrSearchBar::MODE_SEARCH)}, {i18n("Select"), QString::number(KrSearchBar::MODE_SELECT)}, {i18n("Filter"), QString::number(KrSearchBar::MODE_FILTER)}}; cmb = createComboBox("Look&Feel", "Default Search Mode", QString::number(KrSearchBar::MODE_SEARCH), modes, 3, labelMode, groupBox, true, false, i18n("Set the default mode on first usage"), PAGE_GENERAL); hbox->addWidget(cmb); hbox->addWidget(createSpacer(groupBox)); gridLayout->addLayout(hbox, 1, 1); layout->addWidget(groupBox); // -------------------------------------------------------------------------------------------- // ------------------------------- Bookmark search settings ---------------------------------- // -------------------------------------------------------------------------------------------- groupBox = createFrame(i18n("Bookmark Search"), tab); gridLayout = createGridLayout(groupBox); KONFIGURATOR_CHECKBOX_PARAM bookmarkSearchSettings[] = { {"Look&Feel", "Always show search bar", true, i18n("Always show search bar"), false, i18n("Make bookmark search bar always visible") }, {"Look&Feel", "Search in special items", false, i18n("Search in special items"), false, i18n("Bookmark search is also applied to special items in bookmark menu like Trash, Popular URLs, Jump Back, etc.") }, }; KonfiguratorCheckBoxGroup *bookmarkSearchSettingsGroup = createCheckBoxGroup(2, 0, bookmarkSearchSettings, 2 /*count*/, groupBox, PAGE_GENERAL); gridLayout->addWidget(bookmarkSearchSettingsGroup, 1, 0, 1, 2); layout->addWidget(groupBox); // -------------------------------------------------------------------------------------------- // ------------------------------- Status/Totalsbar settings ---------------------------------- // -------------------------------------------------------------------------------------------- groupBox = createFrame(i18n("Status/Totalsbar"), tab); gridLayout = createGridLayout(groupBox); KONFIGURATOR_CHECKBOX_PARAM barSettings[] = { {"Look&Feel", "Show Size In Bytes", false, i18n("Show size in bytes too"), true, i18n("Show size in bytes too") }, {"Look&Feel", "ShowSpaceInformation", true, i18n("Show space information"), true, i18n("Show free/total space on the device") }, }; KonfiguratorCheckBoxGroup *barSett = createCheckBoxGroup(2, 0, barSettings, 2 /*count*/, groupBox, PAGE_GENERAL); gridLayout->addWidget(barSett, 1, 0, 1, 2); layout->addWidget(groupBox); } // -------------------------------------------------------------------------------------------- // ------------------------------------ Layout Tab -------------------------------------------- // -------------------------------------------------------------------------------------------- void KgPanel::setupLayoutTab() { auto *scrollArea = new QScrollArea(tabWidget); QWidget *tab = new QWidget(scrollArea); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setWidget(tab); scrollArea->setWidgetResizable(true); tabWidget->addTab(scrollArea, i18n("Layout")); QGridLayout *grid = createGridLayout(tab); QStringList layoutNames = KrLayoutFactory::layoutNames(); int numLayouts = layoutNames.count(); grid->addWidget(createSpacer(tab), 0, 2); QLabel *l = new QLabel(i18n("Layout:"), tab); l->setAlignment(Qt::AlignRight | Qt::AlignVCenter); grid->addWidget(l, 0, 0); auto *layouts = new KONFIGURATOR_NAME_VALUE_PAIR[numLayouts]; for (int i = 0; i != numLayouts; i++) { layouts[ i ].text = KrLayoutFactory::layoutDescription(layoutNames[i]); layouts[ i ].value = layoutNames[i]; } KonfiguratorComboBox *cmb = createComboBox("PanelLayout", "Layout", "default", layouts, numLayouts, l, tab, true, false, QString(), PAGE_LAYOUT); grid->addWidget(cmb, 0, 1); delete [] layouts; l = new QLabel(i18n("Frame Color:"), tab); l->setAlignment(Qt::AlignRight | Qt::AlignVCenter); grid->addWidget(l, 1, 0); KONFIGURATOR_NAME_VALUE_PAIR frameColor[] = { { i18nc("Frame color", "Defined by Layout"), "default" }, { i18nc("Frame color", "None"), "none" }, { i18nc("Frame color", "Statusbar"), "Statusbar" } }; cmb = createComboBox("PanelLayout", "FrameColor", "default", frameColor, 3, l, tab, true, false, QString(), PAGE_LAYOUT); grid->addWidget(cmb, 1, 1); l = new QLabel(i18n("Frame Shape:"), tab); l->setAlignment(Qt::AlignRight | Qt::AlignVCenter); grid->addWidget(l, 2, 0); KONFIGURATOR_NAME_VALUE_PAIR frameShape[] = { { i18nc("Frame shape", "Defined by Layout"), "default" }, { i18nc("Frame shape", "None"), "NoFrame" }, { i18nc("Frame shape", "Box"), "Box" }, { i18nc("Frame shape", "Panel"), "Panel" }, }; cmb = createComboBox("PanelLayout", "FrameShape", "default", frameShape, 4, l, tab, true, false, QString(), PAGE_LAYOUT); grid->addWidget(cmb, 2, 1); l = new QLabel(i18n("Frame Shadow:"), tab); l->setAlignment(Qt::AlignRight | Qt::AlignVCenter); grid->addWidget(l, 3, 0); KONFIGURATOR_NAME_VALUE_PAIR frameShadow[] = { { i18nc("Frame shadow", "Defined by Layout"), "default" }, { i18nc("Frame shadow", "None"), "Plain" }, { i18nc("Frame shadow", "Raised"), "Raised" }, { i18nc("Frame shadow", "Sunken"), "Sunken" }, }; cmb = createComboBox("PanelLayout", "FrameShadow", "default", frameShadow, 4, l, tab, true, false, QString(), PAGE_LAYOUT); grid->addWidget(cmb, 3, 1); } void KgPanel::setupView(KrViewInstance *instance, QWidget *parent) { QGridLayout *grid = createGridLayout(parent); // -------------------- Filelist icon size ---------------------------------- auto *hbox = new QHBoxLayout(); QLabel *labelIconSize = new QLabel(i18n("Default icon size:"), parent); hbox->addWidget(labelIconSize); auto *iconSizes = new KONFIGURATOR_NAME_VALUE_PAIR[KrView::iconSizes.count()]; for(int i = 0; i < KrView::iconSizes.count(); i++) iconSizes[i].text = iconSizes[i].value = QString::number(KrView::iconSizes[i]); KonfiguratorComboBox *cmb = createComboBox(instance->name(), "IconSize", _FilelistIconSize, iconSizes, KrView::iconSizes.count(), labelIconSize, parent, true, true, QString(), PAGE_VIEW); delete [] iconSizes; cmb->lineEdit()->setValidator(new QRegExpValidator(QRegExp("[1-9]\\d{0,1}"), cmb)); hbox->addWidget(cmb); hbox->addWidget(createSpacer(parent)); grid->addLayout(hbox, 1, 0); //-------------------------------------------------------------------- KONFIGURATOR_CHECKBOX_PARAM iconSettings[] = // cfg_class cfg_name default text restart tooltip { {instance->name(), "With Icons", _WithIcons, i18n("Use icons in the filenames"), true, i18n("Show the icons for filenames and folders.") }, {instance->name(), "ShowPreviews", false, i18n("Show previews by default"), false, i18n("Show previews of files and folders.") }, }; KonfiguratorCheckBoxGroup *iconSett = createCheckBoxGroup(2, 0, iconSettings, 2 /*count*/, parent, PAGE_VIEW); grid->addWidget(iconSett, 2, 0, 1, 2); } // ---------------------------------------------------------------------------------- // ---------------------------- VIEW TAB ------------------------------------------- // ---------------------------------------------------------------------------------- void KgPanel::setupPanelTab() { auto *scrollArea = new QScrollArea(tabWidget); QWidget *tab_panel = new QWidget(scrollArea); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setWidget(tab_panel); scrollArea->setWidgetResizable(true); tabWidget->addTab(scrollArea, i18n("View")); auto *panelLayout = new QGridLayout(tab_panel); panelLayout->setSpacing(6); panelLayout->setContentsMargins(11, 11, 11, 11); QGroupBox *panelGrp = createFrame(i18n("General"), tab_panel); panelLayout->addWidget(panelGrp, 0, 0); QGridLayout *panelGrid = createGridLayout(panelGrp); // ---------------------------------------------------------------------------------- // ---------------------------- General settings ----------------------------------- // ---------------------------------------------------------------------------------- // -------------------- Panel Font ---------------------------------- auto *hbox = new QHBoxLayout(); auto *fontLayout = new QHBoxLayout(); QLabel *labelFontLay = new QLabel(i18n("View font:"), panelGrp); fontLayout->addWidget(labelFontLay); KonfiguratorFontChooser *chsr = createFontChooser("Look&Feel", "Filelist Font", _FilelistFont, labelFontLay, panelGrp, true, QString(), PAGE_VIEW); fontLayout->addWidget(chsr); fontLayout->addStretch(1); hbox->addLayout(fontLayout, 1); // -------------------- Panel Tooltip ---------------------------------- auto *tooltipLayout = new QHBoxLayout(); const QString delayTip = i18n("The duration after a tooltip is shown for a file item, in " "milliseconds. Set a negative value to disable tooltips."); QLabel *tooltipLabel = new QLabel(i18n("Tooltip delay (msec):")); tooltipLayout->addWidget(tooltipLabel); KonfiguratorSpinBox *tooltipSpinBox = createSpinBox("Look&Feel", "Panel Tooltip Delay", 1000, -100, 5000, tooltipLabel, panelGrp, false, delayTip, PAGE_VIEW); tooltipSpinBox->setSingleStep(100); tooltipLayout->addWidget(tooltipSpinBox); tooltipLayout->addStretch(1); hbox->addLayout(tooltipLayout, 1); panelGrid->addLayout(hbox, 1, 0); // -------------------- General options ---------------------------------- KONFIGURATOR_CHECKBOX_PARAM panelSettings[] = // cfg_class cfg_name default text restart tooltip { {"Look&Feel", "Human Readable Size", _HumanReadableSize, i18n("Use human-readable file size"), true , i18n("File sizes are displayed in B, KB, MB and GB, not just in bytes.") }, {"Look&Feel", "Show Hidden", _ShowHidden, i18n("Show hidden files"), false, i18n("Display files beginning with a dot.") }, {"Look&Feel", "Numeric permissions", _NumericPermissions, i18n("Numeric Permissions"), true, i18n("Show octal numbers (0755) instead of the standard permissions (rwxr-xr-x) in the permission column.") }, {"Look&Feel", "Load User Defined Folder Icons", _UserDefinedFolderIcons, i18n("Load the user defined folder icons"), true , i18n("Load the user defined folder icons (can cause decrease in performance).") }, {"Look&Feel", "Always Show Current Item", _AlwaysShowCurrentItem, i18n("Always show current item"), false, i18n("Show current item border decoration in inactive panel.") }, }; KonfiguratorCheckBoxGroup *panelSett = createCheckBoxGroup(2, 0, panelSettings, 5 /*count*/, panelGrp, PAGE_VIEW); panelGrid->addWidget(panelSett, 3, 0, 1, 2); // ========================================================= panelGrid->addWidget(createLine(panelGrp), 4, 0); // ------------------------ Sort Method ---------------------------------- hbox = new QHBoxLayout(); QLabel *labelSort = new QLabel(i18n("Sort method:"), panelGrp); hbox->addWidget(labelSort); KONFIGURATOR_NAME_VALUE_PAIR sortMethods[] = {{ i18n("Alphabetical"), QString::number(KrViewProperties::Alphabetical) }, { i18n("Alphabetical and numbers"), QString::number(KrViewProperties::AlphabeticalNumbers) }, { i18n("Character code"), QString::number(KrViewProperties::CharacterCode) }, { i18n("Character code and numbers"), QString::number(KrViewProperties::CharacterCodeNumbers) }, { i18nc("Krusader sort", "Krusader"), QString::number(KrViewProperties::Krusader) } }; KonfiguratorComboBox *cmb = createComboBox("Look&Feel", "Sort method", QString::number(_DefaultSortMethod), sortMethods, 5, labelSort, panelGrp, true, false, QString(), PAGE_VIEW); hbox->addWidget(cmb); hbox->addWidget(createSpacer(panelGrp)); panelGrid->addLayout(hbox, 5, 0); // ------------------------ Sort Options ---------------------------------- KONFIGURATOR_CHECKBOX_PARAM sortSettings[] = // cfg_class, cfg_name, default, text, restart, tooltip { {"Look&Feel", "Case Sensative Sort", _CaseSensativeSort, i18n("Case sensitive sorting"), true, i18n("All files beginning with capital letters appear before files beginning with non-capital letters (UNIX default).") }, {"Look&Feel", "Show Directories First", true, i18n("Show folders first"), true, nullptr }, {"Look&Feel", "Always sort dirs by name", false, i18n("Always sort dirs by name"), true, i18n("Folders are sorted by name, regardless of the sort column.") }, {"Look&Feel", "Locale Aware Sort", true, i18n("Locale aware sorting"), true, i18n("The sorting is performed in a locale- and also platform-dependent manner. Can be slow.") }, }; KonfiguratorCheckBoxGroup *sortSett = createCheckBoxGroup(2, 0, sortSettings, 4 /*count*/, panelGrp, PAGE_VIEW); sortSett->find("Show Directories First")->addDep(sortSett->find("Always sort dirs by name")); panelGrid->addWidget(sortSett, 6, 0, 1, 2); // ---------------------------------------------------------------------------------- // ---------------------------- View modes ----------------------------------------- // ---------------------------------------------------------------------------------- panelGrp = createFrame(i18n("View modes"), tab_panel); panelLayout->addWidget(panelGrp, 1, 0); panelGrid = createGridLayout(panelGrp); // -------------------- Default Panel Type ---------------------------------- hbox = new QHBoxLayout(); QLabel *labelViewMode = new QLabel(i18n("Default view mode:"), panelGrp); hbox->addWidget(labelViewMode); QList views = KrViewFactory::registeredViews(); const int viewsSize = views.size(); auto *panelTypes = new KONFIGURATOR_NAME_VALUE_PAIR[ viewsSize ]; QString defType = QString('0'); for (int i = 0; i != viewsSize; i++) { KrViewInstance * inst = views[ i ]; panelTypes[ i ].text = inst->description(); panelTypes[ i ].text.remove('&'); panelTypes[ i ].value = QString("%1").arg(inst->id()); if (inst->id() == KrViewFactory::defaultViewId()) defType = QString("%1").arg(inst->id()); } cmb = createComboBox("Look&Feel", "Default Panel Type", defType, panelTypes, viewsSize, labelViewMode, panelGrp, false, false, QString(), PAGE_VIEW); hbox->addWidget(cmb); hbox->addWidget(createSpacer(panelGrp)); delete [] panelTypes; panelGrid->addLayout(hbox, 0, 0); // ----- Individual Settings Per View Type ------------------------ auto *tabs_view = new QTabWidget(panelGrp); panelGrid->addWidget(tabs_view, 11, 0); for(int i = 0; i < views.count(); i++) { QWidget *tab = new QWidget(tabs_view); tabs_view->addTab(tab, views[i]->description()); setupView(views[i], tab); } } // ----------------------------------------------------------------------------------- // -------------------------- Panel Toolbar TAB ---------------------------------- // ----------------------------------------------------------------------------------- void KgPanel::setupButtonsTab() { auto *scrollArea = new QScrollArea(tabWidget); QWidget *tab = new QWidget(scrollArea); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setWidget(tab); scrollArea->setWidgetResizable(true); tabWidget->addTab(scrollArea, i18n("Buttons")); QBoxLayout * tabLayout = new QVBoxLayout(tab); tabLayout->setSpacing(6); tabLayout->setContentsMargins(11, 11, 11, 11); KONFIGURATOR_CHECKBOX_PARAM buttonsParams[] = // cfg_class cfg_name default text restart tooltip { {"ListPanelButtons", "Icons", false, i18n("Toolbar buttons have icons"), true, "" }, {"Look&Feel", "Media Button Visible", true, i18n("Show Media Button"), true , i18n("The media button will be visible.") }, {"Look&Feel", "Back Button Visible", false, i18n("Show Back Button"), true , "Goes back in history." }, {"Look&Feel", "Forward Button Visible", false, i18n("Show Forward Button"), true , "Goes forward in history." }, {"Look&Feel", "History Button Visible", true, i18n("Show History Button"), true , i18n("The history button will be visible.") }, {"Look&Feel", "Bookmarks Button Visible", true, i18n("Show Bookmarks Button"), true , i18n("The bookmarks button will be visible.") }, {"Look&Feel", "Panel Toolbar visible", _PanelToolBar, i18n("Show Panel Toolbar"), true, i18n("The panel toolbar will be visible.") }, }; buttonsCheckboxes = createCheckBoxGroup(1, 0, buttonsParams, 7/*count*/, tab, PAGE_PANELTOOLBAR); connect(buttonsCheckboxes->find("Panel Toolbar visible"), &KonfiguratorCheckBox::stateChanged, this, &KgPanel::slotEnablePanelToolbar); - tabLayout->addWidget(buttonsCheckboxes, 0, nullptr); + tabLayout->addWidget(buttonsCheckboxes, 0, Qt::Alignment()); QGroupBox * panelToolbarGrp = createFrame(i18n("Visible Panel Toolbar buttons"), tab); QGridLayout * panelToolbarGrid = createGridLayout(panelToolbarGrp); KONFIGURATOR_CHECKBOX_PARAM panelToolbarButtonsParams[] = { // cfg_class cfg_name default text restart tooltip {"Look&Feel", "Equal Button Visible", _cdOther, i18n("Equal button (=)"), true , i18n("Changes the panel folder to the other panel folder.") }, {"Look&Feel", "Up Button Visible", _cdUp, i18n("Up button (..)"), true , i18n("Changes the panel folder to the parent folder.") }, {"Look&Feel", "Home Button Visible", _cdHome, i18n("Home button (~)"), true , i18n("Changes the panel folder to the home folder.") }, {"Look&Feel", "Root Button Visible", _cdRoot, i18n("Root button (/)"), true , i18n("Changes the panel folder to the root folder.") }, {"Look&Feel", "SyncBrowse Button Visible", _syncBrowseButton, i18n("Toggle-button for sync-browsing"), true , i18n("Each folder change in the panel is also performed in the other panel.") }, }; panelToolbarButtonsCheckboxes = createCheckBoxGroup(1, 0, panelToolbarButtonsParams, sizeof(panelToolbarButtonsParams) / sizeof(*panelToolbarButtonsParams), panelToolbarGrp, PAGE_PANELTOOLBAR); panelToolbarGrid->addWidget(panelToolbarButtonsCheckboxes, 0, 0); - tabLayout->addWidget(panelToolbarGrp, 1, nullptr); + tabLayout->addWidget(panelToolbarGrp, 1, Qt::Alignment()); // Enable panel toolbar checkboxes slotEnablePanelToolbar(); } // --------------------------------------------------------------------------- // -------------------------- Mouse TAB ---------------------------------- // --------------------------------------------------------------------------- void KgPanel::setupMouseModeTab() { auto *scrollArea = new QScrollArea(tabWidget); QWidget *tab_mouse = new QWidget(scrollArea); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setWidget(tab_mouse); scrollArea->setWidgetResizable(true); tabWidget->addTab(scrollArea, i18n("Selection Mode")); auto *mouseLayout = new QGridLayout(tab_mouse); mouseLayout->setSpacing(6); mouseLayout->setContentsMargins(11, 11, 11, 11); // -------------- General ----------------- QGroupBox *mouseGeneralGroup = createFrame(i18n("General"), tab_mouse); QGridLayout *mouseGeneralGrid = createGridLayout(mouseGeneralGroup); mouseGeneralGrid->setSpacing(0); mouseGeneralGrid->setContentsMargins(5, 5, 5, 5); KONFIGURATOR_NAME_VALUE_TIP mouseSelection[] = { // name value tooltip { i18n("Krusader Mode"), "0", i18n("Both keys allow selecting files. To select more than one file, hold the Ctrl key and click the left mouse button. Right-click menu is invoked using a short click on the right mouse button.") }, { i18n("Konqueror Mode"), "1", i18n("Pressing the left mouse button selects files - you can click and select multiple files. Right-click menu is invoked using a short click on the right mouse button.") }, { i18n("Total-Commander Mode"), "2", i18n("The left mouse button does not select, but sets the current file without affecting the current selection. The right mouse button selects multiple files and the right-click menu is invoked by pressing and holding the right mouse button.") }, { i18n("Ergonomic Mode"), "4", i18n("The left mouse button does not select, but sets the current file without affecting the current selection. The right mouse button invokes the context-menu. You can select with Ctrl key and the left button.") }, { i18n("Custom Selection Mode"), "3", i18n("Design your own selection mode.") } }; mouseRadio = createRadioButtonGroup("Look&Feel", "Mouse Selection", "0", 1, 5, mouseSelection, 5, mouseGeneralGroup, true, PAGE_MOUSE); mouseRadio->layout()->setContentsMargins(0, 0, 0, 0); mouseGeneralGrid->addWidget(mouseRadio, 0, 0); for (int i = 0; i != mouseRadio->count(); i++) connect(mouseRadio->find(i), &QRadioButton::clicked, this, &KgPanel::slotSelectionModeChanged); mouseLayout->addWidget(mouseGeneralGroup, 0, 0); // -------------- Details ----------------- QGroupBox *mouseDetailGroup = createFrame(i18n("Details"), tab_mouse); QGridLayout *mouseDetailGrid = createGridLayout(mouseDetailGroup); mouseDetailGrid->setSpacing(0); mouseDetailGrid->setContentsMargins(5, 5, 5, 5); KONFIGURATOR_NAME_VALUE_TIP singleOrDoubleClick[] = { // name value tooltip { i18n("Double-click selects (classic)"), "0", i18n("A single click on a file will select and focus, a double click opens the file or steps into the folder.") }, { i18n("Obey global selection policy"), "1", i18n("

Use global setting:

Plasma System Settings -> Input Devices -> Mouse

") } }; KonfiguratorRadioButtons *clickRadio = createRadioButtonGroup("Look&Feel", "Single Click Selects", "0", 1, 0, singleOrDoubleClick, 2, mouseDetailGroup, true, PAGE_MOUSE); clickRadio->layout()->setContentsMargins(0, 0, 0, 0); mouseDetailGrid->addWidget(clickRadio, 0, 0); KONFIGURATOR_CHECKBOX_PARAM mouseCheckboxesParam[] = { // {cfg_class, cfg_name, default // text, restart, // tooltip } {"Custom Selection Mode", "QT Selection", _QtSelection, i18n("Based on KDE's selection mode"), true, i18n("If checked, use a mode based on KDE's style.") }, {"Custom Selection Mode", "Left Selects", _LeftSelects, i18n("Left mouse button selects"), true, i18n("If checked, left clicking an item will select it.") }, {"Custom Selection Mode", "Left Preserves", _LeftPreserves, i18n("Left mouse button preserves selection"), true, i18n("If checked, left clicking an item will select it, but will not unselect other, already selected items.") }, {"Custom Selection Mode", "ShiftCtrl Left Selects", _ShiftCtrlLeft, i18n("Shift/Ctrl-Left mouse button selects"), true, i18n("If checked, Shift/Ctrl left clicking will select items.\nNote: this is meaningless if 'Left Button Selects' is checked.") }, {"Custom Selection Mode", "Right Selects", _RightSelects, i18n("Right mouse button selects"), true, i18n("If checked, right clicking an item will select it.") }, {"Custom Selection Mode", "Right Preserves", _RightPreserves, i18n("Right mouse button preserves selection"), true, i18n("If checked, right clicking an item will select it, but will not unselect other, already selected items.") }, {"Custom Selection Mode", "ShiftCtrl Right Selects", _ShiftCtrlRight, i18n("Shift/Ctrl-Right mouse button selects"), true, i18n("If checked, Shift/Ctrl right clicking will select items.\nNote: this is meaningless if 'Right Button Selects' is checked.") }, {"Custom Selection Mode", "Space Moves Down", _SpaceMovesDown, i18n("Spacebar moves down"), true, i18n("If checked, pressing the spacebar will select the current item and move down.\nOtherwise, current item is selected, but remains the current item.") }, {"Custom Selection Mode", "Space Calc Space", _SpaceCalcSpace, i18n("Spacebar calculates disk space"), true, i18n("If checked, pressing the spacebar while the current item is a folder, will (except from selecting the folder)\ncalculate space occupied of the folder (recursively).") }, {"Custom Selection Mode", "Insert Moves Down", _InsertMovesDown, i18n("Insert moves down"), true, i18n("If checked, pressing Insert will select the current item, and move down to the next item.\nOtherwise, current item is not changed.") }, {"Custom Selection Mode", "Immediate Context Menu", _ImmediateContextMenu, i18n("Right clicking pops context menu immediately"), true, i18n("If checked, right clicking will result in an immediate showing of the context menu.\nOtherwise, user needs to click and hold the right mouse button for 500ms.") }, }; mouseCheckboxes = createCheckBoxGroup(1, 0, mouseCheckboxesParam, 11 /*count*/, mouseDetailGroup, PAGE_MOUSE); mouseDetailGrid->addWidget(mouseCheckboxes, 1, 0); for (int i = 0; i < mouseCheckboxes->count(); i++) connect(mouseCheckboxes->find(i), &KonfiguratorCheckBox::clicked, this, &KgPanel::slotMouseCheckBoxChanged); mouseLayout->addWidget(mouseDetailGroup, 0, 1, 2, 1); // Disable the details-button if not in custom-mode slotSelectionModeChanged(); // -------------- Preview ----------------- QGroupBox *mousePreviewGroup = createFrame(i18n("Preview"), tab_mouse); QGridLayout *mousePreviewGrid = createGridLayout(mousePreviewGroup); // TODO preview mousePreview = new KrTreeWidget(mousePreviewGroup); mousePreviewGrid->addWidget(mousePreview, 0 , 0); mousePreviewGroup->setEnabled(false); // TODO re-enable once the preview is implemented // ------------------------------------------ mouseLayout->addWidget(mousePreviewGroup, 1, 0); } // --------------------------------------------------------------------------- // -------------------------- Media Menu TAB ---------------------------------- // --------------------------------------------------------------------------- void KgPanel::setupMediaMenuTab() { auto *scrollArea = new QScrollArea(tabWidget); QWidget *tab = new QWidget(scrollArea); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setWidget(tab); scrollArea->setWidgetResizable(true); tabWidget->addTab(scrollArea, i18n("Media Menu")); QBoxLayout * tabLayout = new QVBoxLayout(tab); tabLayout->setSpacing(6); tabLayout->setContentsMargins(11, 11, 11, 11); KONFIGURATOR_CHECKBOX_PARAM mediaMenuParams[] = { // cfg_class cfg_name default text restart tooltip {"MediaMenu", "ShowPath", true, i18n("Show Mount Path"), false, nullptr }, {"MediaMenu", "ShowFSType", true, i18n("Show File System Type"), false, nullptr }, {"MediaMenu", "HideSquashFS", false, i18n("Hide SquashFS entries"), false, nullptr }, }; KonfiguratorCheckBoxGroup *mediaMenuCheckBoxes = createCheckBoxGroup(1, 0, mediaMenuParams, sizeof(mediaMenuParams) / sizeof(*mediaMenuParams), tab, PAGE_MEDIA_MENU); - tabLayout->addWidget(mediaMenuCheckBoxes, 0, nullptr); + tabLayout->addWidget(mediaMenuCheckBoxes, 0, Qt::Alignment()); auto *showSizeHBox = new QHBoxLayout(); QLabel *labelShowSize = new QLabel(i18n("Show Size:"), tab); showSizeHBox->addWidget(labelShowSize); KONFIGURATOR_NAME_VALUE_PAIR showSizeValues[] = { { i18nc("setting 'show size'", "Always"), "Always" }, { i18nc("setting 'show size'", "When Device has no Label"), "WhenNoLabel" }, { i18nc("setting 'show size'", "Never"), "Never" }, }; KonfiguratorComboBox *showSizeCmb = createComboBox("MediaMenu", "ShowSize", "Always", showSizeValues, sizeof(showSizeValues) / sizeof(*showSizeValues), labelShowSize, tab, false, false, QString(), PAGE_MEDIA_MENU); showSizeHBox->addWidget(showSizeCmb); createIgnoredMountpointsList(tab, tabLayout); showSizeHBox->addStretch(); tabLayout->addLayout(showSizeHBox); tabLayout->addStretch(); } void KgPanel::createIgnoredMountpointsList(QWidget *tab, QBoxLayout *tabLayout) { QWidget *vboxWidget2 = new QWidget(tab); tabLayout->addWidget(vboxWidget2); auto *vbox2 = new QVBoxLayout(vboxWidget2); QWidget *hboxWidget3 = new QWidget(vboxWidget2); vbox2->addWidget(hboxWidget3); auto *hbox3 = new QHBoxLayout(hboxWidget3); QLabel *atomLabel = new QLabel(i18n("Hide following mountpoints:"), hboxWidget3); hbox3->addWidget(atomLabel); int size = QFontMetrics(atomLabel->font()).height(); auto *addButton = new QToolButton(hboxWidget3); hbox3->addWidget(addButton); QPixmap iconPixmap = Icon("list-add").pixmap(size); addButton->setFixedSize(iconPixmap.width() + 4, iconPixmap.height() + 4); addButton->setIcon(QIcon(iconPixmap)); connect(addButton, &QToolButton::clicked, this, &KgPanel::slotAddMountpoint); auto *removeButton = new QToolButton(hboxWidget3); hbox3->addWidget(removeButton); iconPixmap = Icon("list-remove").pixmap(size); removeButton->setFixedSize(iconPixmap.width() + 4, iconPixmap.height() + 4); removeButton->setIcon(QIcon(iconPixmap)); connect(removeButton, &QToolButton::clicked, this, &KgPanel::slotRemoveMountpoint); QStringList defaultHiddenMountpoints; // Empty list listBox = createListBox("MediaMenu", "Hidden Mountpoints", defaultHiddenMountpoints, vboxWidget2, true, QString(), PAGE_MEDIA_MENU); vbox2->addWidget(listBox); } void KgPanel::slotAddMountpoint() { bool ok; QString atomExt = QInputDialog::getText(this, i18n("Add new hidden mount point"), i18n("Mount point:"), QLineEdit::Normal, QString(), &ok); if (ok) { listBox->addItem(atomExt); } } void KgPanel::slotRemoveMountpoint() { QList list = listBox->selectedItems(); for (int i = 0; i != list.count(); i++) listBox->removeItem(list[i]->text()); } void KgPanel::slotEnablePanelToolbar() { bool enableTB = buttonsCheckboxes->find("Panel Toolbar visible")->isChecked(); panelToolbarButtonsCheckboxes->find("Root Button Visible")->setEnabled(enableTB); panelToolbarButtonsCheckboxes->find("Home Button Visible")->setEnabled(enableTB); panelToolbarButtonsCheckboxes->find("Up Button Visible")->setEnabled(enableTB); panelToolbarButtonsCheckboxes->find("Equal Button Visible")->setEnabled(enableTB); panelToolbarButtonsCheckboxes->find("SyncBrowse Button Visible")->setEnabled(enableTB); } void KgPanel::slotSelectionModeChanged() { KrSelectionMode *selectionMode = KrSelectionMode::getSelectionHandlerForMode(mouseRadio->selectedValue()); if (selectionMode == nullptr) //User mode return; selectionMode->init(); mouseCheckboxes->find("QT Selection")->setChecked(selectionMode->useQTSelection()); mouseCheckboxes->find("Left Selects")->setChecked(selectionMode->leftButtonSelects()); mouseCheckboxes->find("Left Preserves")->setChecked(selectionMode->leftButtonPreservesSelection()); mouseCheckboxes->find("ShiftCtrl Left Selects")->setChecked(selectionMode->shiftCtrlLeftButtonSelects()); mouseCheckboxes->find("Right Selects")->setChecked(selectionMode->rightButtonSelects()); mouseCheckboxes->find("Right Preserves")->setChecked(selectionMode->rightButtonPreservesSelection()); mouseCheckboxes->find("ShiftCtrl Right Selects")->setChecked(selectionMode->shiftCtrlRightButtonSelects()); mouseCheckboxes->find("Space Moves Down")->setChecked(selectionMode->spaceMovesDown()); mouseCheckboxes->find("Space Calc Space")->setChecked(selectionMode->spaceCalculatesDiskSpace()); mouseCheckboxes->find("Insert Moves Down")->setChecked(selectionMode->insertMovesDown()); mouseCheckboxes->find("Immediate Context Menu")->setChecked(selectionMode->showContextMenu() == -1); } void KgPanel::slotMouseCheckBoxChanged() { mouseRadio->selectButton("3"); //custom selection mode } int KgPanel::activeSubPage() { return tabWidget->currentIndex(); } bool KgPanel::apply() { // We read the IconSize config file before and after // applying the change, to see if it really changed KConfigGroup briefView(krConfig, "KrInterBriefView"); KConfigGroup detailedView(krConfig, "KrInterDetailedView"); int oldiconBriefSize = briefView.readEntry("IconSize", _FilelistIconSize).toInt(); int oldiconDetailSize = detailedView.readEntry("IconSize", _FilelistIconSize).toInt(); KonfiguratorPage::apply(); int iconBriefSize = briefView.readEntry("IconSize", _FilelistIconSize).toInt(); int iconDetailSize = detailedView.readEntry("IconSize", _FilelistIconSize).toInt(); return oldiconBriefSize != iconBriefSize || oldiconDetailSize != iconDetailSize; } diff --git a/krusader/Konfigurator/krresulttabledialog.cpp b/krusader/Konfigurator/krresulttabledialog.cpp index 30826e3a..1c5f7475 100644 --- a/krusader/Konfigurator/krresulttabledialog.cpp +++ b/krusader/Konfigurator/krresulttabledialog.cpp @@ -1,123 +1,124 @@ /***************************************************************************** * Copyright (C) 2005 Dirk Eschler * * Copyright (C) 2005-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krresulttabledialog.h" // QtGui #include // QtWidgets #include #include #include #include #include #include #include "../krglobal.h" #include "../icon.h" +#include "../compat.h" KrResultTableDialog::KrResultTableDialog(QWidget *parent, DialogType type, const QString& caption, const QString& heading, const QString& headerIcon, const QString& hint) - : QDialog(parent, nullptr) + : QDialog(parent, Qt::WindowFlags()) { setWindowTitle(caption); setWindowModality(Qt::WindowModal); auto *mainLayout = new QVBoxLayout; setLayout(mainLayout); auto *_topLayout = new QVBoxLayout(); _topLayout->setAlignment(Qt::AlignTop); // +++ Heading +++ // prepare the icon QWidget *_iconWidget = new QWidget(this); auto * _iconBox = new QHBoxLayout(_iconWidget); QLabel *_iconLabel = new QLabel(_iconWidget); _iconLabel->setPixmap(Icon(headerIcon).pixmap(32)); _iconLabel->setMinimumWidth(fontMetrics().maxWidth()*20); _iconLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); _iconLabel->setFixedSize(_iconLabel->sizeHint()); _iconBox->addWidget(_iconLabel); QLabel *_headingLabel = new QLabel(heading, _iconWidget); QFont defFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); defFont.setBold(true); _headingLabel->setFont(defFont); _headingLabel->setIndent(10); _iconBox->addWidget(_headingLabel); _topLayout->addWidget(_iconWidget); // +++ Add some space between heading and table +++ auto* hSpacer1 = new QSpacerItem(0, 5); _topLayout->addItem(hSpacer1); // +++ Table +++ switch (type) { case Archiver: _resultTable = new KrArchiverResultTable(this); helpAnchor = QStringLiteral("konfig-archives"); // launch handbook at sect1-id via help button break; case Tool: _resultTable = new KrToolResultTable(this); helpAnchor = QStringLiteral("konfig-dependencies"); // TODO find a good anchor break; default: break; } _topLayout->addWidget(_resultTable); // +++ Separator +++ KSeparator* hSep = new KSeparator(Qt::Horizontal, this); hSep->setContentsMargins(5, 5, 5, 5); _topLayout->addWidget(hSep); // +++ Hint +++ if (!hint.isEmpty()) { QLabel *_hintLabel = new QLabel(hint, this); _hintLabel->setIndent(5); _hintLabel->setAlignment(Qt::AlignRight); _topLayout->addWidget(_hintLabel); } mainLayout->addLayout(_topLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Help); mainLayout->addWidget(buttonBox); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &KrResultTableDialog::accept); connect(buttonBox, &QDialogButtonBox::helpRequested, this, &KrResultTableDialog::showHelp); buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); this->setFixedSize(this->sizeHint()); // make non-resizeable } KrResultTableDialog::~KrResultTableDialog() = default; void KrResultTableDialog::showHelp() { if(!helpAnchor.isEmpty()) { KHelpClient::invokeHelp(helpAnchor); } } diff --git a/krusader/Panel/PanelView/krmousehandler.cpp b/krusader/Panel/PanelView/krmousehandler.cpp index f1227cd4..ca15b718 100644 --- a/krusader/Panel/PanelView/krmousehandler.cpp +++ b/krusader/Panel/PanelView/krmousehandler.cpp @@ -1,387 +1,387 @@ /***************************************************************************** * Copyright (C) 2009 Csaba Karai * * Copyright (C) 2009-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krmousehandler.h" #include "krselectionmode.h" #include "krview.h" #include "krviewitem.h" #include "../defaults.h" #include "../krglobal.h" // QtCore #include // QtWidgets #include #include #include #include #define CANCEL_TWO_CLICK_RENAME {_singleClicked = false;_renameTimer.stop();} KrMouseHandler::KrMouseHandler(KrView *view, int contextMenuShift) : _view(view), _rightClickedItem(nullptr), _contextMenuShift(contextMenuShift), _singleClicked(false), _dragStartPos(-1, -1), _emptyContextMenu(false) { KConfigGroup grpSvr(krConfig, "Look&Feel"); // decide on single click/double click selection bool singleClickTmp = QApplication::style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick); _singleClick = grpSvr.readEntry("Single Click Selects", _SingleClickSelects) && singleClickTmp; connect(&_contextMenuTimer, &QTimer::timeout, this, &KrMouseHandler::showContextMenu); connect(&_renameTimer, &QTimer::timeout, this, &KrMouseHandler::renameCurrentItem); } bool KrMouseHandler::mousePressEvent(QMouseEvent *e) { _rightClickedItem = _clickedItem = nullptr; KrViewItem * item = _view->getKrViewItemAt(e->pos()); if (!_view->isFocused()) _view->op()->emitNeedFocus(); if (e->button() == Qt::LeftButton) { _dragStartPos = e->pos(); if (e->modifiers() == Qt::NoModifier) { if (item) { if (KrSelectionMode::getSelectionHandler()->leftButtonSelects()) { if (KrSelectionMode::getSelectionHandler()->leftButtonPreservesSelection()) item->setSelected(!item->isSelected()); else { if (item->isSelected()) _clickedItem = item; else { // clear the current selection _view->changeSelection(KrQuery("*"), false, true); item->setSelected(true); } } } _view->setCurrentKrViewItem(item); } else { // empty space under items clicked if (KrSelectionMode::getSelectionHandler()->leftButtonSelects() && !KrSelectionMode::getSelectionHandler()->leftButtonPreservesSelection()) { // clear the current selection _view->changeSelection(KrQuery("*"), false, true); } } e->accept(); return true; } else if (e->modifiers() == Qt::ControlModifier) { if (item && (KrSelectionMode::getSelectionHandler()->shiftCtrlLeftButtonSelects() || KrSelectionMode::getSelectionHandler()->leftButtonSelects())) { // get current selected item names _selectedItemNames.clear(); _view->getSelectedItems(&_selectedItemNames, false); item->setSelected(!item->isSelected()); // select also the focused item if there are no other selected items KrViewItem * previousItem = _view->getCurrentKrViewItem(); if (previousItem->name() != ".." && _selectedItemNames.empty()) { previousItem->setSelected(true); } } if (item) { _view->setCurrentKrViewItem(item); } e->accept(); return true; } else if (e->modifiers() == Qt::ShiftModifier) { if (item && (KrSelectionMode::getSelectionHandler()->shiftCtrlLeftButtonSelects() || KrSelectionMode::getSelectionHandler()->leftButtonSelects())) { KrViewItem * current = _view->getCurrentKrViewItem(); if (current != nullptr) _view->selectRegion(item, current, true); } if (item) _view->setCurrentKrViewItem(item); e->accept(); return true; } } if (e->button() == Qt::RightButton) { //dragStartPos = e->pos(); if (e->modifiers() == Qt::NoModifier) { if (item) { if (KrSelectionMode::getSelectionHandler()->rightButtonSelects()) { if (KrSelectionMode::getSelectionHandler()->rightButtonPreservesSelection()) { if (KrSelectionMode::getSelectionHandler()->showContextMenu() >= 0) { _rightClickSelects = !item->isSelected(); _rightClickedItem = item; } item->setSelected(!item->isSelected()); } else { if (item->isSelected()) { _clickedItem = item; } else { // clear the current selection _view->changeSelection(KrQuery("*"), false, true); item->setSelected(true); } } } _view->setCurrentKrViewItem(item); } handleContextMenu(item, e->globalPos()); e->accept(); return true; } else if (e->modifiers() == Qt::ControlModifier) { if (item && (KrSelectionMode::getSelectionHandler()->shiftCtrlRightButtonSelects() || KrSelectionMode::getSelectionHandler()->rightButtonSelects())) { item->setSelected(!item->isSelected()); } if (item) _view->setCurrentKrViewItem(item); e->accept(); return true; } else if (e->modifiers() == Qt::ShiftModifier) { if (item && (KrSelectionMode::getSelectionHandler()->shiftCtrlRightButtonSelects() || KrSelectionMode::getSelectionHandler()->rightButtonSelects())) { KrViewItem * current = _view->getCurrentKrViewItem(); if (current != nullptr) _view->selectRegion(item, current, true); } if (item) _view->setCurrentKrViewItem(item); e->accept(); return true; } } if (e->button() == Qt::ForwardButton) { _view->op()->emitGoForward(); return true; } if (e->button() == Qt::BackButton) { _view->op()->emitGoBack(); return true; } return false; } bool KrMouseHandler::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) _dragStartPos = QPoint(-1, -1); KrViewItem * item = _view->getKrViewItemAt(e->pos()); if (item && item == _clickedItem) { if (((e->button() == Qt::LeftButton) && (e->modifiers() == Qt::NoModifier) && (KrSelectionMode::getSelectionHandler()->leftButtonSelects()) && !(KrSelectionMode::getSelectionHandler()->leftButtonPreservesSelection())) || ((e->button() == Qt::RightButton) && (e->modifiers() == Qt::NoModifier) && (KrSelectionMode::getSelectionHandler()->rightButtonSelects()) && !(KrSelectionMode::getSelectionHandler()->rightButtonPreservesSelection()))) { // clear the current selection _view->changeSelection(KrQuery("*"), false, true); item->setSelected(true); } } if (e->button() == Qt::RightButton) { _rightClickedItem = nullptr; _contextMenuTimer.stop(); } if (_singleClick && e->button() == Qt::LeftButton && e->modifiers() == Qt::NoModifier) { CANCEL_TWO_CLICK_RENAME; e->accept(); if (item == nullptr) return true; QString tmp = item->name(); _view->op()->emitExecuted(tmp); return true; } else if (!_singleClick && e->button() == Qt::LeftButton) { if (item && e->modifiers() == Qt::NoModifier) { if (_singleClicked && !_renameTimer.isActive() && _singleClickedItem == item) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(krConfig, "KDE"); int doubleClickInterval = group.readEntry("DoubleClickInterval", 400); int msecsFromLastClick = _singleClickTime.msecsTo(QTime::currentTime()); if (msecsFromLastClick > doubleClickInterval && msecsFromLastClick < 5 * doubleClickInterval) { _singleClicked = false; _renameTimer.setSingleShot(true); _renameTimer.start(doubleClickInterval); return true; } } CANCEL_TWO_CLICK_RENAME; _singleClicked = true; _singleClickedItem = item; _singleClickTime = QTime::currentTime(); return true; } } CANCEL_TWO_CLICK_RENAME; if (e->button() == Qt::MidButton && item != nullptr) { e->accept(); if (item == nullptr) return true; _view->op()->emitMiddleButtonClicked(item); return true; } return false; } bool KrMouseHandler::mouseDoubleClickEvent(QMouseEvent *e) { CANCEL_TWO_CLICK_RENAME; KrViewItem * item = _view->getKrViewItemAt(e->pos()); if (_singleClick) return false; if (e->button() == Qt::LeftButton && item != nullptr) { e->accept(); const QString& tmp = item->name(); _view->op()->emitExecuted(tmp); return true; } return false; } bool KrMouseHandler::mouseMoveEvent(QMouseEvent *e) { KrViewItem * item = _view->getKrViewItemAt(e->pos()); if ((_singleClicked || _renameTimer.isActive()) && item != _singleClickedItem) CANCEL_TWO_CLICK_RENAME; if (!item) return false; const QString desc = item->description(); _view->op()->emitItemDescription(desc); if (_dragStartPos != QPoint(-1, -1) && (e->buttons() & Qt::LeftButton) && (_dragStartPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) { _view->op()->startDrag(); return true; } if (KrSelectionMode::getSelectionHandler()->rightButtonPreservesSelection() && KrSelectionMode::getSelectionHandler()->rightButtonSelects() && KrSelectionMode::getSelectionHandler()->showContextMenu() >= 0 && e->buttons() == Qt::RightButton) { e->accept(); if (item != _rightClickedItem && item && _rightClickedItem) { _view->selectRegion(item, _rightClickedItem, _rightClickSelects); _rightClickedItem = item; _view->setCurrentKrViewItem(item); _contextMenuTimer.stop(); } return true; } return false; } bool KrMouseHandler::wheelEvent(QWheelEvent *e) { if (!_view->isFocused()) _view->op()->emitNeedFocus(); if (e->modifiers() == Qt::ControlModifier) { - if (e->delta() > 0) { + if (e->angleDelta().y() > 0) { _view->zoomIn(); } else { _view->zoomOut(); } e->accept(); return true; } return false; } void KrMouseHandler::showContextMenu() { if (_rightClickedItem) _rightClickedItem->setSelected(true); if (_emptyContextMenu) _view->op()->emitEmptyContextMenu(_contextMenuPoint); else _view->op()->emitContextMenu(_contextMenuPoint); } void KrMouseHandler::handleContextMenu(KrViewItem * it, const QPoint & pos) { if (!_view->isFocused()) _view->op()->emitNeedFocus(); int i = KrSelectionMode::getSelectionHandler()->showContextMenu(); _contextMenuPoint = QPoint(pos.x(), pos.y() - _contextMenuShift); if (i < 0) { if (!it || it->isDummy()) _view->op()->emitEmptyContextMenu(_contextMenuPoint); else { _view->setCurrentKrViewItem(it); _view->op()->emitContextMenu(_contextMenuPoint); } } else if (i > 0) { _emptyContextMenu = !it || it->isDummy(); _contextMenuTimer.setSingleShot(true); _contextMenuTimer.start(i); } } void KrMouseHandler::otherEvent(QEvent * e) { switch (e->type()) { case QEvent::Timer: case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: break; default: CANCEL_TWO_CLICK_RENAME; } } void KrMouseHandler::cancelTwoClickRename() { CANCEL_TWO_CLICK_RENAME; } bool KrMouseHandler::dragEnterEvent(QDragEnterEvent *e) { QList URLs = KUrlMimeData::urlsFromMimeData(e->mimeData()); e->setAccepted(!URLs.isEmpty()); return true; } bool KrMouseHandler::dragMoveEvent(QDragMoveEvent *e) { QList URLs = KUrlMimeData::urlsFromMimeData(e->mimeData()); e->setAccepted(!URLs.isEmpty()); return true; } bool KrMouseHandler::dragLeaveEvent(QDragLeaveEvent * /*e*/) { return false; } bool KrMouseHandler::dropEvent(QDropEvent *e) { _view->op()->emitGotDrop(e); return true; } diff --git a/krusader/Panel/krlayoutfactory.cpp b/krusader/Panel/krlayoutfactory.cpp index ac40b2bc..930aee02 100644 --- a/krusader/Panel/krlayoutfactory.cpp +++ b/krusader/Panel/krlayoutfactory.cpp @@ -1,306 +1,307 @@ /***************************************************************************** * Copyright (C) 2010 Jan Lepper * * Copyright (C) 2010-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krlayoutfactory.h" #include "listpanelframe.h" #include "listpanel.h" #include "../krglobal.h" +#include "../compat.h" // QtCore #include #include #include #include #include // QtWidgets #include #include // QtXml #include #include #include #define XMLFILE_VERSION "1.0" #define MAIN_FILE "layout.xml" #define MAIN_FILE_RC_PATH ":/" MAIN_FILE #define EXTRA_FILE_MASK "layouts/*.xml" #define DEFAULT_LAYOUT "krusader:default" bool KrLayoutFactory::_parsed = false; QDomDocument KrLayoutFactory::_mainDoc; QList KrLayoutFactory::_extraDocs; QString KrLayoutFactory::layoutDescription(const QString& layoutName) { if(layoutName == DEFAULT_LAYOUT) return i18nc("Default layout", "Default"); else if(layoutName == "krusader:compact") return i18n("Compact"); else if(layoutName == "krusader:classic") return i18n("Classic"); else return i18n("Custom layout: \"%1\"", layoutName); } bool KrLayoutFactory::parseFiles() { if (_parsed) return true; _parsed = parseResource(MAIN_FILE_RC_PATH, _mainDoc); if (!_parsed) { return false; } QStringList extraFilePaths = QStandardPaths::locateAll(QStandardPaths::DataLocation, EXTRA_FILE_MASK); foreach(const QString &path, extraFilePaths) { qWarning() << "extra file: " << path; QDomDocument doc; if (parseFile(path, doc)) _extraDocs << doc; } return true; } bool KrLayoutFactory::parseFile(const QString& path, QDomDocument &doc) { bool success = false; QFile file(path); if (file.open(QIODevice::ReadOnly)) return parseContent(file.readAll(), path, doc); else qWarning() << "can't open" << path; return success; } bool KrLayoutFactory::parseResource(const QString& path, QDomDocument &doc) { QResource res(path); if (res.isValid()) { QByteArray data; - if (res.isCompressed()) + if (QRESOURCE_ISCOMPRESSED(res)) data = qUncompress(res.data(), res.size()); else data = QByteArray(reinterpret_cast(res.data()), res.size()); return parseContent(data, path, doc); } else { qWarning() << "resource does not exist:" << path; return false; } } bool KrLayoutFactory::parseContent(const QByteArray& content, const QString& fileName, QDomDocument &doc) { bool success = false; QString errorMsg; if (doc.setContent(content, &errorMsg)) { QDomElement root = doc.documentElement(); if (root.tagName() == "KrusaderLayout") { QString version = root.attribute("version"); if(version == XMLFILE_VERSION) success = true; else qWarning() << fileName << "has wrong version" << version << "- required is" << XMLFILE_VERSION; } else qWarning() << "root.tagName() != \"KrusaderLayout\""; } else qWarning() << "error parsing" << fileName << ":" << errorMsg; return success; } void KrLayoutFactory::getLayoutNames(const QDomDocument& doc, QStringList &names) { QDomElement root = doc.documentElement(); for(QDomElement e = root.firstChildElement(); ! e.isNull(); e = e.nextSiblingElement()) { if (e.tagName() == "layout") { QString name(e.attribute("name")); if (!name.isEmpty() && (name != DEFAULT_LAYOUT)) names << name; } } } QStringList KrLayoutFactory::layoutNames() { QStringList names; names << DEFAULT_LAYOUT; if (parseFiles()) { getLayoutNames(_mainDoc, names); foreach(const QDomDocument &doc, _extraDocs) getLayoutNames(doc, names); } return names; } QDomElement KrLayoutFactory::findLayout(const QDomDocument& doc, const QString& layoutName) { QDomElement root = doc.documentElement(); for(QDomElement e = root.firstChildElement(); ! e.isNull(); e = e.nextSiblingElement()) { if (e.tagName() == "layout" && e.attribute("name") == layoutName) return e; } return QDomElement(); } QLayout *KrLayoutFactory::createLayout(QString layoutName) { if(layoutName.isEmpty()) { KConfigGroup cg(krConfig, "PanelLayout"); layoutName = cg.readEntry("Layout", DEFAULT_LAYOUT); } QLayout *layout = nullptr; if (parseFiles()) { QDomElement layoutRoot; layoutRoot = findLayout(_mainDoc, layoutName); if (layoutRoot.isNull()) { foreach(const QDomDocument &doc, _extraDocs) { layoutRoot = findLayout(doc, layoutName); if(!layoutRoot.isNull()) break; } } if (layoutRoot.isNull()) { qWarning() << "no layout with name" << layoutName << "found"; if(layoutName != DEFAULT_LAYOUT) return createLayout(DEFAULT_LAYOUT); } else { layout = createLayout(layoutRoot, panel); } } if(layout) { foreach(const QString &name, widgets.keys()) { qWarning() << "widget" << name << "was not added to the layout"; widgets[name]->hide(); } } else qWarning() << "couldn't load layout" << layoutName; return layout; } QBoxLayout *KrLayoutFactory::createLayout(const QDomElement& e, QWidget *parent) { QBoxLayout *l = nullptr; bool horizontal = false; if(e.attribute("type") == "horizontal") { horizontal = true; l = new QHBoxLayout(); } else if(e.attribute("type") == "vertical") l = new QVBoxLayout(); else { qWarning() << "unknown layout type:" << e.attribute("type"); return nullptr; } l->setSpacing(0); l->setContentsMargins(0, 0, 0, 0); for(QDomElement child = e.firstChildElement(); ! child.isNull(); child = child.nextSiblingElement()) { if (child.tagName() == "layout") { if(QLayout *childLayout = createLayout(child, parent)) l->addLayout(childLayout); } else if(child.tagName() == "frame") { QWidget *frame = createFrame(child, parent); l->addWidget(frame); } else if(child.tagName() == "widget") { if(QWidget *w = widgets.take(child.attribute("name"))) l->addWidget(w); else qWarning() << "layout: no such widget:" << child.attribute("name"); } else if(child.tagName() == "hide_widget") { if(QWidget *w = widgets.take(child.attribute("name"))) w->hide(); else qWarning() << "layout: no such widget:" << child.attribute("name"); } else if(child.tagName() == "spacer") { if(horizontal) l->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed)); else l->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding)); } } return l; } QWidget *KrLayoutFactory::createFrame(const QDomElement& e, QWidget *parent) { KConfigGroup cg(krConfig, "PanelLayout"); QString color = cg.readEntry("FrameColor", "default"); if(color == "default") color = e.attribute("color"); else if(color == "none") color.clear(); int shadow = -1, shape = -1; QMetaEnum shadowEnum = QFrame::staticMetaObject.enumerator(QFrame::staticMetaObject.indexOfEnumerator("Shadow")); QString cfgShadow = cg.readEntry("FrameShadow", "default"); if(cfgShadow != "default") shadow = shadowEnum.keyToValue(cfgShadow.toLatin1().data()); if(shadow < 0) shadow = shadowEnum.keyToValue(e.attribute("shadow").toLatin1().data()); QMetaEnum shapeEnum = QFrame::staticMetaObject.enumerator(QFrame::staticMetaObject.indexOfEnumerator("Shape")); QString cfgShape = cg.readEntry("FrameShape", "default"); if(cfgShape!= "default") shape = shapeEnum.keyToValue(cfgShape.toLatin1().data()); if(shape < 0) shape = shapeEnum.keyToValue(e.attribute("shape").toLatin1().data()); ListPanelFrame *frame = new ListPanelFrame(parent, color); frame->setFrameStyle(shape | shadow); frame->setAcceptDrops(true); if(QLayout *l = createLayout(e, frame)) { l->setContentsMargins(frame->frameWidth(), frame->frameWidth(), frame->frameWidth(), frame->frameWidth()); frame->setLayout(l); } QObject::connect(frame, &ListPanelFrame::dropped, panel, [=](QDropEvent *event) { qobject_cast(panel)->handleDrop(event); }); if(!color.isEmpty()) QObject::connect(panel, &ListPanel::signalRefreshColors, frame, &ListPanelFrame::refreshColors); return frame; } diff --git a/krusader/Panel/panelfunc.cpp b/krusader/Panel/panelfunc.cpp index 07f6a822..14ee11e0 100644 --- a/krusader/Panel/panelfunc.cpp +++ b/krusader/Panel/panelfunc.cpp @@ -1,1407 +1,1407 @@ /***************************************************************************** * Copyright (C) 2000 Shie Erlich * * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "panelfunc.h" // QtCore #include #include #include #include #include #include // QtGui #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dirhistoryqueue.h" #include "krcalcspacedialog.h" #include "krerrordisplay.h" #include "krsearchbar.h" #include "listpanel.h" #include "listpanelactions.h" #include "PanelView/krview.h" #include "PanelView/krviewitem.h" #include "../krglobal.h" #include "../krslots.h" #include "../kractions.h" #include "../defaults.h" #include "../abstractpanelmanager.h" #include "../krservices.h" #include "../Archive/krarchandler.h" #include "../Archive/packjob.h" #include "../FileSystem/fileitem.h" #include "../FileSystem/virtualfilesystem.h" #include "../FileSystem/krpermhandler.h" #include "../FileSystem/filesystemprovider.h" #include "../FileSystem/sizecalculator.h" #include "../Dialogs/packgui.h" #include "../Dialogs/krdialogs.h" #include "../Dialogs/krpleasewait.h" #include "../Dialogs/krspwidgets.h" #include "../Dialogs/checksumdlg.h" #include "../KViewer/krviewer.h" #include "../MountMan/kmountman.h" - +#include "../compat.h" QPointer ListPanelFunc::copyToClipboardOrigin; ListPanelFunc::ListPanelFunc(ListPanel *parent) : QObject(parent), panel(parent), fileSystemP(nullptr), urlManuallyEntered(false), _isPaused(true), _refreshAfterPaused(true), _quickSizeCalculator(nullptr) { history = new DirHistoryQueue(panel); delayTimer.setSingleShot(true); connect(&delayTimer, &QTimer::timeout, this, &ListPanelFunc::doRefresh); } ListPanelFunc::~ListPanelFunc() { if (fileSystemP) { fileSystemP->deleteLater(); } delete history; if (_quickSizeCalculator) _quickSizeCalculator->deleteLater(); } bool ListPanelFunc::isSyncing(const QUrl &url) { if(otherFunc()->otherFunc() == this && panel->otherPanel()->gui->syncBrowseButton->isChecked() && !otherFunc()->syncURL.isEmpty() && otherFunc()->syncURL == url) return true; return false; } void ListPanelFunc::openFileNameInternal(const QString &name, bool externallyExecutable) { if (name == "..") { dirUp(); return; } FileItem *fileitem = files()->getFileItem(name); if (fileitem == nullptr) return; QUrl url = files()->getUrl(name); if (fileitem->isDir()) { panel->view->setNameToMakeCurrent(QString()); openUrl(url); return; } QString mime = fileitem->getMime(); QUrl arcPath = browsableArchivePath(name); if (!arcPath.isEmpty()) { bool browseAsDirectory = !externallyExecutable || (KConfigGroup(krConfig, "Archives").readEntry("ArchivesAsDirectories", _ArchivesAsDirectories) && (KrArcHandler::arcSupported(mime) || KrServices::isoSupported(mime))); if (browseAsDirectory) { openUrl(arcPath); return; } } if (externallyExecutable) { if (mime == QLatin1String("application/x-desktop")) { KDesktopFileActions::runWithStartup(url, url.isLocalFile(), QByteArray()); return; } if (KRun::isExecutableFile(url, mime)) { runCommand(KShell::quoteArg(url.path())); return; } KService::Ptr service = KMimeTypeTrader::self()->preferredService(mime); if(service) { runService(*service, QList() << url); return; } displayOpenWithDialog(QList() << url); } } QUrl ListPanelFunc::cleanPath(const QUrl &urlIn) { QUrl url = urlIn; url.setPath(QDir::cleanPath(url.path())); if (!url.isValid() || url.isRelative()) { if (url.url() == "~") url = QUrl::fromLocalFile(QDir::homePath()); else if (!url.url().startsWith('/')) { // possible relative URL - translate to full URL url = files()->currentDirectory(); url.setPath(url.path() + '/' + urlIn.path()); } } url.setPath(QDir::cleanPath(url.path())); return url; } void ListPanelFunc::openUrl(const QUrl &url, const QString& nameToMakeCurrent, bool manuallyEntered) { qDebug() << "URL=" << url.toDisplayString() << "; name to current=" << nameToMakeCurrent; if (panel->syncBrowseButton->isChecked()) { //do sync-browse stuff.... if(syncURL.isEmpty()) syncURL = panel->otherPanel()->virtualPath(); QString relative = QDir(panel->virtualPath().path() + '/').relativeFilePath(url.path()); syncURL.setPath(QDir::cleanPath(syncURL.path() + '/' + relative)); panel->otherPanel()->gui->setTabState(ListPanel::TabState::DEFAULT); otherFunc()->openUrlInternal(syncURL, nameToMakeCurrent, false, false); } openUrlInternal(url, nameToMakeCurrent, false, manuallyEntered); } void ListPanelFunc::immediateOpenUrl(const QUrl &url) { openUrlInternal(url, QString(), true, false); } void ListPanelFunc::openUrlInternal(const QUrl &url, const QString& nameToMakeCurrent, bool immediately, bool manuallyEntered) { const QUrl cleanUrl = cleanPath(url); if (panel->isLocked() && !files()->currentDirectory().matches(cleanUrl, QUrl::StripTrailingSlash)) { panel->_manager->newTab(url); urlManuallyEntered = false; return; } urlManuallyEntered = manuallyEntered; const QString currentItem = history->currentUrl().path() == cleanUrl.path() ? history->currentItem() : nameToMakeCurrent; panel->view->setNameToMakeCurrent(nameToMakeCurrent); history->add(cleanUrl, currentItem); if(immediately) doRefresh(); else refresh(); } void ListPanelFunc::refresh() { panel->cancelProgress(); delayTimer.start(0); // to avoid qApp->processEvents() deadlock situation } void ListPanelFunc::doRefresh() { delayTimer.stop(); if (_isPaused) { _refreshAfterPaused = true; // simulate refresh panel->slotStartUpdate(true); return; } else { _refreshAfterPaused = false; } const QUrl url = history->currentUrl(); if(!url.isValid()) { panel->slotStartUpdate(true); // refresh the panel urlManuallyEntered = false; return; } panel->cancelProgress(); // if we are not refreshing to current URL const bool isEqualUrl = files()->currentDirectory().matches(url, QUrl::StripTrailingSlash); if (!isEqualUrl) { panel->setCursor(Qt::WaitCursor); panel->view->clearSavedSelection(); } if (panel->fileSystemError) { panel->fileSystemError->hide(); } panel->setNavigatorUrl(url); // may get a new filesystem for this url FileSystem *fileSystem = FileSystemProvider::instance().getFilesystem(url, files()); fileSystem->setParentWindow(krMainWindow); connect(fileSystem, &FileSystem::aboutToOpenDir, &krMtMan, &KMountMan::autoMount, Qt::DirectConnection); if (fileSystem != fileSystemP) { panel->view->setFiles(nullptr); // disconnect older signals disconnect(fileSystemP, nullptr, panel, nullptr); fileSystemP->deleteLater(); fileSystemP = fileSystem; // v != 0 so this is safe } else { if (fileSystemP->isRefreshing()) { // TODO remove busy waiting here delayTimer.start(100); /* if filesystem is busy try refreshing later */ return; } } // (re)connect filesystem signals disconnect(files(), nullptr, panel, nullptr); connect(files(), &DirListerInterface::scanDone, panel, &ListPanel::slotStartUpdate); connect(files(), &FileSystem::fileSystemInfoChanged, panel, &ListPanel::updateFilesystemStats); connect(files(), &FileSystem::refreshJobStarted, panel, &ListPanel::slotRefreshJobStarted); connect(files(), &FileSystem::error, panel, &ListPanel::slotFilesystemError); panel->view->setFiles(files()); if (!isEqualUrl || !panel->view->getCurrentKrViewItem()) { // set current item after refresh from history, if there is none yet panel->view->setNameToMakeCurrent(history->currentItem()); } // workaround for detecting panel deletion while filesystem is refreshing QPointer panelSave = panel; // NOTE: this is blocking. Returns false on error or interruption (cancel requested or panel // was deleted) const bool scanned = fileSystemP->refresh(url); if (scanned) { // update the history and address bar, as the actual url might differ from the one requested history->setCurrentUrl(fileSystemP->currentDirectory()); panel->setNavigatorUrl(fileSystemP->currentDirectory()); } else if (!panelSave) { return; } panel->view->setNameToMakeCurrent(QString()); panel->setCursor(Qt::ArrowCursor); // on local file system change the working directory if (files()->isLocal()) QDir::setCurrent(KrServices::urlToLocalPath(files()->currentDirectory())); // see if the open url operation failed, and if so, // put the attempted url in the navigator bar and let the user change it if (!scanned) { if(isSyncing(url)) panel->otherPanel()->gui->syncBrowseButton->setChecked(false); else if(urlManuallyEntered) { panel->setNavigatorUrl(url); if(panel == ACTIVE_PANEL) panel->editLocation(); } } if(otherFunc()->otherFunc() == this) // not true if our tab is not active otherFunc()->syncURL = QUrl(); urlManuallyEntered = false; refreshActions(); } void ListPanelFunc::setPaused(bool paused) { if (paused == _isPaused) return; _isPaused = paused; // TODO: disable refresh() in local file system when paused if (!_isPaused && _refreshAfterPaused) refresh(); } void ListPanelFunc::redirectLink() { if (!files()->isLocal()) { KMessageBox::sorry(krMainWindow, i18n("You can edit links only on local file systems")); return; } FileItem *fileitem = files()->getFileItem(panel->getCurrentName()); if (!fileitem) return; QString file = fileitem->getUrl().path(); QString currentLink = fileitem->getSymDest(); if (currentLink.isEmpty()) { KMessageBox::sorry(krMainWindow, i18n("The current file is not a link, so it cannot be redirected.")); return; } // ask the user for a new destination bool ok = false; QString newLink = QInputDialog::getText(krMainWindow, i18n("Link Redirection"), i18n("Please enter the new link destination:"), QLineEdit::Normal, currentLink, &ok); // if the user canceled - quit if (!ok || newLink == currentLink) return; // delete the current link if (unlink(file.toLocal8Bit()) == -1) { KMessageBox::sorry(krMainWindow, i18n("Cannot remove old link: %1", file)); return; } // try to create a new symlink if (symlink(newLink.toLocal8Bit(), file.toLocal8Bit()) == -1) { KMessageBox:: /* --=={ Patch by Heiner }==-- */sorry(krMainWindow, i18n("Failed to create a new link: %1", file)); return; } } void ListPanelFunc::krlink(bool sym) { if (!files()->isLocal()) { KMessageBox::sorry(krMainWindow, i18n("You can create links only on local file systems")); return; } QString name = panel->getCurrentName(); // ask the new link name.. bool ok = false; QString linkName = QInputDialog::getText(krMainWindow, i18n("New Link"), i18n("Create a new link to: %1", name), QLineEdit::Normal, name, &ok); // if the user canceled - quit if (!ok || linkName == name) return; // if the name is already taken - quit if (files()->getFileItem(linkName) != nullptr) { KMessageBox::sorry(krMainWindow, i18n("A folder or a file with this name already exists.")); return; } // make link name and target absolute path if (linkName.left(1) != "/") linkName = files()->currentDirectory().path() + '/' + linkName; name = files()->getUrl(name).path(); if (sym) { if (symlink(name.toLocal8Bit(), linkName.toLocal8Bit()) == -1) KMessageBox::sorry(krMainWindow, i18n("Failed to create a new symlink '%1' to: '%2'", linkName, name)); } else { if (link(name.toLocal8Bit(), linkName.toLocal8Bit()) == -1) KMessageBox::sorry(krMainWindow, i18n("Failed to create a new link '%1' to '%2'", linkName, name)); } } void ListPanelFunc::view() { panel->searchBar->hideBarIfSearching(); QString fileName = panel->getCurrentName(); if (fileName.isNull()) return; // if we're trying to view a directory, just exit FileItem *fileitem = files()->getFileItem(fileName); if (!fileitem || fileitem->isDir()) return; if (!fileitem->isReadable()) { KMessageBox::sorry(nullptr, i18n("No permissions to view this file.")); return; } // call KViewer. KrViewer::view(files()->getUrl(fileName)); // nothing more to it! } void ListPanelFunc::viewDlg() { // ask the user for a url to view QUrl dest = KChooseDir::getFile(i18n("Enter a URL to view:"), QUrl(panel->getCurrentName()), panel->virtualPath()); if (dest.isEmpty()) return ; // the user canceled KrViewer::view(dest); // view the file } void ListPanelFunc::terminal() { SLOTS->runTerminal(panel->lastLocalPath()); } void ListPanelFunc::editFile(const QUrl &filePath) { panel->searchBar->hideBarIfSearching(); QUrl editPath; if (!filePath.isEmpty()) { editPath = filePath; } else { const QString name = panel->getCurrentName(); if (name.isNull()) return; editPath = files()->getUrl(name); } if (editPath.isLocalFile()) { const KFileItem fileToEdit = KFileItem(editPath); if (fileToEdit.isDir()) { KMessageBox::sorry(krMainWindow, i18n("You cannot edit a folder")); return; } if (!fileToEdit.isReadable()) { KMessageBox::sorry(nullptr, i18n("No permissions to edit this file.")); return; } KrViewer::edit(editPath); } else { KIO::StatJob* statJob = KIO::stat(editPath, KIO::HideProgressInfo); connect(statJob, &KIO::StatJob::result, this, &ListPanelFunc::slotStatEdit); } } void ListPanelFunc::askEditFile() { // ask the user for the filename to edit const QUrl filePath = KChooseDir::getFile(i18n("Enter the filename to edit:"), QUrl(panel->getCurrentName()), panel->virtualPath()); if (filePath.isEmpty()) { return; // the user canceled } if (filePath.isLocalFile()) { // if the file exists, edit it instead of creating a new one QFile file(filePath.toLocalFile()); if (file.exists()) { editFile(filePath); return; } else { // simply create a local file // also because KIO::CopyJob::setDefaultPermissions does not work #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) file.open(QIODevice::NewOnly); #else file.open(QIODevice::WriteOnly); #endif file.close(); slotFileCreated(nullptr, filePath); return; } } else { KIO::StatJob* statJob = KIO::stat(filePath, KIO::HideProgressInfo); connect(statJob, &KIO::StatJob::result, this, &ListPanelFunc::slotStatEdit); } } void ListPanelFunc::slotStatEdit(KJob* job) { if (!job) return; const KIO::StatJob *statJob = dynamic_cast(job); const QUrl &url = statJob->url(); if (job->error()) { if (job->error() == KIO::ERR_DOES_NOT_EXIST) { // create a new file auto *tempFile = new QTemporaryFile; tempFile->setAutoRemove(false); // done below tempFile->open(); // create file KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(tempFile->fileName()), url); job->setUiDelegate(nullptr); job->setDefaultPermissions(true); connect(job, &KIO::CopyJob::result, this, [=](KJob *job) { slotFileCreated(job, url); }); connect(job, &KIO::CopyJob::result, tempFile, &QTemporaryFile::deleteLater); return; } else { KMessageBox::error(nullptr, job->errorString()); return; } } if (statJob->statResult().isDir()) { KMessageBox::sorry(nullptr, i18n("You cannot edit a folder")); return; } KrViewer::edit(url); } void ListPanelFunc::slotFileCreated(KJob *job, const QUrl filePath) { if (!job || (!job->error() || job->error() == KIO::ERR_FILE_ALREADY_EXIST)) { KrViewer::edit(filePath); if (KIO::upUrl(filePath).matches(panel->virtualPath(), QUrl::StripTrailingSlash)) { refresh(); } if (KIO::upUrl(filePath).matches(panel->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)) { otherFunc()->refresh(); } } else { KMessageBox::sorry(krMainWindow, job->errorString()); } } void ListPanelFunc::copyFiles(bool enqueue, bool move) { panel->searchBar->hideBarIfSearching(); const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety QUrl destination = panel->otherPanel()->virtualPath(); bool fullDestPath = false; if (fileNames.count() == 1 && otherFunc()->files()->type() != FileSystem::FS_VIRTUAL) { FileItem *item = files()->getFileItem(fileNames[0]); if (item && !item->isDir()) { fullDestPath = true; // add original filename to destination destination.setPath(QDir(destination.path()).filePath(item->getUrl().fileName())); } } if (!fullDestPath) { destination = FileSystem::ensureTrailingSlash(destination); } const KConfigGroup group(krConfig, "Advanced"); const bool showDialog = move ? group.readEntry("Confirm Move", _ConfirmMove) : group.readEntry("Confirm Copy", _ConfirmCopy); if (showDialog) { QString operationText; if (move) { operationText = fileNames.count() == 1 ? i18n("Move %1 to:", fileNames.first()) : i18np("Move %1 file to:", "Move %1 files to:", fileNames.count()); } else { operationText = fileNames.count() == 1 ? i18n("Copy %1 to:", fileNames.first()) : i18np("Copy %1 file to:", "Copy %1 files to:", fileNames.count()); } // ask the user for the copy/move dest const KChooseDir::ChooseResult result = KChooseDir::getCopyDir(operationText, destination, panel->virtualPath()); destination = result.url; if (destination.isEmpty()) return ; // the user canceled enqueue = result.enqueue; } const JobMan::StartMode startMode = enqueue && krJobMan->isQueueModeEnabled() ? JobMan::Delay : !enqueue && !krJobMan->isQueueModeEnabled() ? JobMan::Start : JobMan::Enqueue; const QList fileUrls = files()->getUrls(fileNames); if (move) { // after the delete return the cursor to the first unmarked file above the current item panel->prepareToDelete(); } // make sure the user does not overwrite multiple files by mistake if (fileNames.count() > 1) { destination = FileSystem::ensureTrailingSlash(destination); } const KIO::CopyJob::CopyMode mode = move ? KIO::CopyJob::Move : KIO::CopyJob::Copy; FileSystemProvider::instance().startCopyFiles(fileUrls, destination, mode, true, startMode); if(KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) { panel->view->saveSelection(); panel->view->unselectAll(); } } // called from SLOTS to begin the renaming process void ListPanelFunc::rename() { panel->searchBar->hideBarIfSearching(); panel->view->renameCurrentItem(); } // called by signal itemRenamed() from the view to complete the renaming process void ListPanelFunc::rename(const QString &oldname, const QString &newname) { if (oldname == newname) return ; // do nothing // set current after rename panel->view->setNameToMakeCurrent(newname); // as always - the filesystem do the job files()->rename(oldname, newname); } void ListPanelFunc::mkdir() { QDialog dialog; dialog.setModal(true); dialog.setWindowTitle(i18n("New Folder")); QVBoxLayout layout; dialog.setLayout(&layout); QLabel comboBoxLabel(i18n("Folder's name:")); layout.addWidget(&comboBoxLabel); KrHistoryComboBox comboBox(&dialog); comboBox.setMaxCount(50); comboBox.setMinimumContentsLength(30); // Ensure that the window title is fully seen layout.addWidget(&comboBox); QDialogButtonBox buttonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, &dialog); layout.addWidget(&buttonBox); layout.setSizeConstraint(QLayout::SetFixedSize); connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); - connect(&comboBox, QOverload::of(&KrHistoryComboBox::activated), + connect(&comboBox, QOverload::of(&KrHistoryComboBox::QCOMBOBOX_ACTIVATED), &comboBox, &KrHistoryComboBox::addToHistory); // ------------------------------------------------------------------------ // load the history and completion list after creating the KrHistoryComboBox // in the configuration file: the group where the configuration is saved const QString confGroup = "Private"; KConfigGroup group(krConfig, confGroup); const QString entryName = "NewFolder"; // in the configuration file: the key where the completion list is saved const QString completionListKey = entryName + " Completion list"; QStringList list = group.readEntry(completionListKey, QStringList()); comboBox.completionObject()->setItems(list); // in the configuration file: the key where the history list is saved const QString historyListKey = entryName + " History list"; list = group.readEntry(historyListKey, QStringList()); comboBox.setHistoryItems(list); // ------------------------------------------------------------------------ // the suggested name is the complete name for the folders, // while filenames are suggested without their extension QString suggestedName = panel->getCurrentName(); if (!suggestedName.isEmpty() && !files()->getFileItem(suggestedName)->isDir()) suggestedName = QFileInfo(suggestedName).completeBaseName(); comboBox.setEditText(suggestedName); comboBox.lineEdit()->selectAll(); // ask the name of the new folder const auto dialogResult = dialog.exec(); const QString dirName = comboBox.currentText(); if (dialogResult == QDialog::Accepted) comboBox.addToHistory(dirName); // save the history and completion list list = comboBox.completionObject()->items(); group.writeEntry(completionListKey, list); list = comboBox.historyItems(); group.writeEntry(historyListKey, list); if (dialogResult != QDialog::Accepted) return; const QString firstName = dirName.section('/', 0, 0, QString::SectionIncludeLeadingSep); // if the user canceled or the name was composed of slashes -> quit if (!dirName.startsWith('/') && firstName.isEmpty()) { return; } // notify the user about an existing folder if only a single directory was given if (!dirName.contains('/') && files()->getFileItem(firstName)) { // focus the existing directory panel->view->setCurrentItem(firstName); // show an error message KMessageBox::sorry(krMainWindow, i18n("A folder or a file with this name already exists.")); return; } // focus new directory when next refresh happens panel->view->setNameToMakeCurrent(firstName); // create new directory (along with underlying directories if necessary) files()->mkDir(dirName); } void ListPanelFunc::defaultOrAlternativeDeleteFiles(bool invert) { const bool trash = KConfigGroup(krConfig, "General").readEntry("Move To Trash", _MoveToTrash); deleteFiles(trash != invert); } void ListPanelFunc::deleteFiles(bool moveToTrash) { panel->searchBar->hideBarIfSearching(); const bool isVFS = files()->type() == FileSystem::FS_VIRTUAL; if (isVFS && files()->isRoot()) { // only virtual deletion possible removeVirtualFiles(); return; } // first get the selected file names list const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return; // move to trash: only if possible moveToTrash = moveToTrash && files()->canMoveToTrash(fileNames); // now ask the user if he/she is sure: const QList confirmedUrls = confirmDeletion( files()->getUrls(fileNames), moveToTrash, isVFS, false); if (confirmedUrls.isEmpty()) return; // nothing to delete // after the delete return the cursor to the first unmarked // file above the current item; panel->prepareToDelete(); // let the filesystem do the job... files()->deleteFiles(confirmedUrls, moveToTrash); } QList ListPanelFunc::confirmDeletion(const QList &urls, bool moveToTrash, bool virtualFS, bool showPath) { QStringList files; for (const QUrl& fileUrl : urls) { files.append(showPath ? fileUrl.toDisplayString(QUrl::PreferLocalFile) : fileUrl.fileName()); } const KConfigGroup advancedGroup(krConfig, "Advanced"); if (advancedGroup.readEntry("Confirm Delete", _ConfirmDelete)) { QString s; // text KGuiItem b; // continue button if (moveToTrash) { s = i18np("Do you really want to move this item to the trash?", "Do you really want to move these %1 items to the trash?", files.count()); b = KGuiItem(i18n("&Trash")); } else if (virtualFS) { s = i18np("Do you really want to delete this item physically (not just " "removing it from the virtual items)?", "Do you really want to delete these %1 items physically (not just " "removing them from the virtual items)?", files.count()); b = KStandardGuiItem::del(); } else { s = i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", files.count()); b = KStandardGuiItem::del(); } // show message // note: i'm using continue and not yes/no because the yes/no has cancel as default button if (KMessageBox::warningContinueCancelList(krMainWindow, s, files, i18n("Warning"), b) != KMessageBox::Continue) { return QList(); } } // we want to warn the user about non empty dir const bool emptyDirVerify = advancedGroup.readEntry("Confirm Unempty Dir", _ConfirmUnemptyDir); QList toDelete; if (emptyDirVerify) { QSet confirmedFiles; for (const QUrl& fileUrl : urls) { confirmedFiles.insert(fileUrl); if (!fileUrl.isLocalFile()) { continue; // TODO only local fs supported } const QString filePath = fileUrl.toLocalFile(); QFileInfo fileInfo(filePath); if (fileInfo.isDir() && !fileInfo.isSymLink()) { // read local dir... const QDir dir(filePath); if (!dir.entryList(QDir::AllEntries | QDir::System | QDir::Hidden | QDir::NoDotAndDotDot).isEmpty()) { // ...is not empty, ask user const QString fileString = showPath ? filePath : fileUrl.fileName(); const KMessageBox::ButtonCode result = KMessageBox::warningYesNoCancel( krMainWindow, i18n("

Folder %1 is not empty.

", fileString) + (moveToTrash ? i18n("

Skip this one or trash all?

") : i18n("

Skip this one or delete all?

")), QString(), KGuiItem(i18n("&Skip")), KGuiItem(moveToTrash ? i18n("&Trash All") : i18n("&Delete All"))); if (result == KMessageBox::Yes) { confirmedFiles.remove(fileUrl); // skip this dir } else if (result == KMessageBox::No) { break; // accept all remaining } else { return QList(); // cancel } } } } toDelete = confirmedFiles.values(); } else { toDelete = urls; } return toDelete; } void ListPanelFunc::removeVirtualFiles() { if (files()->type() != FileSystem::FS_VIRTUAL) { qWarning() << "filesystem not virtual"; return; } const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return; const QString text = i18np("Do you really want to delete this virtual item (physical files stay untouched)?", "Do you really want to delete these %1 virtual items (physical files stay " "untouched)?", fileNames.count()); if (KMessageBox::warningContinueCancelList(krMainWindow, text, fileNames, i18n("Warning"), KStandardGuiItem::remove()) != KMessageBox::Continue) return; auto *fileSystem = dynamic_cast(files()); fileSystem->remove(fileNames); } void ListPanelFunc::goInside(const QString& name) { openFileNameInternal(name, false); } void ListPanelFunc::runCommand(const QString& cmd) { qDebug() << "command=" << cmd; const QString workdir = panel->virtualPath().isLocalFile() ? panel->virtualPath().path() : QDir::homePath(); if(!KRun::runCommand(cmd, krMainWindow, workdir)) KMessageBox::error(nullptr, i18n("Could not start %1", cmd)); } void ListPanelFunc::runService(const KService &service, const QList& urls) { qDebug() << "service name=" << service.name(); KIO::DesktopExecParser parser(service, urls); QStringList args = parser.resultingArguments(); if (!args.isEmpty()) runCommand(KShell::joinArgs(args)); else KMessageBox::error(nullptr, i18n("%1 cannot open %2", service.name(), KrServices::toStringList(urls).join(", "))); } void ListPanelFunc::displayOpenWithDialog(const QList& urls) { // NOTE: we are not using KRun::displayOpenWithDialog() because we want the commands working // directory to be the panel directory KOpenWithDialog dialog(urls, panel); dialog.hideRunInTerminal(); if (dialog.exec()) { KService::Ptr service = dialog.service(); if(!service) service = KService::Ptr(new KService(dialog.text(), dialog.text(), QString())); runService(*service, urls); } } QUrl ListPanelFunc::browsableArchivePath(const QString &filename) { FileItem *fileitem = files()->getFileItem(filename); QUrl url = files()->getUrl(filename); QString mime = fileitem->getMime(); if(url.isLocalFile()) { QString protocol = krArcMan.registeredProtocol(mime); if(!protocol.isEmpty()) { url.setScheme(protocol); return url; } } return QUrl(); } // this is done when you double click on a file void ListPanelFunc::execute(const QString& name) { openFileNameInternal(name, true); } void ListPanelFunc::pack() { const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety if (fileNames.count() == 0) return ; // nothing to pack // choose the default name QString defaultName = panel->virtualPath().fileName(); if (defaultName.isEmpty()) defaultName = "pack"; if (fileNames.count() == 1) defaultName = fileNames.first(); // ask the user for archive name and packer new PackGUI(defaultName, panel->otherPanel()->virtualPath().toDisplayString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash), fileNames.count(), fileNames.first()); if (PackGUI::type.isEmpty()) { return ; // the user canceled } // check for partial URLs if (!PackGUI::destination.contains(":/") && !PackGUI::destination.startsWith('/')) { PackGUI::destination = panel->virtualPath().toDisplayString() + '/' + PackGUI::destination; } QString destDir = PackGUI::destination; if (!destDir.endsWith('/')) destDir += '/'; bool packToOtherPanel = (destDir == FileSystem::ensureTrailingSlash(panel->otherPanel()->virtualPath()).toDisplayString(QUrl::PreferLocalFile)); QUrl destURL = QUrl::fromUserInput(destDir + PackGUI::filename + '.' + PackGUI::type, QString(), QUrl::AssumeLocalFile); if (destURL.isLocalFile() && QFile::exists(destURL.path())) { QString msg = i18n("

The archive %1.%2 already exists. Do you want to overwrite it?

All data in the previous archive will be lost.

", PackGUI::filename, PackGUI::type); if (PackGUI::type == "zip") { msg = i18n("

The archive %1.%2 already exists. Do you want to overwrite it?

Zip will replace identically named entries in the zip archive or add entries for new names.

", PackGUI::filename, PackGUI::type); } if (KMessageBox::warningContinueCancel(krMainWindow, msg, QString(), KStandardGuiItem::overwrite()) == KMessageBox::Cancel) return ; // stop operation } else if (destURL.scheme() == QStringLiteral("virt")) { KMessageBox::error(krMainWindow, i18n("Cannot pack files onto a virtual destination.")); return; } PackJob * job = PackJob::createPacker(files()->currentDirectory(), destURL, fileNames, PackGUI::type, PackGUI::extraProps); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->uiDelegate()->setAutoErrorHandlingEnabled(true); if (packToOtherPanel) connect(job, &PackJob::result, panel->otherPanel()->func, &ListPanelFunc::refresh); } void ListPanelFunc::testArchive() { const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety TestArchiveJob * job = TestArchiveJob::testArchives(files()->currentDirectory(), fileNames); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } void ListPanelFunc::unpack() { const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety QString s; if (fileNames.count() == 1) s = i18n("Unpack %1 to:", fileNames[0]); else s = i18np("Unpack %1 file to:", "Unpack %1 files to:", fileNames.count()); // ask the user for the copy dest QUrl dest = KChooseDir::getDir(s, panel->otherPanel()->virtualPath(), panel->virtualPath()); if (dest.isEmpty()) return ; // the user canceled bool packToOtherPanel = (dest.matches(panel->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)); UnpackJob * job = UnpackJob::createUnpacker(files()->currentDirectory(), dest, fileNames); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->uiDelegate()->setAutoErrorHandlingEnabled(true); if (packToOtherPanel) connect(job, &UnpackJob::result, panel->otherPanel()->func, &ListPanelFunc::refresh); } void ListPanelFunc::createChecksum() { if (!panel->func->files()->isLocal()) return; // only local, non-virtual files are supported const KrViewItemList items = panel->view->getSelectedKrViewItems(); QStringList fileNames; for (KrViewItem *item : items) { FileItem *file = panel->func->getFileItem(item); fileNames.append(file->getUrl().fileName()); } if (fileNames.isEmpty()) return; // nothing selected and no valid current file Checksum::startCreationWizard(panel->virtualPath().toLocalFile(), fileNames); } void ListPanelFunc::matchChecksum() { if (!panel->func->files()->isLocal()) return; // only local, non-virtual files are supported FileItem *currentItem = files()->getFileItem(panel->getCurrentName()); const QString checksumFilePath = currentItem ? currentItem->getUrl().toLocalFile() : QString(); Checksum::startVerifyWizard(panel->virtualPath().toLocalFile(), checksumFilePath); } void ListPanelFunc::calcSpace() { QStringList fileNames; panel->view->getSelectedItems(&fileNames); if (fileNames.isEmpty()) { // current file is ".." dummy file panel->view->selectAllIncludingDirs(); panel->view->getSelectedItems(&fileNames); panel->view->unselectAll(); } SizeCalculator *sizeCalculator = createAndConnectSizeCalculator(files()->getUrls(fileNames)); KrCalcSpaceDialog::showDialog(panel, sizeCalculator); } void ListPanelFunc::quickCalcSpace() { const QString currentName = panel->getCurrentName(); if (currentName.isEmpty()) { // current file is ".." dummy, do a verbose calcSpace calcSpace(); return; } if (!_quickSizeCalculator) { _quickSizeCalculator = createAndConnectSizeCalculator(QList()); panel->connectQuickSizeCalculator(_quickSizeCalculator); } _quickSizeCalculator->add(files()->getUrl(currentName)); } SizeCalculator *ListPanelFunc::createAndConnectSizeCalculator(const QList &urls) { auto *sizeCalculator = new SizeCalculator(urls); connect(sizeCalculator, &SizeCalculator::calculated, this, &ListPanelFunc::slotSizeCalculated); connect(sizeCalculator, &SizeCalculator::finished, panel, &ListPanel::slotUpdateTotals); connect(this, &ListPanelFunc::destroyed, sizeCalculator, &SizeCalculator::deleteLater); return sizeCalculator; } void ListPanelFunc::slotSizeCalculated(const QUrl &url, KIO::filesize_t size) { KrViewItem *item = panel->view->findItemByUrl(url); if (!item) return; item->setSize(size); item->redraw(); } void ListPanelFunc::FTPDisconnect() { // you can disconnect only if connected! if (files()->isRemote()) { panel->_actions->actFTPDisconnect->setEnabled(false); panel->view->setNameToMakeCurrent(QString()); openUrl(QUrl::fromLocalFile(panel->lastLocalPath())); } } void ListPanelFunc::newFTPconnection() { QUrl url = KrSpWidgets::newFTP(); // if the user canceled - quit if (url.isEmpty()) return; panel->_actions->actFTPDisconnect->setEnabled(true); qDebug() << "URL=" << url.toDisplayString(); openUrl(url); } void ListPanelFunc::properties() { const QStringList names = panel->getSelectedNames(); if (names.isEmpty()) { return ; // no names... } KFileItemList fileItems; for (const QString& name : names) { FileItem *fileitem = files()->getFileItem(name); if (!fileitem) { continue; } fileItems.push_back(KFileItem(fileitem->getEntry(), files()->getUrl(name))); } if (fileItems.isEmpty()) return; // Show the properties dialog auto *dialog = new KPropertiesDialog(fileItems, krMainWindow); connect(dialog, &KPropertiesDialog::applied, this, &ListPanelFunc::refresh); dialog->show(); } void ListPanelFunc::refreshActions() { panel->updateButtons(); if(ACTIVE_PANEL != panel) return; QString protocol = files()->currentDirectory().scheme(); krRemoteEncoding->setEnabled(protocol == "ftp" || protocol == "sftp" || protocol == "fish" || protocol == "krarc"); //krMultiRename->setEnabled( fileSystemType == FileSystem::FS_NORMAL ); // batch rename //krProperties ->setEnabled( fileSystemType == FileSystem::FS_NORMAL || fileSystemType == FileSystem::FS_FTP ); // file properties /* krUnpack->setEnabled(true); // unpack archive krTest->setEnabled(true); // test archive krSelect->setEnabled(true); // select a group by filter krSelectAll->setEnabled(true); // select all files krUnselect->setEnabled(true); // unselect by filter krUnselectAll->setEnabled( true); // remove all selections krInvert->setEnabled(true); // invert the selection krFTPConnect->setEnabled(true); // connect to an ftp krFTPNew->setEnabled(true); // create a new connection krAllFiles->setEnabled(true); // show all files in list krCustomFiles->setEnabled(true); // show a custom set of files krRoot->setEnabled(true); // go all the way up krExecFiles->setEnabled(true); // show only executables */ panel->_actions->setViewActions[panel->panelType]->setChecked(true); panel->_actions->actFTPDisconnect->setEnabled(files()->isRemote()); // allow disconnecting a network session panel->_actions->actCreateChecksum->setEnabled(files()->isLocal()); panel->_actions->actDirUp->setEnabled(!files()->isRoot()); panel->_actions->actRoot->setEnabled(!panel->virtualPath().matches(QUrl::fromLocalFile(ROOT_DIR), QUrl::StripTrailingSlash)); panel->_actions->actHome->setEnabled(!atHome()); panel->_actions->actHistoryBackward->setEnabled(history->canGoBack()); panel->_actions->actHistoryForward->setEnabled(history->canGoForward()); panel->view->op()->emitRefreshActions(); } FileSystem* ListPanelFunc::files() { if (!fileSystemP) fileSystemP = FileSystemProvider::instance().getFilesystem(QUrl::fromLocalFile(ROOT_DIR)); return fileSystemP; } QUrl ListPanelFunc::virtualDirectory() { return _isPaused ? history->currentUrl() : files()->currentDirectory(); } FileItem *ListPanelFunc::getFileItem(const QString &name) { return files()->getFileItem(name); } FileItem *ListPanelFunc::getFileItem(KrViewItem *item) { return files()->getFileItem(item->name()); } void ListPanelFunc::clipboardChanged(QClipboard::Mode mode) { if (mode == QClipboard::Clipboard && this == copyToClipboardOrigin) { disconnect(QApplication::clipboard(), nullptr, this, nullptr); copyToClipboardOrigin = nullptr; } } void ListPanelFunc::copyToClipboard(bool move) { const QStringList fileNames = panel->getSelectedNames(); if (fileNames.isEmpty()) return ; // safety QList fileUrls = files()->getUrls(fileNames); auto *mimeData = new QMimeData; mimeData->setData("application/x-kde-cutselection", move ? "1" : "0"); mimeData->setUrls(fileUrls); if (copyToClipboardOrigin) disconnect(QApplication::clipboard(), nullptr, copyToClipboardOrigin, nullptr); copyToClipboardOrigin = this; QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); connect(QApplication::clipboard(), &QClipboard::changed, this, &ListPanelFunc::clipboardChanged); } void ListPanelFunc::pasteFromClipboard() { QClipboard * cb = QApplication::clipboard(); ListPanelFunc *origin = nullptr; if (copyToClipboardOrigin) { disconnect(QApplication::clipboard(), nullptr, copyToClipboardOrigin, nullptr); origin = copyToClipboardOrigin; copyToClipboardOrigin = nullptr; } bool move = false; const QMimeData *data = cb->mimeData(); if (data->hasFormat("application/x-kde-cutselection")) { QByteArray a = data->data("application/x-kde-cutselection"); if (!a.isEmpty()) move = (a.at(0) == '1'); // true if 1 } QList urls = data->urls(); if (urls.isEmpty()) return; if(origin && KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) { origin->panel->view->saveSelection(); for(KrViewItem *item = origin->panel->view->getFirst(); item != nullptr; item = origin->panel->view->getNext(item)) { if (urls.contains(item->getFileItem()->getUrl())) item->setSelected(false); } } files()->addFiles(urls, move ? KIO::CopyJob::Move : KIO::CopyJob::Copy); } ListPanelFunc* ListPanelFunc::otherFunc() { return panel->otherPanel()->func; } void ListPanelFunc::historyGotoPos(int pos) { if(history->gotoPos(pos)) refresh(); } void ListPanelFunc::historyBackward() { if(history->goBack()) refresh(); } void ListPanelFunc::historyForward() { if(history->goForward()) refresh(); } void ListPanelFunc::dirUp() { openUrl(KIO::upUrl(files()->currentDirectory()), files()->currentDirectory().fileName()); } void ListPanelFunc::home() { openUrl(QUrl::fromLocalFile(QDir::homePath())); } void ListPanelFunc::root() { openUrl(QUrl::fromLocalFile(ROOT_DIR)); } void ListPanelFunc::cdToOtherPanel() { openUrl(panel->otherPanel()->virtualPath()); } void ListPanelFunc::syncOtherPanel() { otherFunc()->openUrl(panel->virtualPath()); } bool ListPanelFunc::atHome() { return QUrl::fromLocalFile(QDir::homePath()).matches(panel->virtualPath(), QUrl::StripTrailingSlash); } diff --git a/krusader/Panel/sidebar.cpp b/krusader/Panel/sidebar.cpp index dae02d56..8ca17e57 100644 --- a/krusader/Panel/sidebar.cpp +++ b/krusader/Panel/sidebar.cpp @@ -1,289 +1,290 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "sidebar.h" #include "krfiletreeview.h" #include "krpanel.h" #include "panelfunc.h" #include "viewactions.h" #include "../defaults.h" #include "../icon.h" +#include "../compat.h" #include "../Dialogs/krsqueezedtextlabel.h" #include "../FileSystem/fileitem.h" #include "../FileSystem/filesystem.h" #include "../KViewer/diskusageviewer.h" #include "../KViewer/panelviewer.h" #include "PanelView/krview.h" #include "PanelView/krviewitem.h" // QtCore #include #include // QtWidgets #include #include #include #include Sidebar::Sidebar(QWidget *parent) : QWidget(parent), stack(nullptr), imageFilePreview(nullptr), pjob(nullptr) { auto * layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); // create the label+buttons setup dataLine = new KrSqueezedTextLabel(this); KConfigGroup lg(krConfig, "Look&Feel"); dataLine->setFont(lg.readEntry("Filelist Font", _FilelistFont)); // --- hack: setup colors to be the same as an inactive panel dataLine->setBackgroundRole(QPalette::Window); int sheight = QFontMetrics(dataLine->font()).height() + 4; dataLine->setMaximumHeight(sheight); btns = new QButtonGroup(this); btns->setExclusive(true); - connect(btns, QOverload::of(&QButtonGroup::buttonClicked), this, &Sidebar::tabSelected); + connect(btns, QOverload::of(&QButtonGroup::QBUTTONGROUP_BUTTONCLICKED), this, &Sidebar::tabSelected); treeBtn = new QToolButton(this); treeBtn->setToolTip(i18n("Tree Panel: a tree view of the local file system")); treeBtn->setIcon(Icon("view-list-tree")); treeBtn->setFixedSize(20, 20); treeBtn->setCheckable(true); btns->addButton(treeBtn, Tree); previewBtn = new QToolButton(this); previewBtn->setToolTip(i18n("Preview Panel: display a preview of the current file")); previewBtn->setIcon(Icon("view-preview")); previewBtn->setFixedSize(20, 20); previewBtn->setCheckable(true); btns->addButton(previewBtn, Preview); viewerBtn = new QToolButton(this); viewerBtn->setToolTip(i18n("View Panel: view the current file")); viewerBtn->setIcon(Icon("zoom-original")); viewerBtn->setFixedSize(20, 20); viewerBtn->setCheckable(true); btns->addButton(viewerBtn, View); duBtn = new QToolButton(this); duBtn->setToolTip(i18n("Disk Usage Panel: view the usage of a folder")); duBtn->setIcon(Icon("kr_diskusage")); duBtn->setFixedSize(20, 20); duBtn->setCheckable(true); btns->addButton(duBtn, DskUsage); layout->addWidget(dataLine, 0, 0); layout->addWidget(treeBtn, 0, 1); layout->addWidget(previewBtn, 0, 2); layout->addWidget(viewerBtn, 0, 3); layout->addWidget(duBtn, 0, 4); // create a widget stack on which to put the parts stack = new QStackedWidget(this); // create the tree part ---------- tree = new KrFileTreeView(stack); tree->setProperty("KrusaderWidgetId", QVariant(Tree)); stack->addWidget(tree); // NOTE: the F2 key press event is caught before it gets to the tree tree->setEditTriggers(QAbstractItemView::EditKeyPressed); // connecting signal to signal connect(tree, &KrFileTreeView::urlActivated, this, &Sidebar::urlActivated); // create the quickview part ------ imageFilePreview = new KImageFilePreview(stack); imageFilePreview->setProperty("KrusaderWidgetId", QVariant(Preview)); stack->addWidget(imageFilePreview); // create the panelview fileViewer = new PanelViewer(stack); fileViewer->setProperty("KrusaderWidgetId", QVariant(View)); // kparts demand too much width QSizePolicy sizePolicy = fileViewer->sizePolicy(); sizePolicy.setHorizontalPolicy(QSizePolicy::Ignored); fileViewer->setSizePolicy(sizePolicy); stack->addWidget(fileViewer); connect(fileViewer, &PanelViewer::openUrlRequest, this, &Sidebar::handleOpenUrlRequest); // create the disk usage view diskusage = new DiskUsageViewer(stack); diskusage->setStatusLabel(dataLine, i18n("Disk Usage:")); diskusage->setProperty("KrusaderWidgetId", QVariant(DskUsage)); stack->addWidget(diskusage); connect(diskusage, &DiskUsageViewer::openUrlRequest, this, &Sidebar::handleOpenUrlRequest); // -------- finish the layout (General one) layout->addWidget(stack, 1, 0, 1, 5); hide(); // for not to open the 3rd hand tool at start (selecting the last used tab) setCurrentPage(0); } Sidebar::~Sidebar() = default; void Sidebar::saveSettings(const KConfigGroup& cfg) const { tree->saveSettings(cfg); } void Sidebar::restoreSettings(const KConfigGroup &cfg) { tree->restoreSettings(cfg); } void Sidebar::setCurrentPage(int id) { QAbstractButton * curr = btns->button(id); if (curr) { curr->click(); } } void Sidebar::show() { QWidget::show(); tabSelected(currentPage()); } void Sidebar::hide() { QWidget::hide(); if (currentPage() == View) fileViewer->closeUrl(); if (currentPage() == DskUsage) diskusage->closeUrl(); } void Sidebar::focusInEvent(QFocusEvent*) { switch (currentPage()) { case Preview: if (!isHidden()) imageFilePreview->setFocus(); break; case View: if (!isHidden() && fileViewer->part() && fileViewer->part()->widget()) fileViewer->part()->widget()->setFocus(); break; case DskUsage: if (!isHidden() && diskusage->getWidget() && diskusage->getWidget()->currentWidget()) diskusage->getWidget()->currentWidget()->setFocus(); break; case Tree: if (!isHidden()) tree->setFocus(); break; } } void Sidebar::handleOpenUrlRequest(const QUrl &url) { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(url); if (mime.isValid() && mime.name() == "inode/directory") ACTIVE_PANEL->func->openUrl(url); } void Sidebar::tabSelected(int id) { QUrl url; const FileItem *fileitem = nullptr; if (ACTIVE_PANEL && ACTIVE_PANEL->view) fileitem = ACTIVE_PANEL->func->files()->getFileItem(ACTIVE_PANEL->view->getCurrentItem()); if(fileitem) url = fileitem->getUrl(); // if tab is tree, set something logical in the data line switch (id) { case Tree: stack->setCurrentWidget(tree); dataLine->setText(i18n("Tree:")); if (!isHidden()) tree->setFocus(); if (ACTIVE_PANEL) tree->setCurrentUrl(ACTIVE_PANEL->virtualPath()); break; case Preview: stack->setCurrentWidget(imageFilePreview); dataLine->setText(i18n("Preview:")); update(fileitem); break; case View: stack->setCurrentWidget(fileViewer); dataLine->setText(i18n("View:")); update(fileitem); if (!isHidden() && fileViewer->part() && fileViewer->part()->widget()) fileViewer->part()->widget()->setFocus(); break; case DskUsage: stack->setCurrentWidget(diskusage); dataLine->setText(i18n("Disk Usage:")); update(fileitem); if (!isHidden() && diskusage->getWidget() && diskusage->getWidget()->currentWidget()) diskusage->getWidget()->currentWidget()->setFocus(); break; } if (id != View) fileViewer->closeUrl(); } // decide which part to update, if at all void Sidebar::update(const FileItem *fileitem) { if (isHidden()) return; QUrl url; if(fileitem) url = fileitem->getUrl(); switch (currentPage()) { case Preview: imageFilePreview->showPreview(url); dataLine->setText(i18n("Preview: %1", url.fileName())); break; case View: if(fileitem && !fileitem->isDir() && fileitem->isReadable()) fileViewer->openUrl(fileitem->getUrl()); else fileViewer->closeUrl(); dataLine->setText(i18n("View: %1", url.fileName())); break; case DskUsage: { if(fileitem && !fileitem->isDir()) url = KIO::upUrl(url); dataLine->setText(i18n("Disk Usage: %1", url.fileName())); diskusage->openUrl(url); } break; case Tree: // nothing to do break; } } void Sidebar::onPanelPathChange(const QUrl &url) { switch (currentPage()) { case Tree: if (url.isLocalFile()) { tree->setCurrentUrl(url); // synchronize panel path with tree path } break; } } diff --git a/krusader/Splitter/combiner.cpp b/krusader/Splitter/combiner.cpp index 59ceaf12..e15cf65a 100644 --- a/krusader/Splitter/combiner.cpp +++ b/krusader/Splitter/combiner.cpp @@ -1,348 +1,348 @@ /***************************************************************************** * Copyright (C) 2003 Csaba Karai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "combiner.h" #include "../FileSystem/filesystem.h" // QtCore #include #include #include #include #include #include #include #include //TODO: delete destination file on error //TODO: cache more than one byte array of data Combiner::Combiner(QWidget* parent, QUrl baseURLIn, QUrl destinationURLIn, bool unixNamingIn) : - QProgressDialog(parent, nullptr), baseURL(std::move(baseURLIn)), destinationURL(std::move(destinationURLIn)), + QProgressDialog(parent, Qt::WindowFlags()), baseURL(std::move(baseURLIn)), destinationURL(std::move(destinationURLIn)), hasValidSplitFile(false), fileCounter(0), permissions(-1), receivedSize(0), statJob(nullptr), combineReadJob(nullptr), combineWriteJob(nullptr), unixNaming(unixNamingIn) { crcContext = new CRC32(); splitFile = ""; setMaximum(100); setAutoClose(false); /* don't close or reset the dialog automatically */ setAutoReset(false); setLabelText("Krusader::Combiner"); setWindowModality(Qt::WindowModal); firstFileIs000 = true; //start with this assumption, will set it to false as soon as .000 isn't found } Combiner::~Combiner() { combineAbortJobs(); delete crcContext; } void Combiner::combine() { setWindowTitle(i18n("Krusader::Combining...")); setLabelText(i18n("Combining the file %1...", baseURL.toDisplayString(QUrl::PreferLocalFile))); /* check whether the .crc file exists */ splURL = baseURL.adjusted(QUrl::RemoveFilename); splURL.setPath(splURL.path() + baseURL.fileName() + ".crc"); KFileItem file(splURL); //FIXME: works only for local files - use KIO::stat() instead file.refresh(); if (!file.isReadable()) { int ret = KMessageBox::questionYesNo(nullptr, i18n("The CRC information file (%1) is missing.\n" "Validity checking is impossible without it. Continue combining?", splURL.toDisplayString(QUrl::PreferLocalFile))); if (ret == KMessageBox::No) { emit reject(); return; } statDest(); } else { permissions = file.permissions() | QFile::WriteUser; combineReadJob = KIO::get(splURL, KIO::NoReload, KIO::HideProgressInfo); connect(combineReadJob, &KIO::TransferJob::data, this, &Combiner::combineSplitFileDataReceived); connect(combineReadJob, &KIO::TransferJob::result, this, &Combiner::combineSplitFileFinished); } exec(); } void Combiner::combineSplitFileDataReceived(KIO::Job *, const QByteArray &byteArray) { splitFile += QString(byteArray); } void Combiner::combineSplitFileFinished(KJob *job) { combineReadJob = nullptr; QString error; if (job->error()) error = i18n("Error at reading the CRC file (%1).", splURL.toDisplayString(QUrl::PreferLocalFile)); else { splitFile.remove('\r'); // Windows compatibility QStringList splitFileContent = splitFile.split('\n'); bool hasFileName = false, hasSize = false, hasCrc = false; for (int i = 0; i != splitFileContent.count(); i++) { int ndx = splitFileContent[i].indexOf('='); if (ndx == -1) continue; QString token = splitFileContent[i].left(ndx).trimmed(); QString value = splitFileContent[i].mid(ndx + 1); if (token == "filename") { expectedFileName = value; hasFileName = true; } else if (token == "size") { //FIXME - don't use c functions !!! sscanf(value.trimmed().toLocal8Bit(), "%llu", &expectedSize); hasSize = true; } if (token == "crc32") { expectedCrcSum = value.trimmed().rightJustified(8, '0'); hasCrc = true; } } if (!hasFileName || !hasSize || !hasCrc) error = i18n("Not a valid CRC file."); else hasValidSplitFile = true; } if (!error.isEmpty()) { int ret = KMessageBox::questionYesNo(nullptr, error + i18n("\nValidity checking is impossible without a good CRC file. Continue combining?")); if (ret == KMessageBox::No) { emit reject(); return; } } statDest(); } void Combiner::statDest() { if (writeURL.isEmpty()) { writeURL = FileSystem::ensureTrailingSlash(destinationURL); if (hasValidSplitFile) writeURL.setPath(writeURL.path() + expectedFileName); else if (unixNaming) writeURL.setPath(writeURL.path() + baseURL.fileName() + ".out"); else writeURL.setPath(writeURL.path() + baseURL.fileName()); } #if KIO_VERSION >= QT_VERSION_CHECK(5, 69, 0) statJob = KIO::statDetails(writeURL, KIO::StatJob::DestinationSide, KIO::StatNoDetails, KIO::HideProgressInfo); #else statJob = KIO::stat(writeURL, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); #endif connect(statJob, &KIO::StatJob::result, this, &Combiner::statDestResult); } void Combiner::statDestResult(KJob* job) { statJob = nullptr; if (job->error()) { if (job->error() == KIO::ERR_DOES_NOT_EXIST) { openNextFile(); } else { dynamic_cast(job)->uiDelegate()->showErrorMessage(); emit reject(); } } else { // destination already exists KIO::RenameDialog_Options mode = dynamic_cast(job)->statResult().isDir() ? KIO::RenameDialog_IsDirectory : KIO::RenameDialog_Overwrite; KIO::RenameDialog dlg(this, i18n("File Already Exists"), QUrl(), writeURL, mode); switch (dlg.exec()) { case KIO::Result_Overwrite: openNextFile(); break; case KIO::Result_Rename: { writeURL = dlg.newDestUrl(); statDest(); break; } default: emit reject(); } } } void Combiner::openNextFile() { if (unixNaming) { if (readURL.isEmpty()) readURL = baseURL; else { QString name = readURL.fileName(); int pos = name.length() - 1; QChar ch; do { ch = name.at(pos).toLatin1() + 1; if (ch == QChar('Z' + 1)) ch = 'A'; if (ch == QChar('z' + 1)) ch = 'a'; name[ pos ] = ch; pos--; } while (pos >= 0 && ch.toUpper() == QChar('A')); readURL = readURL.adjusted(QUrl::RemoveFilename); readURL.setPath(readURL.path() + name); } } else { QString index("%1"); /* determining the filename */ index = index.arg(fileCounter++).rightJustified(3, '0'); readURL = baseURL.adjusted(QUrl::RemoveFilename); readURL.setPath(readURL.path() + baseURL.fileName() + '.' + index); } /* creating a read job */ combineReadJob = KIO::get(readURL, KIO::NoReload, KIO::HideProgressInfo); connect(combineReadJob, &KIO::TransferJob::data, this, &Combiner::combineDataReceived); connect(combineReadJob, &KIO::TransferJob::result, this, &Combiner::combineReceiveFinished); if (hasValidSplitFile) connect(combineReadJob, SIGNAL(percent(KJob*,ulong)), this, SLOT(combineWritePercent(KJob*,ulong))); } void Combiner::combineDataReceived(KIO::Job *, const QByteArray &byteArray) { if (byteArray.size() == 0) return; crcContext->update((unsigned char *)byteArray.data(), byteArray.size()); transferArray = QByteArray(byteArray.data(), byteArray.length()); receivedSize += byteArray.size(); if (combineWriteJob == nullptr) { combineWriteJob = KIO::put(writeURL, permissions, KIO::HideProgressInfo | KIO::Overwrite); connect(combineWriteJob, &KIO::TransferJob::dataReq, this, &Combiner::combineDataSend); connect(combineWriteJob, &KIO::TransferJob::result, this, &Combiner::combineSendFinished); } // continue writing and suspend read job until received data is handed over to the write job combineReadJob->suspend(); combineWriteJob->resume(); } void Combiner::combineReceiveFinished(KJob *job) { combineReadJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (job->error()) { if (job->error() == KIO::ERR_DOES_NOT_EXIST) { if (fileCounter == 1) { // .000 file doesn't exist but .001 is still a valid first file firstFileIs000 = false; openNextFile(); } else if (!firstFileIs000 && fileCounter == 2) { // neither .000 nor .001 exist combineAbortJobs(); KMessageBox::error(nullptr, i18n("Cannot open the first split file of %1.", baseURL.toDisplayString(QUrl::PreferLocalFile))); emit reject(); } else { // we've received the last file // write out the remaining part of the file combineWriteJob->resume(); if (hasValidSplitFile) { QString crcResult = QString("%1").arg(crcContext->result(), 0, 16).toUpper().trimmed() .rightJustified(8, '0'); if (receivedSize != expectedSize) error = i18n("Incorrect filesize, the file might have been corrupted."); else if (crcResult != expectedCrcSum.toUpper().trimmed()) error = i18n("Incorrect CRC checksum, the file might have been corrupted."); } } } else { combineAbortJobs(); dynamic_cast(job)->uiDelegate()->showErrorMessage(); emit reject(); } } else openNextFile(); } void Combiner::combineDataSend(KIO::Job *, QByteArray &byteArray) { byteArray = transferArray; transferArray = QByteArray(); if (combineReadJob) { // continue reading and suspend write job until data is available combineReadJob->resume(); combineWriteJob->suspend(); } } void Combiner::combineSendFinished(KJob *job) { combineWriteJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (job->error()) { /* any error occurred? */ combineAbortJobs(); dynamic_cast(job)->uiDelegate()->showErrorMessage(); emit reject(); } else if (!error.isEmpty()) { /* was any error message at reading ? */ combineAbortJobs(); /* we cannot write out it in combineReceiveFinished */ KMessageBox::error(nullptr, error); /* because emit accept closes it in this function */ emit reject(); } else emit accept(); } void Combiner::combineAbortJobs() { if (statJob) statJob->kill(KJob::Quietly); if (combineReadJob) combineReadJob->kill(KJob::Quietly); if (combineWriteJob) combineWriteJob->kill(KJob::Quietly); statJob = combineReadJob = combineWriteJob = nullptr; } void Combiner::combineWritePercent(KJob *, unsigned long) { auto percent = (int)((((double)receivedSize / expectedSize) * 100.) + 0.5); setValue(percent); } diff --git a/krusader/Splitter/splitter.cpp b/krusader/Splitter/splitter.cpp index 222bd9b3..8331d78b 100644 --- a/krusader/Splitter/splitter.cpp +++ b/krusader/Splitter/splitter.cpp @@ -1,301 +1,301 @@ /***************************************************************************** * Copyright (C) 2003 Csaba Karai * * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "splitter.h" #include "../FileSystem/filesystem.h" // QtCore #include // QtWidgets #include #include #include #include #include #include #include #include Splitter::Splitter(QWidget* parent, QUrl fileNameIn, QUrl destinationDirIn, bool overWriteIn) : - QProgressDialog(parent, nullptr), + QProgressDialog(parent, Qt::WindowFlags()), fileName(std::move(fileNameIn)), destinationDir(std::move(destinationDirIn)), splitSize(0), permissions(0), overwrite(overWriteIn), fileNumber(0), outputFileRemaining(0), receivedSize(0), crcContext(new CRC32()), statJob(nullptr), splitReadJob(nullptr), splitWriteJob(nullptr) { setMaximum(100); setAutoClose(false); /* don't close or reset the dialog automatically */ setAutoReset(false); setLabelText("Krusader::Splitter"); setWindowModality(Qt::WindowModal); } Splitter::~Splitter() { splitAbortJobs(); delete crcContext; } void Splitter::split(KIO::filesize_t splitSizeIn) { Q_ASSERT(!splitReadJob); Q_ASSERT(!splitWriteJob); Q_ASSERT(!fileNumber); Q_ASSERT(!receivedSize); Q_ASSERT(!outputFileRemaining); splitReadJob = splitWriteJob = nullptr; fileNumber = receivedSize = outputFileRemaining = 0; splitSize = splitSizeIn; KFileItem file(fileName); file.refresh(); //FIXME: works only for local files - use KIO::stat() instead permissions = file.permissions() | QFile::WriteUser; setWindowTitle(i18n("Krusader::Splitting...")); setLabelText(i18n("Splitting the file %1...", fileName.toDisplayString(QUrl::PreferLocalFile))); if (file.isDir()) { KMessageBox::error(nullptr, i18n("Cannot split a folder.")); return; } splitReadJob = KIO::get(fileName, KIO::NoReload, KIO::HideProgressInfo); connect(splitReadJob, &KIO::TransferJob::data, this, &Splitter::splitDataReceived); connect(splitReadJob, &KIO::TransferJob::result, this, &Splitter::splitReceiveFinished); connect(splitReadJob, SIGNAL(percent(KJob*,ulong)), this, SLOT(splitReceivePercent(KJob*,ulong))); exec(); } void Splitter::splitDataReceived(KIO::Job *, const QByteArray &byteArray) { Q_ASSERT(!transferArray.length()); // transfer buffer must be empty if (byteArray.size() == 0) return; crcContext->update((unsigned char *)byteArray.data(), byteArray.size()); receivedSize += byteArray.size(); if (!splitWriteJob) nextOutputFile(); transferArray = QByteArray(byteArray.data(), byteArray.length()); // suspend read job until transfer buffer is handed to the write job splitReadJob->suspend(); if (splitWriteJob) splitWriteJob->resume(); } void Splitter::splitReceiveFinished(KJob *job) { splitReadJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (splitWriteJob) splitWriteJob->resume(); // finish writing the output if (job->error()) { /* any error occurred? */ splitAbortJobs(); KMessageBox::error(nullptr, i18n("Error reading file %1: %2", fileName.toDisplayString(QUrl::PreferLocalFile), job->errorString())); emit reject(); return; } QString crcResult = QString("%1").arg(crcContext->result(), 0, 16).toUpper().trimmed() .rightJustified(8, '0'); splitInfoFileContent = QString("filename=%1\n").arg(fileName.fileName()) + QString("size=%1\n") .arg(KIO::number(receivedSize)) + QString("crc32=%1\n") .arg(crcResult); } void Splitter::splitReceivePercent(KJob *, unsigned long percent) { setValue(percent); } void Splitter::nextOutputFile() { Q_ASSERT(!outputFileRemaining); fileNumber++; outputFileRemaining = splitSize; QString index("%1"); /* making the split filename */ index = index.arg(fileNumber).rightJustified(3, '0'); QString outFileName = fileName.fileName() + '.' + index; writeURL = destinationDir; writeURL = writeURL.adjusted(QUrl::StripTrailingSlash); writeURL.setPath(writeURL.path() + '/' + (outFileName)); if (overwrite) openOutputFile(); else { #if KIO_VERSION >= QT_VERSION_CHECK(5, 69, 0) statJob = KIO::statDetails(writeURL, KIO::StatJob::DestinationSide, KIO::StatNoDetails, KIO::HideProgressInfo); #else statJob = KIO::stat(writeURL, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); #endif connect(statJob, &KIO::Job::result, this, &Splitter::statOutputFileResult); } } void Splitter::statOutputFileResult(KJob* job) { statJob = nullptr; if (job->error()) { if (job->error() == KIO::ERR_DOES_NOT_EXIST) openOutputFile(); else { dynamic_cast(job)->uiDelegate()->showErrorMessage(); emit reject(); } } else { // destination already exists KIO::RenameDialog dlg(this, i18n("File Already Exists"), QUrl(), writeURL, static_cast(KIO::M_MULTI | KIO::M_OVERWRITE | KIO::M_NORENAME)); switch (dlg.exec()) { case KIO::Result_Overwrite: openOutputFile(); break; case KIO::Result_OverwriteAll: overwrite = true; openOutputFile(); break; default: emit reject(); } } } void Splitter::openOutputFile() { // create write job splitWriteJob = KIO::put(writeURL, permissions, KIO::HideProgressInfo | KIO::Overwrite); connect(splitWriteJob, &KIO::TransferJob::dataReq, this, &Splitter::splitDataSend); connect(splitWriteJob, &KIO::TransferJob::result, this, &Splitter::splitSendFinished); } void Splitter::splitDataSend(KIO::Job *, QByteArray &byteArray) { KIO::filesize_t bufferLen = transferArray.size(); if (!outputFileRemaining) { // current output file needs to be closed ? byteArray = QByteArray(); // giving empty buffer which indicates closing } else if (bufferLen > outputFileRemaining) { // maximum length reached ? byteArray = QByteArray(transferArray.data(), outputFileRemaining); transferArray = QByteArray(transferArray.data() + outputFileRemaining, bufferLen - outputFileRemaining); outputFileRemaining = 0; } else { outputFileRemaining -= bufferLen; // write the whole buffer to the output file byteArray = transferArray; transferArray = QByteArray(); if (splitReadJob) { // suspend write job until transfer buffer is filled or the read job is finished splitWriteJob->suspend(); splitReadJob->resume(); } // else: write job continues until transfer buffer is empty } } void Splitter::splitSendFinished(KJob *job) { splitWriteJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (job->error()) { /* any error occurred? */ splitAbortJobs(); KMessageBox::error(nullptr, i18n("Error writing file %1: %2", writeURL.toDisplayString(QUrl::PreferLocalFile), job->errorString())); emit reject(); return; } if (transferArray.size()) /* any data remained in the transfer buffer? */ nextOutputFile(); else if (splitReadJob) splitReadJob->resume(); else { // read job is finished and transfer buffer is empty -> splitting is finished /* writing the split information file out */ writeURL = destinationDir; writeURL = writeURL.adjusted(QUrl::StripTrailingSlash); writeURL.setPath(writeURL.path() + '/' + (fileName.fileName() + ".crc")); splitWriteJob = KIO::put(writeURL, permissions, KIO::HideProgressInfo | KIO::Overwrite); connect(splitWriteJob, &KIO::TransferJob::dataReq, this, &Splitter::splitFileSend); connect(splitWriteJob, &KIO::TransferJob::result, this, &Splitter::splitFileFinished); } } void Splitter::splitAbortJobs() { if (statJob) statJob->kill(KJob::Quietly); if (splitReadJob) splitReadJob->kill(KJob::Quietly); if (splitWriteJob) splitWriteJob->kill(KJob::Quietly); splitReadJob = splitWriteJob = nullptr; } void Splitter::splitFileSend(KIO::Job *, QByteArray &byteArray) { byteArray = splitInfoFileContent.toLocal8Bit(); splitInfoFileContent = ""; } void Splitter::splitFileFinished(KJob *job) { splitWriteJob = nullptr; /* KIO automatically deletes the object after Finished signal */ if (job->error()) { /* any error occurred? */ KMessageBox::error(nullptr, i18n("Error writing file %1: %2", writeURL.toDisplayString(QUrl::PreferLocalFile), job->errorString())); emit reject(); return; } emit accept(); } diff --git a/krusader/compat.h b/krusader/compat.h index d65dab0f..48ff65c2 100644 --- a/krusader/compat.h +++ b/krusader/compat.h @@ -1,51 +1,135 @@ /***************************************************************************** * Copyright (C) 2019-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #ifndef _COMPAT_H_ #define _COMPAT_H_ #include #if KIO_VERSION >= QT_VERSION_CHECK(5, 48, 0) #define UDS_ENTRY_INSERT(A, B) UDSEntry::fastInsert((A), (B)); #else #define UDS_ENTRY_INSERT(A, B) UDSEntry::insert((A), (B)); #endif +/** + * QButtonGroup::buttonClicked(int id) was made obsoleted in QT 5.15 in + * favor of QButtonGroup::idClicked(int id) + * + * https://doc.qt.io/qt-5.15/qbuttongroup-obsolete.html#buttonClicked-1 + * + * This can be removed when the qt minimum version required will be >= 5.15 + */ +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + #define QBUTTONGROUP_BUTTONCLICKED idClicked +#else + #define QBUTTONGROUP_BUTTONCLICKED buttonClicked +#endif + +/** + * QResource::isCompressed() was made obsoleted in QT 5.15 in + * favor of QResource::Compression QResource::compressionAlgorithm() + * + * https://doc.qt.io/qt-5.15/qresource-obsolete.html#isCompressed + * + * This can be removed when the qt minimum version required will be >= 5.13 + */ +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + #define QRESOURCE_ISCOMPRESSED(A) ((A).compressionAlgorithm() != QResource::NoCompression) +#else + #define QRESOURCE_ISCOMPRESSED(A) (A).isCompressed() +#endif + +/** + * QString::split(QChar sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs = Qt::CaseSensitive) + * was made obsoleted in QT 5.15 in favor of the namespaced Qt::endl + * + * https://doc.qt.io/qt-5.15/qstring-obsolete.html#split-2 + * + * This can be removed when the qt minimum version required will be >= 5.15 + */ +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + #define SKIP_EMPTY_PARTS Qt::SkipEmptyParts +#else + #define SKIP_EMPTY_PARTS QString::SkipEmptyParts +#endif + +/** + * QTextSteam::endl() was made obsoleted in QT 5.15 in + * favor of the namespaced Qt::endl + * + * https://doc.qt.io/qt-5.15/qtextstream-obsolete.html#endl + * + * This can be removed when the qt minimum version required will be >= 5.15 + */ +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + #define QT_ENDL Qt::endl +#else + #define QT_ENDL endl +#endif + +/** + * QComboBox::activated(const QString &text) was made obsoleted in QT 5.15 in + * favor of QComboBox::textActivated(const QString &text) + * + * https://doc.qt.io/qt-5.15/qcombobox-obsolete.html#activated-1 + * + * This can be removed when the qt minimum version required will be >= 5.14 + */ +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + #define QCOMBOBOX_ACTIVATED textActivated +#else + #define QCOMBOBOX_ACTIVATED activated +#endif + +/** + * QComboBox::highlighted(const QString &text) was made obsoleted in QT 5.15 in + * favor of QComboBox::textHighlighted(const QString &text) + * + * https://doc.qt.io/qt-5.15/qcombobox-obsolete.html#highlighted-1 + * + * This can be removed when the qt minimum version required will be >= 5.14 + */ +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + #define QCOMBOBOX_HIGHLIGHTED textHighlighted +#else + #define QCOMBOBOX_HIGHLIGHTED highlighted +#endif + /** * QFontMetrics::width(const QString&, int) was made obsoleted in QT 5.11 in * favor of QFontMetrics::horizontalAdvance(const QString &, int) * * https://doc.qt.io/archives/qt-5.11/qfontmetrics-obsolete.html#width * * This can be removed when the qt minimum version required will be >= 5.11 */ #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) #define QFONTMETRICS_WIDTH(A) horizontalAdvance(A) #else #define QFONTMETRICS_WIDTH(A) width(A) #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #define SET_TAB_STOP_DISTANCE(X) setTabStopDistance(X) #else #define SET_TAB_STOP_DISTANCE(X) setTabStopWidth(X) #endif #endif diff --git a/krusader/krdebuglogger.cpp b/krusader/krdebuglogger.cpp index 26a46c60..8632ce89 100644 --- a/krusader/krdebuglogger.cpp +++ b/krusader/krdebuglogger.cpp @@ -1,58 +1,59 @@ /***************************************************************************** * Copyright (C) 2016 Rafi Yanai * * Copyright (C) 2016 Shie Erlich * * Copyright (C) 2016-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krdebuglogger.h" +#include "compat.h" int KrDebugLogger::indentation = 1; const int KrDebugLogger::indentationIncrease = 3; const QString KrDebugLogger::logFile = QDir::tempPath() + "/krdebug"; KrDebugLogger::KrDebugLogger(const QString &argFunction, int line) : function(argFunction) { QFile file; QTextStream stream; prepareWriting(file, stream); stream << QString("┏"); // Indicates that a function has been started - stream << function << "(" << line << ")" << endl; + stream << function << "(" << line << ")" << QT_ENDL; indentation += indentationIncrease; } KrDebugLogger::~KrDebugLogger() { indentation -= indentationIncrease; QFile file; QTextStream stream; prepareWriting(file, stream); stream << QString("┗"); // Indicates that a function is going to finish - stream << function << endl; + stream << function << QT_ENDL; } //! Prepares some elements before a writing into the krarc debug log file void KrDebugLogger::prepareWriting(QFile &file, QTextStream &stream) { file.setFileName(logFile); file.open(QIODevice::WriteOnly | QIODevice::Append); stream.setDevice(&file); stream << "Pid:" << (int)getpid(); // Applies the indentation level to make logs clearer for (int x = 0; x < indentation; ++x) stream << " "; } diff --git a/krusader/krdebuglogger.h b/krusader/krdebuglogger.h index 344b8aec..1d11ee76 100644 --- a/krusader/krdebuglogger.h +++ b/krusader/krdebuglogger.h @@ -1,69 +1,70 @@ /***************************************************************************** * Copyright (C) 2016 Rafi Yanai * * Copyright (C) 2016 Shie Erlich * * Copyright (C) 2016-2020 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Krusader is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #ifndef KRDEBUGLOGGER_H #define KRDEBUGLOGGER_H // QtCore #include #include #include #include #include +#include "compat.h" //! A class to manage some aspects of the writing of messages into the Krusader debug log file class KrDebugLogger { private: QString function; //! The name of a function which is going to be written about static int indentation; //! The indentation that is presently used, it represents how many spaces are going to be used const static int indentationIncrease; //! The quantity of spaces that are going be added to the indentation when increasing it static const QString logFile; //! The name of the log file public: //! This constructor is used inside the KRFUNC macro. For more details: the description of the KRFUNC macro can be seen KrDebugLogger(const QString &argFunction, int line); //! For more information: the description of the KRFUNC macro can be seen ~KrDebugLogger(); static void prepareWriting(QFile &, QTextStream &); }; #ifdef QT_DEBUG //! Writes a function name, etc. in the Krusader debug log when entering the function and automatically before exiting from it #define KRFUNC \ KrDebugLogger functionLogger(__FUNCTION__, __LINE__); #define KRDEBUG(X...) do{ \ QFile file; \ QTextStream stream; \ KrDebugLogger::prepareWriting(file, stream); \ stream << __FUNCTION__ << "(" <<__LINE__<< "): "; \ - stream << X << endl; \ + stream << X << QT_ENDL; \ } while(0); #else #define KRFUNC #define KRDEBUG(X...) qDebug() << X #endif #endif // KRDEBUGLOGGER_H