## TOV SOLVER FOR SIMPLE POLYTROPES.
## Authors: Phil Chang, Zachariah B. Etienne, Leo Werneck

# Full documentation for this module may be found in the NRPy+ tutorial Jupyter notebook:
#  Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb

##############
# TOV SOLVER #
##############

# Inputs:
# * Output data file name
# * rho_baryon_central, the central density of the TOV star.
# * n, the polytropic equation of state index. n=1 models cold, degenerate neutron star matter.
# * K_Polytrope, the polytropic constant.
# * Verbose output toggle (default = True)

# Output: An initial data file (default file name = "outputTOVpolytrope.txt") that well
#         samples the (spherically symmetric) solution both inside and outside the star.
#         It is up to the initial data module to perform the 1D interpolation to generate
#         the solution at arbitrary radius. The file has the following columns:
# Column 1: Schwarzschild radius
# Column 2: rho(r), *total* mass-energy density (as opposed to baryonic rest-mass density)
# Column 3: P(r), Pressure
# Column 4: m(r), mass enclosed
# Column 5: e^{nu(r)}, g_{tt}(r)
# Column 6: e^{4 phi(r)}, conformal factor g_{rr}(r)
# Column 7: rbar(r), Isotropic radius

# rbar refers to the isotropic radius, and
# R_Schw refers to the Schwarzschild radius

# Step 1: Import needed Python/NRPy+ modules
import numpy as np
import scipy.integrate as si
import math
import sys

