コード例 #1
0
ファイル: util.py プロジェクト: victoraraujo105/BusRiding
def adicionar_linha(app):
    '''
        Adiciona uma linha com base nos campos de texto.
    '''
    # lemos todos os campos de texto
    campos = [
        dest.atualizar_destino(),
        date.update(app),
        vg.atualizar_vagas(),
        it.atualizar_inteira()
    ]

    for campo in campos:
        if popup.campos_invalidos[campo]():
            return
    # aqui, todos os campos de texto possuem valores válidos, então vamos criar a linha

    linhas = app.linhas
    destino = monitoradas.destino.get()
    tempo_exibido = monitoradas.tempo_exibido
    vagas = monitoradas.vagas.get()
    inteira = monitoradas.inteira.get()
    linha = gerar_id_linha(destino, tempo_exibido)
    # antes de inseri-la graficamente, temos que verificar se ela já existe
    if linha in estado.linhas_entradas:
        # Linha desabilitada. Deseja reabilitá-la?
        if linha not in estado.linhas_visiveis:
            texto = 'Linha desabilitada, deseja reabilitá-la?.'
            resposta = messagebox.askyesno(title='Linha existe',
                                           message=texto,
                                           default='yes')
            if resposta:
                app.linhas.reattach(linha, '', 0)
                estado.linhas_visiveis.add(linha)
                onibus_filhos_visiveis = set(app.linhas.get_children(linha))
                estado.onibus_invisiveis -= onibus_filhos_visiveis
                monitoradas.onibus_visiveis.update(onibus_filhos_visiveis)
                ctrl.update_action(app, 'Restaurar')

                monitoradas.historico_redo.clear()
                monitoradas.historico_undo.append(['show', [0], [linha]])
                app.linhas.selection_set(linha)
                app.contador_linhas['text'] = fm.form_cont_linhas(
                    len(estado.linhas_visiveis))
                app.contador_onibus['text'] = fm.form_cont_onibus(
                    len(monitoradas.onibus_visiveis))
                # nova ação: reabilitar
        else:
            app.linhas.see(linha)
            app.linhas.selection_set(linha)
        return
    h = minutos_dia(tempo_exibido)
    estado.linhas_possiveis.discard((destino, h))
    estado.horarios_linhas[h] = estado.horarios_linhas.get(h, set()).union(
        {linha})

    dest.add_destino(app)
    ctrl.update_action(app, 'add')
    linhas.insert(parent='',
                  index=0,
                  values=(destino, fm.form_tempo(tempo_exibido), vagas,
                          inteira),
                  iid=linha)
    estado.linhas_visiveis.add(linha)
    app.contador_linhas['text'] = fm.form_cont_linhas(
        len(estado.linhas_visiveis))

    estado.linhas_entradas[linha] = cte.ESTRUTURA_LINHA(
        destino, tempo_exibido,
        int(vagas) // 2, round(float(inteira) * 100 - 300), dict())

    data_atual = monitoradas.data_atual.replace(second=0, microsecond=0)
    for d in range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1):
        partida = (data_atual + dt.timedelta(d)).replace(
            hour=tempo_exibido.hour, minute=tempo_exibido.minute)
        if data_atual <= partida <= data_atual + dt.timedelta(
                cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA):
            onibus = gerar_id_onibus(linha, partida)
            linhas.insert(parent=linha,
                          index='end',
                          values=('-', fm.form_data(partida), vagas, '-'),
                          iid=onibus)
            monitoradas.onibus_visiveis.add(onibus)
            estado.linhas_entradas[linha].onibus[onibus] = dict()
            app.contador_onibus['text'] = fm.form_cont_onibus(
                len(monitoradas.onibus_visiveis))

    app.contador_linhas['text'] = fm.form_cont_linhas(
        len(estado.linhas_visiveis))
    app.contador_onibus['text'] = fm.form_cont_onibus(
        len(monitoradas.onibus_visiveis))

    historico_undo = monitoradas.historico_undo
    historico_redo = monitoradas.historico_redo
    historico_redo.clear()
    historico_undo.append(['add', [[estado.linhas_entradas[linha], list()]]])
    linhas.selection_set(linha)
