Don't use deprecated QString::sprintf().
[sigrok:sigrok.git] / frontend / gui / mainwindow.cpp
1 /*
2  * This file is part of the sigrok project.
3  *
4  * Copyright (C) 2010 Uwe Hermann <uwe@hermann-uwe.de>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  */
20
21 #include <QMessageBox>
22 #include <QFileDialog>
23 #include <QProgressDialog>
24 #include <QDockWidget>
25 #include <QScrollBar>
26 #include "mainwindow.h"
27 #include "ui_mainwindow.h"
28 #include "configform.h"
29 #include "ui_configform.h"
30
31 #ifdef __cplusplus
32 extern "C" {
33 #endif
34
35 /* __STDC_FORMAT_MACROS is required for PRIu64 and friends (in C++). */
36 #define __STDC_FORMAT_MACROS
37 #include <inttypes.h>
38 #include <stdint.h>
39 #include <glib.h>
40 #include <gmodule.h>
41 #include "sigrok.h"
42 #include "backend.h"
43 #include "device.h"
44 #include "session.h"
45
46 #ifdef __cplusplus
47 }
48 #endif
49
50 GMainContext *gmaincontext = NULL;
51 GMainLoop *gmainloop = NULL;
52
53 uint64_t limit_samples = 0; /* FIXME */
54
55 MainWindow::MainWindow(QWidget *parent)
56         : QMainWindow(parent), ui(new Ui::MainWindow)
57 {
58         currentLA = -1;
59         numChannels = -1;
60         configChannelTitleBarLayout = 0; /* Vertical layout */
61
62         for (int i = 0; i < NUMCHANNELS; ++i)
63                 dockWidgets[i] = NULL;
64
65         ui->setupUi(this);
66
67         /* FIXME */
68         QMainWindow::setCentralWidget(ui->mainWidget);
69
70         // this->setDockOptions(QMainWindow::AllowNestedDocks);
71 }
72
73 MainWindow::~MainWindow()
74 {
75         sigrok_cleanup();
76
77         delete ui;
78 }
79
80 void MainWindow::setupDockWidgets(void)
81 {
82         QColor color;
83
84         /* TODO: Do not create new dockWidgets if we already have them. */
85
86         /* TODO: Kill any old dockWidgets before creating new ones? */
87
88         for (int i = 0; i < getNumChannels(); ++i) {
89                 widgets[i] = new QWidget(this);
90                 gridLayouts[i] = new QGridLayout(widgets[i]);
91
92                 lineEdits[i] = new QLineEdit(this);
93                 lineEdits[i]->setMaximumWidth(150);
94                 lineEdits[i]->setText(QString(tr("Channel %1")).arg(i));
95                 /* Use random colors for the channel names for now. */
96                 QPalette p = QPalette(QApplication::palette());
97                 color = QColor(2 + qrand() * 16);
98                 p.setColor(QPalette::Base, color);
99                 lineEdits[i]->setPalette(p);
100                 gridLayouts[i]->addWidget(lineEdits[i], 0, 0);
101
102                 channelRenderAreas[i] = new ChannelRenderArea(this);
103                 channelRenderAreas[i]->setSizePolicy(QSizePolicy::Minimum,
104                                         QSizePolicy::MinimumExpanding);
105                 channelRenderAreas[i]->setChannelNumber(i);
106                 channelRenderAreas[i]->setChannelColor(color);
107                 gridLayouts[i]->addWidget(channelRenderAreas[i], 0, 1);
108
109                 channelScrollBars[i] = new QScrollBar(Qt::Horizontal);
110                 channelScrollBars[i]->setMinimum(0);
111                 channelScrollBars[i]->setMaximum(0);
112                 gridLayouts[i]->addWidget(channelScrollBars[i], 1, 1);
113
114                 dockWidgets[i] = new QDockWidget(this);
115                 dockWidgets[i]->setAllowedAreas(Qt::RightDockWidgetArea);
116
117                 QDockWidget::DockWidgetFeatures f;
118                 f = QDockWidget::DockWidgetClosable |
119                     QDockWidget::DockWidgetMovable |
120                     QDockWidget::DockWidgetFloatable;
121                 if (configChannelTitleBarLayout == 0)
122                         f |= QDockWidget::DockWidgetVerticalTitleBar;
123                 dockWidgets[i]->setFeatures(f);
124                 dockWidgets[i]->setWidget(widgets[i]);
125                 addDockWidget(Qt::RightDockWidgetArea, dockWidgets[i]);
126
127                 /* Update labels upon changes. */
128                 QObject::connect(channelRenderAreas[i],
129                         SIGNAL(sampleStartChanged(QString)),
130                         ui->labelSampleStart, SLOT(setText(QString)));
131                 QObject::connect(channelRenderAreas[i],
132                         SIGNAL(sampleEndChanged(QString)),
133                         ui->labelSampleEnd, SLOT(setText(QString)));
134                 QObject::connect(channelRenderAreas[i],
135                         SIGNAL(zoomFactorChanged(QString)),
136                         ui->labelZoomFactor, SLOT(setText(QString)));
137
138                 /* Redraw channels upon changes. */
139                 QObject::connect(channelRenderAreas[i],
140                         SIGNAL(sampleStartChanged(QString)),
141                         channelRenderAreas[i], SLOT(generatePainterPath()));
142                 QObject::connect(channelRenderAreas[i],
143                         SIGNAL(sampleEndChanged(QString)),
144                         channelRenderAreas[i], SLOT(generatePainterPath()));
145                 QObject::connect(channelRenderAreas[i],
146                         SIGNAL(zoomFactorChanged(QString)),
147                         channelRenderAreas[i], SLOT(generatePainterPath()));
148
149                 // dockWidgets[i]->show();
150 #if 0
151                 /* If the user renames a channel, adapt the dock title. */
152                 QObject::connect(lineEdits[i], SIGNAL(textChanged(QString)),
153                                  dockWidgets[i], SLOT(setWindowTitle(QString)));
154 #endif
155         }
156 }
157
158 void MainWindow::setCurrentLA(int la)
159 {
160         currentLA = la;
161 }
162
163 int MainWindow::getCurrentLA(void)
164 {
165         return currentLA;
166 }
167
168 void MainWindow::setNumChannels(int ch)
169 {
170         numChannels = ch;
171 }
172
173 int MainWindow::getNumChannels(void)
174 {
175         return numChannels;
176 }
177
178 void MainWindow::on_actionAbout_triggered()
179 {
180         QMessageBox::about(this, tr("About"),
181                 tr("sigrok-gui 0.1<br />\nCopyright (C) 2010 "
182                 "Uwe Hermann &lt;uwe@hermann-uwe.de&gt;<br />\n"
183                 "GNU GPL, version 2 or later<br />\n"
184                 "<a href=\"http://www.sigrok.org\">"
185                 "http://www.sigrok.org</a>"));
186 }
187
188 void MainWindow::on_actionAbout_Qt_triggered()
189 {
190         QMessageBox::aboutQt(this, tr("About Qt"));
191 }
192
193 void MainWindow::on_actionPreferences_triggered()
194 {
195         ConfigForm *form = new ConfigForm();
196         form->show();
197 }
198
199 void MainWindow::on_actionScan_triggered()
200 {
201         int ret;
202         QString s;
203         int num_devices;
204         struct device *device;
205         char *di_num_probes;
206         uint64_t *di_samplerates;
207
208         statusBar()->showMessage(tr("Scanning for logic analyzers..."));
209
210         device_scan();
211         devices = device_list();
212         num_devices = g_slist_length(devices);
213         device = (struct device *)g_slist_nth_data(devices, 0 /* opt_device */);
214
215         if (num_devices == 0) {
216                 s = tr("No supported logic analyzer found.");
217                 statusBar()->showMessage(s);
218                 return;
219         } else {
220                 s = tr("Found supported logic analyzer: ");
221                 s.append(device->plugin->name);
222                 statusBar()->showMessage(s);
223         }
224
225         setCurrentLA(0 /* TODO */);
226
227         di_num_probes = device->plugin->get_device_info(device->plugin_index,
228                                                         DI_NUM_PROBES);
229         if (di_num_probes != NULL) {
230                 setNumChannels(GPOINTER_TO_INT(di_num_probes));
231         } else {
232                 setNumChannels(8); /* FIXME: Error handling. */
233         }
234
235         ui->comboBoxLA->clear();
236         ui->comboBoxLA->addItem(device->plugin->name); /* TODO: Full name */
237
238         s = QString(tr("Channels: %1")).arg(getNumChannels());
239         ui->labelChannels->setText(s);
240
241         di_samplerates = (uint64_t *)device->plugin->get_device_info(
242                         device->plugin_index, DI_SAMPLE_RATES);
243         if (!di_samplerates) {
244                 /* TODO: Error handling. */
245         }
246
247         ui->comboBoxSampleRate->clear();
248         for (int i = 0; di_samplerates[i]; ++i) {
249                 if (di_samplerates[i] < 1000000)
250                         s = QString(tr("%1 kHz")).arg(di_samplerates[i] / 1000);
251                 else
252                         s = QString(tr("%1 MHz")).arg(di_samplerates[i] / 1000000);
253                 ui->comboBoxSampleRate->addItem(s, di_samplerates[i]);
254         }
255
256         /* FIXME */
257         ui->comboBoxNumSamples->clear();
258         ui->comboBoxNumSamples->addItem("100", 100); /* For testing... */
259         ui->comboBoxNumSamples->addItem("3000000", 3000000);
260         ui->comboBoxNumSamples->addItem("2000000", 2000000);
261         ui->comboBoxNumSamples->addItem("1000000", 1000000);
262
263         ui->comboBoxNumSamples->setEditable(true);
264
265         /// ret = sigrok_hw_init(getCurrentLA(), &ctx);
266         // if (ret < 0)
267         //      statusBar()->showMessage(tr("ERROR: LA init failed."));
268
269         if (getCurrentLA() >= 0)
270                 setupDockWidgets();
271
272         /* Enable all relevant fields now (i.e. make them non-gray). */
273         ui->comboBoxSampleRate->setEnabled(true);
274         ui->comboBoxNumSamples->setEnabled(true);
275         ui->labelChannels->setEnabled(true);
276         ui->action_Get_samples->setEnabled(true);
277 }
278
279 void MainWindow::on_action_Open_triggered()
280 {
281         QString s;
282         QString fileName = QFileDialog::getOpenFileName(this,
283                 tr("Open sample file"), ".",
284                 tr("Raw sample files (*.raw *.bin);;"
285                    "Gnuplot data files (*.dat);;"
286                    "VCD files (*.vcd);;"
287                    "All files (*)"));
288
289         if (fileName == NULL)
290                 return;
291
292         QFile file(fileName);
293         file.open(QIODevice::ReadOnly);
294         QDataStream in(&file);
295
296         /* TODO: Implement support for loading different input formats. */
297
298         sample_buffer = (uint8_t *)malloc(file.size());
299         if (sample_buffer == NULL) {
300                 /* TODO: Error handling. */
301         }
302
303         in.readRawData((char *)sample_buffer, file.size());
304
305         setNumSamples(file.size());
306         setNumChannels(8); /* FIXME */
307
308         file.close();
309
310         setupDockWidgets();
311
312         ui->comboBoxLA->clear();
313         ui->comboBoxLA->addItem(tr("File"));
314
315         /* FIXME: Store number of channels in the file or allow user config. */
316         s = QString(tr("Channels: %1")).arg(getNumChannels());
317         ui->labelChannels->setText(s);
318         ui->labelChannels->setEnabled(false);
319
320         ui->comboBoxSampleRate->clear();
321         ui->comboBoxSampleRate->setEnabled(false); /* FIXME */
322
323         ui->comboBoxNumSamples->clear();
324         ui->comboBoxNumSamples->addItem(QString::number(getNumSamples()),
325                                         getNumSamples());
326         ui->comboBoxNumSamples->setEnabled(true);
327
328         ui->labelSampleStart->setText(tr("Start sample: "));
329         ui->labelSampleStart->setEnabled(true);
330
331         ui->labelSampleEnd->setText(tr("End sample: "));
332         ui->labelSampleEnd->setEnabled(true);
333
334         ui->labelZoomFactor->setText(tr("Zoom factor: "));
335         ui->labelZoomFactor->setEnabled(true);
336
337         ui->action_Save_as->setEnabled(true);
338         ui->action_Get_samples->setEnabled(false);
339
340         for (int i = 0; i < getNumChannels(); ++i) {
341                 channelRenderAreas[i]->setChannelNumber(i);
342                 channelRenderAreas[i]->setNumSamples(file.size());
343                 channelRenderAreas[i]->setSampleStart(0);
344                 channelRenderAreas[i]->setSampleEnd(getNumSamples());
345                 channelRenderAreas[i]->update();
346         }
347
348         /* FIXME */
349 }
350
351 void MainWindow::on_action_Save_as_triggered()
352 {
353         QString fileName = QFileDialog::getSaveFileName(this,
354                 tr("Save sample file"), ".",
355                 tr("Raw sample files (*.raw *.bin);;All files (*)"));
356
357         if (fileName == NULL)
358                 return;
359
360         QFile file(fileName);
361         file.open(QIODevice::WriteOnly);
362         QDataStream out(&file);
363
364         /* TODO: Implement support for saving to different output formats. */
365
366         out.writeRawData((const char *)sample_buffer,
367                          getNumSamples() * (getNumChannels() / 8));
368         file.close();
369 }
370
371 void datafeed_callback(struct device *device, struct datafeed_packet *packet)
372 {
373         static int num_probes = 0;
374         static int received_samples = 0;
375         static int probelist[65] = {0};
376
377         struct probe *probe;
378         struct datafeed_header *header;
379         int num_enabled_probes, offset, sample_size, unitsize, p;
380         int bpl_offset, bpl_cnt;
381         uint64_t sample;
382
383         sample_size = -1;
384
385         // if(packet->type != DF_HEADER && linebuf == NULL)
386         //      return;
387
388         switch (packet->type) {
389         case DF_HEADER:
390                 printf("DF_HEADER\n");
391                 header = (struct datafeed_header *) packet->payload;
392                 num_probes = header->num_probes;
393                 num_enabled_probes = 0;
394                 for (int i = 0; i < header->num_probes; ++i) {
395                         probe = (struct probe *)g_slist_nth_data(device->probes, i);
396                         if (probe->enabled)
397                                 probelist[num_enabled_probes++] = probe->index;
398                 }
399
400                 printf("Acquisition with %d/%d probes at %"PRIu64"MHz "
401                        "starting at %s (%"PRIu64" samples)\n",
402                        num_enabled_probes, num_probes, header->rate,
403                        ctime(&header->starttime.tv_sec), limit_samples);
404
405                 // linebuf = g_malloc0(num_probes * linebuf_len);
406                 break;
407         case DF_END:
408                 printf("DF_END\n");
409                 g_main_loop_quit(gmainloop);
410                 return;
411                 break;
412         case DF_TRIGGER:
413                 /* TODO */
414                 break;
415         case DF_LOGIC8:
416                 sample_size = 1;
417                 printf("DF_LOGIC8\n");
418                 break;
419         /* TODO: DF_LOGIC16 etc. */
420         }
421
422         if (sample_size < 0)
423                 return;
424
425         for (uint64_t i = 0; received_samples < limit_samples
426                              && i < packet->length; i += sample_size) {
427                 sample = 0;
428                 memcpy(&sample, packet->payload + offset, sample_size);
429                 sample_buffer[i] = (uint8_t)(sample & 0xff); /* FIXME */
430                 printf("Sample %d: 0x%x\n", i, sample);
431                 received_samples++;
432         }
433 }
434
435 void MainWindow::on_action_Get_samples_triggered()
436 {
437         uint8_t *buf;
438         uint64_t pos = 0;
439         uint64_t numSamplesLocal = ui->comboBoxNumSamples->itemData(
440                         ui->comboBoxNumSamples->currentIndex()).toLongLong();
441         uint64_t chunksize = 512;
442         QString s;
443         int opt_device;
444         struct device *device;
445         char numBuf[16];
446
447         opt_device = 0; /* FIXME */
448
449         limit_samples = numSamplesLocal;
450
451         sample_buffer = (uint8_t *)malloc(limit_samples);
452         if (sample_buffer == NULL) {
453                 /* TODO: Error handling. */
454                 return;
455         }
456
457         session_new();
458         session_output_add_callback(datafeed_callback);
459         device = (struct device *)g_slist_nth_data(devices, opt_device);
460
461         snprintf(numBuf, 16, "%"PRIu64"", limit_samples);
462         device->plugin->set_configuration(device->plugin_index,
463                 HWCAP_LIMIT_SAMPLES, (char *)numBuf);
464
465
466         if (session_device_add(device) != SIGROK_OK) {
467                 printf("Failed to use device.\n");
468                 session_destroy();
469                 return;
470         }
471
472         if (session_start() != SIGROK_OK) {
473                 printf("Failed to start session.\n");
474                 session_destroy();
475                 return;
476         }
477
478         gmaincontext = g_main_context_default();
479         gmainloop = g_main_loop_new(gmaincontext, FALSE);
480         g_main_loop_run(gmainloop);
481
482         session_stop();
483         session_destroy();
484
485 #if 0
486         // if (getCurrentLA() < 0) {
487         //      /* TODO */
488         //      return;
489         // }
490
491         // buf = sigrok_hw_get_samples_init(&ctx, numSamplesLocal, 1000000, 1000);
492         // if (buf == NULL) {
493         //      /* TODO: Error handling. */
494         //      return;
495         // }
496
497         QProgressDialog progress("Getting samples from logic analyzer...",
498                                  "Abort", 0, numSamplesLocal, this);
499         progress.setWindowModality(Qt::WindowModal);
500         progress.setMinimumDuration(500);
501
502         for (uint64_t i = 0; i < numSamplesLocal; i += chunksize) {
503                 progress.setValue(i);
504
505                 /* TODO: Properly handle an abort. */
506                 if (progress.wasCanceled())
507                         break;
508
509                 /* Get a small chunk of samples. */
510                 // ret = sigrok_hw_get_samples_chunk(&ctx, buf, chunksize,
511                 //                                    pos, 1000);
512                 pos += chunksize;
513                 /* TODO: Error handling. */
514         }
515         progress.setValue(numSamplesLocal);
516
517         sample_buffer = buf;
518 #endif
519
520         for (int i = 0; i < getNumChannels(); ++i) {
521                 channelRenderAreas[i]->setChannelNumber(i);
522                 channelRenderAreas[i]->setNumSamples(numSamplesLocal);
523                 channelRenderAreas[i]->setSampleStart(0);
524                 channelRenderAreas[i]->setSampleEnd(numSamplesLocal);
525                 channelRenderAreas[i]->update();
526         }
527
528 #if 0
529         /* FIXME */
530         s.sprintf("%"PRIu64"", (int)channelRenderAreas[0]->getSampleStart());
531         s.prepend(tr("Start sample: "));
532         ui->labelSampleStart->setText(s);
533
534         /* FIXME */
535         s.sprintf("%"PRIu64"", (int)channelRenderAreas[0]->getSampleEnd());
536         s.prepend(tr("End sample: "));
537         ui->labelSampleEnd->setText(s);
538 #endif
539
540         /* Enable the relevant labels/buttons. */
541         ui->labelSampleStart->setEnabled(true);
542         ui->labelSampleEnd->setEnabled(true);
543         ui->labelZoomFactor->setEnabled(true);
544         ui->action_Save_as->setEnabled(true);
545
546         // sigrok_hw_get_samples_shutdown(&ctx, 1000);
547 }
548
549 void MainWindow::setSampleRate(uint64_t s)
550 {
551         sampleRate = s;
552 }
553
554 uint64_t MainWindow::getSampleRate(void)
555 {
556         return sampleRate;
557 }
558
559 void MainWindow::setNumSamples(uint64_t s)
560 {
561         numSamples = s;
562 }
563
564 uint64_t MainWindow::getNumSamples(void)
565 {
566         return numSamples;
567 }
568
569 void MainWindow::on_action_New_triggered()
570 {
571         for (int i = 0; i < NUMCHANNELS; ++i) {
572                 if (dockWidgets[i]) {
573                         /* TODO: Check if all childs are also killed. */
574                         delete dockWidgets[i];
575                         dockWidgets[i] = NULL;
576                 }
577         }
578
579         ui->comboBoxLA->clear();
580         ui->comboBoxLA->addItem(tr("No LA detected"));
581
582         ui->labelChannels->setText(tr("Channels: "));
583         ui->labelChannels->setEnabled(false);
584
585         ui->comboBoxSampleRate->clear();
586         ui->comboBoxSampleRate->setEnabled(false);
587
588         ui->comboBoxNumSamples->clear();
589         ui->comboBoxNumSamples->setEnabled(false);
590
591         ui->labelSampleStart->setText(tr("Start sample: "));
592         ui->labelSampleStart->setEnabled(false);
593
594         ui->labelSampleEnd->setText(tr("End sample: "));
595         ui->labelSampleEnd->setEnabled(false);
596
597         ui->labelZoomFactor->setText(tr("Zoom factor: "));
598         ui->labelZoomFactor->setEnabled(false);
599
600         ui->action_Save_as->setEnabled(false);
601         ui->action_Get_samples->setEnabled(false);
602
603         setNumChannels(0);
604
605         /* TODO: More cleanups. */
606         /* TODO: Free sample buffer(s). */
607 }
608
609 uint8_t *MainWindow::getDemoSampleBuffer(void)
610 {
611         /* TODO */
612         return NULL;
613 }
614
615 void MainWindow::configChannelTitleBarLayoutChanged(int index)
616 {
617         QDockWidget::DockWidgetFeatures f =
618                 QDockWidget::DockWidgetClosable |
619                 QDockWidget::DockWidgetMovable |
620                 QDockWidget::DockWidgetFloatable;
621
622         configChannelTitleBarLayout = index;
623
624         if (configChannelTitleBarLayout == 0)
625                 f |= QDockWidget::DockWidgetVerticalTitleBar;
626
627         for (int i = 0; i < getNumChannels(); ++i)
628                 dockWidgets[i]->setFeatures(f);
629 }