///////////////////////////////////////////////////////////////////////////////////////////////////
// lens.cpp
// Пример диалогового окна для задания конструктивных параметров и отображения оптической системы.
//
// Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru
// Университет ИТМО
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "lens.h"
#include <QDialog>
#include <QPainter>
#include <QMessageBox>
#include <QTextDocument>
#include <QUrl>
#define PI 3.14159265358979
#include <fstream>
#include <cmath>
#include <stdexcept>
using namespace std;
//-------------------------------------------------------------------------------------------------
DQtOpticalSystem::DQtOpticalSystem(QWidget* parent, Qt::WindowFlags f)
: QDialog(parent, f)
{
m_ui.setupUi(this);
// задаем перехват в текущем классе для всех событий от m_qDrawArea (виджет для рисования)
m_ui.m_qDrawArea->installEventFilter(this);
// создаем пункт меню для создания новой поверхности в таблице
m_pqAddSurface = new QAction("Добавить новую поверхность перед выделенной", this );
m_pqAddSurface->setShortcut(QKeySequence("Ctrl+A"));
// создаем пункт меню для удаления поверхности в таблице
m_pqDeleteSurface = new QAction("Удалить выделенную поверхность", this );
m_pqDeleteSurface->setShortcut(QKeySequence("Ctrl+D"));
// создаем контекстное меню для таблицы и записываем туда пункты "добавить" и "удалить"
m_ui.m_qOSParams->setContextMenuPolicy(Qt::ActionsContextMenu);
m_ui.m_qOSParams->addAction(m_pqAddSurface);
m_ui.m_qOSParams->addAction(m_pqDeleteSurface);
// создаем связь между сигналом(событием) от объекта и функцией-обработкой события
connect(m_ui.m_pqMainTabs, SIGNAL(currentChanged(int )), this, SLOT(OnTabChange()));
connect(m_pqAddSurface, SIGNAL(triggered()), this, SLOT(OnAddSurface()));
connect(m_pqDeleteSurface, SIGNAL(triggered()), this, SLOT(OnDeleteSurface()));
// задаем ширину колонок у таблицы (добавляем запас на ширину рамки)
int w=m_ui.m_qOSParams->width()/4.2;
for(int i=0; i<=3; ++i)
{
m_ui.m_qOSParams->setColumnWidth(i, w);
}
/////////////////////////////
// для тестирования - сразу создаем одиночную линзу,
// в дальнейшем можно заменить на чтение из файла, передачу данных из других диалогов и т.д.
m_oSystem.resize(2);
m_oSystem[0].r=100;
m_oSystem[0].d=10;
m_oSystem[0].h=15;
m_oSystem[0].n=1.5;
m_oSystem[1].r=-100;
m_oSystem[1].h=15;
/////////////////////////////
// записываем параметры оптической системы в таблицу
SetOSParam();
}
//-------------------------------------------------------------------------------------------------
DQtOpticalSystem::~DQtOpticalSystem()
{
}
//-------------------------------------------------------------------------------------------------
// обработка смены закладки
void
DQtOpticalSystem::OnTabChange()
{
// tab = 0 - конструктивные параметры О.С.
// tab = 1 - рисование О.С.
// если переходим на любую закладку кроме конструктивных парамеров - записываем данные из таблицы и проверям ошибки
if(m_ui.m_pqMainTabs->currentIndex()!=0)
{
try
{
GetOSParams();
}
catch(const std::exception& oErr)
{
// если возникла ошибка - выдаем сообщение, и возвращаемся на закладку с констр.параметрами
QMessageBox::critical(this, "Неправильно заданы конструктивные параметры", oErr.what());
m_ui.m_pqMainTabs->setCurrentIndex(0);
}
}
}
//-------------------------------------------------------------------------------------------------
// обработка всех событий на диалоговом окне (перегруженная функция)
// перехватывается ТОЛЬКО событие рисования на закладке с рисованием)
bool
DQtOpticalSystem::eventFilter(QObject* pObj_p, QEvent* pEvent_p)
{
// если объект, генерирующие событие - виджет для рисования
if(pObj_p == m_ui.m_qDrawArea)
{
// если тип события - рисование
if( pEvent_p->type() == QEvent::Paint )
{
try
{
// получаем параметры О.С.
GetOSParams();
// создаем QPainter, свзяанный с областью рисования
QPainter p( m_ui.m_qDrawArea );
// задаем парамеры у QPainter
p.setRenderHint(QPainter::Antialiasing);
// рисуем в QPainter
DrawOs(p);
}
// если возникла ошибка - ничего не рисуем, т.к. ошибки проверяются в OnTabChange
catch(...)
{}
}
}
// все события передаются дальше
return QDialog::eventFilter(pObj_p, pEvent_p);
}
//-------------------------------------------------------------------------------------------------
// рисование О.С. в QPainter
void
DQtOpticalSystem::DrawOs(QPainter& p)
{
// вычисляем общую высоту и ширину оптической системы
double dOSHeight=0, dOSWidth=0, dWinHeight=0, dWinWidth=0;
for(int i=0; i<m_oSystem.size(); ++i)
{
dOSWidth+=m_oSystem[i].d;
dOSHeight=std::max(dOSHeight, m_oSystem[i].h);
}
dOSHeight*=2;
// вычисляем высоту и ширину окна в реальных координатах, с учетом высоты и ширины О.С.
// и коэффициента пропорциональности между высотой/шириной окна в px (для правильного масштаба по осям)
dWinWidth=dOSWidth;
dWinHeight=dOSHeight;
double dScale=double(p.viewport().width())/double(p.viewport().height());
if(dWinWidth/dWinHeight > dScale)
dWinHeight=dWinWidth/dScale;
else
dWinWidth=dWinHeight*dScale;
double dH=dWinHeight*0.1;
double dW=dWinWidth*0.1;
// задаем параметры окна в реальных координатах
QRect qRegion(-dW, -(dWinHeight/2+dH), dWinWidth+dW*2, dWinHeight+dH*2);
p.setWindow(qRegion);
// запоминаем старые QPen и QBrush
QPen oldPen=p.pen();
QBrush oldBrush=p.brush();
// создаем новые QPen и QBrush для линзы и оси
QPen qLensPen(QColor(102,102,153));
qLensPen.setCosmetic(true);
QBrush qLensBrush(QColor(102,209,230));
QPen qAxisPen(QColor(0,0,0));
qAxisPen.setStyle(Qt::DashDotLine);
qAxisPen.setCosmetic(true);
// устанавливаем для QPainter текущие QPen и QBrush для рисования линз
p.setPen(qLensPen);
p.setBrush(qLensBrush);
// рисование О.С.
double x=0.;
for(int i=0; i<m_oSystem.size(); ++i)
{
QPainterPath qPath;
// если следующая среда - не воздух, рисуем линзу
if(m_oSystem[i].n > 1)
{
// создаем контур линзы
DrawLens(qPath, x, i);
// отображем контур линзы текущими QPen и QBrush
p.drawPath(qPath);
}
x+=m_oSystem[i].d;
}
// рисуем ось
p.setPen(qAxisPen);
p.drawLine(QPointF(-dW,0), QPointF(dOSWidth+dW,0));
// возвращаем старые QPen и QBrush
p.setPen(oldPen);
p.setBrush(oldBrush);
}
//-------------------------------------------------------------------------------------------------
// создание замкнутого контура одной линзы
// qPath_p - контур линзы
// dX_p - реальная координата X линзы
// iIndex_p - номер первой поверхности линзы в m_oSystem
void
DQtOpticalSystem::DrawLens(QPainterPath& qPath_p, double dX_p, int iIndex_p)
{
// для удобства создаем новы переменные. h1 и р2 будут меняться, поэтому они не ссылки.
double h1=m_oSystem[iIndex_p].h;
double& r1=m_oSystem[iIndex_p].r;
double h2=m_oSystem[iIndex_p+1].h;
double& r2=m_oSystem[iIndex_p+1].r;
double& d=m_oSystem[iIndex_p].d;
// нахидим высоту линзы (max из h1 и h2) и определяем высоты поверхностей в зависимости от знака радиуса
double h=max(h1,h2);
if(r1>0)
h1=h;
if(r2<0)
h2=h;
// вычисляем стрелку прогиба поверхностей
double dArrow1=fabs(r1) - sqrt(r1*r1 - h1*h1);
if(r1<0)
dArrow1=-dArrow1;
double dArrow2=fabs(r2) - sqrt(r2*r2 - h2*h2);
if(r2<0)
dArrow2=-dArrow2;
// первая точка контура - верхняя точка поверхности
qPath_p.moveTo(dX_p + dArrow1, -h);
// если r<0 - рисуем фаску сверху
if(r1<0 && h1<h2)
qPath_p.lineTo(dX_p + dArrow1, -h1);
// рисуем 1ю поверхность (сверху вниз)
double dAngle=asin(h1/r1)*180/PI; // угол между осью и крайней точкой поверхности
if(r1>0)
qPath_p.arcTo(dX_p, -r1, r1*2, r1*2, 180-dAngle, dAngle*2);
else
qPath_p.arcTo(dX_p+2*r1, r1, -r1*2, -r1*2, -dAngle, dAngle*2);
// параметы функции: лев.точка, верхняя точка, ширина, высота (все для описанного вокруг полной окружности прямоугольника)
// начальный угол, длина дуги в градусах
// если r<0 - рисуем фаску снизу
if(r1<0 && h1<h2)
qPath_p.lineTo(dX_p + dArrow1, h);
// добавляем нижнюю линию между поверхностями
qPath_p.lineTo(dX_p + d + dArrow2, h);
dX_p+=d;
// если r>0 - рисуем фаску снизу
if(r2>0 && h2<h1)
qPath_p.lineTo(dX_p + dArrow2, h2);
// рисуем 2ю поверхность (сверху вниз)
dAngle=asin(h2/r2)*180/PI;
if(r2>0)
qPath_p.arcTo(dX_p, -r2, r2*2, r2*2, 180+dAngle, -dAngle*2);
else
qPath_p.arcTo(dX_p+2*r2, r2, -r2*2, -r2*2, dAngle, -dAngle*2);
// если r>0 - рисуем фаску сверху
if(r2>0 && h2<h1)
qPath_p.lineTo(dX_p + dArrow2, -h);
// замыкаем контур
qPath_p.closeSubpath();
}
//-------------------------------------------------------------------------------------------------
// записать параметры О.С. из таблицы в m_oSystem
void
DQtOpticalSystem::GetOSParams()
{
int iRowCount=m_ui.m_qOSParams->rowCount();
m_oSystem.resize(iRowCount);
for(int i=0; i<iRowCount; ++i)
{
// берем все данные из таблицы как double
m_oSystem[i].r=m_ui.m_qOSParams->item(i, 0)->data(0).toDouble();
m_oSystem[i].d=m_ui.m_qOSParams->item(i, 1)->data(0).toDouble();
m_oSystem[i].h=m_ui.m_qOSParams->item(i, 2)->data(0).toDouble();
m_oSystem[i].n=m_ui.m_qOSParams->item(i, 3)->data(0).toDouble();
// проверяем правильность ввода данных, если что-то неправильно - генерируем исключение
if(m_oSystem[i].h<0)
throw std::runtime_error("Световая высота должны быть больше 0");
if(fabs(m_oSystem[i].h)>=fabs(m_oSystem[i].r))
throw std::runtime_error("Световая высота должнабыть меньше чем радиус");
if(m_oSystem[i].n<1)
throw std::runtime_error("Показатель преломления должен быть больше 1");
}
}
//-------------------------------------------------------------------------------------------------
// записать параметры О.С. из m_oSystem в таблицу
void
DQtOpticalSystem::SetOSParam()
{
// удаляем из таблицы все старые данные
for(int i=0; i<=m_ui.m_qOSParams->rowCount(); ++i)
{
m_ui.m_qOSParams->removeRow(i);
}
// добавляем заново все поверхности
for(int i=0; i<m_oSystem.size(); ++i)
{
AddOSParamRow(m_oSystem[i], i);
}
// запрещаем сортировку
m_ui.m_qOSParams->setSortingEnabled(false);
}
//-------------------------------------------------------------------------------------------------
// добавляет одну поверхность в таблицу с конструктивными параметрами
// oSurf_p - параметры поверхности, которая будет добавлена
// iRow_p - номер, под которым будет добавлена поверхность
void
DQtOpticalSystem::AddOSParamRow(const OSurface& oSurf_p, int iRow_p)
{
m_ui.m_qOSParams->insertRow(iRow_p);
m_ui.m_qOSParams->setItem(iRow_p, 0, CreateTableItem(oSurf_p.r));
m_ui.m_qOSParams->setItem(iRow_p, 1, CreateTableItem(oSurf_p.d));
m_ui.m_qOSParams->setItem(iRow_p, 2, CreateTableItem(oSurf_p.h));
m_ui.m_qOSParams->setItem(iRow_p, 3, CreateTableItem(oSurf_p.n));
}
//-------------------------------------------------------------------------------------------------
// создает одну ячейку таблицы с double значением dValue_p
QTableWidgetItem*
DQtOpticalSystem::CreateTableItem(double dValue_p) const
{
// создаем новую ячейку таблицы
QTableWidgetItem* qItem = new QTableWidgetItem();
// устнавливаем параметры: ячейка доступна + ее можно выбрать + ее можно редактировать
qItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
// устанавливаем значение в ячейку как double
qItem->setData(0, QVariant::fromValue(dValue_p));
return qItem;
}
//-------------------------------------------------------------------------------------------------
// обработка добавления новой поверхности в таблицу
void
DQtOpticalSystem::OnAddSurface()
{
// создаем пустую поверхность и добавляем ее в таблицу под номером выбранной строки
// (выбранная строка перейдет на 1 строчку ниже)
OSurface oSurf;
AddOSParamRow(oSurf, m_ui.m_qOSParams->currentRow());
}
//-------------------------------------------------------------------------------------------------
// обработка удаления поверхности из таблицы
void
DQtOpticalSystem::OnDeleteSurface()
{
// удаляем выбранную строку
m_ui.m_qOSParams->removeRow(m_ui.m_qOSParams->currentRow());
}
//-------------------------------------------------------------------------------------------------