コード例 #2
0
ファイル: util.py プロジェクト: victoraraujo105/BusRiding
def editar_linha(app):
    '''
        Edita informações sobre uma linha.
    '''
    selecao = app.linhas.selection()
    if len(selecao) != 1 or app.linhas.parent(selecao[0]) != '':
        texto = 'Selecione uma única linha.'
        messagebox.showwarning(title='Impossível Editar', message=texto)
        return
    [linha] = selecao
    dados_linha = estado.linhas_entradas[linha]
    # a condição a seguir é True sse existe algum ônibus com reservas na linha selecionada.
    if any(len(x) != 0 for x in dados_linha.onibus.values()):
        texto = 'Linha possui reservas.'
        messagebox.showwarning(title='Impossível Editar', message=texto)
        return

    campos = [
        dest.atualizar_destino(),
        date.update(app),
        vg.atualizar_vagas(),
        it.atualizar_inteira()
    ]

    for campo in campos:
        if popup.campos_invalidos[campo]():
            return

    destino_alterada = monitoradas.destino.get()
    tempo_exibido = monitoradas.tempo_exibido
    vagas_alterada = monitoradas.vagas.get()
    inteira_alterada = monitoradas.inteira.get()
    linha_alterada = gerar_id_linha(destino_alterada, tempo_exibido)

    h = minutos_dia(estado.linhas_entradas[linha].horario)

    if linha_alterada in estado.linhas_entradas:
        if linha_alterada in estado.linhas_visiveis:
            if linha != linha_alterada:
                texto = 'Linha já existe.'
                messagebox.showwarning(title='Impossível Editar',
                                       message=texto)
            elif dados_linha[2:4] != [
                    int(vagas_alterada) // 2,
                    round(float(inteira_alterada) * 100 - 300)
            ]:
                dados_linha_alterada = estado.linhas_entradas[
                    linha] = cte.ESTRUTURA_LINHA(
                        *dados_linha[:2],
                        int(vagas_alterada) // 2,
                        round(float(inteira_alterada) * 100 - 300),
                        dados_linha[-1])
                app.linhas.item(
                    linha,
                    values=campos_linha_formatados(dados_linha_alterada))

                app.linhas.selection_set(linha_alterada)
                monitoradas.historico_redo.clear()
                monitoradas.historico_undo.append(
                    ['change', dados_linha, dados_linha_alterada, False])
                app.contador_linhas['text'] = fm.form_cont_linhas(
                    len(estado.linhas_visiveis))
                app.contador_onibus['text'] = fm.form_cont_onibus(
                    len(monitoradas.onibus_visiveis))

        else:
            texto = 'Linha desabilitada, deseja reabilitá-la?'
            resposta = messagebox.askyesno(title='Impossível Editar',
                                           message=texto,
                                           default='no')
            if resposta:
                app.linhas.reattach(linha_alterada, '',
                                    app.linhas.index(linha))
                app.linhas.item(linha_alterada,
                                open=app.linhas.item(linha, 'open'))
                estado.linhas_visiveis.add(linha_alterada)
                onibus_filhos_visiveis = set(
                    app.linhas.get_children(linha_alterada))
                estado.onibus_invisiveis -= onibus_filhos_visiveis
                monitoradas.onibus_visiveis.update(onibus_filhos_visiveis)
                ctrl.update_action(app, 'Restaurar')
                estado.horarios_linhas[h].remove(linha)
                app.linhas.delete(linha)
                estado.linhas_visiveis.remove(linha)
                for onibus in estado.linhas_entradas[linha].onibus:
                    monitoradas.onibus_visiveis.discard(onibus)
                    estado.onibus_invisiveis.discard(onibus)

                monitoradas.historico_redo.clear()
                monitoradas.historico_undo.append([
                    'change', estado.linhas_entradas[linha],
                    estado.linhas_entradas[linha_alterada], True
                ])
                estado.linhas_possiveis.add((dados_linha.destino.title(), h))
                del estado.linhas_entradas[linha]
                app.linhas.selection_set(linha_alterada)
                app.contador_linhas['text'] = fm.form_cont_linhas(
                    len(estado.linhas_visiveis))
                app.contador_onibus['text'] = fm.form_cont_onibus(
                    len(monitoradas.onibus_visiveis))
        return
    h_alt = tempo_exibido.hour * 60 + tempo_exibido.minute
    estado.linhas_possiveis.remove((destino_alterada, h_alt))
    estado.linhas_possiveis.add((dados_linha.destino.title(), h))
    estado.horarios_linhas[h].remove(linha)
    estado.horarios_linhas[h_alt] = estado.horarios_linhas.get(
        h_alt, set()).union({linha_alterada})
    dest.add_destino(app)
    ctrl.update_action(app, 'change')
    indice = app.linhas.index(linha)
    app.linhas.insert(parent='',
                      index=indice,
                      values=(destino_alterada, fm.form_tempo(tempo_exibido),
                              vagas_alterada, inteira_alterada),
                      iid=linha_alterada,
                      open=app.linhas.item(linha, 'open'))
    app.linhas.delete(linha)
    estado.linhas_visiveis.remove(linha)
    estado.linhas_visiveis.add(linha_alterada)

    onibus_filhos = dict()

    data_atual = monitoradas.data_atual.replace(second=0, microsecond=0)
    for d in range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1):
        partida = (data_atual + dt.timedelta(d)).replace(
            hour=tempo_exibido.hour, minute=tempo_exibido.minute)
        if data_atual <= partida <= data_atual + dt.timedelta(
                cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA):
            onibus = gerar_id_onibus(linha_alterada, partida)
            app.linhas.insert(parent=linha_alterada,
                              index='end',
                              values=('-', fm.form_data(partida),
                                      vagas_alterada, '-'),
                              iid=onibus)
            monitoradas.onibus_visiveis.add(onibus)
            onibus_filhos[onibus] = dict()

    estado.linhas_entradas[linha_alterada] = cte.ESTRUTURA_LINHA(
        destino_alterada, tempo_exibido,
        int(vagas_alterada) // 2, round(float(inteira_alterada) * 100 - 300),
        onibus_filhos)

    for onibus in estado.linhas_entradas[linha].onibus:
        monitoradas.onibus_visiveis.discard(onibus)
        estado.onibus_invisiveis.discard(onibus)

    monitoradas.historico_redo.clear()
    monitoradas.historico_undo.append([
        'change', estado.linhas_entradas[linha],
        estado.linhas_entradas[linha_alterada], False
    ])

    del estado.linhas_entradas[linha]
    app.contador_linhas['text'] = fm.form_cont_linhas(
        len(estado.linhas_visiveis))
    app.contador_onibus['text'] = fm.form_cont_onibus(
        len(monitoradas.onibus_visiveis))

    ctrl.update_action(app, 'change')
    app.linhas.selection_set(linha_alterada)
