diff --git a/src/recordpanel.cpp b/src/recordpanel.cpp
new file mode 100644
--- /dev/null
+++ b/src/recordpanel.cpp
@@ -0,0 +1,286 @@
+/*
+  Copyright © 2017 Hasan Yavuz Özderya
+
+  This file is part of serialplot.
+
+  serialplot is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  serialplot is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with serialplot.  If not, see .
+*/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "recordpanel.h"
+#include "ui_recordpanel.h"
+#include "setting_defines.h"
+
+RecordPanel::RecordPanel(DataRecorder* recorder, ChannelManager* channelMan, QWidget *parent) :
+    QWidget(parent),
+    ui(new Ui::RecordPanel),
+    recordToolBar(tr("Record Toolbar")),
+    recordAction(QIcon::fromTheme("media-record"), tr("Record"), this)
+{
+    overwriteSelected = false;
+    _recorder = recorder;
+    _channelMan = channelMan;
+
+    ui->setupUi(this);
+
+    recordToolBar.setObjectName("tbRecord");
+
+    recordAction.setCheckable(true);
+    recordToolBar.addAction(&recordAction);
+    ui->pbRecord->setDefaultAction(&recordAction);
+
+    connect(ui->pbBrowse, &QPushButton::clicked,
+            this, &RecordPanel::selectFile);
+    connect(&recordAction, &QAction::triggered,
+            this, &RecordPanel::onRecord);
+
+    connect(ui->cbRecordPaused, SIGNAL(toggled(bool)),
+            this, SIGNAL(recordPausedChanged(bool)));
+
+    connect(ui->cbDisableBuffering, &QCheckBox::toggled,
+            [this](bool enabled)
+            {
+                _recorder->disableBuffering = enabled;
+            });
+}
+
+RecordPanel::~RecordPanel()
+{
+    delete ui;
+}
+
+QToolBar* RecordPanel::toolbar()
+{
+    return &recordToolBar;
+}
+
+bool RecordPanel::isRecording()
+{
+    return recordAction.isChecked();
+}
+
+bool RecordPanel::recordPaused()
+{
+    return ui->cbRecordPaused->isChecked();
+}
+
+bool RecordPanel::selectFile()
+{
+    QString fileName = QFileDialog::getSaveFileName(
+        parentWidget(), tr("Select recording file"));
+
+    if (fileName.isEmpty())
+    {
+        return false;
+    }
+    else
+    {
+        selectedFile = fileName;
+        ui->lbFileName->setText(selectedFile);
+        overwriteSelected = QFile::exists(fileName);
+        return true;
+    }
+}
+
+void RecordPanel::onRecord(bool start)
+{
+    if (!start)
+    {
+        stopRecording();
+        return;
+    }
+
+    bool canceled = false;
+    if (ui->leSeparator->text().isEmpty())
+    {
+        QMessageBox::critical(this, "Error",
+                              "Column separator cannot be empty! Please select a separator.");
+        ui->leSeparator->setFocus(Qt::OtherFocusReason);
+        canceled = true;
+    }
+
+    // check file name
+    if (!canceled && selectedFile.isEmpty() && !selectFile())
+    {
+        canceled = true;
+    }
+
+    if (!canceled && !overwriteSelected && QFile::exists(selectedFile))
+    {
+        if (ui->cbAutoIncrement->isChecked())
+        {
+            // TODO: should we increment even if user selected to replace?
+            canceled = !incrementFileName();
+        }
+        else
+        {
+            canceled = !confirmOverwrite(selectedFile);
+        }
+    }
+
+    if (canceled)
+    {
+        recordAction.setChecked(false);
+    }
+    else
+    {
+        overwriteSelected = false;
+        startRecording();
+    }
+}
+
+bool RecordPanel::incrementFileName(void)
+{
+    QFileInfo fileInfo(selectedFile);
+
+    QString base = fileInfo.completeBaseName();
+    QRegularExpression regex("(.*?)(\\d+)(?!.*\\d)(.*)");
+    auto match = regex.match(base);
+
+    if (match.hasMatch())
+    {
+        bool ok;
+        int fileNum = match.captured(2).toInt(&ok);
+        base = match.captured(1) + QString::number(fileNum + 1) + match.captured(3);
+    }
+    else
+    {
+        base += "_1";
+    }
+
+    QString suffix = fileInfo.suffix();;
+    if (!suffix.isEmpty())
+    {
+        suffix = "." + suffix;
+    }
+
+    QString autoFileName = fileInfo.path() + "/" + base + suffix;
+
+    // check if auto generated file name exists, ask user another name
+    if (QFile::exists(autoFileName))
+    {
+        if (!confirmOverwrite(autoFileName))
+        {
+            return false;
+        }
+    }
+    else
+    {
+        selectedFile = autoFileName;
+    }
+
+    ui->lbFileName->setText(selectedFile);
+    return true;
+}
+
+bool RecordPanel::confirmOverwrite(QString fileName)
+{
+    // prepare message box
+    QMessageBox mb(parentWidget());
+    mb.setWindowTitle(tr("File Already Exists"));
+    mb.setIcon(QMessageBox::Warning);
+    mb.setText(tr("File (%1) already exists. How to continue?").arg(fileName));
+
+    auto bCancel    = mb.addButton(QMessageBox::Cancel);
+    auto bOverwrite = mb.addButton(tr("Overwrite"), QMessageBox::DestructiveRole);
+    mb.addButton(tr("Select Another File"), QMessageBox::YesRole);
+
+    mb.setEscapeButton(bCancel);
+
+    // show message box
+    mb.exec();
+
+    if (mb.clickedButton() == bCancel)
+    {
+        return false;
+    }
+    else if (mb.clickedButton() == bOverwrite)
+    {
+        selectedFile = fileName;
+        return true;
+    }
+    else                    // select button
+    {
+        return selectFile();
+    }
+}
+
+void RecordPanel::startRecording(void)
+{
+    QStringList channelNames;
+    if (ui->cbHeader->isChecked())
+    {
+        channelNames = _channelMan->infoModel()->channelNames();
+    }
+    _recorder->startRecording(selectedFile, getSeparator(), channelNames);
+    emit recordStarted();
+}
+
+void RecordPanel::stopRecording(void)
+{
+    emit recordStopped();
+    _recorder->stopRecording();
+}
+
+void RecordPanel::onPortClose()
+{
+    if (recordAction.isChecked() && ui->cbStopOnClose->isChecked())
+    {
+        stopRecording();
+        recordAction.setChecked(false);
+    }
+}
+
+QString RecordPanel::getSeparator() const
+{
+    QString sep = ui->leSeparator->text();
+    sep.replace("\\t", "\t");
+    return sep;
+}
+
+void RecordPanel::saveSettings(QSettings* settings)
+{
+    settings->beginGroup(SettingGroup_Record);
+    settings->setValue(SG_Record_AutoIncrement, ui->cbAutoIncrement->isChecked());
+    settings->setValue(SG_Record_RecordPaused, ui->cbRecordPaused->isChecked());
+    settings->setValue(SG_Record_StopOnClose, ui->cbStopOnClose->isChecked());
+    settings->setValue(SG_Record_Header, ui->cbHeader->isChecked());
+    settings->setValue(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked());
+    settings->setValue(SG_Record_Separator, ui->leSeparator->text());
+    settings->endGroup();
+}
+
+void RecordPanel::loadSettings(QSettings* settings)
+{
+    settings->beginGroup(SettingGroup_Record);
+    ui->cbAutoIncrement->setChecked(
+        settings->value(SG_Record_AutoIncrement, ui->cbAutoIncrement->isChecked()).toBool());
+    ui->cbRecordPaused->setChecked(
+        settings->value(SG_Record_RecordPaused, ui->cbRecordPaused->isChecked()).toBool());
+    ui->cbStopOnClose->setChecked(
+        settings->value(SG_Record_StopOnClose, ui->cbStopOnClose->isChecked()).toBool());
+    ui->cbHeader->setChecked(
+        settings->value(SG_Record_Header, ui->cbHeader->isChecked()).toBool());
+    ui->cbDisableBuffering->setChecked(
+        settings->value(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked()).toBool());
+    ui->leSeparator->setText(settings->value(SG_Record_Separator, ui->leSeparator->text()).toString());
+    settings->endGroup();
+}