1 | /** |
2 | * \file |
3 | * |
4 | * \brief Kate Close Except/Like plugin implementation |
5 | * |
6 | * Copyright (C) 2012 Alex Turbov <i.zaufi@gmail.com> |
7 | * |
8 | * \date Thu Mar 8 08:13:43 MSK 2012 -- Initial design |
9 | */ |
10 | /* |
11 | * KateCloseExceptPlugin is free software: you can redistribute it and/or modify it |
12 | * under the terms of the GNU General Public License as published by the |
13 | * Free Software Foundation, either version 3 of the License, or |
14 | * (at your option) any later version. |
15 | * |
16 | * KateCloseExceptPlugin is distributed in the hope that it will be useful, but |
17 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
19 | * See the GNU General Public License for more details. |
20 | * |
21 | * You should have received a copy of the GNU General Public License along |
22 | * with this program. If not, see <http://www.gnu.org/licenses/>. |
23 | */ |
24 | |
25 | // Project specific includes |
26 | #include "config.h" |
27 | #include "close_except_plugin.h" |
28 | #include "close_confirm_dialog.h" |
29 | |
30 | // Standard includes |
31 | #include <kate/application.h> |
32 | #include <kate/documentmanager.h> |
33 | #include <kate/mainwindow.h> |
34 | #include <KAboutData> |
35 | #include <KActionCollection> |
36 | #include <KDebug> |
37 | #include <KPassivePopup> |
38 | #include <KPluginFactory> |
39 | #include <KPluginLoader> |
40 | #include <KTextEditor/Editor> |
41 | #include <QtCore/QFileInfo> |
42 | |
43 | K_PLUGIN_FACTORY(CloseExceptPluginFactory, registerPlugin<kate::CloseExceptPlugin>();) |
44 | K_EXPORT_PLUGIN( |
45 | CloseExceptPluginFactory( |
46 | KAboutData( |
47 | "katecloseexceptplugin" |
48 | , "katecloseexceptplugin" |
49 | , ki18n("Close Except/Like Plugin" ) |
50 | , PLUGIN_VERSION |
51 | , ki18n("Close all documents started from specified path" ) |
52 | , KAboutData::License_LGPL_V3 |
53 | ) |
54 | ) |
55 | ) |
56 | |
57 | namespace kate { |
58 | //BEGIN CloseExceptPlugin |
59 | CloseExceptPlugin::CloseExceptPlugin( |
60 | QObject* application |
61 | , const QList<QVariant>& |
62 | ) |
63 | : Kate::Plugin(static_cast<Kate::Application*>(application), "katecloseexceptplugin" ) |
64 | { |
65 | } |
66 | |
67 | Kate::PluginView* CloseExceptPlugin::createView(Kate::MainWindow* parent) |
68 | { |
69 | return new CloseExceptPluginView(parent, CloseExceptPluginFactory::componentData(), this); |
70 | } |
71 | |
72 | void CloseExceptPlugin::readSessionConfig(KConfigBase* config, const QString& groupPrefix) |
73 | { |
74 | KConfigGroup scg(config, groupPrefix + "menu" ); |
75 | m_show_confirmation_needed = scg.readEntry("ShowConfirmation" , true); |
76 | } |
77 | |
78 | void CloseExceptPlugin::writeSessionConfig(KConfigBase* config, const QString& groupPrefix) |
79 | { |
80 | KConfigGroup scg(config, groupPrefix + "menu" ); |
81 | scg.writeEntry("ShowConfirmation" , m_show_confirmation_needed); |
82 | scg.sync(); |
83 | } |
84 | //END CloseExceptPlugin |
85 | |
86 | //BEGIN CloseExceptPluginView |
87 | CloseExceptPluginView::CloseExceptPluginView( |
88 | Kate::MainWindow* mw |
89 | , const KComponentData& data |
90 | , CloseExceptPlugin* plugin |
91 | ) |
92 | : Kate::PluginView(mw) |
93 | , Kate::XMLGUIClient(data) |
94 | , m_plugin(plugin) |
95 | , m_show_confirmation_action(new KToggleAction(i18nc("@action:inmenu" , "Show Confirmation" ), this)) |
96 | , m_except_menu(new KActionMenu( |
97 | i18nc("@action:inmenu close docs except the following..." , "Close Except" ) |
98 | , this |
99 | )) |
100 | , m_like_menu(new KActionMenu( |
101 | i18nc("@action:inmenu close docs like the following..." , "Close Like" ) |
102 | , this |
103 | )) |
104 | { |
105 | actionCollection()->addAction("file_close_except" , m_except_menu); |
106 | actionCollection()->addAction("file_close_like" , m_like_menu); |
107 | |
108 | // Subscribe self to document creation |
109 | connect( |
110 | m_plugin->application()->editor() |
111 | , SIGNAL(documentCreated(KTextEditor::Editor*, KTextEditor::Document*)) |
112 | , this |
113 | , SLOT(documentCreated(KTextEditor::Editor*, KTextEditor::Document*)) |
114 | ); |
115 | // Configure toggle action and connect it to update state |
116 | m_show_confirmation_action->setChecked(m_plugin->showConfirmationNeeded()); |
117 | connect( |
118 | m_show_confirmation_action |
119 | , SIGNAL(toggled(bool)) |
120 | , m_plugin |
121 | , SLOT(toggleShowConfirmation(bool)) |
122 | ); |
123 | // |
124 | connect( |
125 | mainWindow() |
126 | , SIGNAL(viewCreated(KTextEditor::View*)) |
127 | , this |
128 | , SLOT(viewCreated(KTextEditor::View*)) |
129 | ); |
130 | // Fill menu w/ currently opened document masks/groups |
131 | updateMenu(); |
132 | |
133 | mainWindow()->guiFactory()->addClient(this); |
134 | } |
135 | |
136 | CloseExceptPluginView::~CloseExceptPluginView() |
137 | { |
138 | mainWindow()->guiFactory()->removeClient(this); |
139 | } |
140 | |
141 | void CloseExceptPluginView::viewCreated(KTextEditor::View* view) |
142 | { |
143 | connectToDocument(view->document()); |
144 | updateMenu(); |
145 | } |
146 | |
147 | void CloseExceptPluginView::documentCreated(KTextEditor::Editor*, KTextEditor::Document* document) |
148 | { |
149 | connectToDocument(document); |
150 | updateMenu(); |
151 | } |
152 | |
153 | void CloseExceptPluginView::connectToDocument(KTextEditor::Document* document) |
154 | { |
155 | // Subscribe self to document close and name changes |
156 | connect( |
157 | document |
158 | , SIGNAL(aboutToClose(KTextEditor::Document*)) |
159 | , this |
160 | , SLOT(updateMenuSlotStub(KTextEditor::Document*)) |
161 | ); |
162 | connect( |
163 | document |
164 | , SIGNAL(documentNameChanged(KTextEditor::Document*)) |
165 | , this |
166 | , SLOT(updateMenuSlotStub(KTextEditor::Document*)) |
167 | ); |
168 | connect( |
169 | document |
170 | , SIGNAL(documentUrlChanged(KTextEditor::Document*)) |
171 | , this |
172 | , SLOT(updateMenuSlotStub(KTextEditor::Document*)) |
173 | ); |
174 | } |
175 | |
176 | void CloseExceptPluginView::(KTextEditor::Document*) |
177 | { |
178 | updateMenu(); |
179 | } |
180 | |
181 | void CloseExceptPluginView::( |
182 | const std::set<QString>& paths |
183 | , actions_map_type& actions |
184 | , KActionMenu* |
185 | , QSignalMapper* mapper |
186 | ) |
187 | { |
188 | Q_FOREACH(const QString& path, paths) |
189 | { |
190 | QString action = path.startsWith('*') ? path : path + '*'; |
191 | actions[action] = QPointer<KAction>(new KAction(action, menu)); |
192 | menu->addAction(actions[action]); |
193 | connect(actions[action], SIGNAL(triggered()), mapper, SLOT(map())); |
194 | mapper->setMapping(actions[action], action); |
195 | } |
196 | } |
197 | |
198 | QPointer<QSignalMapper> CloseExceptPluginView::( |
199 | const std::set<QString>& paths |
200 | , const std::set<QString>& masks |
201 | , actions_map_type& actions |
202 | , KActionMenu* |
203 | ) |
204 | { |
205 | // turn menu ON or OFF depending on collected results |
206 | menu->setEnabled(!paths.empty()); |
207 | |
208 | // Clear previous menus |
209 | for (actions_map_type::iterator it = actions.begin(), last = actions.end(); it !=last;) |
210 | { |
211 | menu->removeAction(*it); |
212 | actions.erase(it++); |
213 | } |
214 | // Form a new one |
215 | QPointer<QSignalMapper> mapper = QPointer<QSignalMapper>(new QSignalMapper(this)); |
216 | appendActionsFrom(paths, actions, menu, mapper); |
217 | if (!masks.empty()) |
218 | { |
219 | if (!paths.empty()) |
220 | menu->addSeparator(); // Add separator between paths and file's ext filters |
221 | appendActionsFrom(masks, actions, menu, mapper); |
222 | } |
223 | // Append 'Show Confirmation' toggle menu item |
224 | menu->addSeparator(); // Add separator between paths and show confirmation |
225 | menu->addAction(m_show_confirmation_action); |
226 | return mapper; |
227 | } |
228 | |
229 | void CloseExceptPluginView::() |
230 | { |
231 | const QList<KTextEditor::Document*>& docs = m_plugin->application()->documentManager()->documents(); |
232 | if (docs.size() < 2) |
233 | { |
234 | kDebug() << "No docs r (or the only) opened right now --> disable menu" ; |
235 | m_except_menu->setEnabled(false); |
236 | m_except_menu->addSeparator(); |
237 | m_like_menu->setEnabled(false); |
238 | m_like_menu->addSeparator(); |
239 | /// \note It seems there is always a document present... it named \em 'Untitled' |
240 | } |
241 | else |
242 | { |
243 | // Iterate over documents and form a set of candidates |
244 | typedef std::set<QString> paths_set_type; |
245 | paths_set_type doc_paths; |
246 | paths_set_type masks; |
247 | Q_FOREACH(KTextEditor::Document* document, docs) |
248 | { |
249 | const QString& ext = QFileInfo(document->url().path()).completeSuffix(); |
250 | if (!ext.isEmpty()) |
251 | masks.insert("*." + ext); |
252 | doc_paths.insert(document->url().upUrl().path()); |
253 | } |
254 | paths_set_type paths = doc_paths; |
255 | kDebug() << "stage #1: Collected" << paths.size() << "paths and" << masks.size() << "masks" ; |
256 | // Add common paths to the collection |
257 | for (paths_set_type::iterator it = doc_paths.begin(), last = doc_paths.end(); it != last; ++it) |
258 | { |
259 | for ( |
260 | KUrl url = *it |
261 | ; url.hasPath() && url.path() != "/" |
262 | ; url = url.upUrl() |
263 | ) |
264 | { |
265 | paths_set_type::iterator not_it = it; |
266 | for (++not_it; not_it != last; ++not_it) |
267 | if (!not_it->startsWith(url.path())) |
268 | break; |
269 | if (not_it == last) |
270 | { |
271 | paths.insert(url.path()); |
272 | break; |
273 | } |
274 | } |
275 | } |
276 | kDebug() << "stage #2: Collected" << paths.size() << "paths and" << masks.size() << "masks" ; |
277 | // |
278 | m_except_mapper = updateMenu(paths, masks, m_except_actions, m_except_menu); |
279 | m_like_mapper = updateMenu(paths, masks, m_like_actions, m_like_menu); |
280 | connect(m_except_mapper, SIGNAL(mapped(const QString&)), this, SLOT(closeExcept(const QString&))); |
281 | connect(m_like_mapper, SIGNAL(mapped(const QString&)), this, SLOT(closeLike(const QString&))); |
282 | } |
283 | } |
284 | |
285 | void CloseExceptPluginView::close(const QString& item, const bool close_if_match) |
286 | { |
287 | assert( |
288 | "Parameter seems invalid! Is smth has changed in the code?" |
289 | && !item.isEmpty() && (item[0] == '*' || item[item.size() - 1] == '*') |
290 | ); |
291 | |
292 | const bool is_path = item[0] != '*'; |
293 | const QString mask = is_path ? item.left(item.size() - 1) : item; |
294 | kDebug() << "Going to close items [" << close_if_match << "/" << is_path << "]: " << mask; |
295 | |
296 | QList<KTextEditor::Document*> docs2close; |
297 | const QList<KTextEditor::Document*>& docs = m_plugin->application()->documentManager()->documents(); |
298 | Q_FOREACH(KTextEditor::Document* document, docs) |
299 | { |
300 | const QString& path = document->url().upUrl().path(); |
301 | /// \note Take a dot in account, so \c *.c would not match for \c blah.kcfgc |
302 | const QString& ext = '.' + QFileInfo(document->url().fileName()).completeSuffix(); |
303 | const bool match = (!is_path && mask.endsWith(ext)) |
304 | || (is_path && path.startsWith(mask)) |
305 | ; |
306 | if (match == close_if_match) |
307 | { |
308 | kDebug() << "*** Will close: " << document->url(); |
309 | docs2close.push_back(document); |
310 | } |
311 | } |
312 | if (docs2close.isEmpty()) |
313 | { |
314 | KPassivePopup::message( |
315 | i18nc("@title:window" , "Error" ) |
316 | , i18nc("@info:tooltip" , "No files to close ..." ) |
317 | , qobject_cast<QWidget*>(this) |
318 | ); |
319 | return; |
320 | } |
321 | // Show confirmation dialog if needed |
322 | const bool removeNeeded = !m_plugin->showConfirmationNeeded() |
323 | || CloseConfirmDialog(docs2close, m_show_confirmation_action, qobject_cast<QWidget*>(this)).exec(); |
324 | if (removeNeeded) |
325 | { |
326 | if (docs2close.isEmpty()) |
327 | { |
328 | KPassivePopup::message( |
329 | i18nc("@title:window" , "Error" ) |
330 | , i18nc("@info:tooltip" , "No files to close ..." ) |
331 | , qobject_cast<QWidget*>(this) |
332 | ); |
333 | } |
334 | else |
335 | { |
336 | // Close 'em all! |
337 | m_plugin->application()->documentManager()->closeDocumentList(docs2close); |
338 | updateMenu(); |
339 | KPassivePopup::message( |
340 | i18nc("@title:window" , "Done" ) |
341 | , i18np("%1 file closed" , "%1 files closed" , docs2close.size()) |
342 | , qobject_cast<QWidget*>(this) |
343 | ); |
344 | } |
345 | } |
346 | } |
347 | //END CloseExceptPluginView |
348 | } // namespace kate |
349 | |