/******************************************************************************* XDG desktop portal implementation for TDE Copyright © 2024 Mavridis Philippe This program or library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Improvements and feedback are welcome! *******************************************************************************/ // TQt #include #include #include #include // TDE #include #include #include #include // Portal #include "file_chooser_portal.h" #include "file_chooser_portal.moc" class KMultiFileSaveDialog : public KFileDialog { public: KMultiFileSaveDialog(TQString startDir, TQStringList filelist, const char *name, bool modal) : KFileDialog(startDir, TQString::null, nullptr, name, modal), m_files(filelist) {} KURL::List urls() { return m_urls; } protected slots: // The below is a workaround to prevent the dialog from disappearing // until we are done handling conflicting files. // Don't kick it, it works. void done(int) {} void actuallyDone(int r) { KFileDialog::done(r); } void slotCancel() { actuallyDone(KFileDialog::Rejected); } void slotOk() { KFileDialog::slotOk(); m_urls.clear(); // Check if there are conflicting files in the target directory // and prompt for action TQDir d(selectedURL().path()); for (TQStringList::Iterator it = m_files.begin(); it != m_files.end(); ++it) { TQString filename(*it); TQFileInfo fi(d, filename); KURL url(fi.absFilePath()); if (fi.exists()) { TDEIO::RenameDlg rename(nullptr, caption(), TQString::null, fi.absFilePath(), TDEIO::M_OVERWRITE); int result = rename.exec(); switch (result) { case TDEIO::R_RENAME: m_urls << rename.newDestURL(); break; case TDEIO::R_OVERWRITE: m_urls << url.url(); break; case TDEIO::R_CANCEL: default: actuallyDone(KFileDialog::Rejected); break; } } else { m_urls << url.url(); } } actuallyDone(KFileDialog::Accepted); } private: TQStringList m_files; KURL::List m_urls; }; TDEFileChooserPortal::TDEFileChooserPortal(TQT_DBusConnection &connection) : m_connection(connection) {} TDEFileChooserPortal::~TDEFileChooserPortal() {} void TDEFileChooserPortal::OpenFileAsync(int asyncCallId, const TQT_DBusObjectPath& handle, const TQString& app_id, const TQString& parent_window, const TQString& title, const TQT_DBusVariantMap &options) { FileDialogOpts opts; opts.caption = title; if (OPTION_VALID("accept_label", "s")) opts.okButtonText = options["accept_label"].value.toString(); if (OPTION_VALID("directory", "b")) opts.directory = options["directory"].value.toBool(); if (OPTION_VALID("multiple", "b")) opts.multiple = options["multiple"].value.toBool(); if (OPTION_VALID("modal", "b")) opts.modal = options["modal"].value.toBool(); if (OPTION_VALID("filters", "a(sa(us))")) opts.filters = parseFilterList(options["filters"], options["current_filter"]); opts.windowId = parse_window_id(parent_window); // Execute dialog execFileDialog(&TDEFileChooserPortal::OpenFileAsyncReply, asyncCallId, opts, handle); } void TDEFileChooserPortal::SaveFileAsync(int asyncCallId, const TQT_DBusObjectPath& handle, const TQString& app_id, const TQString& parent_window, const TQString& title, const TQT_DBusVariantMap &options) { FileDialogOpts opts; opts.caption = title; if (OPTION_VALID("accept_label", "s")) opts.okButtonText = options["accept_label"].value.toString(); if (OPTION_VALID("directory", "b")) opts.directory = options["directory"].value.toBool(); if (OPTION_VALID("multiple", "b")) opts.multiple = options["multiple"].value.toBool(); if (OPTION_VALID("modal", "b")) opts.modal = options["modal"].value.toBool(); if (OPTION_VALID("filters", "a(sa(us))")) opts.filters = parseFilterList(options["filters"], options["current_filter"]); if (OPTION_VALID("current_folder", "ay")) opts.startDir = bytelist_to_string(options["current_folder"].value.toList().toByteList()); if (OPTION_VALID("current_name", "s")) opts.startName = options["current_name"].value.toString(); opts.windowId = parse_window_id(parent_window); execFileDialog(&TDEFileChooserPortal::SaveFileAsyncReply, asyncCallId, opts, handle); } // TODO move some of the dialog handling stuff to execFileDialog() void TDEFileChooserPortal::SaveFilesAsync(int asyncCallId, const TQT_DBusObjectPath& handle, const TQString& app_id, const TQString& parent_window, const TQString& title, const TQMap &options) { // Get list of files to save if (!OPTION_VALID("files", "aay")) { kdWarning() << "TDEFileChooserPortal::SaveFiles: " << "Invalid or missing files option" << endl; SaveFilesAsyncReply(asyncCallId, 0, TQT_DBusVariantMap()); return; } TQT_DBusValueList filelist = options["files"].value.toTQValueList(); TQT_DBusValueList::iterator fi; TQStringList files; for (fi = filelist.begin(); fi != filelist.end(); ++fi) { files << bytelist_to_string((*fi).toList().toByteList()); } // Parse options FileDialogOpts opts; opts.caption = title; if (OPTION_VALID("accept_label", "s")) opts.okButtonText = options["accept_label"].value.toString(); if (OPTION_VALID("modal", "b")) opts.modal = options["modal"].value.toBool(); if (OPTION_VALID("current_folder", "ay")) opts.startDir = bytelist_to_string(options["current_folder"].value.toList().toByteList()); // We can't just use execFileDialog because we need to do some special processing KMultiFileSaveDialog *dialog = new KMultiFileSaveDialog(opts.startDir, files, "xdg-tde-file-chooser", opts.modal); dialog->setMode(KFile::LocalOnly | KFile::Directory); if (!opts.caption.isNull()) dialog->setPlainCaption(opts.caption); if (!opts.okButtonText.isNull()) dialog->okButton()->setText(opts.okButtonText); if (opts.windowId > 0) KWin::setMainWindow(dialog, opts.windowId); auto *sender = new DialogResultSender (this, asyncCallId, dialog, &TDEFileChooserPortal::prepareSaveFilesReply, &TDEFileChooserPortal::SaveFilesAsyncReply); dialog->show(); sender->start(); } void TDEFileChooserPortal::handleMethodReply(const TQT_DBusMessage &reply) { m_connection.send(reply); } bool TDEFileChooserPortal::handleSignalSend(const TQT_DBusMessage& reply) { handleMethodReply(reply); return true; } void TDEFileChooserPortal::execFileDialog(ResultSendCallback callback, int asyncCallId, FileDialogOpts options, const TQT_DBusObjectPath& handle) { KFileDialog *dialog = new KFileDialog(options.startDir, TQString::null, nullptr, "xdg-tde-file-chooser", options.modal); uint mode = KFile::LocalOnly; if (options.savingMode) { dialog->setOperationMode(KFileDialog::Saving); } else { mode |= KFile::ExistingOnly; } dialog->setMode(mode | options.mode()); if (!options.caption.isNull()) { dialog->setPlainCaption(options.caption); } if (!options.okButtonText.isNull()) { dialog->okButton()->setText(options.okButtonText); } if (!options.filters.isNull()) { dialog->setFilter(options.filters); } dialog->setSelection(options.startName); if (options.windowId > 0) KWin::setMainWindow(dialog, options.windowId); auto *sender = new DialogResultSender (this, asyncCallId, dialog, &TDEFileChooserPortal::prepareReply, callback); dialog->show(); sender->start(); } DialogResult TDEFileChooserPortal::prepareReply(TQDialog *dlg) { KFileDialog *fd = static_cast(dlg); KURL::List urllist = fd->selectedURLs(); DialogResult res; TQT_DBusDataList urls = kurl_list_to_datalist(urllist); TQT_DBusVariant var = TQT_DBusData::fromList(urls).getAsVariantData().toVariant(); res.results.insert("uris", var); res.response = urllist.isEmpty() ? 1 : 0; return res; } // Special treatment for SaveFiles() // TODO revise this and possibly merge with prepareReply() DialogResult TDEFileChooserPortal::prepareSaveFilesReply(TQDialog *dlg) { DialogResult res; if (dlg->result() == TQDialog::Accepted) { KMultiFileSaveDialog *fd = static_cast(dlg); TQT_DBusDataList urls = kurl_list_to_datalist(fd->urls()); TQT_DBusVariant var = TQT_DBusData::fromList(urls).getAsVariantData().toVariant(); res.response = 0; res.results.insert("uris", var); } else res.response = 1; return res; } TQString TDEFileChooserPortal::parseFilter(const TQT_DBusData data) { TQStringList patternList; TQT_DBusValueList filterData = data.toStruct(); TQString label = filterData[0].toString(); TQT_DBusValueList patterns = filterData[1].toTQValueList(); TQT_DBusValueList::iterator fp; for (fp = patterns.begin(); fp != patterns.end(); ++fp) { TQT_DBusValueList patternData = (*fp).toStruct(); bool isMime = (patternData[0].toUInt32() == 1); if (isMime) { // KFileDialog cannot handle both a mime and a simple // extension filter, so in case we get a mimetype, // we just get the associated extensions from the // MIME system TQString mime = patternData[1].toString(); patternList += KMimeType::mimeType(mime)->patterns(); } else { TQString patternString = patternData[1].toString(); // Regex patterns like *.[hH][tT][mM][lL] are unnecessary for us // and causes a problem with the save dialog's extension autoselection TQString finalPattern = patternString; if (TQRegExp("[*.](?:[[](?:[A-Za-z]{2})[]])+").search(patternString) != -1) { finalPattern = "*."; int pos = patternString.find('['); while (pos != -1) { finalPattern += patternString[pos + 1]; pos = patternString.find('[', patternString.find(']', pos)); } } patternList += finalPattern; } } TQString patternString = patternList.join(" "); return TQString("%1|%2 (%3)").arg(patternString, label, patternString); } TQString TDEFileChooserPortal::parseFilterList(const TQT_DBusVariant filterData, const TQT_DBusVariant currentFilterData) { TQStringList filterList; TQT_DBusValueList filters = filterData.value.toTQValueList(); if (filters.count() > 0) { TQT_DBusValueList::iterator f; for (f = filters.begin(); f != filters.end(); ++f) filterList += parseFilter((*f)); } if (check_variant(currentFilterData, "(sa(us))")) { TQString currentFilter = parseFilter(currentFilterData.value); if (filterList.contains(currentFilter)) { // We have no way to affect the filter selection of the dialog, // so we just move the current filter to the top of the list // to get it selected automatically filterList.remove(currentFilter); filterList.prepend(currentFilter); } } return filterList.join("\n"); } // kate: replace-tabs true; tab-width 4; indent-width 4;