コード例 #3
0
)  # texto do widget que contém o preço da passagem inteira
indice_inteira = 60  # veja o módulo inteira.py para explicação
# texto do widget que contém o texto da atual ação (veja update_actions em control.py)
cur_action = tk.StringVar()
destino = tk.StringVar()  # atual destino selecionado no widget
# O texto do último destino selecionado. Utilizado quando o usuário digita um destino inválido
# e queremos alterar o valor do widget para o último texto válido.
texto_anterior = '' if len(estado.destinos) == 0 else estado.destinos[0]

# O histórico da aplicação é utilizado para responder aos eventos CTRL+Z e CTRL+SHIFT+Z
# cada item da lista é uma tupla (NOME_DA_AÇÃO, INDICES_DAS_LINHAS, ITENS)
historico_undo = []
historico_redo = []
previous_selection = set()
pressed = False
num_entradas = 0
ocupar_assentos = tk.IntVar(value=0)
indice_linhas_geradas = 49
janela_reservas = None

onibus_visiveis = set()  # ids dos ônibus habilitados q ainda não partiram
arrecadado = dict()
# linha: lista cujos índices representam dias da semana, 0 é domingo
ocupacao_media_semanal = dict()

tempo_formatado.set(fm.form_tempo(tempo_exibido))
vagas.set(str(2 * (indice_vagas + 1)))
inteira.set('%.2f' % (3 + indice_inteira * .01))
cur_action.set('')
destino.set(texto_anterior)
コード例 #4
0
def adicionar_linha(app, indice, linha, data_atual=monitoradas.data_atual):
    '''
        Altera o estado da aplicação (incluindo a interface gráfica) para incluir a linha especificada no argumento.
    '''
    destino = linha.campus.title()
    if destino not in estado.destinos:
        # adicionamos o destino da linha do argumento, caso ele não exista
        estado.destinos.append(destino)
        estado.linhas_possiveis.update({(destino, t) for t in range(1440)})
        estado.destinos.sort()  # mantemos a ordenação
        app.entrada_destino['values'] = estado.destinos
        monitoradas.destino.set(estado.destinos[0])
    t = minutos_dia(linha.horario)
    estado.horarios_linhas[t] = estado.horarios_linhas.get(
        t, set()).union({linha.id})
    # incluimos a linha na interface gráfica
    estado.linhas_possiveis.discard((destino, t))
    app.linhas.insert(parent='',
                      index=indice,
                      values=(destino, fm.form_tempo(linha.horario), 2 *
                              linha.fileiras, it.inteira_termo(linha.inteira)),
                      iid=linha.id)
    estado.linhas_visiveis.add(linha.id)
    app.contador_linhas['text'] = fm.form_cont_linhas(
        len(estado.linhas_visiveis))

    estado.linhas_entradas[linha.id] = cte.ESTRUTURA_LINHA(
        destino, linha.horario, linha.fileiras, linha.inteira, dict())

    minutos_atual = minutos_dia(data_atual)

    # calculamos o período de dias em que haverá ônibus partindo
    if t == minutos_atual:
        periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1)
    elif t < minutos_atual:
        # aqui, o horário da linha é menor que o horário atual,
        # então não vamos criar um ônibus para o dia de hoje, pois ele já partiu
        periodo = range(1, cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1)
    else:
        # aqui, o horário da linha é maior que o horário atual,
        # então vamos incluir o dia de hoje
        periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA)

    for d in periodo:
        # para cada dia no período calculado, vamos adicionar um ônibus
        # note que d = 0 representa o dia de hoje, d = 1 é amanhã, etc.
        partida = (data_atual + dt.timedelta(d)
                   ).replace(hour=linha.horario.hour, minute=linha.horario.minute)
        onibus = gerar_id_onibus(linha.id, partida)
        app.linhas.insert(parent=linha.id, index='end', values=(
            '-', fm.form_data(partida), 2*linha.fileiras, '-'), iid=onibus)
        monitoradas.onibus_visiveis.add(onibus)
        # todos os assentos estão livres
        estado.linhas_entradas[linha.id].onibus[onibus] = dict()
        app.contador_onibus['text'] = fm.form_cont_onibus(
            len(monitoradas.onibus_visiveis))

    app.contador_linhas['text'] = fm.form_cont_linhas(
        len(estado.linhas_visiveis))
    app.contador_onibus['text'] = fm.form_cont_onibus(
        len(monitoradas.onibus_visiveis))
