463ce3f2f37242994c7f388e1fe1589a688e1b87
[metze/wireshark/wip.git] / ui / qt / sequence_dialog.cpp
1 /* sequence_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "sequence_dialog.h"
23 #include <ui_sequence_dialog.h>
24
25 #include "epan/addr_resolv.h"
26
27 #include "file.h"
28 #include "cfile-int.h"
29
30 #include "wsutil/nstime.h"
31 #include "wsutil/utf8_entities.h"
32 #include "wsutil/file_util.h"
33
34 #include <ui/qt/utils/color_utils.h>
35 #include "progress_frame.h"
36 #include <ui/qt/utils/qt_ui_utils.h>
37 #include "sequence_diagram.h"
38 #include "wireshark_application.h"
39 #include <ui/qt/utils/variant_pointer.h>
40 #include <ui/alert_box.h>
41
42 #include <QDir>
43 #include <QFileDialog>
44 #include <QFontMetrics>
45 #include <QPoint>
46
47 // To do:
48 // - Resize or show + hide the Time and Comment axes, possibly via one of
49 //   the following:
50 //   - Split the time, diagram, and comment sections into three separate
51 //     widgets inside a QSplitter. This would resemble the GTK+ UI, but we'd
52 //     have to coordinate between the three and we'd lose time and comment
53 //     values in PDF and PNG exports.
54 //   - Add separate controls for the width and/or visibility of the Time and
55 //     Comment columns.
56 //   - Fake a splitter widget by catching mouse events in the plot area.
57 //     Drawing a QCPItemLine or QCPItemPixmap over each Y axis might make
58 //     this easier.
59 // - For general flows, let the user show columns other than COL_INFO.
60 // - Add UTF8 to text dump
61 // - Save to XMI? http://www.umlgraph.org/
62 // - Time: abs vs delta
63 // - Hide nodes
64 // - Clickable time + comments?
65 // - Incorporate packet comments?
66 // - Change line_style to seq_type (i.e. draw ACKs dashed)
67 // - Create WSGraph subclasses with common behavior.
68 // - Help button and text
69
70 static const double min_top_ = -1.0;
71 static const double min_left_ = -0.5;
72
73 typedef struct {
74     int curr_index;
75     QComboBox *flow;
76     SequenceInfo *info;
77 } sequence_items_t;
78
79 SequenceDialog::SequenceDialog(QWidget &parent, CaptureFile &cf, SequenceInfo *info) :
80     WiresharkDialog(parent, cf),
81     ui(new Ui::SequenceDialog),
82     info_(info),
83     num_items_(0),
84     packet_num_(0),
85     sequence_w_(1)
86 {
87     ui->setupUi(this);
88
89     QCustomPlot *sp = ui->sequencePlot;
90     setWindowSubtitle(info_ ? tr("Call Flow") : tr("Flow"));
91
92     if (!info_) {
93         info_ = new SequenceInfo(sequence_analysis_info_new());
94         info_->sainfo()->name = "any";
95     } else {
96         info_->ref();
97         sequence_analysis_free_nodes(info_->sainfo());
98         num_items_ = sequence_analysis_get_nodes(info_->sainfo());
99     }
100
101     seq_diagram_ = new SequenceDiagram(sp->yAxis, sp->xAxis2, sp->yAxis2);
102     sp->addPlottable(seq_diagram_);
103
104     // When dragging is enabled it's easy to drag past the lower and upper
105     // bounds of each axis. Disable it for now.
106     //sp->axisRect()->setRangeDragAxes(sp->xAxis2, sp->yAxis);
107     //sp->setInteractions(QCP::iRangeDrag);
108
109     sp->xAxis->setVisible(false);
110     sp->xAxis->setPadding(0);
111     sp->xAxis->setLabelPadding(0);
112     sp->xAxis->setTickLabelPadding(0);
113
114     QPen base_pen(ColorUtils::alphaBlend(palette().text(), palette().base(), 0.25));
115     base_pen.setWidthF(0.5);
116     sp->xAxis2->setBasePen(base_pen);
117     sp->yAxis->setBasePen(base_pen);
118     sp->yAxis2->setBasePen(base_pen);
119
120     sp->xAxis2->setVisible(true);
121     sp->yAxis2->setVisible(true);
122
123     key_text_ = new QCPItemText(sp);
124     key_text_->setText(tr("Time"));
125     sp->addItem(key_text_);
126
127     key_text_->setPositionAlignment(Qt::AlignRight | Qt::AlignVCenter);
128     key_text_->position->setType(QCPItemPosition::ptAbsolute);
129     key_text_->setClipToAxisRect(false);
130
131     comment_text_ = new QCPItemText(sp);
132     comment_text_->setText(tr("Comment"));
133     sp->addItem(comment_text_);
134
135     comment_text_->setPositionAlignment(Qt::AlignLeft | Qt::AlignVCenter);
136     comment_text_->position->setType(QCPItemPosition::ptAbsolute);
137     comment_text_->setClipToAxisRect(false);
138
139     one_em_ = QFontMetrics(sp->yAxis->labelFont()).height();
140     ui->horizontalScrollBar->setSingleStep(100 / one_em_);
141     ui->verticalScrollBar->setSingleStep(100 / one_em_);
142
143     ui->gridLayout->setSpacing(0);
144     connect(sp->yAxis, SIGNAL(rangeChanged(QCPRange)), sp->yAxis2, SLOT(setRange(QCPRange)));
145
146     ctx_menu_.addAction(ui->actionZoomIn);
147     ctx_menu_.addAction(ui->actionZoomOut);
148     ctx_menu_.addAction(ui->actionReset);
149     ctx_menu_.addSeparator();
150     ctx_menu_.addAction(ui->actionMoveRight10);
151     ctx_menu_.addAction(ui->actionMoveLeft10);
152     ctx_menu_.addAction(ui->actionMoveUp10);
153     ctx_menu_.addAction(ui->actionMoveDown10);
154     ctx_menu_.addAction(ui->actionMoveRight1);
155     ctx_menu_.addAction(ui->actionMoveLeft1);
156     ctx_menu_.addAction(ui->actionMoveUp1);
157     ctx_menu_.addAction(ui->actionMoveDown1);
158     ctx_menu_.addSeparator();
159     ctx_menu_.addAction(ui->actionGoToPacket);
160     ctx_menu_.addAction(ui->actionGoToNextPacket);
161     ctx_menu_.addAction(ui->actionGoToPreviousPacket);
162
163     ui->addressComboBox->setCurrentIndex(0);
164
165     sequence_items_t item_data;
166
167     item_data.curr_index = 0;
168     item_data.flow = ui->flowComboBox;
169     item_data.info = info_;
170
171     //Add all registered analysis to combo box
172     sequence_analysis_table_iterate_tables(addFlowSequenceItem, &item_data);
173
174     if (strcmp(info_->sainfo()->name, "voip") == 0) {
175         ui->flowComboBox->blockSignals(true);
176         ui->controlFrame->hide();
177     }
178
179     QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
180     save_bt->setText(tr("Save As" UTF8_HORIZONTAL_ELLIPSIS));
181
182     QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
183     if (close_bt) {
184         close_bt->setDefault(true);
185     }
186
187     ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
188
189     loadGeometry(parent.width(), parent.height() * 4 / 5);
190
191     connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(hScrollBarChanged(int)));
192     connect(ui->verticalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(vScrollBarChanged(int)));
193     connect(sp->xAxis2, SIGNAL(rangeChanged(QCPRange)), this, SLOT(xAxisChanged(QCPRange)));
194     connect(sp->yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(yAxisChanged(QCPRange)));
195     connect(sp, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(diagramClicked(QMouseEvent*)));
196     connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
197     connect(sp, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheeled(QWheelEvent*)));
198
199     disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
200 }
201
202 SequenceDialog::~SequenceDialog()
203 {
204     info_->unref();
205     delete ui;
206 }
207
208 void SequenceDialog::updateWidgets()
209 {
210     WiresharkDialog::updateWidgets();
211 }
212
213 void SequenceDialog::showEvent(QShowEvent *)
214 {
215     QTimer::singleShot(0, this, SLOT(fillDiagram()));
216 }
217
218 void SequenceDialog::resizeEvent(QResizeEvent *)
219 {
220     if (!info_) return;
221
222     resetAxes(true);
223 }
224
225 void SequenceDialog::keyPressEvent(QKeyEvent *event)
226 {
227     int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
228
229     // XXX - Copy some shortcuts from tcp_stream_dialog.cpp
230     switch(event->key()) {
231     case Qt::Key_Minus:
232     case Qt::Key_Underscore:    // Shifted minus on U.S. keyboards
233         on_actionZoomOut_triggered();
234         break;
235     case Qt::Key_Plus:
236     case Qt::Key_Equal:         // Unshifted plus on U.S. keyboards
237         on_actionZoomIn_triggered();
238         break;
239
240     case Qt::Key_Right:
241     case Qt::Key_L:
242         panAxes(pan_pixels, 0);
243         break;
244     case Qt::Key_Left:
245     case Qt::Key_H:
246         panAxes(-1 * pan_pixels, 0);
247         break;
248     case Qt::Key_Up:
249     case Qt::Key_K:
250         panAxes(0, pan_pixels);
251         break;
252     case Qt::Key_Down:
253     case Qt::Key_J:
254         panAxes(0, -1 * pan_pixels);
255         break;
256
257     case Qt::Key_PageDown:
258     case Qt::Key_Space:
259         ui->verticalScrollBar->setValue(ui->verticalScrollBar->value() + ui->verticalScrollBar->pageStep());
260         break;
261     case Qt::Key_PageUp:
262         ui->verticalScrollBar->setValue(ui->verticalScrollBar->value() - ui->verticalScrollBar->pageStep());
263         break;
264
265     case Qt::Key_0:
266     case Qt::Key_ParenRight:    // Shifted 0 on U.S. keyboards
267     case Qt::Key_R:
268     case Qt::Key_Home:
269         resetAxes();
270         break;
271
272     case Qt::Key_G:
273         on_actionGoToPacket_triggered();
274         break;
275     case Qt::Key_N:
276         on_actionGoToNextPacket_triggered();
277         break;
278     case Qt::Key_P:
279         on_actionGoToPreviousPacket_triggered();
280         break;
281     }
282
283     QDialog::keyPressEvent(event);
284 }
285
286 void SequenceDialog::hScrollBarChanged(int value)
287 {
288     if (qAbs(ui->sequencePlot->xAxis2->range().center()-value/100.0) > 0.01) {
289       ui->sequencePlot->xAxis2->setRange(value/100.0, ui->sequencePlot->xAxis2->range().size(), Qt::AlignCenter);
290       ui->sequencePlot->replot();
291     }
292 }
293
294 void SequenceDialog::vScrollBarChanged(int value)
295 {
296     if (qAbs(ui->sequencePlot->yAxis->range().center()-value/100.0) > 0.01) {
297       ui->sequencePlot->yAxis->setRange(value/100.0, ui->sequencePlot->yAxis->range().size(), Qt::AlignCenter);
298       ui->sequencePlot->replot();
299     }
300 }
301
302 void SequenceDialog::xAxisChanged(QCPRange range)
303 {
304     ui->horizontalScrollBar->setValue(qRound(qreal(range.center()*100.0)));
305     ui->horizontalScrollBar->setPageStep(qRound(qreal(range.size()*100.0)));
306 }
307
308 void SequenceDialog::yAxisChanged(QCPRange range)
309 {
310     ui->verticalScrollBar->setValue(qRound(qreal(range.center()*100.0)));
311     ui->verticalScrollBar->setPageStep(qRound(qreal(range.size()*100.0)));
312 }
313
314 void SequenceDialog::diagramClicked(QMouseEvent *event)
315 {
316     switch (event->button()) {
317     case Qt::LeftButton:
318         on_actionGoToPacket_triggered();
319         break;
320     case Qt::RightButton:
321         // XXX We should find some way to get sequenceDiagram to handle a
322         // contextMenuEvent instead.
323         ctx_menu_.exec(event->globalPos());
324         break;
325     default:
326         break;
327     }
328 }
329
330 void SequenceDialog::mouseMoved(QMouseEvent *event)
331 {
332     packet_num_ = 0;
333     QString hint;
334     if (event) {
335         seq_analysis_item_t *sai = seq_diagram_->itemForPosY(event->pos().y());
336         if (sai) {
337             packet_num_ = sai->frame_number;
338             QString raw_comment = html_escape(sai->comment);
339             hint = QString("Packet %1: %2").arg(packet_num_).arg(raw_comment);
340         }
341     }
342
343     if (hint.isEmpty()) {
344         if (!info_->sainfo()) {
345             hint += tr("No data");
346         } else {
347             hint += tr("%Ln node(s)", "", info_->sainfo()->num_nodes) + QString(", ")
348                     + tr("%Ln item(s)", "", num_items_);
349         }
350     }
351
352     hint.prepend("<small><i>");
353     hint.append("</i></small>");
354     ui->hintLabel->setText(hint);
355 }
356
357 void SequenceDialog::mouseWheeled(QWheelEvent *event)
358 {
359     int scroll_delta = event->delta() * -1 / 15;
360     if (event->orientation() == Qt::Vertical) {
361         scroll_delta *= ui->verticalScrollBar->singleStep();
362         ui->verticalScrollBar->setValue(ui->verticalScrollBar->value() + scroll_delta);
363     } else {
364         scroll_delta *= ui->horizontalScrollBar->singleStep();
365         ui->horizontalScrollBar->setValue(ui->horizontalScrollBar->value() + scroll_delta);
366     }
367     event->accept();
368 }
369
370 void SequenceDialog::on_buttonBox_accepted()
371 {
372     QString file_name, extension;
373     QDir path(wsApp->lastOpenDir());
374     QString pdf_filter = tr("Portable Document Format (*.pdf)");
375     QString png_filter = tr("Portable Network Graphics (*.png)");
376     QString bmp_filter = tr("Windows Bitmap (*.bmp)");
377     // Gaze upon my beautiful graph with lossy artifacts!
378     QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
379     QString ascii_filter = tr("ASCII (*.txt)");
380
381     QString filter = QString("%1;;%2;;%3;;%4")
382             .arg(pdf_filter)
383             .arg(png_filter)
384             .arg(bmp_filter)
385             .arg(jpeg_filter);
386     if (!file_closed_) {
387         filter.append(QString(";;%5").arg(ascii_filter));
388     }
389
390     file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As" UTF8_HORIZONTAL_ELLIPSIS)),
391                                              path.canonicalPath(), filter, &extension);
392
393     if (file_name.length() > 0) {
394         bool save_ok = false;
395         if (extension.compare(pdf_filter) == 0) {
396             save_ok = ui->sequencePlot->savePdf(file_name);
397         } else if (extension.compare(png_filter) == 0) {
398             save_ok = ui->sequencePlot->savePng(file_name);
399         } else if (extension.compare(bmp_filter) == 0) {
400             save_ok = ui->sequencePlot->saveBmp(file_name);
401         } else if (extension.compare(jpeg_filter) == 0) {
402             save_ok = ui->sequencePlot->saveJpg(file_name);
403         } else if (extension.compare(ascii_filter) == 0 && !file_closed_ && info_->sainfo()) {
404             FILE  *outfile = ws_fopen(file_name.toUtf8().constData(), "w");
405             if (outfile != NULL) {
406                 sequence_analysis_dump_to_file(outfile, info_->sainfo(), 0);
407                 save_ok = true;
408             } else {
409                 save_ok = false;
410             }
411         }
412         // else error dialog?
413         if (save_ok) {
414             path = QDir(file_name);
415             wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
416         } else {
417             open_failure_alert_box(file_name.toUtf8().constData(), errno, TRUE);
418         }
419     }
420 }
421
422 void SequenceDialog::fillDiagram()
423 {
424     if (!info_->sainfo() || file_closed_) return;
425
426     QCustomPlot *sp = ui->sequencePlot;
427
428     if (strcmp(info_->sainfo()->name, "voip") == 0) {
429         seq_diagram_->setData(info_->sainfo());
430     } else {
431         seq_diagram_->clearData();
432         sequence_analysis_list_free(info_->sainfo());
433
434         register_analysis_t* analysis = sequence_analysis_find_by_name(info_->sainfo()->name);
435         if (analysis != NULL)
436         {
437             const char *filter = NULL;
438             if (ui->displayFilterCheckBox->checkState() == Qt::Checked)
439                 filter = cap_file_.capFile()->dfilter;
440
441             register_tap_listener(sequence_analysis_get_tap_listener_name(analysis), info_->sainfo(), filter, sequence_analysis_get_tap_flags(analysis),
442                                        NULL, sequence_analysis_get_packet_func(analysis), NULL);
443
444             cf_retap_packets(cap_file_.capFile());
445             remove_tap_listener(info_->sainfo());
446
447             num_items_ = sequence_analysis_get_nodes(info_->sainfo());
448             seq_diagram_->setData(info_->sainfo());
449         }
450     }
451
452     sequence_w_ = one_em_ * 15; // Arbitrary
453
454     mouseMoved(NULL);
455     resetAxes();
456
457     // XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
458     sp->setFocus();
459 }
460
461 void SequenceDialog::panAxes(int x_pixels, int y_pixels)
462 {
463     // We could simplify this quite a bit if we set the scroll bar values instead.
464     if (!info_->sainfo()) return;
465
466     QCustomPlot *sp = ui->sequencePlot;
467     double h_pan = 0.0;
468     double v_pan = 0.0;
469
470     h_pan = sp->xAxis2->range().size() * x_pixels / sp->xAxis2->axisRect()->width();
471     if (h_pan < 0) {
472         h_pan = qMax(h_pan, min_left_ - sp->xAxis2->range().lower);
473     } else {
474         h_pan = qMin(h_pan, info_->sainfo()->num_nodes - sp->xAxis2->range().upper);
475     }
476
477     v_pan = sp->yAxis->range().size() * y_pixels / sp->yAxis->axisRect()->height();
478     if (v_pan < 0) {
479         v_pan = qMax(v_pan, min_top_ - sp->yAxis->range().lower);
480     } else {
481         v_pan = qMin(v_pan, num_items_ - sp->yAxis->range().upper);
482     }
483
484     if (h_pan && !(sp->xAxis2->range().contains(min_left_) && sp->xAxis2->range().contains(info_->sainfo()->num_nodes))) {
485         sp->xAxis2->moveRange(h_pan);
486         sp->replot();
487     }
488     if (v_pan && !(sp->yAxis->range().contains(min_top_) && sp->yAxis->range().contains(num_items_))) {
489         sp->yAxis->moveRange(v_pan);
490         sp->replot();
491     }
492 }
493
494 void SequenceDialog::resetAxes(bool keep_lower)
495 {
496     if (!info_->sainfo()) return;
497
498     QCustomPlot *sp = ui->sequencePlot;
499
500     // Allow space for labels on the top and port numbers on the left.
501     double top_pos = min_top_, left_pos = min_left_;
502     if (keep_lower) {
503         top_pos = sp->yAxis->range().lower;
504         left_pos = sp->xAxis2->range().lower;
505     }
506
507     double range_span = sp->viewport().width() / sequence_w_ * sp->axisRect()->rangeZoomFactor(Qt::Horizontal);
508     sp->xAxis2->setRange(left_pos, range_span + left_pos);
509
510     range_span = sp->axisRect()->height() / (one_em_ * 1.5);
511     sp->yAxis->setRange(top_pos, range_span + top_pos);
512
513     double rmin = sp->xAxis2->range().size() / 2;
514     ui->horizontalScrollBar->setRange((rmin - 0.5) * 100, (info_->sainfo()->num_nodes - 0.5 - rmin) * 100);
515     xAxisChanged(sp->xAxis2->range());
516     ui->horizontalScrollBar->setValue(ui->horizontalScrollBar->minimum()); // Shouldn't be needed.
517
518     rmin = (sp->yAxis->range().size() / 2);
519     ui->verticalScrollBar->setRange((rmin - 1.0) * 100, (num_items_ - 0.5 - rmin) * 100);
520     yAxisChanged(sp->yAxis->range());
521
522     // It would be exceedingly handy if we could do one or both of the
523     // following:
524     // - Position an axis label above its axis inline with the tick labels.
525     // - Anchor a QCPItemText to one of the corners of a QCPAxis.
526     // Neither of those appear to be possible, so we first call replot in
527     // order to lay out our X axes, place our labels, the call replot again.
528     sp->replot(QCustomPlot::rpQueued);
529
530     QRect axis_rect = sp->axisRect()->rect();
531
532     key_text_->position->setCoords(axis_rect.left()
533                                    - sp->yAxis->padding()
534                                    - sp->yAxis->tickLabelPadding()
535                                    - sp->yAxis->offset(),
536                                    axis_rect.top() / 2);
537     comment_text_->position->setCoords(axis_rect.right()
538                                        + sp->yAxis2->padding()
539                                        + sp->yAxis2->tickLabelPadding()
540                                        + sp->yAxis2->offset(),
541                                        axis_rect.top()  / 2);
542
543     sp->replot(QCustomPlot::rpHint);
544 }
545
546 void SequenceDialog::on_resetButton_clicked()
547 {
548     resetAxes();
549 }
550
551 void SequenceDialog::on_actionGoToPacket_triggered()
552 {
553     if (!file_closed_ && packet_num_ > 0) {
554         cf_goto_frame(cap_file_.capFile(), packet_num_);
555         seq_diagram_->setSelectedPacket(packet_num_);
556     }
557 }
558
559 void SequenceDialog::goToAdjacentPacket(bool next)
560 {
561     if (file_closed_) return;
562
563     int old_key = seq_diagram_->selectedKey();
564     int adjacent_packet = seq_diagram_->adjacentPacket(next);
565     int new_key = seq_diagram_->selectedKey();
566
567     if (adjacent_packet > 0) {
568         if (new_key >= 0) {
569             QCustomPlot *sp = ui->sequencePlot;
570             double range_offset = 0.0;
571             // Scroll if we're at our scroll margin and we haven't reached
572             // the end of our range.
573             double scroll_margin = 3.0; // Lines
574
575             if (old_key >= 0) {
576                 range_offset = new_key - old_key;
577             }
578
579             if (new_key < sp->yAxis->range().lower) {
580                 // Out of range, top
581                 range_offset = qRound(new_key - sp->yAxis->range().lower - scroll_margin - 0.5);
582             } else if (new_key > sp->yAxis->range().upper) {
583                 // Out of range, bottom
584                 range_offset = qRound(new_key - sp->yAxis->range().upper + scroll_margin + 0.5);
585             } else if (next) {
586                 // In range, next
587                 if (new_key + scroll_margin < sp->yAxis->range().upper) {
588                     range_offset = 0.0;
589                 }
590             } else {
591                 // In range, previous
592                 if (new_key - scroll_margin > sp->yAxis->range().lower) {
593                     range_offset = 0.0;
594                 }
595             }
596
597             // Clamp to our upper & lower bounds.
598             if (range_offset > 0) {
599                 range_offset = qMin(range_offset, num_items_ - sp->yAxis->range().upper);
600             } else if (range_offset < 0) {
601                 range_offset = qMax(range_offset, min_top_ - sp->yAxis->range().lower);
602             }
603             sp->yAxis->moveRange(range_offset);
604         }
605         cf_goto_frame(cap_file_.capFile(), adjacent_packet);
606         seq_diagram_->setSelectedPacket(adjacent_packet);
607     }
608 }
609
610 void SequenceDialog::on_displayFilterCheckBox_toggled(bool)
611 {
612     fillDiagram();
613 }
614
615 void SequenceDialog::on_flowComboBox_activated(int index)
616 {
617     if (!info_->sainfo() || (strcmp(info_->sainfo()->name, "voip") == 0) || index < 0)
618         return;
619
620     register_analysis_t* analysis = VariantPointer<register_analysis_t>::asPtr(ui->flowComboBox->itemData(index));
621     info_->sainfo()->name = sequence_analysis_get_name(analysis);
622
623     fillDiagram();
624 }
625
626 void SequenceDialog::on_addressComboBox_activated(int index)
627 {
628     if (!info_->sainfo()) return;
629
630     if (index == 0) {
631         info_->sainfo()->any_addr = TRUE;
632     } else {
633         info_->sainfo()->any_addr = FALSE;
634     }
635     fillDiagram();
636 }
637
638 void SequenceDialog::on_actionReset_triggered()
639 {
640     on_resetButton_clicked();
641 }
642
643 void SequenceDialog::on_actionMoveRight10_triggered()
644 {
645     panAxes(10, 0);
646 }
647
648 void SequenceDialog::on_actionMoveLeft10_triggered()
649 {
650     panAxes(-10, 0);
651 }
652
653 void SequenceDialog::on_actionMoveUp10_triggered()
654 {
655     panAxes(0, 10);
656 }
657
658 void SequenceDialog::on_actionMoveDown10_triggered()
659 {
660     panAxes(0, -10);
661 }
662
663 void SequenceDialog::on_actionMoveRight1_triggered()
664 {
665     panAxes(1, 0);
666 }
667
668 void SequenceDialog::on_actionMoveLeft1_triggered()
669 {
670     panAxes(-1, 0);
671 }
672
673 void SequenceDialog::on_actionMoveUp1_triggered()
674 {
675     panAxes(0, 1);
676 }
677
678 void SequenceDialog::on_actionMoveDown1_triggered()
679 {
680     panAxes(0, -1);
681 }
682
683 void SequenceDialog::on_actionZoomIn_triggered()
684 {
685     zoomXAxis(true);
686 }
687
688 void SequenceDialog::on_actionZoomOut_triggered()
689 {
690     zoomXAxis(false);
691 }
692
693 void SequenceDialog::zoomXAxis(bool in)
694 {
695     QCustomPlot *sp = ui->sequencePlot;
696     double h_factor = sp->axisRect()->rangeZoomFactor(Qt::Horizontal);
697
698     if (!in) {
699         h_factor = pow(h_factor, -1);
700     }
701
702     sp->xAxis2->scaleRange(h_factor, sp->xAxis->range().lower);
703     sp->replot();
704 }
705
706 gboolean SequenceDialog::addFlowSequenceItem(const void* key, void *value, void *userdata)
707 {
708     const char* name = (const char*)key;
709     register_analysis_t* analysis = (register_analysis_t*)value;
710     sequence_items_t* item_data = (sequence_items_t*)userdata;
711
712     /* XXX - Although "voip" isn't a registered name yet, it appears to have special
713        handling that will be done outside of registered data */
714     if (strcmp(name, "voip") == 0)
715         return FALSE;
716
717     item_data->flow->addItem(sequence_analysis_get_ui_name(analysis), VariantPointer<register_analysis_t>::asQVariant(analysis));
718
719     if (item_data->flow->itemData(item_data->curr_index).toString().compare(item_data->info->sainfo()->name) == 0)
720         item_data->flow->setCurrentIndex(item_data->curr_index);
721
722     item_data->curr_index++;
723
724     return FALSE;
725 }
726
727 SequenceInfo::SequenceInfo(seq_analysis_info_t *sainfo) :
728     sainfo_(sainfo),
729     count_(1)
730 {
731 }
732
733 SequenceInfo::~SequenceInfo()
734 {
735     sequence_analysis_info_free(sainfo_);
736 }
737
738 /*
739  * Editor modelines
740  *
741  * Local Variables:
742  * c-basic-offset: 4
743  * tab-width: 8
744  * indent-tabs-mode: nil
745  * End:
746  *
747  * ex: set shiftwidth=4 tabstop=8 expandtab:
748  * :indentSize=4:tabSize=8:noTabs=true:
749  */