# Step 2: The TOV equations
def TOV_Solver(outfile = "outputTOVpolytrope.txt",
               rho_baryon_central=0.129285,
               rho_poly_tab=[],Gamma_poly_tab=[2.0], K_poly_tab0=1.0,
               verbose = True ):

    #####################################
    # Polytropic EOS lowlevel functions #
    #####################################

    # Function     : impose_continuity_on_P_cold()
    # Author(s)    : Leo Werneck
    # Description  : This function populates the array K_poly_tab
    #                by demanding that P_cold be everywhere continuous
    # Dependencies : none
    #
    # Inputs       : eos              - named tuple containing the following:
    #                  neos           - number of EOSs to be used (single polytrope = 1)
    #                  rho_poly_tab   - values of rho distinguish one EOS from the
    #                                   other (not required for a single polytrope)
    #                  Gamma_poly_tab - values of Gamma to be used within each EOS
    #                  K_poly_tab     - uninitialized, see output variable below
    #                  P_poly_tab     - uninitialized, see function
    #                                   compute_P_poly_tab() below
    #                K_poly_tab0      - value of K_poly_tab[0], for the first EOS
    #
    # Outputs      : eos.K_poly_tab   - values of K to be used within each EOS, determined
    #                                   by imposing that P_cold be everywhere continuous

    def impose_continuity_on_P_cold(eos,K_poly_tab0):

        # A piecewise polytropic EOS is given by
        # .--------------------------------------------------------------------------.
        # |      /     K_0 * rho^(Gamma_0)     ,                rho < rho_0 ;        |
        # |      |     K_1 * rho^(Gamma_1)     ,        rho_0 < rho < rho_1 ;        |
        # |      |          ...                                 ...                  |
        # | P = <      K_j * rho^(Gamma_j)     ,    rho_(j-1) < rho < rho_j ;        |
        # |      |          ...                                 ...                  |
        # |      | K_(n-2) * rho^(Gamma_(n-2)) , rho_(neos-3) < rho < rho_(neos-2) ; |
        # |      \ K_(n-1) * rho^(Gamma_(n-1)) ,                rho > rho_(neos-2) . |
        # .--------------------------------------------------------------------------.
        # Notice that the case of a single polytropic EOS corresponds to
        # the first EOS in the boxed equation above, with no condition on
        # rho. Thus we need only return K_poly_tab0.
        eos.K_poly_tab[0] = K_poly_tab0
        if eos.neos==1:
            return

        # For the case of a piecewise polytropic EOS, emanding that P_cold
        # be everywhere continuous results in the relation:
        # .-----------------------------------------------------.
        # | K_j = K_(j-1) * rho_(j-1)^( Gamma_(j-1) - Gamma_j ) |
        # .-----------------------------------------------------.
        for j in range(1,eos.neos):
            eos.K_poly_tab[j] = eos.K_poly_tab[j-1]*eos.rho_poly_tab[j-1]**(eos.Gamma_poly_tab[j-1]-eos.Gamma_poly_tab[j])

        return

    # Function     : compute_P_poly_tab()
    # Author(s)    : Leo Werneck
    # Description  : This function populates the array eos.P_poly_tab,
    #                used to distinguish which EOS we are using in the
    #                case of a piecewise polytropic EOS
    # Dependencies : none
    #
    # Inputs       : eos              - named tuple containing the following:
    #                  neos           - number of EOSs to be used (single polytrope = 1)
    #                  rho_poly_tab   - values of rho used to distinguish one EOS from
    #                                   the other (not required for a single polytrope)
    #                  Gamma_poly_tab - values of Gamma to be used within each EOS
    #                  K_poly_tab     - value of K to be used within each EOS
    #                  P_poly_tab     - uninitialized, see output variable below
    #
    # Outputs      : eos.P_poly_tab   - values of P used to distinguish one EOS from
    #                                   the other (not required for a single polytrope)

    def compute_P_poly_tab(eos):

        # We now compute the values of P_poly_tab that are used
        # to find the appropriate polytropic index and, thus,
        # EOS we must use.
        # First, if we have a single polytrope EOS, we need to
        # do nothing.
        if eos.neos==1:
            return

        # For the case of a piecewise polytropic EOS, we have
        # .---------------------------.
        # | P_j = K_j*rho_j^(Gamma_j) |
        # .---------------------------.
        for j in range(eos.neos-1):
            eos.P_poly_tab[j] = eos.K_poly_tab[j]*rho_poly_tab[j]**(Gamma_poly_tab[j])

        return

    # Function     : set_single_or_piecewise_polytrope_EOS_parameters()
    # Author(s)    : Leo Werneck
    # Description  : This function determine all polytropic related
    #                parameters from user input
    # Dependencies : impose_continuity_on_P_cold()
    #                compute_P_poly_tab()
    #
    # Inputs       : neos             - number of EOSs to be used (single polytrope = 1)
    #                rho_poly_tab     - values of rho distinguish one EOS from the
    #                                   other (not required for a single polytrope)
    #                Gamma_poly_tab   - values of Gamma to be used within each EOS
    #                K_poly_tab0      - value of K_poly_tab[0], for the first EOS
    #
    # Outputs      : eos              - named tuple containing the following:
    #                  neos           - number of EOSs to be used (single polytrope = 1)
    #                  rho_poly_tab   - values of rho used to distinguish one EOS from
    #                                   the other (not required for a single polytrope)
    #                  Gamma_poly_tab - values of Gamma to be used within each EOS
    #                  K_poly_tab     - value of K to be used within each EOS
    #                  P_poly_tab     - values of P used to distinguish one EOS from
    #                                   the other (not required for a single polytrope)

    def set_single_or_piecewise_polytrope_EOS_parameters(neos,rho_poly_tab,Gamma_poly_tab,K_poly_tab0):

        # Error check #1: Verify if the correct number of rho_poly_tab has been given by the user
        if (neos == 1):
            pass
        elif len(rho_poly_tab) != neos-1:
            print("Error: neos="+str(neos)+". Expected "+str(neos-1)+" values of rho_poly_tab, but "+str(len(rho_poly_tab))+" values were given.")
            sys.exit(1)

        # Error check #2: Verify if the correct number of Gamma_poly_tab has been given by the user
        if len(Gamma_poly_tab) != neos:
            print("Error: neos="+str(neos)+". Expected "+str(neos)+" values of Gamma_poly_tab, but "+str(len(Gamma_poly_tab))+" values were given.")
            sys.exit(2)

        # Create the arrays to store the values of K_poly_tab and eps_integ_const_tab
        K_poly_tab = [0 for i in range(neos)]
        P_poly_tab = [0 for i in range(neos-1)]

        # Create the EOS "struct" (named tuple)
        from collections import namedtuple
        eos_struct = namedtuple("eos_struct","neos rho_poly_tab Gamma_poly_tab K_poly_tab P_poly_tab")
        eos = eos_struct(neos,rho_poly_tab,Gamma_poly_tab,K_poly_tab,P_poly_tab)

        # Step 1: Determine K_poly_tab. For the details, please see the implementation
        #         of the function impose_continuity_on_P_cold() below.
        impose_continuity_on_P_cold(eos,K_poly_tab0)

        # Step 2: Determine eps_integ_const_tab. For the details, please see the
        #         implementation of the function impose_continuity_on_eps_cold() below.
        compute_P_poly_tab(eos)

        return eos

    # Function     : Polytrope_EOS__compute_P_cold_from_rhob()
    # Author(s)    : Leo Werneck
    # Description  : This function computes P_cold for a polytropic EOS
    # Dependencies : polytropic_index_from_rhob()
    #
    # Inputs       : eos              - named tuple containing the following:
    #                  neos           - number of EOSs to be used (single polytrope = 1)
    #                  rho_poly_tab   - values of rho distinguish one EOS from the
    #                                   other (not required for a single polytrope)
    #                  Gamma_poly_tab - values of Gamma to be used within each EOS
    #                  K_poly_tab     - value of K to be used within each EOS
    #                  P_poly_tab     - values of P used to distinguish one EOS from
    #                                   the other (not required for a single polytrope)
    #                rho_baryon       - the value of rho for which we want to
    #                                   compute P_cold
    #
    # Outputs      : P_cold           - for a single or piecewise polytropic EOS

    def Polytrope_EOS__compute_P_cold_from_rhob(eos, rho_baryon):

        # Compute the polytropic index from rho_baryon
        j = polytropic_index_from_rhob(eos, rho_baryon)

        # Return the value of P_cold for a polytropic EOS
        # .--------------------------------.
        # | P_cold = K_j * rho_b^(Gamma_j) |
        # .--------------------------------.
        return eos.K_poly_tab[j]*rho_baryon**eos.Gamma_poly_tab[j]

    # Function     : Polytrope_EOS__compute_rhob_from_P_cold()
    # Author(s)    : Leo Werneck
    # Description  : This function computes rho_b for a polytropic EOS
    # Dependencies : polytropic_index_from_P()
    #
    # Inputs       : eos              - named tuple containing the following:
    #                  neos           - number of EOSs to be used (single polytrope = 1)
    #                  rho_poly_tab   - values of rho distinguish one EOS from the
    #                                   other (not required for a single polytrope)
    #                  Gamma_poly_tab - values of Gamma to be used within each EOS
    #                  K_poly_tab     - value of K to be used within each EOS
    #                  P_poly_tab     - values of P used to distinguish one EOS from
    #                                   the other (not required for a single polytrope)
    #                P                - the value of P for which we want to
    #                                   compute rho_b
    #
    # Outputs      : rho_baryon       - for a single or piecewise polytropic EOS

    def Polytrope_EOS__compute_rhob_from_P_cold(eos,P):

        # Compute the polytropic index from P
        j = polytropic_index_from_P(eos,P)

        # Return the value of rho_b for a polytropic EOS
        # .----------------------------------.
        # | rho_b = (P_cold/K_j)^(1/Gamma_j) |
        # .----------------------------------.
        return (P/eos.K_poly_tab[j])**(1.0/eos.Gamma_poly_tab[j])

    # Function     : polytropic_index_from_rhob()
    # Author(s)    : Leo Werneck and Zach Etienne
    # Description  : This function computes P_cold for a polytropic EOS
    # Dependencies : none
    #
    # Input(s)     : eos              - named tuple containing the following:
    #                  neos           - number of EOSs to be used (single polytrope = 1)
    #                  rho_poly_tab   - values of rho distinguish one EOS from the
    #                                   other (not required for a single polytrope)
    #                  Gamma_poly_tab - values of Gamma to be used within each EOS
    #                  K_poly_tab     - value of K to be used within each EOS
    #                  P_poly_tab     - values of P used to distinguish one EOS from
    #                                   the other (not required for a single polytrope)
    #                rho_in           - value of rho for which we compute the
    #                                   polytropic index
    #
    # Output(s)    : polytropic index computed from rho_in

    def polytropic_index_from_rhob(eos, rho_in):

        # Returns the value of the polytropic index based on rho_in
        polytropic_index = 0
        if not (eos.neos==1):
            for j in range(eos.neos-1):
                polytropic_index += (rho_in > eos.rho_poly_tab[j])

        return polytropic_index

    # Function     : polytropic_index_from_P()
    # Author(s)    : Leo Werneck and Zach Etienne
    # Description  : This function computes P_cold for a polytropic EOS
    # Dependencies : none
    #
    # Input(s)     : eos              - named tuple containing the following:
    #                  neos           - number of EOSs to be used (single polytrope = 1)
    #                  rho_poly_tab   - values of rho distinguish one EOS from the
    #                                   other (not required for a single polytrope)
    #                  Gamma_poly_tab - values of Gamma to be used within each EOS
    #                  K_poly_tab     - value of K to be used within each EOS
    #                  P_poly_tab     - values of P used to distinguish one EOS from
    #                                   the other (not required for a single polytrope)
    #                P_in             - value of P for which we compute the
    #                                   polytropic index
    #
    # Output(s)    : polytropic index computed from P_in

    def polytropic_index_from_P(eos, P_in):

        # Returns the value of the polytropic index based on P_in
        polytropic_index = 0
        if not (eos.neos==1):
            for j in range(eos.neos-1):
                polytropic_index += (P_in > eos.P_poly_tab[j])

        return polytropic_index
    def TOV_rhs(r_Schw, y) :
    # In \tilde units
    #
        P    = y[0]
        m    = y[1]
        nu   = y[2]
        rbar = y[3]

        j = polytropic_index_from_P(eos,P)
        Gamma = Gamma_poly_tab[j]
        Gam1  = Gamma-1.0

        rho_baryon = Polytrope_EOS__compute_rhob_from_P_cold(eos,P)
        rho = rho_baryon + P/Gam1 # rho is the *total* mass-energy density!

        if( r_Schw < 1e-4 or m <= 0.):
            m = 4*math.pi/3. * rho*r_Schw**3
            dPdrSchw = -(rho + P)*(4.*math.pi/3.*r_Schw*rho + 4.*math.pi*r_Schw*P)/(1.-8.*math.pi*rho*r_Schw*r_Schw)
            drbardrSchw = 1./(1. - 8.*math.pi*rho*r_Schw*r_Schw)**0.5
        else:
            dPdrSchw = -(rho + P)*(m + 4.*math.pi*r_Schw**3*P)/(r_Schw*r_Schw*(1.-2.*m/r_Schw))
            drbardrSchw = 1./(1. - 2.*m/r_Schw)**0.5*rbar/r_Schw

        dmdrSchw  =  4.*math.pi*r_Schw*r_Schw*rho
        dnudrSchw = -2./(P + rho)*dPdrSchw
        return [dPdrSchw, dmdrSchw, dnudrSchw, drbardrSchw]

    def integrateStar( eos, P, dumpData = False ):
        integrator = si.ode(TOV_rhs).set_integrator('dop853')
        y0 = [P, 0., 0., 0.]
        integrator.set_initial_value(y0,0.)
        dr_Schw = 1e-5
        P = y0[0]

        PArr      = []
        r_SchwArr = []
        mArr      = []
        nuArr     = []
        rbarArr   = []

        r_Schw = 0.

        while integrator.successful() and P > 1e-9*y0[0] :
            P, m, nu, rbar = integrator.integrate(r_Schw + dr_Schw)
            r_Schw = integrator.t

            dPdrSchw, dmdrSchw, dnudrSchw, drbardrSchw = TOV_rhs( r_Schw+dr_Schw, [P,m,nu,rbar])
            dr_Schw = 0.1*min(abs(P/dPdrSchw), abs(m/dmdrSchw))
            dr_Schw = min(dr_Schw, 1e-2)
            PArr.append(P)
            r_SchwArr.append(r_Schw)
            mArr.append(m)
            nuArr.append(nu)
            rbarArr.append(rbar)

        M = mArr[-1]
        R_Schw = r_SchwArr[-1]

        # Apply integration constant to ensure rbar is continuous across TOV surface
        for ii in range(len(rbarArr)):
            rbarArr[ii] *= 0.5*(np.sqrt(R_Schw*(R_Schw - 2.0*M)) + R_Schw - M) / rbarArr[-1]

        nuArr_np = np.array(nuArr)
        # Rescale solution to nu so that it satisfies BC: exp(nu(R))=exp(nutilde-nu(r=R)) * (1 - 2m(R)/R)
        #   Thus, nu(R) = (nutilde - nu(r=R)) + log(1 - 2*m(R)/R)
        nuArr_np = nuArr_np - nuArr_np[-1] + math.log(1.-2.*mArr[-1]/r_SchwArr[-1])

        r_SchwArrExtend_np = 10.**(np.arange(0.01,5.0,0.01))*r_SchwArr[-1]

        r_SchwArr.extend(r_SchwArrExtend_np)
        mArr.extend(r_SchwArrExtend_np*0. + M)
        PArr.extend(r_SchwArrExtend_np*0.)
        exp2phiArr_np = np.append( np.exp(nuArr_np), 1. - 2.*M/r_SchwArrExtend_np)
        nuArr.extend(np.log(1. - 2.*M/r_SchwArrExtend_np))
        rbarArr.extend( 0.5*(np.sqrt(r_SchwArrExtend_np**2 - 2.*M*r_SchwArrExtend_np) + r_SchwArrExtend_np - M) )

        # Appending to a Python array does what one would reasonably expect.
        #   Appending to a numpy array allocates space for a new array with size+1,
        #   then copies the data over... over and over... super inefficient.
        r_SchwArr_np     = np.array(r_SchwArr)
        PArr_np          = np.array(PArr)
        rho_baryonArr_np = np.array(PArr)
        for j in range(len(PArr_np)):
            # Compute rho_b from P
            rho_baryonArr_np[j] = Polytrope_EOS__compute_rhob_from_P_cold(eos,PArr_np[j])

        mArr_np               = np.array(mArr)
        rbarArr_np            = np.array(rbarArr)
        confFactor_exp4phi_np = (r_SchwArr_np/rbarArr_np)**2

        # Compute the *total* mass-energy density (as opposed to the *baryonic* mass density)
        rhoArr_np = []
        for i in range(len(rho_baryonArr_np)):
            polytropic_index = 0
            if not (eos.neos==1):
                for i in range(eos.neos-1):
                    polytropic_index += (PArr_np[j] > P_poly_tab[i])

            rhoArr_np.append(rho_baryonArr_np[i] + PArr_np[i]/(eos.Gamma_poly_tab[polytropic_index] - 1.))

        print(len(r_SchwArr_np),len(rhoArr_np),len(PArr_np),len(mArr_np),len(exp2phiArr_np))
        # Special thanks to Leonardo Werneck for pointing out this issue with zip()
        if sys.version_info[0] < 3:
            np.savetxt(outfile, zip(r_SchwArr_np,rhoArr_np,PArr_np,mArr_np,exp2phiArr_np,confFactor_exp4phi_np,rbarArr_np),
                       fmt="%.15e")
        else:
            np.savetxt(outfile, list(zip(r_SchwArr_np,rhoArr_np,PArr_np,mArr_np,exp2phiArr_np,confFactor_exp4phi_np,rbarArr_np)),
                       fmt="%.15e")

        return R_Schw, M

    # Set neos from input variables
    neos = len(Gamma_poly_tab)

    # Set polytropic quantities
    eos = set_single_or_piecewise_polytrope_EOS_parameters(neos,rho_poly_tab,Gamma_poly_tab,K_poly_tab0)

    # Set initial condition from rho_baryon_central
    P_initial_condition = Polytrope_EOS__compute_P_cold_from_rhob(eos, rho_baryon_central)

    # Integrate the initial condition
    R_Schw_TOV, M_TOV = integrateStar(eos, P_initial_condition, True)
    if verbose:
        print("Just generated a TOV star with R_Schw = " + str(R_Schw_TOV) + " , M = " + str(M_TOV) + " , M/R_Schw = "
              + str(M_TOV / R_Schw_TOV) + " .")


year_first = '%Y-%m-%d %H:%M:%S%z'
year_after = '%d-%m-%Y %H:%M:%S%z'

year_first_and_milli_sec = '%Y-%m-%d %H:%M:%S.%f%z'
year_after_and_milli_sec = '%d-%m-%Y %H:%M:%S.%f%z'