コード例 #5
0
    def gerar(event=None):
        '''
            Gera as linhas conforme a quantidade indicada pelo spin_linhas.
            É executado quando o usuário clica no botão de gerar, ou aperta ENTER.
        '''
        if not atualizar_num():
            # aqui, atualizar_num retornou False, o que significa que a entrada é inválida
            messagebox.showwarning(title='Entrada Inválida',
                                   message='Valor inválido!',
                                   parent=configuracoes)
            return

        from random import choice, randrange, sample
        ocupar = monitoradas.ocupar_assentos.get()
        # Uma sequência em que o valor assento_coordenadas(i, j) indica o número do assento na linha i e coluna j.
        # Note que para cada linha, há 4 colunas (índice j varia de 0 a 3).
        # As colunas 0 e 3 são adjacentes à janela e as colunas 1 e 2 são adjacentes ao corredor.
        # Essa sequência pode ser vista ao tentar reservar um assento.
        def assento_coordenadas(i, j): return 2*i + j + \
            1 if j <= 1 else 2*(num_fileiras + i + 2) - j
        dados = []
        data_atual = monitoradas.data_atual
        minutos_atual = data_atual.hour*60 + data_atual.minute
        # termo(monitoradas.indice_linhas_geradas) é o número de linhas indicado pelo spin_linhas
        # note que minutes[:termo(monitoradas.indice_linhas_geradas)] seleciona
        # justamente a quantidade de linhas que queremos!
        # como minutes foi permutado aleatoriamente, os horários em minutes escolhidos
        # pelo slicing são aleatórios e diferentes entre si
        for destino, t in sample(estado.linhas_possiveis, termo(monitoradas.indice_linhas_geradas)):
            estado.linhas_possiveis.discard((destino, t))
            # t é uma quantidade de minutos da lista minutes, que usaremos para calcular o horário dessa linha que
            # iremos cadastrar!
            interval = dt.timedelta(minutes=t)
            horario = data_atual.replace(hour=0, minute=0) + interval
            if data_atual > horario:
                horario += dt.timedelta(1)
            linha = gerar_id_linha(destino, horario)

            indice_vagas = choice(range(cte.MAXIMO_NUMERO_DE_FILEIRAS))
            num_fileiras = indice_vagas + 1
            vagas = vg.vagas_termo(indice_vagas)
            indice_inteira = choice(range(301))
            inteira = it.inteira_termo(indice_inteira)

            estado.horarios_linhas[t] = estado.horarios_linhas.get(
                t, set()).union({linha})

            app.linhas.insert(parent='',
                              index=0,
                              values=(destino, fm.form_tempo(
                                  horario), vagas, inteira),
                              iid=linha)
            estado.linhas_entradas[linha] = cte.ESTRUTURA_LINHA(
                destino, horario, num_fileiras, indice_inteira, dict())
            estado.linhas_visiveis.add(linha)

            reservas = list()

            # calculamos o período de dias em que haverá ônibus partindo
            if t == minutos_atual:
                periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1)
            elif t < minutos_atual:
                # aqui, o horário da linha é menor que o horário atual,
                # então não vamos criar um ônibus para o dia de hoje, pois ele já partiu
                periodo = range(1, cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1)
            else:
                # aqui, o horário da linha é maior que o horário atual,
                # então vamos incluir o dia de hoje
                periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA)

            for d in periodo:
                # para cada dia no período calculado, vamos adicionar um ônibus
                # note que d = 0 representa o dia de hoje, d = 1 é amanhã, etc.
                partida = data_atual + dt.timedelta(d)
                onibus = gerar_id_onibus(linha, partida)
                assentos_disponiveis = randrange(vagas+1) if ocupar else vagas
                app.linhas.insert(parent=linha, index='end', values=(
                    '-', fm.form_data(partida), assentos_disponiveis, '-'), iid=onibus)
                estado.linhas_entradas[linha].onibus[onibus] = dict()
                if assentos_disponiveis < vagas:
                    # nesse caso, significa que o usuário pediu para "Ocupar assentos"
                    # e o valor aleatorio assentos disponíveis é menor que a quantidade de vagas
                    # então, devemos fazer algumas reservas aleatórias!
                    # primeiro, vamos escolher quais assentos serão escolhíveis
                    # (afinal, metade dos assentos serão disponíveis, então vamos escolher qual metade)
                    shift = choice(range(2))
                    # os assentos escolhíveis respeitam as condições impostas pela pandemia
                    # basicamente, selecionamos (i, (i + shift) % 2) e (i, 2 + (i + shift) % 2) para todo i
                    # por exemplo,
                    # i = 0, shift = 0 -> selecionamos (0, 0) e (0, 2)
                    # i = 1, shift = 0 -> selecionamos (1, 1) e (1, 3)
                    # i = 2, shift = 0 -> selecionamos (2, 0) e (2, 2)
                    # Note que a primeira posição é um assento da seção da esquerda
                    # e a segunda posição é um assento da seção da direita
                    # e selecionamos dois assentos por linha
                    assentos_visiveis = {assento_coordenadas(i, 2 * j + (i + shift) % 2) for j in range(2)
                                         for i in range(num_fileiras)}
                    # a função sample seleciona aleatoriamente números de assentos escolhíveis para
                    # fazermos reserva
                    for assento in sample(assentos_visiveis, vagas - assentos_disponiveis):
                        # a variável assento é um número de assento escolhível aleatório
                        # devemos reservar esse assento!
                        passagem = choice([2]*3+[1]*2+[0])
                        estado.linhas_entradas[linha].onibus[onibus][assento] = passagem
                        reserva = gerar_id_reserva(onibus, assento, passagem)
                        estado.reservas.add(reserva)
                        reservas.insert(0, reserva)
                        app.reservas.insert(parent='',
                                            index=0,
                                            values=campos_reserva_formatados(
                                                reserva),
                                            iid=reserva)
                monitoradas.onibus_visiveis.add(onibus)

            dados.append([estado.linhas_entradas[linha], reservas])

        ctrl.update_action(app, 'add')

        historico_undo = monitoradas.historico_undo
        historico_redo = monitoradas.historico_redo
        historico_redo.clear()
        historico_undo.append(['add', dados])

        app.contador_linhas['text'] = fm.form_cont_linhas(
            len(estado.linhas_visiveis))
        app.contador_onibus['text'] = fm.form_cont_onibus(
            len(monitoradas.onibus_visiveis))
        app.contador_reservas['text'] = fm.form_cont_reservas(
            len(estado.reservas))
        configuracoes.destroy()
