close_except_plugin.cpp 11.5 KB
Newer Older
1 2 3 4 5
/**
 * \file
 *
 * \brief Kate Close Except/Like plugin implementation
 *
6 7
 * Copyright (C) 2012 Alex Turbov <i.zaufi@gmail.com>
 *
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 * \date Thu Mar  8 08:13:43 MSK 2012 -- Initial design
 */
/*
 * KateCloseExceptPlugin 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 3 of the License, or
 * (at your option) any later version.
 *
 * KateCloseExceptPlugin 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Project specific includes
#include "close_except_plugin.h"
#include "close_confirm_dialog.h"

// Standard includes
30 31 32 33
#include <KLocalizedString>
#include <KTextEditor/Application>
#include <KTextEditor/MainWindow>
#include <KTextEditor/Editor>
34 35 36 37
#include <KAboutData>
#include <KActionCollection>
#include <KPluginFactory>
#include <KPluginLoader>
Yuri Chornoivan's avatar
Yuri Chornoivan committed
38
#include <QFileInfo>
39 40 41
#include <QUrl>
#include <kio/global.h>
#include <KXMLGUIFactory>
42

Alex Turbov's avatar
Alex Turbov committed
43
K_PLUGIN_FACTORY_WITH_JSON(CloseExceptPluginFactory, "katecloseexceptplugin.json", registerPlugin<kate::CloseExceptPlugin>();)
44

45 46 47 48 49
namespace kate
{
// BEGIN CloseExceptPlugin
CloseExceptPlugin::CloseExceptPlugin(QObject *application, const QList<QVariant> &)
    : KTextEditor::Plugin(application)
50 51 52
{
}

53
QObject *CloseExceptPlugin::createView(KTextEditor::MainWindow *parent)
54
{
55
    return new CloseExceptPluginView(parent, this);
56 57
}

58
void CloseExceptPlugin::readSessionConfig(const KConfigGroup &config)
59
{
60 61
    const KConfigGroup scg(&config, QStringLiteral("menu"));
    m_show_confirmation_needed = scg.readEntry(QStringLiteral("ShowConfirmation"), true);
62 63
}

64
void CloseExceptPlugin::writeSessionConfig(KConfigGroup &config)
65
{
66 67
    KConfigGroup scg(&config, QStringLiteral("menu"));
    scg.writeEntry(QStringLiteral("ShowConfirmation"), m_show_confirmation_needed);
68 69
    scg.sync();
}
70
// END CloseExceptPlugin
71

72 73 74 75 76 77 78 79 80
// BEGIN CloseExceptPluginView
CloseExceptPluginView::CloseExceptPluginView(KTextEditor::MainWindow *mw, CloseExceptPlugin *plugin)
    : QObject(mw)
    , KXMLGUIClient()
    , m_plugin(plugin)
    , m_show_confirmation_action(new KToggleAction(i18nc("@action:inmenu", "Show Confirmation"), this))
    , m_except_menu(new KActionMenu(i18nc("@action:inmenu close docs except the following...", "Close Except"), this))
    , m_like_menu(new KActionMenu(i18nc("@action:inmenu close docs like the following...", "Close Like"), this))
    , m_mainWindow(mw)
Alex Turbov's avatar
Alex Turbov committed
81
{
82 83
    KXMLGUIClient::setComponentName(QStringLiteral("katecloseexceptplugin"), i18n("Close Except/Like Plugin"));
    setXMLFile(QStringLiteral("ui.rc"));
Alex Turbov's avatar
Alex Turbov committed
84

85 86
    actionCollection()->addAction(QStringLiteral("file_close_except"), m_except_menu);
    actionCollection()->addAction(QStringLiteral("file_close_like"), m_like_menu);
87

88
    connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::documentCreated, this, &CloseExceptPluginView::documentCreated);
89 90
    // Configure toggle action and connect it to update state
    m_show_confirmation_action->setChecked(m_plugin->showConfirmationNeeded());
91
    connect(m_show_confirmation_action.data(), &KToggleAction::toggled, m_plugin, &CloseExceptPlugin::toggleShowConfirmation);
92
    //
93
    connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &CloseExceptPluginView::viewCreated);
94 95 96
    // Fill menu w/ currently opened document masks/groups
    updateMenu();

97
    m_mainWindow->guiFactory()->addClient(this);
98 99 100 101
}

CloseExceptPluginView::~CloseExceptPluginView()
{
102
    m_mainWindow->guiFactory()->removeClient(this);
103 104
}

105
void CloseExceptPluginView::viewCreated(KTextEditor::View *view)
106 107 108 109 110
{
    connectToDocument(view->document());
    updateMenu();
}

111
void CloseExceptPluginView::documentCreated(KTextEditor::Editor *, KTextEditor::Document *document)
112 113 114 115 116
{
    connectToDocument(document);
    updateMenu();
}

117
void CloseExceptPluginView::connectToDocument(KTextEditor::Document *document)
118 119
{
    // Subscribe self to document close and name changes
120 121 122
    connect(document, &KTextEditor::Document::aboutToClose, this, &CloseExceptPluginView::updateMenuSlotStub);
    connect(document, &KTextEditor::Document::documentNameChanged, this, &CloseExceptPluginView::updateMenuSlotStub);
    connect(document, &KTextEditor::Document::documentUrlChanged, this, &CloseExceptPluginView::updateMenuSlotStub);
123 124
}

125
void CloseExceptPluginView::updateMenuSlotStub(KTextEditor::Document *)
126 127 128 129
{
    updateMenu();
}

130
void CloseExceptPluginView::appendActionsFrom(const std::set<QUrl> &paths, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction)
131
{
132
    Q_FOREACH (const QUrl &path, paths) {
133 134 135
        QString action = path.path() + QLatin1Char('*');
        actions[action] = QPointer<QAction>(new QAction(action, menu));
        menu->addAction(actions[action]);
136
        connect(actions[action].data(), &QAction::triggered, this, [this, closeFunction, action]() { (this->*closeFunction)(action); });
137 138 139
    }
}

140
void CloseExceptPluginView::appendActionsFrom(const std::set<QString> &masks, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction)
141
{
142
    Q_FOREACH (const QString &mask, masks) {
143 144
        QString action = mask.startsWith(QLatin1Char('*')) ? mask : mask + QLatin1Char('*');
        actions[action] = QPointer<QAction>(new QAction(action, menu));
145
        menu->addAction(actions[action]);
146
        connect(actions[action].data(), &QAction::triggered, this, [this, closeFunction, action]() { (this->*closeFunction)(action); });
147 148 149
    }
}

150
void CloseExceptPluginView::updateMenu(const std::set<QUrl> &paths, const std::set<QString> &masks, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction)
151 152 153 154 155
{
    // turn menu ON or OFF depending on collected results
    menu->setEnabled(!paths.empty());

    // Clear previous menus
156
    for (actions_map_type::iterator it = actions.begin(), last = actions.end(); it != last;) {
157 158 159 160
        menu->removeAction(*it);
        actions.erase(it++);
    }
    // Form a new one
161
    appendActionsFrom(paths, actions, menu, closeFunction);
162
    if (!masks.empty()) {
163
        if (!paths.empty())
164
            menu->addSeparator(); // Add separator between paths and file's ext filters
165
        appendActionsFrom(masks, actions, menu, closeFunction);
166 167
    }
    // Append 'Show Confirmation' toggle menu item
168
    menu->addSeparator(); // Add separator between paths and show confirmation
169 170 171 172 173
    menu->addAction(m_show_confirmation_action);
}

void CloseExceptPluginView::updateMenu()
{
174 175 176
    const QList<KTextEditor::Document *> &docs = KTextEditor::Editor::instance()->application()->documents();
    if (docs.size() < 2) {
        // qDebug() << "No docs r (or the only) opened right now --> disable menu";
177
        m_except_menu->setEnabled(false);
178
        m_except_menu->addSeparator();
179
        m_like_menu->setEnabled(false);
180
        m_like_menu->addSeparator();
181
        /// \note It seems there is always a document present... it named \em 'Untitled'
182
    } else {
183
        // Iterate over documents and form a set of candidates
184 185
        typedef std::set<QUrl> paths_set_type;
        typedef std::set<QString> paths_set_type_masks;
186
        paths_set_type doc_paths;
187
        paths_set_type_masks masks;
188 189
        Q_FOREACH (KTextEditor::Document *document, docs) {
            const QString &ext = QFileInfo(document->url().path()).completeSuffix();
190
            if (!ext.isEmpty())
191 192
                masks.insert(QStringLiteral("*.") + ext);
            doc_paths.insert(KIO::upUrl(document->url()));
193 194
        }
        paths_set_type paths = doc_paths;
195
        // qDebug() << "stage #1: Collected" << paths.size() << "paths and" << masks.size() << "masks";
196
        // Add common paths to the collection
197 198
        for (paths_set_type::iterator it = doc_paths.begin(), last = doc_paths.end(); it != last; ++it) {
            for (QUrl url = *it; (!url.path().isEmpty()) && url.path() != QLatin1String("/"); url = KIO::upUrl(url)) {
199 200
                paths_set_type::iterator not_it = it;
                for (++not_it; not_it != last; ++not_it)
201
                    if (!not_it->path().startsWith(url.path()))
202
                        break;
203
                if (not_it == last) {
204
                    paths.insert(url);
205 206 207 208
                    break;
                }
            }
        }
209
        // qDebug() << "stage #2: Collected" << paths.size() << "paths and" << masks.size() << "masks";
210
        //
211 212
        updateMenu(paths, masks, m_except_actions, m_except_menu, &CloseExceptPluginView::closeExcept);
        updateMenu(paths, masks, m_like_actions, m_like_menu, &CloseExceptPluginView::closeLike);
213 214 215
    }
}

216
void CloseExceptPluginView::close(const QString &item, const bool close_if_match)
217
{
218 219
    QChar asterisk = QLatin1Char('*');
    assert("Parameter seems invalid! Is smth has changed in the code?" && !item.isEmpty() && (item[0] == asterisk || item[item.size() - 1] == asterisk));
220

221
    const bool is_path = item[0] != asterisk;
222
    const QString mask = is_path ? item.left(item.size() - 1) : item;
223
    // qDebug() << "Going to close items [" << close_if_match << "/" << is_path << "]: " << mask;
224

225 226 227 228
    QList<KTextEditor::Document *> docs2close;
    const QList<KTextEditor::Document *> &docs = KTextEditor::Editor::instance()->application()->documents();
    Q_FOREACH (KTextEditor::Document *document, docs) {
        const QString &path = KIO::upUrl(document->url()).path();
229
        /// \note Take a dot in account, so \c *.c would not match for \c blah.kcfgc
230 231 232 233
        const QString &ext = QLatin1Char('.') + QFileInfo(document->url().fileName()).completeSuffix();
        const bool match = (!is_path && mask.endsWith(ext)) || (is_path && path.startsWith(mask));
        if (match == close_if_match) {
            // qDebug() << "*** Will close: " << document->url();
234 235 236
            docs2close.push_back(document);
        }
    }
237 238
    if (docs2close.isEmpty()) {
        displayMessage(i18nc("@title:window", "Error"), i18nc("@info:tooltip", "No files to close ..."), KTextEditor::Message::Error);
239 240 241
        return;
    }
    // Show confirmation dialog if needed
242 243 244 245 246
    const bool removeNeeded = !m_plugin->showConfirmationNeeded() || CloseConfirmDialog(docs2close, m_show_confirmation_action, qobject_cast<QWidget *>(this)).exec();
    if (removeNeeded) {
        if (docs2close.isEmpty()) {
            displayMessage(i18nc("@title:window", "Error"), i18nc("@info:tooltip", "No files to close ..."), KTextEditor::Message::Error);
        } else {
247
            // Close 'em all!
248
            KTextEditor::Editor::instance()->application()->closeDocuments(docs2close);
249
            updateMenu();
250
            displayMessage(i18nc("@title:window", "Done"), i18np("%1 file closed", "%1 files closed", docs2close.size()), KTextEditor::Message::Positive);
251 252 253
        }
    }
}
254 255 256 257

void CloseExceptPluginView::displayMessage(const QString &title, const QString &msg, KTextEditor::Message::MessageType level)
{
    KTextEditor::View *kv = m_mainWindow->activeView();
258 259
    if (!kv)
        return;
260 261 262 263 264 265 266 267 268 269 270

    delete m_infoMessage;
    m_infoMessage = new KTextEditor::Message(xi18nc("@info", "<title>%1</title><nl/>%2", title, msg), level);
    m_infoMessage->setWordWrap(true);
    m_infoMessage->setPosition(KTextEditor::Message::TopInView);
    m_infoMessage->setAutoHide(5000);
    m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate);
    m_infoMessage->setView(kv);
    kv->document()->postMessage(m_infoMessage);
}

271 272
// END CloseExceptPluginView
} // namespace kate
273

274 275
#include "close_except_plugin.moc"

276
// kate: hl C++11/Qt4;