verbose = '%d %B %Y, %H:%M:%S %Z'
verbose_and_millisec = '%d %B %Y, %H:%M:%S.%f %Z'

default = year_first_and_milli_sec

year_first_date = '%Y-%m-%d'
year_after_date = '%d-%m-%Y'

date_default = year_first_date


# -*- coding: utf-8 -*-
#
# http://zetcode.com/gui/pyqt5/
#

import sys, random, os, shutil, json
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import (QDate, QTime, QDateTime, Qt, QTimeZone, pyqtSignal, QObject, QBasicTimer, QMimeData, \
    QFile, QIODevice, QTextStream, QFileDevice, QUrl, QTextCodec, QDir)
from PyQt5.QtGui import QIcon, QFont, QColor, QPixmap, QDrag, QPainter, QPen, QBrush, QPainterPath
from PyQt5.QtWidgets import (QWidget, QToolTip,
    QPushButton, QApplication, QMessageBox, QDesktopWidget,
    QMainWindow, QMenu, QAction, QTextEdit, QLabel, qApp,
    QHBoxLayout, QVBoxLayout, QGridLayout, QLineEdit,
    QLCDNumber, QSlider, QInputDialog, QColorDialog, QFrame, QFileDialog, QFontDialog,
    QCheckBox, QProgressBar, QCalendarWidget, QSplitter, QComboBox, QListView, QListWidget,
    QListWidgetItem, QScrollArea,QGraphicsScene, QGraphicsView, QGraphicsItemGroup, QGraphicsItem,
    QGraphicsSceneMouseEvent, QGraphicsPixmapItem)

def demo_ui():
    # ===============================================================
    # Form implementation generated from reading ui file 'untitled.ui'
    #
    # Created by: PyQt5 UI code generator 5.13.0
    #
    # WARNING! All changes made in this file will be lost!
    class Ui_Dialog(object):
        def setupUi(self, Dialog):
            Dialog.setObjectName("Dialog")
            Dialog.resize(400, 300)
            self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
            self.buttonBox.setGeometry(QtCore.QRect(290, 20, 81, 241))
            self.buttonBox.setOrientation(QtCore.Qt.Vertical)
            self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
            self.buttonBox.setObjectName("buttonBox")

            self.retranslateUi(Dialog)
            self.buttonBox.accepted.connect(Dialog.accept)
            # self.buttonBox.rejected.connect(Dialog.reject)
            QtCore.QMetaObject.connectSlotsByName(Dialog)

        def retranslateUi(self, Dialog):
            _translate = QtCore.QCoreApplication.translate
            Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
    # ===============================================================

    class ImageDialog(QtWidgets.QDialog):
        def __init__(self):
            super(ImageDialog, self).__init__()

            # Set up the user interface from Designer.
            self.ui = Ui_Dialog()
            self.ui.setupUi(self)

            # Make some local modifications.

            # Connect up the buttons.
            # self.ui.buttonBox.accepted.connect(self.accept)
            # self.ui.buttonBox.rejected.connect(self.reject)
            self.ui.buttonBox.rejected.connect(self.clickCancel)

        def clickCancel(self):
            print("clickCancel")

    app = QtWidgets.QApplication(sys.argv)
    widget = ImageDialog()
    # widget.resize(400, 100)
    # widget.setWindowTitle("This is a demo for PyQt Widget.")
    widget.show()
    sys.exit(app.exec_())

def demo_simple_window():
    app = QtWidgets.QApplication(sys.argv)

    # A widget with no parent is called a window
    widget = QtWidgets.QWidget()
    widget.setWindowTitle("demo_simple_window")

    # It is 250px wide and 150px high
    widget.resize(250, 150)
    widget.move(0, 0)
    widget.show()

    # The sys.exit() method ensures a clean exit. The environment will be informed how the application ended.
    # The exec_() method has an underscore. It is because the exec is a Python keyword.
    sys.exit(app.exec_())

def test_date():
    now = QDate.currentDate()
    print("              ISODate:", now.toString(Qt.ISODate))
    print("DefaultLocaleLongDate:", now.toString(Qt.DefaultLocaleLongDate))
    print()

    datetime = QDateTime.currentDateTime()
    print("QDateTime: ", datetime.toString())
    print()

    time = QTime.currentTime()
    print("QTime: ", time.toString(Qt.DefaultLocaleLongDate))
    print()

    utc = datetime.toUTC()
    print("local: ", datetime.toString(Qt.ISODate))
    print("  utc: ", utc.toString(Qt.ISODate))
    print("the offset from UTC is {0} seconds".format(datetime.offsetFromUtc()))
    print()

    print("Days in month: {0}".format(now.daysInMonth()))
    print("Days in year: {0}".format(now.daysInYear()))
    print()

    d1 = QDate(2016, 12, 24)
    d2 = QDate(2017, 12, 24)
    days1 = d1.daysTo(now)
    days2 = now.daysTo(d2)
    print("between {0} and {1}, {2}".format(d1.toString(Qt.ISODate), now.toString(Qt.ISODate), days1))
    print("between {0} and {1}, {2}".format(now.toString(Qt.ISODate), d2.toString(Qt.ISODate), days2))
    print()

    dt1 = datetime.addDays(12)
    dt2 = datetime.addDays(-22)
    dt3 = datetime.addSecs(50)
    dt4 = datetime.addMonths(3)
    dt5 = datetime.addYears(12)
    print("Today: ", datetime.toString(Qt.ISODate))
    print("add 12 days: ", dt1.toString(Qt.ISODate))
    print("sub 22 days: ", dt2.toString(Qt.ISODate))
    print("add 50 seconds: ", dt3.toString(Qt.ISODate))
    print("add 3 months", dt4.toString(Qt.ISODate))
    print("add 12 years: ", dt5.toString(Qt.ISODate))
    print()

    # 夏令时, Daylight saving time (DST)
    print("Time zone: {0}".format(datetime.timeZoneAbbreviation()))
    if datetime.isDaylightTime():
        print("DST time")
    else:
        print("not DST time")
    print()

    unix_time = datetime.toSecsSinceEpoch()
    print("unix_time:", unix_time)
    # convert the Unix time to QDateTime
    d = QDateTime.fromSecsSinceEpoch(unix_time)
    print("convert the Unix time to QDateTime: ", d.toString(Qt.DefaultLocaleLongDate))
    print()

    # Julian day
    print("Julian day: ", now.toJulianDay())
    print(now.dayOfYear())
    print(QDate(-4713, 1, 1).daysTo(now))
    print()

    #
    borodino_battle = QDate(1812, 9, 7)
    slavkov_battle = QDate(1805, 12, 2)

    j_today = now.toJulianDay()
    j_borodino = borodino_battle.toJulianDay()
    j_slavkov = slavkov_battle.toJulianDay()

    d1 = j_today - j_slavkov
    d2 = j_today - j_borodino

    print("Days since Slavkov battle: {0}".format(d1))
    print("Days since Borodino battle: {0}".format(d2))
    print()