コード例 #6
0
def exibir_relatorio(app):
    '''
        Constrói e exibe a janela do relatório.
    '''
    data_atual = monitoradas.data_atual

    janela = tk.Toplevel()
    janela.grab_set()
    janela.title('Relatório')
    janela.resizable(True, True)
    janela.minsize(700, 700)
    janela.transient(app.root)

    frame = ttk.Frame(janela, padding=10)
    frame.pack(fill='both', expand=True)

    def toggle_states(master, children):
        master_value = master.get()
        for state in children:
            state.set(master_value)
        if master_value:
            janela.minsize(700, 700)
            botao_exportar.state(['!disabled'])
            painel_arrecadado.grid()
            painel_passagens.grid()
            painel_ocupacao.grid()
        else:
            janela.minsize(700, 250)
            botao_exportar.state(['disabled'])
            painel_arrecadado.grid_remove()
            painel_passagens.grid_remove()
            painel_ocupacao.grid_remove()

    master_state = tk.IntVar(value=1)
    checkbox_master = ttk.Checkbutton(frame, variable=master_state)
    descriptive_label = ttk.Label(frame, text='Informações', anchor='center')

    distribuicao_passageiros = [0, 0, 0]  # [gratuita, meia, inteira]
    linha_dia_reservas = dict()
    monitoradas.arrecadado = dict()
    monitoradas.ocupacao_media_semanal = dict()

    total_passageiros = len(estado.reservas)

    def hide_widget(widget, show=False):
        x, y = janela.minsize()
        if not show:
            janela.minsize(x, y - 150)
            widget.grid_remove()
            if not any(x.get() for x in
                       [arrecadado_state, passagens_state, ocupacao_state]):
                botao_exportar.state(['disabled'])
            return
        botao_exportar.state(['!disabled'])
        janela.minsize(x, y + 150)
        widget.grid()

    def form_arrecadado(n):
        return '  Total Arrecadado: R$ %s  ' % fm.separar_milhares_decimal(n)

    painel_arrecadado = ttk.LabelFrame(frame,
                                       text='Total Arrecadado',
                                       padding=10)
    arrecadado_state = tk.IntVar(value=1)
    checkbox_arrecadado = ttk.Checkbutton(
        frame,
        variable=arrecadado_state,
        command=lambda: hide_widget(painel_arrecadado, arrecadado_state.get()))
    treeview_arrecadado = ttk.Treeview(painel_arrecadado,
                                       columns=('Linha', 'Arrecadado'),
                                       show='headings',
                                       selectmode='none',
                                       name='arrecadado')

    for i, coluna in enumerate(('Linha', 'Arrecadado (R$)')):
        treeview_arrecadado.heading(
            i,
            text=coluna.title(),
            command=lambda col=i: util.treeview_sort_column(
                app, treeview_arrecadado, col, False))
        treeview_arrecadado.column(i,
                                   width=0,
                                   minwidth=len(coluna) * 15,
                                   anchor='center')

    treeview_arrecadado.bind(
        '<Motion>', lambda ev: util.last_separator(ev, treeview_arrecadado))
    treeview_arrecadado.bind(
        '<1>', lambda ev: util.last_separator(ev, treeview_arrecadado))

    treeview_arrecadado_scroller_v = ttk.Scrollbar(
        painel_arrecadado,
        orient='vertical',
        command=treeview_arrecadado.yview)
    treeview_arrecadado_scroller_h = ttk.Scrollbar(
        painel_arrecadado,
        orient='horizontal',
        command=treeview_arrecadado.xview)

    treeview_arrecadado['yscrollcommand'] = treeview_arrecadado_scroller_v.set
    treeview_arrecadado['xscrollcommand'] = treeview_arrecadado_scroller_h.set

    passagens_state = tk.IntVar(value=1)
    painel_passagens = ttk.LabelFrame(frame,
                                      text='  Distribuição de Passagens  ',
                                      padding=10)
    checkbox_passagens = ttk.Checkbutton(
        frame,
        variable=passagens_state,
        command=lambda: hide_widget(painel_passagens, passagens_state.get()))
    label_passageiros = ttk.Label(painel_passagens,
                                  text='Total de Passageiros: %s' %
                                  fm.separar_milhares(total_passageiros),
                                  anchor='center')
    label_inteira = ttk.Label(painel_passagens, anchor='center')
    label_meia = ttk.Label(painel_passagens, anchor='center')
    label_gratuita = ttk.Label(painel_passagens, anchor='center')

    def form_ocupacao(n):
        return '  Ocupação Média: %s%%  ' % fm.separar_milhares_decimal(n)

    painel_ocupacao = ttk.LabelFrame(frame, text=form_ocupacao(0), padding=10)
    ocupacao_state = tk.IntVar(value=1)
    checkbox_ocupacao = ttk.Checkbutton(
        frame,
        variable=ocupacao_state,
        command=lambda: hide_widget(painel_ocupacao, ocupacao_state.get()))

    treeview_ocupacao = ttk.Treeview(painel_ocupacao,
                                     columns=('Linha', 'Dom', 'Seg', 'Ter',
                                              'Qua', 'Qui', 'Sex', 'Sab'),
                                     show='headings',
                                     selectmode='none',
                                     name='ocupacao')

    for i, coluna in enumerate(
        ('Linha', 'Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab')):
        treeview_ocupacao.heading(
            i,
            text=coluna.title(),
            command=lambda col=i: util.treeview_sort_column(
                app, treeview_ocupacao, col, False))
        treeview_ocupacao.column(i,
                                 width=0,
                                 minwidth=len(coluna) * 15,
                                 anchor='center')

    treeview_ocupacao.bind(
        '<Motion>', lambda ev: util.last_separator(ev, treeview_ocupacao))
    treeview_ocupacao.bind(
        '<1>', lambda ev: util.last_separator(ev, treeview_ocupacao))

    treeview_ocupacao_scroller_v = ttk.Scrollbar(
        painel_ocupacao, orient='vertical', command=treeview_ocupacao.yview)
    treeview_ocupacao_scroller_h = ttk.Scrollbar(
        painel_ocupacao, orient='horizontal', command=treeview_ocupacao.xview)

    treeview_ocupacao['yscrollcommand'] = treeview_ocupacao_scroller_v.set
    treeview_ocupacao['xscrollcommand'] = treeview_ocupacao_scroller_h.set

    checkbox_master['command'] = lambda: toggle_states(
        master_state, [arrecadado_state, passagens_state, ocupacao_state])

    def exportar_relatorio():
        '''
            Exporta o relatório como um arquivo .txt
        '''
        # pede um caminho ao usuário
        endereco = filedialog.asksaveasfilename(title='Exportar Relatório',
                                                defaultextension='.txt',
                                                filetypes=[('txt', '*.txt'),
                                                           ('csv', '*.csv'),
                                                           ('todos', '*')],
                                                parent=janela)
        if endereco == '':
            return
        # cria o arquivo no endereço especificado pelo usuário
        with open(endereco, 'w') as arquivo_relatorio:
            # as variáveis *_state são IntVar, que possuem valor 1 se o check está selecionado, e 0 caso contrário
            # vamos escrever no arquivo .txt apenas as seções em que os checks estão marcados
            if arrecadado_state.get():
                arquivo_relatorio.write('{:*^101}\n'.format(' ARRECADAÇÃO '))
                arquivo_relatorio.write('%s\n' % painel_arrecadado['text'])
                arquivo_relatorio.write('{:*^49} | {:*^49}\n'.format(
                    ' LINHA ', ' ARRECADADO (R$) '))
                for linha in treeview_arrecadado.get_children():
                    arquivo_relatorio.write(
                        '{Linha:^50}|{Arrecadado:^50}\n'.format_map(
                            treeview_arrecadado.set(linha)))
            if passagens_state.get():
                if arrecadado_state.get():
                    arquivo_relatorio.write('\n\n')
                arquivo_relatorio.write(
                    '{:*^101}\n'.format(' DISTRIBUIÇÃO DE PASSAGENS '))
                arquivo_relatorio.write('{:^49} : {:^49}\n'.format(
                    'TOTAL', fm.separar_milhares(total_passageiros)))
                arquivo_relatorio.write('{:^49} : {:^49}\n'.format(
                    'INTEIRA',
                    fm.separar_milhares_precisao(percentual_inteira) + ' %'))
                arquivo_relatorio.write('{:^49} : {:^49}\n'.format(
                    'MEIA',
                    fm.separar_milhares_precisao(percentual_meia) + ' %'))
                arquivo_relatorio.write('{:^49} : {:^49}\n'.format(
                    'GRATUITA',
                    fm.separar_milhares_precisao(percentual_gratuita) + ' %'))
            if ocupacao_state.get():
                if passagens_state.get():
                    arquivo_relatorio.write('\n\n')
                arquivo_relatorio.write(
                    '{:*^167}\n'.format(' OCUPAÇÃO MÉDIA SEMANAL (%) '))
                arquivo_relatorio.write(
                    ('|'.join(['{:*^20}'] * 8) + '\n').format(
                        ' LINHA ', ' DOM ', ' SEG ', ' TER ', ' QUAR ',
                        ' QUI ', ' SEX ', ' SAB '))
                for linha in treeview_ocupacao.get_children():
                    nome_linha = treeview_ocupacao.set(linha, 'Linha')
                    arquivo_relatorio.write(
                        ('|'.join(['{:*^20}'] * 8) + '\n').format(
                            f' {nome_linha} ',
                            *map(
                                lambda x: ' ' + fm.separar_milhares_decimal(
                                    100 * x) + ' ',
                                monitoradas.ocupacao_media_semanal[linha])))

    botao_exportar = ttk.Button(frame,
                                text='Exportar',
                                command=exportar_relatorio)

    checkbox_master.grid(row=0, column=0)
    descriptive_label.grid(row=0, column=1)

    checkbox_arrecadado.grid(row=1, column=0)
    painel_arrecadado.grid(row=1, column=1, sticky='news')
    treeview_arrecadado.grid(row=0, column=0, sticky='news')
    treeview_arrecadado_scroller_v.grid(row=0, column=1, sticky='ns')
    treeview_arrecadado_scroller_h.grid(row=1, column=0, sticky='we')
    painel_arrecadado.rowconfigure(0, weight=1)
    painel_arrecadado.columnconfigure(0, weight=1)

    checkbox_passagens.grid(row=2, column=0)
    painel_passagens.grid(row=2, column=1, sticky='ns')
    label_passageiros.grid(pady=(0, 10))
    label_inteira.grid()
    label_meia.grid()
    label_gratuita.grid()
    for i in range(4):
        painel_passagens.rowconfigure(i, weight=1)
    painel_passagens.columnconfigure(0, weight=1)

    checkbox_ocupacao.grid(row=3, column=0)
    painel_ocupacao.grid(row=3, column=1, sticky='news')
    treeview_ocupacao.grid(row=0, column=0, sticky='news')
    treeview_ocupacao_scroller_v.grid(row=0, column=1, sticky='ns')
    treeview_ocupacao_scroller_h.grid(row=1, column=0, sticky='we')
    painel_ocupacao.rowconfigure(0, weight=1)
    painel_ocupacao.columnconfigure(0, weight=1)

    botao_exportar.grid(row=4, column=0, columnspan=2)

    for i in range(3):
        frame.rowconfigure(i + 1, weight=1)

    frame.columnconfigure(1, weight=1)

    for reserva in estado.reservas:
        onibus = reserva[:-6]
        linha = onibus[:-7]
        dados_reserva = decodificar_id_reserva(reserva)
        passagem, inteira = dados_reserva[-1]
        monitoradas.arrecadado[linha] = monitoradas.arrecadado.get(
            linha, 0) + it.centavos(inteira, passagem)
        distribuicao_passageiros[passagem] += 1
        linha_dia_reservas[linha] = linha_dia_reservas.get(linha, [0] * 7)
        linha_dia_reservas[linha][decodificar_id_onibus(onibus).isoweekday() %
                                  7] += 1

    if total_passageiros == 0:
        percentual_inteira = percentual_meia = percentual_gratuita = 0
    else:
        (percentual_gratuita, percentual_meia, percentual_inteira) = [
            100 * distribuicao_passageiros[i] / total_passageiros
            for i in range(3)
        ]

    label_inteira['text'] = 'Inteira: %s%%' % fm.separar_milhares_decimal(
        percentual_inteira)
    label_meia['text'] = 'Meia: %s%%' % fm.separar_milhares_decimal(
        percentual_meia)
    label_gratuita['text'] = 'Gratuita: %s%%' % fm.separar_milhares_decimal(
        percentual_gratuita)

    total_arrecadado = 0

    def onibus_por_dia(linha):
        data_cadastro = estado.linhas_entradas[linha].horario
        dia_da_semana = data_cadastro.isoweekday()
        intervalo = (data_atual +
                     dt.timedelta(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA) -
                     data_cadastro).days
        onibus_diarios = [0] * 7

        for i in range(7):
            if i in range((intervalo + 1) % 7):
                num_onibus = (intervalo + 1) // 7 + 1
            else:
                num_onibus = (intervalo + 1) // 7

            onibus_diarios[(dia_da_semana + i) % 7] = num_onibus

        return onibus_diarios.copy()

    vagas_ofertadas_totais = 0
    for linha in estado.linhas_entradas:
        destino, horario = estado.linhas_entradas[linha][:2]
        nome_linha = '%s-%s' % (destino.upper(), fm.form_tempo(horario))
        monitoradas.arrecadado[linha] = monitoradas.arrecadado.get(linha, 0)
        quantia = monitoradas.arrecadado[linha] / 100
        total_arrecadado += quantia
        treeview_arrecadado.insert(
            '',
            0,
            linha,
            values=(nome_linha, fm.separar_milhares_decimal(quantia)))
        onibus_diarios = onibus_por_dia(linha)
        capacidade_linha = estado.linhas_entradas[linha].fileiras * 2
        vagas_ofertadas_totais += sum(onibus_diarios) * capacidade_linha
        # print(f'Ônibus diários: {onibus_diarios}\nCapacidade linha: {capacidade_linha}')
        monitoradas.ocupacao_media_semanal[linha] = [
            linha_dia_reservas.get(linha, {d: 0})[d] /
            (onibus_diarios[d] * capacidade_linha)
            if onibus_diarios[d] != 0 else 0 for d in range(7)
        ]
        treeview_ocupacao.insert(
            '',
            0,
            linha,
            values=(nome_linha, *map(
                lambda x: '%s%%' % fm.separar_milhares_decimal(100 * x),
                monitoradas.ocupacao_media_semanal[linha])))
    painel_ocupacao['text'] = form_ocupacao(
        100 * len(estado.reservas) /
        vagas_ofertadas_totais if vagas_ofertadas_totais != 0 else 0)

    painel_arrecadado['text'] = form_arrecadado(total_arrecadado)