1 /***************************************************************************
2 * Copyright (C) 2005-2007 by Tarek Saidi *
3 * tarek.saidi@arcor.de *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; version 2 of the License. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include <QHeaderView>
25 #include "lib/AutoType.h"
26 #include "lib/EntryView.h"
27 #include "dialogs/EditEntryDlg.h"
29 #define NUM_COLUMNS 11
31 // just for the lessThan funtion
32 /*QList<EntryViewItem*>* pItems;
33 KeepassEntryView* pEntryView;*/
35 KeepassEntryView::KeepassEntryView(QWidget* parent) : QTreeWidget(parent) {
37 AutoResizeColumns = true;
38 header()->setResizeMode(QHeaderView::Interactive);
39 header()->setStretchLastSection(false);
40 header()->setClickable(true);
41 header()->setCascadingSectionResizes(true);
42 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
46 connect(header(), SIGNAL(sectionResized(int,int,int)), SLOT(resizeColumns()));
47 connect(this,SIGNAL(itemSelectionChanged()), SLOT(OnItemsChanged()));
48 connect(&ClipboardTimer, SIGNAL(timeout()), SLOT(OnClipboardTimeOut()));
49 connect(this, SIGNAL(itemActivated(QTreeWidgetItem*,int)), SLOT(OnEntryActivated(QTreeWidgetItem*,int)));
50 connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), SLOT(OnEntryDblClicked(QTreeWidgetItem*,int)));
51 Clipboard=QApplication::clipboard();
52 ContextMenu=new QMenu(this);
53 setAlternatingRowColors(config->alternatingRowColors());
59 KeepassEntryView::~KeepassEntryView(){
63 void KeepassEntryView::retranslateColumns() {
64 setHeaderLabels( QStringList() << tr("Title") << tr("Username") << tr("URL") << tr("Password") << tr("Comments")
65 << tr("Expires") << tr("Creation") << tr("Last Change") << tr("Last Access") << tr("Attachment") << tr("Group") );
68 bool KeepassEntryView::columnVisible(int col) {
69 return !header()->isSectionHidden(col);
72 void KeepassEntryView::setColumnVisible(int col, bool visible) {
73 if (columnVisible(col) == visible)
74 return; // nothing to do
76 header()->setSectionHidden(col, !visible);
78 header()->resizeSection(col, columnSizes[col]);
81 void KeepassEntryView::saveHeaderView() {
82 QBitArray columns(NUM_COLUMNS);
83 QList<int> columnOrder;
84 int columnSort = header()->sortIndicatorSection();
85 Qt::SortOrder columnSortOrder = header()->sortIndicatorOrder();
87 for (int i=0; i<NUM_COLUMNS; ++i) {
88 columns.setBit(i, columnVisible(i));
89 columnOrder << header()->visualIndex(i);
92 if (ViewMode == Normal) {
93 config->setColumns(columns);
94 config->setColumnOrder(columnOrder);
95 config->setColumnSizes(columnSizes);
96 config->setColumnSort(columnSort);
97 config->setColumnSortOrder(columnSortOrder);
100 config->setSearchColumns(columns);
101 config->setSearchColumnOrder(columnOrder);
102 config->setSearchColumnSizes(columnSizes);
103 config->setSearchColumnSort(columnSort);
104 config->setSearchColumnSortOrder(columnSortOrder);
108 void KeepassEntryView::restoreHeaderView() {
109 AutoResizeColumns = false;
112 QList<int> columnOrder;
114 Qt::SortOrder columnSortOrder;
116 if (ViewMode == Normal) {
117 columns = config->columns();
118 columnOrder = config->columnOrder();
119 columnSizes = config->columnSizes();
120 columnSort = config->columnSort();
121 columnSortOrder = config->columnSortOrder();
122 columns[10] = 0; // just to be sure
125 columns = config->searchColumns();
126 columnOrder = config->searchColumnOrder();
127 columnSizes = config->searchColumnSizes();
128 columnSort = config->searchColumnSort();
129 columnSortOrder = config->searchColumnSortOrder();
132 // compatibility with KeePassX <= 0.4.0 (100 = column hidden)
133 int lastVisibleIndex = -1;
134 for (int i=0; i<NUM_COLUMNS; ++i) {
135 if (columnOrder[i]!=100 && columnOrder[i]>lastVisibleIndex)
136 lastVisibleIndex = columnOrder[i];
139 QMap<int,int> order; // key=visual index; value=logical index
140 for (int i=0; i<NUM_COLUMNS; ++i) {
141 if (columnOrder[i] == 100)
142 columnOrder[i] = ++lastVisibleIndex;
144 order.insert(columnOrder[i], i);
145 setColumnVisible(i, false); // initally hide all columns
146 if (columnSizes[i] < header()->minimumSectionSize())
147 columnSizes[i] = header()->minimumSectionSize();
150 for (QMap<int,int>::const_iterator i = order.constBegin(); i != order.constEnd(); ++i) {
151 header()->moveSection(header()->visualIndex(i.value()), NUM_COLUMNS-1);
152 header()->resizeSection(i.value(), columnSizes[i.value()]);
153 setColumnVisible(i.value(), columns.testBit(i.value()));
156 header()->setSortIndicator(columnSort, columnSortOrder);
158 AutoResizeColumns = true;
163 void KeepassEntryView::resizeColumns() {
164 if (!AutoResizeColumns)
167 AutoResizeColumns = false;
169 int w = viewport()->width();
172 for (int i=0; i<NUM_COLUMNS; ++i) {
173 if (columnVisible(i))
174 sum += header()->sectionSize(i);
177 double stretch = (double)w / (double)sum;
179 for (int i=0; i<NUM_COLUMNS; ++i) {
180 if (columnVisible(i) && header()->sectionSize(i)!=0) {
181 int size = qRound(header()->sectionSize(i) * stretch);
182 header()->resizeSection(i, size);
183 columnSizes[i] = size;
186 columnSizes[i] = qRound(columnSizes[i] * stretch);
190 AutoResizeColumns = true;
193 void KeepassEntryView::OnGroupChanged(IGroupHandle* group){
198 void KeepassEntryView::OnShowSearchResults(){
203 void KeepassEntryView::OnItemsChanged(){
204 switch(selectedItems().size()){
205 case 0: emit selectionChanged(NONE);
207 case 1: emit selectionChanged(SINGLE);
209 default:emit selectionChanged(MULTIPLE);
213 void KeepassEntryView::OnSaveAttachment(){
214 if (selectedItems().size() == 0) return;
215 CEditEntryDlg::saveAttachment(((EntryViewItem*)selectedItems().first())->EntryHandle,this);
218 void KeepassEntryView::OnCloneEntry(){
219 QList<QTreeWidgetItem*> entries=selectedItems();
220 for(int i=0; i<entries.size();i++){
221 Items.append(new EntryViewItem(this));
222 Items.back()->EntryHandle=
223 db->cloneEntry(((EntryViewItem*)entries[i])->EntryHandle);
224 updateEntry(Items.back());
226 if (header()->isSortIndicatorShown())
227 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
231 void KeepassEntryView::OnDeleteEntry(){
232 QList<QTreeWidgetItem*> entries=selectedItems();
234 if(config->askBeforeDelete()){
236 if(entries.size()==1)
237 text=tr("Are you sure you want to delete this entry?");
239 text=tr("Are you sure you want to delete these %1 entries?").arg(entries.size());
240 if(QMessageBox::question(this,tr("Delete?"),text,QMessageBox::Yes | QMessageBox::No,QMessageBox::No)==QMessageBox::No)
245 IGroupHandle* bGroup = NULL;
246 if (config->backup() && ((EntryViewItem*)entries[0])->EntryHandle->group() != (bGroup=db->backupGroup()))
248 if (backup && !bGroup) {
249 emit requestCreateGroup("Backup", 4, NULL);
250 bGroup = db->backupGroup();
252 for(int i=0; i<entries.size();i++){
253 IEntryHandle* entryHandle = ((EntryViewItem*)entries[i])->EntryHandle;
254 if (backup && bGroup){
255 db->moveEntry(entryHandle, bGroup);
256 QDateTime now = QDateTime::currentDateTime();
257 entryHandle->setLastAccess(now);
258 entryHandle->setLastMod(now);
261 db->deleteEntry(entryHandle);
263 Items.removeAt(Items.indexOf((EntryViewItem*)entries[i]));
269 QString KeepassEntryView::columnString(IEntryHandle* entry, int col, bool forceClearText) {
272 return entry->title();
274 if (config->hideUsernames() && !forceClearText)
277 return entry->username();
282 if (config->hidePasswords() && !forceClearText) {
286 SecString password = entry->password();
288 return password.string();
293 QString comment = entry->comment();
294 int toPos = comment.indexOf(QRegExp("[\\r\\n]"));
298 return comment.left(toPos);
301 return entry->expire().dateToString(Qt::SystemLocaleDate);
303 return entry->creation().dateToString(Qt::SystemLocaleDate);
305 return entry->lastMod().dateToString(Qt::SystemLocaleDate);
307 return entry->lastAccess().dateToString(Qt::SystemLocaleDate);
309 return entry->binaryDesc();
311 return entry->group()->title();
318 void KeepassEntryView::updateEntry(EntryViewItem* item){
319 IEntryHandle* entry = item->EntryHandle;
321 int cols = NUM_COLUMNS - 1;
322 if (ViewMode == ShowSearchResults) {
323 item->setIcon(10, db->icon(entry->group()->image()));
327 for (int i=0; i<cols; ++i) {
328 item->setText(i, columnString(entry, i));
330 item->setIcon(0, db->icon(entry->image()));
333 void KeepassEntryView::editEntry(EntryViewItem* item){
334 IEntryHandle* handle = item->EntryHandle;
335 CEntry old = handle->data();
337 CEditEntryDlg dlg(db,handle,this,true);
338 int result = dlg.exec();
340 case 0: //canceled or no changes
342 case 1: //modifications but same group
346 //entry moved to another group
348 case 3: //not modified
349 Items.removeAll(item);
355 IGroupHandle* bGroup;
356 if ((result==1 || result==2) && config->backup() && handle->group() != (bGroup=db->backupGroup())){
357 old.LastAccess = QDateTime::currentDateTime();
358 old.LastMod = old.LastAccess;
360 emit requestCreateGroup("Backup", 4, NULL);
361 if ((bGroup = db->backupGroup())!=NULL)
362 db->addEntry(&old, bGroup);
370 void KeepassEntryView::OnNewEntry(){
371 IEntryHandle* NewEntry = NULL;
372 if (!CurrentGroup){ // We must be viewing search results. Add the new entry to the first group.
373 if (db->groups().size() > 0)
374 NewEntry=db->newEntry(db->sortedGroups()[0]);
376 QMessageBox::critical(NULL,tr("Error"),tr("At least one group must exist before adding an entry."),tr("OK"));
380 NewEntry=db->newEntry(CurrentGroup);
381 CEditEntryDlg dlg(db,NewEntry,this,true);
383 db->deleteLastEntry();
386 Items.append(new EntryViewItem(this));
387 Items.back()->EntryHandle=NewEntry;
388 updateEntry(Items.back());
390 if (header()->isSortIndicatorShown())
391 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
396 void KeepassEntryView::OnEntryActivated(QTreeWidgetItem* item, int Column){
401 OnUsernameToClipboard();
407 OnPasswordToClipboard();
412 void KeepassEntryView::OnEntryDblClicked(QTreeWidgetItem* item, int Column){
414 editEntry((EntryViewItem*)item);
417 void KeepassEntryView::OnEditEntry(){
418 if (selectedItems().size() == 0) return;
419 editEntry((EntryViewItem*)selectedItems().first());
422 void KeepassEntryView::OnEditOpenUrl(){
423 if (selectedItems().size() == 0) return;
424 openBrowser( ((EntryViewItem*)selectedItems().first())->EntryHandle );
427 void KeepassEntryView::OnEditCopyUrl(){
428 if (selectedItems().size() == 0) return;
429 QString url = ((EntryViewItem*)selectedItems().first())->EntryHandle->url();
430 if (url.trimmed().isEmpty()) return;
431 if (url.startsWith("cmd://") && url.length()>6)
432 url = url.right(url.length()-6);
434 Clipboard->setText(url, QClipboard::Clipboard);
435 if(Clipboard->supportsSelection()){
436 Clipboard->setText(url, QClipboard::Selection);
440 void KeepassEntryView::OnUsernameToClipboard(){
441 if (selectedItems().size() == 0) return;
442 QString username = ((EntryViewItem*)selectedItems().first())->EntryHandle->username();
443 if (username.trimmed().isEmpty()) return;
444 Clipboard->setText(username, QClipboard::Clipboard);
445 if(Clipboard->supportsSelection()){
446 Clipboard->setText(username, QClipboard::Selection);
449 if (config->clipboardTimeOut()!=0) {
450 ClipboardTimer.setSingleShot(true);
451 ClipboardTimer.start(config->clipboardTimeOut()*1000);
455 void KeepassEntryView::OnPasswordToClipboard(){
456 if (selectedItems().size() == 0) return;
458 password=((EntryViewItem*)selectedItems().first())->EntryHandle->password();
460 if (password.string().isEmpty()) return;
461 Clipboard->setText(password.string(), QClipboard::Clipboard);
462 if(Clipboard->supportsSelection()){
463 Clipboard->setText(password.string(), QClipboard::Selection);
466 if (config->clipboardTimeOut()!=0) {
467 ClipboardTimer.setSingleShot(true);
468 ClipboardTimer.start(config->clipboardTimeOut()*1000);
472 void KeepassEntryView::OnClipboardTimeOut(){
473 Clipboard->clear(QClipboard::Clipboard);
474 if(Clipboard->supportsSelection()){
475 Clipboard->clear(QClipboard::Selection);
478 QProcess::startDetached("dcop klipper klipper clearClipboardHistory");
479 QProcess::startDetached("dbus-send --type=method_call --dest=org.kde.klipper /klipper "
480 "org.kde.klipper.klipper.clearClipboardHistory");
485 void KeepassEntryView::contextMenuEvent(QContextMenuEvent* e){
486 if(itemAt(e->pos())){
487 EntryViewItem* item=(EntryViewItem*)itemAt(e->pos());
488 if(!selectedItems().size()){
489 setItemSelected(item,true);
492 if(!isItemSelected(item)){
493 while(selectedItems().size()){
494 setItemSelected(selectedItems().first(),false);
496 setItemSelected(item,true);
501 while (selectedItems().size())
502 setItemSelected(selectedItems().first(),false);
505 ContextMenu->popup(e->globalPos());
508 void KeepassEntryView::resizeEvent(QResizeEvent* e){
510 QTreeWidget::resizeEvent(e);
514 void KeepassEntryView::showSearchResults(){
515 if(ViewMode == Normal){
517 ViewMode = ShowSearchResults;
519 emit viewModeChanged(true);
523 createItems(SearchResults);
527 void KeepassEntryView::showGroup(IGroupHandle* group){
528 if(ViewMode == ShowSearchResults){
532 emit viewModeChanged(false);
536 if(group==NULL)return;
537 QList<IEntryHandle*>entries=db->entries(group);
538 createItems(entries);
541 void KeepassEntryView::createItems(QList<IEntryHandle*>& entries){
542 for (int i=0; i<entries.size(); ++i) {
543 if (!entries[i]->isValid())
546 EntryViewItem* item = new EntryViewItem(this);
547 Items.push_back(item);
548 Items.back()->EntryHandle = entries[i];
554 void KeepassEntryView::updateIcons(){
555 for(int i=0;i<Items.size();i++){
556 Items[i]->setIcon(0,db->icon(Items[i]->EntryHandle->image()));
560 void KeepassEntryView::refreshItems(){
561 for (int i=0;i<Items.size();i++)
562 updateEntry(Items.at(i));
565 void KeepassEntryView::mousePressEvent(QMouseEvent *event){
566 //save event position - maybe this is the start of a drag
567 if (event->button() == Qt::LeftButton)
568 DragStartPos = event->pos();
569 QTreeWidget::mousePressEvent(event);
572 void KeepassEntryView::mouseMoveEvent(QMouseEvent *event){
573 if (!(event->buttons() & Qt::LeftButton))
575 if ((event->pos() - DragStartPos).manhattanLength() < QApplication::startDragDistance())
579 EntryViewItem* DragStartItem=(EntryViewItem*)itemAt(DragStartPos);
581 while(selectedItems().size()){
582 setItemSelected(selectedItems().first(),false);
586 if(selectedItems().isEmpty()){
587 setItemSelected(DragStartItem,true);
590 bool AlreadySelected=false;
591 for(int i=0;i<selectedItems().size();i++){
592 if(selectedItems()[i]==DragStartItem){
593 AlreadySelected=true;
597 if(!AlreadySelected){
598 while(selectedItems().size()){
599 setItemSelected(selectedItems().first(),false);
601 setItemSelected(DragStartItem,true);
605 DragItems=selectedItems();
606 QDrag *drag = new QDrag(this);
607 QMimeData *mimeData = new QMimeData;
608 void* pDragItems=&DragItems;
609 if (header()->logicalIndexAt(event->pos()) != -1) {
610 mimeData->setText(columnStringView(DragStartItem, header()->logicalIndexAt(event->pos()), true));
612 mimeData->setData("application/x-keepassx-entry",QByteArray((char*)&pDragItems,sizeof(void*)));
613 drag->setMimeData(mimeData);
614 EventOccurredBlock = true;
615 drag->exec(Qt::MoveAction);
616 EventOccurredBlock = false;
619 void KeepassEntryView::removeDragItems(){
620 for(int i=0;i<DragItems.size();i++){
621 for(int j=0;j<Items.size();j++){
622 if(Items[j]==DragItems[i]){
632 void KeepassEntryView::OnAutoType(){
633 if (selectedItems().size() == 0) return;
634 autoType->perform(((EntryViewItem*)selectedItems().first())->EntryHandle);
638 void KeepassEntryView::paintEvent(QPaintEvent * event){
639 QTreeWidget::paintEvent(event);
643 EntryViewItem::EntryViewItem(QTreeWidget *parent):QTreeWidgetItem(parent){
647 EntryViewItem::EntryViewItem(QTreeWidget *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){
651 EntryViewItem::EntryViewItem(QTreeWidgetItem *parent):QTreeWidgetItem(parent){
655 EntryViewItem::EntryViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){
660 bool EntryViewItem::operator<(const QTreeWidgetItem& other) const{
661 int SortCol = treeWidget()->header()->sortIndicatorSection();
662 int ListIndex = ((KeepassEntryView*)treeWidget())->header()->logicalIndex(SortCol);
664 int comp = compare(other, SortCol, ListIndex);
668 int visibleCols = treeWidget()->header()->count() - treeWidget()->header()->hiddenSectionCount();
669 int ListIndexOrg = ListIndex;
670 for (int i=0; i<visibleCols; i++){
671 SortCol = treeWidget()->header()->logicalIndex(i);
672 ListIndex = ((KeepassEntryView*)treeWidget())->header()->logicalIndex(SortCol);
673 if (ListIndex==ListIndexOrg || ListIndex==3) // sort or password column
676 comp = compare(other, SortCol, ListIndex);
680 return true; // entries are equal
684 int EntryViewItem::compare(const QTreeWidgetItem& other, int col, int index) const{
685 if (index < 5 || index > 8){ //columns with string values (Title, Username, Password, URL, Comment, Group)
686 return QString::localeAwareCompare(text(col),other.text(col));
689 KpxDateTime DateThis;
690 KpxDateTime DateOther;
694 DateThis=EntryHandle->expire();
695 DateOther=((EntryViewItem&)other).EntryHandle->expire();
698 DateThis=EntryHandle->creation();
699 DateOther=((EntryViewItem&)other).EntryHandle->creation();
702 DateThis=EntryHandle->lastMod();
703 DateOther=((EntryViewItem&)other).EntryHandle->lastMod();
706 DateThis=EntryHandle->lastAccess();
707 DateOther=((EntryViewItem&)other).EntryHandle->lastAccess();
713 if (DateThis==DateOther)
715 else if (DateThis < DateOther)
721 void KeepassEntryView::setCurrentEntry(IEntryHandle* entry){
724 for(i=0;i<Items.size();i++)
725 if(Items.at(i)->EntryHandle==entry){found=true; break;}
727 setCurrentItem(Items.at(i));