def demo_Example():
    class Example(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()

        def init(self):
            x, y = 500, 0
            w, h = 320, 180
            self.setGeometry(x, y, w, h)
            self.setWindowIcon(QIcon("res/icon.jpg"))
            self.setWindowTitle("Example")

            QToolTip.setFont(QFont('SansSerif', 10))

            # tooltip
            self.setToolTip('This is a QWidget widget')

            btn = QPushButton(QIcon("res/icon.jpg"), "QPushButton", self)
            btn.setToolTip('
This is a QPushButton widget')

            # The sizeHint() method gives a recommended size for the button.
            btn.resize(btn.sizeHint())

            btn_rect = btn.geometry().getRect()

            # quit application after click bye button
            #
            # The event processing system in PyQt5 is built with the signal & slot mechanism.
            # If we click on the button, the signal clicked is emitted.
            # The slot can be a Qt slot or any Python callable.
            btnBye = QPushButton("Bye", self)
            # btnBye.clicked.connect(QApplication.instance().quit)
            btnBye.clicked.connect(self.close)
            btnBye.resize(btnBye.sizeHint())
            btnBye.move(0, btn_rect[3] * 1.1)

            self.show()
            pass

        # If we close a QWidget, the QCloseEvent is generated.
        def closeEvent(self, event):
            reply = QMessageBox.question(self, "Message", "Are you sure to quit?",
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

            if reply == QMessageBox.Yes:
                event.accept()
            else:
                event.ignore()

        def moveToCenter(self):
            qr = self.frameGeometry()  # QRect
            desktopQR = QDesktopWidget().availableGeometry()
            print(desktopQR)
            cp = desktopQR.center()  # QPoint
            print("center position = ", cp)
            qr.moveCenter(cp)
            self.move(qr.topLeft())

    app = QtWidgets.QApplication([])
    ex = Example()
    ex.init()
    ex.moveToCenter()
    sys.exit(app.exec_())

def demo_MainWindow():
    class SampleMainWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            self.initUI()

        def initUI(self):
            self.setGeometry(300, 300, 250, 150)

            # create status bar
            self.statusBar().showMessage("statusbar ready")

            # menubar
            menubar = self.menuBar()
            menubar.setNativeMenuBar(False)

            # item in menu
            # QAction is an abstraction for actions performed with
            # a menubar, toolbar, or with a custom keyboard shortcut
            exitAct = QAction(QIcon("res/icon.jpg"), "&Exit", self)
            exitAct.setShortcut("Alt+T")
            exitAct.setStatusTip("Exit application")
            exitAct.triggered.connect(qApp.quit)

            # menu in menubar
            fileMenu = menubar.addMenu("&File")  # fileMenu contains action
            fileMenu.addAction(exitAct)  # exitAct will do the work

            # submenu
            # Import => Import mail
            impMenu = QMenu("Import", self)
            impAct = QAction("Import mail", self)
            impAct.setStatusTip("Import mails ... ")
            impMenu.addAction(impAct)
            fileMenu.addMenu(impMenu)

            # check menu
            viewMenu = menubar.addMenu("View")

            viewStatAct = QAction("View statusbar", self, checkable=True)
            viewStatAct.setStatusTip("View statusbar")
            viewStatAct.setChecked(True)
            viewStatAct.triggered.connect(self.toggleMenu)

            viewMenu.addAction(viewStatAct)

            btn = QPushButton("11", self)
            btn.resize(btn.sizeHint())
            btn.addAction(exitAct)
            btn.clicked.connect(exitAct.triggered)
            btn.move(0, 80)

            # toolbar
            # Menus group all commands that we can use in an application.
            # Toolbars provide a quick access to the most frequently used commands.
            toolbar = self.addToolBar("TB")
            # toolbar.addAction(exitAct)
            # toolbar.addAction(impAct)
            toolbar.addAction(viewStatAct)

            # text edit widget in center
            textEdit = QTextEdit(self)
            self.setCentralWidget(textEdit)

            self.show()

        def toggleMenu(self, state):
            if state:
                self.statusBar().show()
            else:
                self.statusBar().hide()

        # To work with a context menu, we have to reimplement the contextMenuEvent() method.
        def contextMenuEvent(self, event: QtGui.QContextMenuEvent):
            # context menu / popup menu
            cmenu = QMenu(self)
            newAct = cmenu.addAction("New")
            openAct = cmenu.addAction("Open")
            quitAct = cmenu.addAction("Quit")

            # The mapToGlobal() method translates the widget coordinates to the global screen coordinates.
            action = cmenu.exec_(self.mapToGlobal(event.pos()))

            if action == quitAct:
                qApp.quit()

    app = QApplication([])
    main_window = SampleMainWindow()
    sys.exit(app.exec_())

def demo_Layout():
    class Example(QWidget):
        def __init__(self):
            super().__init__()
            self.initUI()

        def initUI(self):
            # ============================================
            # absolute
            # label = QLabel("Samle Text", self)
            # label.move(15, 10)
            # QLabel("Hello!!!", self).move(95, 12)
            # ============================================
            # box layout
            # okButton = QPushButton("OK")
            # cancelButton = QPushButton("Cancel")
            #
            # hbox = QHBoxLayout()
            # # horizontal center
            # hbox.addStretch(1)
            # hbox.addWidget(okButton)
            # hbox.addWidget(cancelButton)
            # hbox.addStretch(1)
            #
            # # self.setLayout(hbox)
            # vbox = QVBoxLayout()
            # vbox.addStretch(1)
            # vbox.addLayout(hbox)
            # self.setLayout(vbox)
            # ============================================
            # grid layout
            grid = QGridLayout()
            self.setLayout(grid)

            names = [
                'Cls', 'Bck', '', 'Close',
                '7', '8', '9', '/',
                '4', '5', '6', '*',
                '1', '2', '3', '-',
                '0', '.', '=', '+'
            ]

            positions = [(i, j) for i in range(5) for j in range(4)]
            print(len(positions), len(names))

            for position, name in zip(positions, names):
                print(">", position, name)

                if name == '':
                    continue
                button = QPushButton(name)
                grid.addWidget(button, *position)

            self.setGeometry(300, 300, 250, 150)
            self.show()

    app = QtWidgets.QApplication([])
    ex = Example()
    sys.exit(app.exec_())

def demo_grid_span():
    class Example(QWidget):
        def __init__(self):
            super().__init__()
            self.initUI()

        def initUI(self):
            title = QLabel("Title")
            author = QLabel("Author")
            review = QLabel("Review")

            titleEdit = QLineEdit()
            authorEdit = QLineEdit()
            reviewEdit = QTextEdit()

            grid = QGridLayout()

            # set spacing between widgets
            grid.setSpacing(10)

            grid.addWidget(title, 1, 0)
            grid.addWidget(author, 2, 0)
            grid.addWidget(review, 3, 0)

            grid.addWidget(titleEdit, 1, 1)
            grid.addWidget(authorEdit, 2, 1)
            grid.addWidget(reviewEdit, 3, 1, 5, 1)  # rowSpan = 5, columnSpan = 1

            self.setLayout(grid)
            self.setGeometry(300, 300, 350, 300)
            self.setWindowTitle("Review")
            self.show()

    app = QApplication([])
    ex = Example()
    sys.exit(app.exec_())

# When we call the application's exec_() method, the application enters the main loop.
# The main loop fetches events and sends them to the objects.
def demo_signals_slots():

    # Objects created from a QObject can emit signals
    class Communicate(QObject):
        closeApp = pyqtSignal()

    class Example(QWidget):
        def __init__(self):
            super().__init__()
            self.initUI()

        def initUI(self):
            self.c = Communicate()
            self.c.closeApp.connect(self.close)

            lcd = QLCDNumber(self)
            sld = QSlider(Qt.Horizontal, self)

            x, y = 0, 0
            self.text = "x: {0}, y: {1}"
            text = self.text.format(x, y)
            self.label = QLabel(text, self)

            btn1 = QPushButton("btn1", self)
            btn2 = QPushButton("btn2", self)
            btn1.clicked.connect(self.buttonClicked)
            btn2.clicked.connect(self.buttonClicked)
            self.btn1 = btn1

            hbox = QHBoxLayout()
            hbox.addWidget(btn1)
            hbox.addWidget(btn2)

            vbox = QVBoxLayout()
            vbox.addWidget(self.label)
            vbox.addWidget(lcd)
            vbox.addWidget(sld)
            vbox.addLayout(hbox)
            vbox.setSpacing(20)

            self.setLayout(vbox)
            sld.valueChanged.connect(lcd.display)

            # Mouse tracking is disabled by default
            self.setMouseTracking(True)

            self.setGeometry(0, 0, 300, 200)
            self.show()

        # reimplement the keyPressEvent() event handler
        def keyPressEvent(self, e: QtGui.QKeyEvent) -> None:
            if e.key() == Qt.Key_Space:
                print("诶?!")
            if e.key() == Qt.Key_Escape:
                self.close()

        def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
            x = a0.x()
            y = a0.y()
            text = self.text.format(x, y)
            self.label.setText(text)

        def buttonClicked(self):
            # We determine the signal source by calling the sender() method.
            sender = self.sender()
            self.label.setText(sender.text() + " was pressed")

            if sender == self.btn1:
                self.c.closeApp.emit()

    app = QApplication([])
    ex = Example()
    sys.exit(app.exec_())

def demo_dialog():
    class Example(QWidget):

        def __init__(self):
            super().__init__()

            self.initUI()

        def initUI(self):
            # =====================================================
            # Input Dialog
            self.btn = QPushButton('Input', self)
            self.btn.move(20, 20)
            self.btn.clicked.connect(self.showDialog)

            self.le = QLineEdit(self)
            self.le.move(130, 22)

            # =====================================================
            # Color Dialog
            col = QColor(0, 0, 0)

            self.btn_color = QPushButton('Color', self)
            self.btn_color.move(20, 50)
            self.btn_color.clicked.connect(self.showColorDialog)

            self.frm = QFrame(self)
            self.frm.setStyleSheet("QWidget { background-color: %s }"
                                   % col.name())
            self.frm.setGeometry(130, 50, 100, 100)
            # =====================================================
            # File Dialog
            self.btn_file = QPushButton('File', self)
            self.btn_file.move(20, 180)
            self.btn_file.clicked.connect(self.showFileDialog)

            textEdit = QTextEdit(self)
            textEdit.setGeometry(130, 180, 100, 100)
            self.textEdit = textEdit
            # =====================================================
            # Font Dialog
            self.btn_font = QPushButton('File', self)
            self.btn_font.move(20, 300)
            self.btn_font.clicked.connect(self.showFontDialog)

            self.label = QLabel("Knowledge only matters", self)
            self.label.move(130, 300)
            # =====================================================

            self.setGeometry(300, 300, 300, 650)
            self.setWindowTitle('dialog')
            self.show()

        def showDialog(self):
            text, ok = QInputDialog.getText(self, 'Input Dialog',
                                            'Enter your name:')

            if ok:
                self.le.setText(str(text))

        def showColorDialog(self):
            col = QColorDialog.getColor()
            if col.isValid():
                self.frm.setStyleSheet("QWidget { background-color: %s }"
                                       % col.name())
                print(col.name())

        def showFileDialog(self):
            # fname = QFileDialog.getOpenFileName(self, 'Open file', '.')

            file_tuple = QFileDialog.getOpenFileNames(self, 'Open files', '.')
            fname = file_tuple[0]
            print(type(fname))
            print(fname)
            # fname_sorted = sorted(fname, key=lambda x: x)
            # print("1 ", fname)
            # print("2 ", fname_sorted)
            return

            if fname[0]:
                f = open(fname[0], 'r')

                with f:
                    data = f.read()
                    self.textEdit.setText(data)

        def showFontDialog(self):
            font, ok = QFontDialog.getFont()
            if ok:
                self.label.setFont(font)

    app = QApplication([])
    ex = Example()
    sys.exit(app.exec_())

def demo_widgets():
    class Example(QWidget):
        def __init__(self):
            super().__init__()
            self.initUI()

        def initUI(self):
            vbox = QVBoxLayout()

            # QLabel with QPixmap
            label = QLabel(self)
            label.setPixmap(QPixmap("res/icon.jpg"))
            label.setVisible(True)
            vbox.addWidget(label)
            self.label = label

            # checkbox
            # A QCheckBox is a widget that has two states: on and off
            cb = QCheckBox("show something", self)
            cb.toggle()
            cb.stateChanged.connect(self.changeTitle)
            cb.resize(cb.sizeHint())

            vbox.addWidget(cb)

            hbox = QHBoxLayout()

            # toggle
            toggle1 = QPushButton("Toggle1", self)
            toggle1.setCheckable(True)
            toggle1.clicked[bool].connect(self.toggle)

            toggle2 = QPushButton("Toggle2", self)
            toggle2.setCheckable(True)
            toggle2.clicked.connect(self.toggle)

            hbox.addWidget(toggle1)
            hbox.addWidget(toggle2)

            vbox.addLayout(hbox)

            # slider
            slider = QSlider(Qt.Horizontal, self)
            slider.valueChanged[int].connect(self.changeSliderVal)

            vbox.addWidget(slider)

            # progressbar
            # A progress bar is a widget that is used when we process lengthy tasks.
            hbox2 = QHBoxLayout()
            self.pbar = QProgressBar(self)
            btn_pbar = QPushButton("start", self)
            btn_pbar.clicked.connect(self.doAction)
            self.btn_pbar = btn_pbar

            self.timer = QBasicTimer()
            self.step = 0

            hbox2.addWidget(self.pbar)
            hbox2.addWidget(btn_pbar)
            vbox.addLayout(hbox2)

            # QCalendarWidget
            calendar = QCalendarWidget(self)
            calendar.setGridVisible(True)
            calendar.clicked[QDate].connect(self.showDate)

            self.labelDate = QLabel("labelDate", self)
            vbox.addWidget(calendar)
            vbox.addWidget(self.labelDate)

            self.setLayout(vbox)

            self.setStyleSheet("QWidget { background-color: %s }" % "#fff")
            # self.setGeometry(50, 50, 400, 600)
            self.show()

        def changeTitle(self, state):
            checked = state == Qt.Checked

            if checked:
                self.setWindowTitle("Hello Widgets")
            else:
                self.setWindowTitle("-")

            self.label.setVisible(checked)

        def toggle(self, pressed):
            source = self.sender()
            print(source.text(), pressed)

            col = "#ffffff"

            if pressed:
                if source.text() == "Toggle1":
                    col = "#894522"
                elif source.text() == "Toggle2":
                    col = "#32a14d"

            self.setStyleSheet("QWidget { background-color: %s }" % col)

        def changeSliderVal(self, val):
            print("valueChanged ", val)

            if val > 50:
                self.label.setPixmap(QPixmap("res/h.png"))
            else:
                self.label.setPixmap(QPixmap("res/icon.jpg"))

        def doAction(self):
            if self.step >= 100:
                self.btn_pbar.setText("start")
                self.step = 0
                self.pbar.setValue(self.step)
                return

            if self.timer.isActive():
                self.timer.stop()
                self.btn_pbar.setText("start")
            else:
                self.timer.start(50, self)
                self.btn_pbar.setText("stop")

        # Each QObject and its descendants have a timerEvent() event handler.
        # In order to react to timer events, we reimplement the event handler.
        def timerEvent(self, e) -> None:
            if self.step >= 100:
                self.timer.stop()
                self.btn_pbar.setText("finished")
            else:
                self.step = self.step + 1
                self.pbar.setValue(self.step)

        def showDate(self, date: QDate):
            # self.labelDate.setText(date.toString(Qt.DefaultLocaleLongDate))
            self.labelDate.setText(date.toString())

    app = QApplication([])
    ex = Example()
    sys.exit(app.exec_())

def demo_widgets2():
    class Example(QWidget):
        def __init__(self):
            super().__init__()
            # self.initUI()
            # self.initSplitter()
            self.initComboBox()

        def initUI(self):
            print("demo_widgets2.initUI")
            self.label = QLabel(self)
            self.edit = QLineEdit(self)
            self.edit.textChanged.connect(self.onTextChanged)

            vbox = QVBoxLayout()
            vbox.addWidget(self.label)
            vbox.addWidget(self.edit)

            self.setLayout(vbox)
            self.show()

        def onTextChanged(self, text):
            self.label.setText(text)
            # self.label.adjustSize()
            print(self.label.size())

        def initSplitter(self):
            print("demo_widgets2.initSplitter")
            hbox = QHBoxLayout(self)
            # hbox = QVBoxLayout(self)

            # QSplitter lets the user control the size of child widgets by dragging the boundary between its children.
            topleft = QFrame(self)
            topright = QFrame(self)
            bottom = QFrame(self)

            # We use a styled frame in order to see the boundaries between the QFrame widgets.
            topleft.setFrameShape(QFrame.StyledPanel)
            topright.setFrameShape(QFrame.StyledPanel)
            bottom.setFrameShape(QFrame.StyledPanel)

            # QLabel("a label", topleft)

            splitter1 = QSplitter(Qt.Horizontal)
            splitter2 = QSplitter(Qt.Vertical)

            splitter1.addWidget(topleft)
            splitter1.addWidget(topright)

            splitter2.addWidget(splitter1)
            splitter2.addWidget(bottom)

            hbox.addWidget(splitter2)

            self.setLayout(hbox)
            self.setGeometry(300, 300, 300, 200)
            self.show()

        def initComboBox(self):
            self.lbl = QLabel("Ubuntu", self)

            combo = QComboBox(self)

            # These are the names of Linux distros.
            combo.addItem("Ubuntu")
            combo.addItem("Mandriva")
            combo.addItem("Fedora")
            combo.addItem("Arch")
            combo.addItem("Gentoo")

            combo.move(50, 50)
            self.lbl.move(50, 150)

            combo.activated[str].connect(self.onActivated)

            self.setGeometry(300, 300, 300, 200)
            self.setWindowTitle('QComboBox')
            self.show()

        def onActivated(self, text):
            self.lbl.setText(text)
            self.lbl.adjustSize()
            print(text, type(text))

    app = QApplication([])
    ex = Example()
    sys.exit(app.exec_())

def demo_button_drag_drop():
    class Button(QPushButton):
        def __init__(self, title, parent):
            super().__init__(title, parent)

        def mouseMoveEvent(self, e: QtGui.QMouseEvent) -> None:
            if e.buttons() != Qt.RightButton:
                return

            mineData = QMimeData()
            mineData.setText("mineData in MouseMoveEvent")

            drag = QDrag(self)
            drag.setMimeData(mineData)
            drag.setHotSpot(e.pos() - self.rect().topLeft())

            dropAction = drag.exec_(Qt.MoveAction)

        def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
            # Notice that we call mousePressEvent() method on the parent as well.
            # Otherwise, we would not see the button being pushed.
            super().mousePressEvent(e)

            if e.button() == Qt.LeftButton:
                print("press")

    class Example(QWidget):
        def __init__(self):
            super().__init__()
        #     self.initUI()
        #
        # def initUI(self):
            self.setAcceptDrops(True)

            self.button = Button("Button", self)
            self.button.move(100, 65)

            self.setWindowTitle("Click or Move")
            self.setGeometry(300, 300, 280, 150)

        def dragEnterEvent(self, e: QtGui.QDragEnterEvent) -> None:
            e.accept()

        def dropEvent(self, e: QtGui.QDropEvent) -> None:
            position = e.pos()
            self.button.move(position)
            print("e.mimeData().text():")
            print(e.mimeData().text())

            e.setDropAction(Qt.MoveAction)
            e.accept()

    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    app.exec_()

def demo_painter():
    class Example(QWidget):

        def __init__(self):
            super().__init__()
            self.text = "Лев Николаевич Толстой\nАнна Каренина"
            self.show()

        # The painting is done within the paintEvent() method.
        # The painting code is placed between the begin() and end() methods of the QPainter object.
        def paintEvent(self, event: QtGui.QPaintEvent) -> None:
            print("paint event!", self.size())
            qp = QPainter()
            qp.begin(self)

            qp.setBrush(Qt.darkGray)
            qp.drawRect(self.rect())

            qp.setRenderHint(QPainter.Antialiasing)

            self.drawRectangles(qp)
            self.drawText(event, qp)
            self.drawPoints(qp)
            self.drawLines(qp)
            self.drawBrushes(qp)
            self.drawBezierCurve(qp)
            qp.end()

        def drawText(self, event, qp: QPainter):
            qp.setPen(QColor(168, 34, 3))
            qp.setFont(QFont("Decorative", 10))
            qp.drawText(event.rect(), Qt.AlignCenter, self.text)

        def drawPoints(self, qp: QPainter):
            # qp.setPen(Qt.red)
            qp.setPen(Qt.yellow)

            size = self.size()

            for i in range(1000):
                x = random.randint(1, size.width()-1)
                y = random.randint(1, size.height() - 1)
                qp.drawPoint(x, y)

        def drawRectangles(self, qp):
            col = QColor(0, 0, 0)
            col.setNamedColor('#d4d4d4')
            qp.setPen(col)

            # A brush is an elementary graphics object which is used to draw the background of a shape.
            qp.setBrush(QColor(200, 0, 0))
            qp.drawRect(10, 15, 90, 60)

            qp.setBrush(QColor(255, 80, 0, 160))
            qp.drawRect(130, 15, 90, 60)

            qp.setBrush(QColor(25, 0, 90, 200))
            qp.drawRect(250, 15, 90, 60)

        def drawLines(self, qp):
            # The QPen is an elementary graphics object.
            # It is used to draw lines, curves and outlines of rectangles, ellipses, polygons, or other shapes.
            pen = QPen(Qt.black, 2, Qt.SolidLine)

            qp.setPen(pen)
            qp.drawLine(20, 40, 250, 40)

            pen.setStyle(Qt.DashLine)
            qp.setPen(pen)
            qp.drawLine(20, 80, 250, 80)

            pen.setStyle(Qt.DashDotLine)
            qp.setPen(pen)
            qp.drawLine(20, 120, 250, 120)

            pen.setStyle(Qt.DotLine)
            qp.setPen(pen)
            qp.drawLine(20, 160, 250, 160)

            pen.setStyle(Qt.DashDotDotLine)
            qp.setPen(pen)
            qp.drawLine(20, 200, 250, 200)

            pen.setStyle(Qt.CustomDashLine)
            # Odd numbers define a dash, even numbers space.
            pen.setDashPattern([1, 4, 15, 4])
            qp.setPen(pen)
            qp.drawLine(20, 240, 250, 240)

        def drawBrushes(self, qp):
            # QBrush is an elementary graphics object.
            # It is used to paint the background of graphics shapes,
            # such as rectangles, ellipses, or polygons.
            # A brush can be of three different types:
            # a predefined brush, a gradient, or a texture pattern.
            brush = QBrush(Qt.SolidPattern)

            dx, dy = 360, 20

            qp.setBrush(brush)
            qp.drawRect(10 + dx, 15 + dy, 90, 60)

            brush.setStyle(Qt.Dense1Pattern)
            qp.setBrush(brush)
            qp.drawRect(130 + dx, 15 + dy, 90, 60)

            brush.setStyle(Qt.Dense2Pattern)
            qp.setBrush(brush)
            qp.drawRect(250 + dx, 15 + dy, 90, 60)

            brush.setStyle(Qt.DiagCrossPattern)
            qp.setBrush(brush)
            qp.drawRect(10 + dx, 105 + dy, 90, 60)

            brush.setStyle(Qt.Dense5Pattern)
            qp.setBrush(brush)
            qp.drawRect(130 + dx, 105 + dy, 90, 60)

            brush.setStyle(Qt.Dense6Pattern)
            qp.setBrush(brush)
            qp.drawRect(250 + dx, 105 + dy, 90, 60)

            brush.setStyle(Qt.HorPattern)
            qp.setBrush(brush)
            qp.drawRect(10 + dx, 195 + dy, 90, 60)

            brush.setStyle(Qt.VerPattern)
            qp.setBrush(brush)
            qp.drawRect(130 + dx, 195 + dy, 90, 60)

            brush.setStyle(Qt.BDiagPattern)
            qp.setBrush(brush)
            qp.drawRect(250 + dx, 195 + dy, 90, 60)

        def drawBezierCurve(self, qp:QPainter):
            # brush = QBrush(Qt.HorPattern)
            # brush.setStyle(Qt.HorPattern)
            # qp.setBrush(brush)

            pen = QPen(Qt.darkGreen, 2, Qt.SolidLine)
            qp.setPen(pen)
            qp.setBrush(Qt.transparent)

            path = QPainterPath()
            dx, dy = 0, 240
            path.moveTo(30 + dx, 30 + dy)
            # The curve is created with cubicTo() method
            # starting point, control point, and ending point
            path.cubicTo(30 + dx, 30 + dy, 200 + dx, 350 + dy, 350 + dx, 30 + dy)

            qp.drawPath(path)

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

def demo_custom_widget():
    MAX_CAPACITY = 700
    OVER_CAPACITY = 750

    class Communicate(QObject):
        updateBW = pyqtSignal(int)

    class BurningWidget(QWidget):
        def __init__(self):
            super().__init__()
            self.initUI()

        def initUI(self):
            self.setMinimumSize(1, 30)
            self.value = 75
            self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]

        def setValue(self, value):
            self.value = value

        def paintEvent(self, e: QtGui.QPaintEvent) -> None:
            qp = QPainter()
            qp.begin(self)
            self.drawWidget(qp)
            qp.end()

        def drawWidget(self, qp):

            font = QFont("Serif", 7, QFont.Light)
            qp.setFont(font)

            size = self.size()
            w = size.width()
            h = size.height()

            step = int(round(w / 10))

            till = int(((w / OVER_CAPACITY) * self.value))
            full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))

            if self.value >= MAX_CAPACITY:
                qp.setPen(QColor(255, 255, 255))
                qp.setBrush(QColor(255, 255, 184))
                qp.drawRect(0, 0, full, h)

                qp.setPen(QColor(255, 175, 175))
                qp.setBrush(QColor(255, 175, 175))
                qp.drawRect(full, 0, till - full, h)
            else:
                qp.setPen(QColor(255, 255, 255))
                qp.setBrush(QColor(255, 255, 184))
                qp.drawRect(0, 0, till, h)

            pen = QPen(QColor(20, 20, 20), 1, Qt.SolidLine)
            qp.setPen(pen)
            qp.setBrush(Qt.NoBrush)
            qp.drawRect(0, 0, w - 1, h - 1)

            j = 0
            for i in range(step, 10*step, step):
                #  draw the vertical lines
                qp.drawLine(i, 0, i, 5)

                # We use font metrics to draw the text.
                # We must know the width of the text in order to center it around the vertical line.
                metrics = qp.fontMetrics()
                fw = metrics.width(str(self.num[j]))
                qp.drawText(i - fw/2, h/2, str(self.num[j]))

                j = j + 1

    class Example(QWidget):
        def __init__(self):
            super().__init__()

            sld = QSlider(Qt.Horizontal, self)
            sld.setFocusPolicy(Qt.NoFocus)
            sld.setRange(1, OVER_CAPACITY)
            sld.setValue(75)
            sld.setGeometry(30, 40, 150, 30)

            self.c = Communicate()
            self.wid = BurningWidget()
            self.c.updateBW[int].connect(self.wid.setValue)
            sld.valueChanged[int].connect(self.changeValue)

            hbox = QHBoxLayout()
            hbox.addWidget(self.wid)

            vbox = QVBoxLayout()
            vbox.addStretch(1)
            vbox.addLayout(hbox)
            self.setLayout(vbox)

            self.setGeometry(300, 300, 390, 210)
            self.setWindowTitle("Burning Widget")
            # self.show()

        def changeValue(self, value):
            self.c.updateBW.emit(value)
            self.wid.repaint()

    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    sys.exit(app.exec_())

