File indexing completed on 2024-04-28 05:48:19
0001 /** 0002 * \file 0003 * 0004 * \brief Kate Close Except/Like plugin implementation 0005 * 0006 * SPDX-FileCopyrightText: 2012 Alex Turbov <i.zaufi@gmail.com> 0007 * 0008 * \date Thu Mar 8 08:13:43 MSK 2012 -- Initial design 0009 */ 0010 /* 0011 * KateCloseExceptPlugin is free software: you can redistribute it and/or modify it 0012 * under the terms of the GNU General Public License as published by the 0013 * Free Software Foundation, either version 3 of the License, or 0014 * (at your option) any later version. 0015 * 0016 * KateCloseExceptPlugin is distributed in the hope that it will be useful, but 0017 * WITHOUT ANY WARRANTY; without even the implied warranty of 0018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 0019 * See the GNU General Public License for more details. 0020 * 0021 * You should have received a copy of the GNU General Public License along 0022 * with this program. If not, see <http://www.gnu.org/licenses/>. 0023 */ 0024 0025 // Project specific includes 0026 #include "close_except_plugin.h" 0027 #include "close_confirm_dialog.h" 0028 0029 // Standard includes 0030 #include <KAboutData> 0031 #include <KActionCollection> 0032 #include <KLocalizedString> 0033 #include <KPluginFactory> 0034 #include <KTextEditor/Application> 0035 #include <KTextEditor/MainWindow> 0036 #include <KXMLGUIFactory> 0037 #include <QFileInfo> 0038 #include <QUrl> 0039 #include <kio/global.h> 0040 0041 K_PLUGIN_FACTORY_WITH_JSON(CloseExceptPluginFactory, "katecloseexceptplugin.json", registerPlugin<kate::CloseExceptPlugin>();) 0042 0043 namespace kate 0044 { 0045 // BEGIN CloseExceptPlugin 0046 CloseExceptPlugin::CloseExceptPlugin(QObject *application, const QVariantList &) 0047 : KTextEditor::Plugin(application) 0048 { 0049 } 0050 0051 QObject *CloseExceptPlugin::createView(KTextEditor::MainWindow *parent) 0052 { 0053 return new CloseExceptPluginView(parent, this); 0054 } 0055 0056 void CloseExceptPlugin::readSessionConfig(const KConfigGroup &config) 0057 { 0058 const KConfigGroup scg(&config, QStringLiteral("menu")); 0059 m_show_confirmation_needed = scg.readEntry(QStringLiteral("ShowConfirmation"), true); 0060 } 0061 0062 void CloseExceptPlugin::writeSessionConfig(KConfigGroup &config) 0063 { 0064 KConfigGroup scg(&config, QStringLiteral("menu")); 0065 scg.writeEntry(QStringLiteral("ShowConfirmation"), m_show_confirmation_needed); 0066 scg.sync(); 0067 } 0068 // END CloseExceptPlugin 0069 0070 // BEGIN CloseExceptPluginView 0071 CloseExceptPluginView::CloseExceptPluginView(KTextEditor::MainWindow *mw, CloseExceptPlugin *plugin) 0072 : QObject(mw) 0073 , m_plugin(plugin) 0074 , m_show_confirmation_action(new KToggleAction(i18nc("@action:inmenu", "Show Confirmation"), this)) 0075 , m_except_menu(new KActionMenu(i18nc("@action:inmenu close docs except the following...", "Close Except"), this)) 0076 , m_like_menu(new KActionMenu(i18nc("@action:inmenu close docs like the following...", "Close Like"), this)) 0077 , m_mainWindow(mw) 0078 { 0079 KXMLGUIClient::setComponentName(QStringLiteral("katecloseexceptplugin"), i18n("Close Except/Like Plugin")); 0080 setXMLFile(QStringLiteral("ui.rc")); 0081 0082 actionCollection()->addAction(QStringLiteral("file_close_except"), m_except_menu); 0083 actionCollection()->addAction(QStringLiteral("file_close_like"), m_like_menu); 0084 0085 connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::documentCreated, this, &CloseExceptPluginView::documentCreated); 0086 // Configure toggle action and connect it to update state 0087 m_show_confirmation_action->setChecked(m_plugin->showConfirmationNeeded()); 0088 connect(m_show_confirmation_action.data(), &KToggleAction::toggled, m_plugin, &CloseExceptPlugin::toggleShowConfirmation); 0089 // 0090 connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &CloseExceptPluginView::viewCreated); 0091 // Fill menu w/ currently opened document masks/groups 0092 updateMenu(); 0093 0094 m_mainWindow->guiFactory()->addClient(this); 0095 } 0096 0097 CloseExceptPluginView::~CloseExceptPluginView() 0098 { 0099 m_mainWindow->guiFactory()->removeClient(this); 0100 } 0101 0102 void CloseExceptPluginView::viewCreated(KTextEditor::View *view) 0103 { 0104 connectToDocument(view->document()); 0105 updateMenu(); 0106 } 0107 0108 void CloseExceptPluginView::documentCreated(KTextEditor::Editor *, KTextEditor::Document *document) 0109 { 0110 connectToDocument(document); 0111 updateMenu(); 0112 } 0113 0114 void CloseExceptPluginView::connectToDocument(KTextEditor::Document *document) 0115 { 0116 // Subscribe self to document close and name changes 0117 connect(document, &KTextEditor::Document::aboutToClose, this, &CloseExceptPluginView::updateMenuSlotStub); 0118 connect(document, &KTextEditor::Document::documentNameChanged, this, &CloseExceptPluginView::updateMenuSlotStub); 0119 connect(document, &KTextEditor::Document::documentUrlChanged, this, &CloseExceptPluginView::updateMenuSlotStub); 0120 } 0121 0122 void CloseExceptPluginView::updateMenuSlotStub(KTextEditor::Document *) 0123 { 0124 updateMenu(); 0125 } 0126 0127 void CloseExceptPluginView::appendActionsFrom(const std::set<QUrl> &paths, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction) 0128 { 0129 for (const QUrl &path : paths) { 0130 QString action = path.path() + QLatin1Char('*'); 0131 actions[action] = QPointer<QAction>(new QAction(action, menu)); 0132 menu->addAction(actions[action]); 0133 connect(actions[action].data(), &QAction::triggered, this, [this, closeFunction, action]() { 0134 (this->*closeFunction)(action); 0135 }); 0136 } 0137 } 0138 0139 void CloseExceptPluginView::appendActionsFrom(const std::set<QString> &masks, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction) 0140 { 0141 for (const QString &mask : masks) { 0142 QString action = mask.startsWith(QLatin1Char('*')) ? mask : mask + QLatin1Char('*'); 0143 actions[action] = QPointer<QAction>(new QAction(action, menu)); 0144 menu->addAction(actions[action]); 0145 connect(actions[action].data(), &QAction::triggered, this, [this, closeFunction, action]() { 0146 (this->*closeFunction)(action); 0147 }); 0148 } 0149 } 0150 0151 void CloseExceptPluginView::updateMenu(const std::set<QUrl> &paths, 0152 const std::set<QString> &masks, 0153 actions_map_type &actions, 0154 KActionMenu *menu, 0155 CloseFunction closeFunction) 0156 { 0157 // turn menu ON or OFF depending on collected results 0158 menu->setEnabled(!paths.empty()); 0159 0160 // Clear previous menus 0161 for (const auto &[_, action] : actions) { 0162 menu->removeAction(action); 0163 } 0164 actions.clear(); 0165 0166 // Form a new one 0167 appendActionsFrom(paths, actions, menu, closeFunction); 0168 if (!masks.empty()) { 0169 if (!paths.empty()) { 0170 menu->addSeparator(); // Add separator between paths and file's ext filters 0171 } 0172 appendActionsFrom(masks, actions, menu, closeFunction); 0173 } 0174 // Append 'Show Confirmation' toggle menu item 0175 menu->addSeparator(); // Add separator between paths and show confirmation 0176 menu->addAction(m_show_confirmation_action); 0177 } 0178 0179 void CloseExceptPluginView::updateMenu() 0180 { 0181 const QList<KTextEditor::Document *> &docs = KTextEditor::Editor::instance()->application()->documents(); 0182 if (docs.size() < 2) { 0183 // qDebug() << "No docs r (or the only) opened right now --> disable menu"; 0184 m_except_menu->setEnabled(false); 0185 m_except_menu->addSeparator(); 0186 m_like_menu->setEnabled(false); 0187 m_like_menu->addSeparator(); 0188 /// \note It seems there is always a document present... it named \em 'Untitled' 0189 } else { 0190 // Iterate over documents and form a set of candidates 0191 typedef std::set<QUrl> paths_set_type; 0192 typedef std::set<QString> paths_set_type_masks; 0193 paths_set_type doc_paths; 0194 paths_set_type_masks masks; 0195 for (KTextEditor::Document *document : docs) { 0196 const QString &ext = QFileInfo(document->url().path()).completeSuffix(); 0197 if (!ext.isEmpty()) { 0198 masks.insert(QStringLiteral("*.") + ext); 0199 } 0200 doc_paths.insert(KIO::upUrl(document->url())); 0201 } 0202 paths_set_type paths = doc_paths; 0203 // qDebug() << "stage #1: Collected" << paths.size() << "paths and" << masks.size() << "masks"; 0204 // Add common paths to the collection 0205 for (paths_set_type::iterator it = doc_paths.begin(), last = doc_paths.end(); it != last; ++it) { 0206 for (QUrl url = *it; (!url.path().isEmpty()) && url.path() != QLatin1String("/"); url = KIO::upUrl(url)) { 0207 paths_set_type::iterator not_it = it; 0208 for (++not_it; not_it != last; ++not_it) { 0209 if (!not_it->path().startsWith(url.path())) { 0210 break; 0211 } 0212 } 0213 if (not_it == last) { 0214 paths.insert(url); 0215 break; 0216 } 0217 } 0218 } 0219 // qDebug() << "stage #2: Collected" << paths.size() << "paths and" << masks.size() << "masks"; 0220 // 0221 updateMenu(paths, masks, m_except_actions, m_except_menu, &CloseExceptPluginView::closeExcept); 0222 updateMenu(paths, masks, m_like_actions, m_like_menu, &CloseExceptPluginView::closeLike); 0223 } 0224 } 0225 0226 void CloseExceptPluginView::close(const QString &item, const bool close_if_match) 0227 { 0228 QChar asterisk = QLatin1Char('*'); 0229 assert("Parameter seems invalid! Is smth has changed in the code?" && !item.isEmpty() && (item[0] == asterisk || item[item.size() - 1] == asterisk)); 0230 0231 const bool is_path = item[0] != asterisk; 0232 const QString mask = is_path ? item.left(item.size() - 1) : item; 0233 // qDebug() << "Going to close items [" << close_if_match << "/" << is_path << "]: " << mask; 0234 0235 QList<KTextEditor::Document *> docs2close; 0236 const QList<KTextEditor::Document *> &docs = KTextEditor::Editor::instance()->application()->documents(); 0237 for (KTextEditor::Document *document : docs) { 0238 const QString &path = KIO::upUrl(document->url()).path(); 0239 /// \note Take a dot in account, so \c *.c would not match for \c blah.kcfgc 0240 const QString &ext = QLatin1Char('.') + QFileInfo(document->url().fileName()).completeSuffix(); 0241 const bool match = (!is_path && mask.endsWith(ext)) || (is_path && path.startsWith(mask)); 0242 if (match == close_if_match) { 0243 // qDebug() << "*** Will close: " << document->url(); 0244 docs2close.push_back(document); 0245 } 0246 } 0247 if (docs2close.isEmpty()) { 0248 displayMessage(i18nc("@title:window", "Error"), i18nc("@info:tooltip", "No files to close ..."), KTextEditor::Message::Error); 0249 return; 0250 } 0251 // Show confirmation dialog if needed 0252 const bool removeNeeded = 0253 !m_plugin->showConfirmationNeeded() || CloseConfirmDialog(docs2close, m_show_confirmation_action, qobject_cast<QWidget *>(this)).exec(); 0254 if (removeNeeded) { 0255 if (docs2close.isEmpty()) { 0256 displayMessage(i18nc("@title:window", "Error"), i18nc("@info:tooltip", "No files to close ..."), KTextEditor::Message::Error); 0257 } else { 0258 // Close 'em all! 0259 KTextEditor::Editor::instance()->application()->closeDocuments(docs2close); 0260 updateMenu(); 0261 displayMessage(i18nc("@title:window", "Done"), i18np("%1 file closed", "%1 files closed", docs2close.size()), KTextEditor::Message::Positive); 0262 } 0263 } 0264 } 0265 0266 void CloseExceptPluginView::displayMessage(const QString &title, const QString &msg, KTextEditor::Message::MessageType level) 0267 { 0268 KTextEditor::View *kv = m_mainWindow->activeView(); 0269 if (!kv) { 0270 return; 0271 } 0272 0273 delete m_infoMessage; 0274 m_infoMessage = new KTextEditor::Message(xi18nc("@info", "<title>%1</title><nl/>%2", title, msg), level); 0275 m_infoMessage->setWordWrap(true); 0276 m_infoMessage->setPosition(KTextEditor::Message::TopInView); 0277 m_infoMessage->setAutoHide(5000); 0278 m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); 0279 m_infoMessage->setView(kv); 0280 kv->document()->postMessage(m_infoMessage); 0281 } 0282 0283 // END CloseExceptPluginView 0284 } // namespace kate 0285 0286 #include "close_except_plugin.moc" 0287 #include "moc_close_except_plugin.cpp" 0288 0289 // kate: hl C++11/Qt4;