Import 0.4.3 version in mainstream branch
[keepassx] / src / lib / GroupView.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2007 by Tarek Saidi                                *
3  *   tarek.saidi@arcor.de                                                  *
4  *                                                                         *
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.               *
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                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18  ***************************************************************************/
19
20
21 #include "EntryView.h"
22 #include "GroupView.h"
23 #include "dialogs/EditGroupDlg.h"
24
25 #include <QBrush>
26 #include <QHeaderView>
27
28 #define INSERT_AREA_WIDTH 4
29
30 KeepassGroupView::KeepassGroupView(QWidget* parent):QTreeWidget(parent){
31         db=NULL;
32         ContextMenu=new QMenu(this);
33         ContextMenuSearchGroup=new QMenu(this);
34         connect(this,SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),this,SLOT(OnCurrentGroupChanged(QTreeWidgetItem*)));
35         connect(this,SIGNAL(itemExpanded(QTreeWidgetItem*)),this,SLOT(OnItemExpanded(QTreeWidgetItem*)));
36         connect(this,SIGNAL(itemCollapsed(QTreeWidgetItem*)),this,SLOT(OnItemCollapsed(QTreeWidgetItem*)));
37 }
38
39 void KeepassGroupView::createItems(){
40         clear();
41         Items.clear();
42         InsLinePos=-1;
43         QList<IGroupHandle*> groups=db->groups();
44         for(int i=0;i<groups.size();i++){
45                 if(groups[i]->parent()==NULL){
46                         Items.append(new GroupViewItem(this));
47                         Items.back()->setText(0,groups[i]->title());
48                         Items.back()->GroupHandle=groups[i];
49                         addChildren(Items.back());
50                 }
51         }
52         for(int i=0;i<Items.size();i++){
53                 Items[i]->setIcon(0,db->icon(Items[i]->GroupHandle->image()));
54                 Items[i]->setExpanded(Items[i]->GroupHandle->expanded());
55         }
56         SearchResultItem=new GroupViewItem();
57         retranslateUi();
58 }
59
60 void KeepassGroupView::retranslateUi() {
61         SearchResultItem->setText(0,tr("Search Results"));
62 }
63
64 void KeepassGroupView::updateIcons(){
65         for(int i=0;i<Items.size();i++){
66                 Items[i]->setIcon(0,db->icon(Items[i]->GroupHandle->image()));
67         }
68 }
69
70 void KeepassGroupView::showSearchResults(){
71         if(topLevelItem(topLevelItemCount()-1)!=SearchResultItem){
72                 addTopLevelItem(SearchResultItem);
73         }
74         setCurrentItem(SearchResultItem);
75         emit searchResultsSelected();
76 }
77
78 void KeepassGroupView::addChildren(GroupViewItem* item){
79         QList<IGroupHandle*>children=item->GroupHandle->children();
80         if(!children.size())
81                 return;
82         for(int i=0; i<children.size(); i++){
83                 Items.push_back(new GroupViewItem(item));
84                 Items.back()->setText(0,children[i]->title());
85                 Items.back()->GroupHandle=children[i];
86                 addChildren(Items.back());
87         }       
88 }
89
90 void KeepassGroupView::OnDeleteGroup(){
91         if(config->askBeforeDelete()){
92                 if(QMessageBox::question(this,tr("Delete?"),
93                    tr("Are you sure you want to delete this group, all its child groups and all their entries?"),
94                           QMessageBox::Yes | QMessageBox::No,QMessageBox::No) == QMessageBox::No)
95                         return;                 
96         }
97         GroupViewItem* item=(GroupViewItem*)currentItem();
98         if(item){
99                 db->deleteGroup(item->GroupHandle);
100                 delete item;
101                 emit fileModified();
102         }
103 }
104
105 void KeepassGroupView::OnHideSearchResults(){
106         takeTopLevelItem(topLevelItemCount()-1);
107 }
108
109 void KeepassGroupView::OnNewGroup(){
110         CGroup NewGroup;
111         CEditGroupDialog dlg(db,&NewGroup,parentWidget());
112         if(dlg.exec())
113                 createGroup(NewGroup.Title, NewGroup.Image, NULL);
114 }
115
116 void KeepassGroupView::OnNewSubgroup(){
117         GroupViewItem* parent=(GroupViewItem*)currentItem();
118         CGroup NewGroup;
119         CEditGroupDialog dlg(db,&NewGroup,parentWidget());
120         if(dlg.exec())
121                 createGroup(NewGroup.Title, NewGroup.Image, parent);
122 }
123
124 void KeepassGroupView::createGroup(const QString& title, quint32 image, GroupViewItem* parent){
125         CGroup NewGroup;
126         NewGroup.Title = title;
127         NewGroup.Image = image;
128         
129         IGroupHandle* group;
130         if(parent){
131                 group=db->addGroup(&NewGroup,parent->GroupHandle);
132                 Items.append(new GroupViewItem(parent));
133         }
134         else{
135                 if(topLevelItemCount()){
136                         int i=1;
137                         if(topLevelItem(topLevelItemCount()-i)==SearchResultItem)
138                                 i++;
139                         if(title!="Backup" && topLevelItem(topLevelItemCount()-i)->text(0)=="Backup")
140                                 i++;
141                         Items.append(new GroupViewItem(this,topLevelItem(topLevelItemCount()-i)));
142                 }
143                 else
144                         Items.append(new GroupViewItem(this));
145                 
146                 group = db->addGroup(&NewGroup,NULL);
147         }
148         
149         Items.back()->GroupHandle = group;
150         Items.back()->setText(0, group->title());
151         Items.back()->setIcon(0, db->icon(group->image()));
152         emit fileModified();
153 }
154
155 void KeepassGroupView::OnEditGroup(){
156         GroupViewItem* item=(GroupViewItem*)currentItem();
157         CEditGroupDialog dlg(db,item->GroupHandle,parentWidget());
158         int r=dlg.exec();
159         if(r){
160                 item->setIcon(0,db->icon(item->GroupHandle->image()));
161                 item->setText(0,item->GroupHandle->title());
162                 if(r==2)emit fileModified();
163         }
164 }
165
166 void KeepassGroupView::contextMenuEvent(QContextMenuEvent* e){
167         if(!(GroupViewItem*)itemAt(e->pos()))
168                 return;
169         
170         e->accept();
171         if(currentItem()==SearchResultItem)
172                 ContextMenuSearchGroup->popup(e->globalPos());
173         else
174                 ContextMenu->popup(e->globalPos());
175 }
176
177 void KeepassGroupView::OnCurrentGroupChanged(QTreeWidgetItem* cur){
178         if(cur){
179                 if(cur==SearchResultItem)
180                         emit searchResultsSelected();
181                 else
182                         emit groupChanged(((GroupViewItem*)cur)->GroupHandle);
183         }
184         else
185                 emit groupChanged(NULL);
186 }
187
188
189 void KeepassGroupView::setCurrentGroup(IGroupHandle* group){
190         bool found=false;
191         int i;
192         for(i=0;i<Items.size();i++)
193                 if(Items[i]->GroupHandle==group){found=true; break;}
194         if(!found)return;
195         setCurrentItem(Items[i]);
196 }
197
198 void KeepassGroupView::selectFirstGroup(){
199         if (Items.isEmpty())
200                 return;
201         
202         setCurrentItem(Items[0]);
203 }
204
205 void KeepassGroupView::dragEnterEvent ( QDragEnterEvent * event ){
206         LastHoverItem=NULL;
207         InsLinePos=-1;
208         
209         if (event->source() == NULL)
210                 return; // drag event came from another application
211         
212         if(event->mimeData()->hasFormat("application/x-keepassx-group")){
213                 DragType=GroupDrag;
214                 event->acceptProposedAction();
215                 return;
216         }
217         if(event->mimeData()->hasFormat("application/x-keepassx-entry")){
218                 DragType=EntryDrag;
219                 memcpy(&EntryDragItems,event->mimeData()->data("application/x-keepassx-entry").data(),sizeof(void*));
220                 event->acceptProposedAction();
221                 return;
222         }
223         
224 }
225
226
227
228 void KeepassGroupView::dragLeaveEvent ( QDragLeaveEvent * event ){
229         Q_UNUSED(event);
230         if(LastHoverItem){
231                 LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
232                 LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
233         }
234         if(InsLinePos!=-1){
235                 int RemoveLine=InsLinePos;
236                 InsLinePos=-1;
237                 viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
238         }
239         
240 }
241
242 void KeepassGroupView::entryDropEvent( QDropEvent * event ){
243         GroupViewItem* Item=(GroupViewItem*)itemAt(event->pos());
244         if(!Item){
245                 event->ignore();
246                 return;
247         }
248         else{
249                 if(Item->GroupHandle==((EntryViewItem*)(*EntryDragItems)[0])->EntryHandle->group())
250                         return;
251                 for(int i=0;i<EntryDragItems->size();i++){
252                         db->moveEntry(((EntryViewItem*)(*EntryDragItems)[i])->EntryHandle,Item->GroupHandle);
253                 }
254                 emit entriesDropped();
255                 emit fileModified();
256         }
257         
258 }
259
260
261 void KeepassGroupView::dropEvent( QDropEvent * event ){
262         if(LastHoverItem){
263                 LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
264                 LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
265         }
266         
267         if(DragType==EntryDrag){
268                 entryDropEvent(event);
269                 return;
270         }
271
272         if(InsLinePos!=-1){
273                 int RemoveLine=InsLinePos;
274                 InsLinePos=-1;
275                 viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
276         }
277         GroupViewItem* Item=(GroupViewItem*)itemAt(event->pos());
278         
279         if(!Item){
280                 qDebug("Append at the end");
281                 db->moveGroup(DragItem->GroupHandle,NULL,-1);
282                 if(DragItem->parent()){
283                         DragItem->parent()->takeChild(DragItem->parent()->indexOfChild(DragItem));
284                 }
285                 else{
286                         takeTopLevelItem(indexOfTopLevelItem(DragItem));
287                 }
288                 insertTopLevelItem(topLevelItemCount(),DragItem);
289                 if(topLevelItemCount()>1){
290                         if(topLevelItem(topLevelItemCount()-2)==SearchResultItem){
291                                 takeTopLevelItem(topLevelItemCount()-2);
292                                 insertTopLevelItem(topLevelItemCount(),SearchResultItem);       
293                         }                       
294                 }
295                 emit fileModified();
296         }
297         else{
298                 if (DragItem->GroupHandle==Item->GroupHandle)
299                         return;
300                 
301                 QRect ItemRect=visualItemRect(Item);
302                 if(event->pos().y()>ItemRect.y()+2 && event->pos().y()<ItemRect.y()+ItemRect.height()-2){
303                         qDebug("Append as child of '%s'",((char*)Item->text(0).toUtf8().data()));
304                         db->moveGroup(DragItem->GroupHandle,Item->GroupHandle,-1);
305                         if(DragItem->parent()){
306                                 DragItem->parent()->takeChild(DragItem->parent()->indexOfChild(DragItem));
307                         }
308                         else{
309                                 takeTopLevelItem(indexOfTopLevelItem(DragItem));
310                         }
311                         Item->insertChild(Item->childCount(),DragItem);
312                         emit fileModified();
313                 }
314                 else{
315                         if(event->pos().y()>ItemRect.y()+2){
316                                 qDebug("Insert behind sibling '%s'",((char*)Item->text(0).toUtf8().data()));
317                                 if(DragItem->parent()){
318                                         DragItem->parent()->takeChild(DragItem->parent()->indexOfChild(DragItem));
319                                 }
320                                 else{
321                                         takeTopLevelItem(indexOfTopLevelItem(DragItem));
322                                 }                               
323                                 if(Item->parent()){
324                                         int index=Item->parent()->indexOfChild(Item)+1;
325                                         db->moveGroup(DragItem->GroupHandle,((GroupViewItem*)Item->parent())->GroupHandle,index);
326                                         Item->parent()->insertChild(index,DragItem);
327                                 }
328                                 else{
329                                         int index=indexOfTopLevelItem(Item)+1;
330                                         db->moveGroup(DragItem->GroupHandle,NULL,index);
331                                         insertTopLevelItem(index,DragItem);     
332                                 }
333                                 emit fileModified();
334                         }
335                         else{   
336                                 qDebug("Insert before sibling '%s'",((char*)Item->text(0).toUtf8().data()));
337                                 if(DragItem->parent()){
338                                         DragItem->parent()->takeChild(DragItem->parent()->indexOfChild(DragItem));
339                                 }
340                                 else{
341                                         takeTopLevelItem(indexOfTopLevelItem(DragItem));
342                                 }                               
343                                 if(Item->parent()){
344                                         int index=Item->parent()->indexOfChild(Item);
345                                         db->moveGroup(DragItem->GroupHandle,((GroupViewItem*)Item->parent())->GroupHandle,index);
346                                         Item->parent()->insertChild(index,DragItem);
347                                 }
348                                 else{
349                                         int index=indexOfTopLevelItem(Item);
350                                         db->moveGroup(DragItem->GroupHandle,NULL,index);
351                                         insertTopLevelItem(index,DragItem);     
352                                 }
353                                 emit fileModified();    
354                         }
355                 }
356                 
357                 
358         }
359         
360 }
361
362 void KeepassGroupView::entryDragMoveEvent(QDragMoveEvent* event){
363
364         GroupViewItem* Item=(GroupViewItem*)itemAt(event->pos());
365         if(!Item){
366                 if(LastHoverItem){
367                         LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
368                         LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
369                         LastHoverItem=NULL;
370                 }
371                 event->ignore();
372                 return;
373         }
374         if(Item==SearchResultItem){
375                 if(LastHoverItem){
376                         LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
377                         LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
378                         LastHoverItem=NULL;
379                 }
380                 event->ignore();
381                 return;
382         }
383         if(LastHoverItem != Item){
384                 if(LastHoverItem){
385                         LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
386                         LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
387                 }
388                 Item->setBackgroundColor(0,QApplication::palette().color(QPalette::Highlight));
389                 Item->setForeground(0,QBrush(QApplication::palette().color(QPalette::HighlightedText)));
390                 LastHoverItem=Item;
391         }
392         event->acceptProposedAction();
393         return;
394         
395 }
396
397 void KeepassGroupView::dragMoveEvent(QDragMoveEvent* event){
398         if(DragType==EntryDrag){
399                 entryDragMoveEvent(event);
400                 return;
401         }       
402         if(DragItem){
403                 GroupViewItem* Item=(GroupViewItem*)itemAt(event->pos());
404                 if(!Item){
405                         if(LastHoverItem){
406                                 LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
407                                 LastHoverItem=NULL;
408                         }
409                         if(InsLinePos!=-1){
410                                 int RemoveLine=InsLinePos;
411                                 InsLinePos=-1;
412                                 viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
413                         }
414                         event->acceptProposedAction();
415                         return;
416                 }
417                 if(Item==DragItem || Item==SearchResultItem){
418                         event->ignore();
419                         return;
420                 }
421                 if(!db->isParent(DragItem->GroupHandle,Item->GroupHandle)){
422                         QRect ItemRect=visualItemRect(Item);
423                         if(event->pos().y()>ItemRect.y()+2 && event->pos().y()<ItemRect.y()+ItemRect.height()-2){
424                                 if(InsLinePos!=-1){
425                                         int RemoveLine=InsLinePos;
426                                         InsLinePos=-1;
427                                         viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
428                                 }
429                                 if(LastHoverItem != Item){
430                                         if(LastHoverItem){
431                                                 LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
432                                         }
433                                         Item->setBackgroundColor(0,QApplication::palette().color(QPalette::Highlight));
434                                         LastHoverItem=Item;
435                                 }
436                         }
437                         else{
438                                 if(LastHoverItem){
439                                         LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
440                                         LastHoverItem=NULL;
441                                 }
442                                 if(InsLinePos!=-1){
443                                         int RemoveLine=InsLinePos;
444                                         InsLinePos=-1;
445                                         viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
446                                 }
447                                 if(event->pos().y()>ItemRect.y()+2){
448                                         InsLinePos=ItemRect.y()+ItemRect.height();
449                                 }
450                                 else{   
451                                         InsLinePos=ItemRect.y();
452                                 }
453                                 InsLineStart=ItemRect.x();
454                                 viewport()->update(QRegion(0,InsLinePos-2,viewport()->width(),4));
455                         }
456                         event->acceptProposedAction();
457                         return;
458                 }               
459                 
460         }
461         event->ignore();
462 }
463
464 void KeepassGroupView::paintEvent(QPaintEvent* event){
465
466         QTreeWidget::paintEvent(event);
467         if(InsLinePos != -1){
468                 QPainter painter(viewport());
469                 painter.setBrush(QBrush(QColor(0,0,0),Qt::Dense4Pattern));
470                 painter.setPen(Qt::NoPen);
471                 painter.drawRect(InsLineStart,InsLinePos-2,viewport()->width(),4);
472         }
473 }
474
475
476 void KeepassGroupView::mousePressEvent(QMouseEvent *event){
477         if (event->button() == Qt::LeftButton)
478                 DragStartPos = event->pos();
479         QTreeWidget::mousePressEvent(event);    
480 }
481
482 void KeepassGroupView::mouseMoveEvent(QMouseEvent *event){
483         if (!(event->buttons() & Qt::LeftButton))
484                 return;
485         if ((event->pos() - DragStartPos).manhattanLength()
486                         < QApplication::startDragDistance())
487                 return;
488         
489         DragItem=(GroupViewItem*)itemAt(event->pos());
490         if(!DragItem)return;
491         
492         if(DragItem==SearchResultItem){
493                 qDebug("SearchGroup");
494                 DragItem=NULL;
495                 return;
496         }
497         
498         setCurrentItem(DragItem);
499         
500         QDrag *drag = new QDrag(this);
501         QMimeData *mimeData = new QMimeData;
502
503         mimeData->setData("application/x-keepassx-group",QByteArray());
504         drag->setMimeData(mimeData);
505
506         EventOccurredBlock = true;
507         drag->exec(Qt::MoveAction);
508         EventOccurredBlock = false;
509 }
510
511 void KeepassGroupView::OnItemExpanded(QTreeWidgetItem* item){
512         static_cast<GroupViewItem*>(item)->GroupHandle->setExpanded(true);
513 }
514
515 void KeepassGroupView::OnItemCollapsed(QTreeWidgetItem* item){
516         static_cast<GroupViewItem*>(item)->GroupHandle->setExpanded(false);
517 }
518
519 void KeepassGroupView::OnSort() {
520         QHash<QTreeWidgetItem*,int> oldIndex;
521         for (int i=0; i<Items.size(); i++) {
522                 if (Items[i]->parent())
523                         oldIndex.insert(Items[i], Items[i]->parent()->indexOfChild(Items[i]));
524                 else
525                         oldIndex.insert(Items[i], invisibleRootItem()->indexOfChild(Items[i]));
526         }
527         
528         sortItems(0, Qt::AscendingOrder);
529         
530         bool modified = false;
531         QMutableHashIterator<QTreeWidgetItem*, int> i(oldIndex);
532         while (i.hasNext()) {
533                 i.next();
534                 int newIndex;
535                 IGroupHandle* parent;
536                 if (i.key()->parent()) {
537                         newIndex = i.key()->parent()->indexOfChild(i.key());
538                         parent = static_cast<GroupViewItem*>(i.key()->parent())->GroupHandle;
539                 }
540                 else {
541                         newIndex = invisibleRootItem()->indexOfChild(i.key());
542                         parent = NULL;
543                 }
544                 
545                 if (newIndex != i.value()) {
546                         db->moveGroup(static_cast<GroupViewItem*>(i.key())->GroupHandle, parent, newIndex);
547                         modified = true;
548                 }
549         }
550         
551         if (modified)
552                 emit fileModified();
553 }
554
555
556
557 GroupViewItem::GroupViewItem():QTreeWidgetItem(){
558 }
559
560 GroupViewItem::GroupViewItem(QTreeWidget *parent):QTreeWidgetItem(parent){
561 }
562
563 GroupViewItem::GroupViewItem(QTreeWidget *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){
564 }
565
566 GroupViewItem::GroupViewItem(QTreeWidgetItem *parent):QTreeWidgetItem(parent){
567 }
568
569 GroupViewItem::GroupViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){
570 }
571
572 bool GroupViewItem::operator<(const QTreeWidgetItem& other) const {
573         const GroupViewItem* otherItem = static_cast<const GroupViewItem*>(&other);
574         KeepassGroupView* groupView = static_cast<KeepassGroupView*>(treeWidget());
575         
576         // Search result is always at the bottom
577         if (this == groupView->SearchResultItem)
578                 return false;
579         if (otherItem == groupView->SearchResultItem)
580                 return true;
581         
582         // Backup group is always at the bottom but above search results
583         if (!parent() && text(0).compare("Backup", Qt::CaseInsensitive) == 0)
584                 return false;
585         if (!other.parent() && other.text(0).compare("Backup", Qt::CaseInsensitive) == 0)
586                 return true;
587         
588         return QString::localeAwareCompare(text(0), other.text(0)) < 0;
589 }