1 /*
2 
3                           Firewall Builder
4 
5                  Copyright (C) 2003 NetCitadel, LLC
6 
7   Author:  Vadim Kurland     vadim@fwbuilder.org
8 
9   $Id$
10 
11   This program is free software which we release under the GNU General Public
12   License. You may redistribute and/or modify this program under the terms
13   of that license as published by the Free Software Foundation; either
14   version 2 of the License, or (at your option) any later version.
15 
16   This program is distributed in the hope that it will be useful,
17   but WITHOUT ANY WARRANTY; without even the implied warranty of
18   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   GNU General Public License for more details.
20 
21   To get a copy of the GNU General Public License, write to the Free Software
22   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 
24 */
25 
26 
27 #include "config.h"
28 #include "global.h"
29 #include "utils.h"
30 
31 #include "FWBSettings.h"
32 #include "FWBTree.h"
33 #include "FWObjectDrag.h"
34 #include "FWObjectPropertiesFactory.h"
35 #include "FWWindow.h"
36 #include "IconSetter.h"
37 #include "ObjectManipulator.h"
38 #include "ObjectTreeView.h"
39 #include "ObjectTreeViewItem.h"
40 #include "ProjectPanel.h"
41 
42 #include "fwbuilder/FWObject.h"
43 #include "fwbuilder/Firewall.h"
44 #include "fwbuilder/Group.h"
45 #include "fwbuilder/Interface.h"
46 #include "fwbuilder/NAT.h"
47 #include "fwbuilder/Routing.h"
48 #include "fwbuilder/Policy.h"
49 #include "fwbuilder/Resources.h"
50 #include "fwbuilder/TCPUDPService.h"
51 #include "fwbuilder/ServiceGroup.h"
52 #include "fwbuilder/FWServiceReference.h"
53 #include "fwbuilder/AddressRange.h"
54 #include "fwbuilder/Network.h"
55 
56 #include <QAbstractItemView>
57 #include <QBitmap>
58 #include <QDragEnterEvent>
59 #include <QDragLeaveEvent>
60 #include <QDragMoveEvent>
61 #include <QDropEvent>
62 #include <QFocusEvent>
63 #include <QHeaderView>
64 #include <QKeyEvent>
65 #include <QMimeData>
66 #include <QMouseEvent>
67 #include <QPixmap>
68 #include <QStyle>
69 #include <QStyleOption>
70 #include <QtDebug>
71 #include <qpainter.h>
72 #include <qpixmapcache.h>
73 
74 #include <iostream>
75 #include <algorithm>
76 
77 using namespace std;
78 using namespace libfwbuilder;
79 
80 /****************************************************************************
81  *
82  *    class ObjectTreeView
83  *
84  ****************************************************************************/
85 
ObjectTreeView(ProjectPanel * project,QWidget * parent,const char * name,Qt::WFlags f)86 ObjectTreeView::ObjectTreeView(ProjectPanel* project,
87                                QWidget* parent,
88                                const char * name,
89                                #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
90                                Qt::WFlags f) :
91                                #else
92                                Qt::WindowFlags f) :
93                                #endif
94     QTreeWidget(parent),
95     m_project(project)
96 {
97     setObjectName(name);
98     this->setParent(parent, f);
99     setFont(st->getTreeFont());
100 
101     QPalette updated_palette = palette();
102     updated_palette.setColor(
103         QPalette::Inactive,
104         QPalette::Highlight,
105         QColor("silver"));
106 
107 //        palette().color(QPalette::Highlight).lighter(300));
108 
109     setPalette(updated_palette);
110 
111     setExpandsOnDoubleClick(false);
112 
113     setDragEnabled(true);
114     item_before_drag_started=NULL;
115     lastSelected = NULL;
116     second_click = false;
117     selectionFrozen = false;
118     expandOrCollapse = false;
119     Lockable = false;
120     Unlockable = false;
121     visible = false;
122     /*
123      * note about process_mouse_release_event
124      *
125      * we use mouseReleaseEvent event to switch object opened in the
126      * editor panel (i.e. we open new object when mouse button is
127      * released rather than when it is pressed). This allows us to
128      * start drag without switching object in the editor. The problem
129      * is that mouseReleaseEvent is received in this widget after the
130      * d&d ends with a drop somewhere else, which triggers call to
131      * mouseReleaseEvent which switches object in the
132      * editor. This is undesired when the editor shows a group and we
133      * try to drag and drop an object into that group. Flag
134      * process_mouse_release_event is used to suppress object
135      * switching when mouseReleaseEvent is called after
136      * successfull drop.
137      */
138     process_mouse_release_event = true;
139 
140     connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
141             this, SLOT(currentItemChanged(QTreeWidgetItem*)));
142 
143     connect(this, SIGNAL(itemSelectionChanged()),
144             this, SLOT(itemSelectionChanged()));
145 
146     connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)),
147             this, SLOT(itemCollapsed(QTreeWidgetItem*)));
148 
149     connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)),
150             this, SLOT(itemExpanded(QTreeWidgetItem*)));
151 
152     QStringList qsl;
153     qsl.push_back(tr("Object"));
154     qsl.push_back(tr("Attributes"));
155     setHeaderLabels(qsl);
156 
157     //header()->hide();
158 
159     header()->setDefaultAlignment(Qt::AlignLeft);
160 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
161     header()->setResizeMode(QHeaderView::Interactive);
162 #else
163     header()->setSectionResizeMode(QHeaderView::Interactive);
164 #endif
165 
166     showOrHideAttributesColumn();
167 
168     setMinimumSize( QSize( 100, 0 ) );
169 
170     setAutoScroll(true);
171     setAutoScrollMargin(50);
172     setAllColumnsShowFocus( true );
173     setSelectionMode( ExtendedSelection );
174     setAcceptDrops( true );
175     setDragDropMode( QAbstractItemView::DragDrop );
176     setRootIsDecorated( true );
177 
178     setFocusPolicy(Qt::StrongFocus);
179 
180     connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this,
181             SLOT(updateFilter()));
182 
183 // disable sorting, otherwise gui crashes when built with
184 // QT 4.3.4 (discovered on Ubuntu Hardy). Crash happened when
185 // second object was added to any branch of the tree.
186 //
187 // This causes crash with Qt 4.6 as well
188 //
189 //    setSortingEnabled(true);
190 
191 }
192 
paintEvent(QPaintEvent * ev)193 void ObjectTreeView::paintEvent(QPaintEvent *ev)
194 {
195     QTreeWidget::paintEvent(ev);
196 }
197 
drawRow(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const198 void ObjectTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option,
199                              const QModelIndex &index ) const
200 {
201     // qDebug() << "ObjectTreeView::drawRow"
202     //          << "QStyleOptionViewItem.state=" << int(option.state)
203     //          << "hasFocus()=" << hasFocus()
204     //          << "isActiveWindow()=" << isActiveWindow();
205 
206     // QWidget *fw = QApplication::focusWidget();
207     // qDebug() << "Currently has focus:" << fw;
208 
209     // this is a patch for #2475
210     // Looks like the state remains State_Active even when the tree view widget
211     // loses focus (as long as parent window is active).
212     QStyleOptionViewItem new_opt = option;
213     if ( ! hasFocus()) new_opt.state &= ~QStyle::State_Active;
214     QTreeWidget::drawRow(painter, new_opt, index);
215 }
216 
event(QEvent * event)217 bool ObjectTreeView::event( QEvent *event )
218 {
219     if (event->type() == QEvent::ToolTip)
220     {
221         QHelpEvent *he = (QHelpEvent*) event;
222         QPoint pos = he->pos();
223 
224         if (st->getObjTooltips())
225         {
226             int cx = pos.x(), cy = pos.y();
227 
228             FWObject  *obj=NULL;
229             QRect      cr;
230 
231             QTreeWidgetItem *itm = itemAt(QPoint(cx, cy - header()->height()));
232             if (itm==NULL) return false;
233             ObjectTreeViewItem *oivi  = dynamic_cast<ObjectTreeViewItem*>(itm);
234             assert(oivi!=NULL);
235             obj = oivi->getFWObject();
236 
237             if (obj==NULL) return false;
238 
239             if (obj->getId() == FWObjectDatabase::ANY_ADDRESS_ID  ||
240                 obj->getId() == FWObjectDatabase::ANY_SERVICE_ID  ||
241                 obj->getId() == FWObjectDatabase::ANY_INTERVAL_ID ||
242                 obj->getId() == FWObjectDatabase::DUMMY_ADDRESS_ID ||
243                 obj->getId() == FWObjectDatabase::DUMMY_SERVICE_ID ||
244                 obj->getId() == FWObjectDatabase::DUMMY_INTERFACE_ID)
245                 return false;
246 
247             cr = visualItemRect(itm);
248 
249             QRect global = QRect(
250                 viewport()->mapToGlobal(cr.topLeft()),
251                 viewport()->mapToGlobal(cr.bottomRight()));
252 
253             //finally stretch rect up to component's width and even more
254             //(it fixes bug with horizontal scroll)
255             global.setWidth(width() + horizontalOffset());
256 
257             QToolTip::showText(mapToGlobal( he->pos() ),
258                 FWObjectPropertiesFactory::getObjectPropertiesDetailed(obj,
259                                                                        true,
260                                                                        true),
261                 this, global);
262         }
263 
264         return true;
265     }
266 
267     return QTreeWidget::event(event);
268 }
269 
currentItemChanged(QTreeWidgetItem *)270 void ObjectTreeView::currentItemChanged(QTreeWidgetItem*)
271 {
272     expandOrCollapse = false;
273 }
274 
itemCollapsed(QTreeWidgetItem * itm)275 void ObjectTreeView::itemCollapsed(QTreeWidgetItem* itm)
276 {
277     expandOrCollapse = true;
278 
279     ObjectTreeViewItem *otvi = dynamic_cast<ObjectTreeViewItem*>(itm);
280     assert(otvi!=NULL);
281     FWObject *o = otvi->getFWObject();
282     if (o)
283     {
284         int id = o->getId();
285         expanded_objects.erase(id);
286     }
287 }
288 
itemExpanded(QTreeWidgetItem * itm)289 void ObjectTreeView::itemExpanded(QTreeWidgetItem* itm)
290 {
291     expandOrCollapse = true;
292 
293     ObjectTreeViewItem *otvi=dynamic_cast<ObjectTreeViewItem*>(itm);
294     assert(otvi!=NULL);
295     FWObject *o = otvi->getFWObject();
296     if (o)
297     {
298         int id = o->getId();
299         expanded_objects.insert(id);
300     }
301 }
302 
303 /*
304  * This method makes list selectedObjects flat. If user selects
305  * several objects in the tree, and some of them have children, QT
306  * puts all the children in the selected objects list even if
307  * corresponding subtrees are collapsed. This method eliminates these
308  * selected children objects.
309  *
310  */
getSimplifiedSelection()311 std::vector<libfwbuilder::FWObject*> ObjectTreeView::getSimplifiedSelection()
312 {
313     vector<FWObject*> so  = selectedObjects;
314     vector<FWObject*> so2 = selectedObjects;
315     for (vector<FWObject*>::iterator i=so2.begin();  i!=so2.end(); ++i)
316     {
317         for (vector<FWObject*>::iterator j=i;  j!=so2.end(); ++j)
318         {
319             vector<FWObject*>::iterator k=std::find(so.begin(),so.end(),*j);
320             if ( (*j)->isChildOf( *i ) && k!=so.end())
321                 so.erase( k );
322         }
323     }
324     return so;
325 }
326 
getCurrentObject()327 FWObject* ObjectTreeView::getCurrentObject()
328 {
329     QTreeWidgetItem *ovi = currentItem();
330     ObjectTreeViewItem *otvi=dynamic_cast<ObjectTreeViewItem*>(ovi);
331     if (otvi==NULL) return NULL;
332     return otvi->getFWObject();
333 }
334 
focusInEvent(QFocusEvent * ev)335 void ObjectTreeView::focusInEvent(QFocusEvent* ev)
336 {
337     QTreeWidget::focusInEvent(ev);
338     QTreeWidgetItem *ci = currentItem();
339     if (ci) repaint();
340 }
341 
focusOutEvent(QFocusEvent * ev)342 void ObjectTreeView::focusOutEvent(QFocusEvent* ev)
343 {
344     QTreeWidget::focusOutEvent(ev);
345     QTreeWidgetItem *ci = currentItem();
346     if (ci) repaint();
347 }
348 
updateTreeIcons()349 void ObjectTreeView::updateTreeIcons()
350 {
351     QTreeWidgetItemIterator it(this);
352     for ( ; *it; ++it)
353     {
354         QTreeWidgetItem *itm = *it;
355         ObjectTreeViewItem *otvi = dynamic_cast<ObjectTreeViewItem*>(itm);
356         FWObject *obj = otvi->getFWObject();
357 
358         /* We can have obj==0 if it's a user-create subfolder */
359         if (obj == 0) continue;
360 
361         QPixmap pm_obj;
362         IconSetter::setObjectIcon(obj, &pm_obj, 0);
363         itm->setIcon(0, pm_obj );
364     }
365     update();
366 }
367 
startDrag(Qt::DropActions supportedActions)368 void ObjectTreeView::startDrag(Qt::DropActions supportedActions)
369 {
370     QTreeWidgetItem *ovi = currentItem();
371     if (ovi==NULL) return;
372 
373     FWObject *current_obj = getCurrentObject();
374 
375     /* User-defined folders can't be dragged */
376     if (current_obj == 0) return;
377 
378     if (fwbdebug) qDebug("ObjectTreeView::startDrag: this: %p current_obj: %s",
379                          this, current_obj->getName().c_str());
380 
381 /* can't drag system folders
382 
383     in fact, I have to allow to drag system folders because otherwise
384     QListView triggers highlighting of objects in the tree when user
385     drags mouse cursor across them. This is weird behavior and there
386     does not seem to be any way to turn it off. It happens close to
387     the end of void QListView::mouseMoveEvent( QMouseEvent * e)
388     (See code after they decided that they do not need to call startDrag())
389 
390     if (FWBTree().isSystem(obj)) return NULL;
391 */
392     if ((current_obj->getId() == FWObjectDatabase::DUMMY_ADDRESS_ID)   ||
393         (current_obj->getId() == FWObjectDatabase::DUMMY_INTERFACE_ID) ||
394         (current_obj->getId() == FWObjectDatabase::DUMMY_SERVICE_ID))
395         return;
396 
397     QString icn = (":/Icons/"+current_obj->getTypeName()+"/icon-ref").c_str();
398 
399     vector<FWObject*> so = getSimplifiedSelection();
400 
401     list<FWObject*> dragobj;
402     for (vector<FWObject*>::iterator v=so.begin(); v!=so.end(); v++)
403     {
404         //m_project->check4Depends(*v, dragobj);
405 
406         if (fwbdebug)
407             qDebug("ObjectTreeView::startDrag: adding object to drag list: %s",
408                    (*v)->getName().c_str());
409 
410         // while obj is still part of the tree, do some clean up
411         // to avoid problems in the future.  Create
412         // InterfaceOptions objects for interfaces because we'll
413         // need them for various validations during paste/drop
414         // operation.
415         Interface *intf = Interface::cast(*v);
416         if (intf) intf->getOptionsObject();
417 
418         dragobj.push_back( *v );
419     }
420     FWObjectDrag *drag = new FWObjectDrag(dragobj, this);
421 
422     QPixmap pm;
423     if ( ! QPixmapCache::find( icn, pm) )
424     {
425         pm.load( icn );
426         QPixmapCache::insert( icn, pm);
427     }
428 
429     if (dragobj.size()>1)
430     {
431         QPixmap npm(32,32);
432         QPainter p( &npm );
433         p.fillRect( 0,0,32,32, QBrush( QColor("white"),Qt::SolidPattern ) );
434         p.setBackgroundMode( Qt::TransparentMode );
435         p.drawPixmap( 0, 32-pm.rect().height(), pm);
436         p.setPen( QColor("red") );
437         p.setBrush( QBrush( QColor("red"),Qt::SolidPattern ) );
438         p.drawPie( 16, 0, 16,16, 0, 5760 );
439         QString txt;
440         txt.setNum(dragobj.size());
441         QRect br=p.boundingRect(0, 0, 1000, 1000,
442                                 Qt::AlignLeft|Qt::AlignVCenter,
443                                 txt );
444         p.setPen( QColor("white") );
445         p.drawText( 24-br.width()/2 , 4+br.height()/2, txt );
446         p.end();
447         npm.setMask( npm.createHeuristicMask() );
448         drag->setPixmap( npm );
449     } else
450         drag->setPixmap( pm );
451 
452 /*
453  * This fragment returns selection in the tree back to the object that
454  * was selected before drag operation has started. This help in the
455  * following case:
456  *
457  *  - open a group for editing (group is selected in the tree)
458  *  - left-click on another object in the tree, start dragging it
459  *
460  * at this point selection in the tree returns to the group, so when
461  * user finishes d&d operation, the selection in the tree is consisten
462  * with object currently opened in the editor panel.
463  *
464  * There is a problem with this however. If user wants to put an
465  * object from a different library into the group, they have to switch
466  * to that library before doing d&d. When they switch, ObjectTree
467  * shown in the left panel becomes different from the tree in which
468  * the group is located. When d&d finishes, the ObjectTree object
469  * receives mouseReleaseEvent event. Since it is not the right
470  * tree object, it can not properly restore selection and choses an
471  * object that was previously opened in that tree, which in turn
472  * changes the object opened in the editor panel. To make things
473  * worse, this event is only delivered to the tree object on Mac OS X.
474  *
475  *
476  */
477     if (fwbdebug) qDebug("ObjectTreeView::dragObject()  this=%p visible=%d",
478                          this,visible);
479 
480     drag->start(supportedActions);
481 }
482 
dragEnterEvent(QDragEnterEvent * ev)483 void ObjectTreeView::dragEnterEvent( QDragEnterEvent *ev)
484 {
485     ev->setAccepted(ev->mimeData()->hasFormat(FWObjectDrag::FWB_MIME_TYPE) );
486     ev->setDropAction(Qt::MoveAction);
487 }
488 
489 
isValidDropTarget(QTreeWidgetItem * item,list<FWObject * > & objs)490 static bool isValidDropTarget(QTreeWidgetItem *item, list<FWObject *> &objs)
491 {
492     ObjectTreeViewItem *dest = dynamic_cast<ObjectTreeViewItem *>(item);
493     if (dest == 0) return false;
494 
495     bool dragIsNoop = true;
496     list<FWObject *>::const_iterator iter;
497     for (iter = objs.begin(); iter != objs.end(); ++iter) {
498         FWObject *dragobj = *iter;
499         assert(dragobj != 0);
500 
501         if (Interface::cast(dragobj) != 0 ||
502             Interface::cast(dragobj->getParent()) != 0 ||
503             Policy::cast(dragobj) != 0 ||
504             NAT::cast(dragobj) != 0 ||
505             Routing::cast(dragobj) != 0) return false;
506 
507         /* See if destination is a user folder */
508         if (dest->getUserFolderParent() != 0) {
509             /* Dragged object has to match parent of user folder */
510             if (dest->getUserFolderParent() != dragobj->getParent()) {
511                 return false;
512             }
513 
514             /* Are we dragging within the same user folder? */
515             if (dest->getUserFolderName() !=
516                 QString::fromUtf8(dragobj->getStr("folder").c_str())) {
517                 dragIsNoop = false;
518             }
519         } else {
520             /* OK to drag onto parent itself, or object that shares parent */
521             if (dragobj->getParent() != dest->getFWObject() &&
522                 dragobj->getParent() != dest->getFWObject()->getParent()) {
523                 return false;
524             }
525 
526             /* Are we dragging to a new place? */
527             if ((FWBTree().isSystem(dest->getFWObject()) &&
528                  dragobj->getStr("folder") != "") ||
529                 (dest->getFWObject()->getStr("folder") !=
530                  dragobj->getStr("folder"))) {
531                 dragIsNoop = false;
532             }
533         }
534     }
535 
536     return !dragIsNoop;
537 }
538 
539 
dragMoveEvent(QDragMoveEvent * ev)540 void ObjectTreeView::dragMoveEvent(QDragMoveEvent *ev)
541 {
542     /* Call the parent so that auto-scrolling works properly */
543     QTreeWidget::dragMoveEvent(ev);
544 
545     list<FWObject*> objs;
546     if (ev->source() != this || !FWObjectDrag::decode(ev, objs) ||
547         !isValidDropTarget(itemAt(ev->pos()), objs)) {
548         ev->setAccepted(false);
549         return;
550     }
551 
552     ev->setDropAction(Qt::MoveAction);
553     ev->setAccepted(true);
554 }
555 
556 
dropEvent(QDropEvent * ev)557 void ObjectTreeView::dropEvent(QDropEvent *ev)
558 {
559     // only accept drops from the same instance of fwbuilder
560     if (ev->source() == NULL) return;
561 
562     ObjectTreeViewItem *dest =
563         dynamic_cast<ObjectTreeViewItem *>(itemAt(ev->pos()));
564     if (dest == 0) {
565     notWanted:
566         ev->setAccepted(false);
567         return;
568     }
569 
570     list<FWObject*> objs;
571     if (!FWObjectDrag::decode(ev, objs)) goto notWanted;
572 
573     /* Make sure the drop event is on an object that can handle it */
574     if (ev->source() != this || !isValidDropTarget(dest, objs)) goto notWanted;
575 
576     emit moveItems_sign(dest, objs);
577     ev->setAccepted(true);
578 }
579 
dragLeaveEvent(QDragLeaveEvent * ev)580 void ObjectTreeView::dragLeaveEvent( QDragLeaveEvent *ev)
581 {
582     QTreeWidget::dragLeaveEvent(ev);
583 
584     clearSelection();
585 }
586 
mouseMoveEvent(QMouseEvent * e)587 void ObjectTreeView::mouseMoveEvent( QMouseEvent * e )
588 {
589     /* This stops highlighting of stuff in the tree when the user
590        clicks and tries to drag something non-draggable. */
591     if (state() == DragSelectingState) return;
592     QTreeWidget::mouseMoveEvent(e);
593     if (e==NULL)  return;
594 }
595 
mousePressEvent(QMouseEvent * e)596 void ObjectTreeView::mousePressEvent( QMouseEvent *e )
597 {
598     if (fwbdebug) qDebug("ObjectTreeView::mousePressEvent");
599 
600     second_click = false;
601     process_mouse_release_event = true;
602 
603     if (fwbdebug)
604     {
605         qDebug() << "ObjectTreeView::mousePressEvent :: currentItem="
606                  << ((currentItem())?currentItem()->text(0):"nil");
607         qDebug() << "ObjectTreeView::mousePressEvent :: lastSelected="
608                  << ((lastSelected)?lastSelected->text(0):"nil");
609     }
610 
611     lastSelected = currentItem();
612 
613     QTreeWidget::mousePressEvent(e);
614 
615     if (e->button() == Qt::LeftButton)
616     {
617         startingDrag = true;
618     }
619 
620     if (e->button() == Qt::RightButton)
621         emit contextMenuRequested_sign(e->pos());
622 }
623 
624 /*
625  * Two modes of operation of this widget:
626  *
627  * 1.  this widget can intercept single mouse click and return
628  * selection back to the object that was current before it. If user
629  * double ckicks mouse button, then this reset is not done and new
630  * object is selected. This is done using timer.
631  *
632  * 2. this widget can act as usual QListView does, that is, select an object
633  * on a single click.
634  *
635  * uncomment the line that starts timer for mode #1.
636  *
637  *
638  * we use mouseReleaseEvent event to switch object opened in the
639  * editor panel (i.e. we open new object when mouse button is released
640  * rather than when it is pressed). This allows us to start drag
641  * without switching object in the editor. The problem is that
642  * mouseReleaseEvent is received in this widget after the d&d ends
643  * with a drop somewhere else, which triggers call to
644  * mouseReleaseEvent which switches object in the editor. This
645  * is undesired when the editor shows a group and we try to drag and
646  * drop an object into that group. Flag process_mouse_release_event is
647  * used to suppress object switching when mouseReleaseEvent is
648  * called after successfull drop.
649  *
650  */
mouseReleaseEvent(QMouseEvent * e)651 void ObjectTreeView::mouseReleaseEvent( QMouseEvent *e )
652 {
653     if (fwbdebug)
654         qDebug("ObjectTreeView::mouseReleaseEvent 1 this=%p  process_mouse_release_event=%d",
655                this,process_mouse_release_event);
656 
657     QTreeWidget::mouseReleaseEvent(e);
658 
659     if (!process_mouse_release_event)
660     {
661         // just do not switch object in the editor, otherwise
662         // process this event as usual
663         process_mouse_release_event = true;
664         return;
665     }
666 
667     if (fwbdebug)
668         qDebug("ObjectTreeView::mouseReleaseEvent 2 selectedObjects.size()=%d getCurrentObject()=%p current object %s",
669                int(selectedObjects.size()),
670                getCurrentObject(),
671                (getCurrentObject()!=NULL)?getCurrentObject()->getName().c_str():"nil");
672 
673     if (expandOrCollapse) return;  // user expanded or collapsed subtree,
674                                    // no need to change object in the editor
675 
676 // Experiment: single click on the object in the tree should not open
677 // it in the editor
678 #if 0
679     if (selectedObjects.size()==1)
680         emit switchObjectInEditor_sign( getCurrentObject() );
681     else
682     {
683         // user selected multiple objects
684         // do not let them if editor has unsaved changes
685         //
686         if (mw->isEditorVisible() && mw->isEditorModified())
687             emit switchObjectInEditor_sign( getCurrentObject() );
688         else
689             mw->blankEditor();
690     }
691 #endif
692 }
693 
694 /*
695  * normally QAbstractItemView::edit starts in-place editing. We use
696  * double click to open object in a separate editor panel
697  */
edit(const QModelIndex & index,EditTrigger trigger,QEvent * event)698 bool ObjectTreeView::edit(const QModelIndex &index,
699                           EditTrigger trigger, QEvent *event)
700 {
701     if (fwbdebug) qDebug("ObjectTreeView::edit");
702     if (trigger==QAbstractItemView::DoubleClicked) editCurrentObject();
703     return QTreeWidget::edit(index, trigger, event);
704 }
705 
706 /*
707  * sends signal that should be connected to a slot in
708  * ObjectManipulator which opens editor panel if it is closed and then
709  * opens current object in it
710  */
editCurrentObject()711 void ObjectTreeView::editCurrentObject()
712 {
713     if (fwbdebug) qDebug("ObjectTreeView::editCurrentObject");
714     emit editCurrentObject_sign();
715     if (fwbdebug) qDebug("ObjectTreeView::editCurrentObject done");
716 }
717 
keyPressEvent(QKeyEvent * ev)718 void ObjectTreeView::keyPressEvent( QKeyEvent* ev )
719 {
720     FWObject *obj = getCurrentObject();
721     if (obj)
722     {
723         if (ev->key()==Qt::Key_Enter || ev->key()==Qt::Key_Return)
724         {
725             if (fwbdebug)
726                 qDebug() << "ObjectTreeView::keyPressEvent Qt::Key_Enter";
727             editCurrentObject();
728             ev->accept();
729             return;
730         }
731         if (ev->key()==Qt::Key_Delete)
732         {
733             emit deleteObject_sign(obj);
734             ev->accept();
735             return;
736         }
737     }
738     QTreeWidget::keyPressEvent(ev);
739 }
740 
keyReleaseEvent(QKeyEvent * ev)741 void ObjectTreeView::keyReleaseEvent( QKeyEvent* ev )
742 {
743     QTreeWidget::keyReleaseEvent(ev);
744 }
745 
itemOpened()746 void ObjectTreeView::itemOpened ()
747 {
748     if (fwbdebug) qDebug("ObjectTreeView::itemOpened");
749     editCurrentObject();
750 }
751 
clearLastSelected()752 void ObjectTreeView::clearLastSelected()
753 {
754     lastSelected = NULL;
755 }
756 
757 
resetSelection()758 void ObjectTreeView::resetSelection()
759 {
760     if (lastSelected)
761     {
762         if (fwbdebug)
763             qDebug() << "ObjectTreeView::resetSelection :: lastSelected="
764                      << lastSelected->text(0);
765 
766         setCurrentItem(lastSelected);
767         lastSelected->setSelected(true);
768     }
769 }
770 
itemSelectionChanged()771 void ObjectTreeView::itemSelectionChanged()
772 {
773     if (fwbdebug)
774         qDebug("ObjectTreeView::itemSelectionChanged selectionFrozen=%d",
775                selectionFrozen);
776 
777     if (selectionFrozen) return;
778 
779 /* in extended selection mode there may be several selected items */
780 
781     selectedObjects.clear();
782 
783     QList<QTreeWidgetItem*> selected = selectedItems();
784     QList<QTreeWidgetItem*>::Iterator it;
785     for (it=selected.begin(); it!=selected.end(); it++)
786     {
787         QTreeWidgetItem *itm = (*it);
788         ObjectTreeViewItem *otvi = dynamic_cast<ObjectTreeViewItem*>(itm);
789 
790         FWObject *obj = otvi->getFWObject();
791         if (obj == 0) continue;
792 
793         selectedObjects.push_back(otvi->getFWObject());
794 
795         if (fwbdebug) qDebug(
796             "ObjectTreeView::selectionChanged: selected otvi=%p object %s",
797             otvi, otvi->getFWObject()->getName().c_str());
798     }
799 
800     if (fwbdebug)
801         qDebug("ObjectTreeView::itemSelectionChanged completed");
802 /* now list  selectedObjects   holds all selected items */
803 }
804 
isSelected(FWObject * obj)805 bool ObjectTreeView::isSelected(FWObject* obj)
806 {
807     for (vector<FWObject*>::iterator i=selectedObjects.begin();
808          i!=selectedObjects.end(); ++i)
809     {
810         if ( (*i)==obj)  return true;
811     }
812     return false;
813 }
814 
getNumSelected()815 int  ObjectTreeView::getNumSelected()
816 {
817     return selectedObjects.size();
818 }
819 
updateAfterPrefEdit()820 void ObjectTreeView::updateAfterPrefEdit()
821 {
822      setFont(st->getTreeFont());
823 }
824 
ExpandTreeItems(const set<int> & ids)825 void ObjectTreeView::ExpandTreeItems(const set<int> &ids)
826 {
827     if (fwbdebug)
828         qDebug() << "ObjectTreeView::ExpandTreeItems()";
829 
830     QTreeWidgetItemIterator it(this);
831     for ( ; *it; ++it)
832     {
833         QTreeWidgetItem *itm = *it;
834         ObjectTreeViewItem *otvi=dynamic_cast<ObjectTreeViewItem*>(itm);
835         FWObject *obj = otvi->getFWObject();
836         if (obj == 0) continue;
837         if (ids.count(obj->getId()))
838             itm->setExpanded(true);
839     }
840 }
841 
showOrHideAttributesColumn()842 void ObjectTreeView::showOrHideAttributesColumn()
843 {
844     if (st->getBool("UI/ShowObjectsAttributesInTree"))
845         setColumnCount(2);
846     else
847         setColumnCount(1);
848 }
849 
resolveChildren(QTreeWidgetItem * parent)850 QSet<QTreeWidgetItem*> ObjectTreeView::resolveChildren(QTreeWidgetItem *parent)
851 {
852     QSet<QTreeWidgetItem*> children;
853     children.insert(parent);
854     if (parent->childCount() == 0) return children;
855     for (int i=0; i<parent->childCount(); i++)
856         children.unite(resolveChildren(parent->child(i)));
857     return children;
858 }
859 
resolveParents(QTreeWidgetItem * child)860 QSet<QTreeWidgetItem*> ObjectTreeView::resolveParents(QTreeWidgetItem *child)
861 {
862     QSet<QTreeWidgetItem*> parents;
863     parents.insert(child);
864     if (child->parent() == NULL) return parents;
865     parents.unite(resolveParents(child->parent()));
866     return parents;
867 }
868 
updateFilter()869 void ObjectTreeView::updateFilter()
870 {
871     if (filter.isEmpty()) return;
872     setFilter(filter);
873 }
874 
filterMatchesPortRange(const QStringList & args,FWObject * obj)875 static bool filterMatchesPortRange(const QStringList &args,
876                                    FWObject *obj)
877 {
878     if (!obj) return false;
879 
880     // We traverse the service group. If the children are references
881     // they may be pointing to ports
882     if (obj->getTypeName() == ServiceGroup::TYPENAME) {
883         for (list<FWObject*>::const_iterator it=obj->begin(); it!=obj->end(); ++it) {
884             FWServiceReference *ref = FWServiceReference::cast(*it);
885             if (ref && filterMatchesPortRange(args, ref->getPointer()))
886                 return true;
887         }
888     }
889 
890     TCPUDPService *service = dynamic_cast<TCPUDPService*>(obj);
891     if (!service) return false;
892 
893     QRegExp rx("\\s*([><]?)\\s*(\\d*)(?:-(\\d*))?");
894 
895     foreach (const QString &arg, args) {
896 
897         if (!rx.exactMatch(arg)) continue;
898 
899         int lowerBound = rx.cap(2).toInt(), upperBound = lowerBound;
900 
901         if (rx.pos(3) != -1) {
902             upperBound = rx.cap(3).toInt();
903         }
904 
905         if (rx.pos(1) != -1) {
906             if (rx.pos(3) != -1) // [><] cannot be combined with range
907                 continue;
908 
909             if (rx.cap(1) == ">") {
910                 upperBound = 65535;
911                 ++lowerBound; // Adjust for using >= below
912             } else {// "<"
913                 lowerBound = 1;
914                 --upperBound; // Adjust for using <= below
915             }
916         }
917 
918         if (lowerBound > upperBound) continue;
919 
920         int ds = service->getDstRangeStart(), de = service->getDstRangeEnd(),
921                 ss = service->getSrcRangeStart(), se = service->getSrcRangeEnd();
922 
923         if (ds && de && (lowerBound <= ds) && (de <= upperBound)) return true;
924         if (ss && se && (lowerBound <= ss) && (se <= upperBound)) return true;
925     } // End foreach
926 
927     return false;
928 }
929 
filterMatchesIpAddress(const QStringList & args,FWObject * obj)930 static bool filterMatchesIpAddress(const QStringList &args,
931                                    FWObject *obj)
932 {
933     if (!obj) return false;
934 
935     // We traverse the object group. If the children are references
936     // they may be pointing to adresses
937     if (obj->getTypeName() == ObjectGroup::TYPENAME) {
938         for (list<FWObject*>::const_iterator it=obj->begin(); it!=obj->end(); ++it) {
939             FWObjectReference *ref = FWObjectReference::cast(*it);
940             if (ref && filterMatchesIpAddress(args, ref->getPointer()))
941                 return true;
942         }
943     }
944 
945     Address *addr = dynamic_cast<Address*>(obj);
946     if (!addr) return false;
947 
948     QRegExp rx("\\s*([.:0-9a-fA-F]+)(?:/([.:0-9a-fA-F]+))?");
949 
950     InetAddrMask searchAddrAndMask;
951     foreach (const QString &arg, args) {
952 
953         if (!rx.exactMatch(arg)) continue;
954 
955         try {
956             std::string netmask = rx.cap(2).isEmpty() ? "32" : rx.cap(2).toStdString();
957             InetAddr ipv4addr(rx.cap(1).toStdString());
958             InetAddr ipv4mask(netmask);
959             searchAddrAndMask = InetAddrMask(ipv4addr, ipv4mask);
960         } catch (const FWException) { // Could not create IPv4 object. Trying IPv6.
961             try {
962                 int netmask = rx.cap(2).isEmpty() ? 128 : rx.cap(2).toInt();
963                 InetAddr ipv6addr(AF_INET6, rx.cap(1).toStdString());
964                 InetAddr ipv6mask(AF_INET6, netmask);
965                 searchAddrAndMask = InetAddrMask(ipv6addr, ipv6mask);
966             } catch (const FWException) { // Could not create IPv6 object.
967                 // User did not submit a valid IP address
968                 return false;
969             }
970         }
971 
972         const InetAddr *searchAddr = searchAddrAndMask.getAddressPtr();
973 
974         if (addr->getTypeName() == AddressRange::TYPENAME) {
975             AddressRange *addrRange = dynamic_cast<AddressRange*>(obj);
976             if (addrRange
977                 && (searchAddr->addressFamily() == addrRange->getRangeStart().addressFamily()) ) {
978 
979                 if ( !(searchAddr->opLT(addrRange->getRangeStart()))
980                      && !(searchAddr->opGT(addrRange->getRangeEnd())) )
981                     return true;
982             }
983             continue; // Next argument
984         }
985 
986         const InetAddr *inetAddr = addr->getAddressPtr();
987 
988 
989         if ( inetAddr && (inetAddr->addressFamily() == searchAddr->addressFamily()) ) {
990             if (addr->getTypeName() == Network::TYPENAME) {
991                 if (addr->belongs(*searchAddr))
992                     return true;
993             }
994             if (searchAddrAndMask.belongs(*inetAddr))
995                 return true;
996         }
997     } // End foreach
998 
999     return false;
1000 }
1001 
filterMatchesCommand(const QString & text,ObjectTreeViewItem * item)1002 static bool filterMatchesCommand(const QString &text,
1003                                  ObjectTreeViewItem *item)
1004 {
1005     QRegExp rx("(?:(port)|(ip)):(.*)", Qt::CaseInsensitive);
1006     if (!rx.exactMatch(text)) return false;
1007 
1008     QStringList args = rx.cap(3).split(",", QString::SkipEmptyParts);
1009 
1010     if (rx.pos(1) != -1)
1011         return (filterMatchesPortRange(args, item->getFWObject()));
1012     else
1013         return (filterMatchesIpAddress(args, item->getFWObject()));
1014 }
1015 
filterMatches(const QString & text,ObjectTreeViewItem * item)1016 static bool filterMatches(const QString &text,
1017                           ObjectTreeViewItem *item)
1018 {
1019     if (text.isEmpty()) return true;
1020     if (item->text(0).contains(text, Qt::CaseInsensitive)) return true;
1021 
1022     // Support for port and ip search
1023     if (filterMatchesCommand(text, item)) return true;
1024 
1025     if (item->getUserFolderParent() != 0) return false;
1026     FWObject *obj = item->getFWObject();
1027 
1028     QByteArray utf8 = text.toUtf8();
1029     set<string> keys = obj->getKeywords();
1030     set<string>::const_iterator iter;
1031     for (iter = keys.begin(); iter != keys.end(); ++iter) {
1032         QString keyword = QString::fromUtf8((*iter).c_str());
1033         if (keyword.contains(text, Qt::CaseInsensitive)) return true;
1034     }
1035 
1036     return false;
1037 }
1038 
qHash(const QStringList & list)1039 static uint qHash(const QStringList &list)
1040 {
1041     uint ret = 0;
1042     for (int ii = 0; ii < list.size(); ii++) {
1043         ret += qHash(list.at(ii));
1044     }
1045     return ret;
1046 }
1047 
doExpandedState(bool save,QStringList & list,QTreeWidgetItem * item)1048 void ObjectTreeView::doExpandedState(bool save, QStringList &list,
1049                                      QTreeWidgetItem *item)
1050 {
1051     list.append(item->text(0));
1052     if (save) {
1053         if (item->isExpanded()) expandedState.insert(list);
1054     } else {
1055         if (expandedState.contains(list)) item->setExpanded(true);
1056     }
1057 
1058     for (int ii = 0; ii < item->childCount(); ii++) {
1059         doExpandedState(save, list, item->child(ii));
1060     }
1061     list.removeLast();
1062 }
1063 
1064 
setFilter(QString text)1065 void ObjectTreeView::setFilter(QString text)
1066 {
1067     if (filter.isEmpty() && !text.isEmpty()) {
1068         QStringList list;
1069         for (int ii = 0; ii < topLevelItemCount(); ii++) {
1070             doExpandedState(true, list, topLevelItem(ii));
1071         }
1072     } else if (text.isEmpty() && !filter.isEmpty()) {
1073         QStringList list;
1074         for (int ii = 0; ii < topLevelItemCount(); ii++) {
1075             doExpandedState(false, list, topLevelItem(ii));
1076         }
1077         expandedState.clear();
1078     }
1079     filter = text;
1080 
1081     if (fwbdebug)
1082         qDebug() << "ObjectTreeView::setFilter " << text;
1083 
1084     list<QTreeWidgetItem *> expand;
1085     for (QTreeWidgetItemIterator wit(this); *wit; ++wit) {
1086         ObjectTreeViewItem *otvi = dynamic_cast<ObjectTreeViewItem *>(*wit);
1087 
1088         if (filterMatches(text, otvi)) {
1089             (*wit)->setHidden(false);
1090 
1091             if (Firewall::cast(otvi->getFWObject()) != 0) {
1092                 expand.push_back(otvi);
1093             }
1094 
1095             QTreeWidgetItem *parent = (*wit)->parent();
1096             while (parent != 0) {
1097                 parent->setHidden(false);
1098                 parent = parent->parent();
1099             }
1100         } else {
1101             (*wit)->setHidden(true);
1102         }
1103     }
1104 
1105     list<QTreeWidgetItem *>::const_iterator iter;
1106     for (iter = expand.begin(); iter != expand.end(); ++iter) {
1107         QTreeWidgetItem *item = *iter;
1108         item->setHidden(false);
1109         for (int ii = 0; ii < item->childCount(); ii++) {
1110             expand.push_back(item->child(ii));
1111         }
1112     }
1113 
1114     if (!text.isEmpty()) this->expandAll();
1115 }
1116