diff --git a/iso/iso.cpp b/iso/iso.cpp index 4061fb8c..39bec5e8 100644 --- a/iso/iso.cpp +++ b/iso/iso.cpp @@ -1,526 +1,526 @@ /***************************************************************************** * Copyright (C) 2000 David Faure * * Copyright (C) 2002 Szombathelyi György * * Copyright (C) 2003 Leo Savernik * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is heavily based on ktar from kdelibs * * * * 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 "iso.h" #include // QtCore #include #include #include #include #include #include #include #include "libisofs/iso_fs.h" #include "kiso.h" #include "kisofile.h" #include "kisodirectory.h" using namespace KIO; extern "C" { int Q_DECL_EXPORT kdemain(int argc, char **argv) { //qDebug() << "Starting " << getpid() << endl; if (argc != 4) { fprintf(stderr, "Usage: kio_iso protocol domain-socket1 domain-socket2\n"); exit(-1); } kio_isoProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); //qDebug() << "Done" << endl; return 0; } } // extern "C" typedef struct { char magic[8]; char uncompressed_len[4]; unsigned char header_size; unsigned char block_size; char reserved[2]; /* Reserved for future use, MBZ */ } compressed_file_header; static const unsigned char zisofs_magic[8] = { 0x37, 0xE4, 0x53, 0x96, 0xC9, 0xDB, 0xD6, 0x07 }; kio_isoProtocol::kio_isoProtocol(const QByteArray &pool, const QByteArray &app) : SlaveBase("iso", pool, app) { //qDebug() << "kio_isoProtocol::kio_isoProtocol" << endl; m_isoFile = nullptr; } kio_isoProtocol::~kio_isoProtocol() { delete m_isoFile; } bool kio_isoProtocol::checkNewFile(QString fullPath, QString & path, int startsec) { //qDebug() << "kio_isoProtocol::checkNewFile " << fullPath << " startsec: " << //startsec << endl; // Are we already looking at that file ? if (m_isoFile && startsec == m_isoFile->startSec() && m_isoFile->fileName() == fullPath.left(m_isoFile->fileName().length())) { // Has it changed ? QT_STATBUF statbuf; if (QT_STAT(QFile::encodeName(m_isoFile->fileName()), &statbuf) == 0) { if (m_mtime == statbuf.st_mtime) { path = fullPath.mid(m_isoFile->fileName().length()); //qDebug() << "kio_isoProtocol::checkNewFile returning " << path << endl; if(path.endsWith(DIR_SEPARATOR_CHAR)) { path.chop(1); } if(path.isEmpty()) { path = DIR_SEPARATOR_CHAR; } return true; } } } //qDebug() << "Need to open a new file" << endl; // Close previous file if (m_isoFile) { m_isoFile->close(); delete m_isoFile; m_isoFile = nullptr; } // Find where the iso file is in the full path int pos = 0; QString isoFile; path.clear(); int len = fullPath.length(); if (len != 0 && fullPath[ len - 1 ] != DIR_SEPARATOR_CHAR) fullPath += DIR_SEPARATOR_CHAR; //qDebug() << "the full path is " << fullPath << endl; while ((pos = fullPath.indexOf(DIR_SEPARATOR_CHAR, pos + 1)) != -1) { QString tryPath = fullPath.left(pos); //qDebug() << fullPath << " trying " << tryPath << endl; QT_STATBUF statbuf; if (QT_LSTAT(QFile::encodeName(tryPath), &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) { bool isFile = true; if (S_ISLNK(statbuf.st_mode)) { char symDest[256]; memset(symDest, 0, 256); int endOfName = readlink(QFile::encodeName(tryPath), symDest, 256); if (endOfName != -1) { if (QDir(QString::fromLocal8Bit(symDest)).exists()) isFile = false; } } if (isFile) { isoFile = tryPath; m_mtime = statbuf.st_mtime; m_mode = statbuf.st_mode; path = fullPath.mid(pos + 1); //qDebug() << "fullPath=" << fullPath << " path=" << path << endl; if(path.endsWith(DIR_SEPARATOR_CHAR)) { path.chop(1); } if(path.isEmpty()) { path = DIR_SEPARATOR_CHAR; } //qDebug() << "Found. isoFile=" << isoFile << " path=" << path << endl; break; } } } if (isoFile.isEmpty()) { //qDebug() << "kio_isoProtocol::checkNewFile: not found" << endl; return false; } // Open new file //qDebug() << "Opening KIso on " << isoFile << endl; m_isoFile = new KIso(isoFile); m_isoFile->setStartSec(startsec); if (!m_isoFile->open(QIODevice::ReadOnly)) { //qDebug() << "Opening " << isoFile << " failed." << endl; delete m_isoFile; m_isoFile = nullptr; return false; } return true; } void kio_isoProtocol::createUDSEntry(const KArchiveEntry * isoEntry, UDSEntry & entry) { entry.clear(); entry.insert(UDSEntry::UDS_NAME, isoEntry->name()); entry.insert(UDSEntry::UDS_FILE_TYPE, isoEntry->permissions() & S_IFMT); // keep file type only entry.insert(UDSEntry::UDS_ACCESS, isoEntry->permissions() & 07777); // keep permissions only if (isoEntry->isFile()) { long long si = ((KIsoFile *)isoEntry)->realsize(); if (!si) si = ((KIsoFile *)isoEntry)->size(); entry.insert(UDSEntry::UDS_SIZE, si); } else { entry.insert(UDSEntry::UDS_SIZE, 0L); } entry.insert(UDSEntry::UDS_USER, isoEntry->user()); entry.insert(UDSEntry::UDS_GROUP, isoEntry->group()); entry.insert((uint)UDSEntry::UDS_MODIFICATION_TIME, isoEntry->date().toTime_t()); entry.insert(UDSEntry::UDS_ACCESS_TIME, isoEntry->isFile() ? ((KIsoFile *)isoEntry)->adate() : ((KIsoDirectory *)isoEntry)->adate()); entry.insert(UDSEntry::UDS_CREATION_TIME, isoEntry->isFile() ? ((KIsoFile *)isoEntry)->cdate() : ((KIsoDirectory *)isoEntry)->cdate()); entry.insert(UDSEntry::UDS_LINK_DEST, isoEntry->symLinkTarget()); } void kio_isoProtocol::listDir(const QUrl &url) { //qDebug() << "kio_isoProtocol::listDir " << url.url() << endl; QString path; if (!checkNewFile(getPath(url), path, url.hasFragment() ? url.fragment(QUrl::FullyDecoded).toInt() : -1)) { QByteArray _path(QFile::encodeName(getPath(url))); //qDebug() << "Checking (stat) on " << _path << endl; QT_STATBUF buff; if (QT_STAT(_path.data(), &buff) == -1 || !S_ISDIR(buff.st_mode)) { error(KIO::ERR_DOES_NOT_EXIST, getPath(url)); return; } // It's a real dir -> redirect QUrl redir; redir.setPath(getPath(url)); if (url.hasFragment()) redir.setFragment(url.fragment(QUrl::FullyDecoded)); //qDebug() << "Ok, redirection to " << redir.url() << endl; redir.setScheme("file"); redirection(redir); finished(); // And let go of the iso file - for people who want to unmount a cdrom after that delete m_isoFile; m_isoFile = nullptr; return; } if (path.isEmpty()) { QUrl redir(QStringLiteral("iso:/")); //qDebug() << "url.path()==" << getPath(url) << endl; if (url.hasFragment()) redir.setFragment(url.fragment(QUrl::FullyDecoded)); redir.setPath(getPath(url) + QString::fromLatin1(DIR_SEPARATOR)); //qDebug() << "kio_isoProtocol::listDir: redirection " << redir.url() << endl; redir.setScheme("file"); redirection(redir); finished(); return; } //qDebug() << "checkNewFile done" << endl; const KArchiveDirectory* root = m_isoFile->directory(); const KArchiveDirectory* dir; if (!path.isEmpty() && path != DIR_SEPARATOR) { //qDebug() << QString("Looking for entry %1").arg(path) << endl; const KArchiveEntry* e = root->entry(path); if (!e) { error(KIO::ERR_DOES_NOT_EXIST, path); return; } if (! e->isDirectory()) { error(KIO::ERR_IS_FILE, path); return; } dir = (KArchiveDirectory*)e; } else { dir = root; } QStringList l = dir->entries(); totalSize(l.count()); UDSEntry entry; QStringList::Iterator it = l.begin(); for (; it != l.end(); ++it) { //qDebug() << (*it) << endl; const KArchiveEntry* isoEntry = dir->entry((*it)); createUDSEntry(isoEntry, entry); listEntry(entry); } finished(); //qDebug() << "kio_isoProtocol::listDir done" << endl; } void kio_isoProtocol::stat(const QUrl &url) { QString path; UDSEntry entry; //qDebug() << "kio_isoProtocol::stat " << url.url() << endl; if (!checkNewFile(getPath(url), path, url.hasFragment() ? url.fragment(QUrl::FullyDecoded).toInt() : -1)) { // We may be looking at a real directory - this happens // when pressing up after being in the root of an archive QByteArray _path(QFile::encodeName(getPath(url))); //qDebug() << "kio_isoProtocol::stat (stat) on " << _path << endl; QT_STATBUF buff; if (QT_STAT(_path.data(), &buff) == -1 || !S_ISDIR(buff.st_mode)) { //qDebug() << "isdir=" << S_ISDIR(buff.st_mode) << " errno=" << strerror(errno) << endl; error(KIO::ERR_DOES_NOT_EXIST, getPath(url)); return; } // Real directory. Return just enough information for KRun to work entry.insert(UDSEntry::UDS_NAME, url.fileName()); //qDebug() << "kio_isoProtocol::stat returning name=" << url.fileName() << endl; entry.insert(UDSEntry::UDS_FILE_TYPE, buff.st_mode & S_IFMT); statEntry(entry); finished(); // And let go of the iso file - for people who want to unmount a cdrom after that delete m_isoFile; m_isoFile = nullptr; return; } const KArchiveDirectory* root = m_isoFile->directory(); const KArchiveEntry* isoEntry; if (path.isEmpty()) { path = QString::fromLatin1(DIR_SEPARATOR); isoEntry = root; } else { isoEntry = root->entry(path); } if (!isoEntry) { error(KIO::ERR_DOES_NOT_EXIST, path); return; } createUDSEntry(isoEntry, entry); statEntry(entry); finished(); } void kio_isoProtocol::getFile(const KIsoFile *isoFileEntry, const QString &path) { unsigned long long size, pos = 0; bool mime = false, zlib = false; QByteArray fileData, pointer_block, inbuf, outbuf; char *pptr = nullptr; compressed_file_header *hdr; int block_shift; unsigned long nblocks; unsigned long fullsize = 0, block_size = 0, block_size2 = 0; size_t ptrblock_bytes; unsigned long cstart, cend, csize; uLong bytes; size = isoFileEntry->realsize(); if (size >= sizeof(compressed_file_header)) zlib = true; if (!size) size = isoFileEntry->size(); totalSize(size); if (!size) mimeType("application/x-zerosize"); if (size && !m_isoFile->device()->isOpen()) { m_isoFile->device()->open(QIODevice::ReadOnly); // seek(0) ensures integrity with the QIODevice's built-in buffer // see bug #372023 for details m_isoFile->device()->seek(0); } if (zlib) { fileData = isoFileEntry->dataAt(0, sizeof(compressed_file_header)); if (fileData.size() == sizeof(compressed_file_header) && !memcmp(fileData.data(), zisofs_magic, sizeof(zisofs_magic))) { hdr = (compressed_file_header*) fileData.data(); block_shift = hdr->block_size; block_size = 1UL << block_shift; block_size2 = block_size << 1; fullsize = isonum_731(hdr->uncompressed_len); nblocks = (fullsize + block_size - 1) >> block_shift; ptrblock_bytes = (nblocks + 1) * 4; pointer_block = isoFileEntry->dataAt(hdr->header_size << 2, ptrblock_bytes); if ((unsigned long)pointer_block.size() == ptrblock_bytes) { inbuf.resize(block_size2); if (inbuf.size()) { outbuf.resize(block_size); if (outbuf.size()) pptr = pointer_block.data(); else { error(KIO::ERR_COULD_NOT_READ, path); return; } } else { error(KIO::ERR_COULD_NOT_READ, path); return; } } else { error(KIO::ERR_COULD_NOT_READ, path); return; } } else { zlib = false; } } while (pos < size) { if (zlib) { cstart = isonum_731(pptr); pptr += 4; cend = isonum_731(pptr); csize = cend - cstart; if (csize == 0) { outbuf.fill(0, -1); } else { if (csize > block_size2) { //err = EX_DATAERR; break; } inbuf = isoFileEntry->dataAt(cstart, csize); if ((unsigned long)inbuf.size() != csize) { break; } bytes = block_size; // Max output buffer size if ((uncompress((Bytef*) outbuf.data(), &bytes, (Bytef*) inbuf.data(), csize)) != Z_OK) { break; } } if (((fullsize > block_size) && (bytes != block_size)) || ((fullsize <= block_size) && (bytes < fullsize))) { break; } if (bytes > fullsize) bytes = fullsize; fileData = outbuf; fileData.resize(bytes); fullsize -= bytes; } else { fileData = isoFileEntry->dataAt(pos, 65536); if (fileData.size() == 0) break; } if (!mime) { QMimeDatabase db; QMimeType mt = db.mimeTypeForFileNameAndData(path, fileData); if (mt.isValid()) { //qDebug() << "Emitting mimetype " << mt.name() << endl; mimeType(mt.name()); mime = true; } } data(fileData); pos += fileData.size(); processedSize(pos); } if (pos != size) { error(KIO::ERR_COULD_NOT_READ, path); return; } fileData.resize(0); data(fileData); processedSize(pos); finished(); } void kio_isoProtocol::get(const QUrl &url) { //qDebug() << "kio_isoProtocol::get" << url.url() << endl; QString path; if (!checkNewFile(getPath(url), path, url.hasFragment() ? url.fragment(QUrl::FullyDecoded).toInt() : -1)) { error(KIO::ERR_DOES_NOT_EXIST, getPath(url)); return; } const KArchiveDirectory* root = m_isoFile->directory(); const KArchiveEntry* isoEntry = root->entry(path); if (!isoEntry) { error(KIO::ERR_DOES_NOT_EXIST, path); return; } if (isoEntry->isDirectory()) { error(KIO::ERR_IS_DIRECTORY, path); return; } - const auto* isoFileEntry = static_cast(isoEntry); + const auto* isoFileEntry = dynamic_cast(isoEntry); if (!isoEntry->symLinkTarget().isEmpty()) { //qDebug() << "Redirection to " << isoEntry->symLinkTarget() << endl; QUrl realURL = QUrl(url).resolved(QUrl(isoEntry->symLinkTarget())); //qDebug() << "realURL= " << realURL.url() << endl; realURL.setScheme("file"); redirection(realURL); finished(); return; } getFile(isoFileEntry, path); if (m_isoFile->device()->isOpen()) m_isoFile->device()->close(); } QString kio_isoProtocol::getPath(const QUrl &url) { QString path = url.path(); REPLACE_DIR_SEP2(path); #ifdef Q_WS_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; } diff --git a/iso/kiso.cpp b/iso/kiso.cpp index 323387c0..545c7e44 100644 --- a/iso/kiso.cpp +++ b/iso/kiso.cpp @@ -1,521 +1,521 @@ /***************************************************************************** * Copyright (C) 2000 David Faure * * Copyright (C) 2002 Szombathelyi György * * Copyright (C) 2003 Leo Savernik * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is heavily based on ktar from kdelibs * * * * 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 "kiso.h" // QtCore #include #include #include #include #include #include #include #include #include #include #include "libisofs/isofs.h" #include "qfilehack.h" #ifdef Q_OS_LINUX #undef __STRICT_ANSI__ #include #define __STRICT_ANSI__ #include #include #endif //////////////////////////////////////////////////////////////////////// /////////////////////////// KIso /////////////////////////////////// //////////////////////////////////////////////////////////////////////// /** * puts the track layout of the device 'fname' into 'tracks' * tracks structure: start sector, track number, ... * tracks should be 100*2 entry long (this is the maximum in the CD-ROM standard) * currently it's linux only, porters are welcome */ static int getTracks(const char *fname, int *tracks) { KRFUNC; int ret = 0; memset(tracks, 0, 200*sizeof(int)); #ifdef Q_OS_LINUX int fd, i; struct cdrom_tochdr tochead; struct cdrom_tocentry tocentry; //qDebug() << "getTracks open:" << fname << endl; fd = QT_OPEN(fname, O_RDONLY | O_NONBLOCK); if (fd > 0) { if (ioctl(fd, CDROMREADTOCHDR, &tochead) != -1) { // qDebug() << "getTracks first track:" << tochead.cdth_trk0 // << " last track " << tochead.cdth_trk1 << endl; for (i = tochead.cdth_trk0;i <= tochead.cdth_trk1;++i) { if (ret > 99) break; memset(&tocentry, 0, sizeof(struct cdrom_tocentry)); tocentry.cdte_track = i; tocentry.cdte_format = CDROM_LBA; if (ioctl(fd, CDROMREADTOCENTRY, &tocentry) < 0) break; // qDebug() << "getTracks got track " << i << " starting at: " << // tocentry.cdte_addr.lba << endl; if ((tocentry.cdte_ctrl & 0x4) == 0x4) { tracks[ret<<1] = tocentry.cdte_addr.lba; tracks[(ret<<1)+1] = i; ret++; } } } close(fd); } #endif return ret; } class KIso::KIsoPrivate { public: KIsoPrivate() = default; QStringList dirList; }; KIso::KIso(const QString& filename, const QString & _mimetype) : KArchive(nullptr) { KRFUNC; KRDEBUG("Starting KIso: " << filename << " - type: " << _mimetype); m_startsec = -1; m_filename = filename; d = new KIsoPrivate; QString mimetype(_mimetype); bool forced = true; if (mimetype.isEmpty()) { QMimeDatabase db; QMimeType mt = db.mimeTypeForFile(filename, QMimeDatabase::MatchContent); if (mt.isValid()) mimetype = mt.name(); //qDebug() << "KIso::KIso mimetype=" << mimetype << endl; // Don't move to prepareDevice - the other constructor theoretically allows ANY filter if (mimetype == "application/x-tgz" || mimetype == "application/x-targz" || // the latter is deprecated but might still be around mimetype == "application/x-webarchive") // that's a gzipped tar file, so ask for gzip filter mimetype = "application/x-gzip"; else if (mimetype == "application/x-tbz") // that's a bzipped2 tar file, so ask for bz2 filter mimetype = "application/x-bzip2"; else { // Something else. Check if it's not really gzip though (e.g. for KOffice docs) QFile file(filename); if (file.open(QIODevice::ReadOnly)) { char firstByte; char secondByte; char thirdByte; file.getChar(&firstByte); file.getChar(&secondByte); file.getChar(&thirdByte); if (firstByte == 0037 && secondByte == (char)0213) mimetype = "application/x-gzip"; else if (firstByte == 'B' && secondByte == 'Z' && thirdByte == 'h') mimetype = "application/x-bzip2"; else if (firstByte == 'P' && secondByte == 'K' && thirdByte == 3) { char fourthByte; file.getChar(&fourthByte); if (fourthByte == 4) mimetype = "application/x-zip"; } } } forced = false; } prepareDevice(filename, mimetype, forced); } void KIso::prepareDevice(const QString & filename, const QString & mimetype, bool forced) { KRFUNC; KRDEBUG("Preparing: " << filename << " - type: " << mimetype << " - using the force: " << forced); /* 'hack' for Qt's false assumption that only S_ISREG is seekable */ if ("inode/blockdevice" == mimetype) setDevice(new QFileHack(filename)); else { if ("application/x-gzip" == mimetype || "application/x-bzip2" == mimetype) forced = true; KCompressionDevice *device; if(mimetype.isEmpty()) { device = new KFilterDev(filename); } else { device = new KCompressionDevice(filename, KFilterDev::compressionTypeForMimeType(mimetype)); } if (device->compressionType() == KCompressionDevice::None && forced) { delete device; } else { setDevice(device); } } } KIso::KIso(QIODevice * dev) : KArchive(dev) { d = new KIsoPrivate; } KIso::~KIso() { // mjarrett: Closes to prevent ~KArchive from aborting w/o device if (isOpen()) close(); if (!m_filename.isEmpty()) delete device(); // we created it ourselves delete d; } /* callback function for libisofs */ static int readf(char *buf, unsigned int start, unsigned int len, void *udata) { KRFUNC; QIODevice* dev = (static_cast(udata))->device(); // seek(0) ensures integrity with the QIODevice's built-in buffer // see bug #372023 for details dev->seek(0); if (dev->seek((qint64)start << (qint64)11)) { if ((dev->read(buf, len << 11u)) != -1) return (len); } //qDebug() << "KIso::ReadRequest failed start: " << start << " len: " << len << endl; return -1; } /* callback function for libisofs */ static int mycallb(struct iso_directory_record *idr, void *udata) { KRFUNC; auto *iso = static_cast(udata); QString path, user, group, symlink; int i; int access; int time, cdate, adate; rr_entry rr; bool special = false; KArchiveEntry *entry = nullptr, *oldentry = nullptr; char z_algo[2], z_params[2]; long long z_size = 0; if ((idr->flags[0] & 1) && !iso->showhidden) return 0; if (iso->level) { if (isonum_711(idr->name_len) == 1) { switch (idr->name[0]) { case 0: path += ("."); special = true; break; case 1: path += (".."); special = true; break; } } if (iso->showrr && ParseRR(idr, &rr) > 0) { if (!special) path = rr.name; symlink = rr.sl; access = rr.mode; time = rr.t_mtime; adate = rr.t_atime; cdate = rr.t_ctime; user.setNum(rr.uid); group.setNum(rr.gid); z_algo[0] = rr.z_algo[0];z_algo[1] = rr.z_algo[1]; z_params[0] = rr.z_params[0];z_params[1] = rr.z_params[1]; z_size = rr.z_size; } else { access = iso->dirent->permissions() & ~S_IFMT; adate = cdate = time = isodate_915(idr->date, 0); user = iso->dirent->user(); group = iso->dirent->group(); if (idr->flags[0] & 2) access |= S_IFDIR; else access |= S_IFREG; if (!special) { if (iso->joliet) { for (i = 0;i < (isonum_711(idr->name_len) - 1);i += 2) { void *p = &(idr->name[i]); QChar ch(be2me_16(*(ushort *)p)); if (ch == ';') break; path += ch; } } else { for (i = 0;i < isonum_711(idr->name_len);++i) { if (idr->name[i] == ';') break; if (idr->name[i]) path += (idr->name[i]); } } if (path.endsWith('.')) path.resize(path.length() - 1); } } if (iso->showrr) FreeRR(&rr); if (idr->flags[0] & 2) { entry = new KIsoDirectory(iso, path, access | S_IFDIR, time, adate, cdate, user, group, symlink); } else { entry = new KIsoFile(iso, path, access, time, adate, cdate, user, group, symlink, (long long)(isonum_733(idr->extent)) << (long long)11, isonum_733(idr->size)); - if (z_size)(static_cast (entry))->setZF(z_algo, z_params, z_size); + if (z_size)(dynamic_cast (entry))->setZF(z_algo, z_params, z_size); } iso->dirent->addEntry(entry); } if ((idr->flags[0] & 2) && (iso->level == 0 || !special)) { if (iso->level) { oldentry = iso->dirent; - iso->dirent = static_cast(entry); + iso->dirent = dynamic_cast(entry); } iso->level++; ProcessDir(&readf, isonum_733(idr->extent), isonum_733(idr->size), &mycallb, udata); iso->level--; - if (iso->level) iso->dirent = static_cast(oldentry); + if (iso->level) iso->dirent = dynamic_cast(oldentry); } return 0; } void KIso::addBoot(struct el_torito_boot_descriptor* bootdesc) { KRFUNC; int i; long long size; boot_head boot; boot_entry *be; QString path; KIsoFile *entry; path = "Catalog"; entry = new KIsoFile(this, path, dirent->permissions() & ~S_IFDIR, dirent->date(), dirent->adate(), dirent->cdate(), dirent->user(), dirent->group(), QString(), (long long)isonum_731(bootdesc->boot_catalog) << (long long)11, (long long)2048); dirent->addEntry(entry); if (!ReadBootTable(&readf, isonum_731(bootdesc->boot_catalog), &boot, this)) { i = 1; be = boot.defentry; while (be) { size = BootImageSize(isonum_711(be->data.d_e.media), isonum_721(be->data.d_e.seccount)); path = "Default Image"; if (i > 1) path += " (" + QString::number(i) + ')'; entry = new KIsoFile(this, path, dirent->permissions() & ~S_IFDIR, dirent->date(), dirent->adate(), dirent->cdate(), dirent->user(), dirent->group(), QString(), (long long)isonum_731(be->data.d_e.start) << (long long)11, size << (long long)9); dirent->addEntry(entry); be = be->next; i++; } FreeBootTable(&boot); } } void KIso::readParams() { KRFUNC; KConfig *config; config = new KConfig("kio_isorc"); KConfigGroup group(config, QString()); showhidden = group.readEntry("showhidden", false); showrr = group.readEntry("showrr", true); delete config; } bool KIso::openArchive(QIODevice::OpenMode mode) { KRFUNC; iso_vol_desc *desc; QString path, uid, gid; QT_STATBUF buf; int tracks[2*100], trackno = 0, i, access, c_b, c_i, c_j; KArchiveDirectory *root; struct iso_directory_record* idr; struct el_torito_boot_descriptor* bootdesc; if (mode == QIODevice::WriteOnly) return false; readParams(); d->dirList.clear(); tracks[0] = 0; if (m_startsec > 0) tracks[0] = m_startsec; //qDebug() << " m_startsec: " << m_startsec << endl; /* We'll use the permission and user/group of the 'host' file except * in Rock Ridge, where the permissions are stored on the file system */ if (QT_STAT(m_filename.toLocal8Bit(), &buf) < 0) { /* defaults, if stat fails */ memset(&buf, 0, sizeof(struct stat)); buf.st_mode = 0777; } else { /* If it's a block device, try to query the track layout (for multisession) */ if (m_startsec == -1 && S_ISBLK(buf.st_mode)) trackno = getTracks(m_filename.toLatin1(), (int*) & tracks); } uid.setNum(buf.st_uid); gid.setNum(buf.st_gid); access = buf.st_mode & ~S_IFMT; //qDebug() << "KIso::openArchive number of tracks: " << trackno << endl; if (trackno == 0) trackno = 1; for (i = 0;i < trackno;++i) { c_b = 1;c_i = 1;c_j = 1; root = rootDir(); if (trackno > 1) { path.clear(); QTextStream(&path) << "Track " << tracks[(i<<1)+1]; root = new KIsoDirectory(this, path, access | S_IFDIR, buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString()); rootDir()->addEntry(root); } desc = ReadISO9660(&readf, tracks[i<<1], this); if (!desc) { //qDebug() << "KIso::openArchive no volume descriptors" << endl; continue; } while (desc) { switch (isonum_711(desc->data.type)) { case ISO_VD_BOOT: bootdesc = (struct el_torito_boot_descriptor*) & (desc->data); if (!memcmp(EL_TORITO_ID, bootdesc->system_id, ISODCL(8, 39))) { path = "El Torito Boot"; if (c_b > 1) path += " (" + QString::number(c_b) + ')'; dirent = new KIsoDirectory(this, path, access | S_IFDIR, buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString()); root->addEntry(dirent); addBoot(bootdesc); c_b++; } break; case ISO_VD_PRIMARY: case ISO_VD_SUPPLEMENTARY: idr = (struct iso_directory_record*) & (((struct iso_primary_descriptor*) & desc->data)->root_directory_record); joliet = JolietLevel(&desc->data); if (joliet) { QTextStream(&path) << "Joliet level " << joliet; if (c_j > 1) path += " (" + QString::number(c_j) + ')'; } else { path = "ISO9660"; if (c_i > 1) path += " (" + QString::number(c_i) + ')'; } dirent = new KIsoDirectory(this, path, access | S_IFDIR, buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString()); root->addEntry(dirent); level = 0; mycallb(idr, this); if (joliet) c_j++; else c_i++; break; } desc = desc->next; } free(desc); } device()->close(); return true; } bool KIso::closeArchive() { KRFUNC; d->dirList.clear(); return true; } bool KIso::writeDir(const QString&, const QString&, const QString&, mode_t, time_t, time_t, time_t) { return false; } bool KIso::prepareWriting(const QString&, const QString&, const QString&, qint64, mode_t, time_t, time_t, time_t) { return false; } bool KIso::finishWriting(qint64) { return false; } bool KIso::writeSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, time_t, time_t, time_t) { return false; } bool KIso::doWriteDir(const QString&, const QString&, const QString&, mode_t, const QDateTime&, const QDateTime &, const QDateTime &) { return false; } bool KIso::doWriteSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, const QDateTime&, const QDateTime&, const QDateTime&) { return false; } bool KIso::doPrepareWriting(const QString& , const QString& , const QString& , qint64, mode_t, const QDateTime&, const QDateTime&, const QDateTime&) { return false; } bool KIso::doFinishWriting(qint64) { return false; } void KIso::virtual_hook(int id, void* data) { KArchive::virtual_hook(id, data); } diff --git a/krusader/Archive/abstractthreadedjob.cpp b/krusader/Archive/abstractthreadedjob.cpp index 2d863b71..69b2ab9b 100644 --- a/krusader/Archive/abstractthreadedjob.cpp +++ b/krusader/Archive/abstractthreadedjob.cpp @@ -1,664 +1,664 @@ /***************************************************************************** * Copyright (C) 2009 Csaba Karai * * Copyright (C) 2009-2018 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 "abstractthreadedjob.h" // QtCore #include #include #include #include #include #include // QtWidgets #include #include #include #include #include "krarchandler.h" #include "../krglobal.h" #include "../krservices.h" #include "../FileSystem/filesystemprovider.h" extern KRarcHandler arcHandler; AbstractThreadedJob::AbstractThreadedJob() : KIO::Job(), _locker(), _waiter(), _stack(), _maxProgressValue(0), _currentProgress(0), _exiting(false), _jobThread(nullptr) { } void AbstractThreadedJob::startAbstractJobThread(AbstractJobThread * jobThread) { _jobThread = jobThread; _jobThread->setJob(this); _jobThread->moveToThread(_jobThread); _jobThread->start(); } AbstractThreadedJob::~AbstractThreadedJob() { _exiting = true; if (_jobThread) { _jobThread->abort(); _locker.lock(); _waiter.wakeAll(); _locker.unlock(); _jobThread->wait(); delete _jobThread; } } bool AbstractThreadedJob::event(QEvent *e) { if (e->type() == QEvent::User) { auto *event = (UserEvent*) e; switch (event->command()) { case CMD_SUCCESS: { emitResult(); } break; case CMD_ERROR: { auto error = event->args()[ 0 ].value(); QString errorText = event->args()[ 1 ].value(); setError(error); setErrorText(errorText); emitResult(); } break; case CMD_INFO: { QString info = event->args()[ 0 ].value(); QString arg1 = event->args()[ 1 ].value(); QString arg2 = event->args()[ 2 ].value(); QString arg3 = event->args()[ 3 ].value(); QString arg4 = event->args()[ 4 ].value(); _title = info; emit description(this, info, qMakePair(arg1, arg2), qMakePair(arg3, arg4)); } break; case CMD_RESET: { QString info = event->args()[ 0 ].value(); QString arg1 = event->args()[ 1 ].value(); QString arg2 = event->args()[ 2 ].value(); QString arg3 = event->args()[ 3 ].value(); QString arg4 = event->args()[ 4 ].value(); _title = info; setProcessedAmount(KJob::Bytes, 0); setTotalAmount(KJob::Bytes, 0); emitSpeed(0); emit description(this, info, qMakePair(arg1, arg2), qMakePair(arg3, arg4)); } break; case CMD_UPLOAD_FILES: case CMD_DOWNLOAD_FILES: { QList sources = KrServices::toUrlList(event->args()[ 0 ].value()); QUrl dest = event->args()[ 1 ].value(); KIO::Job *job = KIO::copy(sources, dest, KIO::HideProgressInfo); addSubjob(job); job->setUiDelegate(new KIO::JobUiDelegate()); connect(job, &KIO::Job::result, this, &AbstractThreadedJob::slotDownloadResult); connect(job, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)), this, SLOT(slotProcessedAmount(KJob*,KJob::Unit,qulonglong))); connect(job, SIGNAL(totalAmount(KJob*,KJob::Unit,qulonglong)), this, SLOT(slotTotalAmount(KJob*,KJob::Unit,qulonglong))); connect(job, SIGNAL(speed(KJob*,ulong)), this, SLOT(slotSpeed(KJob*,ulong))); connect(job, SIGNAL(description(KJob*,QString,QPair,QPair)), this, SLOT(slotDescription(KJob*,QString,QPair,QPair))); } break; case CMD_MAXPROGRESSVALUE: { auto maxValue = event->args()[ 0 ].value(); _maxProgressValue = maxValue; _currentProgress = 0; } break; case CMD_ADD_PROGRESS: { auto progress = event->args()[ 0 ].value(); _currentProgress += progress; if (_maxProgressValue != 0) { setPercent(100 * _currentProgress / _maxProgressValue); int elapsed = _time.isNull() ? 1 : _time.secsTo(QTime::currentTime()); if (elapsed != 0 && event->args().count() > 1) { _time = QTime::currentTime(); QString progressString = (event->args()[ 1 ].value()); emit description(this, _title, qMakePair(progressString, QString("%1/%2").arg(_currentProgress).arg(_maxProgressValue)), qMakePair(QString(), QString()) ); } } } break; case CMD_GET_PASSWORD: { QString path = event->args()[ 0 ].value(); QString password = KRarcHandler::getPassword(path); auto *resultResp = new QList (); (*resultResp) << password; addEventResponse(resultResp); } break; case CMD_MESSAGE: { QString message = event->args()[ 0 ].value(); - auto *ui = static_cast(uiDelegate()); + auto *ui = dynamic_cast(uiDelegate()); KMessageBox::information(ui ? ui->window() : nullptr, message); auto *resultResp = new QList (); addEventResponse(resultResp); } break; } return true; } else { return KIO::Job::event(e); } } void AbstractThreadedJob::addEventResponse(QList * obj) { _locker.lock(); _stack.push(obj); _waiter.wakeOne(); _locker.unlock(); } QList * AbstractThreadedJob::getEventResponse(UserEvent * event) { _locker.lock(); QApplication::postEvent(this, event); _waiter.wait(&_locker); if (_exiting) return nullptr; QList *resp = _stack.pop(); _locker.unlock(); return resp; } void AbstractThreadedJob::sendEvent(UserEvent * event) { QApplication::postEvent(this, event); } void AbstractThreadedJob::slotDownloadResult(KJob* job) { auto *resultResp = new QList (); if (job) { (*resultResp) << QVariant(job->error()); (*resultResp) << QVariant(job->errorText()); } else { (*resultResp) << QVariant(KJob::UserDefinedError); (*resultResp) << QVariant(QString(i18n("Internal error, undefined in result signal"))); } addEventResponse(resultResp); } void AbstractThreadedJob::slotProcessedAmount(KJob *, KJob::Unit unit, qulonglong xu) { setProcessedAmount(unit, xu); } void AbstractThreadedJob::slotTotalAmount(KJob *, KJob::Unit unit, qulonglong xu) { setTotalAmount(unit, xu); } void AbstractThreadedJob::slotSpeed(KJob *, unsigned long spd) { emitSpeed(spd); } void AbstractThreadedJob::slotDescription(KJob *, const QString &title, const QPair &field1, const QPair &field2) { QString mytitle = title; if (!_title.isNull()) mytitle = _title; emit description(this, mytitle, field1, field2); } class AbstractJobObserver : public KRarcObserver { protected: AbstractJobThread * _jobThread; public: explicit AbstractJobObserver(AbstractJobThread * thread): _jobThread(thread) {} ~AbstractJobObserver() override = default; void processEvents() Q_DECL_OVERRIDE { usleep(1000); qApp->processEvents(); } void subJobStarted(const QString & jobTitle, int count) Q_DECL_OVERRIDE { _jobThread->sendReset(jobTitle); _jobThread->sendMaxProgressValue(count); } void subJobStopped() Q_DECL_OVERRIDE { } bool wasCancelled() Q_DECL_OVERRIDE { return _jobThread->_exited; } void error(const QString & error) Q_DECL_OVERRIDE { _jobThread->sendError(KIO::ERR_NO_CONTENT, error); } void detailedError(const QString & error, const QString & details) Q_DECL_OVERRIDE { _jobThread->sendError(KIO::ERR_NO_CONTENT, error + '\n' + details); } void incrementProgress(int c) Q_DECL_OVERRIDE { _jobThread->sendAddProgress(c, _jobThread->_progressTitle); } }; AbstractJobThread::AbstractJobThread() : _job(nullptr), _downloadTempDir(nullptr), _observer(nullptr), _tempFile(nullptr), _tempDir(nullptr), _exited(false) { } AbstractJobThread::~AbstractJobThread() { if (_downloadTempDir) { delete _downloadTempDir; _downloadTempDir = nullptr; } if (_observer) { delete _observer; _observer = nullptr; } if (_tempFile) { delete _tempFile; _tempFile = nullptr; } } void AbstractJobThread::run() { QTimer::singleShot(0, this, &AbstractJobThread::slotStart); QPointer threadLoop = new QEventLoop(this); _loop = threadLoop; threadLoop->exec(); _loop = nullptr; delete threadLoop; } void AbstractJobThread::terminate() { if (_loop && !_exited) { _loop->quit(); _exited = true; } } void AbstractJobThread::abort() { terminate(); } QList AbstractJobThread::remoteUrls(const QUrl &baseUrl, const QStringList & files) { QList urlList; foreach(const QString &name, files) { QUrl url = baseUrl; url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + (name)); urlList << url; } return urlList; } QUrl AbstractJobThread::downloadIfRemote(const QUrl &baseUrl, const QStringList & files) { // download remote URL-s if necessary if (!baseUrl.isLocalFile()) { sendInfo(i18n("Downloading remote files")); _downloadTempDir = new QTemporaryDir(); QList urlList = remoteUrls(baseUrl, files); QUrl dest(_downloadTempDir->path()); QList args; args << KrServices::toStringList(urlList); args << dest; auto * downloadEvent = new UserEvent(CMD_DOWNLOAD_FILES, args); QList * result = _job->getEventResponse(downloadEvent); if (result == nullptr) return QUrl(); auto errorCode = (*result)[ 0 ].value(); QString errorText = (*result)[ 1 ].value(); delete result; if (errorCode) { sendError(errorCode, errorText); return QUrl(); } else { return dest; } } else { return baseUrl; } } QString AbstractJobThread::tempFileIfRemote(const QUrl &kurl, const QString &type) { if (kurl.isLocalFile()) { return kurl.path(); } _tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.") + type); _tempFile->open(); _tempFileName = _tempFile->fileName(); _tempFile->close(); // necessary to create the filename QFile::remove(_tempFileName); _tempFileTarget = kurl; return _tempFileName; } QString AbstractJobThread::tempDirIfRemote(const QUrl &kurl) { if (kurl.isLocalFile()) { return kurl.adjusted(QUrl::StripTrailingSlash).path(); } _tempDir = new QTemporaryDir(); _tempDirTarget = kurl; return _tempDirName = _tempDir->path(); } void AbstractJobThread::sendSuccess() { terminate(); QList args; auto * errorEvent = new UserEvent(CMD_SUCCESS, args); _job->sendEvent(errorEvent); } void AbstractJobThread::sendError(int errorCode, const QString& message) { terminate(); QList args; args << errorCode; args << message; auto * errorEvent = new UserEvent(CMD_ERROR, args); _job->sendEvent(errorEvent); } void AbstractJobThread::sendInfo(const QString& message, const QString& a1, const QString& a2, const QString& a3, const QString& a4) { QList args; args << message; args << a1; args << a2; args << a3; args << a4; auto * infoEvent = new UserEvent(CMD_INFO, args); _job->sendEvent(infoEvent); } void AbstractJobThread::sendReset(const QString& message, const QString& a1, const QString& a2, const QString& a3, const QString& a4) { QList args; args << message; args << a1; args << a2; args << a3; args << a4; auto * infoEvent = new UserEvent(CMD_RESET, args); _job->sendEvent(infoEvent); } void AbstractJobThread::sendMaxProgressValue(qulonglong value) { QList args; args << value; auto * infoEvent = new UserEvent(CMD_MAXPROGRESSVALUE, args); _job->sendEvent(infoEvent); } void AbstractJobThread::sendAddProgress(qulonglong value, const QString &progress) { QList args; args << value; if (!progress.isNull()) args << progress; auto * infoEvent = new UserEvent(CMD_ADD_PROGRESS, args); _job->sendEvent(infoEvent); } void countFiles(const QString &path, unsigned long &totalFiles, bool &stop) { const QDir dir(path); if (!dir.exists()) { totalFiles++; // assume it's a file return; } for (const QString name : dir.entryList()) { if (stop) return; if (name == QStringLiteral(".") || name == QStringLiteral("..")) continue; countFiles(dir.absoluteFilePath(name), totalFiles, stop); } } void AbstractJobThread::countLocalFiles(const QUrl &baseUrl, const QStringList &names, unsigned long &totalFiles) { sendReset(i18n("Counting files")); FileSystem *calcSpaceFileSystem = FileSystemProvider::instance().getFilesystem(baseUrl); calcSpaceFileSystem->scanDir(baseUrl); for (const QString name : names) { if (_exited) return; const QString path = calcSpaceFileSystem->getUrl(name).toLocalFile(); if (!QFileInfo(path).exists()) return; countFiles(path, totalFiles, _exited); } delete calcSpaceFileSystem; } KRarcObserver * AbstractJobThread::observer() { if (_observer) return _observer; _observer = new AbstractJobObserver(this); return _observer; } bool AbstractJobThread::uploadTempFiles() { if (_tempFile != nullptr || _tempDir != nullptr) { sendInfo(i18n("Uploading to remote destination")); if (_tempFile) { QList urlList; urlList << QUrl::fromLocalFile(_tempFileName); QList args; args << KrServices::toStringList(urlList); args << _tempFileTarget; auto * uploadEvent = new UserEvent(CMD_UPLOAD_FILES, args); QList * result = _job->getEventResponse(uploadEvent); if (result == nullptr) return false; auto errorCode = (*result)[ 0 ].value(); QString errorText = (*result)[ 1 ].value(); delete result; if (errorCode) { sendError(errorCode, errorText); return false; } } if (_tempDir) { QList urlList; QDir tempDir(_tempDirName); QStringList list = tempDir.entryList(); foreach(const QString &name, list) { if (name == "." || name == "..") continue; QUrl url = QUrl::fromLocalFile(_tempDirName).adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + (name)); urlList << url; } QList args; args << KrServices::toStringList(urlList); args << _tempDirTarget; auto * uploadEvent = new UserEvent(CMD_UPLOAD_FILES, args); QList * result = _job->getEventResponse(uploadEvent); if (result == nullptr) return false; auto errorCode = (*result)[ 0 ].value(); QString errorText = (*result)[ 1 ].value(); delete result; if (errorCode) { sendError(errorCode, errorText); return false; } } } return true; } QString AbstractJobThread::getPassword(const QString &path) { QList args; args << path; auto * getPasswdEvent = new UserEvent(CMD_GET_PASSWORD, args); QList * result = _job->getEventResponse(getPasswdEvent); if (result == nullptr) return QString(); QString password = (*result)[ 0 ].value(); if (password.isNull()) password = QString(""); delete result; return password; } void AbstractJobThread::sendMessage(const QString &message) { QList args; args << message; auto * getPasswdEvent = new UserEvent(CMD_MESSAGE, args); QList * result = _job->getEventResponse(getPasswdEvent); if (result == nullptr) return; delete result; } //! Gets some archive information that is needed in several cases. /*! \param path A path to the archive. \param type The type of the archive. \param password The password of the archive. \param arcName The name of the archive. \param sourceFolder A QUrl, which may be remote, of the folder where the archive is. \return If the archive information has been obtained. */ bool AbstractJobThread::getArchiveInformation(QString &path, QString &type, QString &password, QString &arcName, const QUrl &sourceFolder) { // Safety checks (though the user shouldn't have been able to select something named "" or "..") if (arcName.isEmpty()) return false; if (arcName == "..") return false; QUrl url = sourceFolder.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + arcName); path = url.adjusted(QUrl::StripTrailingSlash).path(); QMimeDatabase db; QMimeType mt = db.mimeTypeForUrl(url); QString mime = mt.isValid() ? mt.name() : QString(); bool encrypted = false; type = arcHandler.getType(encrypted, path, mime); // Check that the archive is supported if (!KRarcHandler::arcSupported(type)) { sendError(KIO::ERR_NO_CONTENT, i18nc("%1=archive filename", "%1, unsupported archive type.", arcName)); return false; } password = encrypted ? getPassword(path) : QString(); return true; } diff --git a/krusader/BookMan/krbookmark.cpp b/krusader/BookMan/krbookmark.cpp index f0afe041..5575927f 100644 --- a/krusader/BookMan/krbookmark.cpp +++ b/krusader/BookMan/krbookmark.cpp @@ -1,152 +1,152 @@ /***************************************************************************** * Copyright (C) 2002 Shie Erlich * * Copyright (C) 2002 Rafi Yanai * * Copyright (C) 2004-2018 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 "krbookmark.h" #include "../krglobal.h" #include "../icon.h" #include "../Archive/krarchandler.h" #include "../FileSystem/krtrashhandler.h" #include "../Panel/listpanelactions.h" #include #include #include #define BM_NAME(X) (QString("Bookmark:")+X) static const char* NAME_TRASH = I18N_NOOP("Trash bin"); static const char* NAME_VIRTUAL = I18N_NOOP("Virtual Filesystem"); static const char* NAME_LAN = I18N_NOOP("Local Network"); KrBookmark::KrBookmark(const QString& name, QUrl url, KActionCollection *parent, const QString& iconName, const QString& actionName) : QAction(parent), _url(std::move(url)), _iconName(iconName), _folder(false), _separator(false), _autoDelete(true) { QString actName = actionName.isNull() ? BM_NAME(name) : BM_NAME(actionName); setText(name); parent->addAction(actName, this); connect(this, &KrBookmark::triggered, this, &KrBookmark::activatedProxy); setIconName(iconName); } KrBookmark::KrBookmark(const QString& name, const QString& iconName) : QAction(Icon(iconName), name, nullptr), _iconName(iconName), _folder(true), _separator(false), _autoDelete(false) { setIcon(Icon(iconName == "" ? "folder" : iconName)); } KrBookmark::~KrBookmark() { if (_autoDelete) { QListIterator it(_children); while (it.hasNext()) delete it.next(); _children.clear(); } } void KrBookmark::setIconName(const QString& iconName) { if (!iconName.isEmpty()) { setIcon(Icon(iconName)); } else if (_url.isLocalFile()) { setIcon(Icon("folder")); } else if (KRarcHandler::isArchive(_url)) { setIcon(Icon("application-x-tar")); } else { setIcon(Icon("folder-html")); } } KrBookmark * KrBookmark::getExistingBookmark(const QString& actionName, KActionCollection *collection) { - return static_cast(collection->action(BM_NAME(actionName))); + return dynamic_cast(collection->action(BM_NAME(actionName))); } KrBookmark * KrBookmark::trash(KActionCollection *collection) { KrBookmark *bm = getExistingBookmark(i18n(NAME_TRASH), collection); if (!bm) bm = new KrBookmark(i18n(NAME_TRASH), QUrl("trash:/"), collection); bm->setIcon(Icon(KrTrashHandler::trashIconName())); return bm; } KrBookmark * KrBookmark::virt(KActionCollection *collection) { KrBookmark *bm = getExistingBookmark(i18n(NAME_VIRTUAL), collection); if (!bm) { bm = new KrBookmark(i18n(NAME_VIRTUAL), QUrl("virt:/"), collection); bm->setIcon(Icon("document-open-remote")); } return bm; } KrBookmark * KrBookmark::lan(KActionCollection *collection) { KrBookmark *bm = getExistingBookmark(i18n(NAME_LAN), collection); if (!bm) { bm = new KrBookmark(i18n(NAME_LAN), QUrl("remote:/"), collection); bm->setIcon(Icon("network-workgroup")); } return bm; } QAction * KrBookmark::jumpBackAction(KActionCollection *collection, bool isSetter, ListPanelActions *sourceActions) { auto actionName = isSetter ? QString("setJumpBack") : QString("jumpBack"); auto action = collection->action(actionName); if (action) { return action; } if (!sourceActions) { return nullptr; } // copy essential part of source action auto sourceAction = isSetter ? sourceActions->actSetJumpBack : sourceActions->actJumpBack; action = new QAction(sourceAction->icon(), sourceAction->text(), sourceAction); action->setShortcut(sourceAction->shortcut()); action->setShortcutContext(Qt::WidgetShortcut); connect(action, &QAction::triggered, sourceAction, &QAction::trigger); // ensure there are no accelerator keys coming from another menu action->setText(KLocalizedString::removeAcceleratorMarker(action->text())); collection->addAction(actionName, action); return action; } KrBookmark * KrBookmark::separator() { KrBookmark *bm = new KrBookmark(""); bm->_separator = true; bm->_folder = false; return bm; } void KrBookmark::activatedProxy() { emit activated(url()); } diff --git a/krusader/BookMan/krbookmarkhandler.cpp b/krusader/BookMan/krbookmarkhandler.cpp index 3e14e27a..57dab085 100644 --- a/krusader/BookMan/krbookmarkhandler.cpp +++ b/krusader/BookMan/krbookmarkhandler.cpp @@ -1,882 +1,882 @@ /***************************************************************************** * Copyright (C) 2002 Shie Erlich * * Copyright (C) 2002 Rafi Yanai * * Copyright (C) 2004-2018 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 "krbookmarkhandler.h" #include "kraddbookmarkdlg.h" #include "../krglobal.h" #include "../icon.h" #include "../krslots.h" #include "../kractions.h" #include "../krmainwindow.h" #include "../Dialogs/popularurls.h" #include "../FileSystem/filesystem.h" #include "../Panel/krpanel.h" #include "../Panel/listpanelactions.h" // QtCore #include #include #include #include #include #include // QtGui #include #include #include #include #include #include #include #include #define SPECIAL_BOOKMARKS true // ------------------------ for internal use #define BOOKMARKS_FILE "krusader/krbookmarks.xml" #define CONNECT_BM(X) { disconnect(X, SIGNAL(activated(QUrl)), 0, 0); connect(X, SIGNAL(activated(QUrl)), this, SLOT(slotActivated(QUrl))); } KrBookmarkHandler::KrBookmarkHandler(KrMainWindow *mainWindow) : QObject(mainWindow->widget()), _mainWindow(mainWindow), _middleClick(false), _mainBookmarkPopup(nullptr), _specialBookmarks(), _quickSearchAction(nullptr), _quickSearchBar(nullptr), _quickSearchMenu(nullptr) { // create our own action collection and make the shortcuts apply only to parent _privateCollection = new KActionCollection(this); _collection = _mainWindow->actions(); // create _root: father of all bookmarks. it is a dummy bookmark and never shown _root = new KrBookmark(i18n("Bookmarks")); _root->setParent(this); // load bookmarks importFromFile(); // create bookmark manager QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + BOOKMARKS_FILE; manager = KBookmarkManager::managerForFile(filename, QStringLiteral("krusader")); connect(manager, &KBookmarkManager::changed, this, &KrBookmarkHandler::bookmarksChanged); // create the quick search bar and action _quickSearchAction = new QWidgetAction(this); _quickSearchBar = new QLineEdit(); _quickSearchBar->setPlaceholderText(i18n("Type to search...")); _quickSearchAction->setDefaultWidget(_quickSearchBar); // ownership of the bar is transferred to the action _quickSearchAction->setEnabled(false); _setQuickSearchText(""); // fill a dummy menu to properly init actions (allows toolbar bookmark buttons to work properly) auto menu = new QMenu(mainWindow->widget()); populate(menu); menu->deleteLater(); } KrBookmarkHandler::~KrBookmarkHandler() { delete manager; delete _privateCollection; } void KrBookmarkHandler::bookmarkCurrent(QUrl url) { QPointer dlg = new KrAddBookmarkDlg(_mainWindow->widget(), std::move(url)); if (dlg->exec() == QDialog::Accepted) { KrBookmark *bm = new KrBookmark(dlg->name(), dlg->url(), _collection); addBookmark(bm, dlg->folder()); } delete dlg; } void KrBookmarkHandler::addBookmark(KrBookmark *bm, KrBookmark *folder) { if (folder == nullptr) folder = _root; // add to the list (bottom) folder->children().append(bm); exportToFile(); } void KrBookmarkHandler::deleteBookmark(KrBookmark *bm) { if (bm->isFolder()) clearBookmarks(bm); // remove the child bookmarks removeReferences(_root, bm); foreach(QWidget *w, bm->associatedWidgets()) w->removeAction(bm); delete bm; exportToFile(); } void KrBookmarkHandler::removeReferences(KrBookmark *root, KrBookmark *bmToRemove) { int index = root->children().indexOf(bmToRemove); if (index >= 0) root->children().removeAt(index); QListIterator it(root->children()); while (it.hasNext()) { KrBookmark *bm = it.next(); if (bm->isFolder()) removeReferences(bm, bmToRemove); } } void KrBookmarkHandler::exportToFileBookmark(QDomDocument &doc, QDomElement &where, KrBookmark *bm) { if (bm->isSeparator()) { QDomElement bookmark = doc.createElement("separator"); where.appendChild(bookmark); } else { QDomElement bookmark = doc.createElement("bookmark"); // url bookmark.setAttribute("href", bm->url().toDisplayString()); // icon bookmark.setAttribute("icon", bm->iconName()); // title QDomElement title = doc.createElement("title"); title.appendChild(doc.createTextNode(bm->text())); bookmark.appendChild(title); where.appendChild(bookmark); } } void KrBookmarkHandler::exportToFileFolder(QDomDocument &doc, QDomElement &parent, KrBookmark *folder) { QListIterator it(folder->children()); while (it.hasNext()) { KrBookmark *bm = it.next(); if (bm->isFolder()) { QDomElement newFolder = doc.createElement("folder"); newFolder.setAttribute("icon", bm->iconName()); parent.appendChild(newFolder); QDomElement title = doc.createElement("title"); title.appendChild(doc.createTextNode(bm->text())); newFolder.appendChild(title); exportToFileFolder(doc, newFolder, bm); } else { exportToFileBookmark(doc, parent, bm); } } } // export to file using the xbel standard // // // Developer Web Site // // Title of this folder // KDE Web Site // // My own bookmarks // KOffice Web Site // // KDevelop Web Site // // // void KrBookmarkHandler::exportToFile() { QDomDocument doc("xbel"); QDomElement root = doc.createElement("xbel"); doc.appendChild(root); exportToFileFolder(doc, root, _root); if (!doc.firstChild().isProcessingInstruction()) { // adding: if not already present QDomProcessingInstruction instr = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\" "); doc.insertBefore(instr, doc.firstChild()); } QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + BOOKMARKS_FILE; QFile file(filename); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); stream << doc.toString(); file.close(); } else { KMessageBox::error(_mainWindow->widget(), i18n("Unable to write to %1", filename), i18n("Error")); } } bool KrBookmarkHandler::importFromFileBookmark(QDomElement &e, KrBookmark *parent, const QString& path, QString *errorMsg) { QString url, name, iconName; // verify tag if (e.tagName() != "bookmark") { *errorMsg = i18n("%1 instead of %2", e.tagName(), QLatin1String("bookmark")); return false; } // verify href if (!e.hasAttribute("href")) { *errorMsg = i18n("missing tag %1", QLatin1String("href")); return false; } else url = e.attribute("href"); // verify title QDomElement te = e.firstChild().toElement(); if (te.tagName() != "title") { *errorMsg = i18n("missing tag %1", QLatin1String("title")); return false; } else name = te.text(); // do we have an icon? if (e.hasAttribute("icon")) { iconName = e.attribute("icon"); } // ok: got name and url, let's add a bookmark KrBookmark *bm = KrBookmark::getExistingBookmark(path + name, _collection); if (!bm) { bm = new KrBookmark(name, QUrl(url), _collection, iconName, path + name); } else { bm->setURL(QUrl(url)); bm->setIconName(iconName); } parent->children().append(bm); return true; } bool KrBookmarkHandler::importFromFileFolder(QDomNode &first, KrBookmark *parent, const QString& path, QString *errorMsg) { QString name; QDomNode n = first; while (!n.isNull()) { QDomElement e = n.toElement(); if (e.tagName() == "bookmark") { if (!importFromFileBookmark(e, parent, path, errorMsg)) return false; } else if (e.tagName() == "folder") { QString iconName = ""; if (e.hasAttribute("icon")) iconName = e.attribute("icon"); // the title is the first child of the folder QDomElement tmp = e.firstChild().toElement(); if (tmp.tagName() != "title") { *errorMsg = i18n("missing tag %1", QLatin1String("title")); return false; } else name = tmp.text(); KrBookmark *folder = new KrBookmark(name, iconName); parent->children().append(folder); QDomNode nextOne = tmp.nextSibling(); if (!importFromFileFolder(nextOne, folder, path + name + '/', errorMsg)) return false; } else if (e.tagName() == "separator") { parent->children().append(KrBookmark::separator()); } n = n.nextSibling(); } return true; } void KrBookmarkHandler::importFromFile() { clearBookmarks(_root, false); QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + BOOKMARKS_FILE; QFile file(filename); if (!file.open(QIODevice::ReadOnly)) return; // no bookmarks file QString errorMsg; QDomNode n; QDomElement e; QDomDocument doc("xbel"); if (!doc.setContent(&file, &errorMsg)) { goto BM_ERROR; } // iterate through the document: first child should be "xbel" (skip all until we find it) n = doc.firstChild(); while (!n.isNull() && n.toElement().tagName() != "xbel") n = n.nextSibling(); if (n.isNull() || n.toElement().tagName() != "xbel") { errorMsg = i18n("%1 does not seem to be a valid bookmarks file", filename); goto BM_ERROR; } else n = n.firstChild(); // skip the xbel part importFromFileFolder(n, _root, "", &errorMsg); goto BM_SUCCESS; BM_ERROR: KMessageBox::error(_mainWindow->widget(), i18n("Error reading bookmarks file: %1", errorMsg), i18n("Error")); BM_SUCCESS: file.close(); } void KrBookmarkHandler::_setQuickSearchText(const QString &text) { bool isEmptyQuickSearchBarVisible = KConfigGroup(krConfig, "Look&Feel").readEntry("Always show search bar", true); _quickSearchBar->setText(text); auto length = text.length(); bool isVisible = isEmptyQuickSearchBarVisible || length > 0; _quickSearchAction->setVisible(isVisible); _quickSearchBar->setVisible(isVisible); if (length == 0) { qDebug() << "Bookmark search: reset"; _resetActionTextAndHighlighting(); } else { qDebug() << "Bookmark search: query =" << text; } } QString KrBookmarkHandler::_quickSearchText() const { return _quickSearchBar->text(); } void KrBookmarkHandler::_highlightAction(QAction *action, bool isMatched) { auto font = action->font(); font.setBold(isMatched); action->setFont(font); } void KrBookmarkHandler::populate(QMenu *menu) { // removing action from previous menu is necessary // otherwise it won't be displayed in the currently populating menu if (_mainBookmarkPopup) { _mainBookmarkPopup->removeAction(_quickSearchAction); } _mainBookmarkPopup = menu; menu->clear(); _specialBookmarks.clear(); buildMenu(_root, menu); } void KrBookmarkHandler::buildMenu(KrBookmark *parent, QMenu *menu, int depth) { // add search bar widget to the top of the menu if (depth == 0) { menu->addAction(_quickSearchAction); } // run the loop twice, in order to put the folders on top. stupid but easy :-) // note: this code drops the separators put there by the user QListIterator it(parent->children()); while (it.hasNext()) { KrBookmark *bm = it.next(); if (!bm->isFolder()) continue; auto *newMenu = new QMenu(menu); newMenu->setIcon(Icon(bm->iconName())); newMenu->setTitle(bm->text()); QAction *menuAction = menu->addMenu(newMenu); QVariant v; v.setValue(bm); menuAction->setData(v); buildMenu(bm, newMenu, depth + 1); } it.toFront(); while (it.hasNext()) { KrBookmark *bm = it.next(); if (bm->isFolder()) continue; if (bm->isSeparator()) { menu->addSeparator(); continue; } menu->addAction(bm); CONNECT_BM(bm); } if (depth == 0) { KConfigGroup group(krConfig, "Private"); bool hasPopularURLs = group.readEntry("BM Popular URLs", true); bool hasTrash = group.readEntry("BM Trash", true); bool hasLan = group.readEntry("BM Lan", true); bool hasVirtualFS = group.readEntry("BM Virtual FS", true); bool hasJumpback = group.readEntry("BM Jumpback", true); if (hasPopularURLs) { menu->addSeparator(); // add the popular links submenu auto *newMenu = new QMenu(menu); newMenu->setTitle(i18n("Popular URLs")); newMenu->setIcon(Icon("folder-bookmark")); QAction *bmfAct = menu->addMenu(newMenu); _specialBookmarks.append(bmfAct); // add the top 15 urls #define MAX 15 QList list = _mainWindow->popularUrls()->getMostPopularUrls(MAX); QList::Iterator it; for (it = list.begin(); it != list.end(); ++it) { QString name; if ((*it).isLocalFile()) name = (*it).path(); else name = (*it).toDisplayString(); // note: these bookmark are put into the private collection // as to not spam the general collection KrBookmark *bm = KrBookmark::getExistingBookmark(name, _privateCollection); if (!bm) bm = new KrBookmark(name, *it, _privateCollection); newMenu->addAction(bm); CONNECT_BM(bm); } newMenu->addSeparator(); newMenu->addAction(krPopularUrls); newMenu->installEventFilter(this); } // do we need to add special bookmarks? if (SPECIAL_BOOKMARKS) { if (hasTrash || hasLan || hasVirtualFS) menu->addSeparator(); KrBookmark *bm; // note: special bookmarks are not kept inside the _bookmarks list and added ad-hoc if (hasTrash) { bm = KrBookmark::trash(_collection); menu->addAction(bm); _specialBookmarks.append(bm); CONNECT_BM(bm); } if (hasLan) { bm = KrBookmark::lan(_collection); menu->addAction(bm); _specialBookmarks.append(bm); CONNECT_BM(bm); } if (hasVirtualFS) { bm = KrBookmark::virt(_collection); menu->addAction(bm); _specialBookmarks.append(bm); CONNECT_BM(bm); } if (hasJumpback) { menu->addSeparator(); ListPanelActions *actions = _mainWindow->listPanelActions(); auto slotTriggered = [=] { if (_mainBookmarkPopup && !_mainBookmarkPopup->isHidden()) { _mainBookmarkPopup->close(); } }; auto addJumpBackAction = [=](bool isSetter) { auto action = KrBookmark::jumpBackAction(_privateCollection, isSetter, actions); if (action) { menu->addAction(action); _specialBookmarks.append(action); // disconnecting from this as a receiver is important: // we don't want to break connections established by KrBookmark::jumpBackAction disconnect(action, &QAction::triggered, this, nullptr); connect(action, &QAction::triggered, this, slotTriggered); } }; addJumpBackAction(true); addJumpBackAction(false); } } menu->addSeparator(); menu->addAction(KrActions::actAddBookmark); _specialBookmarks.append(KrActions::actAddBookmark); QAction *bmAct = menu->addAction(Icon("bookmarks"), i18n("Manage Bookmarks"), manager, SLOT(slotEditBookmarks())); _specialBookmarks.append(bmAct); // make sure the menu is connected to us disconnect(menu, SIGNAL(triggered(QAction*)), nullptr, nullptr); } menu->installEventFilter(this); } void KrBookmarkHandler::clearBookmarks(KrBookmark *root, bool removeBookmarks) { for (auto it = root->children().begin(); it != root->children().end(); it = root->children().erase(it)) { KrBookmark *bm = *it; if (bm->isFolder()) { clearBookmarks(bm, removeBookmarks); delete bm; } else if (bm->isSeparator()) { delete bm; } else if (removeBookmarks) { foreach (QWidget *w, bm->associatedWidgets()) { w->removeAction(bm); } delete bm; } } } void KrBookmarkHandler::bookmarksChanged(const QString&, const QString&) { importFromFile(); } bool KrBookmarkHandler::eventFilter(QObject *obj, QEvent *ev) { auto eventType = ev->type(); auto *menu = qobject_cast(obj); if (eventType == QEvent::Show && menu) { _setQuickSearchText(""); _quickSearchMenu = menu; qDebug() << "Bookmark search: menu" << menu << "is shown"; return QObject::eventFilter(obj, ev); } if (eventType == QEvent::Close && menu && _quickSearchMenu) { if (_quickSearchMenu == menu) { qDebug() << "Bookmark search: stopped on menu" << menu; _setQuickSearchText(""); _quickSearchMenu = nullptr; } else { qDebug() << "Bookmark search: active action =" << _quickSearchMenu->activeAction(); // fix automatic deactivation of current action due to spurious close event from submenu auto quickSearchMenu = _quickSearchMenu; auto activeAction = _quickSearchMenu->activeAction(); QTimer::singleShot(0, this, [=]() { qDebug() << "Bookmark search: active action =" << quickSearchMenu->activeAction(); if (!quickSearchMenu->activeAction() && activeAction) { quickSearchMenu->setActiveAction(activeAction); qDebug() << "Bookmark search: restored active action =" << quickSearchMenu->activeAction(); } }); } return QObject::eventFilter(obj, ev); } // Having it occur on keypress is consistent with other shortcuts, // such as Ctrl+W and accelerator keys if (eventType == QEvent::KeyPress && menu) { - auto *kev = static_cast(ev); + auto *kev = dynamic_cast(ev); QList acts = menu->actions(); bool quickSearchStarted = false; bool searchInSpecialItems = KConfigGroup(krConfig, "Look&Feel").readEntry("Search in special items", false); if (kev->key() == Qt::Key_Left && kev->modifiers() == Qt::NoModifier) { menu->close(); return true; } if ((kev->modifiers() != Qt::ShiftModifier && kev->modifiers() != Qt::NoModifier) || kev->text().isEmpty() || kev->key() == Qt::Key_Delete || kev->key() == Qt::Key_Return || kev->key() == Qt::Key_Escape) { return QObject::eventFilter(obj, ev); } // update quick search text if (kev->key() == Qt::Key_Backspace) { auto newSearchText = _quickSearchText(); newSearchText.chop(1); _setQuickSearchText(newSearchText); if (_quickSearchText().length() == 0) { return QObject::eventFilter(obj, ev); } } else { quickSearchStarted = _quickSearchText().length() == 0; _setQuickSearchText(_quickSearchText().append(kev->text())); } if (quickSearchStarted) { _quickSearchMenu = menu; qDebug() << "Bookmark search: started on menu" << menu; } // match actions QAction *matchedAction = nullptr; int nMatches = 0; const Qt::CaseSensitivity matchCase = _quickSearchText() == _quickSearchText().toLower() ? Qt::CaseInsensitive : Qt::CaseSensitive; for (auto act : acts) { if (act->isSeparator() || act->text().isEmpty()) { continue; } if (!searchInSpecialItems && _specialBookmarks.contains(act)) { continue; } if (quickSearchStarted) { // if the first key press is an accelerator key, let the accelerator handler process this event if (act->text().contains('&' + kev->text(), Qt::CaseInsensitive)) { qDebug() << "Bookmark search: hit accelerator key of" << act; _setQuickSearchText(""); return QObject::eventFilter(obj, ev); } // strip accelerator keys from actions so they don't interfere with the search key press events auto text = act->text(); _quickSearchOriginalActionTitles.insert(act, text); act->setText(KLocalizedString::removeAcceleratorMarker(text)); } // match prefix of the action text to the query if (act->text().left(_quickSearchText().length()).compare(_quickSearchText(), matchCase) == 0) { _highlightAction(act); if (!matchedAction || matchedAction->menu()) { // Can't highlight menus (see comment below), hopefully pick something we can matchedAction = act; } nMatches++; } else { _highlightAction(act, false); } } if (matchedAction) { qDebug() << "Bookmark search: primary match =" << matchedAction->text() << ", number of matches =" << nMatches; } else { qDebug() << "Bookmark search: no matches"; } // trigger the matched menu item or set an active item accordingly if (nMatches == 1) { _setQuickSearchText(""); if ((bool) matchedAction->menu()) { menu->setActiveAction(matchedAction); } else { matchedAction->activate(QAction::Trigger); } } else if (nMatches > 1) { // Because of a bug submenus cannot be highlighted // https://bugreports.qt.io/browse/QTBUG-939 if (!matchedAction->menu()) { menu->setActiveAction(matchedAction); } else { menu->setActiveAction(nullptr); } } else { menu->setActiveAction(nullptr); } return true; } if (eventType == QEvent::MouseButtonRelease) { - switch (static_cast(ev)->button()) { + switch (dynamic_cast(ev)->button()) { case Qt::RightButton: _middleClick = false; if (obj->inherits("QMenu")) { - auto *menu = static_cast(obj); - QAction *act = menu->actionAt(static_cast(ev)->pos()); + auto *menu = dynamic_cast(obj); + QAction *act = menu->actionAt(dynamic_cast(ev)->pos()); if (obj == _mainBookmarkPopup && _specialBookmarks.contains(act)) { rightClickOnSpecialBookmark(); return true; } auto *bm = qobject_cast(act); if (bm != nullptr) { rightClicked(menu, bm); return true; } else if (act && act->data().canConvert()) { auto *bm = act->data().value(); rightClicked(menu, bm); } } break; case Qt::LeftButton: _middleClick = false; break; case Qt::MidButton: _middleClick = true; break; default: break; } } return QObject::eventFilter(obj, ev); } void KrBookmarkHandler::_resetActionTextAndHighlighting() { for (QHash::const_iterator i = _quickSearchOriginalActionTitles.begin(); i != _quickSearchOriginalActionTitles.end(); ++i) { QAction *action = i.key(); action->setText(i.value()); _highlightAction(action, false); } _quickSearchOriginalActionTitles.clear(); } #define POPULAR_URLS_ID 100100 #define TRASH_ID 100101 #define LAN_ID 100103 #define VIRTUAL_FS_ID 100102 #define JUMP_BACK_ID 100104 void KrBookmarkHandler::rightClickOnSpecialBookmark() { KConfigGroup group(krConfig, "Private"); bool hasPopularURLs = group.readEntry("BM Popular URLs", true); bool hasTrash = group.readEntry("BM Trash", true); bool hasLan = group.readEntry("BM Lan", true); bool hasVirtualFS = group.readEntry("BM Virtual FS", true); bool hasJumpback = group.readEntry("BM Jumpback", true); QMenu menu(_mainBookmarkPopup); menu.setTitle(i18n("Enable special bookmarks")); QAction *act; act = menu.addAction(i18n("Popular URLs")); act->setData(QVariant(POPULAR_URLS_ID)); act->setCheckable(true); act->setChecked(hasPopularURLs); act = menu.addAction(i18n("Trash bin")); act->setData(QVariant(TRASH_ID)); act->setCheckable(true); act->setChecked(hasTrash); act = menu.addAction(i18n("Local Network")); act->setData(QVariant(LAN_ID)); act->setCheckable(true); act->setChecked(hasLan); act = menu.addAction(i18n("Virtual Filesystem")); act->setData(QVariant(VIRTUAL_FS_ID)); act->setCheckable(true); act->setChecked(hasVirtualFS); act = menu.addAction(i18n("Jump back")); act->setData(QVariant(JUMP_BACK_ID)); act->setCheckable(true); act->setChecked(hasJumpback); connect(_mainBookmarkPopup, SIGNAL(highlighted(int)), &menu, SLOT(close())); connect(_mainBookmarkPopup, SIGNAL(activated(int)), &menu, SLOT(close())); int result = -1; QAction *res = menu.exec(QCursor::pos()); if (res && res->data().canConvert()) result = res->data().toInt(); bool doCloseMain = true; switch (result) { case POPULAR_URLS_ID: group.writeEntry("BM Popular URLs", !hasPopularURLs); break; case TRASH_ID: group.writeEntry("BM Trash", !hasTrash); break; case LAN_ID: group.writeEntry("BM Lan", !hasLan); break; case VIRTUAL_FS_ID: group.writeEntry("BM Virtual FS", !hasVirtualFS); break; case JUMP_BACK_ID: group.writeEntry("BM Jumpback", !hasJumpback); break; default: doCloseMain = false; break; } menu.close(); if (doCloseMain && _mainBookmarkPopup) _mainBookmarkPopup->close(); } #define OPEN_ID 100200 #define OPEN_NEW_TAB_ID 100201 #define DELETE_ID 100202 void KrBookmarkHandler::rightClicked(QMenu *menu, KrBookmark * bm) { QMenu popup(_mainBookmarkPopup); QAction * act; if (!bm->isFolder()) { act = popup.addAction(Icon("document-open"), i18n("Open")); act->setData(QVariant(OPEN_ID)); act = popup.addAction(Icon("tab-new"), i18n("Open in a new tab")); act->setData(QVariant(OPEN_NEW_TAB_ID)); popup.addSeparator(); } act = popup.addAction(Icon("edit-delete"), i18n("Delete")); act->setData(QVariant(DELETE_ID)); connect(menu, SIGNAL(highlighted(int)), &popup, SLOT(close())); connect(menu, SIGNAL(activated(int)), &popup, SLOT(close())); int result = -1; QAction *res = popup.exec(QCursor::pos()); if (res && res->data().canConvert ()) result = res->data().toInt(); popup.close(); if (_mainBookmarkPopup && result >= OPEN_ID && result <= DELETE_ID) { _mainBookmarkPopup->close(); } switch (result) { case OPEN_ID: SLOTS->refresh(bm->url()); break; case OPEN_NEW_TAB_ID: _mainWindow->activeManager()->newTab(bm->url()); break; case DELETE_ID: deleteBookmark(bm); break; } } // used to monitor middle clicks. if mid is found, then the // bookmark is opened in a new tab. ugly, but easier than overloading // KAction and KActionCollection. void KrBookmarkHandler::slotActivated(const QUrl &url) { if (_mainBookmarkPopup && !_mainBookmarkPopup->isHidden()) _mainBookmarkPopup->close(); if (_middleClick) _mainWindow->activeManager()->newTab(url); else SLOTS->refresh(url); } diff --git a/krusader/DiskUsage/dulines.cpp b/krusader/DiskUsage/dulines.cpp index 9440472c..b40b2f70 100644 --- a/krusader/DiskUsage/dulines.cpp +++ b/krusader/DiskUsage/dulines.cpp @@ -1,534 +1,534 @@ /***************************************************************************** * Copyright (C) 2004 Csaba Karai * * Copyright (C) 2004-2018 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 "dulines.h" #include "../icon.h" #include "../krglobal.h" #include "../FileSystem/krpermhandler.h" // QtCore #include // QtGui #include #include #include #include #include #include // QtWidgets #include #include #include #include #include #include #include class DULinesItemDelegate : public QItemDelegate { public: explicit DULinesItemDelegate(QObject *parent = nullptr) : QItemDelegate(parent) {} void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE { QItemDelegate::paint(painter, option, index); QVariant value = index.data(Qt::UserRole); if (value.isValid()) { QString text = value.toString(); value = index.data(Qt::DisplayRole); QString display; if (value.isValid()) display = value.toString(); QSize iconSize; value = index.data(Qt::DecorationRole); if (value.isValid()) iconSize = qvariant_cast(value).actualSize(option.decorationSize); painter->save(); painter->setClipRect(option.rect); QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) cg = QPalette::Inactive; if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); } else { painter->setPen(option.palette.color(cg, QPalette::Text)); } QFont fnt = option.font; fnt.setItalic(true); painter->setFont(fnt); QFontMetrics fm(fnt); QString renderedText = text; int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin); int pos = 3 * textMargin + option.fontMetrics.width(display) + iconSize.width(); bool truncd = false; QRect rct = option.rect; if (rct.width() > pos) { rct.setX(rct.x() + pos); if (fm.width(renderedText) > rct.width()) { truncd = true; int points = fm.width("..."); while (!renderedText.isEmpty() && (fm.width(renderedText) + points > rct.width())) renderedText.truncate(renderedText.length() - 1); renderedText += "..."; } painter->drawText(rct, Qt::AlignLeft, renderedText); } else truncd = true; if (truncd) ((QAbstractItemModel *)index.model())->setData(index, QVariant(display + " " + text), Qt::ToolTipRole); else ((QAbstractItemModel *)index.model())->setData(index, QVariant(), Qt::ToolTipRole); painter->restore(); } } }; class DULinesItem : public QTreeWidgetItem { public: DULinesItem(DiskUsage *diskUsageIn, File *fileItem, QTreeWidget * parent, const QString& label1, const QString& label2, const QString& label3) : QTreeWidgetItem(parent), diskUsage(diskUsageIn), file(fileItem) { setText(0, label1); setText(1, label2); setText(2, label3); setTextAlignment(1, Qt::AlignRight); } DULinesItem(DiskUsage *diskUsageIn, File *fileItem, QTreeWidget * parent, QTreeWidgetItem * after, const QString& label1, const QString& label2, const QString& label3) : QTreeWidgetItem(parent, after), diskUsage(diskUsageIn), file(fileItem) { setText(0, label1); setText(1, label2); setText(2, label3); setTextAlignment(1, Qt::AlignRight); } bool operator<(const QTreeWidgetItem &other) const Q_DECL_OVERRIDE { int column = treeWidget() ? treeWidget()->sortColumn() : 0; if (text(0) == "..") return true; const auto *compWith = dynamic_cast< const DULinesItem * >(&other); if (compWith == nullptr) return false; switch (column) { case 0: case 1: return file->size() > compWith->file->size(); default: return text(column) < other.text(column); } } inline File * getFile() { return file; } private: DiskUsage *diskUsage; File *file; }; DULines::DULines(DiskUsage *usage) : KrTreeWidget(usage), diskUsage(usage), refreshNeeded(false), started(false) { setItemDelegate(itemDelegate = new DULinesItemDelegate()); setAllColumnsShowFocus(true); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); setIndentation(10); int defaultSize = QFontMetrics(font()).width("W"); QStringList labels; labels << i18n("Line View"); labels << i18n("Percent"); labels << i18n("Name"); setHeaderLabels(labels); header()->setSectionResizeMode(QHeaderView::Interactive); KConfigGroup group(krConfig, diskUsage->getConfigGroup()); showFileSize = group.readEntry("L Show File Size", true); if (group.hasKey("L State")) header()->restoreState(group.readEntry("L State", QByteArray())); else { setColumnWidth(0, defaultSize * 20); setColumnWidth(1, defaultSize * 6); setColumnWidth(2, defaultSize * 20); } setStretchingColumn(0); header()->setSortIndicatorShown(true); sortItems(1, Qt::AscendingOrder); // toolTip = new DULinesToolTip( diskUsage, viewport(), this ); connect(diskUsage, &DiskUsage::enteringDirectory, this, &DULines::slotDirChanged); connect(diskUsage, &DiskUsage::clearing, this, &DULines::clear); connect(header(), &QHeaderView::sectionResized, this, &DULines::sectionResized); connect(this, &DULines::itemRightClicked, this, &DULines::slotRightClicked); connect(diskUsage, &DiskUsage::changed, this, &DULines::slotChanged); connect(diskUsage, &DiskUsage::deleted, this, &DULines::slotDeleted); started = true; } DULines::~DULines() { KConfigGroup group(krConfig, diskUsage->getConfigGroup()); group.writeEntry("L State", header()->saveState()); delete itemDelegate; } bool DULines::event(QEvent * event) { switch (event->type()) { case QEvent::ToolTip: { - auto *he = static_cast(event); + auto *he = dynamic_cast(event); if (viewport()) { QPoint pos = viewport()->mapFromGlobal(he->globalPos()); QTreeWidgetItem * item = itemAt(pos); int column = columnAt(pos.x()); if (item && column == 1) { File *fileItem = ((DULinesItem *)item)->getFile(); QToolTip::showText(he->globalPos(), diskUsage->getToolTip(fileItem), this); return true; } } } break; default: break; } return KrTreeWidget::event(event); } void DULines::slotDirChanged(Directory *dirEntry) { clear(); QTreeWidgetItem * lastItem = nullptr; if (!(dirEntry->parent() == nullptr)) { lastItem = new QTreeWidgetItem(this); lastItem->setText(0, ".."); lastItem->setIcon(0, Icon("go-up")); lastItem->setFlags(lastItem->flags() & (~Qt::ItemIsSelectable)); } int maxPercent = -1; for (Iterator it = dirEntry->iterator(); it != dirEntry->end(); ++it) { File *item = *it; if (!item->isExcluded() && item->intPercent() > maxPercent) maxPercent = item->intPercent(); } for (Iterator it = dirEntry->iterator(); it != dirEntry->end(); ++it) { File *item = *it; QString fileName = item->name(); if (lastItem == nullptr) lastItem = new DULinesItem(diskUsage, item, this, "", item->percent() + " ", fileName); else lastItem = new DULinesItem(diskUsage, item, this, lastItem, "", item->percent() + " ", fileName); if (item->isExcluded()) lastItem->setHidden(true); int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; lastItem->setIcon(2, diskUsage->getIcon(item->mime())); lastItem->setData(0, Qt::DecorationRole, createPixmap(item->intPercent(), maxPercent, header()->sectionSize(0) - 2 * textMargin)); if (showFileSize) lastItem->setData(2, Qt::UserRole, " [" + KIO::convertSize(item->size()) + ']'); QSize size = lastItem->sizeHint(0); size.setWidth(16); lastItem->setSizeHint(0, size); } if (topLevelItemCount() > 0) { setCurrentItem(topLevelItem(0)); } } QPixmap DULines::createPixmap(int percent, int maxPercent, int maxWidth) { if (percent < 0 || percent > maxPercent || maxWidth < 2 || maxPercent == 0) return QPixmap(); maxWidth -= 2; int actualWidth = maxWidth * percent / maxPercent; if (actualWidth == 0) return QPixmap(); QPen pen; pen.setColor(Qt::black); QPainter painter; int size = QFontMetrics(font()).height() - 2; QRect rect(0, 0, actualWidth, size); QRect frameRect(0, 0, actualWidth - 1, size - 1); QPixmap pixmap(rect.width(), rect.height()); painter.begin(&pixmap); painter.setPen(pen); for (int i = 1; i < actualWidth - 1; i++) { int color = (511 * i / (maxWidth - 1)); if (color < 256) pen.setColor(QColor(255 - color, 255, 0)); else pen.setColor(QColor(color - 256, 511 - color, 0)); painter.setPen(pen); painter.drawLine(i, 1, i, size - 1); } pen.setColor(Qt::black); painter.setPen(pen); if (actualWidth != 1) painter.drawRect(frameRect); else painter.drawLine(0, 0, 0, size); painter.end(); pixmap.detach(); return pixmap; } void DULines::resizeEvent(QResizeEvent * re) { KrTreeWidget::resizeEvent(re); if (started && (re->oldSize() != re->size())) sectionResized(0); } void DULines::sectionResized(int column) { if (topLevelItemCount() == 0 || column != 0) return; Directory * currentDir = diskUsage->getCurrentDir(); if (currentDir == nullptr) return; int maxPercent = -1; for (Iterator it = currentDir->iterator(); it != currentDir->end(); ++it) { File *item = *it; if (!item->isExcluded() && item->intPercent() > maxPercent) maxPercent = item->intPercent(); } QTreeWidgetItemIterator it2(this); while (*it2) { QTreeWidgetItem *lvitem = *it2; if (lvitem->text(0) != "..") { auto *duItem = dynamic_cast< DULinesItem *>(lvitem); if (duItem) { int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; duItem->setData(0, Qt::DecorationRole, createPixmap(duItem->getFile()->intPercent(), maxPercent, header()->sectionSize(0) - 2 * textMargin)); QSize size = duItem->sizeHint(0); size.setWidth(16); duItem->setSizeHint(0, size); } } it2++; } } bool DULines::doubleClicked(QTreeWidgetItem * item) { if (item) { if (item->text(0) != "..") { File *fileItem = ((DULinesItem *)item)->getFile(); if (fileItem->isDir()) diskUsage->changeDirectory(dynamic_cast(fileItem)); return true; } else { auto *upDir = (Directory *)diskUsage->getCurrentDir()->parent(); if (upDir) diskUsage->changeDirectory(upDir); return true; } } return false; } void DULines::mouseDoubleClickEvent(QMouseEvent * e) { if (e || e->button() == Qt::LeftButton) { QPoint vp = viewport()->mapFromGlobal(e->globalPos()); QTreeWidgetItem * item = itemAt(vp); if (doubleClicked(item)) return; } KrTreeWidget::mouseDoubleClickEvent(e); } void DULines::keyPressEvent(QKeyEvent *e) { switch (e->key()) { case Qt::Key_Return : case Qt::Key_Enter : if (doubleClicked(currentItem())) return; break; case Qt::Key_Left : case Qt::Key_Right : case Qt::Key_Up : case Qt::Key_Down : if (e->modifiers() == Qt::ShiftModifier) { e->ignore(); return; } break; case Qt::Key_Delete : e->ignore(); return; } KrTreeWidget::keyPressEvent(e); } void DULines::slotRightClicked(QTreeWidgetItem *item, const QPoint &pos) { File * file = nullptr; if (item && item->text(0) != "..") file = ((DULinesItem *)item)->getFile(); QMenu linesPopup; QAction *act = linesPopup.addAction(i18n("Show file sizes"), this, SLOT(slotShowFileSizes())); act->setChecked(showFileSize); diskUsage->rightClickMenu(pos, file, &linesPopup, i18n("Lines")); } void DULines::slotShowFileSizes() { showFileSize = !showFileSize; slotDirChanged(diskUsage->getCurrentDir()); } File * DULines::getCurrentFile() { QTreeWidgetItem *item = currentItem(); if (item == nullptr || item->text(0) == "..") return nullptr; return ((DULinesItem *)item)->getFile(); } void DULines::slotChanged(File * item) { QTreeWidgetItemIterator it(this); while (*it) { QTreeWidgetItem *lvitem = *it; it++; if (lvitem->text(0) != "..") { auto *duItem = (DULinesItem *)(lvitem); if (duItem->getFile() == item) { setSortingEnabled(false); duItem->setHidden(item->isExcluded()); duItem->setText(1, item->percent()); if (!refreshNeeded) { refreshNeeded = true; QTimer::singleShot(0, this, &DULines::slotRefresh); } break; } } } } void DULines::slotDeleted(File * item) { QTreeWidgetItemIterator it(this); while (*it) { QTreeWidgetItem *lvitem = *it; it++; if (lvitem->text(0) != "..") { auto *duItem = (DULinesItem *)(lvitem); if (duItem->getFile() == item) { delete duItem; break; } } } } void DULines::slotRefresh() { if (refreshNeeded) { refreshNeeded = false; setSortingEnabled(true); sortItems(1, Qt::AscendingOrder); } } diff --git a/krusader/DiskUsage/radialMap/builder.cpp b/krusader/DiskUsage/radialMap/builder.cpp index 937e19e4..a12ba42d 100644 --- a/krusader/DiskUsage/radialMap/builder.cpp +++ b/krusader/DiskUsage/radialMap/builder.cpp @@ -1,149 +1,149 @@ /***************************************************************************** * Copyright (C) 2003-2004 Max Howell * * Copyright (C) 2004-2018 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 "builder.h" #include "Config.h" #include "widget.h" // QtCore #include #include //**** REMOVE NEED FOR the +1 with MAX_RING_DEPTH uses //**** add some angle bounds checking (possibly in Segment ctor? can I delete in a ctor?) //**** this class is a mess RadialMap::Builder::Builder(RadialMap::Map *m, const Directory* const d, bool fast) : m_map(m) , m_root(d) , m_minSize(static_cast((d->size() * 3) / (PI * m->height() - m->MAP_2MARGIN))) , m_depth(&m->m_visibleDepth) { m_signature = new Chain [*m_depth + 1]; if (!fast) { //|| *m_depth == 0 ) //depth 0 is special case usability-wise //**** WHY?! //determine depth rather than use old one findVisibleDepth(d); //sets m_depth } m_map->setRingBreadth(); setLimits(m_map->m_ringBreadth); build(d); m_map->m_signature = m_signature; delete []m_limits; } void RadialMap::Builder::findVisibleDepth(const Directory* const dir, const unsigned int depth) { //**** because I don't use the same minimumSize criteria as in the visual function // this can lead to incorrect visual representation //**** BUT, you can't set those limits until you know m_depth! //**** also this function doesn't check to see if anything is actually visible // it just assumes that when it reaches a new level everything in it is visible // automatically. This isn't right especially as there might be no files in the // dir provided to this function! static uint stopDepth = 0; if (dir == m_root) { stopDepth = *m_depth; *m_depth = 0; } if (*m_depth < depth) *m_depth = depth; if (*m_depth >= stopDepth) return; for (ConstIterator it = dir->constIterator(); it != dir->end(); ++it) if ((*it)->isDir() && (*it)->size() > m_minSize) findVisibleDepth((Directory *)*it, depth + 1); //if no files greater than min size the depth is still recorded } void RadialMap::Builder::setLimits(const uint &b) //b = breadth? { double size3 = m_root->size() * 3; double pi2B = PI * 2 * b; m_limits = new FileSize [*m_depth + 1]; //FIXME delete! for (unsigned int d = 0; d <= *m_depth; ++d) m_limits[d] = (FileSize)(size3 / (double)(pi2B * (d + 1))); //min is angle that gives 3px outer diameter for that depth } //**** segments currently overlap at edges (i.e. end of first is start of next) bool RadialMap::Builder::build(const Directory* const dir, const unsigned int depth, unsigned int a_start, const unsigned int a_end) { //first iteration: dir == m_root if (dir->fileCount() == 0) //we do fileCount rather than size to avoid chance of divide by zero later return false; FileSize hiddenSize = 0; uint hiddenFileCount = 0; for (ConstIterator it = dir->constIterator(); it != dir->end(); ++it) { if ((*it)->size() > m_limits[depth]) { auto a_len = (unsigned int)(5760 * ((double)(*it)->size() / (double)m_root->size())); auto *s = new Segment(*it, a_start, a_len); (m_signature + depth)->append(s); if ((*it)->isDir()) { if (depth != *m_depth) { //recurse s->m_hasHiddenChildren = build((Directory*) * it, depth + 1, a_start, a_start + a_len); } else s->m_hasHiddenChildren = true; } a_start += a_len; //**** should we add 1? } else { hiddenSize += (*it)->size(); if ((*it)->isDir()) //**** considered virtual, but dir wouldn't count itself! - hiddenFileCount += static_cast(*it)->fileCount(); //need to add one to count the dir as well + hiddenFileCount += dynamic_cast(*it)->fileCount(); //need to add one to count the dir as well ++hiddenFileCount; } } if (hiddenFileCount == dir->fileCount() && !Config::showSmallFiles) return true; else if ((Config::showSmallFiles && hiddenSize > m_limits[depth]) || (depth == 0 && (hiddenSize > dir->size() / 8)) /*|| > size() * 0.75*/) { //append a segment for unrepresented space - a "fake" segment const QString s = i18np("%1 file: ~ %2", "%1 files: ~ %2", QLocale().toString(hiddenFileCount), File::humanReadableSize(hiddenSize / hiddenFileCount)); (m_signature + depth)->append(new Segment(new File(s, hiddenSize), a_start, a_end - a_start, true)); } return false; } diff --git a/krusader/DiskUsage/radialMap/segmentTip.cpp b/krusader/DiskUsage/radialMap/segmentTip.cpp index 9e4eeaac..38e67b9d 100644 --- a/krusader/DiskUsage/radialMap/segmentTip.cpp +++ b/krusader/DiskUsage/radialMap/segmentTip.cpp @@ -1,187 +1,187 @@ /***************************************************************************** * Copyright (C) 2003-2004 Max Howell * * Copyright (C) 2004-2018 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 "segmentTip.h" // QtCore #include // QtGui #include // QtWidgets #include //installing eventFilters #include #include //for its palette #include #include "fileTree.h" namespace RadialMap { SegmentTip::SegmentTip(uint h) : QWidget(nullptr, Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint) , m_cursorHeight(-h) { setAttribute(Qt::WA_NoSystemBackground, true); } void SegmentTip::moveto(QPoint p, QWidget &canvas, bool placeAbove) { //**** this function is very slow and seems to be visibly influenced by operations like mapFromGlobal() (who knows why!) // ** so any improvements are much desired //TODO uints could improve the class p.rx() -= rect().center().x(); p.ry() -= (placeAbove ? 8 + height() : m_cursorHeight - 8); const QRect screen = QApplication::desktop()->screenGeometry(parentWidget()); const int x = p.x(); const int y = p.y(); const int x2 = x + width(); const int y2 = y + height(); //how's it ever gunna get below screen height?! (well you never know I spose) const int sw = screen.width(); const int sh = screen.height(); if (x < 0) p.setX(0); if (y < 0) p.setY(0); if (x2 > sw) p.rx() -= x2 - sw; if (y2 > sh) p.ry() -= y2 - sh; //I'm using this QPoint to determine where to offset the bitBlt in m_pixmap QPoint offset = canvas.mapToGlobal(QPoint()) - p; if (offset.x() < 0) offset.setX(0); if (offset.y() < 0) offset.setY(0); const QRect alphaMaskRect(canvas.mapFromGlobal(p), size()); const QRect intersection(alphaMaskRect.intersected(canvas.rect())); m_pixmap = QPixmap(size()); //move to updateTip once you are sure it can never be null //bitBlt( &m_pixmap, offset, &canvas, intersection, Qt::CopyROP ); QPainter(&m_pixmap).drawPixmap(offset, QPixmap::grabWidget(&canvas, intersection)); QColor col = QToolTip::palette().color(QPalette::Active, QPalette::Window); col.setAlpha(153); // 0.6 QRect rct = rect(); if (rct.width()) rct.setWidth(rct.width() - 1); if (rct.height()) rct.setHeight(rct.height() - 1); QPainter paint(&m_pixmap); paint.setPen(Qt::black); paint.setBrush(col); paint.drawRect(rct); paint.end(); paint.begin(&m_pixmap); paint.drawText(rect(), Qt::AlignCenter, m_text); paint.end(); p += screen.topLeft(); //for Xinerama users move(x, y); show(); repaint(); } void SegmentTip::updateTip(const File* const file, const Directory* const root) { const QString s1 = file->fullPath(root); QString s2 = file->humanReadableSize(); QLocale loc; const uint MARGIN = 3; const uint pc = 100 * file->size() / root->size(); uint maxw = 0; uint h = fontMetrics().height() * 2 + 2 * MARGIN; if (pc > 0) s2 += QString(" (%1%)").arg(loc.toString(pc)); m_text = s1; m_text += '\n'; m_text += s2; if (file->isDir()) { - double files = static_cast(file)->fileCount(); + double files = dynamic_cast(file)->fileCount(); const auto pc = uint((100 * files) / (double)root->fileCount()); QString s3 = i18n("Files: %1", loc.toString(files, 'f', 0)); if (pc > 0) s3 += QString(" (%1%)").arg(loc.toString(pc)); maxw = fontMetrics().width(s3); h += fontMetrics().height(); m_text += '\n'; m_text += s3; } uint w = fontMetrics().width(s1); if (w > maxw) maxw = w; w = fontMetrics().width(s2); if (w > maxw) maxw = w; resize(maxw + 2 * MARGIN, h); } bool SegmentTip::event(QEvent *e) { switch (e->type()) { case QEvent::Show: qApp->installEventFilter(this); break; case QEvent::Hide: qApp->removeEventFilter(this); break; case QEvent::Paint: { // bitBlt( this, 0, 0, &m_pixmap ); QPainter(this).drawPixmap(0, 0, m_pixmap); return true; } default: ; } return false/*QWidget::event( e )*/; } bool SegmentTip::eventFilter(QObject*, QEvent *e) { switch (e->type()) { case QEvent::Leave: // case QEvent::MouseButtonPress: // case QEvent::MouseButtonRelease: case QEvent::KeyPress: case QEvent::KeyRelease: case QEvent::FocusIn: case QEvent::FocusOut: case QEvent::Wheel: hide(); //FALL THROUGH default: return false; //allow this event to passed to target } } } //namespace RadialMap diff --git a/krusader/DiskUsage/radialMap/widgetEvents.cpp b/krusader/DiskUsage/radialMap/widgetEvents.cpp index 9fcd05dd..5d111304 100644 --- a/krusader/DiskUsage/radialMap/widgetEvents.cpp +++ b/krusader/DiskUsage/radialMap/widgetEvents.cpp @@ -1,252 +1,252 @@ /***************************************************************************** * Copyright (C) 2003-2004 Max Howell * * Copyright (C) 2004-2018 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 "fileTree.h" #include "radialMap.h" //class Segment #include "widget.h" #include "../../icon.h" #include // QtCore #include //::resizeEvent() // QtGui #include #include #include #include // QtWidgets #include //QApplication::setOverrideCursor() #include #include #include #include #include #include #include #include void RadialMap::Widget::resizeEvent(QResizeEvent*) { if (m_map.resize(rect())) { m_timer.setSingleShot(true); m_timer.start(500); //will cause signature to rebuild for new size } //always do these as they need to be initialised on creation m_offset.rx() = (width() - m_map.width()) / 2; m_offset.ry() = (height() - m_map.height()) / 2; } void RadialMap::Widget::paintEvent(QPaintEvent*) { //bltBit for some Qt setups will bitBlt _after_ the labels are painted. Which buggers things up! //shame as bitBlt is faster, possibly Qt bug? Should report the bug? - seems to be race condition //bitBlt( this, m_offset, &m_map ); QPainter paint(this); paint.drawPixmap(m_offset, m_map); //vertical strips if (m_map.width() < width()) { paint.eraseRect(0, 0, m_offset.x(), height()); paint.eraseRect(m_map.width() + m_offset.x(), 0, m_offset.x() + 1, height()); } //horizontal strips if (m_map.height() < height()) { paint.eraseRect(0, 0, width(), m_offset.y()); paint.eraseRect(0, m_map.height() + m_offset.y(), width(), m_offset.y() + 1); } //exploded labels if (!m_map.isNull() && !m_timer.isActive()) paintExplodedLabels(paint); } const RadialMap::Segment* RadialMap::Widget::segmentAt(QPoint &e) const { //determine which segment QPoint e is above e -= m_offset; if (e.x() <= m_map.width() && e.y() <= m_map.height()) { //transform to cartesian coords e.rx() -= m_map.width() / 2; //should be an int e.ry() = m_map.height() / 2 - e.y(); double length = std::hypot(e.x(), e.y()); if (length >= m_map.m_innerRadius) { //not hovering over inner circle uint depth = ((int)length - m_map.m_innerRadius) / m_map.m_ringBreadth; if (depth <= m_map.m_visibleDepth) { //**** do earlier since you can //** check not outside of range //vector calculation, reduces to simple trigonometry //cos angle = (aibi + ajbj) / albl //ai = x, bi=1, aj=y, bj=0 //cos angle = x / (length) auto a = (uint)(acos((double)e.x() / length) * 916.736); //916.7324722 = #radians in circle * 16 //acos only understands 0-180 degrees if (e.y() < 0) a = 5760 - a; #define ring (m_map.m_signature + depth) for (ConstIterator it = ring->constIterator(); it != ring->end(); ++it) if ((*it)->intersects(a)) return *it; #undef ring } } else return m_rootSegment; //hovering over inner circle } return nullptr; } void RadialMap::Widget::mouseMoveEvent(QMouseEvent *e) { //set m_focus to what we hover over, update UI if it's a new segment Segment const * const oldFocus = m_focus; QPoint p = e->pos(); m_focus = segmentAt(p); //NOTE p is passed by non-const reference if (m_focus && m_focus->file() != m_tree) { if (m_focus != oldFocus) { //if not same as last time setCursor(QCursor(Qt::PointingHandCursor)); m_tip.updateTip(m_focus->file(), m_tree); emit mouseHover(m_focus->file()->fullPath()); //repaint required to update labels now before transparency is generated repaint(); } // updates tooltip pseudo-transparent background m_tip.moveto(e->globalPos(), *this, (p.y() < 0)); } else if (oldFocus && oldFocus->file() != m_tree) { unsetCursor(); m_tip.hide(); update(); emit mouseHover(QString()); } } void RadialMap::Widget::mousePressEvent(QMouseEvent *e) { //m_tip is hidden already by event filter //m_focus is set correctly (I've been strict, I assure you it is correct!) if (m_focus && !m_focus->isFake()) { const QUrl url = Widget::url(m_focus->file()); const bool isDir = m_focus->file()->isDir(); if (e->button() == Qt::RightButton) { QMenu popup; popup.setTitle(m_focus->file()->fullPath(m_tree)); QAction * actKonq = nullptr, * actKonsole = nullptr, *actViewMag = nullptr, * actFileOpen = nullptr, * actEditDel = nullptr; if (isDir) { actKonq = popup.addAction(Icon("system-file-manager"), i18n("Open File Manager Here")); if (url.scheme() == "file") actKonsole = popup.addAction(Icon("utilities-terminal"), i18n("Open Terminal Here")); if (m_focus->file() != m_tree) { popup.addSeparator(); actViewMag = popup.addAction(Icon("zoom-original"), i18n("&Center Map Here")); } } else actFileOpen = popup.addAction(Icon("document-open"), i18n("&Open")); popup.addSeparator(); actEditDel = popup.addAction(Icon("edit-delete"), i18n("&Delete")); QAction * result = popup.exec(e->globalPos()); if (result == nullptr) result = (QAction *) - 1; // sanity if (result == actKonq) //KRun::runCommand will show an error message if there was trouble KRun::runCommand(QString("kfmclient openURL '%1'").arg(url.url()), this); else if (result == actKonsole) KRun::runCommand(QString("konsole --workdir '%1'").arg(url.url()), this); else if (result == actViewMag || result == actFileOpen) goto sectionTwo; else if (result == actEditDel) { const QUrl url = Widget::url(m_focus->file()); const QString message = (m_focus->file()->isDir() ? i18n("The folder at '%1' will be recursively and permanently deleted.", url.toDisplayString()) : i18n("'%1' will be permanently deleted.", url.toDisplayString())); const int userIntention = KMessageBox::warningContinueCancel(this, message, QString(), KStandardGuiItem::del()); if (userIntention == KMessageBox::Continue) { KIO::Job *job = KIO::del(url); - auto *ui = static_cast(job->uiDelegate()); + auto *ui = dynamic_cast(job->uiDelegate()); ui->setWindow(this); connect(job, &KIO::Job::result, this, &Widget::deleteJobFinished); QApplication::setOverrideCursor(Qt::BusyCursor); } } else //ensure m_focus is set for new mouse position sendFakeMouseEvent(); } else { sectionTwo: const QRect rect(e->x() - 20, e->y() - 20, 40, 40); m_tip.hide(); //user expects this if (!isDir || e->button() == Qt::MidButton) { #if 0 // TODO: PORTME KIconEffect::visualActivate(this, rect); #endif new KRun(url, this, true); //FIXME see above } else if (m_focus->file() != m_tree) { //is left mouse button #if 0 // TODO: PORTME KIconEffect::visualActivate(this, rect); #endif emit activated(url); //activate first, this will cause UI to prepare itself if (m_focus) createFromCache((Directory *)m_focus->file()); } } } } void RadialMap::Widget::deleteJobFinished(KJob *job) { QApplication::restoreOverrideCursor(); if (!job->error()) invalidate(); else job->uiDelegate()->showErrorMessage(); } diff --git a/krusader/FileSystem/defaultfilesystem.cpp b/krusader/FileSystem/defaultfilesystem.cpp index a595a325..37ab5e93 100644 --- a/krusader/FileSystem/defaultfilesystem.cpp +++ b/krusader/FileSystem/defaultfilesystem.cpp @@ -1,412 +1,412 @@ /***************************************************************************** * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2004-2018 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 "defaultfilesystem.h" // QtCore #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fileitem.h" #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #include "../JobMan/krjob.h" DefaultFileSystem::DefaultFileSystem(): FileSystem(), _watcher() { _type = FS_DEFAULT; } void DefaultFileSystem::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode, bool showProgressInfo, JobMan::StartMode startMode) { // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::JobFlags flags = showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KrJob *krJob = KrJob::createCopyJob(mode, urls, dest, flags); // destination can be a full path with filename when copying/moving a single file const QUrl destDir = dest.adjusted(QUrl::RemoveFilename); connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJobToDestination(job, destDir); }); if (mode == KIO::CopyJob::Move) { // notify source about removed files connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJobToSources(job, urls); }); } krJobMan->manageJob(krJob, startMode); } void DefaultFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) { qDebug() << "destination=" << destination; // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::DropJob *job = KIO::drop(event, dest); #if KIO_VERSION >= QT_VERSION_CHECK(5, 30, 0) // NOTE: a DropJob "starts" with showing a menu. If the operation is chosen (copy/move/link) // the actual CopyJob starts automatically - we cannot manage the start of the CopyJob (see // documentation for KrJob) connect(job, &KIO::DropJob::copyJobStarted, this, [=](KIO::CopyJob *kJob) { connectJobToDestination(job, dest); // now we have to refresh the destination KrJob *krJob = KrJob::createDropJob(job, kJob); krJobMan->manageStartedJob(krJob, kJob); if (kJob->operationMode() == KIO::CopyJob::Move) { // notify source about removed files connectJobToSources(kJob, kJob->srcUrls()); } }); #else // NOTE: DropJob does not provide information about the actual user choice // (move/copy/link/abort). We have to assume the worst (move) connectJobToDestination(job, dest); connectJobToSources(job, KUrlMimeData::urlsFromMimeData(event->mimeData())); #endif } void DefaultFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, const QString &dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); const QString scheme = destination.scheme(); if (scheme == "tar" || scheme == "zip" || scheme == "krarc") { if (QDir(destination.path()).exists()) // if we get out from the archive change the protocol destination.setScheme("file"); } } destination = ensureTrailingSlash(destination); // destination is always a directory copyFiles(fileUrls, destination, mode); } void DefaultFileSystem::mkDir(const QString &name) { KJob *job; if (name.contains('/')) { job = KIO::mkpath(getUrl(name)); } else { job = KIO::mkdir(getUrl(name)); } connectJobToDestination(job, currentDirectory()); } void DefaultFileSystem::rename(const QString &oldName, const QString &newName) { const QUrl oldUrl = getUrl(oldName); const QUrl newUrl = getUrl(newName); KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); connectJobToDestination(job, currentDirectory()); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); } QUrl DefaultFileSystem::getUrl(const QString& name) const { // NOTE: on non-local fs file URL does not have to be path + name! FileItem *fileItem = getFileItem(name); if (fileItem) return fileItem->getUrl(); QUrl absoluteUrl(_currentDirectory); if (name.startsWith('/')) { absoluteUrl.setPath(name); } else { absoluteUrl.setPath(absoluteUrl.path() + '/' + name); } return absoluteUrl; } void DefaultFileSystem::updateFilesystemInfo() { if (!KConfigGroup(krConfig, "Look&Feel").readEntry("ShowSpaceInformation", true)) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("Space information disabled"), "", 0, 0); return; } // TODO get space info for trash:/ with KIO spaceInfo job if (!_currentDirectory.isLocalFile()) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("No space information on non-local filesystems"), "", 0, 0); return; } const QString path = _currentDirectory.path(); const KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(path); if (!info.isValid()) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("Space information unavailable"), "", 0, 0); return; } _mountPoint = info.mountPoint(); const KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(path); const QString fsType = mountPoint ? mountPoint->mountType() : ""; emit fileSystemInfoChanged("", fsType, info.size(), info.available()); } // ==== protected ==== bool DefaultFileSystem::refreshInternal(const QUrl &directory, bool onlyScan) { qDebug() << "refresh internal to URL=" << directory.toDisplayString(); if (!KProtocolManager::supportsListing(directory)) { emit error(i18n("Protocol not supported by Krusader:\n%1", directory.url())); return false; } delete _watcher; // stop watching the old dir if (directory.isLocalFile()) { qDebug() << "start local refresh to URL=" << directory.toDisplayString(); // we could read local directories with KIO but using Qt is a lot faster! return refreshLocal(directory, onlyScan); } _currentDirectory = cleanUrl(directory); // start the listing job KIO::ListJob *job = KIO::listDir(_currentDirectory, KIO::HideProgressInfo, showHiddenFiles()); connect(job, &KIO::ListJob::entries, this, &DefaultFileSystem::slotAddFiles); connect(job, &KIO::ListJob::redirection, this, &DefaultFileSystem::slotRedirection); connect(job, &KIO::ListJob::permanentRedirection, this, &DefaultFileSystem::slotRedirection); connect(job, &KIO::Job::result, this, &DefaultFileSystem::slotListResult); // ensure connection credentials are asked only once if(!parentWindow.isNull()) { - auto *ui = static_cast(job->uiDelegate()); + auto *ui = dynamic_cast(job->uiDelegate()); ui->setWindow(parentWindow); } emit refreshJobStarted(job); _listError = false; // ugly: we have to wait here until the list job is finished QEventLoop eventLoop; connect(job, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() return !_listError; } // ==== protected slots ==== void DefaultFileSystem::slotListResult(KJob *job) { qDebug() << "got list result"; if (job && job->error()) { // we failed to refresh _listError = true; qDebug() << "error=" << job->errorString() << "; text=" << job->errorText(); emit error(job->errorString()); // display error message (in panel) } } void DefaultFileSystem::slotAddFiles(KIO::Job *, const KIO::UDSEntryList& entries) { for (const KIO::UDSEntry entry : entries) { FileItem *fileItem = FileSystem::createFileItemFromKIO(entry, _currentDirectory); if (fileItem) { addFileItem(fileItem); } } } void DefaultFileSystem::slotRedirection(KIO::Job *job, const QUrl &url) { qDebug() << "redirection to URL=" << url.toDisplayString(); // some protocols (zip, tar) send redirect to local URL without scheme const QUrl newUrl = preferLocalUrl(url); if (newUrl.scheme() != _currentDirectory.scheme()) { // abort and start over again, // some protocols (iso, zip, tar) do this on transition to local fs job->kill(); _isRefreshing = false; refresh(newUrl); return; } _currentDirectory = cleanUrl(newUrl); } void DefaultFileSystem::slotWatcherCreated(const QString& path) { qDebug() << "path created (doing nothing): " << path; } void DefaultFileSystem::slotWatcherDirty(const QString& path) { qDebug() << "path dirty: " << path; if (path == realPath()) { // this happens // 1. if a directory was created/deleted/renamed inside this directory. // 2. during and after a file operation (create/delete/rename/touch) inside this directory // KDirWatcher doesn't reveal the name of changed directories and we have to refresh. // (QFileSystemWatcher in Qt5.7 can't help here either) refresh(); return; } const QString name = QUrl::fromLocalFile(path).fileName(); FileItem *fileItem = getFileItem(name); if (!fileItem) { qWarning() << "file not found (unexpected), path=" << path; // this happens at least for cifs mounted filesystems: when a new file is created, a dirty // signal with its file path but no other signals are sent (buggy behaviour of KDirWatch) refresh(); return; } // we have an updated file.. FileItem *newFileItem = createLocalFileItem(name); addFileItem(newFileItem); emit updatedFileItem(newFileItem); delete fileItem; } void DefaultFileSystem::slotWatcherDeleted(const QString& path) { qDebug() << "path deleted: " << path; if (path != _currentDirectory.toLocalFile()) { // ignore deletion of files here, a 'dirty' signal will be send anyway return; } // the current directory was deleted. Try a refresh, which will fail. An error message will // be emitted and the empty (non-existing) directory remains. refresh(); } bool DefaultFileSystem::refreshLocal(const QUrl &directory, bool onlyScan) { const QString path = KrServices::urlToLocalPath(directory); #ifdef Q_WS_WIN if (!path.contains("/")) { // change C: to C:/ path = path + QString("/"); } #endif // check if the new directory exists if (!QDir(path).exists()) { emit error(i18n("The folder %1 does not exist.", path)); return false; } // mount if needed emit aboutToOpenDir(path); // set the current directory... _currentDirectory = directory; _currentDirectory.setPath(QDir::cleanPath(_currentDirectory.path())); // Note: we are using low-level Qt functions here. // It's around twice as fast as using the QDir class. QT_DIR* dir = QT_OPENDIR(path.toLocal8Bit()); if (!dir) { emit error(i18n("Cannot open the folder %1.", path)); return false; } // change directory to the new directory const QString savedDir = QDir::currentPath(); if (!QDir::setCurrent(path)) { emit error(i18nc("%1=folder path", "Access to %1 denied", path)); QT_CLOSEDIR(dir); return false; } QT_DIRENT* dirEnt; QString name; const bool showHidden = showHiddenFiles(); while ((dirEnt = QT_READDIR(dir)) != nullptr) { name = QString::fromLocal8Bit(dirEnt->d_name); // show hidden files? if (!showHidden && name.left(1) == ".") continue; // we don't need the "." and ".." entries if (name == "." || name == "..") continue; FileItem* temp = createLocalFileItem(name); addFileItem(temp); } // clean up QT_CLOSEDIR(dir); QDir::setCurrent(savedDir); if (!onlyScan) { // start watching the new dir for file changes _watcher = new KDirWatch(this); // if the current dir is a link path the watcher needs to watch the real path - and signal // parameters will be the real path _watcher->addDir(realPath(), KDirWatch::WatchFiles); connect(_watcher.data(), &KDirWatch::dirty, this, &DefaultFileSystem::slotWatcherDirty); // NOTE: not connecting 'created' signal. A 'dirty' is send after that anyway //connect(_watcher.data(), &KDirWatch::created, this, &DefaultFileSystem::slotWatcherCreated); connect(_watcher.data(), &KDirWatch::deleted, this, &DefaultFileSystem::slotWatcherDeleted); _watcher->startScan(false); } return true; } FileItem *DefaultFileSystem::createLocalFileItem(const QString &name) { return FileSystem::createLocalFileItem(name, _currentDirectory.path()); } QString DefaultFileSystem::realPath() { // NOTE: current dir must exist return QDir(_currentDirectory.toLocalFile()).canonicalPath(); } QUrl DefaultFileSystem::resolveRelativePath(const QUrl &url) { // if e.g. "/tmp/bin" is a link to "/bin", // resolve "/tmp/bin/.." to "/tmp" and not "/" return url.adjusted(QUrl::NormalizePathSegments); } diff --git a/krusader/FileSystem/sizecalculator.cpp b/krusader/FileSystem/sizecalculator.cpp index 1fe208d3..40403cfc 100644 --- a/krusader/FileSystem/sizecalculator.cpp +++ b/krusader/FileSystem/sizecalculator.cpp @@ -1,184 +1,184 @@ /***************************************************************************** * Copyright (C) 2017-2018 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 "sizecalculator.h" // QtCore #include #include #include #include "fileitem.h" #include "virtualfilesystem.h" SizeCalculator::SizeCalculator(const QList &urls) : QObject(nullptr), m_urls(urls), m_nextUrls(urls), m_totalSize(0), m_totalFiles(0), m_totalDirs(0), m_canceled(false), m_directorySizeJob(nullptr) { QTimer::singleShot(0, this, &SizeCalculator::start); } SizeCalculator::~SizeCalculator() { if (m_directorySizeJob) m_directorySizeJob->kill(); } void SizeCalculator::start() { emit started(); nextUrl(); } void SizeCalculator::add(const QUrl &url) { m_urls.append(url); m_nextUrls.append(url); emitProgress(); } KIO::filesize_t SizeCalculator::totalSize() const { return m_totalSize + (m_directorySizeJob ? m_directorySizeJob->totalSize() : 0); } unsigned long SizeCalculator::totalFiles() const { return m_totalFiles + (m_directorySizeJob ? m_directorySizeJob->totalFiles() : 0); } unsigned long SizeCalculator::totalDirs() const { return m_totalDirs + (m_directorySizeJob ? m_directorySizeJob->totalSubdirs() : 0); } void SizeCalculator::cancel() { m_canceled = true; if (m_directorySizeJob) m_directorySizeJob->kill(); done(); } void SizeCalculator::nextUrl() { if (m_canceled) return; emitProgress(); if (!m_currentUrl.isEmpty()) emit calculated(m_currentUrl, m_currentUrlSize); m_currentUrlSize = 0; if (m_nextUrls.isEmpty()) { done(); return; } m_currentUrl = m_nextUrls.takeFirst(); if (m_currentUrl.scheme() == "virt") { // calculate size of all files/directories in this virtual directory auto *fs = new VirtualFileSystem(); if (!fs->scanDir(m_currentUrl)) { qWarning() << "cannot scan virtual FS, URL=" << m_currentUrl.toDisplayString(); nextUrl(); return; } for (FileItem *file : fs->fileItems()) m_nextSubUrls << file->getUrl(); delete fs; } else { m_nextSubUrls << m_currentUrl; } nextSubUrl(); } void SizeCalculator::nextSubUrl() { if (m_canceled) return; if (m_nextSubUrls.isEmpty()) { nextUrl(); return; } const QUrl url = m_nextSubUrls.takeFirst(); KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); connect(statJob, &KIO::Job::result, this, &SizeCalculator::slotStatResult); } void SizeCalculator::slotStatResult(KJob *job) { if (m_canceled) return; if (job->error()) { qWarning() << "stat job failed, error=" << job->error() << "; error string=" << job->errorString(); nextSubUrl(); return; } - const KIO::StatJob *statJob = static_cast(job); + const KIO::StatJob *statJob = dynamic_cast(job); const QUrl url = statJob->url(); const KFileItem kfi(statJob->statResult(), url, true); if (kfi.isFile() || kfi.isLink()) { m_totalFiles++; m_totalSize += kfi.size(); m_currentUrlSize += kfi.size(); nextSubUrl(); return; } // URL should be a directory, we are always counting the directory itself m_totalDirs++; m_directorySizeJob = KIO::directorySize(url); connect(m_directorySizeJob.data(), &KIO::Job::result, this, &SizeCalculator::slotDirectorySizeResult); } void SizeCalculator::slotDirectorySizeResult(KJob *) { if (!m_directorySizeJob->error()) { m_totalSize += m_directorySizeJob->totalSize(); // do not count filesystem size of empty directories for this current directory m_currentUrlSize += m_directorySizeJob->totalFiles() == 0 ? 0 : m_directorySizeJob->totalSize(); m_totalFiles += m_directorySizeJob->totalFiles(); m_totalDirs += m_directorySizeJob->totalSubdirs(); } m_directorySizeJob = nullptr; nextSubUrl(); } void SizeCalculator::done() { emit finished(m_canceled); deleteLater(); } void SizeCalculator::emitProgress() { emit progressChanged((m_urls.length() - (float)m_nextUrls.length()) / m_urls.length() * 100); } diff --git a/krusader/FileSystem/virtualfilesystem.cpp b/krusader/FileSystem/virtualfilesystem.cpp index a331806f..227cf468 100644 --- a/krusader/FileSystem/virtualfilesystem.cpp +++ b/krusader/FileSystem/virtualfilesystem.cpp @@ -1,355 +1,355 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * Copyright (C) 2004-2018 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 "virtualfilesystem.h" // QtCore #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "fileitem.h" #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #define VIRTUALFILESYSTEM_DB "virtualfilesystem.db" QHash *> VirtualFileSystem::_virtFilesystemDict; QHash VirtualFileSystem::_metaInfoDict; VirtualFileSystem::VirtualFileSystem() : FileSystem() { if (_virtFilesystemDict.isEmpty()) { restore(); } _type = FS_VIRTUAL; } void VirtualFileSystem::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode /*mode*/, bool /*showProgressInfo*/, JobMan::StartMode /*startMode*/) { const QString dir = QDir(destination.path()).absolutePath().remove('/'); if (dir.isEmpty()) { showError(i18n("You cannot copy files directly to the 'virt:/' folder.\n" "You can create a sub folder and copy your files into it.")); return; } if (!_virtFilesystemDict.contains(dir)) { mkDirInternal(dir); } QList *urlList = _virtFilesystemDict[dir]; for (const QUrl &fileUrl : urls) { if (!urlList->contains(fileUrl)) { urlList->push_back(fileUrl); } } emit fileSystemChanged(QUrl("virt:///" + dir), false); // may call refresh() } void VirtualFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) { const QList &urls = KUrlMimeData::urlsFromMimeData(event->mimeData()); // dropping on virtual filesystem is always copy operation copyFiles(urls, destination); } void VirtualFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode /*mode*/, const QString &dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); } copyFiles(fileUrls, destination); } void VirtualFileSystem::remove(const QStringList &fileNames) { const QString parentDir = currentDir(); if (parentDir == "/") { // remove virtual directory for (const QString &filename : fileNames) { _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + filename)); delete _virtFilesystemDict[filename]; _virtFilesystemDict.remove(filename); _metaInfoDict.remove(filename); } } else { // remove the URLs from the collection for (const QString name : fileNames) { if (_virtFilesystemDict.find(parentDir) != _virtFilesystemDict.end()) { QList *urlList = _virtFilesystemDict[parentDir]; urlList->removeAll(getUrl(name)); } } } emit fileSystemChanged(currentDirectory(), true); // will call refresh() } QUrl VirtualFileSystem::getUrl(const QString &name) const { FileItem *item = getFileItem(name); if (!item) { return QUrl(); // not found } return item->getUrl(); } void VirtualFileSystem::mkDir(const QString &name) { if (currentDir() != "/") { showError(i18n("Creating new folders is allowed only in the 'virt:/' folder.")); return; } mkDirInternal(name); emit fileSystemChanged(currentDirectory(), false); // will call refresh() } void VirtualFileSystem::rename(const QString &fileName, const QString &newName) { FileItem *item = getFileItem(fileName); if (!item) return; // not found if (currentDir() == "/") { // rename virtual directory _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + newName)); _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + fileName)); _virtFilesystemDict.insert(newName, _virtFilesystemDict.take(fileName)); refresh(); return; } // newName can be a (local) path or a full url QUrl dest(newName); if (dest.scheme().isEmpty()) dest.setScheme("file"); // add the new url to the list // the list is refreshed, only existing files remain - // so we don't have to worry if the job was successful _virtFilesystemDict[currentDir()]->append(dest); KIO::Job *job = KIO::moveAs(item->getUrl(), dest, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, false); }); connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(currentDirectory(), false); }); } bool VirtualFileSystem::canMoveToTrash(const QStringList &fileNames) const { if (isRoot()) return false; for (const QString fileName : fileNames) { if (!getUrl(fileName).isLocalFile()) { return false; } } return true; } void VirtualFileSystem::setMetaInformation(const QString &info) { _metaInfoDict[currentDir()] = info; } // ==== protected ==== bool VirtualFileSystem::refreshInternal(const QUrl &directory, bool onlyScan) { _currentDirectory = cleanUrl(directory); _currentDirectory.setHost(""); // remove invalid subdirectories _currentDirectory.setPath('/' + _currentDirectory.path().remove('/')); if (!_virtFilesystemDict.contains(currentDir())) { if (onlyScan) { return false; // virtual dir does not exist } else { // Silently creating non-existing directories here. The search and locate tools // expect this. And the user can enter some directory and it will be created. mkDirInternal(currentDir()); save(); // infinite loop possible // emit fileSystemChanged(currentDirectory()); return true; } } QList *urlList = _virtFilesystemDict[currentDir()]; if (!onlyScan) { const QString metaInfo = _metaInfoDict[currentDir()]; emit fileSystemInfoChanged(metaInfo.isEmpty() ? i18n("Virtual filesystem") : metaInfo, "", 0, 0); } QMutableListIterator it(*urlList); while (it.hasNext()) { const QUrl url = it.next(); FileItem *item = createFileItem(url); if (!item) { // remove URL from the list for a file that no longer exists it.remove(); } else { addFileItem(item); } } save(); return true; } // ==== private ==== void VirtualFileSystem::mkDirInternal(const QString &name) { // clean path, consistent with currentDir() QString dirName = name; dirName = dirName.remove('/'); if (dirName.isEmpty()) dirName = '/'; _virtFilesystemDict.insert(dirName, new QList()); _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + dirName)); } void VirtualFileSystem::save() { KConfig *db = &VirtualFileSystem::getVirtDB(); db->deleteGroup("virt_db"); KConfigGroup group(db, "virt_db"); QHashIterator *> it(_virtFilesystemDict); while (it.hasNext()) { it.next(); QList *urlList = it.value(); QList::iterator url; QStringList entry; for (url = urlList->begin(); url != urlList->end(); ++url) { entry.append((*url).toDisplayString()); } // KDE 4.0 workaround: 'Item_' prefix is added as KConfig fails on 1 char names (such as /) group.writeEntry("Item_" + it.key(), entry); group.writeEntry("MetaInfo_" + it.key(), _metaInfoDict[it.key()]); } db->sync(); } void VirtualFileSystem::restore() { KConfig *db = &VirtualFileSystem::getVirtDB(); const KConfigGroup dbGrp(db, "virt_db"); const QMap map = db->entryMap("virt_db"); QMapIterator it(map); while (it.hasNext()) { it.next(); // KDE 4.0 workaround: check and remove 'Item_' prefix if (!it.key().startsWith(QLatin1String("Item_"))) continue; const QString key = it.key().mid(5); const QList urlList = KrServices::toUrlList(dbGrp.readEntry(it.key(), QStringList())); _virtFilesystemDict.insert(key, new QList(urlList)); _metaInfoDict.insert(key, dbGrp.readEntry("MetaInfo_" + key, QString())); } if (!_virtFilesystemDict["/"]) { // insert root element if missing for some reason _virtFilesystemDict.insert("/", new QList()); } } FileItem *VirtualFileSystem::createFileItem(const QUrl &url) { if (url.scheme() == "virt") { // return a virtual directory in root QString path = url.path().mid(1); if (path.isEmpty()) path = '/'; return FileItem::createVirtualDir(path, url); } const QUrl directory = url.adjusted(QUrl::RemoveFilename); if (url.isLocalFile()) { QFileInfo file(url.path()); return file.exists() ? FileSystem::createLocalFileItem(url.fileName(), directory.path(), true) : nullptr; } KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); connect(statJob, &KIO::Job::result, this, &VirtualFileSystem::slotStatResult); // ugly: we have to wait here until the stat job is finished QEventLoop eventLoop; connect(statJob, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() if (_fileEntry.count() == 0) { return nullptr; // stat job failed } if (!_fileEntry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)) { // TODO this also happens for FTP directories return nullptr; // file not found } return FileSystem::createFileItemFromKIO(_fileEntry, directory, true); } KConfig &VirtualFileSystem::getVirtDB() { //virtualfilesystem_db = new KConfig("data",VIRTUALFILESYSTEM_DB,KConfig::NoGlobals); static KConfig db(VIRTUALFILESYSTEM_DB, KConfig::CascadeConfig, QStandardPaths::AppDataLocation); return db; } void VirtualFileSystem::slotStatResult(KJob *job) { - _fileEntry = job->error() ? KIO::UDSEntry() : static_cast(job)->statResult(); + _fileEntry = job->error() ? KIO::UDSEntry() : dynamic_cast(job)->statResult(); } void VirtualFileSystem::showError(const QString &error) { QWidget *window = QApplication::activeWindow(); KMessageBox::sorry(window, error); // window can be null, is allowed } diff --git a/krusader/Filter/filterdialog.cpp b/krusader/Filter/filterdialog.cpp index 69ac336d..10c1360d 100644 --- a/krusader/Filter/filterdialog.cpp +++ b/krusader/Filter/filterdialog.cpp @@ -1,107 +1,107 @@ /***************************************************************************** * Copyright (C) 2005 Csaba Karai * * Copyright (C) 2005-2018 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 "filterdialog.h" #include "filtertabs.h" #include "generalfilter.h" // QtWidgets #include #include #include #include #include FilterDialog::FilterDialog(QWidget *parent, const QString& caption, QStringList extraOptions, bool modal) : QDialog(parent) { setWindowTitle(caption.isNull() ? i18n("Krusader::Choose Files") : caption); setModal(modal); auto *mainLayout = new QVBoxLayout; setLayout(mainLayout); auto *filterWidget = new QTabWidget; filterTabs = FilterTabs::addTo(filterWidget, FilterTabs::HasProfileHandler, std::move(extraOptions)); - generalFilter = static_cast (filterTabs->get("GeneralFilter")); + generalFilter = dynamic_cast (filterTabs->get("GeneralFilter")); mainLayout->addWidget(filterWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Reset); mainLayout->addWidget(buttonBox); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &FilterDialog::slotOk); connect(buttonBox, &QDialogButtonBox::rejected, this, &FilterDialog::reject); connect(buttonBox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &FilterDialog::slotReset); connect(filterTabs, &FilterTabs::closeRequest, this, &FilterDialog::slotCloseRequest); generalFilter->searchFor->setFocus(); if(modal) exec(); } void FilterDialog::applySettings(const FilterSettings &s) { filterTabs->applySettings(s); } KRQuery FilterDialog::getQuery() { return settings.toQuery(); } bool FilterDialog::isExtraOptionChecked(QString name) { return filterTabs->isExtraOptionChecked(std::move(name)); } void FilterDialog::checkExtraOption(QString name, bool check) { filterTabs->checkExtraOption(std::move(name), check); } void FilterDialog::slotCloseRequest(bool doAccept) { if (doAccept) { slotOk(); accept(); } else reject(); } void FilterDialog::slotReset() { filterTabs->reset(); generalFilter->searchFor->setFocus(); } void FilterDialog::slotOk() { settings = filterTabs->getSettings(); if(settings.isValid()) accept(); } diff --git a/krusader/Filter/filtertabs.cpp b/krusader/Filter/filtertabs.cpp index 1f81548f..0c5dc88e 100644 --- a/krusader/Filter/filtertabs.cpp +++ b/krusader/Filter/filtertabs.cpp @@ -1,156 +1,156 @@ /***************************************************************************** * Copyright (C) 2005 Csaba Karai * * Copyright (C) 2005-2018 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 "filtertabs.h" #include "filterdialog.h" #include "generalfilter.h" #include "advancedfilter.h" #include "../krglobal.h" // QtWidgets #include #include #include #include #include FilterTabs::FilterTabs(int properties, QTabWidget *tabWidget, QObject *parent, QStringList extraOptions) : QObject(parent) { this->tabWidget = tabWidget; GeneralFilter *generalFilter = new GeneralFilter(this, properties, tabWidget, std::move(extraOptions)); tabWidget->addTab(generalFilter, i18n("&General")); filterList.append(generalFilter); pageNumbers.append(tabWidget->indexOf(generalFilter)); auto *advancedFilter = new AdvancedFilter(this, tabWidget); tabWidget->addTab(advancedFilter, i18n("&Advanced")); filterList.append(advancedFilter); pageNumbers.append(tabWidget->indexOf(advancedFilter)); reset(); // apply defaults } bool FilterTabs::isExtraOptionChecked(QString name) { - return static_cast(get("GeneralFilter"))->isExtraOptionChecked(std::move(name)); + return dynamic_cast(get("GeneralFilter"))->isExtraOptionChecked(std::move(name)); } void FilterTabs::checkExtraOption(QString name, bool check) { - static_cast(get("GeneralFilter"))->checkExtraOption(std::move(name), check); + dynamic_cast(get("GeneralFilter"))->checkExtraOption(std::move(name), check); } FilterTabs * FilterTabs::addTo(QTabWidget *tabWidget, int props, QStringList extraOptions) { return new FilterTabs(props, tabWidget, tabWidget, std::move(extraOptions)); } FilterSettings FilterTabs::getSettings() { FilterSettings s; for (int i = 0; i != filterList.count(); i++) { if(!filterList[i]->getSettings(s)) { tabWidget->setCurrentIndex(pageNumbers[i]); return FilterSettings(); } } s.valid = true; acceptQuery(); return s; } void FilterTabs::applySettings(const FilterSettings &s) { if(s.isValid()) { QListIterator it(filterList); while (it.hasNext()) it.next()->applySettings(s); } } void FilterTabs::reset() { FilterSettings s; // default settings s.valid = true; applySettings(s); } void FilterTabs::saveToProfile(const QString& name) { FilterSettings s(getSettings()); if(s.isValid()) s.save(KConfigGroup(krConfig, name)); krConfig->sync(); } void FilterTabs::loadFromProfile(const QString& name) { FilterSettings s; s.load(KConfigGroup(krConfig, name)); if(!s.isValid()) KMessageBox::error(tabWidget, i18n("Could not load profile.")); else applySettings(s); } void FilterTabs::acceptQuery() { QListIterator it(filterList); while (it.hasNext()) { FilterBase *filter = it.next(); filter->queryAccepted(); } } bool FilterTabs::fillQuery(KRQuery *query) { *query = getSettings().toQuery(); return !query->isNull(); } FilterBase * FilterTabs::get(const QString& name) { QListIterator it(filterList); while (it.hasNext()) { FilterBase *filter = it.next(); if (filter->name() == name) return filter; } return nullptr; } KRQuery FilterTabs::getQuery(QWidget *parent) { FilterDialog dialog(parent); return dialog.getQuery(); } diff --git a/krusader/GUI/krtreewidget.cpp b/krusader/GUI/krtreewidget.cpp index e0996d43..21d7bcc4 100644 --- a/krusader/GUI/krtreewidget.cpp +++ b/krusader/GUI/krtreewidget.cpp @@ -1,202 +1,202 @@ /***************************************************************************** * Copyright (C) 2008 Csaba Karai * * Copyright (C) 2008-2018 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 "krtreewidget.h" #include "krstyleproxy.h" // QtGui #include // QtWidgets #include #include #include #include KrTreeWidget::KrTreeWidget(QWidget * parent) : QTreeWidget(parent), _inResize(false) { setRootIsDecorated(false); setSortingEnabled(true); setAllColumnsShowFocus(true); _stretchingColumn = -1; auto *krstyle = new KrStyleProxy(); krstyle->setParent(this); setStyle(krstyle); } bool KrTreeWidget::event(QEvent * event) { switch (event->type()) { // HACK: QT 4 Context menu key isn't handled properly case QEvent::ContextMenu: { auto* ce = (QContextMenuEvent*) event; if (ce->reason() == QContextMenuEvent::Mouse) { QPoint pos = viewport()->mapFromGlobal(ce->globalPos()); QTreeWidgetItem * item = itemAt(pos); int column = columnAt(pos.x()); emit itemRightClicked(item, ce->globalPos(), column); return true; } else { if (currentItem()) { QRect r = visualItemRect(currentItem()); QPoint p = viewport()->mapToGlobal(QPoint(r.x() + 5, r.y() + 5)); emit itemRightClicked(currentItem(), p, currentColumn()); return true; } } } break; case QEvent::KeyPress: { // HACK: QT 4 Ctrl+A bug fix: Ctrl+A doesn't work if QTreeWidget contains parent / child items // Insert doesn't change the selections for multi selection modes auto* ke = (QKeyEvent*) event; switch (ke->key()) { case Qt::Key_Insert: { if (ke->modifiers() != 0) break; QAbstractItemView::SelectionMode mode = selectionMode(); if (mode != QAbstractItemView::ContiguousSelection && mode != QAbstractItemView::ExtendedSelection && mode != QAbstractItemView::MultiSelection) break; ke->accept(); if (currentItem() == nullptr) return true; currentItem()->setSelected(!currentItem()->isSelected()); return true; } case Qt::Key_A: if (ke->modifiers() == Qt::ControlModifier) { QAbstractItemView::SelectionMode mode = selectionMode(); if (mode == QAbstractItemView::ContiguousSelection || mode == QAbstractItemView::ExtendedSelection || mode == QAbstractItemView::MultiSelection) { selectAll(); ke->accept(); return true; } } break; default: break; } } break; case QEvent::Resize: { auto * re = (QResizeEvent *)event; if (!_inResize && re->oldSize() != re->size()) { if (_stretchingColumn != -1 && columnCount()) { QList< int > columnsSizes; int oldSize = 0; for (int i = 0; i != header()->count(); i++) { columnsSizes.append(header()->sectionSize(i)); oldSize += header()->sectionSize(i); } bool res = QTreeWidget::event(event); int newSize = viewport()->width(); int delta = newSize - oldSize; if (delta) { _inResize = true; for (int i = 0; i != header()->count(); i++) { if (i == _stretchingColumn) { int newNs = columnsSizes[ i ] + delta; if (newNs < 8) newNs = 8; header()->resizeSection(i, newNs); } else if (header()->sectionSize(i) != columnsSizes[ i ]) { header()->resizeSection(i, columnsSizes[ i ]); } } _inResize = false; } return res; } } break; } case QEvent::ToolTip: { - auto *he = static_cast(event); + auto *he = dynamic_cast(event); if (viewport()) { QPoint pos = viewport()->mapFromGlobal(he->globalPos()); QTreeWidgetItem * item = itemAt(pos); int column = columnAt(pos.x()); if (item) { if (!item->toolTip(column).isEmpty()) break; QString tip = item->text(column); int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; int requiredWidth = QFontMetrics(font()).width(tip) + 2 * textMargin; if (column == 0 && indentation()) { int level = 0; QTreeWidgetItem *parent = item; while ((parent = parent->parent())) level++; if (rootIsDecorated()) level++; requiredWidth += level * indentation(); } QIcon icon = item->icon(column); if (!icon.isNull()) { QStyleOptionViewItem opts = viewOptions(); QSize iconSize = icon.actualSize(opts.decorationSize); requiredWidth += iconSize.width(); int pixmapMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, this) + 1; requiredWidth += 2 * pixmapMargin; } if (!tip.isEmpty() && (columnWidth(column) < requiredWidth)) QToolTip::showText(he->globalPos(), tip, this); return true; } } } break; default: break; } return QTreeWidget::event(event); } diff --git a/krusader/GUI/mediabutton.cpp b/krusader/GUI/mediabutton.cpp index fa77f5ac..4e8f46de 100644 --- a/krusader/GUI/mediabutton.cpp +++ b/krusader/GUI/mediabutton.cpp @@ -1,630 +1,630 @@ /***************************************************************************** * Copyright (C) 2006 Csaba Karai * * Copyright (C) 2006-2018 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 "mediabutton.h" #include "../krglobal.h" #include "../icon.h" #include "../MountMan/kmountman.h" // QtCore #include // QtGui #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QString MediaButton::remotePrefix = QLatin1String("remote:"); MediaButton::MediaButton(QWidget *parent) : QToolButton(parent), popupMenu(nullptr), rightMenu(nullptr), openInNewTab(false) { setAutoRaise(true); setIcon(Icon("system-file-manager")); setText(i18n("Open the available media list")); setToolTip(i18n("Open the available media list")); setPopupMode(QToolButton::InstantPopup); setAcceptDrops(false); popupMenu = new QMenu(this); popupMenu->installEventFilter(this); Q_CHECK_PTR(popupMenu); setMenu(popupMenu); connect(popupMenu, &QMenu::aboutToShow, this, &MediaButton::slotAboutToShow); connect(popupMenu, &QMenu::aboutToHide, this, &MediaButton::slotAboutToHide); connect(popupMenu, &QMenu::triggered, this, &MediaButton::slotPopupActivated); Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); connect(notifier, &Solid::DeviceNotifier::deviceAdded, this, &MediaButton::slotDeviceAdded); connect(notifier, &Solid::DeviceNotifier::deviceRemoved, this, &MediaButton::slotDeviceRemoved); connect(&mountCheckerTimer, &QTimer::timeout, this, &MediaButton::slotCheckMounts); } MediaButton::~MediaButton() = default; void MediaButton::updateIcon(const QString &mountPoint) { if(!mountPoint.isEmpty() && mountPoint == currentMountPoint) return; currentMountPoint = mountPoint; QString iconName("system-file-manager"); QStringList overlays; if(!mountPoint.isEmpty()) { Solid::Device device(krMtMan.findUdiForPath(mountPoint, Solid::DeviceInterface::StorageAccess));; auto *vol = device.as (); if(device.isValid()) iconName = device.icon(); if (vol && vol->usage() == Solid::StorageVolume::Encrypted) overlays << "security-high"; } setIcon(Icon(iconName, overlays)); } void MediaButton::slotAboutToShow() { emit aboutToShow(); popupMenu->clear(); udiNameMap.clear(); createMediaList(); } void MediaButton::slotAboutToHide() { if (rightMenu) rightMenu->close(); mountCheckerTimer.stop(); } void MediaButton::createMediaList() { // devices detected by solid storageDevices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); for (int p = storageDevices.count() - 1 ; p >= 0; p--) { Solid::Device device = storageDevices[ p ]; QString udi = device.udi(); QString name; QIcon kdeIcon; if (!getNameAndIcon(device, name, kdeIcon)) continue; QAction * act = popupMenu->addAction(kdeIcon, name); act->setData(QVariant(udi)); udiNameMap[ udi ] = name; connect(device.as(), &Solid::StorageAccess::accessibilityChanged, this, &MediaButton::slotAccessibilityChanged); } KMountPoint::List possibleMountList = KMountPoint::possibleMountPoints(); KMountPoint::List currentMountList = KMountPoint::currentMountPoints(); for (auto & it : possibleMountList) { if (krMtMan.networkFilesystem(it->mountType())) { QString path = it->mountPoint(); bool mounted = false; for (auto & it2 : currentMountList) { if (krMtMan.networkFilesystem(it2->mountType()) && it->mountPoint() == it2->mountPoint()) { mounted = true; break; } } QString name = i18nc("%1 is the mount point of the remote share", "Remote Share [%1]", it->mountPoint()); QStringList overlays; if (mounted) overlays << "emblem-mounted"; QAction * act = popupMenu->addAction(Icon("network-wired", overlays), name); QString udi = remotePrefix + it->mountPoint(); act->setData(QVariant(udi)); } } mountCheckerTimer.setSingleShot(true); mountCheckerTimer.start(1000); } bool MediaButton::getNameAndIcon(Solid::Device & device, QString &name, QIcon &iconOut) { auto *access = device.as(); if (access == nullptr) return false; QString udi = device.udi(); QString label = i18nc("Unknown label", "Unknown"); bool mounted = access->isAccessible(); QString path = access->filePath(); QString type = i18nc("Unknown media type", "Unknown"); QString iconName = device.icon(); QString fstype; QString size; auto * vol = device.as (); if (vol) { label = vol->label(); fstype = vol->fsType(); size = KIO::convertSize(vol->size()); } bool printSize = false; if (iconName == "media-floppy") type = i18n("Floppy"); else if (iconName == "drive-optical") type = i18n("CD/DVD-ROM"); else if (iconName == "drive-removable-media-usb-pendrive") type = i18n("USB pen drive"), printSize = true; else if (iconName == "drive-removable-media-usb") type = i18n("USB device"), printSize = true; else if (iconName == "drive-removable-media") type = i18n("Removable media"), printSize = true; else if (iconName == "drive-harddisk") type = i18n("Hard Disk"), printSize = true; else if (iconName == "camera-photo") type = i18n("Camera"); else if (iconName == "media-optical-video") type = i18n("Video CD/DVD-ROM"); else if (iconName == "media-optical-audio") type = i18n("Audio CD/DVD-ROM"); else if (iconName == "media-optical") type = i18n("Recordable CD/DVD-ROM"); KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("MediaMenu")); if (printSize) { QString showSizeSetting = cfg.readEntry("ShowSize", "Always"); if (showSizeSetting == "WhenNoLabel") printSize = label.isEmpty(); else if (showSizeSetting == "Never") printSize = false; } if (printSize && !size.isEmpty()) name += size + ' '; if (!label.isEmpty()) name += label + ' '; else name += type + ' '; if (!fstype.isEmpty() && cfg.readEntry("ShowFSType", true)) name += '(' + fstype + ") "; if (!path.isEmpty() && cfg.readEntry("ShowPath", true)) name += '[' + path + "] "; name = name.trimmed(); QStringList overlays; if (mounted) { overlays << "emblem-mounted"; } else { overlays << QString(); // We have to guarantee the placement of the next emblem } if (vol && vol->usage() == Solid::StorageVolume::Encrypted) { overlays << "security-high"; } iconOut = Icon(iconName, overlays); return true; } void MediaButton::slotPopupActivated(QAction *action) { if (action && action->data().canConvert()) { QString udi = action->data().toString(); if (!udi.isEmpty()) { bool mounted = false; QString mountPoint; getStatus(udi, mounted, &mountPoint); if (mounted) emit openUrl(QUrl::fromLocalFile(mountPoint)); else mount(udi, true); } } } void MediaButton::showMenu() { QMenu * pP = menu(); if (pP) { menu() ->exec(mapToGlobal(QPoint(0, height()))); } } bool MediaButton::eventFilter(QObject *o, QEvent *e) { if (o == popupMenu) { if (e->type() == QEvent::ContextMenu) { QAction *act = popupMenu->activeAction(); if (act && act->data().canConvert()) { QString id = act->data().toString(); if (!id.isEmpty()) { QPoint globalPos = popupMenu->mapToGlobal(popupMenu->actionGeometry(act).topRight()); rightClickMenu(id, globalPos); return true; } } } else if (e->type() == QEvent::KeyPress) { - auto *ke = static_cast(e); + auto *ke = dynamic_cast(e); if (ke->key() == Qt::Key_Return && ke->modifiers() == Qt::ControlModifier) { if (QAction *act = popupMenu->activeAction()) { QString id = act->data().toString(); if (!id.isEmpty()) { toggleMount(id); return true; } } } } else if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease) { - auto *m = static_cast(e); + auto *m = dynamic_cast(e); if (m->button() == Qt::RightButton) { if (e->type() == QEvent::MouseButtonPress) { QAction * act = popupMenu->actionAt(m->pos()); if (act && act->data().canConvert()) { QString id = act->data().toString(); if (!id.isEmpty()) rightClickMenu(id, m->globalPos()); } } m->accept(); return true; } } } return false; } void MediaButton::rightClickMenu(const QString& udi, QPoint pos) { if (rightMenu) rightMenu->close(); bool ejectable = false; bool mounted = false; QString mountPoint; getStatus(udi, mounted, &mountPoint, &ejectable); QUrl openURL = QUrl::fromLocalFile(mountPoint); QMenu * myMenu = rightMenu = new QMenu(popupMenu); QAction * actOpen = myMenu->addAction(i18n("Open")); actOpen->setData(QVariant(1)); QAction * actOpenNewTab = myMenu->addAction(i18n("Open in a new tab")); actOpenNewTab->setData(QVariant(2)); myMenu->addSeparator(); if (!mounted) { QAction * actMount = myMenu->addAction(i18n("Mount")); actMount->setData(QVariant(3)); } else { QAction * actUnmount = myMenu->addAction(i18n("Unmount")); actUnmount->setData(QVariant(4)); } if (ejectable) { QAction * actEject = myMenu->addAction(i18n("Eject")); actEject->setData(QVariant(5)); } QAction *act = myMenu->exec(pos); int result = -1; if (act != nullptr && act->data().canConvert()) result = act->data().toInt(); delete myMenu; if (rightMenu == myMenu) rightMenu = nullptr; else return; switch (result) { case 1: case 2: popupMenu->close(); if (mounted) { if (result == 1) emit openUrl(openURL); else emit newTab(openURL); } else { mount(udi, true, result == 2); // mount first, when mounted open the tab } break; case 3: mount(udi); break; case 4: umount(udi); break; case 5: eject(udi); break; default: break; } } void MediaButton::toggleMount(const QString& udi) { bool mounted = false; getStatus(udi, mounted); if (mounted) umount(udi); else mount(udi); } void MediaButton::getStatus(const QString& udi, bool &mounted, QString *mountPointOut, bool *ejectableOut) { mounted = false; bool network = udi.startsWith(remotePrefix); bool ejectable = false; QString mountPoint; if (network) { mountPoint = udi.mid(remotePrefix.length()); KMountPoint::List currentMountList = KMountPoint::currentMountPoints(); for (auto & it : currentMountList) { if (krMtMan.networkFilesystem(it->mountType()) && it->mountPoint() == mountPoint) { mounted = true; break; } } } else { Solid::Device device(udi); auto *access = device.as(); auto *optdisc = device.as(); if (access) mountPoint = access->filePath(); if (access && access->isAccessible()) mounted = true; if (optdisc) ejectable = true; } if (mountPointOut) *mountPointOut = mountPoint; if (ejectableOut) *ejectableOut = ejectable; } void MediaButton::mount(const QString& udi, bool open, bool newtab) { if (udi.startsWith(remotePrefix)) { QString mp = udi.mid(remotePrefix.length()); krMtMan.mount(mp, true); if (newtab) emit newTab(QUrl::fromLocalFile(mp)); else emit openUrl(QUrl::fromLocalFile(mp)); return; } Solid::Device device(udi); auto *access = device.as(); if (access && !access->isAccessible()) { if (open) udiToOpen = device.udi(), openInNewTab = newtab; connect(access, &Solid::StorageAccess::setupDone, this, &MediaButton::slotSetupDone); access->setup(); } } void MediaButton::slotSetupDone(Solid::ErrorType error, const QVariant& errorData, const QString &udi) { if (error == Solid::NoError) { if (udi == udiToOpen) { auto *access = Solid::Device(udi).as(); if (access && access->isAccessible()) { if (openInNewTab) emit newTab(QUrl::fromLocalFile(access->filePath())); else emit openUrl(QUrl::fromLocalFile(access->filePath())); } udiToOpen = QString(), openInNewTab = false; } } else { if (udi == udiToOpen) udiToOpen = QString(), openInNewTab = false; QString name; if (udiNameMap.contains(udi)) name = udiNameMap[ udi ]; if (errorData.isValid()) { KMessageBox::sorry(this, i18n("An error occurred while accessing '%1', the system responded: %2", name, errorData.toString())); } else { KMessageBox::sorry(this, i18n("An error occurred while accessing '%1'", name)); } } } void MediaButton::umount(const QString& udi) { if (udi.startsWith(remotePrefix)) { krMtMan.unmount(udi.mid(remotePrefix.length()), false); return; } krMtMan.unmount(krMtMan.pathForUdi(udi), false); } void MediaButton::eject(QString udi) { krMtMan.eject(krMtMan.pathForUdi(std::move(udi))); } void MediaButton::slotAccessibilityChanged(bool /*accessible*/, const QString & udi) { QList actionList = popupMenu->actions(); foreach(QAction * act, actionList) { if (act && act->data().canConvert() && act->data().toString() == udi) { Solid::Device device(udi); QString name; QIcon kdeIcon; if (getNameAndIcon(device, name, kdeIcon)) { act->setText(name); act->setIcon(kdeIcon); } break; } } } void MediaButton::slotDeviceAdded(const QString& udi) { if (popupMenu->isHidden()) return; Solid::Device device(udi); auto *access = device.as(); if (access == nullptr) return; QString name; QIcon kdeIcon; if (!getNameAndIcon(device, name, kdeIcon)) return; QAction * act = popupMenu->addAction(kdeIcon, name); act->setData(QVariant(udi)); udiNameMap[ udi ] = name; connect(device.as(), &Solid::StorageAccess::accessibilityChanged, this, &MediaButton::slotAccessibilityChanged); } void MediaButton::slotDeviceRemoved(const QString& udi) { if (popupMenu->isHidden()) return; QList actionList = popupMenu->actions(); foreach(QAction * act, actionList) { if (act && act->data().canConvert() && act->data().toString() == udi) { popupMenu->removeAction(act); delete act; break; } } } void MediaButton::slotCheckMounts() { if (isHidden()) return; KMountPoint::List possibleMountList = KMountPoint::possibleMountPoints(); KMountPoint::List currentMountList = KMountPoint::currentMountPoints(); QList actionList = popupMenu->actions(); foreach(QAction * act, actionList) { if (act && act->data().canConvert() && act->data().toString().startsWith(remotePrefix)) { QString mountPoint = act->data().toString().mid(remotePrefix.length()); bool available = false; for (auto & it : possibleMountList) { if (krMtMan.networkFilesystem(it->mountType()) && it->mountPoint() == mountPoint) { available = true; break; } } if (!available) { popupMenu->removeAction(act); delete act; } } } for (auto & it : possibleMountList) { if (krMtMan.networkFilesystem(it->mountType())) { QString path = it->mountPoint(); bool mounted = false; QString udi = remotePrefix + path; QAction * correspondingAct = nullptr; foreach(QAction * act, actionList) { if (act && act->data().canConvert() && act->data().toString() == udi) { correspondingAct = act; break; } } for (auto & it2 : currentMountList) { if (krMtMan.networkFilesystem(it2->mountType()) && path == it2->mountPoint()) { mounted = true; break; } } QString name = i18nc("%1 is the mount point of the remote share", "Remote Share [%1]", it->mountPoint()); QStringList overlays; if (mounted) overlays << "emblem-mounted"; QIcon kdeIcon = Icon("network-wired", overlays); if (!correspondingAct) { QAction * act = popupMenu->addAction(kdeIcon, name); act->setData(QVariant(udi)); } else { correspondingAct->setText(name); correspondingAct->setIcon(kdeIcon); } } } mountCheckerTimer.setSingleShot(true); mountCheckerTimer.start(1000); } diff --git a/krusader/JobMan/jobman.cpp b/krusader/JobMan/jobman.cpp index 14e452b1..a86ee994 100644 --- a/krusader/JobMan/jobman.cpp +++ b/krusader/JobMan/jobman.cpp @@ -1,459 +1,459 @@ /***************************************************************************** * Copyright (C) 2016-2018 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 "jobman.h" // QtCore #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "krjob.h" #include "../krglobal.h" #include "../icon.h" const int MAX_OLD_MENU_ACTIONS = 10; /** The menu action entry for a job in the popup menu.*/ class JobMenuAction : public QWidgetAction { Q_OBJECT public: JobMenuAction(KrJob *krJob, QObject *parent, KJob *kJob = nullptr) : QWidgetAction(parent), m_krJob(krJob) { QWidget *container = new QWidget(); auto *layout = new QGridLayout(container); m_description = new QLabel(krJob->description()); m_progressBar = new QProgressBar(); layout->addWidget(m_description, 0, 0, 1, 3); layout->addWidget(m_progressBar, 1, 0); m_pauseResumeButton = new QPushButton(); updatePauseResumeButton(); connect(m_pauseResumeButton, &QPushButton::clicked, this, &JobMenuAction::slotPauseResumeButtonClicked); layout->addWidget(m_pauseResumeButton, 1, 1); m_cancelButton = new QPushButton(); m_cancelButton->setIcon(Icon("remove")); m_cancelButton->setToolTip(i18n("Cancel Job")); connect(m_cancelButton, &QPushButton::clicked, this, &JobMenuAction::slotCancelButtonClicked); layout->addWidget(m_cancelButton, 1, 2); setDefaultWidget(container); if (kJob) { slotStarted(kJob); } else { connect(krJob, &KrJob::started, this, &JobMenuAction::slotStarted); } connect(krJob, &KrJob::terminated, this, &JobMenuAction::slotTerminated); } bool isDone() { return !m_krJob; } protected slots: void slotDescription(KJob *, const QString &description, const QPair &field1, const QPair &field2) { const QPair textField = !field2.first.isEmpty() ? field2 : field1; QString text = description; if (!textField.first.isEmpty()) { text += QString(" - %1: %2").arg(textField.first, textField.second); } m_description->setText(text); if (!field2.first.isEmpty() && !field1.first.isEmpty()) { // NOTE: tooltips for QAction items in menu are not shown m_progressBar->setToolTip(QString("%1: %2").arg(field1.first, field1.second)); } } void slotPercent(KJob *, unsigned long percent) { m_progressBar->setValue(percent); } void updatePauseResumeButton() { m_pauseResumeButton->setIcon(Icon( m_krJob->isRunning() ? "media-playback-pause" : m_krJob->isPaused() ? "media-playback-start" : "chronometer-start")); m_pauseResumeButton->setToolTip(m_krJob->isRunning() ? i18n("Pause Job") : m_krJob->isPaused() ? i18n("Resume Job") : i18n("Start Job")); } void slotResult(KJob *job) { // NOTE: m_job may already set to NULL now if(!job->error()) { // percent signal is not reliable, set manually m_progressBar->setValue(100); } } void slotTerminated() { qDebug() << "job description=" << m_krJob->description(); m_pauseResumeButton->setEnabled(false); m_cancelButton->setIcon(Icon("edit-clear")); m_cancelButton->setToolTip(i18n("Clear")); m_krJob = nullptr; } void slotPauseResumeButtonClicked() { if (!m_krJob) return; if (m_krJob->isRunning()) m_krJob->pause(); else m_krJob->start(); } void slotCancelButtonClicked() { if (m_krJob) { m_krJob->cancel(); } else { deleteLater(); } } private slots: void slotStarted(KJob *job) { connect(job, &KJob::description, this, &JobMenuAction::slotDescription); connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(slotPercent(KJob*,ulong))); connect(job, &KJob::suspended, this, &JobMenuAction::updatePauseResumeButton); connect(job, &KJob::resumed, this, &JobMenuAction::updatePauseResumeButton); connect(job, &KJob::result, this, &JobMenuAction::slotResult); connect(job, &KJob::warning, this, [](KJob *, const QString &plain, const QString &) { qWarning() << "unexpected job warning: " << plain; }); updatePauseResumeButton(); } private: KrJob *m_krJob; QLabel *m_description; QProgressBar *m_progressBar; QPushButton *m_pauseResumeButton; QPushButton *m_cancelButton; }; #include "jobman.moc" // required for class definitions with Q_OBJECT macro in implementation files const QString JobMan::sDefaultToolTip = i18n("No jobs"); JobMan::JobMan(QObject *parent) : QObject(parent), m_messageBox(nullptr) { // job control action m_controlAction = new KToolBarPopupAction(Icon("media-playback-pause"), i18n("Play/Pause &Job"), this); m_controlAction->setEnabled(false); connect(m_controlAction, &QAction::triggered, this, &JobMan::slotControlActionTriggered); auto *menu = new QMenu(krMainWindow); menu->setMinimumWidth(300); // make scrollable if menu is too long menu->setStyleSheet("QMenu { menu-scrollable: 1; }"); m_controlAction->setMenu(menu); // progress bar action m_progressBar = new QProgressBar(); m_progressBar->setToolTip(sDefaultToolTip); m_progressBar->setEnabled(false); // listen to clicks on progress bar m_progressBar->installEventFilter(this); auto *progressAction = new QWidgetAction(krMainWindow); progressAction->setText(i18n("Job Progress Bar")); progressAction->setDefaultWidget(m_progressBar); m_progressAction = progressAction; // job queue mode action KConfigGroup cfg(krConfig, "JobManager"); m_queueMode = cfg.readEntry("Queue Mode", false); m_modeAction = new QAction(Icon("media-playlist-repeat"), i18n("Job Queue Mode"), krMainWindow); m_modeAction->setToolTip(i18n("Run only one job in parallel")); m_modeAction->setCheckable(true); m_modeAction->setChecked(m_queueMode); connect(m_modeAction, &QAction::toggled, this, [=](bool checked) mutable { m_queueMode = checked; cfg.writeEntry("Queue Mode", m_queueMode); }); // undo action KIO::FileUndoManager *undoManager = KIO::FileUndoManager::self(); undoManager->uiInterface()->setParentWidget(krMainWindow); m_undoAction = new QAction(Icon("edit-undo"), i18n("Undo Last Job"), krMainWindow); m_undoAction->setEnabled(false); connect(m_undoAction, &QAction::triggered, undoManager, &KIO::FileUndoManager::undo); connect(undoManager, static_cast(&KIO::FileUndoManager::undoAvailable), m_undoAction, &QAction::setEnabled); connect(undoManager, &KIO::FileUndoManager::undoTextChanged, this, &JobMan::slotUndoTextChange); } bool JobMan::waitForJobs(bool waitForUserInput) { if (m_jobs.isEmpty() && !waitForUserInput) return true; // attempt to get all job threads does not work //QList threads = krMainWindow->findChildren(); m_autoCloseMessageBox = !waitForUserInput; m_messageBox = new QMessageBox(krMainWindow); m_messageBox->setWindowTitle(i18n("Warning")); m_messageBox->setIconPixmap(Icon("dialog-warning") .pixmap(QMessageBox::standardIcon(QMessageBox::Information).size())); m_messageBox->setText(i18n("Are you sure you want to quit?")); m_messageBox->addButton(QMessageBox::Abort); m_messageBox->addButton(QMessageBox::Cancel); m_messageBox->setDefaultButton(QMessageBox::Cancel); for (KrJob *job: m_jobs) connect(job, &KrJob::terminated, this, &JobMan::slotUpdateMessageBox); slotUpdateMessageBox(); int result = m_messageBox->exec(); // blocking m_messageBox->deleteLater(); m_messageBox = nullptr; // accepted -> cancel all jobs if (result == QMessageBox::Abort) { for (KrJob *job: m_jobs) { job->cancel(); } return true; } // else: return false; } void JobMan::manageJob(KrJob *job, StartMode startMode) { qDebug() << "new job, startMode=" << startMode; managePrivate(job); connect(job, &KrJob::started, this, &JobMan::slotKJobStarted); const bool enqueue = startMode == Enqueue || (startMode == Default && m_queueMode); if (startMode == Start || (startMode == Default && !m_queueMode) || (enqueue && !jobsAreRunning())) { job->start(); } updateUI(); } void JobMan::manageStartedJob(KrJob *krJob, KJob *kJob) { managePrivate(krJob, kJob); slotKJobStarted(kJob); updateUI(); } // #### protected slots void JobMan::slotKJobStarted(KJob *job) { // KJob has two percent() functions connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(slotPercent(KJob*,ulong))); connect(job, &KJob::description, this, &JobMan::slotDescription); connect(job, &KJob::suspended, this, &JobMan::updateUI); connect(job, &KJob::resumed, this, &JobMan::updateUI); } void JobMan::slotControlActionTriggered() { if (m_jobs.isEmpty()) { m_controlAction->menu()->clear(); m_controlAction->setEnabled(false); return; } const bool anyRunning = jobsAreRunning(); if (!anyRunning && m_queueMode) { m_jobs.first()->start(); } else { for (KrJob *job : m_jobs) { if (anyRunning) job->pause(); else job->start(); } } } void JobMan::slotPercent(KJob *, unsigned long) { updateUI(); } void JobMan::slotDescription(KJob*,const QString &description, const QPair &field1, const QPair &field2) { // TODO cache all descriptions if (m_jobs.length() > 1) return; m_progressBar->setToolTip( QString("%1\n%2: %3\n%4: %5") .arg(description, field1.first, field1.second, field2.first, field2.second)); } void JobMan::slotTerminated(KrJob *krJob) { qDebug() << "terminated, job description: " << krJob->description(); m_jobs.removeAll(krJob); // NOTE: ignoring queue mode here. We assume that if queue mode is turned off, the user created // jobs which were not already started with a "queue" option and still wants queue behaviour. if (!m_jobs.isEmpty() && !jobsAreRunning()) { foreach (KrJob *job, m_jobs) { if (!job->isPaused()) { // start next job job->start(); break; } } } updateUI(); cleanupMenu(); } void JobMan::slotUpdateControlAction() { m_controlAction->setEnabled(!m_controlAction->menu()->isEmpty()); } void JobMan::slotUndoTextChange(const QString &text) { m_undoAction->setToolTip(KIO::FileUndoManager::self()->undoAvailable() ? text : i18n("Undo Last Job")); } void JobMan::slotUpdateMessageBox() { if (!m_messageBox) return; if (m_jobs.isEmpty() && m_autoCloseMessageBox) { m_messageBox->done(QMessageBox::Abort); return; } if (m_jobs.isEmpty()) { m_messageBox->setInformativeText(""); m_messageBox->setButtonText(QMessageBox::Abort, "Quit"); return; } m_messageBox->setInformativeText(i18np("There is one job operation left.", "There are %1 job operations left.", m_jobs.length())); m_messageBox->setButtonText(QMessageBox::Abort, "Abort Jobs and Quit"); } // #### private void JobMan::managePrivate(KrJob *job, KJob *kJob) { auto *menuAction = new JobMenuAction(job, m_controlAction, kJob); connect(menuAction, &QObject::destroyed, this, &JobMan::slotUpdateControlAction); m_controlAction->menu()->addAction(menuAction); cleanupMenu(); slotUpdateControlAction(); connect(job, &KrJob::terminated, this, &JobMan::slotTerminated); m_jobs.append(job); } void JobMan::cleanupMenu() { const QList actions = m_controlAction->menu()->actions(); for (QAction *action : actions) { if (m_controlAction->menu()->actions().count() <= MAX_OLD_MENU_ACTIONS) break; - auto *jobAction = static_cast(action); + auto *jobAction = dynamic_cast(action); if (jobAction->isDone()) { m_controlAction->menu()->removeAction(action); action->deleteLater(); } } } void JobMan::updateUI() { int totalPercent = 0; for (KrJob *job: m_jobs) { totalPercent += job->percent(); } const bool hasJobs = !m_jobs.isEmpty(); m_progressBar->setEnabled(hasJobs); if (hasJobs) { m_progressBar->setValue(totalPercent / m_jobs.length()); } else { m_progressBar->reset(); } if (!hasJobs) m_progressBar->setToolTip(i18n("No Jobs")); if (m_jobs.length() > 1) m_progressBar->setToolTip(i18np("%1 Job", "%1 Jobs", m_jobs.length())); const bool running = jobsAreRunning(); m_controlAction->setIcon(Icon( !hasJobs ? "edit-clear" : running ? "media-playback-pause" : "media-playback-start")); m_controlAction->setToolTip(!hasJobs ? i18n("Clear Job List") : running ? i18n("Pause All Jobs") : i18n("Resume Job List")); } bool JobMan::jobsAreRunning() { for (KrJob *job: m_jobs) if (job->isRunning()) return true; return false; } diff --git a/krusader/KViewer/krviewer.cpp b/krusader/KViewer/krviewer.cpp index 1db05b1b..ac00878d 100644 --- a/krusader/KViewer/krviewer.cpp +++ b/krusader/KViewer/krviewer.cpp @@ -1,712 +1,712 @@ /***************************************************************************** * Copyright (C) 2002 Shie Erlich * * Copyright (C) 2002 Rafi Yanai * * Copyright (C) 2004-2018 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" #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), tabBar(this), reservedKeys(), reservedKeyActions(), 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(&tabBar, &QTabWidget::currentChanged, this, &KrViewer::tabChanged); connect(&tabBar, &QTabWidget::tabCloseRequested, this, [=](int index) { tabCloseRequest(index, false); }); tabBar.setDocumentMode(true); tabBar.setMovable(true); setCentralWidget(&tabBar); 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); tabBar.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(tabBar.count()) tabCloseRequest(tabBar.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 = (QKeyEvent*) 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 = (QKeyEvent*) 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 = tabBar.addTab(pvb, makeTabIcon(pvb), makeTabText(pvb)); tabBar.setCurrentIndex(tabIndex); tabBar.setTabToolTip(tabIndex, makeTabToolTip(pvb)); updateActions(); // now we can offer the option to detach tabs (we have more than one) if (tabBar.count() > 1) { detachAction->setEnabled(true); } tabBar.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 (tabBar.currentWidget() == pvb) { manager.setActivePart(part); if (part->widget()) part->widget()->setFocus(); } } } else { tabCloseRequest(tabBar.currentIndex(), false); } } void KrViewer::tabChanged(int index) { QWidget *w = tabBar.widget(index); if(!w) return; - KParts::ReadOnlyPart *part = static_cast(w)->part(); + 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 = static_cast(tabBar.widget(index)); + auto *pvb = dynamic_cast(tabBar.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(); tabBar.removeTab(index); delete pvb; pvb = nullptr; if (tabBar.count() <= 0) { if (returnFocusToThisWidget) { returnFocusToThisWidget->raise(); returnFocusToThisWidget->activateWindow(); } else { krMainWindow->raise(); krMainWindow->activateWindow(); } QTimer::singleShot(0, this, &KrViewer::close); } else if (tabBar.count() == 1) { // no point in detaching only one tab.. detachAction->setEnabled(false); } } void KrViewer::tabCloseRequest() { tabCloseRequest(tabBar.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 != tabBar.count(); i++) { - auto* pvb = static_cast(tabBar.widget(i)); + auto* pvb = dynamic_cast(tabBar.widget(i)); if (!pvb) continue; tabBar.setCurrentIndex(i); if (!pvb->queryClose()) return false; } return true; } void KrViewer::viewGeneric() { - auto* pvb = static_cast(tabBar.currentWidget()); + auto* pvb = dynamic_cast(tabBar.currentWidget()); if (pvb) viewInternal(pvb->url(), Generic); } void KrViewer::viewText() { - auto* pvb = static_cast(tabBar.currentWidget()); + auto* pvb = dynamic_cast(tabBar.currentWidget()); if (pvb) viewInternal(pvb->url(), Text); } void KrViewer::viewLister() { - auto* pvb = static_cast(tabBar.currentWidget()); + auto* pvb = dynamic_cast(tabBar.currentWidget()); if (pvb) viewInternal(pvb->url(), Lister); } void KrViewer::viewHex() { - auto* pvb = static_cast(tabBar.currentWidget()); + auto* pvb = dynamic_cast(tabBar.currentWidget()); if (pvb) viewInternal(pvb->url(), Hex); } void KrViewer::editText() { - auto* pvb = static_cast(tabBar.currentWidget()); + auto* pvb = dynamic_cast(tabBar.currentWidget()); if (pvb) editInternal(pvb->url(), Text); } void KrViewer::checkModified() { QTimer::singleShot(CHECK_MODFIED_INTERVAL, this, &KrViewer::checkModified); - auto* pvb = static_cast(tabBar.currentWidget()); + auto* pvb = dynamic_cast(tabBar.currentWidget()); if (pvb) refreshTab(pvb); } void KrViewer::refreshTab(PanelViewerBase* pvb) { int ndx = tabBar.indexOf(pvb); tabBar.setTabText(ndx, makeTabText(pvb)); tabBar.setTabIcon(ndx, makeTabIcon(pvb)); tabBar.setTabToolTip(ndx, makeTabToolTip(pvb)); } void KrViewer::nextTab() { int index = (tabBar.currentIndex() + 1) % tabBar.count(); tabBar.setCurrentIndex(index); } void KrViewer::prevTab() { int index = (tabBar.currentIndex() - 1) % tabBar.count(); while (index < 0) index += tabBar.count(); tabBar.setCurrentIndex(index); } void KrViewer::detachTab() { - auto* pvb = static_cast(tabBar.currentWidget()); + auto* pvb = dynamic_cast(tabBar.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); tabBar.removeTab(tabBar.indexOf(pvb)); if (tabBar.count() == 1) { //no point in detaching only one tab.. detachAction->setEnabled(false); } pvb->setParent(&viewer->tabBar); 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 = static_cast(tabBar.currentWidget()); + auto* pvb = dynamic_cast(tabBar.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 = static_cast(tabBar.currentWidget()); + auto* pvb = dynamic_cast(tabBar.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(&tabBar, mode); addTab(editWidget); editWidget->openUrl(std::move(url)); } diff --git a/krusader/KViewer/lister.cpp b/krusader/KViewer/lister.cpp index 9553f23a..9eacf139 100644 --- a/krusader/KViewer/lister.cpp +++ b/krusader/KViewer/lister.cpp @@ -1,2276 +1,2276 @@ /***************************************************************************** * Copyright (C) 2009 Csaba Karai * * Copyright (C) 2009-2018 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" #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.width("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW") - fm.width("W")) / 99.0; const int sizeY = contentRect.height() / fontHeight; _pageSize = sizeY; const int textViewportWidth = std::max(contentRect.width() - (int) fontWidth, 0); setTabStopWidth(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(); 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 = static_cast(e); + 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() Q_DECL_OVERRIDE { return _lister->characterSet(); } void chooseDefault() Q_DECL_OVERRIDE { _lister->setCharacterSet(QString()); } void chooseEncoding(QString encodingName) Q_DECL_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 = (KIO::TransferJob *)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.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/KViewer/panelviewer.cpp b/krusader/KViewer/panelviewer.cpp index 4505c333..9cb57ca9 100644 --- a/krusader/KViewer/panelviewer.cpp +++ b/krusader/KViewer/panelviewer.cpp @@ -1,430 +1,430 @@ /***************************************************************************** * Copyright (C) 2002 Shie Erlich * * Copyright (C) 2002 Rafi Yanai * * Copyright (C) 2004-2018 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 "panelviewer.h" // QtCore #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "lister.h" #include "../defaults.h" #define DICTSIZE 211 PanelViewerBase::PanelViewerBase(QWidget *parent, KrViewer::Mode mode) : QStackedWidget(parent), mimes(nullptr), cpart(nullptr), mode(mode) { setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored)); mimes = new QHash >(); cpart = nullptr; // NOTE: the fallback should be never visible. The viewer is not opened without a file and the // tab is closed if a file cannot be opened. fallback = new QLabel(i18n("No file selected or selected file cannot be displayed."), this); fallback->setAlignment(Qt::Alignment(QFlag(Qt::AlignCenter | Qt::TextExpandTabs))); fallback->setWordWrap(true); addWidget(fallback); setCurrentWidget(fallback); } PanelViewerBase::~PanelViewerBase() { // cpart->queryClose(); closeUrl(); QHashIterator< QString, QPointer > lit(*mimes); while (lit.hasNext()) { QPointer p = lit.next().value(); if (p) delete p; } mimes->clear(); delete mimes; delete fallback; } void PanelViewerBase::slotStatResult(KJob* job) { if (job->error()) { KMessageBox::error(this, job->errorString()); emit openUrlFinished(this, false); } else { - KIO::UDSEntry entry = static_cast(job)->statResult(); + KIO::UDSEntry entry = dynamic_cast(job)->statResult(); openFile(KFileItem(entry, curl)); } } KParts::ReadOnlyPart* PanelViewerBase::getPart(const QString& mimetype) { KParts::ReadOnlyPart* part = nullptr; if (mimes->find(mimetype) == mimes->end()) { part = createPart(mimetype); if(part) mimes->insert(mimetype, part); } else part = (*mimes)[mimetype]; return part; } void PanelViewerBase::openUrl(const QUrl& url) { closeUrl(); curl = url; emit urlChanged(this, url); if (url.isLocalFile()) openFile(KFileItem(url)); else { KIO::StatJob* statJob = KIO::stat(url, KIO::HideProgressInfo); connect(statJob, &KIO::StatJob::result, this, &PanelViewerBase::slotStatResult); } } /* ----==={ PanelViewer }===---- */ PanelViewer::PanelViewer(QWidget *parent, KrViewer::Mode mode) : PanelViewerBase(parent, mode) { } PanelViewer::~PanelViewer() = default; KParts::ReadOnlyPart* PanelViewer::getListerPart(bool hexMode) { KParts::ReadOnlyPart* part = nullptr; if (mimes->find(QLatin1String("krusader_lister")) == mimes->end()) { part = new Lister(this); mimes->insert(QLatin1String("krusader_lister"), part); } else part = (*mimes)[ QLatin1String("krusader_lister")]; if (part) { auto *lister = qobject_cast((KParts::ReadOnlyPart *)part); if (lister) lister->setHexMode(hexMode); } return part; } KParts::ReadOnlyPart* PanelViewer::getHexPart() { KParts::ReadOnlyPart* part = nullptr; if (KConfigGroup(krConfig, "General").readEntry("UseOktetaViewer", _UseOktetaViewer)) { if (mimes->find("oktetapart") == mimes->end()) { KPluginFactory* factory = nullptr; // Okteta >= 0.26 provides a desktop file, prefer that as the binary changes name KService::Ptr service = KService::serviceByDesktopName("oktetapart"); if (service) { factory = KPluginLoader(*service.data()).factory(); } else { // fallback to search for desktopfile-less old variant factory = KPluginLoader("oktetapart").factory(); } if (factory) { if ((part = factory->create(this, this))) mimes->insert("oktetapart", part); } } else part = (*mimes)["oktetapart"]; } return part ? part : getListerPart(true); } KParts::ReadOnlyPart* PanelViewer::getTextPart() { KParts::ReadOnlyPart* part = getPart("text/plain"); if(!part) part = getPart("all/allfiles"); return part ? part : getListerPart(); } KParts::ReadOnlyPart* PanelViewer::getDefaultPart(const KFileItem& fi) { KConfigGroup group(krConfig, "General"); QString modeString = group.readEntry("Default Viewer Mode", QString("generic")); KrViewer::Mode mode = KrViewer::Generic; if (modeString == "generic") mode = KrViewer::Generic; else if (modeString == "text") mode = KrViewer::Text; else if (modeString == "hex") mode = KrViewer::Hex; else if (modeString == "lister") mode = KrViewer::Lister; QMimeType mimeType = fi.determineMimeType(); bool isBinary = false; if (mode == KrViewer::Generic || mode == KrViewer::Lister) { isBinary = !mimeType.inherits(QStringLiteral("text/plain")); } KIO::filesize_t fileSize = fi.size(); KIO::filesize_t limit = (KIO::filesize_t)group.readEntry("Lister Limit", _ListerLimit) * 0x100000; QString mimetype = fi.mimetype(); KParts::ReadOnlyPart* part = nullptr; switch(mode) { case KrViewer::Generic: if ((mimetype.startsWith(QLatin1String("text/")) || mimetype.startsWith(QLatin1String("all/"))) && fileSize > limit) { part = getListerPart(isBinary); break; } else if ((part = getPart(mimetype))) { break; } #if __GNUC__ >= 7 [[gnu::fallthrough]]; #endif case KrViewer::Text: part = fileSize > limit ? getListerPart(false) : getTextPart(); break; case KrViewer::Lister: part = getListerPart(isBinary); break; case KrViewer::Hex: part = fileSize > limit ? getListerPart(true) : getHexPart(); break; default: abort(); } return part; } void PanelViewer::openFile(KFileItem fi) { switch(mode) { case KrViewer::Generic: cpart = getPart(fi.mimetype()); break; case KrViewer::Text: cpart = getTextPart(); break; case KrViewer::Lister: cpart = getListerPart(); break; case KrViewer::Hex: cpart = getHexPart(); break; case KrViewer::Default: cpart = getDefaultPart(fi); break; default: abort(); } if(cpart) { addWidget(cpart->widget()); setCurrentWidget(cpart->widget()); if (cpart->inherits("KParts::ReadWritePart")) - static_cast(cpart.data())->setReadWrite(false); + dynamic_cast(cpart.data())->setReadWrite(false); KParts::OpenUrlArguments args; args.setReload(true); cpart->setArguments(args); if (cpart->openUrl(curl)) { connect(cpart.data(), &KParts::ReadOnlyPart::destroyed, this, &PanelViewer::slotCPartDestroyed); emit openUrlFinished(this, true); return; } } setCurrentWidget(fallback); emit openUrlFinished(this, false); } bool PanelViewer::closeUrl() { setCurrentWidget(fallback); if (cpart && cpart->closeUrl()) { cpart = nullptr; return true; } return false; } KParts::ReadOnlyPart* PanelViewer::createPart(QString mimetype) { KParts::ReadOnlyPart * part = nullptr; KService::Ptr ptr = KMimeTypeTrader::self()->preferredService(mimetype, "KParts/ReadOnlyPart"); if (ptr) { QVariantList args; QVariant argsProp = ptr->property("X-KDE-BrowserView-Args"); if (argsProp.isValid()) args << argsProp; QVariant prop = ptr->property("X-KDE-BrowserView-AllowAsDefault"); if (!prop.isValid() || prop.toBool()) // defaults to true part = ptr->createInstance(this, this, args); } if (part) { KParts::BrowserExtension * ext = KParts::BrowserExtension::childObject(part); if (ext) { connect(ext, &KParts::BrowserExtension::openUrlRequestDelayed, this, &PanelViewer::openUrl); connect(ext, &KParts::BrowserExtension::openUrlRequestDelayed, this, &PanelViewer::openUrlRequest); } } return part; } /* ----==={ PanelEditor }===---- */ PanelEditor::PanelEditor(QWidget *parent, KrViewer::Mode mode) : PanelViewerBase(parent, mode) { } PanelEditor::~PanelEditor() = default; void PanelEditor::configureDeps() { KService::Ptr ptr = KMimeTypeTrader::self()->preferredService("text/plain", "KParts/ReadWritePart"); if (!ptr) ptr = KMimeTypeTrader::self()->preferredService("all/allfiles", "KParts/ReadWritePart"); if (!ptr) KMessageBox::sorry(nullptr, missingKPartMsg(), i18n("Missing Plugin"), KMessageBox::AllowLink); } QString PanelEditor::missingKPartMsg() { return i18nc("missing kpart - arg1 is a URL", "No text editor plugin available.
" "Internal editor will not work without this.
" "You can fix this by installing Kate:
%1", QString("%1").arg( "http://www.kde.org/applications/utilities/kate")); } void PanelEditor::openFile(KFileItem fi) { KIO::filesize_t fileSize = fi.size(); KConfigGroup group(krConfig, "General"); KIO::filesize_t limitMB = (KIO::filesize_t)group.readEntry("Lister Limit", _ListerLimit); QString mimetype = fi.mimetype(); if (mode == KrViewer::Generic) cpart = getPart(mimetype); if(fileSize > limitMB * 0x100000) { if(!cpart || mimetype.startsWith(QLatin1String("text/")) || mimetype.startsWith(QLatin1String("all/"))) { if(KMessageBox::Cancel == KMessageBox::warningContinueCancel(this, i18n("%1 is bigger than %2 MB" , curl.toDisplayString(QUrl::PreferLocalFile), limitMB))) { setCurrentWidget(fallback); emit openUrlFinished(this, false); return; } } } if (!cpart) cpart = getPart("text/plain"); if (!cpart) cpart = getPart("all/allfiles"); if (cpart) { addWidget(cpart->widget()); setCurrentWidget(cpart->widget()); KParts::OpenUrlArguments args; args.setReload(true); cpart->setArguments(args); if (cpart->openUrl(curl)) { connect(cpart.data(), &KParts::ReadOnlyPart::destroyed, this, &PanelEditor::slotCPartDestroyed); emit openUrlFinished(this, true); return; } // else: don't show error message - assume this has been done by the editor part } else KMessageBox::sorry(this, missingKPartMsg(), i18n("Cannot edit %1", curl.toDisplayString(QUrl::PreferLocalFile)), KMessageBox::AllowLink); setCurrentWidget(fallback); emit openUrlFinished(this, false); } bool PanelEditor::queryClose() { if (!cpart) return true; - return static_cast((KParts::ReadOnlyPart *)cpart)->queryClose(); + return dynamic_cast((KParts::ReadOnlyPart *)cpart)->queryClose(); } bool PanelEditor::closeUrl() { if (!cpart) return false; - static_cast((KParts::ReadOnlyPart *)cpart)->closeUrl(false); + dynamic_cast((KParts::ReadOnlyPart *)cpart)->closeUrl(false); setCurrentWidget(fallback); cpart = nullptr; return true; } KParts::ReadOnlyPart* PanelEditor::createPart(QString mimetype) { KParts::ReadWritePart * part = nullptr; KService::Ptr ptr = KMimeTypeTrader::self()->preferredService(mimetype, "KParts/ReadWritePart"); if (ptr) { QVariantList args; QVariant argsProp = ptr->property("X-KDE-BrowserView-Args"); if (argsProp.isValid()) args << argsProp; QVariant prop = ptr->property("X-KDE-BrowserView-AllowAsDefault"); if (!prop.isValid() || prop.toBool()) // defaults to true part = ptr->createInstance(this, this, args); } if (part) { KParts::BrowserExtension * ext = KParts::BrowserExtension::childObject(part); if (ext) { connect(ext, &KParts::BrowserExtension::openUrlRequestDelayed, this, &PanelEditor::openUrl); connect(ext, &KParts::BrowserExtension::openUrlRequestDelayed, this, &PanelEditor::openUrlRequest); } } return part; } bool PanelEditor::isModified() { if (cpart) - return static_cast((KParts::ReadOnlyPart *)cpart)->isModified(); + return dynamic_cast((KParts::ReadOnlyPart *)cpart)->isModified(); else return false; } diff --git a/krusader/Panel/PanelView/krinterbriefview.cpp b/krusader/Panel/PanelView/krinterbriefview.cpp index 9ec802f2..77391d4b 100644 --- a/krusader/Panel/PanelView/krinterbriefview.cpp +++ b/krusader/Panel/PanelView/krinterbriefview.cpp @@ -1,704 +1,704 @@ /***************************************************************************** * Copyright (C) 2009 Csaba Karai * * Copyright (C) 2009-2018 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 "krinterbriefview.h" // QtCore #include #include #include #include // QtGui #include #include #include // QtWidgets #include #include #include #include #include #include #include #include "krmousehandler.h" #include "krviewfactory.h" #include "krviewitemdelegate.h" #include "krviewitem.h" #include "listmodel.h" #include "../krcolorcache.h" #include "../FileSystem/krpermhandler.h" #include "../defaults.h" #include "../GUI/krstyleproxy.h" #define MAX_BRIEF_COLS 5 KrInterBriefView::KrInterBriefView(QWidget *parent, KrViewInstance &instance, KConfig *cfg) : QAbstractItemView(parent), KrInterView(instance, cfg, this), _header(nullptr) { setWidget(this); setModel(_model); setSelectionMode(QAbstractItemView::NoSelection); setSelectionModel(new DummySelectionModel(_model, this)); KConfigGroup grpSvr(_config, "Look&Feel"); _viewFont = grpSvr.readEntry("Filelist Font", _FilelistFont); auto *style = new KrStyleProxy(); style->setParent(this); setStyle(style); viewport()->setStyle(style); // for custom tooltip delay setItemDelegate(new KrViewItemDelegate()); setMouseTracking(true); setAcceptDrops(true); setDropIndicatorShown(true); connect(_mouseHandler, &KrMouseHandler::renameCurrentItem, this, &KrInterBriefView::renameCurrentItem); _model->setExtensionEnabled(false); _model->setAlternatingTable(true); connect(_model, &ListModel::layoutChanged, this, &KrInterBriefView::updateGeometries); } KrInterBriefView::~KrInterBriefView() { delete _properties; _properties = nullptr; delete _operator; _operator = nullptr; } void KrInterBriefView::doRestoreSettings(KConfigGroup group) { _properties->numberOfColumns = group.readEntry("Number Of Brief Columns", _NumberOfBriefColumns); if (_properties->numberOfColumns < 1) _properties->numberOfColumns = 1; else if (_properties->numberOfColumns > MAX_BRIEF_COLS) _properties->numberOfColumns = MAX_BRIEF_COLS; _numOfColumns = _properties->numberOfColumns; KrInterView::doRestoreSettings(group); updateGeometries(); } void KrInterBriefView::saveSettings(KConfigGroup grp, KrViewProperties::PropertyType properties) { KrInterView::saveSettings(grp, properties); if(properties & KrViewProperties::PropColumns) grp.writeEntry("Number Of Brief Columns", _numOfColumns); } int KrInterBriefView::itemsPerPage() { int height = getItemHeight(); if (height == 0) height ++; int numRows = viewport()->height() / height; return numRows; } void KrInterBriefView::updateView() { } void KrInterBriefView::setup() { _header = new QHeaderView(Qt::Horizontal, this); _header->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); _header->setParent(this); _header->setModel(_model); _header->hideSection(KrViewProperties::Type); _header->hideSection(KrViewProperties::Permissions); _header->hideSection(KrViewProperties::KrPermissions); _header->hideSection(KrViewProperties::Owner); _header->hideSection(KrViewProperties::Group); _header->hideSection(KrViewProperties::Changed); _header->hideSection(KrViewProperties::Accessed); _header->setStretchLastSection(true); _header->setSectionResizeMode(QHeaderView::Fixed); _header->setSectionsClickable(true); _header->setSortIndicatorShown(true); connect(_header, &QHeaderView::sortIndicatorChanged, _model, QOverload::of(&ListModel::sort)); _header->installEventFilter(this); _numOfColumns = _properties->numberOfColumns; setSortMode(_properties->sortColumn, (_properties->sortOptions & KrViewProperties::Descending)); } void KrInterBriefView::keyPressEvent(QKeyEvent *e) { if (!e || !_model->ready()) return ; // subclass bug if (handleKeyEvent(e)) return; QAbstractItemView::keyPressEvent(e); } bool KrInterBriefView::handleKeyEvent(QKeyEvent *e) { if ((e->key() != Qt::Key_Left && e->key() != Qt::Key_Right) && (KrView::handleKeyEvent(e))) // did the view class handled the event? return true; switch (e->key()) { case Qt::Key_Right : { if (e->modifiers() == Qt::ControlModifier) { // let the panel handle it e->ignore(); break; } KrViewItem *i = getCurrentKrViewItem(); KrViewItem *newCurrent = i; if (!i) break; int num = itemsPerPage() + 1; if (e->modifiers() & Qt::ShiftModifier) i->setSelected(!i->isSelected()); while (i && num > 0) { if (e->modifiers() & Qt::ShiftModifier) i->setSelected(!i->isSelected()); newCurrent = i; i = getNext(i); num--; } if (newCurrent) { setCurrentKrViewItem(newCurrent); makeCurrentVisible(); } if (e->modifiers() & Qt::ShiftModifier) op()->emitSelectionChanged(); return true; } case Qt::Key_Left : { if (e->modifiers() == Qt::ControlModifier) { // let the panel handle it e->ignore(); break; } KrViewItem *i = getCurrentKrViewItem(); KrViewItem *newCurrent = i; if (!i) break; int num = itemsPerPage() + 1; if (e->modifiers() & Qt::ShiftModifier) i->setSelected(!i->isSelected()); while (i && num > 0) { if (e->modifiers() & Qt::ShiftModifier) i->setSelected(!i->isSelected()); newCurrent = i; i = getPrev(i); num--; } if (newCurrent) { setCurrentKrViewItem(newCurrent); makeCurrentVisible(); } if (e->modifiers() & Qt::ShiftModifier) op()->emitSelectionChanged(); return true; } } return false; } void KrInterBriefView::wheelEvent(QWheelEvent *ev) { if (!_mouseHandler->wheelEvent(ev)) QApplication::sendEvent(horizontalScrollBar(), ev); } bool KrInterBriefView::eventFilter(QObject *object, QEvent *event) { if (object == _header) { if (event->type() == QEvent::ContextMenu) { auto *me = (QContextMenuEvent *)event; showContextMenu(me->globalPos()); return true; } } return false; } void KrInterBriefView::showContextMenu(const QPoint & p) { QMenu popup(this); popup.setTitle(i18n("Columns")); int COL_ID = 14700; for (int i = 1; i <= MAX_BRIEF_COLS; i++) { QAction *act = popup.addAction(QString("%1").arg(i)); act->setData(QVariant(COL_ID + i)); act->setCheckable(true); act->setChecked(properties()->numberOfColumns == i); } QAction * res = popup.exec(p); int result = -1; if (res && res->data().canConvert()) result = res->data().toInt(); if (result > COL_ID && result <= COL_ID + MAX_BRIEF_COLS) { _properties->numberOfColumns = result - COL_ID; _numOfColumns = _properties->numberOfColumns; updateGeometries(); op()->settingsChanged(KrViewProperties::PropColumns); } } QRect KrInterBriefView::visualRect(const QModelIndex&ndx) const { int width = (viewport()->width()) / _numOfColumns; if ((viewport()->width()) % _numOfColumns) width++; int height = getItemHeight(); int numRows = viewport()->height() / height; if (numRows == 0) numRows++; int x = width * (ndx.row() / numRows); int y = height * (ndx.row() % numRows); return mapToViewport(QRect(x, y, width, height)); } void KrInterBriefView::scrollTo(const QModelIndex &ndx, QAbstractItemView::ScrollHint hint) { const QRect rect = visualRect(ndx); if (hint == EnsureVisible && viewport()->rect().contains(rect)) { setDirtyRegion(rect); return; } const QRect area = viewport()->rect(); const bool leftOf = rect.left() < area.left(); const bool rightOf = rect.right() > area.right(); int horizontalValue = horizontalScrollBar()->value(); if (leftOf) horizontalValue -= area.left() - rect.left(); else if (rightOf) horizontalValue += rect.right() - area.right(); horizontalScrollBar()->setValue(horizontalValue); } QModelIndex KrInterBriefView::indexAt(const QPoint& p) const { int x = p.x() + horizontalOffset(); int y = p.y() + verticalOffset(); int itemWidth = (viewport()->width()) / _numOfColumns; if ((viewport()->width()) % _numOfColumns) itemWidth++; int itemHeight = getItemHeight(); int numRows = viewport()->height() / itemHeight; if (numRows == 0) numRows++; int row = y / itemHeight; int col = x / itemWidth; int numColsTotal = _model->rowCount() / numRows; if(_model->rowCount() % numRows) numColsTotal++; if(row < numRows && col < numColsTotal) return _model->index((col * numRows) + row, 0); return QModelIndex(); } QModelIndex KrInterBriefView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers) { if (_model->rowCount() == 0) return QModelIndex(); QModelIndex current = currentIndex(); if (!current.isValid()) return _model->index(0, 0); switch (cursorAction) { case MoveLeft: case MovePageDown: { int newRow = current.row() - itemsPerPage(); if (newRow < 0) newRow = 0; return _model->index(newRow, 0); } case MoveRight: case MovePageUp: { int newRow = current.row() + itemsPerPage(); if (newRow >= _model->rowCount()) newRow = _model->rowCount() - 1; return _model->index(newRow, 0); } case MovePrevious: case MoveUp: { int newRow = current.row() - 1; if (newRow < 0) newRow = 0; return _model->index(newRow, 0); } case MoveNext: case MoveDown: { int newRow = current.row() + 1; if (newRow >= _model->rowCount()) newRow = _model->rowCount() - 1; return _model->index(newRow, 0); } case MoveHome: return _model->index(0, 0); case MoveEnd: return _model->index(_model->rowCount() - 1, 0); } return current; } int KrInterBriefView::horizontalOffset() const { return horizontalScrollBar()->value(); } int KrInterBriefView::verticalOffset() const { return 0; } bool KrInterBriefView::isIndexHidden(const QModelIndex&ndx) const { return ndx.column() != 0; } #if 0 QRegion KrInterBriefView::visualRegionForSelection(const QItemSelection &selection) const { if (selection.isEmpty()) return QRegion(); QRegion selectionRegion; for (int i = 0; i < selection.count(); ++i) { QItemSelectionRange range = selection.at(i); if (!range.isValid()) continue; QModelIndex leftIndex = range.topLeft(); if (!leftIndex.isValid()) continue; const QRect leftRect = visualRect(leftIndex); int top = leftRect.top(); QModelIndex rightIndex = range.bottomRight(); if (!rightIndex.isValid()) continue; const QRect rightRect = visualRect(rightIndex); int bottom = rightRect.bottom(); if (top > bottom) qSwap(top, bottom); int height = bottom - top + 1; QRect combined = leftRect | rightRect; combined.setX(range.left()); selectionRegion += combined; } return selectionRegion; } #endif void KrInterBriefView::paintEvent(QPaintEvent *e) { QStyleOptionViewItem option = viewOptions(); option.widget = this; option.decorationSize = QSize(_fileIconSize, _fileIconSize); option.decorationPosition = QStyleOptionViewItem::Left; QPainter painter(viewport()); QModelIndex curr = currentIndex(); QVector intersectVector; QRect area = e->rect(); area.adjust(horizontalOffset(), verticalOffset(), horizontalOffset(), verticalOffset()); intersectionSet(area, intersectVector); foreach(const QModelIndex &mndx, intersectVector) { option.state = QStyle::State_None; option.rect = visualRect(mndx); painter.save(); itemDelegate()->paint(&painter, option, mndx); // (always) draw dashed line border around current item row const bool isCurrent = curr.isValid() && curr.row() == mndx.row(); if (isCurrent && drawCurrent()) { QStyleOptionFocusRect o; o.QStyleOption::operator=(option); QPalette::ColorGroup cg = QPalette::Normal; o.backgroundColor = option.palette.color(cg, QPalette::Background); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, &painter); } painter.restore(); } } int KrInterBriefView::getItemHeight() const { int textHeight = QFontMetrics(_viewFont).height(); int height = textHeight; int iconSize = 0; if (properties()->displayIcons) iconSize = _fileIconSize; if (iconSize > textHeight) height = iconSize; if (height == 0) height++; return height; } void KrInterBriefView::updateGeometries() { if (_header) { QSize hint = _header->sizeHint(); setViewportMargins(0, hint.height(), 0, 0); QRect vg = viewport()->geometry(); QRect geometryRect(vg.left(), vg.top() - hint.height(), vg.width(), hint.height()); _header->setGeometry(geometryRect); int items = 0; for (int i = 0; i != _header->count(); i++) if (!_header->isSectionHidden(i)) items++; if (items == 0) items++; int sectWidth = viewport()->width() / items; for (int i = 0; i != _header->count(); i++) if (!_header->isSectionHidden(i)) _header->resizeSection(i, sectWidth); QMetaObject::invokeMethod(_header, "updateGeometries"); } if (_model->rowCount() <= 0) horizontalScrollBar()->setRange(0, 0); else { int itemsPerColumn = viewport()->height() / getItemHeight(); if (itemsPerColumn <= 0) itemsPerColumn = 1; int columnWidth = (viewport()->width()) / _numOfColumns; if ((viewport()->width()) % _numOfColumns) columnWidth++; int maxWidth = _model->rowCount() / itemsPerColumn; if (_model->rowCount() % itemsPerColumn) maxWidth++; maxWidth *= columnWidth; if (maxWidth > viewport()->width()) { horizontalScrollBar()->setSingleStep(columnWidth); horizontalScrollBar()->setPageStep(columnWidth * _numOfColumns); horizontalScrollBar()->setRange(0, maxWidth - viewport()->width()); } else { horizontalScrollBar()->setRange(0, 0); } } QAbstractItemView::updateGeometries(); } void KrInterBriefView::setSortMode(KrViewProperties::ColumnType sortColumn, bool descending) { Qt::SortOrder sortDir = descending ? Qt::DescendingOrder : Qt::AscendingOrder; _header->setSortIndicator(sortColumn, sortDir); } int KrInterBriefView::elementWidth(const QModelIndex & index) { QString text = index.data(Qt::DisplayRole).toString(); int textWidth = QFontMetrics(_viewFont).width(text); const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; textWidth += 2 * textMargin; QVariant decor = index.data(Qt::DecorationRole); if (decor.isValid() && decor.type() == QVariant::Pixmap) { QPixmap p = decor.value(); textWidth += p.width() + 2 * textMargin; } return textWidth; } void KrInterBriefView::intersectionSet(const QRect &rect, QVector &ndxList) { int maxNdx = _model->rowCount(); int width = (viewport()->width()) / _numOfColumns; if ((viewport()->width()) % _numOfColumns) width++; int height = getItemHeight(); int items = viewport()->height() / height; if (items == 0) items++; int xmin = -1; int ymin = -1; int xmax = -1; int ymax = -1; xmin = rect.x() / width; ymin = rect.y() / height; xmax = (rect.x() + rect.width()) / width; if ((rect.x() + rect.width()) % width) xmax++; ymax = (rect.y() + rect.height()) / height; if ((rect.y() + rect.height()) % height) ymax++; for (int i = ymin; i < ymax; i++) for (int j = xmin; j < xmax; j++) { int ndx = j * items + i; if (ndx < maxNdx) ndxList.append(_model->index(ndx, 0)); } } QRect KrInterBriefView::itemRect(const FileItem *item) { return visualRect(_model->fileItemIndex(item)); } void KrInterBriefView::copySettingsFrom(KrView *other) { if(other->instance() == instance()) { // the other view is of the same type - auto *v = static_cast(other); + auto *v = dynamic_cast(other); int column = v->_model->lastSortOrder(); Qt::SortOrder sortDir = v->_model->lastSortDir(); _header->setSortIndicator(column, sortDir); _model->sort(column, sortDir); setFileIconSize(v->fileIconSize()); } } void KrInterBriefView::setFileIconSize(int size) { KrView::setFileIconSize(size); setIconSize(QSize(fileIconSize(), fileIconSize())); updateGeometries(); } void KrInterBriefView::currentChanged(const QModelIndex & current, const QModelIndex & previous) { if (_model->ready()) { KrViewItem * item = getKrViewItem(currentIndex()); op()->emitCurrentChanged(item); } QAbstractItemView::currentChanged(current, previous); } void KrInterBriefView::renameCurrentItem() { QModelIndex cIndex = currentIndex(); QModelIndex nameIndex = _model->index(cIndex.row(), KrViewProperties::Name); edit(nameIndex); updateEditorData(); update(nameIndex); } bool KrInterBriefView::event(QEvent * e) { _mouseHandler->otherEvent(e); return QAbstractItemView::event(e); } void KrInterBriefView::mousePressEvent(QMouseEvent * ev) { if (!_mouseHandler->mousePressEvent(ev)) QAbstractItemView::mousePressEvent(ev); } void KrInterBriefView::mouseReleaseEvent(QMouseEvent * ev) { if (!_mouseHandler->mouseReleaseEvent(ev)) QAbstractItemView::mouseReleaseEvent(ev); } void KrInterBriefView::mouseDoubleClickEvent(QMouseEvent *ev) { if (!_mouseHandler->mouseDoubleClickEvent(ev)) QAbstractItemView::mouseDoubleClickEvent(ev); } void KrInterBriefView::mouseMoveEvent(QMouseEvent * ev) { if (!_mouseHandler->mouseMoveEvent(ev)) QAbstractItemView::mouseMoveEvent(ev); } void KrInterBriefView::dragEnterEvent(QDragEnterEvent *ev) { if (!_mouseHandler->dragEnterEvent(ev)) QAbstractItemView::dragEnterEvent(ev); } void KrInterBriefView::dragMoveEvent(QDragMoveEvent *ev) { QAbstractItemView::dragMoveEvent(ev); _mouseHandler->dragMoveEvent(ev); } void KrInterBriefView::dragLeaveEvent(QDragLeaveEvent *ev) { if (!_mouseHandler->dragLeaveEvent(ev)) QAbstractItemView::dragLeaveEvent(ev); } void KrInterBriefView::dropEvent(QDropEvent *ev) { if (!_mouseHandler->dropEvent(ev)) QAbstractItemView::dropEvent(ev); } QRect KrInterBriefView::mapToViewport(const QRect &rect) const { if (!rect.isValid()) return rect; QRect result = rect; int dx = -horizontalOffset(); int dy = -verticalOffset(); result.adjust(dx, dy, dx, dy); return result; } diff --git a/krusader/Panel/PanelView/krinterdetailedview.cpp b/krusader/Panel/PanelView/krinterdetailedview.cpp index b9815769..23d7c17a 100644 --- a/krusader/Panel/PanelView/krinterdetailedview.cpp +++ b/krusader/Panel/PanelView/krinterdetailedview.cpp @@ -1,450 +1,450 @@ /***************************************************************************** * Copyright (C) 2002 Shie Erlich * * Copyright (C) 2002 Rafi Yanai * * Copyright (C) 2004-2018 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 "krinterdetailedview.h" // QtCore #include #include // QtWidgets #include #include #include #include #include #include #include #include #include "krviewfactory.h" #include "krviewitemdelegate.h" #include "krviewitem.h" #include "listmodel.h" #include "../FileSystem/krpermhandler.h" #include "../defaults.h" #include "../krglobal.h" #include "krmousehandler.h" #include "../krcolorcache.h" #include "../GUI/krstyleproxy.h" KrInterDetailedView::KrInterDetailedView(QWidget *parent, KrViewInstance &instance, KConfig *cfg): QTreeView(parent), KrInterView(instance, cfg, this), _autoResizeColumns(true) { connect(_mouseHandler, &KrMouseHandler::renameCurrentItem, this, &KrInterDetailedView::renameCurrentItem); setWidget(this); KConfigGroup grpSvr(_config, "Look&Feel"); _viewFont = grpSvr.readEntry("Filelist Font", _FilelistFont); setModel(_model); setRootIsDecorated(false); setItemsExpandable(false); setAllColumnsShowFocus(true); setUniformRowHeights(true); setMouseTracking(true); setAcceptDrops(true); setDropIndicatorShown(true); setSelectionMode(QAbstractItemView::NoSelection); setSelectionModel(new DummySelectionModel(_model, this)); header()->installEventFilter(this); header()->setSectionResizeMode(QHeaderView::Interactive); header()->setStretchLastSection(false); auto *style = new KrStyleProxy(); style->setParent(this); setStyle(style); viewport()->setStyle(style); // for custom tooltip delay setItemDelegate(new KrViewItemDelegate(this)); connect(header(), &QHeaderView::sectionResized, this, &KrInterDetailedView::sectionResized); connect(header(), &QHeaderView::sectionMoved, this, &KrInterDetailedView::sectionMoved); } KrInterDetailedView::~KrInterDetailedView() { delete _properties; _properties = nullptr; delete _operator; _operator = nullptr; } void KrInterDetailedView::currentChanged(const QModelIndex & current, const QModelIndex & previous) { if (_model->ready()) { KrViewItem * item = getKrViewItem(currentIndex()); op()->emitCurrentChanged(item); } QTreeView::currentChanged(current, previous); } void KrInterDetailedView::doRestoreSettings(KConfigGroup grp) { auto headerView = header(); _autoResizeColumns = grp.readEntry("AutoResizeColumns", true); QByteArray savedState = grp.readEntry("Saved State", QByteArray()); if (savedState.isEmpty()) { hideColumn(KrViewProperties::Type); hideColumn(KrViewProperties::Permissions); hideColumn(KrViewProperties::Owner); hideColumn(KrViewProperties::Group); hideColumn(KrViewProperties::Changed); hideColumn(KrViewProperties::Accessed); headerView->resizeSection(KrViewProperties::Ext, QFontMetrics(_viewFont).width("tar.bz2 ")); headerView->resizeSection(KrViewProperties::KrPermissions, QFontMetrics(_viewFont).width("rwx ")); headerView->resizeSection(KrViewProperties::Size, QFontMetrics(_viewFont).width("9") * 10); QDateTime tmp(QDate(2099, 12, 29), QTime(23, 59)); QString desc = QLocale().toString(tmp, QLocale::ShortFormat) + " "; headerView->resizeSection(KrViewProperties::Modified, QFontMetrics(_viewFont).width(desc)); } else { headerView->restoreState(savedState); // do not show new columns by default; restoreState() shows columns not saved if (KrGlobal::sCurrentConfigVersion < KrGlobal::sConfigVersion) { hideColumn(KrViewProperties::Changed); hideColumn(KrViewProperties::Accessed); } _model->setExtensionEnabled(!isColumnHidden(KrViewProperties::Ext)); } // In case a column is assigned zero size for some reason, it's impossible to fix this from interface. // We correct this problem by enforcing the minimum width of the column. auto minSize = headerView->minimumSectionSize(); for (int i = KrViewProperties::Ext; i < KrViewProperties::MAX_COLUMNS; i++) { if (!headerView->isSectionHidden(i) && headerView->sectionSize(i) < minSize) { headerView->resizeSection(i, minSize); } } KrInterView::doRestoreSettings(grp); } void KrInterDetailedView::saveSettings(KConfigGroup grp, KrViewProperties::PropertyType properties) { KrInterView::saveSettings(grp, properties); grp.writeEntry("AutoResizeColumns", _autoResizeColumns); if(properties & KrViewProperties::PropColumns) { QByteArray state = header()->saveState(); grp.writeEntry("Saved State", state); } } int KrInterDetailedView::itemsPerPage() { QRect rect = visualRect(currentIndex()); if (!rect.isValid()) { for (int i = 0; i != _model->rowCount(); i++) { rect = visualRect(_model->index(i, 0)); if (rect.isValid()) break; } } if (!rect.isValid()) return 0; int size = (height() - header()->height()) / rect.height(); if (size < 0) size = 0; return size; } void KrInterDetailedView::updateView() { } void KrInterDetailedView::setup() { setSortMode(_properties->sortColumn, (_properties->sortOptions & KrViewProperties::Descending)); setSortingEnabled(true); } void KrInterDetailedView::keyPressEvent(QKeyEvent *e) { if (!e || !_model->ready()) return ; // subclass bug if (handleKeyEvent(e)) // did the view class handled the event? return; QTreeView::keyPressEvent(e); } void KrInterDetailedView::mousePressEvent(QMouseEvent * ev) { if (!_mouseHandler->mousePressEvent(ev)) QTreeView::mousePressEvent(ev); } void KrInterDetailedView::mouseReleaseEvent(QMouseEvent * ev) { if (!_mouseHandler->mouseReleaseEvent(ev)) QTreeView::mouseReleaseEvent(ev); } void KrInterDetailedView::mouseDoubleClickEvent(QMouseEvent *ev) { if (!_mouseHandler->mouseDoubleClickEvent(ev)) QTreeView::mouseDoubleClickEvent(ev); } void KrInterDetailedView::mouseMoveEvent(QMouseEvent * ev) { if (!_mouseHandler->mouseMoveEvent(ev)) QTreeView::mouseMoveEvent(ev); } void KrInterDetailedView::wheelEvent(QWheelEvent *ev) { if (!_mouseHandler->wheelEvent(ev)) QTreeView::wheelEvent(ev); } void KrInterDetailedView::dragEnterEvent(QDragEnterEvent *ev) { if (!_mouseHandler->dragEnterEvent(ev)) QTreeView::dragEnterEvent(ev); } void KrInterDetailedView::dragMoveEvent(QDragMoveEvent *ev) { QTreeView::dragMoveEvent(ev); _mouseHandler->dragMoveEvent(ev); } void KrInterDetailedView::dragLeaveEvent(QDragLeaveEvent *ev) { if (!_mouseHandler->dragLeaveEvent(ev)) QTreeView::dragLeaveEvent(ev); } void KrInterDetailedView::dropEvent(QDropEvent *ev) { if (!_mouseHandler->dropEvent(ev)) QTreeView::dropEvent(ev); } bool KrInterDetailedView::event(QEvent * e) { _mouseHandler->otherEvent(e); return QTreeView::event(e); } void KrInterDetailedView::renameCurrentItem() { QModelIndex cIndex = currentIndex(); QModelIndex nameIndex = _model->index(cIndex.row(), KrViewProperties::Name); edit(nameIndex); updateEditorData(); update(nameIndex); } bool KrInterDetailedView::eventFilter(QObject *object, QEvent *event) { if (object == header()) { if (event->type() == QEvent::ContextMenu) { auto *me = (QContextMenuEvent *)event; showContextMenu(me->globalPos()); return true; } else if (event->type() == QEvent::Resize) { recalculateColumnSizes(); return false; } } return false; } void KrInterDetailedView::showContextMenu(const QPoint & p) { QMenu popup(this); popup.setTitle(i18n("Columns")); QVector actions; for(int i = KrViewProperties::Ext; i < KrViewProperties::MAX_COLUMNS; i++) { QString text = (_model->headerData(i, Qt::Horizontal)).toString(); QAction *act = popup.addAction(text); act->setCheckable(true); act->setChecked(!header()->isSectionHidden(i)); act->setData(i); actions.append(act); } popup.addSeparator(); QAction *actAutoResize = popup.addAction(i18n("Automatically Resize Columns")); actAutoResize->setCheckable(true); actAutoResize->setChecked(_autoResizeColumns); QAction *res = popup.exec(p); if (res == nullptr) return; if(res == actAutoResize) { _autoResizeColumns = actAutoResize->isChecked(); recalculateColumnSizes(); } else { int column = res->data().toInt(); if(header()->isSectionHidden(column)) header()->showSection(column); else header()->hideSection(column); if(KrViewProperties::Ext == column) _model->setExtensionEnabled(!header()->isSectionHidden(KrViewProperties::Ext)); } op()->settingsChanged(KrViewProperties::PropColumns); } void KrInterDetailedView::sectionResized(int /*column*/, int oldSize, int newSize) { // *** taken from dolphin *** // If the user changes the size of the headers, the autoresize feature should be // turned off. As there is no dedicated interface to find out whether the header // section has been resized by the user or by a resize event, another approach is used. // Attention: Take care when changing the if-condition to verify that there is no // regression in combination with bug 178630 (see fix in comment #8). if ((QApplication::mouseButtons() & Qt::LeftButton) && header()->underMouse()) { _autoResizeColumns = false; op()->settingsChanged(KrViewProperties::PropColumns); } if (oldSize == newSize || !_model->ready()) return; recalculateColumnSizes(); } void KrInterDetailedView::sectionMoved(int /*logicalIndex*/, int /*oldVisualIndex*/, int /*newVisualIndex*/) { op()->settingsChanged(KrViewProperties::PropColumns); } void KrInterDetailedView::recalculateColumnSizes() { if(!_autoResizeColumns) return; int sum = 0; for (int i = 0; i != _model->columnCount(); i++) { if (!isColumnHidden(i)) sum += header()->sectionSize(i); } if (sum != header()->width()) { int delta = sum - header()->width(); int nameSize = header()->sectionSize(KrViewProperties::Name); if (nameSize - delta > 20) header()->resizeSection(KrViewProperties::Name, nameSize - delta); } } bool KrInterDetailedView::viewportEvent(QEvent * event) { if (event->type() == QEvent::ToolTip) { // only show tooltip if column is not wide enough to show all text. In this case the column // data text is abbreviated and the full text is shown as tooltip, see ListModel::data(). - auto *he = static_cast(event); + auto *he = dynamic_cast(event); const QModelIndex index = indexAt(he->pos()); // name column has a detailed tooltip if (index.isValid() && index.column() != KrViewProperties::Name) { int width = header()->sectionSize(index.column()); QString text = index.data(Qt::DisplayRole).toString(); int textWidth = QFontMetrics(_viewFont).width(text); const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; textWidth += 2 * textMargin; QVariant decor = index.data(Qt::DecorationRole); if (decor.isValid() && decor.type() == QVariant::Pixmap) { QPixmap p = decor.value(); textWidth += p.width() + 2 * textMargin; } if (textWidth <= width) { QToolTip::hideText(); event->accept(); return true; } } } return QTreeView::viewportEvent(event); } void KrInterDetailedView::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const { QTreeView::drawRow(painter, options, index); // (may) draw dashed line border around current item row. This is done internally in // QTreeView::drawRow() only when panel is focused, we have to repeat it here. if (index == currentIndex() && drawCurrent()) { QStyleOptionFocusRect o; o.backgroundColor = options.palette.color(QPalette::Normal, QPalette::Background); const QRect focusRect(0, options.rect.y(), header()->length(), options.rect.height()); o.rect = style()->visualRect(layoutDirection(), viewport()->rect(), focusRect); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); } } void KrInterDetailedView::setSortMode(KrViewProperties::ColumnType sortColumn, bool descending) { Qt::SortOrder sortDir = descending ? Qt::DescendingOrder : Qt::AscendingOrder; sortByColumn(sortColumn, sortDir); } void KrInterDetailedView::setFileIconSize(int size) { KrView::setFileIconSize(size); setIconSize(QSize(fileIconSize(), fileIconSize())); } QRect KrInterDetailedView::itemRect(const FileItem *item) { QRect r = visualRect(_model->fileItemIndex(item)); r.setLeft(0); r.setWidth(header()->length()); return r; } void KrInterDetailedView::copySettingsFrom(KrView *other) { if(other->instance() == instance()) { // the other view is of the same type - auto *v = static_cast(other); + auto *v = dynamic_cast(other); _autoResizeColumns = v->_autoResizeColumns; header()->restoreState(v->header()->saveState()); _model->setExtensionEnabled(!isColumnHidden(KrViewProperties::Ext)); recalculateColumnSizes(); setFileIconSize(v->fileIconSize()); } } diff --git a/krusader/Panel/PanelView/krviewitemdelegate.cpp b/krusader/Panel/PanelView/krviewitemdelegate.cpp index f82d09d6..3bdcf6c5 100644 --- a/krusader/Panel/PanelView/krviewitemdelegate.cpp +++ b/krusader/Panel/PanelView/krviewitemdelegate.cpp @@ -1,153 +1,153 @@ /***************************************************************************** * Copyright (C) 2009 Csaba Karai * * Copyright (C) 2009-2018 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 "krviewitemdelegate.h" #include "krviewproperties.h" #include "../krglobal.h" #include "../listpanel.h" // QtGui #include #include // QtWidgets #include #include #include #include KrViewItemDelegate::KrViewItemDelegate(QObject *parent) : QItemDelegate(parent), _currentlyEdited(-1), _dontDraw(false) {} void KrViewItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; opt.state &= ~QStyle::State_Selected; _dontDraw = (_currentlyEdited == index.row()) && (index.column() == KrViewProperties::Ext); QItemDelegate::paint(painter, opt, index); } void KrViewItemDelegate::drawDisplay(QPainter * painter, const QStyleOptionViewItem & option, const QRect & rect, const QString & text) const { if (!_dontDraw) QItemDelegate::drawDisplay(painter, option, rect, text); } QWidget * KrViewItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &sovi, const QModelIndex &index) const { _currentlyEdited = index.row(); return QItemDelegate::createEditor(parent, sovi, index); } void KrViewItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QItemDelegate::setEditorData(editor, index); auto *lineEdit = qobject_cast (editor); if (lineEdit) { KConfigGroup gl(krConfig, "Look&Feel"); QFont font = index.data(Qt::FontRole).value(); lineEdit->setFont(font); if (gl.readEntry("Rename Selects Extension", true)) lineEdit->selectAll(); else { QString nameWithoutExt = index.data(Qt::UserRole).toString(); lineEdit->deselect(); lineEdit->setSelection(0, nameWithoutExt.length()); } } } QSize KrViewItemDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const { QSize size = QItemDelegate::sizeHint(option, index); if (size.isEmpty()) { // prevent items without text from bloating the view vertically return QSize(0, 0); } return size; } bool KrViewItemDelegate::eventFilter(QObject *object, QEvent *event) { QWidget *editor = qobject_cast(object); if (!editor) return false; if (event->type() == QEvent::KeyPress) { - switch (static_cast(event)->key()) { + switch (dynamic_cast(event)->key()) { case Qt::Key_Tab: case Qt::Key_Backtab: _currentlyEdited = -1; emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache); return true; case Qt::Key_Enter: case Qt::Key_Return: if (auto *e = qobject_cast(editor)) { if (!e->hasAcceptableInput()) return true; event->accept(); emit commitData(editor); emit closeEditor(editor, QAbstractItemDelegate::SubmitModelCache); _currentlyEdited = -1; return true; } return false; case Qt::Key_Escape: event->accept(); // don't commit data _currentlyEdited = -1; emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache); break; default: return false; } if (editor->parentWidget()) editor->parentWidget()->setFocus(); return true; } else if (event->type() == QEvent::FocusOut) { if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) { QWidget *w = QApplication::focusWidget(); while (w) { // don't worry about focus changes internally in the editor if (w == editor) return false; w = w->parentWidget(); } // Opening a modal dialog will start a new eventloop // that will process the deleteLater event. if (QApplication::activeModalWidget() && !QApplication::activeModalWidget()->isAncestorOf(editor) && qobject_cast(QApplication::activeModalWidget())) return false; _currentlyEdited = -1; // manually set focus back to panel after rename canceled by focusing another window ACTIVE_PANEL->gui->slotFocusOnMe(); emit closeEditor(editor, RevertModelCache); } } else if (event->type() == QEvent::ShortcutOverride) { - const QKeyEvent *ke = static_cast(event); + const QKeyEvent *ke = dynamic_cast(event); if (ke->key() == Qt::Key_Escape || (ke->key() == Qt::Key_Backspace && ke->modifiers() == Qt::ControlModifier)) { event->accept(); return true; } } return false; } diff --git a/krusader/Panel/krsearchbar.cpp b/krusader/Panel/krsearchbar.cpp index 5b5d48d7..78b31266 100644 --- a/krusader/Panel/krsearchbar.cpp +++ b/krusader/Panel/krsearchbar.cpp @@ -1,445 +1,446 @@ /***************************************************************************** * Copyright (C) 2010 Jan Lepper * * Copyright (C) 2010-2018 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 "krsearchbar.h" #include "PanelView/krview.h" #include "PanelView/krviewitem.h" #include "../FileSystem/dirlisterinterface.h" #include "../defaults.h" #include "../krglobal.h" #include "../icon.h" #include #include #include #include #include #include #include #include #include KrSearchBar::KrSearchBar(KrView *view, QWidget *parent) : QWidget(parent), _view(nullptr), _rightArrowEntersDirFlag(true) { // close button auto *closeButton = new QToolButton(this); closeButton->setAutoRaise(true); closeButton->setIcon(Icon(QStringLiteral("dialog-close"))); closeButton->setToolTip(i18n("Close the search bar")); connect(closeButton, &QToolButton::clicked, this, &KrSearchBar::hideBar); // combo box for changing search mode _modeBox = new QComboBox(this); _modeBox->addItems(QStringList() << i18n("Search") << i18n("Select") << i18n("Filter")); _modeBox->setToolTip(i18n("Change the search mode")); connect(_modeBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KrSearchBar::onModeChange); _currentMode = static_cast(_modeBox->currentIndex()); // combo box for entering search string _textBox = new KComboBox(this); _textBox->setEditable(true); _modeBox->setToolTip(i18n("Enter or select search string")); QStringList savedSearches = KConfigGroup(krConfig, "Private") .readEntry("Predefined Selections", QStringList()); if (savedSearches.count() > 0) _textBox->addItems(savedSearches); _textBox->setCurrentText(""); _textBox->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); connect(_textBox, &KComboBox::currentTextChanged, this, &KrSearchBar::onSearchChange); auto *saveSearchBtn = new QToolButton(this); saveSearchBtn->setIcon(Icon("document-save")); saveSearchBtn->setFixedSize(20, 20); saveSearchBtn->setToolTip(i18n("Save the current search string")); connect(saveSearchBtn, &QToolButton::clicked, this, &KrSearchBar::saveSearchString); _openSelectDialogBtn = new QToolButton(this); _openSelectDialogBtn->setIcon(Icon("configure")); _openSelectDialogBtn->setFixedSize(20, 20); _openSelectDialogBtn->setToolTip(i18n("Open selection dialog")); auto *layout = new QHBoxLayout(this); layout->setMargin(0); layout->addWidget(closeButton); layout->addWidget(_modeBox); layout->addWidget(_textBox); layout->addWidget(saveSearchBtn); layout->addWidget(_openSelectDialogBtn); _textBox->installEventFilter(this); setView(view); } void KrSearchBar::setView(KrView *view) { if (_view) { _view->widget()->removeEventFilter(this); disconnect(_openSelectDialogBtn, nullptr, nullptr, nullptr); } _view = view; connect(_openSelectDialogBtn, &QToolButton::clicked, [this](){ _view->customSelection(true); }); _view->widget()->installEventFilter(this); } void KrSearchBar::hideBarIfSearching() { if (_currentMode == MODE_SEARCH) hideBar(); } // #### public slots void KrSearchBar::showBar(SearchMode mode) { int index = mode == MODE_DEFAULT ? KConfigGroup(krConfig, "Look&Feel") .readEntry("Default Search Mode", QString::number(KrSearchBar::MODE_SEARCH)) .toInt() : mode; _modeBox->setCurrentIndex(index); show(); _textBox->setFocus(); _rightArrowEntersDirFlag = true; } void KrSearchBar::hideBar() { resetSearch(); if (_textBox->hasFocus()) _view->widget()->setFocus(); hide(); } void KrSearchBar::resetSearch() { _textBox->clearEditText(); indicateMatch(true); } // #### protected slots void KrSearchBar::onModeChange() { if (_currentMode == MODE_FILTER) { _view->op()->filterSearch(QString(), true); // reset filter } _currentMode = static_cast(_modeBox->currentIndex()); onSearchChange(); } void KrSearchBar::onSearchChange() { const QString text = _textBox->currentText(); switch(_currentMode) { case MODE_SEARCH: { const bool anyMatch = _view->op()->searchItem(text, caseSensitive()); indicateMatch(anyMatch); break; } case MODE_SELECT: { _view->unselectAll(); if (!text.isEmpty()) { const bool anyMatch = _view->changeSelection(KRQuery(text, caseSensitive()), true); indicateMatch(anyMatch); } break; } case MODE_FILTER: { const bool anyMatch =_view->op()->filterSearch(text, caseSensitive()); indicateMatch(anyMatch); break; } default: qWarning() << "unexpected search mode: " << _currentMode; } _textBox->setFocus(); } void KrSearchBar::saveSearchString() { KConfigGroup group(krConfig, "Private"); QStringList lst = group.readEntry("Predefined Selections", QStringList()); QString searchString = _textBox->currentText(); if (lst.indexOf(searchString) != -1) { // already saved return; } lst.append(searchString); group.writeEntry("Predefined Selections", lst); _textBox->addItem(searchString); QToolTip::showText(QCursor::pos(), i18n("Saved search text to history")); } // #### protected void KrSearchBar::keyPressEvent(QKeyEvent *event) { const bool handled = handleKeyPressEvent(static_cast(event)); if (handled) { return; } QWidget::keyPressEvent(event); } bool KrSearchBar::eventFilter(QObject *watched, QEvent *event) { if (event->type() != QEvent::ShortcutOverride && watched == _view->widget()) { + // NOTE: cannot use dynamic cast here auto *ke = static_cast(event); // overwrite "escape" shortcut if bar is shown if ((ke->key() == Qt::Key_Escape) && (ke->modifiers() == Qt::NoModifier) && !isHidden()) { ke->accept(); handleKeyPressEvent(ke); return true; } } if (event->type() != QEvent::KeyPress) { return false; } qDebug() << "key press event=" << event; auto *ke = static_cast(event); auto modifiers = ke->modifiers(); if (watched == _view->widget()) { KConfigGroup grpSv(krConfig, "Look&Feel"); const bool autoShow = grpSv.readEntry("New Style Quicksearch", _NewStyleQuicksearch); if (isHidden() && !autoShow) { return false; } if (!isHidden()) { // view widget has focus but search bar is open and may wants to steal key events const bool handled = handleKeyPressEvent(ke); if (handled) { return true; } } if (isHidden() || // view can handle its own event if user does not want to remove text or... !((ke->key() == Qt::Key_Backspace && !_textBox->currentText().isEmpty()) || // ...insert space in search bar (even if not focused) (ke->key() == Qt::Key_Space && _currentMode == KrSearchBar::MODE_SEARCH))) { const bool handled = _view->handleKeyEvent(ke); if (handled) { return true; } } if (ke->text().isEmpty() || (modifiers != Qt::NoModifier && modifiers != Qt::ShiftModifier && modifiers != Qt::KeypadModifier)) { return false; } // start searching if bar is hidden? if (isHidden()) { if (autoShow) { showBar(); } else { return false; } } // bar is visible and gets the key input _textBox->setFocus(); if (ke->key() == Qt::Key_Backspace) { _textBox->lineEdit()->backspace(); } else { _textBox->setEditText(_textBox->currentText().append(ke->text())); } return true; } else if (watched == _textBox) { const bool handled = handleKeyPressEvent(ke); if (handled) { _view->widget()->setFocus(); return true; } // allow the view to handle (most) key events from the text box if ((modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier) && ke->key() != Qt::Key_Space && ke->key() != Qt::Key_Backspace && ke->key() != Qt::Key_Left && ke->key() != Qt::Key_Right) { const bool handled = _view->handleKeyEvent(ke); if (handled) { _view->widget()->setFocus(); return true; } } } return false; } // #### private bool KrSearchBar::handleKeyPressEvent(QKeyEvent *ke) { auto modifiers = ke->modifiers(); if (!(modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier)) { return false; } switch (ke->key()) { case Qt::Key_Escape: { hideBar(); return true; } case Qt::Key_Enter: case Qt::Key_Return: { hideBarIfSearching(); return false; } case Qt::Key_Up: return handleUpDownKeyPress(true); case Qt::Key_Down: return handleUpDownKeyPress(false); case Qt::Key_Left: case Qt::Key_Right: return handleLeftRightKeyPress(ke); case Qt::Key_Insert: { // select current item and jump to next search result KrViewItem * item = _view->getCurrentKrViewItem(); if (item) { item->setSelected(!item->isSelected()); _view->op()->searchItem(_textBox->currentText(), caseSensitive(), 1); } return true; } case Qt::Key_Home: { // jump to first search result KrViewItem * item = _view->getLast(); if (item) { _view->setCurrentKrViewItem(_view->getLast()); _view->op()->searchItem(_textBox->currentText(), caseSensitive(), 1); } return true; } case Qt::Key_End: { // jump to last search result KrViewItem * item = _view->getFirst(); if (item) { _view->setCurrentKrViewItem(_view->getFirst()); _view->op()->searchItem(_textBox->currentText(), caseSensitive(), -1); } return true; } } return false; } bool KrSearchBar::handleUpDownKeyPress(bool up) { if (_currentMode != MODE_SEARCH) { return false; } const bool updownCancel = KConfigGroup(krConfig, "Look&Feel") .readEntry("Up/Down Cancels Quicksearch", false); if (updownCancel) { hideBar(); return false; } const bool anyMatch = _view->op()->searchItem(_textBox->currentText(), caseSensitive(), up ? -1 : 1); indicateMatch(anyMatch); return true; } bool KrSearchBar::handleLeftRightKeyPress(QKeyEvent *ke) { const bool useQuickDirectoryNavigation = KConfigGroup(krConfig, "Look&Feel") .readEntry("Navigation with Right Arrow Quicksearch", true); if (!useQuickDirectoryNavigation) return false; const bool isRight = ke->key() == Qt::Key_Right; if (isRight && _rightArrowEntersDirFlag) { // in case the Right Arrow has been pressed when cursor is in the end of the line if (_textBox->cursorPosition() == _textBox->currentText().length()) // we let the view enter the directory if it's selected return _view->handleKeyEvent(ke); } else { _rightArrowEntersDirFlag = false; } return false; } void KrSearchBar::indicateMatch(bool anyMatch) { KConfigGroup gc(krConfig, "Colors"); QPalette p = QGuiApplication::palette(); QString foreground, background; if (anyMatch) { foreground = "Quicksearch Match Foreground"; background = "Quicksearch Match Background"; } else { foreground = "Quicksearch Non-match Foreground"; background = "Quicksearch Non-match Background"; } QColor fore = Qt::black; QString foreSetting = gc.readEntry(foreground, QString()); if (foreSetting == "KDE default") { fore = p.color(QPalette::Active, QPalette::Text); } else if (!foreSetting.isEmpty()) { fore = gc.readEntry(foreground, fore); } QColor back = anyMatch ? QColor(192, 255, 192) : QColor(255, 192, 192); QString backSetting = gc.readEntry(background, QString()); if (backSetting == "KDE default") { back = p.color(QPalette::Active, QPalette::Base); } else if (!backSetting.isEmpty()) { back = gc.readEntry(background, back); } QPalette pal = palette(); pal.setColor(QPalette::Base, back); pal.setColor(QPalette::Text, fore); _textBox->lineEdit()->setPalette(pal); } bool KrSearchBar::caseSensitive() { KConfigGroup grpSvr(krConfig, "Look&Feel"); return grpSvr.readEntry("Case Sensitive Quicksearch", _CaseSensitiveQuicksearch); } diff --git a/krusader/Panel/listpanel.cpp b/krusader/Panel/listpanel.cpp index 94938f6b..d50d5b2c 100644 --- a/krusader/Panel/listpanel.cpp +++ b/krusader/Panel/listpanel.cpp @@ -1,1379 +1,1379 @@ /***************************************************************************** * Copyright (C) 2000 Shie Erlich * * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2004-2018 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 "listpanel.h" // QtCore #include #include #include #include #include #include #include // QtGui #include #include #include #include #include #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dirhistoryqueue.h" #include "krcolorcache.h" #include "krerrordisplay.h" #include "krlayoutfactory.h" #include "krpreviewpopup.h" #include "krsearchbar.h" #include "listpanelactions.h" #include "panelcontextmenu.h" #include "panelfunc.h" #include "sidebar.h" #include "viewactions.h" #include "PanelView/krview.h" #include "PanelView/krviewfactory.h" #include "PanelView/krviewitem.h" #include "../defaults.h" #include "../icon.h" #include "../krservices.h" #include "../krslots.h" #include "../krusader.h" #include "../krusaderview.h" #include "../Archive/krarchandler.h" #include "../BookMan/krbookmarkbutton.h" #include "../FileSystem/fileitem.h" #include "../FileSystem/filesystem.h" #include "../FileSystem/krpermhandler.h" #include "../FileSystem/sizecalculator.h" #include "../Dialogs/krdialogs.h" #include "../Dialogs/krspwidgets.h" #include "../Dialogs/krsqueezedtextlabel.h" #include "../Dialogs/percentalsplitter.h" #include "../Dialogs/popularurls.h" #include "../GUI/dirhistorybutton.h" #include "../GUI/kcmdline.h" #include "../GUI/mediabutton.h" #include "../MountMan/kmountman.h" #include "../UserAction/useractionpopupmenu.h" class ActionButton : public QToolButton { public: ActionButton(QWidget *parent, ListPanel *panel, QAction *action, const QString& text = QString()) : QToolButton(parent), panel(panel), action(action) { setText(text); setAutoRaise(true); if(KConfigGroup(krConfig, "ListPanelButtons").readEntry("Icons", false) || text.isEmpty()) setIcon(action->icon()); setToolTip(action->toolTip()); } protected: void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE { panel->slotFocusOnMe(); action->trigger(); } ListPanel *panel; QAction *action; }; ///////////////////////////////////////////////////// // The list panel constructor // ///////////////////////////////////////////////////// ListPanel::ListPanel(QWidget *parent, AbstractPanelManager *manager, const KConfigGroup& cfg) : QWidget(parent), KrPanel(manager, this, new ListPanelFunc(this)), panelType(-1), colorMask(255), compareMode(false), previewJob(nullptr), inlineRefreshJob(nullptr), searchBar(nullptr), cdRootButton(nullptr), cdUpButton(nullptr), sidebarButton(nullptr), sidebar(nullptr), fileSystemError(nullptr), _navigatorUrl(), _tabState(TabState::DEFAULT) { if(cfg.isValid()) panelType = cfg.readEntry("Type", -1); if (panelType == -1) panelType = defaultPanelType(); _actions = krApp->listPanelActions(); setAcceptDrops(true); QHash widgets; #define ADD_WIDGET(widget) widgets.insert(#widget, widget); // media button mediaButton = new MediaButton(this); connect(mediaButton, &MediaButton::aboutToShow, this, [=]() { slotFocusOnMe(); }); connect(mediaButton, &MediaButton::openUrl, [=](const QUrl & _t1) { func->openUrl(_t1); }); connect(mediaButton, &MediaButton::newTab, this, [=](const QUrl &url) { newTab(url); }); ADD_WIDGET(mediaButton); // status bar status = new KrSqueezedTextLabel(this); KConfigGroup group(krConfig, "Look&Feel"); status->setFont(group.readEntry("Filelist Font", _FilelistFont)); status->setAutoFillBackground(false); status->setText(""); // needed for initialization code! status->setWhatsThis(i18n("The statusbar displays information about the filesystem " "which holds your current folder: total size, free space, " "type of filesystem, etc.")); ADD_WIDGET(status); // back button backButton = new ActionButton(this, this, _actions->actHistoryBackward); ADD_WIDGET(backButton); // forward button forwardButton = new ActionButton(this, this, _actions->actHistoryForward); ADD_WIDGET(forwardButton); // ... create the history button historyButton = new DirHistoryButton(func->history, this); connect(historyButton, &DirHistoryButton::aboutToShow, this, [=]() { slotFocusOnMe(); }); connect(historyButton, &DirHistoryButton::gotoPos, func, &ListPanelFunc::historyGotoPos); ADD_WIDGET(historyButton); // bookmarks button bookmarksButton = new KrBookmarkButton(this); connect(bookmarksButton, &KrBookmarkButton::aboutToShow, this, [=]() { slotFocusOnMe(); }); connect(bookmarksButton, &KrBookmarkButton::openUrl, [=](const QUrl & _t1) { func->openUrl(_t1); }); bookmarksButton->setWhatsThis(i18n("Open menu with bookmarks. You can also add " "current location to the list, edit bookmarks " "or add subfolder to the list.")); ADD_WIDGET(bookmarksButton); // url input field urlNavigator = new KUrlNavigator(new KFilePlacesModel(this), QUrl(), this); urlNavigator->setWhatsThis(i18n("Name of folder where you are. You can also " "enter name of desired location to move there. " "Use of Net protocols like ftp or fish is possible.")); // handle certain key events here in event filter urlNavigator->editor()->installEventFilter(this); urlNavigator->setUrlEditable(isNavigatorEditModeSet()); urlNavigator->setShowFullPath(group.readEntry("Navigator Full Path", false)); connect(urlNavigator, &KUrlNavigator::returnPressed, this, [=]() { slotFocusOnMe(); }); connect(urlNavigator, &KUrlNavigator::urlChanged, this, &ListPanel::slotNavigatorUrlChanged); connect(urlNavigator->editor()->lineEdit(), &QLineEdit::editingFinished, this, &ListPanel::resetNavigatorMode); connect(urlNavigator, &KUrlNavigator::tabRequested, this, [=](const QUrl &url) { ListPanel::newTab(url); }); connect(urlNavigator, &KUrlNavigator::urlsDropped, this, QOverload::of(&ListPanel::handleDrop)); ADD_WIDGET(urlNavigator); // toolbar QWidget * toolbar = new QWidget(this); auto * toolbarLayout = new QHBoxLayout(toolbar); toolbarLayout->setContentsMargins(0, 0, 0, 0); toolbarLayout->setSpacing(0); ADD_WIDGET(toolbar); fileSystemError = new KrErrorDisplay(this); fileSystemError->setWordWrap(true); fileSystemError->hide(); ADD_WIDGET(fileSystemError); // client area clientArea = new QWidget(this); auto *clientLayout = new QVBoxLayout(clientArea); clientLayout->setSpacing(0); clientLayout->setContentsMargins(0, 0, 0, 0); ADD_WIDGET(clientArea); // totals label totals = new KrSqueezedTextLabel(this); totals->setFont(group.readEntry("Filelist Font", _FilelistFont)); totals->setAutoFillBackground(false); totals->setWhatsThis(i18n("The totals bar shows how many files exist, " "how many selected and the bytes math")); ADD_WIDGET(totals); // free space label freeSpace = new KrSqueezedTextLabel(this); freeSpace->setFont(group.readEntry("Filelist Font", _FilelistFont)); freeSpace->setAutoFillBackground(false); freeSpace->setText(""); freeSpace->setAlignment(Qt::AlignRight | Qt::AlignVCenter); ADD_WIDGET(freeSpace); // progress indicator and cancel button for the quick calc size quickSizeCalcProgress = new QProgressBar(this); quickSizeCalcProgress->hide(); ADD_WIDGET(quickSizeCalcProgress); cancelQuickSizeCalcButton = new QToolButton(this); cancelQuickSizeCalcButton->hide(); cancelQuickSizeCalcButton->setIcon(Icon("dialog-cancel")); cancelQuickSizeCalcButton->setToolTip(i18n("Cancel directory space calculation")); ADD_WIDGET(cancelQuickSizeCalcButton); // progress indicator for the preview job previewProgress = new QProgressBar(this); previewProgress->hide(); ADD_WIDGET(previewProgress); // a cancel button for the filesystem refresh and preview job cancelProgressButton = new QToolButton(this); cancelProgressButton->hide(); cancelProgressButton->setIcon(Icon("dialog-cancel")); connect(cancelProgressButton, &QToolButton::clicked, this, &ListPanel::cancelProgress); ADD_WIDGET(cancelProgressButton); // button for changing the panel sidebar position in the panel sidebarPositionButton = new QToolButton(this); sidebarPositionButton->hide(); sidebarPositionButton->setAutoRaise(true); sidebarPositionButton->setIcon(Icon("exchange-positions")); sidebarPositionButton->setToolTip(i18n("Move Sidebar clockwise")); connect(sidebarPositionButton, &QToolButton::clicked, [this]() { // moving position clockwise setSidebarPosition((sidebarPosition() + 1) % 4); }); ADD_WIDGET(sidebarPositionButton); // a quick button to open the sidebar sidebarButton = new QToolButton(this); sidebarButton->setAutoRaise(true); sidebarButton->setIcon(Icon("arrow-up")); connect(sidebarButton, &QToolButton::clicked, this, &ListPanel::toggleSidebar); sidebarButton->setToolTip(i18n("Open the Sidebar")); ADD_WIDGET(sidebarButton); #undef ADD_WIDGET // toolbar buttons cdOtherButton = new ActionButton(toolbar, this, _actions->actCdToOther, "="); toolbarLayout->addWidget(cdOtherButton); cdUpButton = new ActionButton(toolbar, this, _actions->actDirUp, ".."); toolbarLayout->addWidget(cdUpButton); cdHomeButton = new ActionButton(toolbar, this, _actions->actHome, "~"); toolbarLayout->addWidget(cdHomeButton); cdRootButton = new ActionButton(toolbar, this, _actions->actRoot, "/"); toolbarLayout->addWidget(cdRootButton); // create the button for sync-browsing syncBrowseButton = new QToolButton(toolbar); syncBrowseButton->setIcon(Icon("kr_syncbrowse_off")); syncBrowseButton->setCheckable(true); const QString syncBrowseText = i18n("This button toggles the sync-browse mode.\n" "When active, each folder change is performed in the\n" "active and inactive panel - if possible."); syncBrowseButton->setText(syncBrowseText); syncBrowseButton->setToolTip(syncBrowseText); connect(syncBrowseButton, &QToolButton::toggled, [=](bool checked) { syncBrowseButton->setIcon( Icon(checked ? "kr_syncbrowse_on" : "kr_syncbrowse_off")); }); syncBrowseButton->setAutoRaise(true); toolbarLayout->addWidget(syncBrowseButton); setButtons(); // create a splitter to hold the view and the sidebar sidebarSplitter = new PercentalSplitter(clientArea); sidebarSplitter->setChildrenCollapsible(true); sidebarSplitter->setOrientation(Qt::Horizontal); // expand vertical if splitter orientation is horizontal QSizePolicy sizePolicy = sidebarSplitter->sizePolicy(); sizePolicy.setVerticalPolicy(QSizePolicy::Expanding); sidebarSplitter->setSizePolicy(sizePolicy); clientLayout->addWidget(sidebarSplitter); // view createView(); // search (in folder) bar searchBar = new KrSearchBar(view, clientArea); searchBar->hide(); bool top = group.readEntry("Quicksearch Position", "bottom") == "top"; clientLayout->insertWidget(top ? 0 : -1, searchBar); // create the layout KrLayoutFactory fact(this, widgets); QLayout *layout = fact.createLayout(); if(!layout) { // fallback: create a layout by ourself auto *v = new QVBoxLayout; v->setContentsMargins(0, 0, 0, 0); v->setSpacing(0); auto *h = new QHBoxLayout; h->setContentsMargins(0, 0, 0, 0); h->setSpacing(0); h->addWidget(urlNavigator); h->addWidget(toolbar); h->addStretch(); v->addLayout(h); h = new QHBoxLayout; h->setContentsMargins(0, 0, 0, 0); h->setSpacing(0); h->addWidget(mediaButton); h->addWidget(status); h->addWidget(backButton); h->addWidget(forwardButton); h->addWidget(historyButton); h->addWidget(bookmarksButton); v->addLayout(h); v->addWidget(fileSystemError); v->addWidget(clientArea); h = new QHBoxLayout; h->setContentsMargins(0, 0, 0, 0); h->setSpacing(0); h->addWidget(totals); h->addWidget(freeSpace); h->addWidget(quickSizeCalcProgress); h->addWidget(cancelQuickSizeCalcButton); h->addWidget(previewProgress); h->addWidget(cancelProgressButton); h->addWidget(sidebarButton); v->addLayout(h); layout = v; } setLayout(layout); connect(&KrColorCache::getColorCache(), &KrColorCache::colorsRefreshed, this, QOverload<>::of(&ListPanel::refreshColors)); connect(krApp, &Krusader::shutdown, this, &ListPanel::cancelProgress); } ListPanel::~ListPanel() { cancelProgress(); delete view; view = nullptr; delete func; delete status; delete bookmarksButton; delete totals; delete urlNavigator; delete cdRootButton; delete cdHomeButton; delete cdUpButton; delete cdOtherButton; delete syncBrowseButton; // delete layout; } void ListPanel::reparent(QWidget *parent, AbstractPanelManager *manager) { setParent(parent); _manager = manager; } int ListPanel::defaultPanelType() { KConfigGroup group(krConfig, "Look&Feel"); return group.readEntry("Default Panel Type", KrViewFactory::defaultViewId()); } bool ListPanel::isNavigatorEditModeSet() { KConfigGroup group(krConfig, "Look&Feel"); return group.readEntry("Navigator Edit Mode", false); } void ListPanel::createView() { view = KrViewFactory::createView(panelType, sidebarSplitter, krConfig); view->init(); view->setMainWindow(krApp); // KrViewFactory may create a different view type than requested panelType = view->instance()->id(); if(this == ACTIVE_PANEL) view->prepareForActive(); else view->prepareForPassive(); view->refreshColors(); sidebarSplitter->insertWidget(sidebarPosition() < 2 ? 1 : 0, view->widget()); view->widget()->installEventFilter(this); connect(view->op(), &KrViewOperator::quickCalcSpace, func, &ListPanelFunc::quickCalcSpace); connect(view->op(), &KrViewOperator::goHome, func, &ListPanelFunc::home); connect(view->op(), &KrViewOperator::dirUp, func, &ListPanelFunc::dirUp); connect(view->op(), &KrViewOperator::defaultDeleteFiles, func, &ListPanelFunc::defaultDeleteFiles); connect(view->op(), &KrViewOperator::middleButtonClicked, this, QOverload::of(&ListPanel::newTab)); connect(view->op(), &KrViewOperator::currentChanged, this, &ListPanel::slotCurrentChanged); connect(view->op(), &KrViewOperator::renameItem, func, QOverload::of(&ListPanelFunc::rename)); connect(view->op(), &KrViewOperator::executed, func, &ListPanelFunc::execute); connect(view->op(), &KrViewOperator::goInside, func, &ListPanelFunc::goInside); connect(view->op(), &KrViewOperator::needFocus, this, [=]() { slotFocusOnMe(); }); connect(view->op(), &KrViewOperator::selectionChanged, this, &ListPanel::slotUpdateTotals); connect(view->op(), &KrViewOperator::itemDescription, krApp, &Krusader::statusBarUpdate); connect(view->op(), &KrViewOperator::contextMenu, this, &ListPanel::popRightClickMenu); connect(view->op(), &KrViewOperator::emptyContextMenu, this, &ListPanel::popEmptyRightClickMenu); connect(view->op(), &KrViewOperator::letsDrag, this, &ListPanel::startDragging); connect(view->op(), &KrViewOperator::gotDrop, this, [this](QDropEvent *event) {handleDrop(event, true); }); connect(view->op(), &KrViewOperator::previewJobStarted, this, &ListPanel::slotPreviewJobStarted); connect(view->op(), &KrViewOperator::refreshActions, krApp->viewActions(), &ViewActions::refreshActions); connect(view->op(), &KrViewOperator::currentChanged, func->history, &DirHistoryQueue::saveCurrentItem); connect(view->op(), &KrViewOperator::goBack, func, &ListPanelFunc::historyBackward); connect(view->op(), &KrViewOperator::goForward, func, &ListPanelFunc::historyForward); view->setFiles(func->files()); func->refreshActions(); } void ListPanel::changeType(int type) { if (panelType != type) { QString current = view->getCurrentItem(); QList selection = view->selectedUrls(); bool filterApplysToDirs = view->properties()->filterApplysToDirs; KrViewProperties::FilterSpec filter = view->filter(); FilterSettings filterSettings = view->properties()->filterSettings; panelType = type; KrView *oldView = view; createView(); searchBar->setView(view); delete oldView; view->setFilter(filter, filterSettings, filterApplysToDirs); view->setSelectionUrls(selection); view->setCurrentItem(current); view->makeItemVisible(view->getCurrentKrViewItem()); } } int ListPanel::getProperties() { int props = 0; if (syncBrowseButton->isChecked()) { props |= PROP_SYNC_BUTTON_ON; } if (isLocked()) { props |= PROP_LOCKED; } else if (isPinned()) { props |= PROP_PINNED; } return props; } void ListPanel::setProperties(int prop) { syncBrowseButton->setChecked(prop & PROP_SYNC_BUTTON_ON); if (prop & PROP_LOCKED) { _tabState = TabState::LOCKED; } else if (prop & PROP_PINNED) { _tabState = TabState::PINNED; } else { _tabState = TabState::DEFAULT; } } bool ListPanel::eventFilter(QObject * watched, QEvent * e) { if(view && watched == view->widget()) { if(e->type() == QEvent::FocusIn && this != ACTIVE_PANEL && !isHidden()) slotFocusOnMe(); else if(e->type() == QEvent::ShortcutOverride) { - auto *ke = static_cast(e); + auto *ke = dynamic_cast(e); if(ke->key() == Qt::Key_Escape && ke->modifiers() == Qt::NoModifier) { // if the cancel refresh action has no shortcut assigned, // we need this event ourselves to cancel refresh if(_actions->actCancelRefresh->shortcut().isEmpty()) { e->accept(); return true; } } } } // handle URL navigator key events else if(watched == urlNavigator->editor()) { // override default shortcut for panel focus if(e->type() == QEvent::ShortcutOverride) { - auto *ke = static_cast(e); + auto *ke = dynamic_cast(e); if ((ke->key() == Qt::Key_Escape) && (ke->modifiers() == Qt::NoModifier)) { e->accept(); // we will get the key press event now return true; } } else if(e->type() == QEvent::KeyPress) { - auto *ke = static_cast(e); + auto *ke = dynamic_cast(e); if ((ke->key() == Qt::Key_Down) && (ke->modifiers() == Qt::ControlModifier)) { slotFocusOnMe(); return true; } else if ((ke->key() == Qt::Key_Escape) && (ke->modifiers() == Qt::NoModifier)) { // reset navigator urlNavigator->editor()->setUrl(urlNavigator->locationUrl()); slotFocusOnMe(); return true; } } } return false; } void ListPanel::toggleSidebar() { if(!sidebar) { sidebar = new Sidebar(sidebarSplitter); // fix vertical grow of splitter (and entire window) if its content // demands more space QSizePolicy sizePolicy = sidebar->sizePolicy(); sizePolicy.setVerticalPolicy(QSizePolicy::Ignored); sidebar->setSizePolicy(sizePolicy); connect(this, &ListPanel::pathChanged, sidebar, &Sidebar::onPanelPathChange); connect(sidebar, &Sidebar::urlActivated, SLOTS, &KRslots::refresh); sidebarSplitter->insertWidget(0, sidebar); } if (sidebar->isHidden()) { if (sidebarSplitterSizes.count() > 0) { sidebarSplitter->setSizes(sidebarSplitterSizes); } else { // on the first time, resize to 50% QList lst; lst << height() / 2 << height() / 2; sidebarSplitter->setSizes(lst); } sidebar->show(); sidebarButton->setIcon(Icon("arrow-down")); sidebarButton->setToolTip(i18n("Close the Sidebar")); sidebarPositionButton->show(); } else { sidebarSplitterSizes.clear(); sidebarSplitterSizes = sidebarSplitter->sizes(); sidebar->hide(); sidebarButton->setIcon(Icon("arrow-up")); sidebarButton->setToolTip(i18n("Open the Sidebar")); sidebarPositionButton->hide(); QList lst; lst << height() << 0; sidebarSplitter->setSizes(lst); if (ACTIVE_PANEL) ACTIVE_PANEL->gui->slotFocusOnMe(); } } QString ListPanel::lastLocalPath() const { return _lastLocalPath; } void ListPanel::setButtons() { KConfigGroup group(krConfig, "Look&Feel"); mediaButton->setVisible(group.readEntry("Media Button Visible", true)); backButton->setVisible(group.readEntry("Back Button Visible", false)); forwardButton->setVisible(group.readEntry("Forward Button Visible", false)); historyButton->setVisible(group.readEntry("History Button Visible", true)); bookmarksButton->setVisible(group.readEntry("Bookmarks Button Visible", true)); if (group.readEntry("Panel Toolbar visible", _PanelToolBar)) { cdRootButton->setVisible(group.readEntry("Root Button Visible", _cdRoot)); cdHomeButton->setVisible(group.readEntry("Home Button Visible", _cdHome)); cdUpButton->setVisible(group.readEntry("Up Button Visible", _cdUp)); cdOtherButton->setVisible(group.readEntry("Equal Button Visible", _cdOther)); syncBrowseButton->setVisible(group.readEntry("SyncBrowse Button Visible", _syncBrowseButton)); } else { cdRootButton->hide(); cdHomeButton->hide(); cdUpButton->hide(); cdOtherButton->hide(); syncBrowseButton->hide(); } } void ListPanel::slotUpdateTotals() { totals->setText(view->statistics()); } void ListPanel::compareDirs(bool otherPanelToo) { // Performs a check in order to avoid that the next code is executed twice if (otherPanelToo == true) { // If both panels are showing the same directory if (_manager->currentPanel()->virtualPath() == otherPanel()->virtualPath()) { if (KMessageBox::warningContinueCancel(this, i18n("Warning: The left and the right side are showing the same folder.")) != KMessageBox::Continue) { return; } } } KConfigGroup pg(krConfig, "Private"); int compareMode = pg.readEntry("Compare Mode", 0); KConfigGroup group(krConfig, "Look&Feel"); bool selectDirs = group.readEntry("Mark Dirs", false); KrViewItem *item, *otherItem; for (item = view->getFirst(); item != nullptr; item = view->getNext(item)) { if (item->name() == "..") continue; for (otherItem = otherPanel()->view->getFirst(); otherItem != nullptr && otherItem->name() != item->name(); otherItem = otherPanel()->view->getNext(otherItem)); bool isSingle = (otherItem == nullptr), isDifferent = false, isNewer = false; if (func->getFileItem(item)->isDir() && !selectDirs) { item->setSelected(false); continue; } if (otherItem) { if (!func->getFileItem(item)->isDir()) isDifferent = otherPanel()->func->getFileItem(otherItem)->getSize() != func->getFileItem(item)->getSize(); isNewer = func->getFileItem(item)->getTime_t() > otherPanel()->func->getFileItem(otherItem)->getTime_t(); } switch (compareMode) { case 0: item->setSelected(isNewer || isSingle); break; case 1: item->setSelected(isNewer); break; case 2: item->setSelected(isSingle); break; case 3: item->setSelected(isDifferent || isSingle); break; case 4: item->setSelected(isDifferent); break; } } view->updateView(); if (otherPanelToo) otherPanel()->gui->compareDirs(false); } void ListPanel::refreshColors() { view->refreshColors(); emit refreshColors(this == ACTIVE_PANEL); } void ListPanel::slotFocusOnMe(bool focus) { if (focus && _manager->currentPanel() != this) { // ignore focus request if this panel is not shown return; } krApp->setUpdatesEnabled(false); if(focus) { emit activate(); _actions->activePanelChanged(); func->refreshActions(); slotCurrentChanged(view->getCurrentKrViewItem()); view->prepareForActive(); otherPanel()->gui->slotFocusOnMe(false); } else { // in case a new url was entered but not refreshed to, // reset url navigator to the current url setNavigatorUrl(virtualPath()); view->prepareForPassive(); } urlNavigator->setActive(focus); refreshColors(); krApp->setUpdatesEnabled(true); } // this is used to start the panel ////////////////////////////////////////////////////////////////// void ListPanel::start(const QUrl &url) { QUrl startUrl(url); if (!startUrl.isValid()) startUrl = QUrl::fromLocalFile(ROOT_DIR); _lastLocalPath = startUrl.isLocalFile() ? startUrl.path() : ROOT_DIR; func->openUrl(startUrl); setJumpBack(startUrl); } void ListPanel::slotStartUpdate(bool directoryChange) { if (inlineRefreshJob) inlineRefreshListResult(nullptr); setCursor(Qt::BusyCursor); const QUrl currentUrl = virtualPath(); if (directoryChange) { if (this == ACTIVE_PANEL) { slotFocusOnMe(); } if (currentUrl.isLocalFile()) _lastLocalPath = currentUrl.path(); setNavigatorUrl(currentUrl); emit pathChanged(currentUrl); krApp->popularUrls()->addUrl(currentUrl); searchBar->hideBar(); } if (compareMode) otherPanel()->view->refresh(); // return cursor to normal arrow setCursor(Qt::ArrowCursor); slotUpdateTotals(); } void ListPanel::updateFilesystemStats(const QString &metaInfo, const QString &fsType, KIO::filesize_t total, KIO::filesize_t free) { QString statusText, mountPoint, freeSpaceText; if (!metaInfo.isEmpty()) { statusText = metaInfo; mountPoint = freeSpaceText = ""; } else { const int perc = total == 0 ? 0 : (int)(((float)free / (float)total) * 100.0); mountPoint = func->files()->mountPoint(); statusText = i18nc("%1=free space,%2=total space,%3=percentage of usage, " "%4=mountpoint,%5=filesystem type", "%1 free out of %2 (%3%) on %4 [(%5)]", KIO::convertSize(free), KIO::convertSize(total), perc, mountPoint, fsType); freeSpaceText = " " + i18n("%1 free", KIO::convertSize(free)); } status->setText(statusText); freeSpace->setText(freeSpaceText); mediaButton->updateIcon(mountPoint); } void ListPanel::handleDrop(QDropEvent *event, bool onView) { // check what was dropped const QList urls = KUrlMimeData::urlsFromMimeData(event->mimeData()); if (urls.isEmpty()) { event->ignore(); // not for us to handle! return; } // find dropping destination QString destinationDir = ""; const bool dragFromThisPanel = event->source() == this; const KrViewItem *item = onView ? view->getKrViewItemAt(event->pos()) : nullptr; if (item) { const FileItem *file = item->getFileItem(); if (file && !file->isDir() && dragFromThisPanel) { event->ignore(); // dragging on files in same panel, ignore return; } else if (!file || file->isDir()) { // item is ".." dummy or a directory destinationDir = item->name(); } } else if (dragFromThisPanel) { event->ignore(); // dragged from this panel onto an empty spot in this panel, ignore return; } QUrl destination = QUrl(virtualPath()); destination.setPath(destination.path() + '/' + destinationDir); func->files()->dropFiles(destination, event); if(KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) { KrPanel *p = dragFromThisPanel ? this : otherPanel(); p->view->saveSelection(); p->view->unselectAll(); } } void ListPanel::handleDrop(const QUrl &destination, QDropEvent *event) { func->files()->dropFiles(destination, event); } void ListPanel::startDragging(const QStringList& names, const QPixmap& px) { if (names.isEmpty()) { // avoid dragging empty urls return; } QList urls = func->files()->getUrls(names); auto *drag = new QDrag(this); auto *mimeData = new QMimeData; drag->setPixmap(px); mimeData->setUrls(urls); drag->setMimeData(mimeData); drag->start(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction); } // pops a right-click menu for items void ListPanel::popRightClickMenu(const QPoint &loc) { // run it, on the mouse location int j = QFontMetrics(font()).height() * 2; PanelContextMenu::run(QPoint(loc.x() + 5, loc.y() + j), this); } void ListPanel::popEmptyRightClickMenu(const QPoint &loc) { PanelContextMenu::run(loc, this); } QString ListPanel::getCurrentName() { QString name = view->getCurrentItem(); if (name != "..") return name; else return QString(); } QStringList ListPanel::getSelectedNames() { QStringList fileNames; view->getSelectedItems(&fileNames); return fileNames; } void ListPanel::prepareToDelete() { const bool skipCurrent = (view->numSelected() == 0); view->setNameToMakeCurrent(view->firstUnmarkedBelowCurrent(skipCurrent)); } void ListPanel::keyPressEvent(QKeyEvent *e) { switch (e->key()) { case Qt::Key_Enter : case Qt::Key_Return : if (e->modifiers() & Qt::ControlModifier) { if (e->modifiers() & Qt::AltModifier) { FileItem *fileitem = func->files()->getFileItem(view->getCurrentKrViewItem()->name()); if (fileitem && fileitem->isDir()) newTab(fileitem->getUrl(), true); } else { SLOTS->insertFileName((e->modifiers()&Qt::ShiftModifier)!=0); } } else { e->ignore(); } break; case Qt::Key_Right : case Qt::Key_Left : if (e->modifiers() == Qt::ControlModifier) { // user pressed CTRL+Right/Left - refresh other panel to the selected path if it's a // directory otherwise as this one if ((isLeft() && e->key() == Qt::Key_Right) || (!isLeft() && e->key() == Qt::Key_Left)) { QUrl newPath; KrViewItem *it = view->getCurrentKrViewItem(); if (it->name() == "..") { newPath = KIO::upUrl(virtualPath()); } else { FileItem *v = func->getFileItem(it); // If it's a directory different from ".." if (v && v->isDir() && v->getName() != "..") { newPath = v->getUrl(); } else { // If it's a supported compressed file if (v && KRarcHandler::arcSupported(v->getMime())) { newPath = func->browsableArchivePath(v->getUrl().fileName()); } else { newPath = virtualPath(); } } } otherPanel()->func->openUrl(newPath); } else { func->openUrl(otherPanel()->virtualPath()); } return; } else e->ignore(); break; case Qt::Key_Down : if (e->modifiers() == Qt::ControlModifier) { // give the keyboard focus to the command line if (MAIN_VIEW->cmdLine()->isVisible()) MAIN_VIEW->cmdLineFocus(); else MAIN_VIEW->focusTerminalEmulator(); return; } else if (e->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { // give the keyboard focus to TE MAIN_VIEW->focusTerminalEmulator(); } else e->ignore(); break; case Qt::Key_Up : if (e->modifiers() == Qt::ControlModifier) { // give the keyboard focus to the url navigator editLocation(); return; } else e->ignore(); break; case Qt::Key_Escape: cancelProgress(); break; default: // if we got this, it means that the view is not doing // the quick search thing, so send the characters to the commandline, if normal key if (e->modifiers() == Qt::NoModifier) MAIN_VIEW->cmdLine()->addText(e->text()); //e->ignore(); } } void ListPanel::showEvent(QShowEvent *e) { panelVisible(); QWidget::showEvent(e); } void ListPanel::hideEvent(QHideEvent *e) { panelHidden(); QWidget::hideEvent(e); } void ListPanel::panelVisible() { func->setPaused(false); } void ListPanel::panelHidden() { func->setPaused(true); } void ListPanel::slotPreviewJobStarted(KJob *job) { previewJob = job; connect(job, SIGNAL(percent(KJob*,ulong)), SLOT(slotPreviewJobPercent(KJob*,ulong))); connect(job, &KJob::result, this, &ListPanel::slotPreviewJobResult); cancelProgressButton->setMaximumHeight(sidebarButton->height()); cancelProgressButton->show(); previewProgress->setValue(0); previewProgress->setFormat(i18n("loading previews: %p%")); previewProgress->setMaximumHeight(cancelProgressButton->height()); previewProgress->show(); } void ListPanel::slotPreviewJobPercent(KJob* /*job*/, unsigned long percent) { previewProgress->setValue(percent); } void ListPanel::slotPreviewJobResult(KJob* /*job*/) { previewJob = nullptr; previewProgress->hide(); if(!inlineRefreshJob) cancelProgressButton->hide(); } void ListPanel::slotRefreshJobStarted(KIO::Job* job) { // disable the parts of the panel we don't want touched status->setEnabled(false); urlNavigator->setEnabled(false); cdRootButton->setEnabled(false); cdHomeButton->setEnabled(false); cdUpButton->setEnabled(false); cdOtherButton->setEnabled(false); sidebarButton->setEnabled(false); if(sidebar) sidebar->setEnabled(false); bookmarksButton->setEnabled(false); historyButton->setEnabled(false); syncBrowseButton->setEnabled(false); // connect to the job interface to provide in-panel refresh notification connect(job, &KIO::Job::infoMessage, this, &ListPanel::inlineRefreshInfoMessage); connect(job, SIGNAL(percent(KJob*,ulong)), SLOT(inlineRefreshPercent(KJob*,ulong))); connect(job, &KIO::Job::result, this, &ListPanel::inlineRefreshListResult); inlineRefreshJob = job; totals->setText(i18n(">> Reading...")); cancelProgressButton->show(); } void ListPanel::cancelProgress() { if (inlineRefreshJob) { disconnect(inlineRefreshJob, nullptr, this, nullptr); inlineRefreshJob->kill(KJob::EmitResult); inlineRefreshListResult(nullptr); } if(previewJob) { disconnect(previewJob, nullptr, this, nullptr); previewJob->kill(KJob::EmitResult); slotPreviewJobResult(nullptr); } } void ListPanel::setNavigatorUrl(const QUrl &url) { _navigatorUrl = url; urlNavigator->setLocationUrl(url); } void ListPanel::inlineRefreshPercent(KJob*, unsigned long perc) { QString msg = i18n(">> Reading: %1 % complete...", perc); totals->setText(msg); } void ListPanel::inlineRefreshInfoMessage(KJob*, const QString &msg) { totals->setText(i18n(">> Reading: %1", msg)); } void ListPanel::inlineRefreshListResult(KJob*) { if(inlineRefreshJob) disconnect(inlineRefreshJob, nullptr, this, nullptr); inlineRefreshJob = nullptr; // reenable everything status->setEnabled(true); urlNavigator->setEnabled(true); cdRootButton->setEnabled(true); cdHomeButton->setEnabled(true); cdUpButton->setEnabled(true); cdOtherButton->setEnabled(true); sidebarButton->setEnabled(true); if(sidebar) sidebar->setEnabled(true); bookmarksButton->setEnabled(true); historyButton->setEnabled(true); syncBrowseButton->setEnabled(true); if(!previewJob) cancelProgressButton->hide(); } void ListPanel::jumpBack() { func->openUrl(_jumpBackURL); } void ListPanel::setJumpBack(QUrl url) { _jumpBackURL = std::move(url); } void ListPanel::slotFilesystemError(const QString& msg) { refreshColors(); fileSystemError->setText(i18n("Error: %1", msg)); fileSystemError->show(); } void ListPanel::showButtonMenu(QToolButton *b) { if(this != ACTIVE_PANEL) slotFocusOnMe(); if(b->isHidden()) b->menu()->exec(mapToGlobal(clientArea->pos())); else b->click(); } void ListPanel::openBookmarks() { showButtonMenu(bookmarksButton); } void ListPanel::openHistory() { showButtonMenu(historyButton); } void ListPanel::openMedia() { showButtonMenu(mediaButton); } void ListPanel::rightclickMenu() { if (view->getCurrentKrViewItem()) popRightClickMenu(mapToGlobal(view->getCurrentKrViewItem()->itemRect().topLeft())); } void ListPanel::toggleSyncBrowse() { syncBrowseButton->toggle(); } void ListPanel::editLocation() { urlNavigator->setUrlEditable(true); urlNavigator->setFocus(); urlNavigator->editor()->lineEdit()->selectAll(); } void ListPanel::showSearchBar() { searchBar->showBar(KrSearchBar::MODE_SEARCH); } void ListPanel::showSearchBarSelection() { searchBar->showBar(KrSearchBar::MODE_SELECT); } void ListPanel::showSearchBarFilter() { searchBar->showBar(KrSearchBar::MODE_FILTER); } void ListPanel::saveSettings(KConfigGroup cfg, bool saveHistory) { QUrl url = virtualPath(); url.setPassword(QString()); // make sure no password is saved cfg.writeEntry("Url", url.toString()); cfg.writeEntry("Type", getType()); cfg.writeEntry("Properties", getProperties()); cfg.writeEntry("PinnedUrl", pinnedUrl().toString()); if(saveHistory) func->history->save(KConfigGroup(&cfg, "History")); view->saveSettings(KConfigGroup(&cfg, "View")); // splitter/sidebar state if (sidebar && !sidebar->isHidden()) { sidebar->saveSettings(KConfigGroup(&cfg, "PanelPopup")); cfg.writeEntry("PopupPosition", sidebarPosition()); cfg.writeEntry("SplitterSizes", sidebarSplitter->saveState()); cfg.writeEntry("PopupPage", sidebar->currentPage()); } else { cfg.deleteEntry("PopupPosition"); cfg.deleteEntry("SplitterSizes"); cfg.deleteEntry("PopupPage"); } } void ListPanel::restoreSettings(KConfigGroup cfg) { changeType(cfg.readEntry("Type", defaultPanelType())); view->restoreSettings(KConfigGroup(&cfg, "View")); // "locked" property must be set after URL path is restored! // This panel can be reused when loading a profile, // so we reset its properties before calling openUrl(). setProperties(0); _lastLocalPath = ROOT_DIR; if(func->history->restore(KConfigGroup(&cfg, "History"))) { func->refresh(); } else { QUrl url(cfg.readEntry("Url", "invalid")); if (!url.isValid()) url = QUrl::fromLocalFile(ROOT_DIR); func->openUrl(url); } setJumpBack(func->history->currentUrl()); setProperties(cfg.readEntry("Properties", 0)); if (isPinned()) { QUrl pinnedUrl(cfg.readEntry("PinnedUrl", "invalid")); if (!pinnedUrl.isValid()) { pinnedUrl = func->history->currentUrl(); } func->openUrl(pinnedUrl); setPinnedUrl(pinnedUrl); } if (cfg.hasKey("PopupPosition")) { // sidebar was visible, restore toggleSidebar(); // create and show sidebar->restoreSettings(KConfigGroup(&cfg, "PanelPopup")); setSidebarPosition(cfg.readEntry("PopupPosition", 42 /* dummy */)); sidebarSplitter->restoreState(cfg.readEntry("SplitterSizes", QByteArray())); sidebar->setCurrentPage(cfg.readEntry("PopupPage", 0)); } } void ListPanel::slotCurrentChanged(KrViewItem *item) { // update status bar if (item) krApp->statusBarUpdate(item->description()); // update sidebar; which panel to display on? Sidebar *p; if (sidebar && !sidebar->isHidden()) { p = sidebar; } else if(otherPanel()->gui->sidebar && !otherPanel()->gui->sidebar->isHidden()) { p = otherPanel()->gui->sidebar; } else { return; } p->update(item ? func->files()->getFileItem(item->name()) : nullptr); } void ListPanel::otherPanelChanged() { func->syncURL = QUrl(); } void ListPanel::getFocusCandidates(QVector &widgets) { if(urlNavigator->editor()->isVisible()) widgets << urlNavigator->editor(); if(view->widget()->isVisible()) widgets << view->widget(); if(sidebar && sidebar->isVisible()) widgets << sidebar; } void ListPanel::updateButtons() { backButton->setEnabled(func->history->canGoBack()); forwardButton->setEnabled(func->history->canGoForward()); historyButton->setEnabled(func->history->count() > 1); cdRootButton->setEnabled(!virtualPath().matches(QUrl::fromLocalFile(ROOT_DIR), QUrl::StripTrailingSlash)); cdUpButton->setEnabled(!func->files()->isRoot()); cdHomeButton->setEnabled(!func->atHome()); } void ListPanel::newTab(KrViewItem *it) { if (!it) return; else if (it->name() == "..") { newTab(KIO::upUrl(virtualPath()), true); } else if (func->getFileItem(it)->isDir()) { QUrl url = virtualPath(); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + (it->name())); newTab(url, true); } } void ListPanel::newTab(const QUrl &url, bool nextToThis) { _manager->newTab(url, nextToThis ? this : nullptr); } void ListPanel::slotNavigatorUrlChanged(const QUrl &url) { if (url == _navigatorUrl) return; // this is the URL we just set ourself if (!isNavigatorEditModeSet()) { urlNavigator->setUrlEditable(false); } func->openUrl(KrServices::escapeFileUrl(url), QString(), true); } void ListPanel::resetNavigatorMode() { if (isNavigatorEditModeSet()) return; // set to "navigate" mode if url wasn't changed if (urlNavigator->uncommittedUrl().matches(virtualPath(), QUrl::StripTrailingSlash)) { // NOTE: this also sets focus to the navigator urlNavigator->setUrlEditable(false); slotFocusOnMe(); } } int ListPanel::sidebarPosition() const { int pos = sidebarSplitter->orientation() == Qt::Vertical ? 1 : 0; return pos + (qobject_cast(sidebarSplitter->widget(0)) == NULL ? 2 : 0); } void ListPanel::setSidebarPosition(int pos) { sidebarSplitter->setOrientation(pos % 2 == 0 ? Qt::Horizontal : Qt::Vertical); if ((pos < 2) != (qobject_cast(sidebarSplitter->widget(0)) != NULL)) { sidebarSplitter->insertWidget(0, sidebarSplitter->widget(1)); // swapping widgets in splitter } } void ListPanel::connectQuickSizeCalculator(SizeCalculator *sizeCalculator) { connect(sizeCalculator, &SizeCalculator::started, this, [=]() { quickSizeCalcProgress->reset(); quickSizeCalcProgress->show(); cancelQuickSizeCalcButton->show(); }); connect(cancelQuickSizeCalcButton, &QToolButton::clicked, sizeCalculator, &SizeCalculator::cancel); connect(sizeCalculator, &SizeCalculator::progressChanged, quickSizeCalcProgress, &QProgressBar::setValue); connect(sizeCalculator, &SizeCalculator::finished, this, [=]() { cancelQuickSizeCalcButton->hide(); quickSizeCalcProgress->hide(); }); } diff --git a/krusader/Panel/panelfunc.cpp b/krusader/Panel/panelfunc.cpp index f59b64c5..280b3469 100644 --- a/krusader/Panel/panelfunc.cpp +++ b/krusader/Panel/panelfunc.cpp @@ -1,1291 +1,1291 @@ /***************************************************************************** * Copyright (C) 2000 Shie Erlich * * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2004-2018 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 "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" 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 (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; 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::edit() { panel->searchBar->hideBarIfSearching(); KFileItem tmp; if (fileToCreate.isEmpty()) { QString name = panel->getCurrentName(); if (name.isNull()) return; fileToCreate = files()->getUrl(name); } tmp = KFileItem(fileToCreate); if (tmp.isDir()) { KMessageBox::sorry(krMainWindow, i18n("You cannot edit a folder")); fileToCreate = QUrl(); return; } if (!tmp.isReadable()) { KMessageBox::sorry(nullptr, i18n("No permissions to edit this file.")); fileToCreate = QUrl(); return; } KrViewer::edit(fileToCreate); fileToCreate = QUrl(); } void ListPanelFunc::editNew() { if(!fileToCreate.isEmpty()) return; // ask the user for the filename to edit fileToCreate = KChooseDir::getFile(i18n("Enter the filename to edit:"), QUrl(panel->getCurrentName()), panel->virtualPath()); if(fileToCreate.isEmpty()) return ; // the user canceled // if the file exists, edit it instead of creating a new one QFile f(fileToCreate.toLocalFile()); if(f.exists()) { edit(); } else { auto *tempFile = new QTemporaryFile; tempFile->open(); KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(tempFile->fileName()), fileToCreate); job->setUiDelegate(nullptr); job->setDefaultPermissions(true); connect(job, &KIO::CopyJob::result, this, &ListPanelFunc::slotFileCreated); connect(job, &KIO::CopyJob::result, tempFile, &QTemporaryFile::deleteLater); } } void ListPanelFunc::slotFileCreated(KJob *job) { if(!job->error() || job->error() == KIO::ERR_FILE_ALREADY_EXIST) { KrViewer::edit(fileToCreate); if(KIO::upUrl(fileToCreate).matches(panel->virtualPath(), QUrl::StripTrailingSlash)) refresh(); else if(KIO::upUrl(fileToCreate).matches(panel->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)) otherFunc()->refresh(); } else KMessageBox::sorry(krMainWindow, job->errorString()); fileToCreate = QUrl(); } 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() { // ask the new dir name.. // suggested name is the complete name for the directories // while filenames are suggested without their extension QString suggestedName = panel->getCurrentName(); if (!suggestedName.isEmpty() && !files()->getFileItem(suggestedName)->isDir()) suggestedName = QFileInfo(suggestedName).completeBaseName(); const QString dirName = QInputDialog::getText(krMainWindow, i18n("New folder"), i18n("Folder's name:"), QLineEdit::Normal, suggestedName); 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 user about existing folder if only single directory was given if (!dirName.contains('/') && files()->getFileItem(firstName)) { // focus the existing directory panel->view->setCurrentItem(firstName); // show 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 (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 = urls.toSet(); for (QUrl fileUrl : urls) { 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.toList(); } 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 = static_cast(files()); + 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 = KrServices::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 KrViewItemList items; panel->view->getSelectedKrViewItems(&items); 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/Splitter/combiner.cpp b/krusader/Splitter/combiner.cpp index 607ade68..28682172 100644 --- a/krusader/Splitter/combiner.cpp +++ b/krusader/Splitter/combiner.cpp @@ -1,343 +1,343 @@ /***************************************************************************** * Copyright (C) 2003 Csaba Karai * * Copyright (C) 2004-2018 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 //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)), 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()); } statJob = KIO::stat(writeURL, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); 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 { - static_cast(job)->uiDelegate()->showErrorMessage(); + dynamic_cast(job)->uiDelegate()->showErrorMessage(); emit reject(); } } else { // destination already exists - KIO::RenameDialog_Options mode = static_cast(job)->statResult().isDir() ? + 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::R_OVERWRITE: openNextFile(); break; case KIO::R_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(); - static_cast(job)->uiDelegate()->showErrorMessage(); + 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(); - static_cast(job)->uiDelegate()->showErrorMessage(); + 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 1970ce08..067c45a1 100644 --- a/krusader/Splitter/splitter.cpp +++ b/krusader/Splitter/splitter.cpp @@ -1,296 +1,296 @@ /***************************************************************************** * Copyright (C) 2003 Csaba Karai * * Copyright (C) 2004-2018 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 Splitter::Splitter(QWidget* parent, QUrl fileNameIn, QUrl destinationDirIn, bool overWriteIn) : QProgressDialog(parent, nullptr), 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 { statJob = KIO::stat(writeURL, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); 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 { - static_cast(job)->uiDelegate()->showErrorMessage(); + 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::R_OVERWRITE: openOutputFile(); break; case KIO::R_OVERWRITE_ALL: 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/Synchronizer/synchronizer.cpp b/krusader/Synchronizer/synchronizer.cpp index f95b17ad..fea96596 100644 --- a/krusader/Synchronizer/synchronizer.cpp +++ b/krusader/Synchronizer/synchronizer.cpp @@ -1,1443 +1,1443 @@ /***************************************************************************** * Copyright (C) 2003 Csaba Karai * * Copyright (C) 2004-2018 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 "synchronizer.h" #include "synchronizerdirlist.h" #include "../krglobal.h" #include "../krservices.h" #include "../FileSystem/filesystem.h" #include "../FileSystem/krquery.h" #include // QtCore #include #include #include #include #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_POSIX_ACL #include #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS #include #endif #endif #define DISPLAY_UPDATE_PERIOD 2 Synchronizer::Synchronizer() : displayUpdateCount(0), markEquals(true), markDiffers(true), markCopyToLeft(true), markCopyToRight(true), markDeletable(true), stack(), jobMap(), receivedMap(), parentWidget(nullptr), resultListIt(resultList) { } Synchronizer::~Synchronizer() { clearLists(); } QUrl Synchronizer::fsUrl(const QString& strUrl) { QUrl result = QUrl::fromUserInput(strUrl, QString(), QUrl::AssumeLocalFile); return KrServices::escapeFileUrl(result); } void Synchronizer::clearLists() { QListIterator i1(resultList); while (i1.hasNext()) delete i1.next(); resultList.clear(); QListIterator i2(stack); while (i2.hasNext()) delete i2.next(); stack.clear(); temporaryList.clear(); } void Synchronizer::reset() { displayUpdateCount = 0; markEquals = markDiffers = markCopyToLeft = markCopyToRight = markDeletable = true; stopped = false; recurseSubDirs = followSymLinks = ignoreDate = asymmetric = cmpByContent = ignoreCase = autoScroll = false; markEquals = markDiffers = markCopyToLeft = markCopyToRight = markDeletable = markDuplicates = markSingles = false; leftCopyEnabled = rightCopyEnabled = deleteEnabled = overWrite = autoSkip = paused = false; leftCopyNr = rightCopyNr = deleteNr = 0; leftCopySize = rightCopySize = deleteSize = 0; comparedDirs = fileCount = 0; leftBaseDir.clear(); rightBaseDir.clear(); clearLists(); } int Synchronizer::compare(QString leftURL, QString rightURL, KRQuery *query, bool subDirs, bool symLinks, bool igDate, bool asymm, bool cmpByCnt, bool igCase, bool autoSc, QStringList &selFiles, int equThres, int timeOffs, int parThreads, bool hiddenFiles) { clearLists(); recurseSubDirs = subDirs; followSymLinks = symLinks; ignoreDate = igDate; asymmetric = asymm; cmpByContent = cmpByCnt; autoScroll = autoSc; ignoreCase = igCase; selectedFiles = selFiles; equalsThreshold = equThres; timeOffset = timeOffs; parallelThreads = parThreads; ignoreHidden = hiddenFiles; stopped = false; this->query = query; leftURL = KUrlCompletion::replacedPath(leftURL, true, true); rightURL = KUrlCompletion::replacedPath(rightURL, true, true); if (!leftURL.endsWith('/')) leftURL += '/'; if (!rightURL.endsWith('/')) rightURL += '/'; excludedPaths = KrServices::toStringList(query->dontSearchInDirs()); for (int i = 0; i != excludedPaths.count(); i++) if (excludedPaths[ i ].endsWith('/')) excludedPaths[ i ].truncate(excludedPaths[ i ].length() - 1); comparedDirs = fileCount = 0; stack.append(new CompareTask(nullptr, leftBaseDir = leftURL, rightBaseDir = rightURL, "", "", ignoreHidden)); compareLoop(); QListIterator it(temporaryList); while (it.hasNext()) { SynchronizerFileItem * item = it.next(); if (item->isTemporary()) delete item; } temporaryList.clear(); if (!autoScroll) refresh(true); emit statusInfo(i18n("Number of files: %1", fileCount)); return fileCount; } void Synchronizer::compareLoop() { while (!stopped && !stack.isEmpty()) { for (int thread = 0; thread < (int)stack.count() && thread < parallelThreads; thread++) { SynchronizerTask * entry = stack.at(thread); if (entry->state() == ST_STATE_NEW) entry->start(parentWidget); if (entry->inherits("CompareTask")) { if (entry->state() == ST_STATE_READY) { auto *ctentry = (CompareTask *) entry; if (ctentry->isDuplicate()) compareDirectory(ctentry->parent(), ctentry->leftDirList(), ctentry->rightDirList(), ctentry->leftDir(), ctentry->rightDir()); else addSingleDirectory(ctentry->parent(), ctentry->dirList(), ctentry->dir(), ctentry->isLeft()); } if (entry->state() == ST_STATE_READY || entry->state() == ST_STATE_ERROR) comparedDirs++; } switch (entry->state()) { case ST_STATE_STATUS: emit statusInfo(entry->status()); break; case ST_STATE_READY: case ST_STATE_ERROR: emit statusInfo(i18n("Number of compared folders: %1", comparedDirs)); stack.removeAll(entry); delete entry; continue; default: break; } } if (!stack.isEmpty()) qApp->processEvents(); } QListIterator it(stack); while (it.hasNext()) delete it.next(); stack.clear(); } void Synchronizer::compareDirectory(SynchronizerFileItem *parent, SynchronizerDirList * left_directory, SynchronizerDirList * right_directory, const QString &leftDir, const QString &rightDir) { const QString &leftURL = left_directory->url(); const QString &rightURL = right_directory->url(); FileItem *left_file; FileItem *right_file; QString file_name; bool checkIfSelected = false; if (leftDir.isEmpty() && rightDir.isEmpty() && selectedFiles.count()) checkIfSelected = true; /* walking through in the left directory */ for (left_file = left_directory->first(); left_file != nullptr && !stopped; left_file = left_directory->next()) { if (isDir(left_file)) continue; file_name = left_file->getName(); if (checkIfSelected && !selectedFiles.contains(file_name)) continue; if (!query->match(left_file)) continue; if ((right_file = right_directory->search(file_name, ignoreCase)) == nullptr) addLeftOnlyItem(parent, file_name, leftDir, left_file->getSize(), left_file->getTime_t(), readLink(left_file), left_file->getOwner(), left_file->getGroup(), left_file->getMode(), left_file->getACL()); else { if (isDir(right_file)) continue; addDuplicateItem(parent, file_name, right_file->getName(), leftDir, rightDir, left_file->getSize(), right_file->getSize(), left_file->getTime_t(), right_file->getTime_t(), readLink(left_file), readLink(right_file), left_file->getOwner(), right_file->getOwner(), left_file->getGroup(), right_file->getGroup(), left_file->getMode(), right_file->getMode(), left_file->getACL(), right_file->getACL()); } } /* walking through in the right directory */ for (right_file = right_directory->first(); right_file != nullptr && !stopped; right_file = right_directory->next()) { if (isDir(right_file)) continue; file_name = right_file->getName(); if (checkIfSelected && !selectedFiles.contains(file_name)) continue; if (!query->match(right_file)) continue; if (left_directory->search(file_name, ignoreCase) == nullptr) addRightOnlyItem(parent, file_name, rightDir, right_file->getSize(), right_file->getTime_t(), readLink(right_file), right_file->getOwner(), right_file->getGroup(), right_file->getMode(), right_file->getACL()); } /* walking through the subdirectories */ if (recurseSubDirs) { for (left_file = left_directory->first(); left_file != nullptr && !stopped; left_file = left_directory->next()) { if (left_file->isDir() && (followSymLinks || !left_file->isSymLink())) { QString left_file_name = left_file->getName(); if (checkIfSelected && !selectedFiles.contains(left_file_name)) continue; if (excludedPaths.contains(leftDir.isEmpty() ? left_file_name : leftDir + '/' + left_file_name)) continue; if (!query->matchDirName(left_file_name)) continue; if ((right_file = right_directory->search(left_file_name, ignoreCase)) == nullptr) { SynchronizerFileItem *me = addLeftOnlyItem(parent, left_file_name, leftDir, 0, left_file->getTime_t(), readLink(left_file), left_file->getOwner(), left_file->getGroup(), left_file->getMode(), left_file->getACL(), true, !query->match(left_file)); stack.append(new CompareTask(me, leftURL + left_file_name + '/', leftDir.isEmpty() ? left_file_name : leftDir + '/' + left_file_name, true, ignoreHidden)); } else { QString right_file_name = right_file->getName(); SynchronizerFileItem *me = addDuplicateItem(parent, left_file_name, right_file_name, leftDir, rightDir, 0, 0, left_file->getTime_t(), right_file->getTime_t(), readLink(left_file), readLink(right_file), left_file->getOwner(), right_file->getOwner(), left_file->getGroup(), right_file->getGroup(), left_file->getMode(), right_file->getMode(), left_file->getACL(), right_file->getACL(), true, !query->match(left_file)); stack.append(new CompareTask(me, leftURL + left_file_name + '/', rightURL + right_file_name + '/', leftDir.isEmpty() ? left_file_name : leftDir + '/' + left_file_name, rightDir.isEmpty() ? right_file_name : rightDir + '/' + right_file_name, ignoreHidden)); } } } /* walking through the right side subdirectories */ for (right_file = right_directory->first(); right_file != nullptr && !stopped; right_file = right_directory->next()) { if (right_file->isDir() && (followSymLinks || !right_file->isSymLink())) { file_name = right_file->getName(); if (checkIfSelected && !selectedFiles.contains(file_name)) continue; if (excludedPaths.contains(rightDir.isEmpty() ? file_name : rightDir + '/' + file_name)) continue; if (!query->matchDirName(file_name)) continue; if (left_directory->search(file_name, ignoreCase) == nullptr) { SynchronizerFileItem *me = addRightOnlyItem(parent, file_name, rightDir, 0, right_file->getTime_t(), readLink(right_file), right_file->getOwner(), right_file->getGroup(), right_file->getMode(), right_file->getACL(), true, !query->match(right_file)); stack.append(new CompareTask(me, rightURL + file_name + '/', rightDir.isEmpty() ? file_name : rightDir + '/' + file_name, false, ignoreHidden)); } } } } } QString Synchronizer::getTaskTypeName(TaskType taskType) { static QString names[] = {"=", "!=", "<-", "->", "DEL", "?", "?", "?", "?", "?"}; return names[taskType]; } SynchronizerFileItem * Synchronizer::addItem(SynchronizerFileItem *parent, const QString &leftFile, const QString &rightFile, const QString &leftDir, const QString &rightDir, bool existsLeft, bool existsRight, KIO::filesize_t leftSize, KIO::filesize_t rightSize, time_t leftDate, time_t rightDate, const QString &leftLink, const QString &rightLink, const QString &leftOwner, const QString &rightOwner, const QString &leftGroup, const QString &rightGroup, mode_t leftMode, mode_t rightMode, const QString &leftACL, const QString &rightACL, TaskType tsk, bool isDir, bool isTemp) { bool marked = autoScroll ? !isTemp && isMarked(tsk, existsLeft && existsRight) : false; auto *item = new SynchronizerFileItem(leftFile, rightFile, leftDir, rightDir, marked, existsLeft, existsRight, leftSize, rightSize, leftDate, rightDate, leftLink, rightLink, leftOwner, rightOwner, leftGroup, rightGroup, leftMode, rightMode, leftACL, rightACL, tsk, isDir, isTemp, parent); if (!isTemp) { while (parent && parent->isTemporary()) setPermanent(parent); bool doRefresh = false; if (marked) { fileCount++; if (autoScroll && markParentDirectories(item)) doRefresh = true; } resultList.append(item); emit comparedFileData(item); if (doRefresh) refresh(true); if (marked && (displayUpdateCount++ % DISPLAY_UPDATE_PERIOD == (DISPLAY_UPDATE_PERIOD - 1))) qApp->processEvents(); } else temporaryList.append(item); return item; } void Synchronizer::compareContentResult(SynchronizerFileItem * item, bool res) { item->compareContentResult(res); bool marked = autoScroll ? isMarked(item->task(), item->existsInLeft() && item->existsInRight()) : false; item->setMarked(marked); if (marked) { markParentDirectories(item); fileCount++; emit markChanged(item, true); } } void Synchronizer::setPermanent(SynchronizerFileItem *item) { if (item->parent() && item->parent()->isTemporary()) setPermanent(item->parent()); item->setPermanent(); resultList.append(item); emit comparedFileData(item); } SynchronizerFileItem * Synchronizer::addLeftOnlyItem(SynchronizerFileItem *parent, const QString &file_name, const QString &dir, KIO::filesize_t size, time_t date, const QString &link, const QString &owner, const QString &group, mode_t mode, const QString &acl, bool isDir, bool isTemp) { return addItem(parent, file_name, file_name, dir, dir, true, false, size, 0, date, 0, link, QString(), owner, QString(), group, QString(), mode, (mode_t) - 1, acl, QString(), asymmetric ? TT_DELETE : TT_COPY_TO_RIGHT, isDir, isTemp); } SynchronizerFileItem * Synchronizer::addRightOnlyItem(SynchronizerFileItem *parent, const QString &file_name, const QString &dir, KIO::filesize_t size, time_t date, const QString &link, const QString &owner, const QString &group, mode_t mode, const QString &acl, bool isDir, bool isTemp) { return addItem(parent, file_name, file_name, dir, dir, false, true, 0, size, 0, date, QString(), link, QString(), owner, QString(), group, (mode_t) - 1, mode, QString(), acl, TT_COPY_TO_LEFT, isDir, isTemp); } SynchronizerFileItem * Synchronizer::addDuplicateItem(SynchronizerFileItem *parent, const QString &leftName, const QString &rightName, const QString &leftDir, const QString &rightDir, KIO::filesize_t leftSize, KIO::filesize_t rightSize, time_t leftDate, time_t rightDate, const QString &leftLink, const QString &rightLink, const QString &leftOwner, const QString &rightOwner, const QString &leftGroup, const QString &rightGroup, mode_t leftMode, mode_t rightMode, const QString &leftACL, const QString &rightACL, bool isDir, bool isTemp) { TaskType task; int checkedRightDate = rightDate - timeOffset; int uncertain = 0; do { if (isDir) { task = TT_EQUALS; break; } if (leftSize == rightSize) { if (!leftLink.isNull() || !rightLink.isNull()) { if (leftLink == rightLink) { task = TT_EQUALS; break; } } else if (cmpByContent) uncertain = TT_UNKNOWN; else { if (ignoreDate || leftDate == checkedRightDate) { task = TT_EQUALS; break; } time_t diff = (leftDate > checkedRightDate) ? leftDate - checkedRightDate : checkedRightDate - leftDate; if (diff <= equalsThreshold) { task = TT_EQUALS; break; } } } if (asymmetric) task = TT_COPY_TO_LEFT; else if (ignoreDate) task = TT_DIFFERS; else if (leftDate > checkedRightDate) task = TT_COPY_TO_RIGHT; else if (leftDate < checkedRightDate) task = TT_COPY_TO_LEFT; else task = TT_DIFFERS; } while (false); SynchronizerFileItem * item = addItem(parent, leftName, rightName, leftDir, rightDir, true, true, leftSize, rightSize, leftDate, rightDate, leftLink, rightLink, leftOwner, rightOwner, leftGroup, rightGroup, leftMode, rightMode, leftACL, rightACL, (TaskType)(task + uncertain), isDir, isTemp); if (uncertain == TT_UNKNOWN) { QUrl leftURL = Synchronizer::fsUrl(leftDir.isEmpty() ? leftBaseDir + leftName : leftBaseDir + leftDir + '/' + leftName); QUrl rightURL = Synchronizer::fsUrl(rightDir.isEmpty() ? rightBaseDir + rightName : rightBaseDir + rightDir + '/' + rightName); stack.append(new CompareContentTask(this, item, leftURL, rightURL, leftSize)); } return item; } void Synchronizer::addSingleDirectory(SynchronizerFileItem *parent, SynchronizerDirList *directory, const QString &dirName, bool isLeft) { const QString &url = directory->url(); FileItem *file; QString file_name; /* walking through the directory files */ for (file = directory->first(); file != nullptr && !stopped; file = directory->next()) { if (isDir(file)) continue; file_name = file->getName(); if (!query->match(file)) continue; if (isLeft) addLeftOnlyItem(parent, file_name, dirName, file->getSize(), file->getTime_t(), readLink(file), file->getOwner(), file->getGroup(), file->getMode(), file->getACL()); else addRightOnlyItem(parent, file_name, dirName, file->getSize(), file->getTime_t(), readLink(file), file->getOwner(), file->getGroup(), file->getMode(), file->getACL()); } /* walking through the subdirectories */ for (file = directory->first(); file != nullptr && !stopped; file = directory->next()) { if (file->isDir() && (followSymLinks || !file->isSymLink())) { file_name = file->getName(); if (excludedPaths.contains(dirName.isEmpty() ? file_name : dirName + '/' + file_name)) continue; if (!query->matchDirName(file_name)) continue; SynchronizerFileItem *me; if (isLeft) me = addLeftOnlyItem(parent, file_name, dirName, 0, file->getTime_t(), readLink(file), file->getOwner(), file->getGroup(), file->getMode(), file->getACL(), true, !query->match(file)); else me = addRightOnlyItem(parent, file_name, dirName, 0, file->getTime_t(), readLink(file), file->getOwner(), file->getGroup(), file->getMode(), file->getACL(), true, !query->match(file)); stack.append(new CompareTask(me, url + file_name + '/', dirName.isEmpty() ? file_name : dirName + '/' + file_name, isLeft, ignoreHidden)); } } } void Synchronizer::setMarkFlags(bool left, bool equal, bool differs, bool right, bool dup, bool sing, bool del) { markEquals = equal; markDiffers = differs; markCopyToLeft = left; markCopyToRight = right; markDeletable = del; markDuplicates = dup; markSingles = sing; } bool Synchronizer::isMarked(TaskType task, bool isDuplicate) { if ((isDuplicate && !markDuplicates) || (!isDuplicate && !markSingles)) return false; switch (task) { case TT_EQUALS: return markEquals; case TT_DIFFERS: return markDiffers; case TT_COPY_TO_LEFT: return markCopyToLeft; case TT_COPY_TO_RIGHT: return markCopyToRight; case TT_DELETE: return markDeletable; default: return false; } } bool Synchronizer::markParentDirectories(SynchronizerFileItem *item) { if (item->parent() == nullptr || item->parent()->isMarked()) return false; markParentDirectories(item->parent()); item->parent()->setMarked(true); fileCount++; emit markChanged(item->parent(), false); return true; } int Synchronizer::refresh(bool nostatus) { fileCount = 0; QListIterator it(resultList); while (it.hasNext()) { SynchronizerFileItem * item = it.next(); bool marked = isMarked(item->task(), item->existsInLeft() && item->existsInRight()); item->setMarked(marked); if (marked) { markParentDirectories(item); fileCount++; } } it.toFront(); while (it.hasNext()) { SynchronizerFileItem * item = it.next(); emit markChanged(item, false); } if (!nostatus) emit statusInfo(i18n("Number of files: %1", fileCount)); return fileCount; } void Synchronizer::operate(SynchronizerFileItem *item, void (*executeOperation)(SynchronizerFileItem *)) { executeOperation(item); if (item->isDir()) { QString leftDirName = (item->leftDirectory().isEmpty()) ? item->leftName() : item->leftDirectory() + '/' + item->leftName(); QString rightDirName = (item->rightDirectory().isEmpty()) ? item->rightName() : item->rightDirectory() + '/' + item->rightName(); QListIterator it(resultList); while (it.hasNext()) { SynchronizerFileItem * item = it.next(); if (item->leftDirectory() == leftDirName || item->leftDirectory().startsWith(leftDirName + '/') || item->rightDirectory() == rightDirName || item->rightDirectory().startsWith(rightDirName + '/')) executeOperation(item); } } } void Synchronizer::excludeOperation(SynchronizerFileItem *item) { item->setTask(TT_DIFFERS); } void Synchronizer::exclude(SynchronizerFileItem *item) { if (!item->parent() || item->parent()->task() != TT_DELETE) operate(item, excludeOperation); /* exclude only if the parent task is not DEL */ } void Synchronizer::restoreOperation(SynchronizerFileItem *item) { item->restoreOriginalTask(); } void Synchronizer::restore(SynchronizerFileItem *item) { operate(item, restoreOperation); while ((item = item->parent()) != nullptr) /* in case of restore, the parent directories */ { /* must be changed for being consistent */ if (item->task() != TT_DIFFERS) break; if (item->originalTask() == TT_DELETE) /* if the parent original task is delete */ break; /* don't touch it */ item->restoreOriginalTask(); /* restore */ } } void Synchronizer::reverseDirectionOperation(SynchronizerFileItem *item) { if (item->existsInRight() && item->existsInLeft()) { if (item->task() == TT_COPY_TO_LEFT) item->setTask(TT_COPY_TO_RIGHT); else if (item->task() == TT_COPY_TO_RIGHT) item->setTask(TT_COPY_TO_LEFT); } } void Synchronizer::reverseDirection(SynchronizerFileItem *item) { operate(item, reverseDirectionOperation); } void Synchronizer::deleteLeftOperation(SynchronizerFileItem *item) { if (!item->existsInRight() && item->existsInLeft()) item->setTask(TT_DELETE); } void Synchronizer::deleteLeft(SynchronizerFileItem *item) { operate(item, deleteLeftOperation); } void Synchronizer::copyToLeftOperation(SynchronizerFileItem *item) { if (item->existsInRight()) { if (!item->isDir()) item->setTask(TT_COPY_TO_LEFT); else { if (item->existsInLeft() && item->existsInRight()) item->setTask(TT_EQUALS); else if (!item->existsInLeft() && item->existsInRight()) item->setTask(TT_COPY_TO_LEFT); } } } void Synchronizer::copyToLeft(SynchronizerFileItem *item) { operate(item, copyToLeftOperation); while ((item = item->parent()) != nullptr) { if (item->task() != TT_DIFFERS) break; if (item->existsInLeft() && item->existsInRight()) item->setTask(TT_EQUALS); else if (!item->existsInLeft() && item->existsInRight()) item->setTask(TT_COPY_TO_LEFT); } } void Synchronizer::copyToRightOperation(SynchronizerFileItem *item) { if (item->existsInLeft()) { if (!item->isDir()) item->setTask(TT_COPY_TO_RIGHT); else { if (item->existsInLeft() && item->existsInRight()) item->setTask(TT_EQUALS); else if (item->existsInLeft() && !item->existsInRight()) item->setTask(TT_COPY_TO_RIGHT); } } } void Synchronizer::copyToRight(SynchronizerFileItem *item) { operate(item, copyToRightOperation); while ((item = item->parent()) != nullptr) { if (item->task() != TT_DIFFERS && item->task() != TT_DELETE) break; if (item->existsInLeft() && item->existsInRight()) item->setTask(TT_EQUALS); else if (item->existsInLeft() && !item->existsInRight()) item->setTask(TT_COPY_TO_RIGHT); } } bool Synchronizer::totalSizes(int * leftCopyNr, KIO::filesize_t *leftCopySize, int * rightCopyNr, KIO::filesize_t *rightCopySize, int *deleteNr, KIO::filesize_t *deletableSize) { bool hasAnythingToDo = false; *leftCopySize = *rightCopySize = *deletableSize = 0; *leftCopyNr = *rightCopyNr = *deleteNr = 0; QListIterator it(resultList); while (it.hasNext()) { SynchronizerFileItem * item = it.next(); if (item->isMarked()) { switch (item->task()) { case TT_COPY_TO_LEFT: *leftCopySize += item->rightSize(); (*leftCopyNr)++; hasAnythingToDo = true; break; case TT_COPY_TO_RIGHT: *rightCopySize += item->leftSize(); (*rightCopyNr)++; hasAnythingToDo = true; break; case TT_DELETE: *deletableSize += item->leftSize(); (*deleteNr)++; hasAnythingToDo = true; break; default: break; } } } return hasAnythingToDo; } void Synchronizer::swapSides() { QString leftTmp = leftBaseDir; leftBaseDir = rightBaseDir; rightBaseDir = leftTmp; QListIterator it(resultList); while (it.hasNext()) { SynchronizerFileItem * item = it.next(); item->swap(asymmetric); } } void Synchronizer::setScrolling(bool scroll) { autoScroll = scroll; if (autoScroll) { int oldFileCount = fileCount; refresh(true); fileCount = oldFileCount; } } void Synchronizer::synchronize(QWidget *syncWdg, bool leftCopyEnabled, bool rightCopyEnabled, bool deleteEnabled, bool overWrite, int parThreads) { this->leftCopyEnabled = leftCopyEnabled; this->rightCopyEnabled = rightCopyEnabled; this->deleteEnabled = deleteEnabled; this->overWrite = overWrite; this->parallelThreads = parThreads; this->syncDlgWidget = syncWdg; autoSkip = paused = disableNewTasks = false; leftCopyNr = rightCopyNr = deleteNr = 0; leftCopySize = rightCopySize = deleteSize = 0; inTaskFinished = 0; lastTask = nullptr; jobMap.clear(); receivedMap.clear(); resultListIt = resultList; synchronizeLoop(); } void Synchronizer::synchronizeLoop() { if (disableNewTasks) { if (!resultListIt.hasNext() && jobMap.count() == 0) emit synchronizationFinished(); return; } while ((int)jobMap.count() < parallelThreads) { SynchronizerFileItem *task = getNextTask(); if (task == nullptr) { if (jobMap.count() == 0) emit synchronizationFinished(); return; } executeTask(task); if (disableNewTasks) break; } } SynchronizerFileItem * Synchronizer::getNextTask() { TaskType task; SynchronizerFileItem * currentTask; do { if (!resultListIt.hasNext()) return nullptr; currentTask = resultListIt.next(); if (currentTask->isMarked()) { task = currentTask->task(); if (leftCopyEnabled && task == TT_COPY_TO_LEFT) break; else if (rightCopyEnabled && task == TT_COPY_TO_RIGHT) break; else if (deleteEnabled && task == TT_DELETE) break; } } while (true); return lastTask = currentTask; } void Synchronizer::executeTask(SynchronizerFileItem * task) { QString leftDirName = task->leftDirectory(); if (!leftDirName.isEmpty()) leftDirName += '/'; QString rightDirName = task->rightDirectory(); if (!rightDirName.isEmpty()) rightDirName += '/'; QUrl leftURL = Synchronizer::fsUrl(leftBaseDir + leftDirName + task->leftName()); QUrl rightURL = Synchronizer::fsUrl(rightBaseDir + rightDirName + task->rightName()); switch (task->task()) { case TT_COPY_TO_LEFT: if (task->isDir()) { KIO::SimpleJob *job = KIO::mkdir(leftURL); connect(job, &KIO::MkdirJob::result, this, &Synchronizer::slotTaskFinished); jobMap[ job ] = task; disableNewTasks = true; } else { QUrl destURL(leftURL); if (!task->destination().isNull()) destURL = Synchronizer::fsUrl(task->destination()); if (task->rightLink().isNull()) { KIO::FileCopyJob *job = KIO::file_copy(rightURL, destURL, -1, ((overWrite || task->overWrite()) ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo); connect(job, SIGNAL(processedSize(KJob*,qulonglong)), this, SLOT(slotProcessedSize(KJob*,qulonglong))); connect(job, &KIO::FileCopyJob::result, this, &Synchronizer::slotTaskFinished); jobMap[ job ] = task; } else { KIO::SimpleJob *job = KIO::symlink(task->rightLink(), destURL, ((overWrite || task->overWrite()) ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo); connect(job, &KIO::SimpleJob::result, this, &Synchronizer::slotTaskFinished); jobMap[ job ] = task; } } break; case TT_COPY_TO_RIGHT: if (task->isDir()) { KIO::SimpleJob *job = KIO::mkdir(rightURL); connect(job, &KIO::SimpleJob::result, this, &Synchronizer::slotTaskFinished); jobMap[ job ] = task; disableNewTasks = true; } else { QUrl destURL(rightURL); if (!task->destination().isNull()) destURL = Synchronizer::fsUrl(task->destination()); if (task->leftLink().isNull()) { KIO::FileCopyJob *job = KIO::file_copy(leftURL, destURL, -1, ((overWrite || task->overWrite()) ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo); connect(job, SIGNAL(processedSize(KJob*,qulonglong)), this, SLOT(slotProcessedSize(KJob*,qulonglong))); connect(job, &KIO::FileCopyJob::result, this, &Synchronizer::slotTaskFinished); jobMap[ job ] = task; } else { KIO::SimpleJob *job = KIO::symlink(task->leftLink(), destURL, ((overWrite || task->overWrite()) ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo); connect(job, &KIO::SimpleJob::result, this, &Synchronizer::slotTaskFinished); jobMap[ job ] = task; } } break; case TT_DELETE: { KIO::DeleteJob *job = KIO::del(leftURL, KIO::DefaultFlags); connect(job, &KIO::DeleteJob::result, this, &Synchronizer::slotTaskFinished); jobMap[ job ] = task; } break; default: break; } } void Synchronizer::slotTaskFinished(KJob *job) { inTaskFinished++; SynchronizerFileItem * item = jobMap[ job ]; jobMap.remove(job); KIO::filesize_t receivedSize = 0; if (receivedMap.contains(job)) { receivedSize = receivedMap[ job ]; receivedMap.remove(job); } if (disableNewTasks && item == lastTask) disableNewTasks = false; // the blocker task finished QString leftDirName = item->leftDirectory().isEmpty() ? "" : item->leftDirectory() + '/'; QString rightDirName = item->rightDirectory().isEmpty() ? "" : item->rightDirectory() + '/'; QUrl leftURL = Synchronizer::fsUrl(leftBaseDir + leftDirName + item->leftName()); QUrl rightURL = Synchronizer::fsUrl(rightBaseDir + rightDirName + item->rightName()); do { if (!job->error()) { switch (item->task()) { case TT_COPY_TO_LEFT: if (leftURL.isLocalFile()) { struct utimbuf timestamp; timestamp.actime = time(nullptr); timestamp.modtime = item->rightDate() - timeOffset; utime((const char *)(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), ×tamp); auto newOwnerID = (uid_t) - 1; // chown(2) : -1 means no change if (!item->rightOwner().isEmpty()) { struct passwd* pw = getpwnam(QFile::encodeName(item->rightOwner())); if (pw != nullptr) newOwnerID = pw->pw_uid; } auto newGroupID = (gid_t) - 1; // chown(2) : -1 means no change if (!item->rightGroup().isEmpty()) { struct group* g = getgrnam(QFile::encodeName(item->rightGroup())); if (g != nullptr) newGroupID = g->gr_gid; } int status1 = chown((const char *)(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), newOwnerID, (gid_t) - 1); int status2 = chown((const char *)(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), (uid_t) - 1, newGroupID); if (status1 < 0 || status2 < 0) { // synchronizer currently ignores chown errors } chmod((const char *)(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), item->rightMode() & 07777); #ifdef HAVE_POSIX_ACL if (!item->rightACL().isNull()) { acl_t acl = acl_from_text(item->rightACL().toLatin1()); if (acl && !acl_valid(acl)) acl_set_file(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit(), ACL_TYPE_ACCESS, acl); if (acl) acl_free(acl); } #endif } break; case TT_COPY_TO_RIGHT: if (rightURL.isLocalFile()) { struct utimbuf timestamp; timestamp.actime = time(nullptr); timestamp.modtime = item->leftDate() + timeOffset; utime((const char *)(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), ×tamp); auto newOwnerID = (uid_t) - 1; // chown(2) : -1 means no change if (!item->leftOwner().isEmpty()) { struct passwd* pw = getpwnam(QFile::encodeName(item->leftOwner())); if (pw != nullptr) newOwnerID = pw->pw_uid; } auto newGroupID = (gid_t) - 1; // chown(2) : -1 means no change if (!item->leftGroup().isEmpty()) { struct group* g = getgrnam(QFile::encodeName(item->leftGroup())); if (g != nullptr) newGroupID = g->gr_gid; } int status1 = chown((const char *)(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), newOwnerID, (uid_t) - 1); int status2 = chown((const char *)(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), (uid_t) - 1, newGroupID); if (status1 < 0 || status2 < 0) { // synchronizer currently ignores chown errors } chmod((const char *)(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), item->leftMode() & 07777); #ifdef HAVE_POSIX_ACL if (!item->leftACL().isNull()) { acl_t acl = acl_from_text(item->leftACL().toLatin1()); if (acl && !acl_valid(acl)) acl_set_file(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit(), ACL_TYPE_ACCESS, acl); if (acl) acl_free(acl); } #endif } break; default: break; } } else { if (job->error() == KIO::ERR_FILE_ALREADY_EXIST && item->task() != TT_DELETE) { KIO::RenameDialog_Result result; QString newDest; if (autoSkip) break; - auto *ui = static_cast(job->uiDelegate()); + auto *ui = dynamic_cast(job->uiDelegate()); ui->setWindow(syncDlgWidget); if (item->task() == TT_COPY_TO_LEFT) { result = ui->askFileRename(job, i18n("File Already Exists"), rightURL, leftURL, KIO::RenameDialog_Overwrite | KIO::RenameDialog_Skip | KIO::RenameDialog_MultipleItems, newDest, item->rightSize(), item->leftSize(), QDateTime(), QDateTime(), QDateTime::fromTime_t(item->rightDate()), QDateTime::fromTime_t(item->leftDate())); } else { result = ui->askFileRename(job, i18n("File Already Exists"), leftURL, rightURL, KIO::RenameDialog_Overwrite | KIO::RenameDialog_Skip | KIO::RenameDialog_MultipleItems, newDest, item->leftSize(), item->rightSize(), QDateTime(), QDateTime(), QDateTime::fromTime_t(item->leftDate()), QDateTime::fromTime_t(item->rightDate())); } switch (result) { case KIO::R_RENAME: item->setDestination(newDest); executeTask(item); inTaskFinished--; return; case KIO::R_OVERWRITE: item->setOverWrite(); executeTask(item); inTaskFinished--; return; case KIO::R_OVERWRITE_ALL: overWrite = true; executeTask(item); inTaskFinished--; return; case KIO::R_AUTO_SKIP: autoSkip = true; case KIO::R_SKIP: default: break; } break; } if (job->error() != KIO::ERR_DOES_NOT_EXIST || item->task() != TT_DELETE) { if (autoSkip) break; QString error; switch (item->task()) { case TT_COPY_TO_LEFT: error = i18n("Error at copying file %1 to %2.", rightURL.toDisplayString(QUrl::PreferLocalFile), leftURL.toDisplayString(QUrl::PreferLocalFile)); break; case TT_COPY_TO_RIGHT: error = i18n("Error at copying file %1 to %2.", leftURL.toDisplayString(QUrl::PreferLocalFile), rightURL.toDisplayString(QUrl::PreferLocalFile)); break; case TT_DELETE: error = i18n("Error at deleting file %1.", leftURL.toDisplayString(QUrl::PreferLocalFile)); break; default: break; } - auto *ui = static_cast(job->uiDelegate()); + auto *ui = dynamic_cast(job->uiDelegate()); ui->setWindow(syncDlgWidget); KIO::SkipDialog_Result result = ui->askSkip(job, KIO::SkipDialog_MultipleItems, error); switch (result) { case KIO::S_CANCEL: executeTask(item); /* simply retry */ inTaskFinished--; return; case KIO::S_AUTO_SKIP: autoSkip = true; default: break; } } } } while (false); switch (item->task()) { case TT_COPY_TO_LEFT: leftCopyNr++; leftCopySize += item->rightSize() - receivedSize; break; case TT_COPY_TO_RIGHT: rightCopyNr++; rightCopySize += item->leftSize() - receivedSize; break; case TT_DELETE: deleteNr++; deleteSize += item->leftSize() - receivedSize; break; default: break; } emit processedSizes(leftCopyNr, leftCopySize, rightCopyNr, rightCopySize, deleteNr, deleteSize); if (--inTaskFinished == 0) { if (paused) emit pauseAccepted(); else synchronizeLoop(); } } void Synchronizer::slotProcessedSize(KJob * job , qulonglong size) { KIO::filesize_t dl = 0, dr = 0, dd = 0; SynchronizerFileItem * item = jobMap[ job ]; KIO::filesize_t lastProcessedSize = 0; if (receivedMap.contains(job)) lastProcessedSize = receivedMap[ job ]; receivedMap[ job ] = size; switch (item->task()) { case TT_COPY_TO_LEFT: dl = size - lastProcessedSize; break; case TT_COPY_TO_RIGHT: dr = size - lastProcessedSize; break; case TT_DELETE: dd = size - lastProcessedSize; break; default: break; } emit processedSizes(leftCopyNr, leftCopySize += dl, rightCopyNr, rightCopySize += dr, deleteNr, deleteSize += dd); } void Synchronizer::pause() { paused = true; } void Synchronizer::resume() { paused = false; synchronizeLoop(); } QString Synchronizer::leftBaseDirectory() { return leftBaseDir; } QString Synchronizer::rightBaseDirectory() { return rightBaseDir; } KgetProgressDialog::KgetProgressDialog(QWidget *parent, const QString &caption, const QString &text, bool modal) : QDialog(parent) { if (caption.isEmpty()) setWindowTitle(caption); setModal(modal); auto *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(new QLabel(text)); mProgressBar = new QProgressBar; mainLayout->addWidget(mProgressBar); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); mPauseButton = new QPushButton(i18n("Pause")); buttonBox->addButton(mPauseButton, QDialogButtonBox::ActionRole); buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true); connect(mPauseButton, &QPushButton::clicked, this, &KgetProgressDialog::slotPause); connect(buttonBox, &QDialogButtonBox::rejected, this, &KgetProgressDialog::slotCancel); mCancelled = mPaused = false; } void KgetProgressDialog::slotPause() { if ((mPaused = !mPaused) == false) mPauseButton->setText(i18n("Pause")); else mPauseButton->setText(i18n("Resume")); } void KgetProgressDialog::slotCancel() { mCancelled = true; reject(); } void Synchronizer::synchronizeWithKGet() { bool isLeftLocal = QUrl::fromUserInput(leftBaseDirectory(), QString(), QUrl::AssumeLocalFile).isLocalFile(); KgetProgressDialog *progDlg = nullptr; int processedCount = 0, totalCount = 0; QListIterator it(resultList); while (it.hasNext()) if (it.next()->isMarked()) totalCount++; it.toFront(); while (it.hasNext()) { SynchronizerFileItem * item = it.next(); if (item->isMarked()) { QUrl downloadURL; QUrl destURL; QString destDir; QString leftDirName = item->leftDirectory().isEmpty() ? "" : item->leftDirectory() + '/'; QString rightDirName = item->rightDirectory().isEmpty() ? "" : item->rightDirectory() + '/'; if (progDlg == nullptr) { progDlg = new KgetProgressDialog(krMainWindow, i18n("Krusader::Synchronizer"), i18n("Feeding the URLs to KGet"), true); progDlg->progressBar()->setMaximum(totalCount); progDlg->show(); qApp->processEvents(); } if (item->task() == TT_COPY_TO_RIGHT && !isLeftLocal) { downloadURL = Synchronizer::fsUrl(leftBaseDirectory() + leftDirName + item->leftName()); destDir = rightBaseDirectory() + rightDirName; destURL = Synchronizer::fsUrl(destDir + item->rightName()); if (item->isDir()) destDir += item->leftName(); } if (item->task() == TT_COPY_TO_LEFT && isLeftLocal) { downloadURL = Synchronizer::fsUrl(rightBaseDirectory() + rightDirName + item->rightName()); destDir = leftBaseDirectory() + leftDirName; destURL = Synchronizer::fsUrl(destDir + item->leftName()); if (item->isDir()) destDir += item->rightName(); } // creating the directory system for (int i = 0; i >= 0 ; i = destDir.indexOf('/', i + 1)) if (!QDir(destDir.left(i)).exists()) QDir().mkdir(destDir.left(i)); if (!item->isDir() && !downloadURL.isEmpty()) { if (QFile(destURL.path()).exists()) QFile(destURL.path()).remove(); QString source = downloadURL.toDisplayString(); if (source.indexOf('@') >= 2) { /* is this an ftp proxy URL? */ int lastAt = source.lastIndexOf('@'); QString startString = source.left(lastAt); QString endString = source.mid(lastAt); startString.replace('@', "%40"); source = startString + endString; } KProcess p; p << KrServices::fullPathName("kget") << source << destURL.path(); if (!p.startDetached()) KMessageBox::error(parentWidget, i18n("Error executing %1.", KrServices::fullPathName("kget"))); } progDlg->progressBar()->setValue(++processedCount); QTime t; t.start(); bool canExit = false; do { qApp->processEvents(); if (progDlg->wasCancelled()) break; canExit = (t.elapsed() > 100); if (progDlg->isPaused() || !canExit) usleep(10000); } while (progDlg->isPaused() || !canExit); if (progDlg->wasCancelled()) break; } } if (progDlg) delete progDlg; } bool Synchronizer::isDir(const FileItem *file) { if (followSymLinks) { return file->isDir(); } else { return file->isDir() && !file->isSymLink(); } } QString Synchronizer::readLink(const FileItem *file) { if (file->isSymLink()) return file->getSymDest(); else return QString(); } SynchronizerFileItem *Synchronizer::getItemAt(unsigned ndx) { if (ndx < (unsigned)resultList.count()) return resultList.at(ndx); else return nullptr; } diff --git a/krusader/Synchronizer/synchronizerdirlist.cpp b/krusader/Synchronizer/synchronizerdirlist.cpp index 3446c9af..24a2d928 100644 --- a/krusader/Synchronizer/synchronizerdirlist.cpp +++ b/krusader/Synchronizer/synchronizerdirlist.cpp @@ -1,181 +1,181 @@ /***************************************************************************** * Copyright (C) 2006 Csaba Karai * * Copyright (C) 2006-2018 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 "synchronizerdirlist.h" #ifdef HAVE_POSIX_ACL #include #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS #include #endif #endif #include // QtCore #include // QtWidgets #include #include #include #include #include #include "../FileSystem/filesystem.h" #include "../FileSystem/krpermhandler.h" #include "../krservices.h" SynchronizerDirList::SynchronizerDirList(QWidget *w, bool hidden) : QObject(), QHash(), fileIterator(nullptr), parentWidget(w), busy(false), result(false), ignoreHidden(hidden), currentUrl() { } SynchronizerDirList::~SynchronizerDirList() { if (fileIterator) delete fileIterator; QHashIterator< QString, FileItem *> lit(*this); while (lit.hasNext()) delete lit.next().value(); } FileItem *SynchronizerDirList::search(const QString &name, bool ignoreCase) { if (!ignoreCase) { if (!contains(name)) return nullptr; return (*this)[ name ]; } QHashIterator iter(*this); iter.toFront(); QString file = name.toLower(); while (iter.hasNext()) { FileItem *item = iter.next().value(); if (file == item->getName().toLower()) return item; } return nullptr; } FileItem *SynchronizerDirList::first() { if (fileIterator == nullptr) fileIterator = new QHashIterator (*this); fileIterator->toFront(); if (fileIterator->hasNext()) return fileIterator->next().value(); return nullptr; } FileItem *SynchronizerDirList::next() { if (fileIterator == nullptr) fileIterator = new QHashIterator (*this); if (fileIterator->hasNext()) return fileIterator->next().value(); return nullptr; } bool SynchronizerDirList::load(const QString &urlIn, bool wait) { if (busy) return false; currentUrl = urlIn; const QUrl url = QUrl::fromUserInput(urlIn, QString(), QUrl::AssumeLocalFile); QHashIterator< QString, FileItem *> lit(*this); while (lit.hasNext()) delete lit.next().value(); clear(); if (fileIterator) { delete fileIterator; fileIterator = nullptr; } if (url.isLocalFile()) { const QString dir = FileSystem::ensureTrailingSlash(url).path(); QT_DIR* qdir = QT_OPENDIR(dir.toLocal8Bit()); if (!qdir) { KMessageBox::error(parentWidget, i18n("Cannot open the folder %1.", dir), i18n("Error")); emit finished(result = false); return false; } QT_DIRENT* dirEnt; while ((dirEnt = QT_READDIR(qdir)) != nullptr) { const QString name = QString::fromLocal8Bit(dirEnt->d_name); if (name == "." || name == "..") continue; if (ignoreHidden && name.startsWith('.')) continue; FileItem *item = FileSystem::createLocalFileItem(name, dir); insert(name, item); } QT_CLOSEDIR(qdir); emit finished(result = true); return true; } else { KIO::ListJob *job = KIO::listDir(KrServices::escapeFileUrl(url), KIO::HideProgressInfo, true); connect(job, &KIO::ListJob::entries, this, &SynchronizerDirList::slotEntries); connect(job, &KIO::ListJob::result, this, &SynchronizerDirList::slotListResult); busy = true; if (!wait) return true; while (busy) qApp->processEvents(); return result; } } void SynchronizerDirList::slotEntries(KIO::Job *job, const KIO::UDSEntryList& entries) { - auto *listJob = static_cast(job); + auto *listJob = dynamic_cast(job); for (const KIO::UDSEntry entry : entries) { FileItem *item = FileSystem::createFileItemFromKIO(entry, listJob->url()); if (item) { insert(item->getName(), item); } } } void SynchronizerDirList::slotListResult(KJob *job) { busy = false; if (job && job->error()) { job->uiDelegate()->showErrorMessage(); emit finished(result = false); return; } emit finished(result = true); } diff --git a/krusader/paneltabbar.cpp b/krusader/paneltabbar.cpp index 8934822a..d1bf9d31 100644 --- a/krusader/paneltabbar.cpp +++ b/krusader/paneltabbar.cpp @@ -1,339 +1,339 @@ /***************************************************************************** * Copyright (C) 2002 Shie Erlich * * Copyright (C) 2002 Rafi Yanai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * Based on original code from Sebastian Trueg * * * * 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 "paneltabbar.h" #include "defaults.h" #include "tabactions.h" #include "../krglobal.h" #include "../icon.h" #include "Panel/listpanel.h" #include "Panel/panelfunc.h" // QtCore #include // QtGui #include #include #include // QtWidgets #include #include #include #include #include static const int sDragEnterDelay = 500; // msec PanelTabBar::PanelTabBar(QWidget *parent, TabActions *actions): QTabBar(parent), _maxTabLength(0), _tabClicked(false), _draggingTab(false), _dragTabIndex(-1) { _panelActionMenu = new KActionMenu(i18n("Panel"), this); setAcceptDrops(true); insertAction(actions->actNewTab); insertAction(actions->actLockTab); insertAction(actions->actPinTab); insertAction(actions->actDupTab); insertAction(actions->actMoveTabToOtherSide); insertAction(actions->actCloseTab); insertAction(actions->actCloseInactiveTabs); insertAction(actions->actCloseDuplicatedTabs); setMovable(true); // enable drag'n'drop _dragTimer = new QTimer(this); _dragTimer->setSingleShot(true); _dragTimer->setInterval(sDragEnterDelay); connect(_dragTimer, &QTimer::timeout, this, [=]() { if (_dragTabIndex != -1 && _dragTabIndex != currentIndex()) { setCurrentIndex(_dragTabIndex); } _dragTabIndex = -1; }); setShape(QTabBar::TriangularSouth); } void PanelTabBar::insertAction(QAction* action) { _panelActionMenu->addAction(action); } int PanelTabBar::addPanel(ListPanel *panel, bool setCurrent, KrPanel *nextTo) { int insertIndex = -1; if (nextTo) { for (int i = 0; i < count(); i++) { if (getPanel(i) == nextTo) { insertIndex = i + 1; break; } } } QUrl virtualPath = panel->virtualPath(); panel->setPinnedUrl(virtualPath); const QString text = squeeze(virtualPath); const int index = insertIndex != -1 ? insertTab(insertIndex, text) : addTab(text); setTabData(index, QVariant((long long) panel)); setIcon(index, panel); // make sure all tabs lengths are correct layoutTabs(); if (setCurrent) setCurrentIndex(index); return index; } ListPanel* PanelTabBar::getPanel(int tabIdx) { QVariant v = tabData(tabIdx); if (v.isNull()) return nullptr; return (ListPanel*)v.toLongLong(); } void PanelTabBar::changePanel(int tabIdx, ListPanel *panel) { setTabData(tabIdx, QVariant((long long) panel)); } ListPanel* PanelTabBar::removePanel(int index, ListPanel* &panelToDelete) { panelToDelete = getPanel(index); // old panel to kill later disconnect(panelToDelete, nullptr, this, nullptr); removeTab(index); layoutTabs(); return getPanel(currentIndex()); } ListPanel* PanelTabBar::removeCurrentPanel(ListPanel* &panelToDelete) { return removePanel(currentIndex(), panelToDelete); } void PanelTabBar::updateTab(ListPanel *panel) { // find which is the correct tab for (int i = 0; i < count(); i++) { if ((ListPanel*)tabData(i).toLongLong() == panel) { setPanelTextToTab(i, panel); setIcon(i, panel); break; } } } void PanelTabBar::duplicateTab() { int id = currentIndex(); emit newTab(((ListPanel*)tabData(id).toLongLong())->virtualPath()); } void PanelTabBar::setIcon(int index, ListPanel *panel) { Icon tabIcon; if (panel->isLocked()) { tabIcon = Icon("lock"); } else if (panel->isPinned()) { tabIcon = Icon("pin"); } setTabIcon(index, tabIcon); } QString PanelTabBar::squeeze(const QUrl &url, int tabIndex) { const QString longText = url.isEmpty() ? i18n("[invalid]") : url.isLocalFile() ? url.path() : url.toDisplayString(); if (tabIndex >= 0) setTabToolTip(tabIndex, longText); const KConfigGroup group(krConfig, "Look&Feel"); const bool showLongNames = group.readEntry("Fullpath Tab Names", _FullPathTabNames); QString text; if (!showLongNames) { const QString scheme = url.scheme().isEmpty() || url.isLocalFile() ? "" : (url.scheme() + ':'); const QString host = url.host().isEmpty() ? "" : (url.host() + ':'); const QString name = url.isLocalFile() && url.fileName().isEmpty() ? "/" : url.fileName(); text = scheme + host + name; } else { text = longText; } if (text.isEmpty()) text = i18nc("invalid URL path", "?"); // set the real max length QFontMetrics fm(fontMetrics()); - _maxTabLength = (static_cast(parent())->width() - (6 * fm.width("W"))) / fm.width("W"); + _maxTabLength = (dynamic_cast(parent())->width() - (6 * fm.width("W"))) / fm.width("W"); // each tab gets a fair share of the max tab length const int effectiveTabLength = _maxTabLength / (count() == 0 ? 1 : count()); const int labelWidth = fm.width("W") * effectiveTabLength; const int textWidth = fm.width(text); if (textWidth <= labelWidth) return text; // squeeze text - start with the dots only QString squeezedText = "..."; int squeezedWidth = fm.width(squeezedText); int letters = text.length() * (labelWidth - squeezedWidth) / textWidth / 2; if (labelWidth < squeezedWidth) letters = 1; squeezedText = text.left(letters) + "..." + text.right(letters); squeezedWidth = fm.width(squeezedText); if (squeezedWidth < labelWidth) { // we estimated too short // add letters while text < label do { letters++; squeezedText = text.left(letters) + "..." + text.right(letters); squeezedWidth = fm.width(squeezedText); } while (squeezedWidth < labelWidth); letters--; squeezedText = text.left(letters) + "..." + text.right(letters); } else if (squeezedWidth > labelWidth) { // we estimated too long // remove letters while text > label do { letters--; squeezedText = text.left(letters) + "..." + text.right(letters); squeezedWidth = fm.width(squeezedText); } while (letters && squeezedWidth > labelWidth); } return squeezedText; } void PanelTabBar::resizeEvent(QResizeEvent *e) { QTabBar::resizeEvent(e); layoutTabs(); } void PanelTabBar::mouseMoveEvent(QMouseEvent* e) { QTabBar::mouseMoveEvent(e); if(_tabClicked) { _draggingTab = true; emit draggingTab(e); } } void PanelTabBar::mousePressEvent(QMouseEvent* e) { int clickedTab = tabAt(e->pos()); if (-1 == clickedTab) { // clicked on nothing ... QTabBar::mousePressEvent(e); return; } _tabClicked = true; setCurrentIndex(clickedTab); ListPanel *p = getPanel(clickedTab); if (p) p->slotFocusOnMe(); if (e->button() == Qt::RightButton) { // show the popup menu _panelActionMenu->menu()->popup(e->globalPos()); } else { if (e->button() == Qt::MidButton)// close the current tab emit closeCurrentTab(); } QTabBar::mousePressEvent(e); } void PanelTabBar::mouseReleaseEvent(QMouseEvent* e) { QTabBar::mouseReleaseEvent(e); if(_draggingTab) emit draggingTabFinished(e); _draggingTab = false; _tabClicked = false; } void PanelTabBar::dragEnterEvent(QDragEnterEvent *e) { e->accept(); handleDragEvent(tabAt(e->pos())); QTabBar::dragEnterEvent(e); } void PanelTabBar::dragLeaveEvent(QDragLeaveEvent *) { handleDragEvent(-1); } void PanelTabBar::dragMoveEvent(QDragMoveEvent *e) { e->ignore(); handleDragEvent(tabAt(e->pos())); QTabBar::dragMoveEvent(e); } void PanelTabBar::handleDragEvent(int tabIndex) { if (_dragTabIndex == tabIndex) return; _dragTabIndex = tabIndex; if (_dragTabIndex == -1) { _dragTimer->stop(); } else { _dragTimer->start(); } } void PanelTabBar::layoutTabs() { for (int i = 0; i < count(); i++) { setPanelTextToTab(i, (ListPanel*) tabData(i).toLongLong()); } } void PanelTabBar::setPanelTextToTab(int tabIndex, ListPanel *panel) { // update tab text from pinnedUrl in case the tab is pinned if (panel->isPinned()) { setTabText(tabIndex, squeeze(panel->pinnedUrl(), tabIndex)); } else { setTabText(tabIndex, squeeze(panel->virtualPath(), tabIndex)); } } diff --git a/krusader/tabactions.cpp b/krusader/tabactions.cpp index a753fdc6..1a9bfd74 100644 --- a/krusader/tabactions.cpp +++ b/krusader/tabactions.cpp @@ -1,120 +1,120 @@ /***************************************************************************** * Copyright (C) 2000 Shie Erlich * * Copyright (C) 2000 Rafi Yanai * * Copyright (C) 2011 Jan Lepper * * Copyright (C) 2004-2018 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 "tabactions.h" #include "krmainwindow.h" #include "panelmanager.h" #include "Panel/listpanel.h" // QtWidgets #include #include TabActions::TabActions(QObject *parent, KrMainWindow *mainWindow) : ActionsBase(parent, mainWindow) { actNewTab = action(i18n("New Tab"), "tab-new", QKeySequence::keyBindings(QKeySequence::AddTab), this, SLOT(newTab()), "new tab"); actDupTab = action(i18n("Duplicate Current Tab"), "tab-duplicate", Qt::ALT + Qt::CTRL + Qt::SHIFT + Qt::Key_N, SLOT(duplicateTab()), "duplicate tab"); actMoveTabToOtherSide = action(i18n("Move Current Tab to Other Side"), nullptr, Qt::CTRL + Qt::SHIFT + Qt::Key_O, SLOT(moveTabToOtherSide()), "move_tab_to_other_side"); actCloseTab = action(i18n("Close Current Tab"), "tab-close", KStandardShortcut::close(), this, SLOT(closeTab()), "close tab"); actNextTab = action(i18n("Next Tab"), QString(), KStandardShortcut::tabNext(), this, SLOT(nextTab()), "next tab"); actPreviousTab = action(i18n("Previous Tab"), QString(), KStandardShortcut::tabPrev(), this, SLOT(previousTab()), "previous tab"); actCloseInactiveTabs = action(i18n("Close Inactive Tabs"), nullptr, 0, SLOT(closeInactiveTabs()), "close inactive tabs"); actCloseDuplicatedTabs = action(i18n("Close Duplicated Tabs"), nullptr, 0, SLOT(closeDuplicatedTabs()), "close duplicated tabs"); actLockTab = action(i18n("Lock Tab"), "lock", 0, SLOT(lockTab()), "lock tab"); actPinTab = action(i18n("Pin Tab"), "pin", 0, SLOT(pinTab()), "pin tab"); } inline PanelManager *TabActions::activeManager() { - return static_cast( + return dynamic_cast( static_cast(_mainWindow)->activeManager()); } void TabActions::refreshActions() { if (!activeManager()) return; int tabCount = activeManager()->tabCount(); actCloseTab->setEnabled(tabCount > 1); actCloseInactiveTabs->setEnabled(tabCount > 1); actCloseDuplicatedTabs->setEnabled(tabCount > 1); actMoveTabToOtherSide->setEnabled(tabCount > 1); actNextTab->setEnabled(tabCount > 1); actPreviousTab->setEnabled(tabCount > 1); bool locked = activeManager()->currentPanel()->gui->isLocked(); actLockTab->setText(locked ? i18n("Unlock Tab") : i18n("Lock Tab")); bool pinned = activeManager()->currentPanel()->gui->isPinned(); actPinTab->setText(pinned ? i18n("Unpin Tab") : i18n("Pin Tab")); } void TabActions::newTab() { activeManager()->slotNewTab(); } void TabActions::duplicateTab() { KrPanel *activePanel = static_cast(_mainWindow)->activePanel(); activeManager()->newTab(activePanel->virtualPath(), activePanel); } void TabActions::moveTabToOtherSide() { activeManager()->moveTabToOtherSide(); } void TabActions::nextTab() { activeManager()->slotNextTab(); } void TabActions::previousTab() { activeManager()->slotPreviousTab(); } void TabActions::closeTab() { activeManager()->slotCloseTab(); } void TabActions::closeInactiveTabs() { activeManager()->slotCloseInactiveTabs(); } void TabActions::closeDuplicatedTabs() { activeManager()->slotCloseDuplicatedTabs(); } void TabActions::lockTab() { activeManager()->slotLockTab(); } void TabActions::pinTab() { activeManager()->slotPinTab(); }