#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys, math
from PyQt4 import QtGui, QtCore

# Перед тем, как изучать код, рекомендую запустить программу
# и увидеть, как она работает. Так будет понятнее.
# Не забудьте ввесити во все поля численные значения. Хотя бы нули.

class TrajectoryDrawer(QtGui.QWidget):
    
    def __init__(self):
        super(TrajectoryDrawer, self).__init__()
        
        self.initUI()
        
    def initUI(self):
        grid = QtGui.QGridLayout() # Выравнивание виджетов по элементам сетки.

        variables = ['x1', 'y1', 'vx1', 'vy1', 'm1', 'x2', 'y2', 'vx2', 'vy2', 'm2'] # Имена параметров.
        self.editDict = {}
        for ind, v in enumerate(variables): # ind и v -- номер и индекс параметра
            grid.addWidget(QtGui.QLabel(v, self), ind, 0) # Добавляет в сетку текстовую метку с именем парметра
                        #ind -- номер строки в сетке, 0 -- номер колонки.
            self.editDict[v] = QtGui.QLineEdit(self) # Сохраняем в словаре виджет текстового ввода для параметра v.
            self.editDict[v].setText('1') # Пусть при запуске программ во всех полях ввода будет стоять 1,
                                          # чтобы меньше было вводить при отладке.
            grid.addWidget(self.editDict[v], ind, 1) # и добавляем его на сетку с коориднатами (ind, 1)

        self.button = QtGui.QPushButton("Run!", self) # Кнопка запуска с надписью "Run!"
        grid.addWidget(self.button, ind+1, 0, 1, 2) # добавлем на сетку с координтами (ind+1, 0) и размером в
                        # 1 строку и 2 колонки.
        self.button.clicked.connect(self.run) # при нажатии кнопки будет выполяться метод run.

        hbox = QtGui.QHBoxLayout() # Горизотнальное выравнивание.
        hbox.addStretch(1) # Добавляем stretch, который отожмёт табичку с именами параметров и полями ввода вправо.
        hbox.addLayout(grid) # Затем добавить выравниваение по сетке внутрь горизотнального.
        self.setLayout(hbox) # Гравным выравниваем в окне будет hbox.

        self.setGeometry(200, 150, 700, 600)
        self.setWindowTitle('Trajectories')    
        self.show()

        self.traj = [] # Траекторий пока нет.
        self.timer = QtCore.QBasicTimer() # Таймер позволяет нам делать анимацию.

    def run(self, b): # Эта функция подкючена к назатию кнопки с надписью "Run!" и вызывается при нажатии.
        d = self.editDict
        x1 = float(d['x1'].text()) # достаём значения переменных из полей ввода.
                    # d['x1'] -- поле ввода для x1, d['x1'].text() -- содержащийся в нём текст.
        y1 = float(d['y1'].text())
        vx1 = float(d['vx1'].text())
        vy1 = float(d['vy1'].text())
        m1 = float(d['m1'].text())
        x2 = float(d['x2'].text())
        y2 = float(d['y2'].text())
        vx2 = float(d['vx2'].text())
        vy2 = float(d['vy2'].text())
        m2 = float(d['m2'].text())
        self.traj = self.makeTrajectory(x1, y1, vx1, vy1, m1, x2, y2, vx2, vy2, m2) # Генерируем новые траектории.
        self.timer.start(100, self) # запускаем таймер с интервалом 100мс (= 0.1с).
                    # по договорённости, таймер будет вызывать в переданном ему объекте (в данном случае self)
                    # метод timerEvent.
        self.step = 0 # Индекс, до которого рисуется траектория на данном шаге анимации. Вначале 0, потом растёт
                    # c шагом 1.

    def timerEvent(self, e):
        if self.step >= len(self.traj[0]): # Если шагов по траектории больше, чем точек в траектории,
            self.timer.stop() # ... то остановим таймер.
        else:
            self.step += 1; # иначе, увеличим шаг
            self.update() # метод update вызовет paintEvent и перерисует окошко.
    
    def makeTrajectory(self, x1, y1, vx1, vy1, m1, x2, y2, vx2, vy2, m2):
        # Здесь должен быть расчёт движения планет, и нужно вернуть две траектории.
        # Пока возвращаются 2 тракетории: окружности радиусами 1 и 0.6.
        # !!!! Только этот метод и нужно трогать, всё остальное будет сделано автоматически. !!!!!
        tr1 = [ (math.cos(math.pi * i / 50), math.sin(math.pi * i / 50)) for i in range(0, 100) ]
        tr2 = [ (0.6 * math.cos(math.pi * i / 50), 0.6 * math.sin(math.pi * i / 50)) for i in range(0, 100) ]
        return tr1, tr2

    def paintEvent(self, e):
        def last_n_points(trajectory, current_position, n):
            """ Возвращает кривую из n последних точек на текущий момент движения тела.
            Если в траектории ещё нет n точек, то в линии их будет столько, сколько есть на данных момент.
            Возвращаем список из объектов QPointF, так как QPainter умеет удобно ризовать линию по ним.

            Кстати, это вложенная в метод paintEvent функция, она видна только внутри него.
            """
            start_ind = max(0, current_position - n)
            return [ QtCore.QPointF(x, y) for x, y in trajectory[start_ind:current_position] ]

        p = QtGui.QPainter()
        p.begin(self)
        p.setRenderHint(QtGui.QPainter.Antialiasing, True) # Включаем сглаживание линий при рисовании.
        p.translate(250, 250) # Сдвиг координат.
        p.scale(200, -200) # Масштабирование координат.
        # Сдвинули и отмасштабировал систему координат так, чтобы можно было рисовать в прямоугольнике
        # от -1 до 1 по X и от -1 до 1 по Y.
        if self.traj: # Вначале self.traj -- пустой список, и мы не войдём в тело if'a.
            p.setPen(QtGui.QColor(255, 0, 0)) # Можно не создавать QPen, а просто передавать цвет.
            curve = last_n_points(self.traj[0], self.step, 100)
            p.drawPolyline(*curve) # метод drawPolyline принимает не список, а переменное чило аргументов.
                    # унарная * в питоне "раскрывает" из одного аргумента в последовательность аргументов.
            p.setPen(QtGui.QColor(150, 0, 0)) # более тёмный красный цвет.
            p.drawEllipse(curve[-1], 0.01, 0.01) # Рисуем кружок с центром в конце кривой и радиусом 0.01.

            # И то же самое для второй траектории.
            p.setPen(QtGui.QColor(0, 255, 0))
            curve = last_n_points(self.traj[1], self.step, 100)
            p.drawPolyline(*curve)
            p.setPen(QtGui.QColor(0, 150, 0))
            p.drawEllipse(curve[-1], 0.01, 0.01)

        p.end()
        
def main():
    app = QtGui.QApplication(sys.argv)
    ex = TrajectoryDrawer()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()