/*************************************************************************** * Copyright (C) 2005-2007 by Tarek Saidi * * tarek.saidi@arcor.de * * * * This program 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; version 2 of the License. * * * * This program 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, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "lib/AutoType.h" #include "lib/EntryView.h" #include "dialogs/EditEntryDlg.h" // just for the lessThan funtion QList* pItems; KeepassEntryView* pEntryView; KeepassEntryView::KeepassEntryView(QWidget* parent):QTreeWidget(parent){ ViewMode=Normal; AutoResizeColumns=true; header()->setResizeMode(QHeaderView::Interactive); header()->setStretchLastSection(false); header()->setClickable(true); header()->setCascadingSectionResizes(true); ColumnSizes=config->columnSizes(); Columns=config->columns(); ColumnOrder=config->columnOrder(); updateColumns(); header()->setSortIndicator(config->columnSort(), config->columnSortOrder()); connect(header(),SIGNAL(sectionResized(int,int,int)),this,SLOT(OnColumnResized(int,int,int))); connect(this,SIGNAL(itemSelectionChanged()),this,SLOT(OnItemsChanged())); connect(&ClipboardTimer, SIGNAL(timeout()), this, SLOT(OnClipboardTimeOut())); connect(header(),SIGNAL(sectionClicked(int)),this,SLOT(OnHeaderSectionClicked(int))); connect(header(),SIGNAL(sectionMoved(int,int,int)),this,SLOT(OnColumnMoved(int,int,int))); Clipboard=QApplication::clipboard(); ContextMenu=new QMenu(this); setAlternatingRowColors(config->alternatingRowColors()); pItems=&Items; pEntryView=this; } KeepassEntryView::~KeepassEntryView(){ config->setColumnSizes(ColumnSizes); config->setColumns(Columns); config->setColumnOrder(ColumnOrder); config->setColumnSort(header()->sortIndicatorSection()); config->setColumnSortOrder(header()->sortIndicatorOrder()); } void KeepassEntryView::OnGroupChanged(IGroupHandle* group){ CurrentGroup=group; showGroup(group); } void KeepassEntryView::OnShowSearchResults(){ CurrentGroup=NULL; showSearchResults(); } void KeepassEntryView::OnItemsChanged(){ switch(selectedItems().size()){ case 0: emit selectionChanged(NONE); break; case 1: emit selectionChanged(SINGLE); break; default:emit selectionChanged(MULTIPLE); } } bool sortSearchResultsLessThan(const IEntryHandle* a, const IEntryHandle* b){ int indexA, indexB; for(indexA=0;indexAsize();indexA++){ if((*pItems)[indexA]->EntryHandle==a)break; } for(indexB=0;indexBsize();indexB++){ if((*pItems)[indexB]->EntryHandle==b)break; } return pEntryView->indexOfTopLevelItem((*pItems)[indexA])indexOfTopLevelItem((*pItems)[indexB]); } void KeepassEntryView::OnHeaderSectionClicked(int index){ if(header()->sortIndicatorSection()==index){ header()->setSortIndicator(index,header()->sortIndicatorOrder() ? Qt::DescendingOrder : Qt::AscendingOrder); } else{ header()->setSortIndicator(index,Qt::AscendingOrder); } if(ViewMode==Normal){ for(int i=0;iEntryHandle->setVisualIndexDirectly(indexOfTopLevelItem(Items[i])); } } else if(ViewMode==ShowSearchResults){ if(header()->sortIndicatorOrder()==Qt::AscendingOrder) qSort(SearchResults.begin(),SearchResults.end(),sortSearchResultsLessThan); else qSort(SearchResults.end(),SearchResults.begin(),sortSearchResultsLessThan); } } void KeepassEntryView::OnSaveAttachment(){ if (selectedItems().size() == 0) return; CEditEntryDlg::saveAttachment(((EntryViewItem*)selectedItems().first())->EntryHandle,this); } void KeepassEntryView::OnCloneEntry(){ QList entries=selectedItems(); for(int i=0; iEntryHandle= db->cloneEntry(((EntryViewItem*)entries[i])->EntryHandle); updateEntry(Items.back()); } emit fileModified(); } void KeepassEntryView::OnDeleteEntry(){ QList entries=selectedItems(); if(config->askBeforeDelete()){ QString text; if(entries.size()==1) text=tr("Are you sure you want to delete this entry?"); else text=tr("Are you sure you want to delete these %1 entries?").arg(entries.size()); if(QMessageBox::question(this,tr("Delete?"),text,QMessageBox::Yes | QMessageBox::No,QMessageBox::No)==QMessageBox::No) return; } bool backup = false; IGroupHandle* bGroup; if (config->backup() && ((EntryViewItem*)entries[0])->EntryHandle->group() != (bGroup=db->backupGroup())) backup = true; for(int i=0; iEntryHandle; if (backup){ db->moveEntry(entryHandle, bGroup); QDateTime now = QDateTime::currentDateTime(); entryHandle->setLastAccess(now); entryHandle->setLastMod(now); } else{ db->deleteEntry(entryHandle); } Items.removeAt(Items.indexOf((EntryViewItem*)entries[i])); delete entries[i]; } emit fileModified(); } void KeepassEntryView::updateEntry(EntryViewItem* item){ IEntryHandle* entry = item->EntryHandle; int j=0; if(Columns.at(0)){ item->setText(j++,entry->title()); item->setIcon(0,db->icon(entry->image())); } if (Columns.at(1)){ if(config->hideUsernames()) item->setText(j++,"******"); else item->setText(j++,entry->username()); } if (Columns.at(2)){item->setText(j++,entry->url());} if (Columns.at(3)){ if(config->hidePasswords()) item->setText(j++,"******"); else{ SecString password=entry->password(); password.unlock(); item->setText(j++,password.string()); } } if (Columns.at(4)){ item->setText(j++,entry->comment().section('\n',0,0));} if (Columns.at(5)){ item->setText(j++,entry->expire().dateToString(Qt::SystemLocaleDate));} if (Columns.at(6)){ item->setText(j++,entry->creation().dateToString(Qt::SystemLocaleDate));} if (Columns.at(7)){ item->setText(j++,entry->lastMod().dateToString(Qt::SystemLocaleDate));} if (Columns.at(8)){ item->setText(j++,entry->lastAccess().dateToString(Qt::SystemLocaleDate));} if (Columns.at(9)){ item->setText(j++,entry->binaryDesc());} if(Columns.at(10) && ViewMode==ShowSearchResults){ item->setText(j,entry->group()->title()); item->setIcon(j++,db->icon(entry->group()->image())); } } void KeepassEntryView::editEntry(EntryViewItem* item){ CEntry old = item->EntryHandle->data(); CEditEntryDlg dlg(db,item->EntryHandle,this,true); int result = dlg.exec(); switch(result){ case 0: //canceled or no changes break; case 1: //modifications but same group updateEntry(item); emit fileModified(); break; //entry moved to another group case 2: //modified case 3: //not modified delete item; Items.removeAt(Items.indexOf(item)); emit fileModified(); break; } IGroupHandle* bGroup; if ((result==1 || result==2) && config->backup() && item->EntryHandle->group() != (bGroup=db->backupGroup())){ old.LastAccess = QDateTime::currentDateTime(); old.LastMod = old.LastAccess; if (bGroup==NULL) emit requestCreateGroup("Backup", 4, NULL); if ((bGroup = db->backupGroup())!=NULL) db->addEntry(&old, bGroup); } } void KeepassEntryView::OnNewEntry(){ IEntryHandle* NewEntry = NULL; if (!CurrentGroup){// We must be viewing search results. Add the new entry to the first group. if (db->groups().size() > 0) NewEntry=db->newEntry(db->sortedGroups()[0]); else{ QMessageBox::critical(NULL,tr("Error"),tr("At least one group must exist before adding an entry."),tr("OK")); } } else NewEntry=db->newEntry(CurrentGroup); CEditEntryDlg dlg(db,NewEntry,this,true,true); if(!dlg.exec()){ db->deleteLastEntry(); } else{ Items.append(new EntryViewItem(this)); Items.back()->EntryHandle=NewEntry; updateEntry(Items.back()); emit fileModified(); if (header()->isSortIndicatorShown()) sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); } } void KeepassEntryView::OnEntryActivated(QTreeWidgetItem* item,int Column){ switch (columnListIndex(Column)){ case 0: editEntry((EntryViewItem*)item); break; case 1: OnUsernameToClipboard(); break; case 2: OnEditOpenUrl(); break; case 3: OnPasswordToClipboard(); break; } } void KeepassEntryView::OnEditEntry(){ if (selectedItems().size() == 0) return; editEntry((EntryViewItem*)selectedItems().first()); } void KeepassEntryView::OnEditOpenUrl(){ if (selectedItems().size() == 0) return; openBrowser( ((EntryViewItem*)selectedItems().first())->EntryHandle ); } void KeepassEntryView::OnEditCopyUrl(){ if (selectedItems().size() == 0) return; QString url = ((EntryViewItem*)selectedItems().first())->EntryHandle->url(); if (url.trimmed().isEmpty()) return; if (url.startsWith("cmd://") && url.length()>6) url = url.right(url.length()-6); Clipboard->setText(url, QClipboard::Clipboard); if(Clipboard->supportsSelection()){ Clipboard->setText(url, QClipboard::Selection); } } void KeepassEntryView::OnUsernameToClipboard(){ if (selectedItems().size() == 0) return; QString username = ((EntryViewItem*)selectedItems().first())->EntryHandle->username(); if (username.trimmed().isEmpty()) return; Clipboard->setText(username, QClipboard::Clipboard); if(Clipboard->supportsSelection()){ Clipboard->setText(username, QClipboard::Selection); } if (config->clipboardTimeOut()!=0) { ClipboardTimer.setSingleShot(true); ClipboardTimer.start(config->clipboardTimeOut()*1000); } } void KeepassEntryView::OnPasswordToClipboard(){ if (selectedItems().size() == 0) return; SecString password; password=((EntryViewItem*)selectedItems().first())->EntryHandle->password(); password.unlock(); if (password.string().isEmpty()) return; Clipboard->setText(password.string(), QClipboard::Clipboard); if(Clipboard->supportsSelection()){ Clipboard->setText(password.string(), QClipboard::Selection); } if (config->clipboardTimeOut()!=0) { ClipboardTimer.setSingleShot(true); ClipboardTimer.start(config->clipboardTimeOut()*1000); } } void KeepassEntryView::OnClipboardTimeOut(){ Clipboard->clear(QClipboard::Clipboard); if(Clipboard->supportsSelection()){ Clipboard->clear(QClipboard::Selection); } #ifdef Q_WS_X11 static bool clearKlipper3 = true; static bool clearKlipper4 = true; if (clearKlipper3){ if (QProcess::execute("dcop klipper klipper clearClipboardHistory")!=0) clearKlipper3 = false; } if (clearKlipper4){ if (QProcess::execute("dbus-send --type=method_call --print-reply --dest=org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory")!=0) clearKlipper4 = false; } #endif } void KeepassEntryView::contextMenuEvent(QContextMenuEvent* e){ if(itemAt(e->pos())){ EntryViewItem* item=(EntryViewItem*)itemAt(e->pos()); if(!selectedItems().size()){ setItemSelected(item,true); } else{ if(!isItemSelected(item)){ while(selectedItems().size()){ setItemSelected(selectedItems().first(),false); } setItemSelected(item,true); } } } else{ while (selectedItems().size()) setItemSelected(selectedItems().first(),false); } e->accept(); ContextMenu->popup(e->globalPos()); } void KeepassEntryView::resizeEvent(QResizeEvent* e){ resizeColumns(); e->accept(); } void KeepassEntryView::showSearchResults(){ if(ViewMode==Normal){ ViewMode=ShowSearchResults; if(Columns.at(10))ColumnOrder[10]--; updateColumns(); } clear(); Items.clear(); createItems(SearchResults); } void KeepassEntryView::showGroup(IGroupHandle* group){ if(ViewMode==ShowSearchResults){ ViewMode=Normal; updateColumns(); } clear(); Items.clear(); if(group==NULL)return; QListentries=db->entries(group); createItems(entries); } void KeepassEntryView::createItems(QList& entries){ for(int i=0;iisValid())continue; EntryViewItem* item=new EntryViewItem(this); Items.push_back(item); Items.back()->EntryHandle=entries[i]; int j=0; if (Columns.at(0)){ item->setText(j++,entries[i]->title()); item->setIcon(0,db->icon(entries[i]->image()));} if (Columns.at(1)){ if(config->hideUsernames()) item->setText(j++,"******"); else item->setText(j++,entries[i]->username());} if (Columns.at(2)){item->setText(j++,entries[i]->url());} if (Columns.at(3)){ if(config->hidePasswords()) item->setText(j++,"******"); else{ SecString password=entries[i]->password(); password.unlock(); item->setText(j++,password.string()); } } if (Columns.at(4)){ item->setText(j++,entries[i]->comment().section('\n',0,0));} if (Columns.at(5)){ item->setText(j++,entries[i]->expire().dateToString(Qt::SystemLocaleDate));} if (Columns.at(6)){ item->setText(j++,entries[i]->creation().dateToString(Qt::SystemLocaleDate));} if (Columns.at(7)){ item->setText(j++,entries[i]->lastMod().dateToString(Qt::SystemLocaleDate));} if (Columns.at(8)){ item->setText(j++,entries[i]->lastAccess().dateToString(Qt::SystemLocaleDate));} if (Columns.at(9)){ item->setText(j++,entries[i]->binaryDesc());} if(Columns.at(10) && ViewMode==ShowSearchResults){ item->setText(j,entries[i]->group()->title()); item->setIcon(j++,db->icon(entries[i]->group()->image()));} } } void KeepassEntryView::updateIcons(){ for(int i=0;isetIcon(0,db->icon(Items[i]->EntryHandle->image())); } } void KeepassEntryView::setEntry(IEntryHandle* entry){ } void KeepassEntryView::updateColumns(){ setColumnCount(0); QStringList cols; if (Columns.at(0)){ cols << tr("Title");} if (Columns.at(1)){ cols << tr("Username");} if (Columns.at(2)){ cols << tr("URL");} if (Columns.at(3)){ cols << tr("Password");} if (Columns.at(4)){ cols << tr("Comments");} if (Columns.at(5)){ cols << tr("Expires");} if (Columns.at(6)){ cols << tr("Creation");} if (Columns.at(7)){ cols << tr("Last Change");} if (Columns.at(8)){ cols << tr("Last Access");} if (Columns.at(9)){ cols << tr("Attachment");} if(Columns.at(10) && ViewMode==ShowSearchResults){ cols << tr("Group");} setHeaderLabels(cols); for(int i=0;i Order; for(int i=NUM_COLUMNS-1;i>=0;i--) Order.insertMulti(ColumnOrder.at(i),i); QMapIterator i(Order); while (i.hasNext()) { i.next(); int index=i.value(); if(!Columns.at(index))continue; header()->moveSection(header()->visualIndex(logicalColIndex(index)),header()->count()-1); } resizeColumns(); } void KeepassEntryView::refreshItems(){ for (int i=0;icount();i++){ ColumnOrder[columnListIndex(header()->logicalIndex(i))]=i; } sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); } int KeepassEntryView::logicalColIndex(int LstIndex){ int c=-1; for(int i=0;iwidth(); int sum=0; for(int i=0;icount();i++){ sum+=ColumnSizes.at(columnListIndex(i)); } float stretch=((float)w)/((float)sum); sum=0; for(int i=0;icount();i++){ int lstIndex=columnListIndex(header()->logicalIndex(i)); int NewSize=qRound(stretch*(float)ColumnSizes.at(lstIndex)); sum+=NewSize; if(i==header()->count()-1){ NewSize+=(w-sum); // add rounding difference to the last column } header()->resizeSection(header()->logicalIndex(i),NewSize); ColumnSizes[lstIndex]=NewSize; } AutoResizeColumns=true; } int KeepassEntryView::columnListIndex(int LogicalIndex){ int c=-1; int i; for(i=0;icount();i++){ ColumnSizes[columnListIndex(i)]=header()->sectionSize(i); } resizeColumns(); } void KeepassEntryView::mousePressEvent(QMouseEvent *event){ //save event position - maybe this is the start of a drag if (event->button() == Qt::LeftButton) DragStartPos = event->pos(); QTreeWidget::mousePressEvent(event); } void KeepassEntryView::mouseMoveEvent(QMouseEvent *event){ if (!(event->buttons() & Qt::LeftButton)) return; if ((event->pos() - DragStartPos).manhattanLength() < QApplication::startDragDistance()) return; DragItems.clear(); EntryViewItem* DragStartItem=(EntryViewItem*)itemAt(DragStartPos); if(!DragStartItem){ while(selectedItems().size()){ setItemSelected(selectedItems().first(),false); } return; } if(selectedItems().isEmpty()){ setItemSelected(DragStartItem,true); } else{ bool AlreadySelected=false; for(int i=0;isetData("text/plain;charset=UTF-8",DragItems.first()->text(0).toUtf8()); mimeData->setData("application/x-keepassx-entry",QByteArray((char*)&pDragItems,sizeof(void*))); drag->setMimeData(mimeData); EventOccurredBlock = true; drag->exec(Qt::MoveAction); EventOccurredBlock = false; } void KeepassEntryView::removeDragItems(){ for(int i=0;iperform(((EntryViewItem*)selectedItems().first())->EntryHandle); } #endif void KeepassEntryView::paintEvent(QPaintEvent * event){ QTreeWidget::paintEvent(event); } EntryViewItem::EntryViewItem(QTreeWidget *parent):QTreeWidgetItem(parent){ } EntryViewItem::EntryViewItem(QTreeWidget *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){ } EntryViewItem::EntryViewItem(QTreeWidgetItem *parent):QTreeWidgetItem(parent){ } EntryViewItem::EntryViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){ } bool EntryViewItem::operator<(const QTreeWidgetItem& other) const{ int SortCol = treeWidget()->header()->sortIndicatorSection(); int ListIndex = ((KeepassEntryView*)treeWidget())->columnListIndex(SortCol); int comp = compare(other, SortCol, ListIndex); if (comp!=0) return (comp<0); else { int visibleCols = treeWidget()->header()->count() - treeWidget()->header()->hiddenSectionCount(); int ListIndexOrg = ListIndex; for (int i=0; iheader()->logicalIndex(i); ListIndex = ((KeepassEntryView*)treeWidget())->columnListIndex(SortCol); if (ListIndex==ListIndexOrg || ListIndex==3) // sort or password column continue; comp = compare(other, SortCol, ListIndex); if (comp!=0) return (comp<0); } return true; // entries are equal } } int EntryViewItem::compare(const QTreeWidgetItem& other, int col, int index) const{ if (index < 5 || index > 8){ //columns with string values (Title, Username, Password, URL, Comment, Group) return QString::localeAwareCompare(text(col),other.text(col)); } KpxDateTime DateThis; KpxDateTime DateOther; switch (index){ case 5: DateThis=EntryHandle->expire(); DateOther=((EntryViewItem&)other).EntryHandle->expire(); break; case 6: DateThis=EntryHandle->creation(); DateOther=((EntryViewItem&)other).EntryHandle->creation(); break; case 7: DateThis=EntryHandle->lastMod(); DateOther=((EntryViewItem&)other).EntryHandle->lastMod(); break; case 8: DateThis=EntryHandle->lastAccess(); DateOther=((EntryViewItem&)other).EntryHandle->lastAccess(); break; default: Q_ASSERT(false); } if (DateThis==DateOther) return 0; else if (DateThis < DateOther) return -1; else return 1; } void KeepassEntryView::setCurrentEntry(IEntryHandle* entry){ bool found=false; int i; for(i=0;iEntryHandle==entry){found=true; break;} if(!found)return; setCurrentItem(Items.at(i)); }