def demo_drop_file():
    class DropFileWidget(QWidget):
        def __init__(self):
            super().__init__()

            vbox = QVBoxLayout()
            textEdit = QTextEdit(self)
            self.textEdit = textEdit

            textEdit.setAcceptDrops(False)
            # print("textEdit.acceptDrops() = ", textEdit.acceptDrops())
            # textEdit.dropEvent[QtGui.QDropEvent].connect(self.dropEvent)

            label = QLabel("Drag Text File", self)
            label.setAlignment(Qt.AlignCenter)
            print("label.acceptDrops()", label.acceptDrops())

            # ========================================================
            # # box layout
            #
            # box_1 = QHBoxLayout()
            #
            # box_1.addStretch(1)
            # box_1.addWidget(label)
            # box_1.addStretch(1)
            #
            # vbox.addLayout(box_1)
            # vbox.addWidget(label)
            #
            # # vbox.addStretch(1)
            # vbox.addWidget(textEdit)
            # self.setLayout(vbox)
            # ========================================================
            # grid layout
            grid = QGridLayout()

            grid.addWidget(label, 0, 0, 1, 2)
            grid.addWidget(textEdit, 1, 1, 3, 1)

            self.setLayout(grid)
            # ========================================================

            self.setAcceptDrops(True)
            self.setGeometry(100, 100, 400, 300)
            self.setWindowTitle("Drop File To Blank Area")
            self.show()

        def dragEnterEvent(self, e: QtGui.QDragEnterEvent) -> None:
            print("dragEnterEvent")
            e.accept()

        def dropEvent(self, e: QtGui.QDropEvent) -> None:
            print("drop event received..")
            print(e.mimeData().formats())
            # ==================================================

            # file_path = os.path.abspath(e.mimeData().text())
            # print(file_path)

            for url in e.mimeData().urls():
                # file_path = url.toLocalFile().toLocal8Bit().data()  # error: toLocal8Bit
                # print(url.toLocalFile())
                print()

                qurl = QUrl(url)
                file_path = qurl.toLocalFile()
                print("file_path = ", file_path)

                f = QFile(file_path)
                print("exist? ", f.exists())

                if f.exists():
                    f.open(QIODevice.ReadOnly | QIODevice.Text)
                    isOpen = f.isOpen()
                    print("f.isOpen() = ", isOpen)

                    if isOpen:
                        text_stream = QTextStream(f)
                        text_stream.setCodec(QTextCodec.codecForName("UTF-8"))
                        text_stream.setAutoDetectUnicode(True)
                        text = text_stream.readAll()
                        self.textEdit.setText(text)
                        print(text)
            # ==================================================
            e.accept()

    app = QApplication([])
    w = DropFileWidget()
    sys.exit(app.exec_())
    return

