1/*
2 Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301 USA.
18*/
19
20// Own
21#include "ManageProfilesDialog.h"
22
23// Qt
24#include <QtCore/QFileInfo>
25#include <QStandardItem>
26
27// KDE
28#include <KKeySequenceWidget>
29#include <KStandardDirs>
30#include <KDebug>
31
32// Konsole
33#include "EditProfileDialog.h"
34#include "ProfileManager.h"
35#include "Session.h"
36#include "TerminalDisplay.h"
37#include "SessionManager.h"
38#include "SessionController.h"
39#include "ui_ManageProfilesDialog.h"
40
41using namespace Konsole;
42
43ManageProfilesDialog::ManageProfilesDialog(QWidget* aParent)
44 : KDialog(aParent)
45 , _sessionModel(new QStandardItemModel(this))
46{
47 setCaption(i18nc("@title:window", "Manage Profiles"));
48 setButtons(KDialog::Close);
49
50 connect(this, SIGNAL(finished()),
51 ProfileManager::instance(), SLOT(saveSettings()));
52
53 _ui = new Ui::ManageProfilesDialog();
54 _ui->setupUi(mainWidget());
55
56 // hide vertical header
57 _ui->sessionTable->verticalHeader()->hide();
58 _ui->sessionTable->setShowGrid(false);
59
60 _ui->sessionTable->setItemDelegateForColumn(FavoriteStatusColumn, new FavoriteItemDelegate(this));
61 _ui->sessionTable->setItemDelegateForColumn(ShortcutColumn, new ShortcutItemDelegate(this));
62 _ui->sessionTable->setEditTriggers(_ui->sessionTable->editTriggers() | QAbstractItemView::SelectedClicked);
63
64 // populate the table with profiles
65 populateTable();
66
67 // listen for changes to profiles
68 connect(ProfileManager::instance(), SIGNAL(profileAdded(Profile::Ptr)), this,
69 SLOT(addItems(Profile::Ptr)));
70 connect(ProfileManager::instance(), SIGNAL(profileRemoved(Profile::Ptr)), this,
71 SLOT(removeItems(Profile::Ptr)));
72 connect(ProfileManager::instance(), SIGNAL(profileChanged(Profile::Ptr)), this,
73 SLOT(updateItems(Profile::Ptr)));
74 connect(ProfileManager::instance() ,
75 SIGNAL(favoriteStatusChanged(Profile::Ptr,bool)), this,
76 SLOT(updateFavoriteStatus(Profile::Ptr,bool)));
77
78 // resize the session table to the full width of the table
79 _ui->sessionTable->horizontalHeader()->setHighlightSections(false);
80 _ui->sessionTable->resizeColumnsToContents();
81
82 // allow a larger width for the shortcut column to account for the
83 // increased with needed by the shortcut editor compared with just
84 // displaying the text of the shortcut
85 _ui->sessionTable->setColumnWidth(ShortcutColumn,
86 _ui->sessionTable->columnWidth(ShortcutColumn) + 100);
87
88 // setup buttons
89 connect(_ui->newProfileButton, SIGNAL(clicked()), this, SLOT(createProfile()));
90 connect(_ui->editProfileButton, SIGNAL(clicked()), this, SLOT(editSelected()));
91 connect(_ui->deleteProfileButton, SIGNAL(clicked()), this, SLOT(deleteSelected()));
92 connect(_ui->setAsDefaultButton, SIGNAL(clicked()), this, SLOT(setSelectedAsDefault()));
93}
94
95void ManageProfilesDialog::showEvent(QShowEvent*)
96{
97 Q_ASSERT(_ui->sessionTable->model());
98
99 // try to ensure that all the text in all the columns is visible initially.
100 // FIXME: this is not a good solution, look for a more correct way to do this
101
102 int totalWidth = 0;
103 const int columnCount = _ui->sessionTable->model()->columnCount();
104
105 for (int i = 0 ; i < columnCount ; i++)
106 totalWidth += _ui->sessionTable->columnWidth(i);
107
108 // the margin is added to account for the space taken by the resize grips
109 // between the columns, this ensures that a horizontal scroll bar is not added
110 // automatically
111 int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin) * columnCount;
112 _ui->sessionTable->setMinimumWidth(totalWidth + margin);
113 _ui->sessionTable->horizontalHeader()->setStretchLastSection(true);
114}
115
116ManageProfilesDialog::~ManageProfilesDialog()
117{
118 delete _ui;
119}
120
121void ManageProfilesDialog::itemDataChanged(QStandardItem* item)
122{
123 if (item->column() == ShortcutColumn) {
124 QKeySequence sequence = QKeySequence::fromString(item->text());
125 ProfileManager::instance()->setShortcut(item->data(ShortcutRole).value<Profile::Ptr>(),
126 sequence);
127 } else if (item->column() == ProfileNameColumn) {
128 QString newName = item->text();
129 Profile::Ptr profile = item->data(ProfileKeyRole).value<Profile::Ptr>();
130 QString oldName = profile->name();
131
132 if (newName != oldName) {
133 QHash<Profile::Property, QVariant> properties;
134 properties.insert(Profile::Name, newName);
135 properties.insert(Profile::UntranslatedName, newName);
136
137 ProfileManager::instance()->changeProfile(profile, properties);
138 }
139 }
140}
141
142int ManageProfilesDialog::rowForProfile(const Profile::Ptr profile) const
143{
144 const int rowCount = _sessionModel->rowCount();
145 for (int i = 0; i < rowCount; i++) {
146 if (_sessionModel->item(i, ProfileNameColumn)->data(ProfileKeyRole)
147 .value<Profile::Ptr>() == profile) {
148 return i;
149 }
150 }
151 return -1;
152}
153void ManageProfilesDialog::removeItems(const Profile::Ptr profile)
154{
155 int row = rowForProfile(profile);
156 if (row < 0)
157 return;
158
159 _sessionModel->removeRow(row);
160}
161void ManageProfilesDialog::updateItems(const Profile::Ptr profile)
162{
163 const int row = rowForProfile(profile);
164 if (row < 0)
165 return;
166
167 QList<QStandardItem*> items;
168 items << _sessionModel->item(row, ProfileNameColumn);
169 items << _sessionModel->item(row, FavoriteStatusColumn);
170 items << _sessionModel->item(row, ShortcutColumn);
171
172 updateItemsForProfile(profile, items);
173}
174void ManageProfilesDialog::updateItemsForProfile(const Profile::Ptr profile, QList<QStandardItem*>& items) const
175{
176 // Profile Name
177 items[ProfileNameColumn]->setText(profile->name());
178 if (!profile->icon().isEmpty())
179 items[ProfileNameColumn]->setIcon(KIcon(profile->icon()));
180 items[ProfileNameColumn]->setData(QVariant::fromValue(profile), ProfileKeyRole);
181 items[ProfileNameColumn]->setToolTip(i18nc("@info:tooltip", "Click to rename profile"));
182
183 // Favorite Status
184 const bool isFavorite = ProfileManager::instance()->findFavorites().contains(profile);
185 if (isFavorite)
186 items[FavoriteStatusColumn]->setData(KIcon("dialog-ok-apply"), Qt::DecorationRole);
187 else
188 items[FavoriteStatusColumn]->setData(KIcon(), Qt::DecorationRole);
189 items[FavoriteStatusColumn]->setData(QVariant::fromValue(profile), ProfileKeyRole);
190 items[FavoriteStatusColumn]->setToolTip(i18nc("@info:tooltip", "Click to toggle status"));
191
192 // Shortcut
193 QString shortcut = ProfileManager::instance()->shortcut(profile).toString();
194 items[ShortcutColumn]->setText(shortcut);
195 items[ShortcutColumn]->setData(QVariant::fromValue(profile), ShortcutRole);
196 items[ShortcutColumn]->setToolTip(i18nc("@info:tooltip", "Double click to change shortcut"));
197}
198void ManageProfilesDialog::addItems(const Profile::Ptr profile)
199{
200 if (profile->isHidden())
201 return;
202
203 QList<QStandardItem*> items;
204 for (int i = 0; i < 3; i++)
205 items << new QStandardItem;
206
207 updateItemsForProfile(profile, items);
208 _sessionModel->appendRow(items);
209}
210void ManageProfilesDialog::populateTable()
211{
212 Q_ASSERT(!_ui->sessionTable->model());
213
214 _ui->sessionTable->setModel(_sessionModel);
215
216 _sessionModel->clear();
217 // setup session table
218 _sessionModel->setHorizontalHeaderLabels(QStringList() << i18nc("@title:column Profile label", "Name")
219 << i18nc("@title:column Display profile in file menu", "Show in Menu")
220 << i18nc("@title:column Profile shortcut text", "Shortcut"));
221
222 QList<Profile::Ptr> profiles = ProfileManager::instance()->allProfiles();
223 ProfileManager::instance()->sortProfiles(profiles);
224
225 foreach(const Profile::Ptr& profile, profiles) {
226 addItems(profile);
227 }
228 updateDefaultItem();
229
230 connect(_sessionModel, SIGNAL(itemChanged(QStandardItem*)), this,
231 SLOT(itemDataChanged(QStandardItem*)));
232
233 // listen for changes in the table selection and update the state of the form's buttons
234 // accordingly.
235 //
236 // it appears that the selection model is changed when the model itself is replaced,
237 // so the signals need to be reconnected each time the model is updated.
238 connect(_ui->sessionTable->selectionModel(),
239 SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this,
240 SLOT(tableSelectionChanged(QItemSelection)));
241
242 _ui->sessionTable->selectRow(0);
243}
244void ManageProfilesDialog::updateDefaultItem()
245{
246 Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile();
247
248 const int rowCount = _sessionModel->rowCount();
249 for (int i = 0; i < rowCount; i++) {
250 QStandardItem* item = _sessionModel->item(i);
251 QFont itemFont = item->font();
252
253 bool isDefault = (defaultProfile == item->data().value<Profile::Ptr>());
254
255 if (isDefault && !itemFont.bold()) {
256 item->setIcon(KIcon(defaultProfile->icon(), 0, QStringList("emblem-favorite")));
257 itemFont.setBold(true);
258 item->setFont(itemFont);
259 } else if (!isDefault && itemFont.bold()) {
260 item->setIcon(KIcon(defaultProfile->icon()));
261 itemFont.setBold(false);
262 item->setFont(itemFont);
263 }
264 }
265}
266void ManageProfilesDialog::tableSelectionChanged(const QItemSelection&)
267{
268 const int selectedRows = _ui->sessionTable->selectionModel()->selectedRows().count();
269 const ProfileManager* manager = ProfileManager::instance();
270 const bool isNotDefault = (selectedRows > 0) && currentProfile() != manager->defaultProfile();
271 const bool isDeletable = (selectedRows > 1) ||
272 (selectedRows == 1 && isProfileDeletable(currentProfile()));
273
274 _ui->newProfileButton->setEnabled(selectedRows < 2);
275 // FIXME: At some point editing 2+ profiles no longer works
276 _ui->editProfileButton->setEnabled(selectedRows == 1);
277 // do not allow the default session type to be removed
278 _ui->deleteProfileButton->setEnabled(isDeletable && isNotDefault);
279 _ui->setAsDefaultButton->setEnabled(isNotDefault && (selectedRows < 2));
280}
281void ManageProfilesDialog::deleteSelected()
282{
283 foreach(const Profile::Ptr & profile, selectedProfiles()) {
284 if (profile != ProfileManager::instance()->defaultProfile())
285 ProfileManager::instance()->deleteProfile(profile);
286 }
287}
288void ManageProfilesDialog::setSelectedAsDefault()
289{
290 ProfileManager::instance()->setDefaultProfile(currentProfile());
291 // do not allow the new default session type to be removed
292 _ui->deleteProfileButton->setEnabled(false);
293 _ui->setAsDefaultButton->setEnabled(false);
294
295 // update font of new default item
296 updateDefaultItem();
297}
298
299void ManageProfilesDialog::moveUpSelected()
300{
301 Q_ASSERT(_sessionModel);
302
303 const int rowIndex = _ui->sessionTable->currentIndex().row();
304 const QList<QStandardItem*>items = _sessionModel->takeRow(rowIndex);
305 _sessionModel->insertRow(rowIndex - 1, items);
306 _ui->sessionTable->selectRow(rowIndex - 1);
307}
308
309void ManageProfilesDialog::moveDownSelected()
310{
311 Q_ASSERT(_sessionModel);
312
313 const int rowIndex = _ui->sessionTable->currentIndex().row();
314 const QList<QStandardItem*>items = _sessionModel->takeRow(rowIndex);
315 _sessionModel->insertRow(rowIndex + 1, items);
316 _ui->sessionTable->selectRow(rowIndex + 1);
317}
318
319void ManageProfilesDialog::createProfile()
320{
321 // setup a temporary profile which is a clone of the selected profile
322 // or the default if no profile is selected
323 Profile::Ptr sourceProfile;
324
325 Profile::Ptr selectedProfile = currentProfile();
326 if (!selectedProfile)
327 sourceProfile = ProfileManager::instance()->defaultProfile();
328 else
329 sourceProfile = selectedProfile;
330
331 Q_ASSERT(sourceProfile);
332
333 Profile::Ptr newProfile = Profile::Ptr(new Profile(ProfileManager::instance()->fallbackProfile()));
334 newProfile->clone(sourceProfile, true);
335 newProfile->setProperty(Profile::Name, i18nc("@item This will be used as part of the file name", "New Profile"));
336 newProfile->setProperty(Profile::UntranslatedName, "New Profile");
337 newProfile->setProperty(Profile::MenuIndex, QString("0"));
338
339 QWeakPointer<EditProfileDialog> dialog = new EditProfileDialog(this);
340 dialog.data()->setProfile(newProfile);
341 dialog.data()->selectProfileName();
342
343 if (dialog.data()->exec() == QDialog::Accepted) {
344 ProfileManager::instance()->addProfile(newProfile);
345 ProfileManager::instance()->setFavorite(newProfile, true);
346 ProfileManager::instance()->changeProfile(newProfile, newProfile->setProperties());
347 }
348 delete dialog.data();
349}
350void ManageProfilesDialog::editSelected()
351{
352 QList<Profile::Ptr> profiles(selectedProfiles());
353
354 foreach (Session* session, SessionManager::instance()->sessions()) {
355 foreach (TerminalDisplay* terminal, session->views()) {
356 // Searching for opened profiles
357 if (terminal->sessionController()->profileDialogPointer() != NULL) {
358 foreach (const Profile::Ptr & profile, profiles) {
359 if (profile->name() == terminal->sessionController()->profileDialogPointer()->lookupProfile()->name()
360 && terminal->sessionController()->profileDialogPointer()->isVisible()) {
361 // close opened edit dialog
362 terminal->sessionController()->profileDialogPointer()->close();
363 }
364 }
365 }
366 }
367 }
368
369 EditProfileDialog dialog(this);
370 // the dialog will delete the profile group when it is destroyed
371 ProfileGroup* group = new ProfileGroup;
372 foreach (const Profile::Ptr & profile, profiles) {
373 group->addProfile(profile);
374 }
375 group->updateValues();
376
377 dialog.setProfile(Profile::Ptr(group));
378 dialog.exec();
379}
380QList<Profile::Ptr> ManageProfilesDialog::selectedProfiles() const
381{
382 QList<Profile::Ptr> list;
383 QItemSelectionModel* selection = _ui->sessionTable->selectionModel();
384 if (!selection)
385 return list;
386
387 foreach(const QModelIndex & index, selection->selectedIndexes()) {
388 if (index.column() == ProfileNameColumn)
389 list << index.data(ProfileKeyRole).value<Profile::Ptr>();
390 }
391
392 return list;
393}
394Profile::Ptr ManageProfilesDialog::currentProfile() const
395{
396 QItemSelectionModel* selection = _ui->sessionTable->selectionModel();
397
398 if (!selection || selection->selectedRows().count() != 1)
399 return Profile::Ptr();
400
401 return selection->
402 selectedIndexes().first().data(ProfileKeyRole).value<Profile::Ptr>();
403}
404bool ManageProfilesDialog::isProfileDeletable(Profile::Ptr profile) const
405{
406 static const QString systemDataLocation = KStandardDirs::installPath("data") + "konsole/";
407
408 if (profile) {
409 QFileInfo fileInfo(profile->path());
410
411 if (fileInfo.exists()) {
412 // never remove a system wide profile, no matter whether the
413 // current user has enough permission
414 if (profile->path().startsWith(systemDataLocation)) {
415 return false;
416 }
417
418 // check whether user has enough permission
419 QFileInfo dirInfo(fileInfo.path());
420 return dirInfo.isWritable();
421 } else {
422 return true;
423 }
424 } else {
425 return true;
426 }
427}
428void ManageProfilesDialog::updateFavoriteStatus(Profile::Ptr profile, bool favorite)
429{
430 Q_ASSERT(_sessionModel);
431
432 const int rowCount = _sessionModel->rowCount();
433 for (int i = 0; i < rowCount; i++) {
434 QModelIndex index = _sessionModel->index(i, FavoriteStatusColumn);
435 if (index.data(ProfileKeyRole).value<Profile::Ptr>() == profile) {
436 const KIcon icon = favorite ? KIcon("dialog-ok-apply") : KIcon();
437 _sessionModel->setData(index, icon, Qt::DecorationRole);
438 }
439 }
440}
441void ManageProfilesDialog::setShortcutEditorVisible(bool visible)
442{
443 _ui->sessionTable->setColumnHidden(ShortcutColumn, !visible);
444}
445void StyledBackgroundPainter::drawBackground(QPainter* painter, const QStyleOptionViewItem& option,
446 const QModelIndex&)
447{
448 const QStyleOptionViewItemV3* v3option = qstyleoption_cast<const QStyleOptionViewItemV3*>(&option);
449 const QWidget* widget = v3option ? v3option->widget : 0;
450
451 QStyle* style = widget ? widget->style() : QApplication::style();
452
453 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, widget);
454}
455
456FavoriteItemDelegate::FavoriteItemDelegate(QObject* aParent)
457 : QStyledItemDelegate(aParent)
458{
459}
460void FavoriteItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
461{
462 // See implementation of QStyledItemDelegate::paint()
463 QStyleOptionViewItemV4 opt = option;
464 initStyleOption(&opt, index);
465
466 StyledBackgroundPainter::drawBackground(painter, opt, index);
467
468 int margin = (opt.rect.height() - opt.decorationSize.height()) / 2;
469 margin++;
470
471 opt.rect.setTop(opt.rect.top() + margin);
472 opt.rect.setBottom(opt.rect.bottom() - margin);
473
474 QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
475 icon.paint(painter, opt.rect, Qt::AlignCenter);
476}
477
478bool FavoriteItemDelegate::editorEvent(QEvent* aEvent, QAbstractItemModel*,
479 const QStyleOptionViewItem&, const QModelIndex& index)
480{
481 if (aEvent->type() == QEvent::MouseButtonPress ||
482 aEvent->type() == QEvent::KeyPress ||
483 aEvent->type() == QEvent::MouseButtonDblClick) {
484 Profile::Ptr profile = index.data(ManageProfilesDialog::ProfileKeyRole).value<Profile::Ptr>();
485 const bool isFavorite = ProfileManager::instance()->findFavorites().contains(profile);
486
487 ProfileManager::instance()->setFavorite(profile, !isFavorite);
488 }
489
490 return true;
491}
492ShortcutItemDelegate::ShortcutItemDelegate(QObject* aParent)
493 : QStyledItemDelegate(aParent)
494{
495}
496void ShortcutItemDelegate::editorModified(const QKeySequence& keys)
497{
498 Q_UNUSED(keys);
499 //kDebug() << keys.toString();
500
501 KKeySequenceWidget* editor = qobject_cast<KKeySequenceWidget*>(sender());
502 Q_ASSERT(editor);
503 _modifiedEditors.insert(editor);
504 emit commitData(editor);
505 emit closeEditor(editor);
506}
507void ShortcutItemDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
508 const QModelIndex& index) const
509{
510 _itemsBeingEdited.remove(index);
511
512 if (!_modifiedEditors.contains(editor))
513 return;
514
515 QString shortcut = qobject_cast<KKeySequenceWidget*>(editor)->keySequence().toString();
516 model->setData(index, shortcut, Qt::DisplayRole);
517
518 _modifiedEditors.remove(editor);
519}
520
521QWidget* ShortcutItemDelegate::createEditor(QWidget* aParent, const QStyleOptionViewItem&, const QModelIndex& index) const
522{
523 _itemsBeingEdited.insert(index);
524
525 KKeySequenceWidget* editor = new KKeySequenceWidget(aParent);
526 editor->setFocusPolicy(Qt::StrongFocus);
527 editor->setModifierlessAllowed(false);
528 QString shortcutString = index.data(Qt::DisplayRole).toString();
529 editor->setKeySequence(QKeySequence::fromString(shortcutString));
530 connect(editor, SIGNAL(keySequenceChanged(QKeySequence)), this, SLOT(editorModified(QKeySequence)));
531 editor->captureKeySequence();
532 return editor;
533}
534void ShortcutItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
535 const QModelIndex& index) const
536{
537 if (_itemsBeingEdited.contains(index))
538 StyledBackgroundPainter::drawBackground(painter, option, index);
539 else
540 QStyledItemDelegate::paint(painter, option, index);
541}
542
543#include "ManageProfilesDialog.moc"
544