/*
    schwingungen
    Copyright (C) 2003  Andreas Dangel <adabolo at adabolo.de>

    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#include <wx/wx.h>
#include <math.h>
#include "schwingungen.h"


IMPLEMENT_APP(SchwingungenApp)

enum {
  ID_Quit = wxID_HIGHEST,
  ID_Generator,
  ID_Punktiert,
  ID_Parameter,
  ID_Settings_OK,
  ID_Settings_Cancel,
  ID_VerPlus,
  ID_VerMinus,
  ID_VerNix,
  ID_Skala,
  ID_Save
};


bool SchwingungenApp::OnInit() {
  frame = new wxFrame((wxFrame *) NULL, -1, "Schwingungen");

  // Menü erstellen
  wxMenuBar *menuBar = new wxMenuBar;
  wxMenu *menuFile = new wxMenu;
  wxMenu *menuSettings = new wxMenu;

  menuFile->Append(ID_Save, "Abbild speichern...");
  menuFile->AppendSeparator();
  menuFile->Append(ID_Quit, "&Beenden");

  generator = FALSE;
  menuSettings->Append(ID_Generator, "Generator", "Generator einzeichnen", TRUE);
  punktiert = FALSE;
  menuSettings->Append(ID_Punktiert, "Punktiert", "Punkte nicht verbinden", TRUE);
  skala = FALSE;
  menuSettings->Append(ID_Skala, "Skala", "Skala ein- bzw. ausschalten", TRUE);
  menuSettings->AppendSeparator();
  menuSettings->Append(ID_VerPlus, "Verstärkung höher", "Verstärkung erhöhen");
  menuSettings->Append(ID_VerMinus, "Verstärkung niedriger", "Verstärkung verniedrigen");
  menuSettings->Append(ID_VerNix, "Verstärkung aus", "Verstärkung ausschalten");
  menuSettings->AppendSeparator();
  menuSettings->Append(ID_Parameter, "Parameter", "Parameter einstellen");

  menuBar->Append(menuFile, "&Datei");
  menuBar->Append(menuSettings, "&Einstellungen");

  frame->SetMenuBar(menuBar);

  // data initialisieren
  data.l = 5 * pow(10, -3);  // 5 mH
  data.c = 2 * pow(10, -6);  // 2 µF
  data.r = 5; // 5 Ohm
  data.f = pow(10, 4) / (2 * M_PI); // ca. 1590 Hz
  data.u_1_dach = 1; // 1 V
  data.delta_t = 1 * pow(10, -5); // 0.00001 s = 0.01 ms
  data.start_t = 0; // 0s
  data.start_i = 0; // 0A
  data.start_q = 0; // 0C

  data.verstaerkung = 10;

  frame->Show(TRUE);
  SetTopWindow(frame);

  Connect(frame->GetId(), wxEVT_PAINT, (wxObjectEventFunction) &SchwingungenApp::OnPaint);
  Connect(ID_Quit, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &SchwingungenApp::OnQuit);
  Connect(ID_Generator, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &SchwingungenApp::OnGenerator);
  Connect(ID_Punktiert, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &SchwingungenApp::OnPunktiert);
  Connect(ID_Skala, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &SchwingungenApp::OnSkala);
  Connect(ID_Parameter, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &SchwingungenApp::OnParameter);
  Connect(ID_Settings_OK, wxEVT_COMMAND_BUTTON_CLICKED, (wxObjectEventFunction) &SchwingungenApp::OnSettingsOK);
  Connect(ID_Settings_Cancel, wxEVT_COMMAND_BUTTON_CLICKED, (wxObjectEventFunction) &SchwingungenApp::OnSettingsCancel);
  Connect(ID_VerPlus, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &SchwingungenApp::OnVerPlus);
  Connect(ID_VerMinus, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &SchwingungenApp::OnVerMinus);
  Connect(ID_VerNix, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &SchwingungenApp::OnVerNix);
  Connect(ID_Save, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &SchwingungenApp::OnSave);


  return true;
}

void SchwingungenApp::Draw(wxDC& dc) {
  int width, height;
  int x = 0;
  frame->GetClientSize(&width, &height);

  //wxPaintDC dc(frame);

  dc.SetBackground(wxBrush(wxColour(255,255,255), wxSOLID));
  dc.Clear();
  dc.SetPen(wxPen(wxColour(0,0,0), 1, wxSOLID));
  dc.BeginDrawing();
  dc.DrawLine(0, height/2, width-1, height/2);

  if (skala) {
    int i;

    // Teil 1: y-Achse
    double y = 0;

    // 10 Markierungen nach oben und 10 nach unten...
    for (i = 1; i <= 10; i++) {
      y = i * 50. / data.verstaerkung;

      // positive Skala
      dc.DrawLine(0, height/2 - y * data.verstaerkung, 5, height/2 - y * data.verstaerkung);
      dc.DrawText(wxString::Format("%.2f V", y), 10, height/2 - y * data.verstaerkung - 9);
      // negative Skala
      dc.DrawLine(0, height/2 + y * data.verstaerkung, 5, height/2 + y * data.verstaerkung);
      dc.DrawText(wxString::Format("-%.2f V", y), 10, height/2 + y * data.verstaerkung - 9);
    }

    // Teil 2: x-Achse

    // alle 100 Pixel eine Markierung
    for (i = 100; i <= width; i += 100) {
      dc.DrawLine(i, height/2, i, height/2 + 5);
      dc.DrawText(wxString::Format("%.2f ms", i * data.delta_t * pow(10, 3)), i - 20, height/2 + 5 + 2);
    }

  }

  //dc.DrawText(wxString::Format("Verstärkung: %i", data.verstaerkung), 10, 10);


  // Konstanten
  double L = data.l;
  double C = data.c;

  double f = data.f;
  double omega = 2 * M_PI * f;

  double delta_t = data.delta_t;
  int wiederholungen = width;

  double U_1_dach = data.u_1_dach;
  double R = data.r;

  // Startwerte
  double t = data.start_t;
  double t_ende = t + wiederholungen * delta_t;
  x = - int(t / delta_t);
  t = 0.0;
  double I = data.start_i;
  double Q = data.start_q;

  double U_1_alt = U_1_dach * sin(omega * t);
  double U_C_alt = Q / C;

  while (t <= t_ende) {
    double U_1 = U_1_dach * sin(omega * t);  // Generator
    double U_C = Q / C;                      // Kondensator
    double delta_I = (U_1/L - Q/(L*C) - R*I/L) * delta_t; // Änderung delta_I
    I = I + delta_I;      // neue Stromstärke
    Q = Q + I * delta_t;  // neue Ladung
    t = t + delta_t;      // neue Zeit

    // Ausgabe
    if (punktiert == FALSE) {
      dc.DrawLine(x-1, height/2 - U_C_alt * data.verstaerkung, x, height/2 - U_C * data.verstaerkung);
    } else {
      dc.DrawPoint(x, height/2 - U_C * data.verstaerkung);
    }

    if (generator) {
      dc.SetPen(wxPen(wxColour(255,0,0), 1, wxSOLID));
      if (punktiert == FALSE) {
        dc.DrawLine(x-1, height/2 - U_1_alt * data.verstaerkung, x, height/2 - U_1 * data.verstaerkung);
      } else {
        dc.DrawPoint(x, height/2 - U_1 * data.verstaerkung);
      }
    }

    dc.SetPen(wxPen(wxColour(0,0,0), 1, wxSOLID));


    x++;
    U_C_alt = U_C;
    U_1_alt = U_1;
  }

  dc.EndDrawing();
}

void SchwingungenApp::OnPaint(wxPaintEvent& event) {
  wxPaintDC dc(frame);
  Draw(dc);
}

void SchwingungenApp::OnQuit(wxCommandEvent& event) {
  frame->Close(TRUE);
}

void SchwingungenApp::OnGenerator(wxCommandEvent& event) {
  if (generator == FALSE) {
    generator = TRUE;
  } else {
    generator = FALSE;
  }

  frame->Refresh();
}

void SchwingungenApp::OnPunktiert(wxCommandEvent& event) {
  if (punktiert == FALSE) {
    punktiert = TRUE;
  } else {
    punktiert = FALSE;
  }

  frame->Refresh();
}

void SchwingungenApp::OnParameter(wxCommandEvent& event) {
  dialog = new wxDialog(frame, -1, wxString("Parameter"));
  wxBoxSizer * topsizer = new wxBoxSizer(wxVERTICAL);
  wxFlexGridSizer * grid = new wxFlexGridSizer(4); // 4 columns
  wxBoxSizer * buttons = new wxBoxSizer(wxHORIZONTAL);

  //Controls
  wxTextCtrl *spule = new wxTextCtrl(dialog, -1);
  spule->SetValue(wxString::Format("%f", data.l * pow(10, 3)));
  wxTextCtrl *kondensator = new wxTextCtrl(dialog, -1);
  kondensator->SetValue(wxString::Format("%f", data.c * pow(10, 6)));
  wxTextCtrl *widerstand = new wxTextCtrl(dialog, -1);
  widerstand->SetValue(wxString::Format("%f", data.r));
  wxTextCtrl *frequenz = new wxTextCtrl(dialog, -1);
  frequenz->SetValue(wxString::Format("%f", data.f));
  wxTextCtrl *u_1_dach = new wxTextCtrl(dialog, -1);
  u_1_dach->SetValue(wxString::Format("%f", data.u_1_dach));
  wxTextCtrl *delta_t = new wxTextCtrl(dialog, -1);
  delta_t->SetValue(wxString::Format("%f", data.delta_t * pow(10, 3)));
  wxTextCtrl *verstaerker = new wxTextCtrl(dialog, -1);
  verstaerker->SetValue(wxString::Format("%f", data.verstaerkung));
  wxTextCtrl *start_t = new wxTextCtrl(dialog, -1);
  start_t->SetValue(wxString::Format("%f", data.start_t));
  wxTextCtrl *start_i = new wxTextCtrl(dialog, -1);
  start_i->SetValue(wxString::Format("%f", data.start_i));
  wxTextCtrl *start_q = new wxTextCtrl(dialog, -1);
  start_q->SetValue(wxString::Format("%f", data.start_q));


  grid->Add(new wxStaticText(dialog, -1, "Spule:"));
  grid->Add(new wxStaticText(dialog, -1, " L = "));
  grid->Add(spule);
  grid->Add(new wxStaticText(dialog, -1, " mH"));

  grid->Add(new wxStaticText(dialog, -1, "Kondensator:"));
  grid->Add(new wxStaticText(dialog, -1, " C = "));
  grid->Add(kondensator);
  grid->Add(new wxStaticText(dialog, -1, " µF"));

  grid->Add(new wxStaticText(dialog, -1, "Widerstand:"));
  grid->Add(new wxStaticText(dialog, -1, " R = "));
  grid->Add(widerstand);
  grid->Add(new wxStaticText(dialog, -1, " Ohm"));

  grid->Add(new wxStaticText(dialog, -1, "Generator-Frequenz:"));
  grid->Add(new wxStaticText(dialog, -1, " f = "));
  grid->Add(frequenz);
  grid->Add(new wxStaticText(dialog, -1, " Hz"));

  grid->Add(new wxStaticText(dialog, -1, "Generator-Maximalspannung:"));
  grid->Add(new wxStaticText(dialog, -1, " U_1_dach = "));
  grid->Add(u_1_dach);
  grid->Add(new wxStaticText(dialog, -1, " V"));

  grid->Add(new wxStaticText(dialog, -1, "Zeitschritt:"));
  grid->Add(new wxStaticText(dialog, -1, " delta_t = "));
  grid->Add(delta_t);
  grid->Add(new wxStaticText(dialog, -1, " ms"));

  grid->Add(new wxStaticText(dialog, -1, "Verstärkung:"));
  grid->Add(new wxStaticText(dialog, -1, " "));
  grid->Add(verstaerker);
  grid->Add(new wxStaticText(dialog, -1, " x"));

  grid->Add(new wxStaticText(dialog, -1, "Startwert für t:"));
  grid->Add(new wxStaticText(dialog, -1, " t = "));
  grid->Add(start_t);
  grid->Add(new wxStaticText(dialog, -1, " s"));

  grid->Add(new wxStaticText(dialog, -1, "Starwert für die Stromstärke:"));
  grid->Add(new wxStaticText(dialog, -1, " I = "));
  grid->Add(start_i);
  grid->Add(new wxStaticText(dialog, -1, " A"));

  grid->Add(new wxStaticText(dialog, -1, "Startwert für die Ladung:"));
  grid->Add(new wxStaticText(dialog, -1, " Q = "));
  grid->Add(start_q);
  grid->Add(new wxStaticText(dialog, -1, " C"));


  buttons->Add(new wxButton(dialog, ID_Settings_OK, "OK"), 1, wxRIGHT, 5);
  buttons->Add(new wxButton(dialog, ID_Settings_Cancel, "Abbrechen"), 1, wxLEFT, 5);


  topsizer->Add(grid, 0, wxALL, 10);
  topsizer->Add(buttons, 0, wxALIGN_CENTER | wxALL, 5);

  dialog->SetAutoLayout(TRUE);
  dialog->SetSizer(topsizer);
  topsizer->Fit(dialog);

  if (dialog->ShowModal() == ID_Settings_OK) {
    //Parameter speichern

    if (spule->GetValue().ToDouble(&data.l)) {
      data.l *= pow(10, -3);
    }
    if (kondensator->GetValue().ToDouble(&data.c)) {
      data.c *= pow(10, -6);
    }
    widerstand->GetValue().ToDouble(&data.r);
    frequenz->GetValue().ToDouble(&data.f);
    u_1_dach->GetValue().ToDouble(&data.u_1_dach);
    if (delta_t->GetValue().ToDouble(&data.delta_t)) {
      data.delta_t *= pow(10, -3);
    }
    verstaerker->GetValue().ToDouble(&data.verstaerkung);
    start_t->GetValue().ToDouble(&data.start_t);
    start_i->GetValue().ToDouble(&data.start_i);
    start_q->GetValue().ToDouble(&data.start_q);

  }

  // neu zeichnen
  frame->Refresh();
}

void SchwingungenApp::OnSettingsOK(wxCommandEvent &event) {
  dialog->EndModal(ID_Settings_OK);
}

void SchwingungenApp::OnSettingsCancel(wxCommandEvent &event) {
  dialog->EndModal(ID_Settings_Cancel);
}

void SchwingungenApp::OnVerPlus(wxCommandEvent& event) {
  data.verstaerkung += 5;
  frame->Refresh();
}

void SchwingungenApp::OnVerMinus(wxCommandEvent& event) {
  data.verstaerkung -= 5;
  if (data.verstaerkung <= 0) {
    data.verstaerkung = 1;
  }
  frame->Refresh();
}

void SchwingungenApp::OnVerNix(wxCommandEvent &event) {
  data.verstaerkung = 1;
  frame->Refresh();
}

void SchwingungenApp::OnSkala(wxCommandEvent &event) {
  if (skala == FALSE) {
    skala = TRUE;
  } else {
    skala = FALSE;
  }
  frame->Refresh();
}

void SchwingungenApp::OnSave(wxCommandEvent &event) {
  wxString filename;
  wxFileDialog * file = new wxFileDialog(frame);
  file->SetWildcard("BMP Dateien (*.bmp)|*.bmp|Alle Dateien (*.*)|*.*");
  file->SetDirectory(::wxGetWorkingDirectory());
  if (file->ShowModal() == wxID_OK) {
    filename = file->GetDirectory();
    filename += "/";
    filename += file->GetFilename();
  } else {
    return;
  }

  int width, height;
  frame->GetClientSize(&width, &height);

  wxMemoryDC mem;
  wxBitmap bitmap(width, height);
  mem.SelectObject(bitmap);
  Draw(mem);

  bitmap.SaveFile(filename, wxBITMAP_TYPE_BMP);
}


syntax highlighted by Code2HTML, v. 0.9.1