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(){
61 if (ClipboardTimer.isActive()) {
62 ClipboardTimer.stop();
67 void KeepassEntryView::retranslateColumns() {
68 setHeaderLabels( QStringList() << tr("Title") << tr("Username") << tr("URL") << tr("Password") << tr("Comments")
69 << tr("Expires") << tr("Creation") << tr("Last Change") << tr("Last Access") << tr("Attachment") << tr("Group") );
72 bool KeepassEntryView::columnVisible(int col) {
73 return !header()->isSectionHidden(col);
76 void KeepassEntryView::setColumnVisible(int col, bool visible) {
77 if (columnVisible(col) == visible)
78 return; // nothing to do
80 header()->setSectionHidden(col, !visible);
82 header()->resizeSection(col, columnSizes[col]);
85 void KeepassEntryView::saveHeaderView() {
86 QBitArray columns(NUM_COLUMNS);
87 QList<int> columnOrder;
88 int columnSort = header()->sortIndicatorSection();
89 Qt::SortOrder columnSortOrder = header()->sortIndicatorOrder();
91 for (int i=0; i<NUM_COLUMNS; ++i) {
92 columns.setBit(i, columnVisible(i));
93 columnOrder << header()->visualIndex(i);
96 if (ViewMode == Normal) {
97 config->setColumns(columns);
98 config->setColumnOrder(columnOrder);
99 config->setColumnSizes(columnSizes);
100 config->setColumnSort(columnSort);
101 config->setColumnSortOrder(columnSortOrder);
104 config->setSearchColumns(columns);
105 config->setSearchColumnOrder(columnOrder);
106 config->setSearchColumnSizes(columnSizes);
107 config->setSearchColumnSort(columnSort);
108 config->setSearchColumnSortOrder(columnSortOrder);
112 void KeepassEntryView::restoreHeaderView() {
113 AutoResizeColumns = false;
116 QList<int> columnOrder;
118 Qt::SortOrder columnSortOrder;
120 if (ViewMode == Normal) {
121 columns = config->columns();
122 columnOrder = config->columnOrder();
123 columnSizes = config->columnSizes();
124 columnSort = config->columnSort();
125 columnSortOrder = config->columnSortOrder();
126 columns[10] = 0; // just to be sure
129 columns = config->searchColumns();
130 columnOrder = config->searchColumnOrder();
131 columnSizes = config->searchColumnSizes();
132 columnSort = config->searchColumnSort();
133 columnSortOrder = config->searchColumnSortOrder();
136 // compatibility with KeePassX <= 0.4.0 (100 = column hidden)
137 int lastVisibleIndex = -1;
138 for (int i=0; i<NUM_COLUMNS; ++i) {
139 if (columnOrder[i]!=100 && columnOrder[i]>lastVisibleIndex)
140 lastVisibleIndex = columnOrder[i];
143 QMap<int,int> order; // key=visual index; value=logical index
144 for (int i=0; i<NUM_COLUMNS; ++i) {
145 if (columnOrder[i] == 100)
146 columnOrder[i] = ++lastVisibleIndex;
148 order.insert(columnOrder[i], i);
149 setColumnVisible(i, false); // initally hide all columns
150 if (columnSizes[i] < header()->minimumSectionSize())
151 columnSizes[i] = header()->minimumSectionSize();
154 for (QMap<int,int>::const_iterator i = order.constBegin(); i != order.constEnd(); ++i) {
155 header()->moveSection(header()->visualIndex(i.value()), NUM_COLUMNS-1);
156 header()->resizeSection(i.value(), columnSizes[i.value()]);
157 setColumnVisible(i.value(), columns.testBit(i.value()));
160 header()->setSortIndicator(columnSort, columnSortOrder);
162 AutoResizeColumns = true;
167 void KeepassEntryView::resizeColumns() {
168 if (!AutoResizeColumns)
171 AutoResizeColumns = false;
173 int w = viewport()->width();
176 for (int i=0; i<NUM_COLUMNS; ++i) {
177 if (columnVisible(i))
178 sum += header()->sectionSize(i);
181 double stretch = (double)w / (double)sum;
183 for (int i=0; i<NUM_COLUMNS; ++i) {
184 if (columnVisible(i) && header()->sectionSize(i)!=0) {
185 int size = qRound(header()->sectionSize(i) * stretch);
186 header()->resizeSection(i, size);
187 columnSizes[i] = size;
190 columnSizes[i] = qRound(columnSizes[i] * stretch);
194 AutoResizeColumns = true;
197 void KeepassEntryView::OnGroupChanged(IGroupHandle* group){
202 void KeepassEntryView::OnShowSearchResults(){
207 void KeepassEntryView::OnItemsChanged(){
208 switch(selectedItems().size()){
209 case 0: emit selectionChanged(NONE);
211 case 1: emit selectionChanged(SINGLE);
213 default:emit selectionChanged(MULTIPLE);
217 void KeepassEntryView::OnSaveAttachment(){
218 if (selectedItems().size() == 0) return;
219 CEditEntryDlg::saveAttachment(((EntryViewItem*)selectedItems().first())->EntryHandle,this);
222 void KeepassEntryView::OnCloneEntry(){
223 QList<QTreeWidgetItem*> entries=selectedItems();
224 for(int i=0; i<entries.size();i++){
225 Items.append(new EntryViewItem(this));
226 Items.back()->EntryHandle=
227 db->cloneEntry(((EntryViewItem*)entries[i])->EntryHandle);
228 updateEntry(Items.back());
230 if (header()->isSortIndicatorShown())
231 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
235 void KeepassEntryView::OnDeleteEntry(){
236 QList<QTreeWidgetItem*> entries=selectedItems();
238 if(config->askBeforeDelete()){
240 if(entries.size()==1)
241 text=tr("Are you sure you want to delete this entry?");
243 text=tr("Are you sure you want to delete these %1 entries?").arg(entries.size());
244 if(QMessageBox::question(this,tr("Delete?"),text,QMessageBox::Yes | QMessageBox::No,QMessageBox::No)==QMessageBox::No)
249 IGroupHandle* bGroup = NULL;
250 if (config->backup() && ((EntryViewItem*)entries[0])->EntryHandle->group() != (bGroup=db->backupGroup()))
252 if (backup && !bGroup) {
253 emit requestCreateGroup("Backup", 4, NULL);
254 bGroup = db->backupGroup();
256 for(int i=0; i<entries.size();i++){
257 IEntryHandle* entryHandle = ((EntryViewItem*)entries[i])->EntryHandle;
258 if (backup && bGroup){
259 db->moveEntry(entryHandle, bGroup);
260 QDateTime now = QDateTime::currentDateTime();
261 entryHandle->setLastAccess(now);
262 entryHandle->setLastMod(now);
265 db->deleteEntry(entryHandle);
267 Items.removeAt(Items.indexOf((EntryViewItem*)entries[i]));
273 QString KeepassEntryView::columnString(IEntryHandle* entry, int col, bool forceClearText) {
276 return entry->title();
278 if (config->hideUsernames() && !forceClearText)
281 return entry->username();
286 if (config->hidePasswords() && !forceClearText) {
290 SecString password = entry->password();
292 return password.string();
297 QString comment = entry->comment();
298 int toPos = comment.indexOf(QRegExp("[\\r\\n]"));
302 return comment.left(toPos);
305 return entry->expire().dateToString(Qt::SystemLocaleDate);
307 return entry->creation().dateToString(Qt::SystemLocaleDate);
309 return entry->lastMod().dateToString(Qt::SystemLocaleDate);
311 return entry->lastAccess().dateToString(Qt::SystemLocaleDate);
313 return entry->binaryDesc();
315 return entry->group()->title();
322 void KeepassEntryView::updateEntry(EntryViewItem* item){
323 IEntryHandle* entry = item->EntryHandle;
325 int cols = NUM_COLUMNS - 1;
326 if (ViewMode == ShowSearchResults) {
327 item->setIcon(10, db->icon(entry->group()->image()));
331 for (int i=0; i<cols; ++i) {
332 item->setText(i, columnString(entry, i));
334 item->setIcon(0, db->icon(entry->image()));
337 void KeepassEntryView::editEntry(EntryViewItem* item){
338 IEntryHandle* handle = item->EntryHandle;
339 CEntry old = handle->data();
341 CEditEntryDlg dlg(db,handle,this,true);
342 int result = dlg.exec();
344 case 0: //canceled or no changes
346 case 1: //modifications but same group
350 //entry moved to another group
352 case 3: //not modified
353 Items.removeAll(item);
359 IGroupHandle* bGroup;
360 if ((result==1 || result==2) && config->backup() && handle->group() != (bGroup=db->backupGroup())){
361 old.LastAccess = QDateTime::currentDateTime();
362 old.LastMod = old.LastAccess;
364 emit requestCreateGroup("Backup", 4, NULL);
365 if ((bGroup = db->backupGroup())!=NULL)
366 db->addEntry(&old, bGroup);
374 void KeepassEntryView::OnNewEntry(){
375 IGroupHandle* ParentGroup;
377 if (!CurrentGroup){ // We must be viewing search results. Add the new entry to the first group.
378 if (db->groups().size() > 0)
379 ParentGroup = db->sortedGroups()[0];
382 QMessageBox::critical(NULL,tr("Error"),tr("At least one group must exist before adding an entry."),tr("OK"));
386 ParentGroup = CurrentGroup;
389 IEntryHandle* NewEntry = db->newEntry(ParentGroup);
390 NewEntry->setImage(ParentGroup->image());
392 CEditEntryDlg dlg(db,NewEntry,this,true);
394 db->deleteLastEntry();
397 Items.append(new EntryViewItem(this));
398 Items.back()->EntryHandle=NewEntry;
399 updateEntry(Items.back());
401 if (header()->isSortIndicatorShown())
402 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
403 setCurrentItem(Items.back());
408 void KeepassEntryView::OnEntryActivated(QTreeWidgetItem* item, int Column){
413 OnUsernameToClipboard();
419 OnPasswordToClipboard();
424 void KeepassEntryView::OnEntryDblClicked(QTreeWidgetItem* item, int Column){
426 editEntry((EntryViewItem*)item);
429 void KeepassEntryView::OnEditEntry(){
430 if (selectedItems().size() == 0) return;
431 editEntry((EntryViewItem*)selectedItems().first());
434 void KeepassEntryView::OnEditOpenUrl(){
435 if (selectedItems().size() == 0) return;
436 openBrowser( ((EntryViewItem*)selectedItems().first())->EntryHandle );
439 void KeepassEntryView::OnEditCopyUrl(){
440 if (selectedItems().size() == 0) return;
441 QString url = ((EntryViewItem*)selectedItems().first())->EntryHandle->url();
442 if (url.startsWith("cmd://") && url.length()>6)
443 url = url.right(url.length()-6);
445 Clipboard->setText(url, QClipboard::Clipboard);
446 if(Clipboard->supportsSelection()){
447 Clipboard->setText(url, QClipboard::Selection);
451 void KeepassEntryView::OnUsernameToClipboard(){
452 if (selectedItems().size() == 0) return;
453 QString username = ((EntryViewItem*)selectedItems().first())->EntryHandle->username();
454 Clipboard->setText(username, QClipboard::Clipboard);
455 if(Clipboard->supportsSelection()){
456 Clipboard->setText(username, QClipboard::Selection);
459 if (config->clipboardTimeOut()!=0 && !username.trimmed().isEmpty()) {
460 ClipboardTimer.setSingleShot(true);
461 ClipboardTimer.start(config->clipboardTimeOut()*1000);
465 void KeepassEntryView::OnPasswordToClipboard(){
466 if (selectedItems().size() == 0) return;
468 password=((EntryViewItem*)selectedItems().first())->EntryHandle->password();
470 Clipboard->setText(password.string(), QClipboard::Clipboard);
471 if(Clipboard->supportsSelection()){
472 Clipboard->setText(password.string(), QClipboard::Selection);
475 if (config->clipboardTimeOut()!=0 && !password.string().isEmpty()) {
476 ClipboardTimer.setSingleShot(true);
477 ClipboardTimer.start(config->clipboardTimeOut()*1000);
481 void KeepassEntryView::OnClipboardTimeOut(){
482 Clipboard->clear(QClipboard::Clipboard);
483 if(Clipboard->supportsSelection()){
484 Clipboard->clear(QClipboard::Selection);
487 QProcess::startDetached("dcop klipper klipper clearClipboardHistory");
488 QProcess::startDetached("dbus-send --type=method_call --dest=org.kde.klipper /klipper "
489 "org.kde.klipper.klipper.clearClipboardHistory");
494 void KeepassEntryView::contextMenuEvent(QContextMenuEvent* e){
495 if(itemAt(e->pos())){
496 EntryViewItem* item=(EntryViewItem*)itemAt(e->pos());
497 if(!selectedItems().size()){
498 setItemSelected(item,true);
501 if(!isItemSelected(item)){
502 while(selectedItems().size()){
503 setItemSelected(selectedItems().first(),false);
505 setItemSelected(item,true);
510 while (selectedItems().size())
511 setItemSelected(selectedItems().first(),false);
514 ContextMenu->popup(e->globalPos());
517 void KeepassEntryView::resizeEvent(QResizeEvent* e){
519 QTreeWidget::resizeEvent(e);
523 void KeepassEntryView::showSearchResults(){
524 if(ViewMode == Normal){
526 ViewMode = ShowSearchResults;
528 emit viewModeChanged(true);
532 createItems(SearchResults);
536 void KeepassEntryView::showGroup(IGroupHandle* group){
537 if(ViewMode == ShowSearchResults){
541 emit viewModeChanged(false);
545 if(group==NULL)return;
546 QList<IEntryHandle*>entries=db->entries(group);
547 createItems(entries);
550 void KeepassEntryView::createItems(QList<IEntryHandle*>& entries){
551 for (int i=0; i<entries.size(); ++i) {
552 if (!entries[i]->isValid())
555 EntryViewItem* item = new EntryViewItem(this);
556 Items.push_back(item);
557 Items.back()->EntryHandle = entries[i];
563 void KeepassEntryView::updateIcons(){
564 for(int i=0;i<Items.size();i++){
565 Items[i]->setIcon(0,db->icon(Items[i]->EntryHandle->image()));
569 void KeepassEntryView::refreshItems(){
570 for (int i=0;i<Items.size();i++)
571 updateEntry(Items.at(i));
574 void KeepassEntryView::mousePressEvent(QMouseEvent *event){
575 //save event position - maybe this is the start of a drag
576 if (event->button() == Qt::LeftButton)
577 DragStartPos = event->pos();
578 QTreeWidget::mousePressEvent(event);
581 void KeepassEntryView::mouseMoveEvent(QMouseEvent *event){
582 if (!(event->buttons() & Qt::LeftButton))
584 if ((event->pos() - DragStartPos).manhattanLength() < QApplication::startDragDistance())
588 EntryViewItem* DragStartItem=(EntryViewItem*)itemAt(DragStartPos);
590 while(selectedItems().size()){
591 setItemSelected(selectedItems().first(),false);
595 if(selectedItems().isEmpty()){
596 setItemSelected(DragStartItem,true);
599 bool AlreadySelected=false;
600 for(int i=0;i<selectedItems().size();i++){
601 if(selectedItems()[i]==DragStartItem){
602 AlreadySelected=true;
606 if(!AlreadySelected){
607 while(selectedItems().size()){
608 setItemSelected(selectedItems().first(),false);
610 setItemSelected(DragStartItem,true);
614 DragItems=selectedItems();
615 QDrag *drag = new QDrag(this);
616 QMimeData *mimeData = new QMimeData;
617 void* pDragItems=&DragItems;
618 if (header()->logicalIndexAt(event->pos()) != -1) {
619 mimeData->setText(columnStringView(DragStartItem, header()->logicalIndexAt(event->pos()), true));
621 mimeData->setData("application/x-keepassx-entry",QByteArray((char*)&pDragItems,sizeof(void*)));
622 drag->setMimeData(mimeData);
623 EventOccurredBlock = true;
624 drag->exec(Qt::MoveAction);
625 EventOccurredBlock = false;
628 void KeepassEntryView::removeDragItems(){
629 for(int i=0;i<DragItems.size();i++){
630 for(int j=0;j<Items.size();j++){
631 if(Items[j]==DragItems[i]){
641 void KeepassEntryView::OnAutoType(){
642 if (selectedItems().size() == 0) return;
643 autoType->perform(((EntryViewItem*)selectedItems().first())->EntryHandle);
647 void KeepassEntryView::paintEvent(QPaintEvent * event){
648 QTreeWidget::paintEvent(event);
652 EntryViewItem::EntryViewItem(QTreeWidget *parent):QTreeWidgetItem(parent){
656 EntryViewItem::EntryViewItem(QTreeWidget *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){
660 EntryViewItem::EntryViewItem(QTreeWidgetItem *parent):QTreeWidgetItem(parent){
664 EntryViewItem::EntryViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){
669 bool EntryViewItem::operator<(const QTreeWidgetItem& other) const{
670 int SortCol = treeWidget()->header()->sortIndicatorSection();
671 int ListIndex = ((KeepassEntryView*)treeWidget())->header()->logicalIndex(SortCol);
673 int comp = compare(other, SortCol, ListIndex);
677 int visibleCols = treeWidget()->header()->count() - treeWidget()->header()->hiddenSectionCount();
678 int ListIndexOrg = ListIndex;
679 for (int i=0; i<visibleCols; i++){
680 SortCol = treeWidget()->header()->logicalIndex(i);
681 ListIndex = ((KeepassEntryView*)treeWidget())->header()->logicalIndex(SortCol);
682 if (ListIndex==ListIndexOrg || ListIndex==3) // sort or password column
685 comp = compare(other, SortCol, ListIndex);
689 return true; // entries are equal
693 int EntryViewItem::compare(const QTreeWidgetItem& other, int col, int index) const{
694 if (index < 5 || index > 8){ //columns with string values (Title, Username, Password, URL, Comment, Group)
695 return QString::localeAwareCompare(text(col),other.text(col));
698 KpxDateTime DateThis;
699 KpxDateTime DateOther;
703 DateThis=EntryHandle->expire();
704 DateOther=((EntryViewItem&)other).EntryHandle->expire();
707 DateThis=EntryHandle->creation();
708 DateOther=((EntryViewItem&)other).EntryHandle->creation();
711 DateThis=EntryHandle->lastMod();
712 DateOther=((EntryViewItem&)other).EntryHandle->lastMod();
715 DateThis=EntryHandle->lastAccess();
716 DateOther=((EntryViewItem&)other).EntryHandle->lastAccess();
722 if (DateThis==DateOther)
724 else if (DateThis < DateOther)
730 void KeepassEntryView::setCurrentEntry(IEntryHandle* entry){
733 for(i=0;i<Items.size();i++)
734 if(Items.at(i)->EntryHandle==entry){found=true; break;}
736 setCurrentItem(Items.at(i));