def demo_file():
    file_path = "out/temp/playQT.spec"
    file_path = os.path.abspath(file_path)

    f = QFile(file_path)
    # ret = f.copy("out/temp/playQT.spec.new.12")
    # f = QFile("out/temp/playQT.spec.new")

    ret = f.exists()
    print("exists ret = ", ret)

    f.open(QIODevice.ReadOnly | QIODevice.Text | QIODevice.Truncate)
    print("f.isOpen() ", f.isOpen())

    text_stream = QTextStream(f)
    s = text_stream.readAll()

    print(type(s))
    print("> len(s) = ", len(s))
    print("f.error() = ", f.error(), "QFileDevice.NoError = ", QFileDevice.NoError)
    print("errorString = ", f.errorString())
    f.close()
    print("f.isOpen() ", f.isOpen())

def demo_dir():
    out_path = os.path.abspath("out")
    # out_path = os.path.join(out_path, "d1")
    d = QDir(out_path)
    print(d.absolutePath())
    print("d.exists()", d.exists())
    print("d.exists('a.txt')", d.exists('a.txt'))  # check file exist
    print("d.exists('temp')", d.exists('temp'))  # check directory exist

    # remove empty directory
    # ret = d.rmdir("d1")
    # print("rmdir = ", ret)

    # remove directory
    # ret = d.removeRecursively()
    # print("ret = ", ret)

    # make a directory
    # d.mkdir("d1")

    # make directory path
    # ret = d.mkpath("d_1/d_2/d_3")
    # print("ret = ", ret)
    # ret = d.mkpath("d_1/d_4/d_5")
    # print("ret = ", ret)

    # remove directory path, if directory is empty
    # ret = d.rmpath("d_1/d_4/d_5")
    # print("ret = ", ret)

    ret = d.cd("d_1")
    print("cd = ", ret)
    print(d.absolutePath())
    if ret:
        ret = d.removeRecursively()
        print("removeRecursively() ret = ", ret)
    else:
        ret = d.mkpath("d_1/d_2/d_3")
        print("ret = ", ret)

        ret = d.mkpath("d_1/d_4/d_5")
        print("ret = ", ret)

        ret = d.cd("d_1/d_4/d_5")
        if ret:
            f = QFile(d.absoluteFilePath("a.txt"))
            f.open(QIODevice.ReadWrite | QIODevice.Text)
            text_stream = QTextStream(f)
            text_stream << \
"""begin
new line
next...
another...
end"""

