Fix .desktop file path
[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->mimeData()->hasFormat("application/x-keepassx-group")){
210                 DragType=GroupDrag;
211                 event->acceptProposedAction();
212                 return;
213         }
214         if(event->mimeData()->hasFormat("application/x-keepassx-entry")){
215                 DragType=EntryDrag;
216                 memcpy(&EntryDragItems,event->mimeData()->data("application/x-keepassx-entry").data(),sizeof(void*));
217                 event->acceptProposedAction();
218                 return;
219         }
220         
221 }
222
223
224
225 void KeepassGroupView::dragLeaveEvent ( QDragLeaveEvent * event ){
226         Q_UNUSED(event);
227         if(LastHoverItem){
228                 LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
229                 LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
230         }
231         if(InsLinePos!=-1){
232                 int RemoveLine=InsLinePos;
233                 InsLinePos=-1;
234                 viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
235         }
236         
237 }
238
239 void KeepassGroupView::entryDropEvent( QDropEvent * event ){
240         GroupViewItem* Item=(GroupViewItem*)itemAt(event->pos());
241         if(!Item){
242                 event->ignore();
243                 return;
244         }
245         else{
246                 if(Item->GroupHandle==((EntryViewItem*)(*EntryDragItems)[0])->EntryHandle->group())
247                         return;
248                 for(int i=0;i<EntryDragItems->size();i++){
249                         db->moveEntry(((EntryViewItem*)(*EntryDragItems)[i])->EntryHandle,Item->GroupHandle);
250                 }
251                 emit entriesDropped();
252                 emit fileModified();
253         }
254         
255 }
256
257
258 void KeepassGroupView::dropEvent( QDropEvent * event ){
259         if(LastHoverItem){
260                 LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
261                 LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
262         }
263         
264         if(DragType==EntryDrag){
265                 entryDropEvent(event);
266                 return;
267         }
268
269         if(InsLinePos!=-1){
270                 int RemoveLine=InsLinePos;
271                 InsLinePos=-1;
272                 viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
273         }
274         GroupViewItem* Item=(GroupViewItem*)itemAt(event->pos());
275         
276         if(!Item){
277                 qDebug("Append at the end");
278                 db->moveGroup(DragItem->GroupHandle,NULL,-1);
279                 if(DragItem->parent()){
280                         DragItem->parent()->takeChild(DragItem->parent()->indexOfChild(DragItem));
281                 }
282                 else{
283                         takeTopLevelItem(indexOfTopLevelItem(DragItem));
284                 }
285                 insertTopLevelItem(topLevelItemCount(),DragItem);
286                 if(topLevelItemCount()>1){
287                         if(topLevelItem(topLevelItemCount()-2)==SearchResultItem){
288                                 takeTopLevelItem(topLevelItemCount()-2);
289                                 insertTopLevelItem(topLevelItemCount(),SearchResultItem);       
290                         }                       
291                 }
292                 emit fileModified();
293         }
294         else{
295                 if (DragItem->GroupHandle==Item->GroupHandle)
296                         return;
297                 
298                 QRect ItemRect=visualItemRect(Item);
299                 if(event->pos().y()>ItemRect.y()+2 && event->pos().y()<ItemRect.y()+ItemRect.height()-2){
300                         qDebug("Append as child of '%s'",((char*)Item->text(0).toUtf8().data()));
301                         db->moveGroup(DragItem->GroupHandle,Item->GroupHandle,-1);
302                         if(DragItem->parent()){
303                                 DragItem->parent()->takeChild(DragItem->parent()->indexOfChild(DragItem));
304                         }
305                         else{
306                                 takeTopLevelItem(indexOfTopLevelItem(DragItem));
307                         }
308                         Item->insertChild(Item->childCount(),DragItem);
309                         emit fileModified();
310                 }
311                 else{
312                         if(event->pos().y()>ItemRect.y()+2){
313                                 qDebug("Insert behind sibling '%s'",((char*)Item->text(0).toUtf8().data()));
314                                 if(DragItem->parent()){
315                                         DragItem->parent()->takeChild(DragItem->parent()->indexOfChild(DragItem));
316                                 }
317                                 else{
318                                         takeTopLevelItem(indexOfTopLevelItem(DragItem));
319                                 }                               
320                                 if(Item->parent()){
321                                         int index=Item->parent()->indexOfChild(Item)+1;
322                                         db->moveGroup(DragItem->GroupHandle,((GroupViewItem*)Item->parent())->GroupHandle,index);
323                                         Item->parent()->insertChild(index,DragItem);
324                                 }
325                                 else{
326                                         int index=indexOfTopLevelItem(Item)+1;
327                                         db->moveGroup(DragItem->GroupHandle,NULL,index);
328                                         insertTopLevelItem(index,DragItem);     
329                                 }
330                                 emit fileModified();
331                         }
332                         else{   
333                                 qDebug("Insert before sibling '%s'",((char*)Item->text(0).toUtf8().data()));
334                                 if(DragItem->parent()){
335                                         DragItem->parent()->takeChild(DragItem->parent()->indexOfChild(DragItem));
336                                 }
337                                 else{
338                                         takeTopLevelItem(indexOfTopLevelItem(DragItem));
339                                 }                               
340                                 if(Item->parent()){
341                                         int index=Item->parent()->indexOfChild(Item);
342                                         db->moveGroup(DragItem->GroupHandle,((GroupViewItem*)Item->parent())->GroupHandle,index);
343                                         Item->parent()->insertChild(index,DragItem);
344                                 }
345                                 else{
346                                         int index=indexOfTopLevelItem(Item);
347                                         db->moveGroup(DragItem->GroupHandle,NULL,index);
348                                         insertTopLevelItem(index,DragItem);     
349                                 }
350                                 emit fileModified();    
351                         }
352                 }
353                 
354                 
355         }
356         
357 }
358
359 void KeepassGroupView::entryDragMoveEvent(QDragMoveEvent* event){
360
361         GroupViewItem* Item=(GroupViewItem*)itemAt(event->pos());
362         if(!Item){
363                 if(LastHoverItem){
364                         LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
365                         LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
366                         LastHoverItem=NULL;
367                 }
368                 event->ignore();
369                 return;
370         }
371         if(Item==SearchResultItem){
372                 if(LastHoverItem){
373                         LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
374                         LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
375                         LastHoverItem=NULL;
376                 }
377                 event->ignore();
378                 return;
379         }
380         if(LastHoverItem != Item){
381                 if(LastHoverItem){
382                         LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
383                         LastHoverItem->setForeground(0,QBrush(QApplication::palette().color(QPalette::Text)));
384                 }
385                 Item->setBackgroundColor(0,QApplication::palette().color(QPalette::Highlight));
386                 Item->setForeground(0,QBrush(QApplication::palette().color(QPalette::HighlightedText)));
387                 LastHoverItem=Item;
388         }
389         event->acceptProposedAction();
390         return;
391         
392 }
393
394 void KeepassGroupView::dragMoveEvent(QDragMoveEvent* event){
395         if(DragType==EntryDrag){
396                 entryDragMoveEvent(event);
397                 return;
398         }       
399         if(DragItem){
400                 GroupViewItem* Item=(GroupViewItem*)itemAt(event->pos());
401                 if(!Item){
402                         if(LastHoverItem){
403                                 LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
404                                 LastHoverItem=NULL;
405                         }
406                         if(InsLinePos!=-1){
407                                 int RemoveLine=InsLinePos;
408                                 InsLinePos=-1;
409                                 viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
410                         }
411                         event->acceptProposedAction();
412                         return;
413                 }
414                 if(Item==DragItem || Item==SearchResultItem){
415                         event->ignore();
416                         return;
417                 }
418                 if(!db->isParent(DragItem->GroupHandle,Item->GroupHandle)){
419                         QRect ItemRect=visualItemRect(Item);
420                         if(event->pos().y()>ItemRect.y()+2 && event->pos().y()<ItemRect.y()+ItemRect.height()-2){
421                                 if(InsLinePos!=-1){
422                                         int RemoveLine=InsLinePos;
423                                         InsLinePos=-1;
424                                         viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
425                                 }
426                                 if(LastHoverItem != Item){
427                                         if(LastHoverItem){
428                                                 LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
429                                         }
430                                         Item->setBackgroundColor(0,QApplication::palette().color(QPalette::Highlight));
431                                         LastHoverItem=Item;
432                                 }
433                         }
434                         else{
435                                 if(LastHoverItem){
436                                         LastHoverItem->setBackgroundColor(0,QApplication::palette().color(QPalette::Base));
437                                         LastHoverItem=NULL;
438                                 }
439                                 if(InsLinePos!=-1){
440                                         int RemoveLine=InsLinePos;
441                                         InsLinePos=-1;
442                                         viewport()->update(QRegion(0,RemoveLine-2,viewport()->width(),4));
443                                 }
444                                 if(event->pos().y()>ItemRect.y()+2){
445                                         InsLinePos=ItemRect.y()+ItemRect.height();
446                                 }
447                                 else{   
448                                         InsLinePos=ItemRect.y();
449                                 }
450                                 InsLineStart=ItemRect.x();
451                                 viewport()->update(QRegion(0,InsLinePos-2,viewport()->width(),4));
452                         }
453                         event->acceptProposedAction();
454                         return;
455                 }               
456                 
457         }
458         event->ignore();
459 }
460
461 void KeepassGroupView::paintEvent(QPaintEvent* event){
462
463         QTreeWidget::paintEvent(event);
464         if(InsLinePos != -1){
465                 QPainter painter(viewport());
466                 painter.setBrush(QBrush(QColor(0,0,0),Qt::Dense4Pattern));
467                 painter.setPen(Qt::NoPen);
468                 painter.drawRect(InsLineStart,InsLinePos-2,viewport()->width(),4);
469         }
470 }
471
472
473 void KeepassGroupView::mousePressEvent(QMouseEvent *event){
474         if (event->button() == Qt::LeftButton)
475                 DragStartPos = event->pos();
476         QTreeWidget::mousePressEvent(event);    
477 }
478
479 void KeepassGroupView::mouseMoveEvent(QMouseEvent *event){
480         if (!(event->buttons() & Qt::LeftButton))
481                 return;
482         if ((event->pos() - DragStartPos).manhattanLength()
483                         < QApplication::startDragDistance())
484                 return;
485         
486         DragItem=(GroupViewItem*)itemAt(event->pos());
487         if(!DragItem)return;
488         
489         if(DragItem==SearchResultItem){
490                 qDebug("SearchGroup");
491                 DragItem=NULL;
492                 return;
493         }
494         
495         setCurrentItem(DragItem);
496         
497         QDrag *drag = new QDrag(this);
498         QMimeData *mimeData = new QMimeData;
499
500         mimeData->setData("application/x-keepassx-group",QByteArray());
501         drag->setMimeData(mimeData);
502
503         EventOccurredBlock = true;
504         drag->exec(Qt::MoveAction);
505         EventOccurredBlock = false;
506 }
507
508 void KeepassGroupView::OnItemExpanded(QTreeWidgetItem* item){
509         static_cast<GroupViewItem*>(item)->GroupHandle->setExpanded(true);
510 }
511
512 void KeepassGroupView::OnItemCollapsed(QTreeWidgetItem* item){
513         static_cast<GroupViewItem*>(item)->GroupHandle->setExpanded(false);
514 }
515
516 void KeepassGroupView::OnSort() {
517         QHash<QTreeWidgetItem*,int> oldIndex;
518         for (int i=0; i<Items.size(); i++) {
519                 if (Items[i]->parent())
520                         oldIndex.insert(Items[i], Items[i]->parent()->indexOfChild(Items[i]));
521                 else
522                         oldIndex.insert(Items[i], invisibleRootItem()->indexOfChild(Items[i]));
523         }
524         
525         sortItems(0, Qt::AscendingOrder);
526         
527         bool modified = false;
528         QMutableHashIterator<QTreeWidgetItem*, int> i(oldIndex);
529         while (i.hasNext()) {
530                 i.next();
531                 int newIndex;
532                 IGroupHandle* parent;
533                 if (i.key()->parent()) {
534                         newIndex = i.key()->parent()->indexOfChild(i.key());
535                         parent = static_cast<GroupViewItem*>(i.key()->parent())->GroupHandle;
536                 }
537                 else {
538                         newIndex = invisibleRootItem()->indexOfChild(i.key());
539                         parent = NULL;
540                 }
541                 
542                 if (newIndex != i.value()) {
543                         db->moveGroup(static_cast<GroupViewItem*>(i.key())->GroupHandle, parent, newIndex);
544                         modified = true;
545                 }
546         }
547         
548         if (modified)
549                 emit fileModified();
550 }
551
552
553
554 GroupViewItem::GroupViewItem():QTreeWidgetItem(){
555 }
556
557 GroupViewItem::GroupViewItem(QTreeWidget *parent):QTreeWidgetItem(parent){
558 }
559
560 GroupViewItem::GroupViewItem(QTreeWidget *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){
561 }
562
563 GroupViewItem::GroupViewItem(QTreeWidgetItem *parent):QTreeWidgetItem(parent){
564 }
565
566 GroupViewItem::GroupViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding):QTreeWidgetItem(parent,preceding){
567 }
568
569 bool GroupViewItem::operator<(const QTreeWidgetItem& other) const {
570         const GroupViewItem* otherItem = static_cast<const GroupViewItem*>(&other);
571         KeepassGroupView* groupView = static_cast<KeepassGroupView*>(treeWidget());
572         
573         // Search result is always at the bottom
574         if (this == groupView->SearchResultItem)
575                 return false;
576         if (otherItem == groupView->SearchResultItem)
577                 return true;
578         
579         // Backup group is always at the bottom but above search results
580         if (!parent() && text(0).compare("Backup", Qt::CaseInsensitive) == 0)
581                 return false;
582         if (!other.parent() && other.text(0).compare("Backup", Qt::CaseInsensitive) == 0)
583                 return true;
584         
585         return QString::localeAwareCompare(text(0), other.text(0)) < 0;
586 }