HACK: 1. try to match RowsetProperties
[wireshark-wip.git] / ui / qt / sequence_dialog.cpp
blobe861b6283722b3d5b87573fcc8d8b590c966fec7
1 /* sequence_dialog.cpp
3 * $Id$
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "sequence_dialog.h"
25 #include "ui_sequence_dialog.h"
27 #include "epan/addr_resolv.h"
29 #include "wsutil/nstime.h"
31 #include "wireshark_application.h"
33 #include <QDir>
34 #include <QFileDialog>
35 #include <QFontMetrics>
36 #include <QPoint>
38 #include <QDebug>
40 // To do:
41 // - Add UTF8 to text dump
42 // - Save to XMI? http://www.umlgraph.org/
43 // - Time: abs vs delta
44 // - Hide nodes
45 // - Clickable time + comments?
46 // - Incorporate packet comments?
47 // - Change line_style to seq_type (i.e. draw ACKs dashed)
48 // - Create WSGraph subclasses with common behavior.
49 // - Help button and text
51 SequenceDialog::SequenceDialog(QWidget *parent, capture_file *cf, SequenceType type) :
52 QDialog(parent),
53 ui(new Ui::SequenceDialog),
54 cap_file_(cf),
55 num_items_(0),
56 packet_num_(0),
57 node_label_w_(20)
59 ui->setupUi(this);
60 QCustomPlot *sp = ui->sequencePlot;
62 seq_diagram_ = new SequenceDiagram(sp->yAxis, sp->xAxis2, sp->yAxis2);
63 sp->addPlottable(seq_diagram_);
64 sp->axisRect()->setRangeDragAxes(sp->xAxis2, sp->yAxis);
66 sp->xAxis->setVisible(false);
67 sp->xAxis->setPadding(0);
68 sp->xAxis->setLabelPadding(0);
69 sp->xAxis->setTickLabelPadding(0);
70 sp->xAxis2->setVisible(true);
71 sp->yAxis2->setVisible(true);
73 one_em_ = QFontMetrics(sp->yAxis->labelFont()).height();
74 ui->horizontalScrollBar->setSingleStep(100 / one_em_);
75 ui->verticalScrollBar->setSingleStep(100 / one_em_);
77 sp->setInteractions(QCP::iRangeDrag);
79 ui->gridLayout->setSpacing(0);
80 connect(sp->yAxis, SIGNAL(rangeChanged(QCPRange)), sp->yAxis2, SLOT(setRange(QCPRange)));
82 ctx_menu_.addAction(ui->actionReset);
83 ctx_menu_.addSeparator();
84 ctx_menu_.addAction(ui->actionMoveRight10);
85 ctx_menu_.addAction(ui->actionMoveLeft10);
86 ctx_menu_.addAction(ui->actionMoveUp10);
87 ctx_menu_.addAction(ui->actionMoveDown10);
88 ctx_menu_.addAction(ui->actionMoveRight1);
89 ctx_menu_.addAction(ui->actionMoveLeft1);
90 ctx_menu_.addAction(ui->actionMoveUp1);
91 ctx_menu_.addAction(ui->actionMoveDown1);
92 ctx_menu_.addSeparator();
93 ctx_menu_.addAction(ui->actionGoToPacket);
95 memset (&seq_analysis_, 0, sizeof(seq_analysis_));
97 ui->showComboBox->blockSignals(true);
98 ui->showComboBox->setCurrentIndex(0);
99 ui->showComboBox->blockSignals(false);
100 ui->addressComboBox->blockSignals(true);
101 ui->addressComboBox->setCurrentIndex(0);
102 ui->addressComboBox->blockSignals(false);
104 QComboBox *fcb = ui->flowComboBox;
105 fcb->addItem(ui->actionFlowAny->text(), SEQ_ANALYSIS_ANY);
106 fcb->addItem(ui->actionFlowTcp->text(), SEQ_ANALYSIS_TCP);
108 ui->flowComboBox->blockSignals(true);
109 switch (type) {
110 case any:
111 seq_analysis_.type = SEQ_ANALYSIS_ANY;
112 ui->flowComboBox->setCurrentIndex(SEQ_ANALYSIS_ANY);
113 break;
114 case tcp:
115 seq_analysis_.type = SEQ_ANALYSIS_TCP;
116 ui->flowComboBox->setCurrentIndex(SEQ_ANALYSIS_TCP);
117 break;
118 case voip:
119 seq_analysis_.type = SEQ_ANALYSIS_VOIP;
120 ui->flowComboBox->hide();
121 ui->flowLabel->hide();
122 break;
124 ui->flowComboBox->blockSignals(false);
125 seq_analysis_.all_packets = TRUE;
127 QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
128 save_bt->setText(tr("Save As..."));
130 if (parent) {
131 resize(parent->width(), parent->height() * 4 / 5);
134 connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(hScrollBarChanged(int)));
135 connect(ui->verticalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(vScrollBarChanged(int)));
136 connect(sp->xAxis2, SIGNAL(rangeChanged(QCPRange)), this, SLOT(xAxisChanged(QCPRange)));
137 connect(sp->yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(yAxisChanged(QCPRange)));
138 connect(sp, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(diagramClicked(QMouseEvent*)));
139 connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
140 connect(sp, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseReleased(QMouseEvent*)));
141 connect(this, SIGNAL(goToPacket(int)), seq_diagram_, SLOT(setSelectedPacket(int)));
143 disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
145 fillDiagram();
148 SequenceDialog::~SequenceDialog()
150 delete ui;
153 void SequenceDialog::setCaptureFile(capture_file *cf)
155 if (!cf) { // We only want to know when the file closes.
156 cap_file_ = NULL;
160 void SequenceDialog::showEvent(QShowEvent *event)
162 Q_UNUSED(event);
163 resetAxes();
166 void SequenceDialog::resizeEvent(QResizeEvent *event)
168 Q_UNUSED(event);
169 resetAxes(true);
172 void SequenceDialog::keyPressEvent(QKeyEvent *event)
174 int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
176 // XXX - Copy some shortcuts from tcp_stream_dialog.cpp
177 switch(event->key()) {
178 case Qt::Key_Right:
179 case Qt::Key_L:
180 panAxes(pan_pixels, 0);
181 break;
182 case Qt::Key_Left:
183 case Qt::Key_H:
184 panAxes(-1 * pan_pixels, 0);
185 break;
186 case Qt::Key_Up:
187 case Qt::Key_K:
188 panAxes(0, -1 * pan_pixels);
189 break;
190 case Qt::Key_Down:
191 case Qt::Key_J:
192 panAxes(0, pan_pixels);
193 break;
195 case Qt::Key_0:
196 case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
197 case Qt::Key_R:
198 case Qt::Key_Home:
199 resetAxes();
200 break;
202 case Qt::Key_G:
203 on_actionGoToPacket_triggered();
204 break;
207 QDialog::keyPressEvent(event);
210 void SequenceDialog::mouseReleaseEvent(QMouseEvent *event)
212 mouseReleased(event);
215 void SequenceDialog::hScrollBarChanged(int value)
217 if (qAbs(ui->sequencePlot->xAxis2->range().center()-value/100.0) > 0.01) {
218 ui->sequencePlot->xAxis2->setRange(value/100.0, ui->sequencePlot->xAxis2->range().size(), Qt::AlignCenter);
219 ui->sequencePlot->replot();
223 void SequenceDialog::vScrollBarChanged(int value)
225 if (qAbs(ui->sequencePlot->yAxis->range().center()-value/100.0) > 0.01) {
226 ui->sequencePlot->yAxis->setRange(value/100.0, ui->sequencePlot->yAxis->range().size(), Qt::AlignCenter);
227 ui->sequencePlot->replot();
231 void SequenceDialog::xAxisChanged(QCPRange range)
233 ui->horizontalScrollBar->setValue(qRound(range.center()*100.0));
234 ui->horizontalScrollBar->setPageStep(qRound(range.size()*100.0));
237 void SequenceDialog::yAxisChanged(QCPRange range)
239 ui->verticalScrollBar->setValue(qRound(range.center()*100.0));
240 ui->verticalScrollBar->setPageStep(qRound(range.size()*100.0));
243 void SequenceDialog::diagramClicked(QMouseEvent *event)
245 QCustomPlot *sp = ui->sequencePlot;
247 if (event->button() == Qt::RightButton) {
248 // XXX We should find some way to get sequenceDiagram to handle a
249 // contextMenuEvent instead.
250 ctx_menu_.exec(event->globalPos());
251 } else if (sp->axisRect()->rect().contains(event->pos())) {
252 sp->setCursor(QCursor(Qt::ClosedHandCursor));
254 on_actionGoToPacket_triggered();
257 void SequenceDialog::mouseMoved(QMouseEvent *event)
259 QCustomPlot *sp = ui->sequencePlot;
260 Qt::CursorShape shape = Qt::ArrowCursor;
261 if (event) {
262 if (event->buttons().testFlag(Qt::LeftButton)) {
263 shape = Qt::ClosedHandCursor;
264 } else {
265 if (sp->axisRect()->rect().contains(event->pos())) {
266 shape = Qt::OpenHandCursor;
270 sp->setCursor(QCursor(shape));
272 packet_num_ = 0;
273 QString hint;
274 if (event) {
275 seq_analysis_item_t *sai = seq_diagram_->itemForPosY(event->pos().y());
276 if (sai) {
277 packet_num_ = sai->fd->num;
278 hint = QString("Packet %1: %2").arg(packet_num_).arg(sai->comment);
282 if (hint.isEmpty()) {
283 hint += QString("%1 nodes, %2 items").arg(seq_analysis_.num_nodes).arg(num_items_);
286 hint.prepend("<small><i>");
287 hint.append("</i></small>");
288 ui->hintLabel->setText(hint);
291 void SequenceDialog::mouseReleased(QMouseEvent *event)
293 Q_UNUSED(event);
294 if (ui->sequencePlot->cursor().shape() == Qt::ClosedHandCursor) {
295 ui->sequencePlot->setCursor(QCursor(Qt::OpenHandCursor));
299 void SequenceDialog::on_buttonBox_accepted()
301 QString file_name, extension;
302 QDir path(wsApp->lastOpenDir());
303 QString pdf_filter = tr("Portable Document Format (*.pdf)");
304 QString png_filter = tr("Portable Network Graphics (*.png)");
305 QString bmp_filter = tr("Windows Bitmap (*.bmp)");
306 // Gaze upon my beautiful graph with lossy artifacts!
307 QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
308 QString ascii_filter = tr("ASCII (*.txt)");
310 QString filter = QString("%1;;%2;;%3;;%4")
311 .arg(pdf_filter)
312 .arg(png_filter)
313 .arg(bmp_filter)
314 .arg(jpeg_filter);
315 if (cap_file_) {
316 filter.append(QString(";;%5").arg(ascii_filter));
319 file_name = QFileDialog::getSaveFileName(this, tr("Wireshark: Save Graph As..."),
320 path.canonicalPath(), filter, &extension);
322 if (file_name.length() > 0) {
323 bool save_ok = false;
324 if (extension.compare(pdf_filter) == 0) {
325 save_ok = ui->sequencePlot->savePdf(file_name);
326 } else if (extension.compare(png_filter) == 0) {
327 save_ok = ui->sequencePlot->savePng(file_name);
328 } else if (extension.compare(bmp_filter) == 0) {
329 save_ok = ui->sequencePlot->saveBmp(file_name);
330 } else if (extension.compare(jpeg_filter) == 0) {
331 save_ok = ui->sequencePlot->saveJpg(file_name);
332 } else if (extension.compare(ascii_filter) == 0 && cap_file_) {
333 save_ok = sequence_analysis_dump_to_file(file_name.toUtf8().constData(), &seq_analysis_, cap_file_, 0);
335 // else error dialog?
336 if (save_ok) {
337 path = QDir(file_name);
338 wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
343 void SequenceDialog::fillDiagram()
345 QCustomPlot *sp = ui->sequencePlot;
347 sequence_analysis_list_free(&seq_analysis_);
348 sequence_analysis_list_get(cap_file_, &seq_analysis_);
349 num_items_ = sequence_analysis_get_nodes(&seq_analysis_);
351 seq_diagram_->setData(&seq_analysis_);
353 QFontMetrics vfm = QFontMetrics(sp->xAxis2->labelFont());
354 node_label_w_ = 0;
355 for (guint i = 0; i < seq_analysis_.num_nodes; i++) {
356 int label_w = vfm.width(get_addr_name(&(seq_analysis_.nodes[i])));
357 if (node_label_w_ < label_w) {
358 node_label_w_ = label_w;
361 node_label_w_ = (node_label_w_ * 3 / 4) + one_em_;
363 mouseMoved(NULL);
364 resetAxes();
366 // XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
367 sp->setFocus();
370 void SequenceDialog::panAxes(int x_pixels, int y_pixels)
372 QCustomPlot *sp = ui->sequencePlot;
373 double h_pan = 0.0;
374 double v_pan = 0.0;
376 h_pan = sp->xAxis2->range().size() * x_pixels / sp->xAxis2->axisRect()->width();
377 v_pan = sp->yAxis->range().size() * y_pixels / sp->yAxis->axisRect()->height();
378 // The GTK+ version won't pan unless we're zoomed. Should we do the same here?
379 if (h_pan) {
380 sp->xAxis2->moveRange(h_pan);
381 sp->replot();
383 if (v_pan) {
384 sp->yAxis->moveRange(v_pan);
385 sp->replot();
389 void SequenceDialog::resetAxes(bool keep_lower)
391 QCustomPlot *sp = ui->sequencePlot;
392 // Allow space for labels on the top and port numbers on the left.
393 double top_pos = -1.0, left_pos = -0.5;
394 if (keep_lower) {
395 top_pos = sp->yAxis->range().lower;
396 left_pos = sp->xAxis2->range().lower;
399 double range_ratio = sp->xAxis2->axisRect()->width() / node_label_w_;
400 sp->xAxis2->setRange(left_pos, range_ratio + left_pos);
402 range_ratio = sp->yAxis->axisRect()->height() / (one_em_ * 1.5);
403 sp->yAxis->setRange(top_pos, range_ratio + top_pos);
405 double rmin = sp->xAxis2->range().size() / 2;
406 ui->horizontalScrollBar->setRange((rmin - 0.5) * 100, (seq_analysis_.num_nodes - 0.5 - rmin) * 100);
407 xAxisChanged(sp->xAxis2->range());
409 rmin = (sp->yAxis->range().size() / 2);
410 ui->verticalScrollBar->setRange((rmin - 1.0) * 100, (num_items_ - 0.5 - rmin) * 100);
411 yAxisChanged(sp->yAxis->range());
413 sp->replot();
416 void SequenceDialog::on_resetButton_clicked()
418 resetAxes();
421 void SequenceDialog::on_actionGoToPacket_triggered()
423 if (cap_file_ && packet_num_ > 0) {
424 emit goToPacket(packet_num_);
428 void SequenceDialog::on_showComboBox_currentIndexChanged(int index)
430 if (index == 0) {
431 seq_analysis_.all_packets = TRUE;
432 } else {
433 seq_analysis_.all_packets = FALSE;
435 fillDiagram();
438 void SequenceDialog::on_flowComboBox_currentIndexChanged(int index)
440 if (index < 0) return;
441 seq_analysis_.type = static_cast<seq_analysis_type>(ui->flowComboBox->itemData(index).toInt());
442 fillDiagram();
445 void SequenceDialog::on_addressComboBox_currentIndexChanged(int index)
447 if (index == 0) {
448 seq_analysis_.any_addr = TRUE;
449 } else {
450 seq_analysis_.any_addr = FALSE;
452 fillDiagram();
455 void SequenceDialog::on_actionReset_triggered()
457 on_resetButton_clicked();
460 void SequenceDialog::on_actionMoveRight10_triggered()
462 panAxes(10, 0);
465 void SequenceDialog::on_actionMoveLeft10_triggered()
467 panAxes(-10, 0);
470 void SequenceDialog::on_actionMoveUp10_triggered()
472 panAxes(0, -10);
475 void SequenceDialog::on_actionMoveDown10_triggered()
477 panAxes(0, 10);
480 void SequenceDialog::on_actionMoveRight1_triggered()
482 panAxes(1, 0);
485 void SequenceDialog::on_actionMoveLeft1_triggered()
487 panAxes(-1, 0);
490 void SequenceDialog::on_actionMoveUp1_triggered()
492 panAxes(0, -1);
495 void SequenceDialog::on_actionMoveDown1_triggered()
497 panAxes(0, 1);