def drop_display():
    class FileLineEdit(QLineEdit):
        drop_signal = pyqtSignal(str)

        def dropEvent(self, e: QtGui.QDropEvent) -> None:
            for url in e.mimeData().urls():
                qurl = QUrl(url)
                file_path = qurl.toLocalFile()
                self.setText(file_path)
                self.selectAll()
                self.drop_signal.emit(file_path)
                break

    class DisplayAbsolutePathWidget(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            self.initUI()

        def initUI(self):
            label = QLabel("drag file in: ", self)
            line_edit = FileLineEdit("", self)
            self.line_edit = line_edit

            layout = QGridLayout()
            layout.addWidget(label, 0, 0)
            layout.addWidget(line_edit, 0, 1, 0, 5)

            self.setLayout(layout)
            self.setAcceptDrops(True)
            self.setMinimumWidth(567)
            self.setMaximumHeight(self.sizeHint().height())

        def dragEnterEvent(self, e: QtGui.QDragEnterEvent) -> None:
            e.accept()

        def dropEvent(self, e: QtGui.QDropEvent) -> None:
            self.line_edit.dropEvent(e)

    class SampleWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            qWidget = QWidget(self)

            widget = DisplayAbsolutePathWidget()
            widget.line_edit.drop_signal[str].connect(self.onDrop)

            box = QVBoxLayout()
            box.addWidget(widget)
            # box.addWidget(DisplayAbsolutePathWidget())

            self.label = QLabel(self)
            box.addWidget(self.label, alignment=Qt.AlignCenter)

            qWidget.setLayout(box)
            # self.layout().addWidget(widget)
            # self.setCentralWidget(widget)
            self.setCentralWidget(qWidget)

        def onDrop(self, file_path):
            self.label.setPixmap(QPixmap(file_path))

    app = QApplication([])
    # widget = DisplayAbsolutePathWidget()
    # widget.show()
    window = SampleWindow()
    window.show()
    sys.exit(app.exec_())

    return

def demo_graphicsview():
    class MyGroup(QGraphicsItemGroup):

        def __init__(self):
            super().__init__()

            self.setCursor(Qt.OpenHandCursor)
            self.setFlag(QGraphicsItem.ItemIsMovable)
            self.setFlag(QGraphicsItem.ItemIsSelectable, True)

        def paint(self, painter, option, widget):
            painter.setRenderHint(QPainter.Antialiasing)

            brush = QBrush(QColor("#33EE33"))
            pen = QPen(brush, 1.0)
            pen.setStyle(Qt.DotLine)
            # pen.setStyle(Qt.SolidLine)
            painter.setPen(pen)

            if self.isSelected():
                boundRect = self.boundingRect()
                painter.drawRect(boundRect)

    class Scene(QGraphicsScene):

        def __init__(self):
            super().__init__()

        def setGraphicsView(self, view: QGraphicsView):
            self._view = view

        def initScene(self):
            # 初始化的坐标是(0,0)的时候, 后面的setPos()才会生效
            # self.r1 = self.addRect(120, 248, 100, 50)
            self.r1 = self.addRect(0, 0, 100, 50)
            # self.r1.setFlag(QGraphicsItem.ItemIsMovable)
            # self.r1.setFlag(QGraphicsItem.ItemIsSelectable, True)

            self.r2 = self.addRect(0, 0, 50, 50)
            self.r2.setFlag(QGraphicsItem.ItemIsMovable)
            self.r2.setFlag(QGraphicsItem.ItemIsSelectable, True)

            self.c = self.addEllipse(30, 150, 60, 60)
            self.c.setFlag(QGraphicsItem.ItemIsMovable)
            self.c.setFlag(QGraphicsItem.ItemIsSelectable, True)

            self.l = self.addLine(2.0, 2.0, 1000.0, 0.0, QPen(QColor("#ee8090"), 2.0, Qt.DashLine, Qt.RoundCap))
            self.addRect(self.r2.rect().width() + 2, 2, 50, 50)

            self.r2.setPos(2, 2)
            bottomRight = self.r2.rect().bottomRight()
            print(int(bottomRight.x()), int(bottomRight.y()))

            p0 = self.r2.mapToParent(int(bottomRight.x()), int(bottomRight.y()))
            p0 = self.r2.mapToParent(50, 50)
            print("p0 = ", p0.x(), p0.y())
            # setPos() 需要传入 parent 坐标系内的坐标
            self.r1.setPos(p0)

            # self.addRect(p0.x(), p0.y(), 150, 150)

        def mouseMoveEvent(self, e):
            p0 = self.r2.mapToScene(self.r2.pos())
            p = e.scenePos()
            self.r1.setPos(p)
            self.l.setLine(p0.x(), p0.y(), p.x(), p.y())

    class View(QGraphicsView):

        def __init__(self):
            super().__init__()

            self.setGeometry(0, 0, 300, 300)

            policy = Qt.ScrollBarAlwaysOff

            self.setVerticalScrollBarPolicy(policy)
            self.setHorizontalScrollBarPolicy(policy)
            self.setRenderHint(QPainter.Antialiasing)
            self.setDragMode(QGraphicsView.RubberBandDrag)

            self.init()

        def init(self):
            self.group = None
            self.scene = Scene()
            self.scene.setGraphicsView(self)
            self.scene.initScene()
            self.setSceneRect(0, 0, 300, 300)
            self.setScene(self.scene)

        def keyPressEvent(self, event):

            key = event.key()

            if key == Qt.Key_U:

                if self.group != None and self.group.isSelected():

                    items = self.group.childItems()
                    self.scene.destroyItemGroup(self.group)
                    self.group = None

                    for item in items:
                        item.setSelected(False)

            if key == Qt.Key_G:

                if self.group:
                    return

                selectedItems = self.scene.selectedItems()

                if len(selectedItems) > 0:
                    self.group = MyGroup()

                    for item in selectedItems:
                        self.group.addToGroup(item)

                    self.scene.addItem(self.group)

    class Example(QWidget):

        def __init__(self):
            super().__init__()

            self.initUI()

        def initUI(self):
            # hbox = QHBoxLayout()
            self.view = View()
            # btn = QPushButton("show/hide", self)
            # btn.clicked.connect(self.onClicked)

            # hbox.addWidget(btn)
            # hbox.addWidget(self.view)
            # hbox.addStretch(1)

            # self.setLayout(hbox)
            self.view.setParent(self)
            self.setWindowTitle("Grouping")
            self.setGeometry(250, 150, 300, 300)

        def onClicked(self):
            print(self.view.isVisible())
            self.view.setVisible(not self.view.isVisibleTo(self))

    app = QApplication([])
    ex = Example()
    ex.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    print("case __main__")
    # demo_ui()
    # test_date()
    # demo_simple_window()
    # demo_Example()
    # demo_MainWindow()
    # demo_Layout()
    # demo_grid_span()
    # demo_signals_slots()
    # demo_dialog()
    # demo_widgets()
    # demo_widgets2()
    # demo_button_drag_drop()
    # demo_painter()
    # demo_custom_widget()
    # demo_drop_file()
    # demo_file()
    # demo_dir()
    # drop_display()
    demo_graphicsview()


import sys
import requests
import numpy as np
from inc import twitter

from keras.models import Sequential
from keras.layers import Dense
from textblob import TextBlob

# Where the csv file will live
FILE_NAME = 'data/historical.csv'

def stock_sentiment(quote, num_tweets):
    list_of_tweets = twitter.q(quote, num_tweets)
    positive, null = 0, 0

    for tweet in list_of_tweets:
        blob = TextBlob(tweet.text).sentiment

        if blob.subjectivity == 0:
            null += 1
            next
        if blob.polarity > 0:
            positive += 1

    if positive > ((num_tweets - null)/2):
        return True

def get_historical(quote):
    url = 'https://app.quotemedia.com/quotetools/getHistoryDownload.csv?&webmasterId=501&startDay=02&startMonth=02&startYear=2002&endDay=02&endMonth=12&endYear=2017&isRanged=false&symbol=' + quote
    r = requests.get(url, stream=True)

    if r.status_code == 200:
        with open(FILE_NAME, 'wb') as f:
            for chunk in r:
                f.write(chunk)

        return True
    else:
        print('Could not load csv with financial data: ' + url)
        sys.exit()

def stock_prediction():

    # Collect data points from csv
    dataset = []

    with open(FILE_NAME) as f:
        for n, line in enumerate(f):
            if n != 0:
                dataset.append(float(line.split(',')[1]))

    dataset = np.array(dataset)

    # Create dataset matrix (X=t and Y=t+1)
    def create_dataset(dataset):
        dataX = [dataset[n+1] for n in range(len(dataset)-2)]
        return np.array(dataX), dataset[2:]

    trainX, trainY = create_dataset(dataset)

    # Create and fit Multilinear Perceptron model
    model = Sequential()
    model.add(Dense(8, input_dim=1, activation='relu'))
    model.add(Dense(1))
    model.compile(loss='mean_squared_error', optimizer='adam')
    model.fit(trainX, trainY, batch_size=2, epochs=500, verbose=0)

    # Our prediction for tomorrow
    prediction = model.predict(np.array([dataset[0]]))
    result = 'The price of ' + stock_quote + ' will move from %s to %s' % (dataset[0], prediction[0][0])

    return result

# Ask user for a stock quote
stock_quote = input('Enter a stock quote from NASDAQ (e.j: AAPL, FB, GOOGL): ').upper()

# Check if the stock sentiment is positve
if not stock_sentiment(stock_quote, num_tweets=500):
    print('This stock has bad sentiment, please re-run the script')
    #sys.exit()

# Check if we got te historical data
if not get_historical(stock_quote):
    print('Google returned a 404, please re-run the script and')
    print('enter a valid stock quote from NASDAQ')
    sys.exit()

# We have our file so we create the neural net and get the prediction
print(stock_prediction())

# We are done so we delete the csv file
os.remove(FILE_NAME)


# !/usr/bin/env python
# encoding: utf-8
"""
:copyright (c) 2014 - 2017, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Department of Energy) and contributors. All rights reserved.  # NOQA
:author
"""

import json

from django.http import JsonResponse
from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import list_route

from seed.authentication import SEEDAuthentication
from seed.decorators import ajax_request_class, require_organization_id_class
from seed.utils.api import api_endpoint_class
from seed.utils.buildings import (
    get_columns as utils_get_columns,
)

DEFAULT_CUSTOM_COLUMNS = [
    'project_id',
    'project_building_snapshots__status_label__name',
    'address_line_1',
    'city',
    'state_province',
]

class ColumnViewSetV1(viewsets.ViewSet):
    raise_exception = True
    authentication_classes = (SessionAuthentication, SEEDAuthentication)

    @require_organization_id_class
    @api_endpoint_class
    @ajax_request_class
    def list(self, request):
        """
        Returns a JSON list of columns a user can select as his/her default

        Requires the organization_id as a query parameter.

        This was formally /get_columns
        """
        all_fields = request.query_params.get('all_fields', '')
        all_fields = True if all_fields.lower() == 'true' else False
        return JsonResponse(utils_get_columns(request.query_params['organization_id'], all_fields))

    @api_endpoint_class
    @ajax_request_class
    @list_route(methods=['GET'])
    def get_default_columns(self, request):
        """
        Get default columns for building list view.

        front end is expecting a JSON object with an array of field names

        Returns::

            {
                "columns": ["project_id", "name", "gross_floor_area"]
            }
        """
        columns = request.user.default_custom_columns

        if columns == '{}' or isinstance(columns, dict):
            columns = DEFAULT_CUSTOM_COLUMNS
        if isinstance(columns, unicode):
            # PostgreSQL 9.1 stores JSONField as unicode
            columns = json.loads(columns)

        return JsonResponse({
            'columns': columns,
        })


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.utils import timezone
import logging
from datetime import datetime, timedelta
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.shortcuts import render
from intranet import settings
from ..users.models import User
from ..schedule.views import schedule_context
from ..announcements.models import Announcement, AnnouncementRequest
from ..eighth.models import (
    EighthBlock, EighthSignup, EighthScheduledActivity
)

logger = logging.getLogger(__name__)

def gen_schedule(user, num_blocks=6, surrounding_blocks=None):
    """Generate a list of information about a block and a student's
    current activity signup.

    Returns:
        schedule
        no_signup_today

    """
    no_signup_today = None
    schedule = []

    if surrounding_blocks is None:
        surrounding_blocks = EighthBlock.objects.get_upcoming_blocks(num_blocks)

    if len(surrounding_blocks) == 0:
        return None, False

    # Use select_related to reduce query count
    signups = (EighthSignup.objects.filter(user=user,
                                           scheduled_activity__block__in=surrounding_blocks)
               .select_related("scheduled_activity",
                               "scheduled_activity__block",
                               "scheduled_activity__activity")
               .nocache())
    block_signup_map = {s.scheduled_activity.block.id: s.scheduled_activity for s in signups}

    for b in surrounding_blocks:
        current_sched_act = block_signup_map.get(b.id, None)
        if current_sched_act:
            current_signup = current_sched_act.title_with_flags
            current_signup_cancelled = current_sched_act.cancelled
            current_signup_sticky = current_sched_act.activity.sticky
        else:
            current_signup = None
            current_signup_cancelled = False
            current_signup_sticky = False

        # warning flag (red block text and signup link) if no signup today
        # cancelled flag (red activity text) if cancelled
        flags = "locked" if b.locked else "open"
        blk_today = b.is_today()
        if (blk_today and not current_signup):
            flags += " warning"
        if current_signup_cancelled:
            flags += " cancelled warning"

        if current_signup_cancelled:
            # don't duplicate this info; already caught
            current_signup = current_signup.replace(" (Cancelled)", "")

        info = {
            "id": b.id,
            "block": b,
            "block_letter": b.block_letter,
            "current_signup": current_signup,
            "current_signup_cancelled": current_signup_cancelled,
            "current_signup_sticky": current_signup_sticky,
            "locked": b.locked,
            "date": b.date,
            "flags": flags,
            "is_today": blk_today,
            "signup_time": b.signup_time,
            "signup_time_future": b.signup_time_future
        }
        schedule.append(info)

        if blk_today and not current_signup:
            no_signup_today = True

    return schedule, no_signup_today

def gen_sponsor_schedule(user, sponsor=None, num_blocks=6, surrounding_blocks=None):
    """Return a list of :class:`EighthScheduledActivity`\s in which the
    given user is sponsoring.

    Returns:
        sponsor_schedule
        no_attendance_today
    """

    no_attendance_today = None
    acts = []

    if sponsor is None:
        sponsor = user.get_eighth_sponsor()

    if surrounding_blocks is None:
        surrounding_blocks = EighthBlock.objects.get_upcoming_blocks(num_blocks).nocache()

    activities_sponsoring = (EighthScheduledActivity.objects.for_sponsor(sponsor)
                                                            .select_related("block")
                                                            .filter(block__in=surrounding_blocks)
                                                            .nocache())
    sponsoring_block_map = {}
    for sa in activities_sponsoring:
        bid = sa.block.id
        if bid in sponsoring_block_map:
            sponsoring_block_map[bid] += [sa]
        else:
            sponsoring_block_map[bid] = [sa]

    for b in surrounding_blocks:
        num_added = 0
        sponsored_for_block = sponsoring_block_map.get(b.id, [])

        for schact in sponsored_for_block:
            acts.append(schact)
            if schact.block.is_today():
                if not schact.attendance_taken and schact.block.locked:
                    no_attendance_today = True

            num_added += 1

        if num_added == 0:
            # fake an entry for a block where there is no sponsorship
            acts.append({
                "block": b,
                "id": None,
                "fake": True
            })

    logger.debug(acts)
    return acts, no_attendance_today

def find_birthdays(request):
    """Return information on user birthdays."""
    today = datetime.now().date()
    custom = False
    yr_inc = 0
    if "birthday_month" in request.GET and "birthday_day" in request.GET:
        try:
            mon = int(request.GET["birthday_month"])
            day = int(request.GET["birthday_day"])
            yr = today.year

            """ If searching a date that already happened this year, skip to the next year. """
            if mon < today.month or (mon == today.month and day < today.day):
                yr += 1
                yr_inc = 1

            real_today = today
            today = datetime(yr, mon, day).date()
            if today:
                custom = True
            else:
                today = real_today
        except Exception:
            pass

    key = "birthdays:{}".format(today)

    cached = cache.get(key)

    if cached:
        logger.debug("Birthdays on {} loaded "
                     "from cache.".format(today))
        logger.debug(cached)
        return cached
    else:
        logger.debug("Loading and caching birthday info for {}".format(today))
        tomorrow = today + timedelta(days=1)
        try:
            data = {
                "custom": custom,
                "today": {
                    "date": today,
                    "users": [{
                        "id": u.id,
                        "full_name": u.full_name,
                        "grade": {
                            "name": u.grade.name
                        },
                        "age": (u.age + yr_inc) if u.age is not None else -1
                    } if u else {} for u in User.objects.users_with_birthday(today.month, today.day)],
                    "inc": 0
                },
                "tomorrow": {
                    "date": tomorrow,
                    "users": [{
                        "id": u.id,
                        "full_name": u.full_name,
                        "grade": {
                            "name": u.grade.name
                        },
                        "age": (u.age - 1)
                    } for u in User.objects.users_with_birthday(tomorrow.month, tomorrow.day)],
                    "inc": 1
                }
            }
        except AttributeError:
            return None
        else:
            cache.set(key, data, timeout=60 * 60 * 24)
            return data

@login_required
def dashboard_view(request, show_widgets=True, show_expired=False):
    """Process and show the dashboard."""

    user = request.user

    announcements_admin = user.has_admin_permission("announcements")

    if not show_expired:
        show_expired = ("show_expired" in request.GET)

    if announcements_admin and "show_all" in request.GET:
        # Show all announcements if user has admin permissions and the
        # show_all GET argument is given.
        announcements = (Announcement.objects.all())
    else:
        # Only show announcements for groups that the user is enrolled in.
        if show_expired:
            announcements = (Announcement.objects
                             .visible_to_user(user))
        else:
            announcements = (Announcement.objects
                                         .visible_to_user(user)
                                         .filter(expiration_date__gt=timezone.now()))

    # pagination
    if "start" in request.GET:
        try:
            start_num = int(request.GET.get("start"))
        except ValueError:
            start_num = 0
    else:
        start_num = 0

    display_num = 10
    end_num = start_num + display_num
    more_announcements = ((announcements.count() - start_num) > display_num)
    try:
        announcements_sorted = announcements[start_num:end_num]
    except (ValueError, AssertionError):
        announcements_sorted = announcements[:display_num]
    else:
        announcements = announcements_sorted

    announcements = announcements.select_related("user").prefetch_related("groups", "event")

    user_hidden_announcements = (Announcement.objects.hidden_announcements(user)
                                                     .values_list("id", flat=True)).nocache()

    is_student = user.is_student
    is_teacher = user.is_teacher
    is_senior = (user.grade.number == 12) if user.grade and user.grade.number else False
    eighth_sponsor = user.get_eighth_sponsor()

    if show_widgets:
        dashboard_title = "Dashboard"
        dashboard_header = "Announcements"
    elif show_expired:
        dashboard_title = dashboard_header = "Announcement Archive"
    else:
        dashboard_title = dashboard_header = "Announcements"

    context = {
        "announcements": announcements,
        "announcements_admin": announcements_admin,
        "start_num": start_num,
        "end_num": end_num,
        "prev_page": start_num - display_num,
        "more_announcements": more_announcements,
        "hide_announcements": True,
        "user_hidden_announcements": user_hidden_announcements,
        "show_widgets": show_widgets,
        "show_expired": show_expired,
        "dashboard_title": dashboard_title,
        "dashboard_header": dashboard_header,
        "is_student": is_student,
        "is_teacher": is_teacher,
        "is_senior": is_senior
    }

    if show_widgets:
        if is_student or eighth_sponsor:
            num_blocks = 6
            surrounding_blocks = EighthBlock.objects.get_upcoming_blocks(num_blocks)

        if is_student:
            schedule, no_signup_today = gen_schedule(user, num_blocks, surrounding_blocks)
            context.update({
                "schedule": schedule,
                "no_signup_today": no_signup_today,
                "senior_graduation": settings.SENIOR_GRADUATION,
                "senior_graduation_year": settings.SENIOR_GRADUATION_YEAR,
            })

        if eighth_sponsor:
            sponsor_schedule, no_attendance_today = gen_sponsor_schedule(user, eighth_sponsor, num_blocks, surrounding_blocks)
            context.update({
                "sponsor_schedule": sponsor_schedule,
                "no_attendance_today": no_attendance_today
            })

        context.update({
            "eighth_sponsor": eighth_sponsor,
            "birthdays": find_birthdays(request),
            "sched_ctx": schedule_context(request)["sched_ctx"]
        })

    if announcements_admin:
        all_waiting = AnnouncementRequest.objects.filter(posted=None, rejected=False)
        awaiting_teacher = all_waiting.filter(teachers_approved__isnull=True)
        awaiting_approval = all_waiting.filter(teachers_approved__isnull=False)

        context.update({
            "awaiting_teacher": awaiting_teacher,
            "awaiting_approval": awaiting_approval,
        })

    return render(request, "dashboard/dashboard.html", context)


from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.index),
    url(r'^register', views.register),
    url(r'^logout', views.logout),
    url(r'^home', views.home),
    url(r'^wow', views.wow),
    url(r'^profile', views.profile)
]