Yell at the user less.
[metze/wireshark/wip.git] / ui / qt / interface_tree_model.cpp
1 /* interface_tree_model.cpp
2  * Model for the interface data for display in the interface frame
3  *
4  * Wireshark - Network traffic analyzer
5  * By Gerald Combs <gerald@wireshark.org>
6  * Copyright 1998 Gerald Combs
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22
23 #include "config.h"
24
25 #include "interface_tree_model.h"
26
27 #ifdef HAVE_LIBPCAP
28 #include "ui/capture.h"
29 #include "caputils/capture-pcap-util.h"
30 #include "capture_opts.h"
31 #include "ui/capture_ui_utils.h"
32 #include "ui/capture_globals.h"
33 #endif
34
35 #include "wsutil/filesystem.h"
36
37 #include "qt_ui_utils.h"
38 #include "stock_icon.h"
39 #include "wireshark_application.h"
40
41 /* Needed for the meta type declaration of QList<int>* */
42 #include "sparkline_delegate.h"
43
44 #ifdef HAVE_EXTCAP
45 #include "extcap.h"
46 #endif
47
48 const QString InterfaceTreeModel::DefaultNumericValue = QObject::tr("default");
49
50 /**
51  * This is the data model for interface trees. It implies, that the index within
52  * global_capture_opts.all_ifaces is identical to the row. This is always the case, even
53  * when interfaces are hidden by the proxy model. But for this to work, every access
54  * to the index from within the view, has to be filtered through the proxy model.
55  */
56 InterfaceTreeModel::InterfaceTreeModel(QObject *parent) :
57     QAbstractTableModel(parent)
58 #ifdef HAVE_LIBPCAP
59     ,stat_cache_(NULL)
60 #endif
61 {
62     connect(wsApp, SIGNAL(appInitialized()), this, SLOT(interfaceListChanged()));
63     connect(wsApp, SIGNAL(localInterfaceListChanged()), this, SLOT(interfaceListChanged()));
64 }
65
66 InterfaceTreeModel::~InterfaceTreeModel(void)
67 {
68 #ifdef HAVE_LIBPCAP
69     if (stat_cache_) {
70         capture_stat_stop(stat_cache_);
71         stat_cache_ = NULL;
72     }
73 #endif // HAVE_LIBPCAP
74 }
75
76 QString InterfaceTreeModel::interfaceError()
77 {
78     QString errorText;
79     if ( rowCount() == 0 )
80     {
81         errorText = tr("No Interfaces found.");
82     }
83 #ifdef HAVE_LIBPCAP
84     else if ( global_capture_opts.ifaces_err != 0 )
85     {
86         errorText = tr(global_capture_opts.ifaces_err_info);
87     }
88 #endif
89
90     return errorText;
91 }
92
93 int InterfaceTreeModel::rowCount(const QModelIndex & ) const
94 {
95 #ifdef HAVE_LIBPCAP
96     return (global_capture_opts.all_ifaces ? global_capture_opts.all_ifaces->len : 0);
97 #else
98     /* Currently no interfaces available for libpcap-less builds */
99     return 0;
100 #endif
101 }
102
103 int InterfaceTreeModel::columnCount(const QModelIndex & ) const
104 {
105     /* IFTREE_COL_MAX is not being displayed, it is the definition for the maximum numbers of columns */
106     return ((int) IFTREE_COL_MAX);
107 }
108
109 QVariant InterfaceTreeModel::data(const QModelIndex &index, int role) const
110 {
111 #ifdef HAVE_LIBPCAP
112     bool interfacesLoaded = true;
113     if ( ! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len == 0 )
114         interfacesLoaded = false;
115
116     if ( !index.isValid() )
117         return QVariant();
118
119     int row = index.row();
120     InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
121
122     if ( interfacesLoaded )
123     {
124         interface_t device = g_array_index(global_capture_opts.all_ifaces, interface_t, row);
125
126         /* Data for display in cell */
127         if ( role == Qt::DisplayRole )
128         {
129             /* Only the name is being displayed */
130             if ( col == IFTREE_COL_NAME )
131             {
132                 return QString(device.display_name);
133             }
134             else if ( col == IFTREE_COL_INTERFACE_NAME )
135             {
136                 return QString(device.name);
137             }
138             else if ( col == IFTREE_COL_PIPE_PATH )
139             {
140                 return QString(device.if_info.name);
141             }
142             else if ( col == IFTREE_COL_CAPTURE_FILTER )
143             {
144                 if ( device.cfilter && strlen(device.cfilter) > 0 )
145                     return html_escape(QString(device.cfilter));
146             }
147 #ifdef HAVE_EXTCAP
148             else if ( col == IFTREE_COL_EXTCAP_PATH )
149             {
150                 return QString(device.if_info.extcap);
151             }
152 #endif
153             else if ( col == IFTREE_COL_SNAPLEN )
154             {
155                 return device.has_snaplen ? QString::number(device.snaplen) : DefaultNumericValue;
156             }
157 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
158             else if ( col == IFTREE_COL_BUFFERLEN )
159             {
160                 return QString::number(device.buffer);
161             }
162 #endif
163             else if ( col == IFTREE_COL_TYPE )
164             {
165                 return QVariant::fromValue((int)device.if_info.type);
166             }
167             else if ( col == IFTREE_COL_INTERFACE_COMMENT )
168             {
169                 QString comment = gchar_free_to_qstring(capture_dev_user_descr_find(device.name));
170                 if ( comment.length() > 0 )
171                     return comment;
172                 else
173                     return QString(device.if_info.vendor_description);
174             }
175             else if ( col == IFTREE_COL_DLT )
176             {
177                 QString linkname = QObject::tr("DLT %1").arg(device.active_dlt);
178                 for (GList *list = device.links; list != NULL; list = g_list_next(list)) {
179                     link_row *linkr = (link_row*)(list->data);
180                     if (linkr->dlt != -1 && linkr->dlt == device.active_dlt) {
181                         linkname = linkr->name;
182                         break;
183                     }
184                 }
185
186                 return linkname;
187             }
188             else
189             {
190                 /* Return empty string for every other DisplayRole */
191                 return QVariant();
192             }
193         }
194         else if ( role == Qt::CheckStateRole )
195         {
196             if ( col == IFTREE_COL_HIDDEN )
197             {
198                 /* Hidden is a de-selection, therefore inverted logic here */
199                 return device.hidden ? Qt::Unchecked : Qt::Checked;
200             }
201             else if ( col == IFTREE_COL_PROMISCUOUSMODE )
202             {
203                 return device.pmode ? Qt::Checked : Qt::Unchecked;
204             }
205 #ifdef HAVE_PCAP_CREATE
206             else if ( col == IFTREE_COL_MONITOR_MODE )
207             {
208                 return device.monitor_mode_enabled ? Qt::Checked : Qt::Unchecked;
209             }
210 #endif
211         }
212         /* Used by SparkLineDelegate for loading the data for the statistics line */
213         else if ( role == Qt::UserRole )
214         {
215             if ( col == IFTREE_COL_STATS )
216             {
217                 if ( points.contains(device.name) )
218                     return qVariantFromValue(points[device.name]);
219             }
220             else if ( col == IFTREE_COL_HIDDEN )
221             {
222                 return QVariant::fromValue((bool)device.hidden);
223             }
224         }
225 #ifdef HAVE_EXTCAP
226         /* Displays the configuration icon for extcap interfaces */
227         else if ( role == Qt::DecorationRole )
228         {
229             if ( col == IFTREE_COL_EXTCAP )
230             {
231                 if ( device.if_info.type == IF_EXTCAP )
232                     return QIcon(StockIcon("x-capture-options"));
233             }
234         }
235         else if ( role == Qt::TextAlignmentRole )
236         {
237             if ( col == IFTREE_COL_EXTCAP )
238             {
239                 return Qt::AlignRight;
240             }
241         }
242 #endif
243         /* Displays the tooltip for each row */
244         else if ( role == Qt::ToolTipRole )
245         {
246             return toolTipForInterface(row);
247         }
248     }
249 #else
250     Q_UNUSED(index);
251     Q_UNUSED(role);
252 #endif
253
254     return QVariant();
255 }
256
257 QVariant InterfaceTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
258 {
259     if ( orientation == Qt::Horizontal )
260     {
261         if ( role == Qt::DisplayRole )
262         {
263             if ( section == IFTREE_COL_HIDDEN )
264             {
265                 return tr("Show");
266             }
267             else if ( section == IFTREE_COL_INTERFACE_NAME )
268             {
269                 return tr("Friendly Name");
270             }
271             else if ( section == IFTREE_COL_NAME )
272             {
273                 return tr("Interface Name");
274             }
275             else if ( section == IFTREE_COL_PIPE_PATH )
276             {
277                 return tr("Local Pipe Path");
278             }
279             else if ( section == IFTREE_COL_INTERFACE_COMMENT )
280             {
281                 return tr("Comment");
282             }
283             else if ( section == IFTREE_COL_DLT )
284             {
285                 return tr("Link-Layer Header");
286             }
287             else if ( section == IFTREE_COL_PROMISCUOUSMODE )
288             {
289                 return tr("Promiscuous");
290             }
291             else if ( section == IFTREE_COL_SNAPLEN )
292             {
293                 return tr("Snaplen (B)");
294             }
295 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
296             else if ( section == IFTREE_COL_BUFFERLEN )
297             {
298                 return tr("Buffer (MB)");
299             }
300 #endif
301 #ifdef HAVE_PCAP_CREATE
302             else if ( section == IFTREE_COL_MONITOR_MODE )
303             {
304                 return tr("Monitor Mode");
305             }
306 #endif
307             else if ( section == IFTREE_COL_CAPTURE_FILTER )
308             {
309                 return tr("Capture Filter");
310             }
311         }
312     }
313
314     return QVariant();
315 }
316
317 QVariant InterfaceTreeModel::getColumnContent(int idx, int col, int role)
318 {
319     return InterfaceTreeModel::data(index(idx, col), role);
320 }
321
322 #ifdef HAVE_PCAP_REMOTE
323 bool InterfaceTreeModel::isRemote(int idx)
324 {
325     interface_t device = g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
326     if ( device.remote_opts.src_type == CAPTURE_IFREMOTE )
327         return true;
328     return false;
329 }
330 #endif
331
332 /**
333  * The interface list has changed. global_capture_opts.all_ifaces may have been reloaded
334  * or changed with current data. beginResetModel() and endResetModel() will signalize the
335  * proxy model and the view, that the data has changed and the view has to reload
336  */
337 void InterfaceTreeModel::interfaceListChanged()
338 {
339     emit beginResetModel();
340
341     points.clear();
342
343     emit endResetModel();
344 }
345
346 /*
347  * Displays the tooltip code for the given device index.
348  */
349 QVariant InterfaceTreeModel::toolTipForInterface(int idx) const
350 {
351 #ifdef HAVE_LIBPCAP
352     if ( ! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx)
353         return QVariant();
354
355     interface_t device = g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
356
357     QString tt_str = "<p>";
358     if ( device.no_addresses > 0 )
359     {
360         tt_str += QString("%1: %2")
361                 .arg(device.no_addresses > 1 ? tr("Addresses") : tr("Address"))
362                 .arg(html_escape(device.addresses))
363                 .replace('\n', ", ");
364     }
365 #ifdef HAVE_EXTCAP
366     else if ( device.if_info.type == IF_EXTCAP )
367     {
368         tt_str = QString(tr("Extcap interface: %1")).arg(get_basename(device.if_info.extcap));
369     }
370 #endif
371     else
372     {
373         tt_str = tr("No addresses");
374     }
375     tt_str += "<br/>";
376
377     QString cfilter = device.cfilter;
378     if ( cfilter.isEmpty() )
379     {
380         tt_str += tr("No capture filter");
381     }
382     else
383     {
384         tt_str += QString("%1: %2")
385                 .arg(tr("Capture filter"))
386                 .arg(html_escape(cfilter));
387     }
388     tt_str += "</p>";
389
390     return tt_str;
391 #else
392     Q_UNUSED(idx);
393
394     return QVariant();
395 #endif
396 }
397
398 #ifdef HAVE_LIBPCAP
399 void InterfaceTreeModel::stopStatistic()
400 {
401     if ( stat_cache_ )
402     {
403         capture_stat_stop(stat_cache_);
404         stat_cache_ = NULL;
405     }
406 }
407 #endif
408
409 void InterfaceTreeModel::updateStatistic(unsigned int idx)
410 {
411 #ifdef HAVE_LIBPCAP
412     guint diff;
413     if ( ! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx )
414         return;
415
416     interface_t device = g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
417
418     if ( device.if_info.type == IF_PIPE )
419         return;
420
421     if ( !stat_cache_ )
422     {
423         // Start gathering statistics using dumpcap
424         // We crash (on OS X at least) if we try to do this from ::showEvent.
425         stat_cache_ = capture_stat_start(&global_capture_opts);
426     }
427     if ( !stat_cache_ )
428         return;
429
430     struct pcap_stat stats;
431
432     diff = 0;
433     if ( capture_stats(stat_cache_, device.name, &stats) )
434     {
435         if ( (int)(stats.ps_recv - device.last_packets) >= 0 )
436         {
437             diff = stats.ps_recv - device.last_packets;
438             device.packet_diff = diff;
439         }
440         device.last_packets = stats.ps_recv;
441
442         global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, idx);
443         g_array_insert_val(global_capture_opts.all_ifaces, idx, device);
444     }
445
446     points[device.name].append(diff);
447     emit dataChanged(index(idx, IFTREE_COL_STATS), index(idx, IFTREE_COL_STATS));
448 #else
449     Q_UNUSED(idx);
450 #endif
451 }
452
453 void InterfaceTreeModel::getPoints(int idx, PointList *pts)
454 {
455 #ifdef HAVE_LIBPCAP
456     if ( ! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx )
457         return;
458
459     interface_t device = g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
460     if ( points.contains(device.name) )
461         pts->append(points[device.name]);
462 #else
463     Q_UNUSED(idx);
464     Q_UNUSED(pts);
465 #endif
466 }
467
468 QItemSelection InterfaceTreeModel::selectedDevices()
469 {
470     QItemSelection mySelection;
471 #ifdef HAVE_LIBPCAP
472     for( int idx = 0; idx < rowCount(); idx++ )
473     {
474         interface_t device = g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
475
476         if ( device.selected )
477         {
478             QModelIndex selectIndex = index(idx, 0);
479             mySelection.merge(
480                     QItemSelection( selectIndex, index(selectIndex.row(), columnCount() - 1) ),
481                     QItemSelectionModel::SelectCurrent
482                     );
483         }
484     }
485 #endif
486     return mySelection;
487 }
488
489 bool InterfaceTreeModel::updateSelectedDevices(QItemSelection sourceSelection)
490 {
491     bool selectionHasChanged = false;
492 #ifdef HAVE_LIBPCAP
493     QList<int> selectedIndices;
494
495     QItemSelection::const_iterator it = sourceSelection.constBegin();
496     while(it != sourceSelection.constEnd())
497     {
498         QModelIndexList indeces = ((QItemSelectionRange) (*it)).indexes();
499
500         QModelIndexList::const_iterator cit = indeces.constBegin();
501         while(cit != indeces.constEnd())
502         {
503             QModelIndex index = (QModelIndex) (*cit);
504             if ( ! selectedIndices.contains(index.row()) )
505             {
506                 selectedIndices.append(index.row());
507             }
508             ++cit;
509         }
510         ++it;
511     }
512
513     global_capture_opts.num_selected = 0;
514
515     for ( unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++ )
516     {
517         interface_t device = g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
518         if ( !device.locked )
519         {
520             if ( selectedIndices.contains(idx) )
521             {
522                 if (! device.selected )
523                     selectionHasChanged = true;
524                 device.selected = TRUE;
525                 global_capture_opts.num_selected++;
526             } else {
527                 if ( device.selected )
528                     selectionHasChanged = true;
529                 device.selected = FALSE;
530             }
531             device.locked = TRUE;
532             global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, idx);
533             g_array_insert_val(global_capture_opts.all_ifaces, idx, device);
534
535             device.locked = FALSE;
536             global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, idx);
537             g_array_insert_val(global_capture_opts.all_ifaces, idx, device);
538         }
539     }
540 #else
541     Q_UNUSED(sourceSelection);
542 #endif
543     return selectionHasChanged;
544 }
545
546
547 /*
548  * Editor modelines
549  *
550  * Local Variables:
551  * c-basic-offset: 4
552  * tab-width: 8
553  * indent-tabs-mode: nil
554  * End:
555  *
556  * ex: set shiftwidth=4 tabstop=8 expandtab:
557  * :indentSize=4:tabSize=8:noTabs=true:
558  */