예제 #1
0
def descadastrar_linhas(app, dados):
    actions = 0
    reservas_changed = False

    for dados_linha, reservas in dados:
        linha = gerar_id_linha(*dados_linha[:2])
        for reserva in reservas:
            estado.reservas.discard(reserva)
            app.reservas.delete(reserva)
            reservas_changed = True

        app.contador_reservas['text'] = fm.form_cont_reservas(
            len(estado.reservas))

        t = minutos_dia(dados_linha.horario)

        del estado.linhas_entradas[linha]
        estado.linhas_possiveis.add((dados_linha.destino.title(), t))
        estado.horarios_linhas[t].remove(linha)
        estado.linhas_visiveis.remove(linha)
        app.contador_linhas['text'] = fm.form_cont_linhas(
            len(estado.linhas_visiveis))
        onibus_filhos = dados_linha.onibus.keys()
        monitoradas.onibus_visiveis -= onibus_filhos
        estado.onibus_invisiveis -= onibus_filhos
        app.contador_onibus['text'] = fm.form_cont_onibus(
            len(monitoradas.onibus_visiveis))
        app.linhas.delete(linha)

        actions += 1

    if not reservas_changed:
        app.abas.select(app.aba_linhas)

    return actions
예제 #2
0
def reservar(app, indice, reserva):
    '''
        Atualiza o estado da aplicação para incluir a reserva especificada no argumento.
    '''
    # lembrando que reserva é um wrapper (veja csv_to_reserva) e reserva.reserva é o id da reserva
    estado.reservas.add(reserva.reserva)
    # o wrapper nos dá as informações que precisamos
    campus = reserva.campus.title()
    partida = reserva.partida.strftime('%d/%m/%y %H:%M')
    assento = reserva.assento
    passagem = '%8s: R$%s' % (cte.INDICE_PASSAGEM[reserva.passagem].title(
    ), it.inteira_termo(reserva.inteira, reserva.passagem))
    # adicionamos a reserva à interface gráfica
    app.reservas.insert(parent='',
                        index=indice,
                        values=(campus, partida, assento, passagem),
                        iid=reserva.reserva)

    onibus = reserva.onibus
    linha = reserva.linha
    estado.linhas_entradas[linha].onibus[onibus][reserva.assento] = reserva.passagem

    # vamos atualizar o valor da coluna "assentos livres" do ônibus associado à reserva
    prev_values = app.linhas.set(onibus)
    prev_values['assentos livres'] = int(prev_values['assentos livres']) - 1
    # colocamos foco e selecionamos o ônibus para o qual foi realizada a reserva
    app.linhas.see(onibus)
    app.linhas.selection_add(onibus)
    app.linhas.item(onibus, values=tuple(prev_values.values()))
    app.contador_reservas['text'] = fm.form_cont_reservas(len(estado.reservas))
예제 #3
0
def devolver_reservas(app, reservas):
    actions = 0
    app.linhas.selection_set(*[])
    for _, reserva in reservas:
        estado.reservas.discard(reserva)
        app.reservas.delete(reserva)

        onibus = reserva[:-6]
        linha = onibus[:-7]

        if onibus in estado.linhas_entradas[linha].onibus:
            del estado.linhas_entradas[linha].onibus[onibus][
                decodificar_id_reserva(reserva).assento]

            prev_values = app.linhas.set(onibus)
            prev_values['assentos livres'] = int(
                prev_values['assentos livres']) + 1
            app.linhas.see(onibus)
            app.linhas.selection_add(onibus)
            app.linhas.item(onibus, values=tuple(prev_values.values()))

        actions += 1

    app.contador_reservas['text'] = fm.form_cont_reservas(len(estado.reservas))

    return actions
예제 #4
0
def rereservar_reservas(app, reservas):
    actions = 0
    app.reservas.selection_set(*[])
    app.linhas.selection_set(*[])
    for indice, reserva in reservas:
        estado.reservas.add(reserva)
        app.reservas.insert(parent='',
                            index=indice,
                            values=campos_reserva_formatados(reserva),
                            iid=reserva)
        app.reservas.selection_add(reserva)

        onibus = reserva[:-6]
        linha = onibus[:-7]
        if onibus in estado.linhas_entradas[linha].onibus:
            info_reserva = decodificar_id_reserva(reserva)
            assento, passagem = info_reserva.assento, info_reserva.passagem.tipo
            estado.linhas_entradas[linha].onibus[onibus][assento] = passagem

            prev_values = app.linhas.set(onibus)
            prev_values['assentos livres'] = int(
                prev_values['assentos livres']) - 1
            app.linhas.see(onibus)
            app.linhas.selection_add(onibus)
            app.linhas.item(onibus, values=tuple(prev_values.values()))
        actions += 1

    app.contador_reservas['text'] = fm.form_cont_reservas(len(estado.reservas))

    return actions
예제 #5
0
def devolver(app):
    '''
        Remove as reservas selecionadas, devolvendo as vagas.
    '''
    selecao = app.reservas.selection()
    if len(selecao) == 0:
        texto = 'Selecione reservas cujos ônibus ainda não partiram.'
        messagebox.showwarning(title='Não Há O Que Devolver', message=texto)
        return

    devolviveis = [
        reserva for reserva in selecao if app.linhas.exists(reserva[:-6])
    ]
    if len(devolviveis) < len(selecao):
        if len(devolviveis) == 0:
            texto = 'Ônibus já partiram. Impossível devolver.'
            messagebox.showwarning(title='Impossível Devolver', message=texto)
            return
        else:
            texto = 'Os ônibus de algumas reservas já partiram, deseja devolver as demais?'
            resposta = messagebox.askyesno(title='ônibus Partiram',
                                           message=texto,
                                           default='yes')
            if not resposta:
                return
    reservas = []
    app.linhas.selection_set(*[])
    for reserva in devolviveis:
        reservas.insert(0, [app.reservas.index(reserva), reserva])
        estado.reservas.discard(reserva)
        app.reservas.delete(reserva)

        onibus = reserva[:-6]
        linha = onibus[:-7]

        del estado.linhas_entradas[linha].onibus[onibus][
            decodificar_id_reserva(reserva).assento]

        prev_values = app.linhas.set(onibus)
        prev_values['assentos livres'] = int(
            prev_values['assentos livres']) + 1
        app.linhas.see(onibus)
        app.linhas.selection_add(onibus)
        app.linhas.item(onibus, values=tuple(prev_values.values()))

    app.contador_reservas['text'] = fm.form_cont_reservas(len(estado.reservas))
    monitoradas.historico_redo.clear()
    monitoradas.historico_undo.append(['refund', reservas])
예제 #6
0
    def finalizar():
        '''
            Realiza a reserva. Chamado quando o usuário clica no botão Reservar.
        '''
        nonlocal selected
        assento = int(selected['text'])
        if len(estado.linhas_entradas[linha].onibus[onibus]) == 0:
            shift = (sum(coordenadas_assento(assento)) + 1) % 2
            assentos_invisiveis = {
                assento_coordenadas(i, 2 * j + (i + shift) % 2)
                for j in range(2) for i in range(num_fileiras)
            }
            for assento_indisponivel in assentos_invisiveis:
                labels[assento_indisponivel].grid_remove()
        tipo = passagem.get()
        estado.linhas_entradas[linha].onibus[onibus][assento] = tipo
        reserva = gerar_id_reserva(onibus, assento, tipo)
        estado.reservas.add(reserva)
        # insere essa reserva na aba de reservas
        app.reservas.insert(parent='',
                            index=0,
                            values=campos_reserva_formatados(reserva),
                            iid=reserva)
        prev_values = linhas.set(onibus)
        prev_values['assentos livres'] = int(
            prev_values['assentos livres']) - 1
        linhas.item(onibus, values=tuple(prev_values.values()))
        labels[assento]['cursor'] = 'X_cursor'
        labels[assento]['style'] = estilo[tipo]
        labels[assento].unbind('<Button-1>')
        selected = ttk.Label(painel_assentos, text='None')

        historico_undo = monitoradas.historico_undo
        historico_redo = monitoradas.historico_redo
        historico_redo.clear()
        historico_undo.append(['book', reserva])
        button_state()
        app.contador_reservas['text'] = fm.form_cont_reservas(
            len(estado.reservas))
        ctrl.update_action(app, 'book')
예제 #7
0
def povoar_treeviews(app):
    '''
        Inicializa a interface gráfica com os dados persistidos.
    '''
    onibus_invisiveis = set()
    for linha in estado.ordem_inicial_linhas:
        onibus_invisiveis.update(reinsert_linha(app, linha))
    estado.ordem_inicial_linhas = tuple()
    for linha in estado.linhas_entradas.keys() - estado.linhas_visiveis:
        onibus_invisiveis.update(reinsert_linha(app, linha, False))
    for reserva in estado.ordem_inicial_reservas:
        app.reservas.insert(parent='',
                            index=0,
                            values=campos_reserva_formatados(reserva),
                            iid=reserva)
    estado.onibus_invisiveis = onibus_invisiveis
    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))
    print('Dados persistentes carregados.')
예제 #8
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()
예제 #9
0
def recadastrar_linhas(app, dados):
    actions = 0
    reservas_changed = False

    app.linhas.selection_set(*[])
    app.reservas.selection_set(*[])

    for dados_linha, reservas in dados:
        linha = gerar_id_linha(*dados_linha[:2])

        estado.linhas_entradas[linha] = cte.ESTRUTURA_LINHA(
            *dados_linha[:-1], dict())

        for reserva in reservas:
            estado.reservas.add(reserva)
            app.reservas.insert(parent='',
                                index=0,
                                values=campos_reserva_formatados(reserva),
                                iid=reserva)
            reservas_changed = True

        app.contador_reservas['text'] = fm.form_cont_reservas(
            len(estado.reservas))

        t = minutos_dia(dados_linha.horario)
        estado.linhas_possiveis.remove((dados_linha.destino.title(), t))

        estado.horarios_linhas[t] = estado.horarios_linhas.get(t, set()).union(
            {linha})
        estado.linhas_visiveis.add(linha)
        app.contador_linhas['text'] = fm.form_cont_linhas(
            len(estado.linhas_visiveis))

        app.linhas.insert(parent='',
                          index=0,
                          values=campos_linha_formatados(dados_linha),
                          iid=linha)

        data_atual = monitoradas.data_atual.replace(second=0, microsecond=0)
        minutos_atual = data_atual.hour * 60 + data_atual.minute

        if t == minutos_atual:
            periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1)
        elif t < minutos_atual:
            periodo = range(1, cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1)
        else:
            periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA)

        for d in periodo:
            partida = (data_atual + dt.timedelta(d)).replace(
                hour=dados_linha.horario.hour,
                minute=dados_linha.horario.minute)
            onibus = gerar_id_onibus(linha, partida)
            assentos = dados_linha.onibus.get(onibus, dict())
            app.linhas.insert(
                parent=linha,
                index='end',
                values=('-', fm.form_data(partida),
                        2 * int(dados_linha.fileiras) - len(assentos), '-'),
                iid=onibus)
            monitoradas.onibus_visiveis.add(onibus)
            app.contador_onibus['text'] = fm.form_cont_onibus(
                len(monitoradas.onibus_visiveis))
            estado.linhas_entradas[linha].onibus[onibus] = assentos

        app.linhas.selection_add(linha)
        app.reservas.selection_add(*reservas)

        actions += 1

    if not reservas_changed:
        app.abas.select(app.aba_linhas)

    return actions
예제 #10
0
    def __init__(self, root):
        '''
            Inicializa a interface gráfica e armazena as instâncias dos componentes
            nesse objeto (self).

            O argumento root é uma instância de Tk, a janela principal da aplicação.

            O manager grid é utilizado para o posicionamento dos componentes, sendo especificado
            posições de linhas e colunas para os filhos de um componente.


            ::: self.component.grid(row=1, column=2, sticky='n')
            significa que component será posicionado na LINHA 1 e COLUNA 2 em relação ao seu master
            e alinhado ao NORTE ('n'), ou seja, o componente "gruda" no topo.
            sticky='ns' significa que o componente "gruda" no NORTE e no SUL, se esticando verticalmente.
            Outros sticky possuem significado com lógica similar.

            ::: self.component.columnconfigure(0, weight=1)
            ::: self.component.columnconfigure(1, weight=1)
            ::: self.component.columnconfigure(2, weight=1)
            significa que os filhos de component nas colunas 0, 1 e 2 possuem o mesmo PESO, ou seja,
            ocupam a mesma quantidade de espaço

            ::: self.component.bind('<Evento>', funcao)
            significa que o event handler "funcao" será associado (bind) ao evento <Evento>.
            assim, sempre que <Evento> for recebido por component, funcao irá executar
        '''
        root.minsize(*cte.TAMANHO_DA_JANELA_PRINCIPAL
                     )  # tamanho mínimo da janela principal
        # título, aparece na porção superior do programa
        root.title(cte.TITULO)
        # permitimos o acesso por self.root (ou app.root, fora dessa função)
        self.root = root
        # ícone
        self.estilo = ttk.Style()
        self.estilo.configure('inteira.TLabel',
                              background='#e30000',
                              foreground='#fff')
        self.estilo.configure('meia.TLabel',
                              background='#eb7100',
                              foreground='#fff')
        self.estilo.configure('gratuita.TLabel',
                              background='#095f9c',
                              foreground='#fff')
        self.estilo.configure('available.TLabel',
                              background='#07de00',
                              foreground='#fff')
        self.estilo.configure('selected.TLabel',
                              background='#fff',
                              foreground='#005')

        self.frame = ttk.Frame()
        self.frame.pack(fill='both', expand=True)

        # crio um objeto Notebook em root (janela principal), que permite a divisão de uma janela em abas
        self.abas = ttk.Notebook(self.frame)

        # a primeira aba, onde o usuário poderá manipular as entradas de linhas de ônibus
        self.aba_linhas = ttk.Frame(self.abas, padding=12, name='aba_linhas')
        self.aba_linhas.pack(
        )  # peço ao módulo para exibir a aba usando o método pack

        self.linhas = ttk.Treeview(self.aba_linhas,
                                   columns=cte.COLUNAS_LINHAS,
                                   selectmode='extended',
                                   name='linhas')
        self.linhas.column('#0', width=20, stretch=0)
        for i, coluna in enumerate(cte.COLUNAS_LINHAS):
            self.linhas.heading(
                i,
                text=coluna.title(),
                command=lambda col=i: util.treeview_sort_column(
                    self, self.linhas, col, False))
            self.linhas.column(i,
                               width=0,
                               minwidth=len(coluna) * 15,
                               anchor='center')
        self.linhas.bind('<Motion>',
                         lambda ev: util.last_separator(ev, self.linhas))
        self.linhas.bind('<1>',
                         lambda ev: util.last_separator(ev, self.linhas))

        self.linhas_scroller_v = ttk.Scrollbar(self.aba_linhas,
                                               orient='vertical',
                                               command=self.linhas.yview)
        self.linhas_scroller_h = ttk.Scrollbar(self.aba_linhas,
                                               orient='horizontal',
                                               command=self.linhas.xview)

        self.linhas['yscrollcommand'] = self.linhas_scroller_v.set
        self.linhas['xscrollcommand'] = self.linhas_scroller_h.set

        # gridding - aba_linhas
        self.linhas.grid(row=0, column=0, sticky='nsew')
        self.linhas_scroller_v.grid(row=0, column=1, sticky='ens')
        self.linhas_scroller_h.grid(row=1, column=0, sticky='sew')

        self.aba_reservas = ttk.Frame(self.abas,
                                      padding=12,
                                      name='aba_reservas')
        self.aba_reservas.pack()

        self.reservas = ttk.Treeview(self.aba_reservas,
                                     columns=cte.COLUNAS_RESERVAS,
                                     show='headings',
                                     selectmode='extended',
                                     name='reservas')
        for i, coluna in enumerate(cte.COLUNAS_RESERVAS):
            self.reservas.heading(
                i,
                text=coluna.title(),
                command=lambda col=i: util.treeview_sort_column(
                    self, self.reservas, col, False))
            self.reservas.column(i,
                                 width=0,
                                 minwidth=len(coluna) * 15,
                                 anchor='center')
        self.reservas.bind('<Motion>',
                           lambda ev: util.last_separator(ev, self.reservas))
        self.reservas.bind('<1>',
                           lambda ev: util.last_separator(ev, self.reservas))

        self.reservas_scroller_v = ttk.Scrollbar(self.aba_reservas,
                                                 orient='vertical',
                                                 command=self.reservas.yview)
        self.reservas_scroller_h = ttk.Scrollbar(self.aba_reservas,
                                                 orient='horizontal',
                                                 command=self.reservas.xview)

        self.reservas['yscrollcommand'] = self.reservas_scroller_v.set
        self.reservas['xscrollcommand'] = self.reservas_scroller_h.set

        # gridding - aba_reservas
        self.reservas.grid(row=0, column=0, sticky='nsew')
        self.reservas_scroller_v.grid(row=0, column=1, sticky='ens')
        self.reservas_scroller_h.grid(row=1, column=0, sticky='new')

        self.painel_botoes_reservas = ttk.Frame(self.aba_reservas)
        self.painel_botoes_reservas.grid(row=2,
                                         column=0,
                                         columnspan=1,
                                         sticky='nesw',
                                         pady=(12, 6))

        self.botao_devolver = ttk.Button(self.painel_botoes_reservas,
                                         command=lambda: util.devolver(self),
                                         text='Devolver')
        self.botao_devolver.grid(row=0, column=0, sticky='we')

        self.botao_importar_reservas = ttk.Button(
            self.painel_botoes_reservas,
            command=lambda: arq.importar_reservas(self),
            text='Importar')
        self.botao_importar_reservas.grid(row=0, column=1, sticky='we')

        self.botao_exportar_reservas = ttk.Button(
            self.painel_botoes_reservas,
            command=lambda: arq.exportar_reservas(self),
            text='Exportar')
        self.botao_exportar_reservas.grid(row=0, column=2, sticky='we')

        for i in range(3):
            self.painel_botoes_reservas.columnconfigure(i, weight=1)

        self.painel_entrada = ttk.Frame(self.aba_linhas)
        self.painel_entrada.columnconfigure(0, weight=1)
        self.painel_entrada.columnconfigure(1, weight=1)
        self.painel_entrada.columnconfigure(2, weight=1)
        self.painel_entrada.columnconfigure(3, weight=1)

        self.painel_destino = ttk.Frame(self.painel_entrada)
        self.painel_destino.grid(row=0, column=0, sticky='w', padx=0)

        self.painel_partida = ttk.Frame(self.painel_entrada)
        self.painel_partida.grid(row=0, column=1, sticky='we', padx=0)

        self.entrada_destino = ttk.Combobox(self.painel_destino,
                                            justify='center',
                                            textvariable=monitoradas.destino,
                                            values=estado.destinos,
                                            width=15)
        self.entrada_destino.bind('<FocusOut>', dest.atualizar_destino)
        self.entrada_destino.bind('<Return>', dest.validar_destino)
        self.entrada_destino.grid(row=0, column=0, sticky='ew', padx=0)

        self.botao_add_destino = ttk.Button(
            self.painel_destino,
            command=lambda: dest.add_destino(self),
            text='+',
            width=2)
        self.botao_add_destino.grid(row=0, column=2, sticky='w')

        self.botao_rmv_destino = ttk.Button(
            self.painel_destino,
            command=lambda: dest.rmv_destino(self),
            text='-',
            width=2)
        self.botao_rmv_destino.grid(row=0, column=1, sticky='w')

        self.spin_hora = Spinbox(self.painel_entrada,
                                 textvariable=monitoradas.tempo_formatado,
                                 justify='center',
                                 width=6)
        self.spin_hora.bind('<<Increment>>',
                            lambda e: date.increment(self, cte.MIN))
        self.spin_hora.bind('<<Decrement>>',
                            lambda e: date.decrement(self, cte.MIN))
        self.spin_hora.bind('<Return>', lambda e: date.update(self))
        self.spin_hora.grid(row=0, column=1, sticky='', padx=(0, 30))

        self.spin_vagas = Spinbox(self.painel_entrada,
                                  textvariable=monitoradas.vagas,
                                  justify='center',
                                  width=4)
        self.spin_vagas.bind('<<Increment>>', vg.incrementar_vagas)
        self.spin_vagas.bind('<<Decrement>>', vg.decrementar_vagas)
        self.spin_vagas.bind('<Return>', vg.atualizar_vagas)
        self.spin_vagas.grid(row=0, column=2, sticky='', padx=(30, 0))

        self.painel_inteira = ttk.Frame(self.painel_entrada)
        self.painel_inteira.columnconfigure(0, weight=1)
        self.painel_inteira.columnconfigure(1, weight=1)
        self.painel_inteira.grid(row=0, column=3, sticky='e', padx=0)

        self.reais = ttk.Label(self.painel_inteira,
                               text='R$',
                               width=4,
                               anchor='center')
        self.reais.grid(row=0, column=0, sticky='e')

        self.spin_inteira = Spinbox(self.painel_inteira,
                                    textvariable=monitoradas.inteira,
                                    justify='center',
                                    width=5)
        self.spin_inteira.bind('<<Increment>>', it.incrementar_inteira)
        self.spin_inteira.bind('<<Decrement>>', it.decrementar_inteira)
        self.spin_inteira.bind('<Return>', it.atualizar_inteira)
        self.spin_inteira.grid(row=0, column=1, sticky='ew', padx=0)

        self.painel_botoes = ttk.Frame(self.painel_entrada)
        self.painel_botoes.columnconfigure(0, weight=1)
        self.painel_botoes.columnconfigure(1, weight=1)
        self.painel_botoes.columnconfigure(2, weight=1)
        self.painel_botoes.grid(row=1, column=0, columnspan=4, sticky='ew')

        self.root.bind('<Control-z>', lambda ev: hst.undo(self, ev))
        self.root.bind('<Control-Shift-KeyPress-Z>',
                       lambda ev: hst.redo(self, ev))
        self.root.bind('<Control-a>', lambda ev: util.select_all(self, ev))
        self.root.bind('<Delete>', lambda ev: util.del_selecao(self))
        self.root.bind('<Control-w>', lambda *_: quit())
        self.root.protocol("WM_DELETE_WINDOW", lambda: estado.persistir(self))

        self.botao_remover = ttk.Button(self.painel_botoes, text='Remover')
        self.botao_remover.bind('<ButtonPress>',
                                lambda ev: util.remover_linhas(self, ev))
        self.botao_remover.bind('<ButtonRelease>', util.released)
        self.botao_remover.grid(row=0, column=0, sticky='we')

        self.botao_editar = ttk.Button(self.painel_botoes,
                                       text='Editar',
                                       command=lambda: util.editar_linha(self))
        self.botao_editar.grid(row=0, column=1, sticky='we')

        self.botao_adicionar = ttk.Button(
            self.painel_botoes,
            text='Adicionar',
            command=lambda: util.adicionar_linha(self))
        self.botao_adicionar.grid(row=0, column=2, sticky='we')

        self.botao_reservar = ttk.Button(
            self.painel_botoes,
            text='Reservar',
            command=lambda: util.reservar_viagem(self))
        self.botao_reservar.grid(row=1, column=3, sticky='we', padx=(10, 0))
        self.painel_botoes.columnconfigure(3, weight=1)

        self.botao_gerar = ttk.Button(self.painel_botoes,
                                      text='Gerar',
                                      command=lambda: gerar.gerar_linhas(self))
        self.botao_gerar.grid(row=0, column=3, sticky='we', padx=(10, 0))

        self.botao_relatorio = ttk.Button(
            self.painel_botoes,
            text='Relatório',
            command=lambda: rel.exibir_relatorio(self))
        self.botao_relatorio.grid(row=1, column=0, sticky='we')

        self.botao_importar_linhas = ttk.Button(
            self.painel_botoes,
            text='Importar',
            command=lambda: arq.importar_linhas(self))
        self.botao_importar_linhas.grid(row=1, column=1, sticky='we')

        self.botao_exportar_linhas = ttk.Button(
            self.painel_botoes,
            text='Exportar',
            command=lambda: arq.exportar_linhas(self))
        self.botao_exportar_linhas.grid(row=1, column=2, sticky='we')

        self.painel_contadores = ttk.Frame(self.frame)

        self.contador_linhas = ttk.Label(self.painel_contadores,
                                         text=fm.form_cont_linhas(
                                             len(estado.linhas_visiveis)),
                                         anchor='sw')
        self.contador_linhas.grid(row=0, column=0, sticky='sw')

        self.contador_onibus = ttk.Label(self.painel_contadores,
                                         text=fm.form_cont_onibus(
                                             len(monitoradas.onibus_visiveis)),
                                         anchor='sw')
        self.contador_onibus.grid(row=0, column=1, sticky='s')

        self.contador_reservas = ttk.Label(self.painel_contadores,
                                           text=fm.form_cont_reservas(
                                               len(estado.reservas)),
                                           anchor='sw')
        self.contador_reservas.grid(row=0, column=2, sticky='s')

        self.label_action = ttk.Label(self.painel_contadores,
                                      textvariable=monitoradas.cur_action,
                                      anchor='w',
                                      width=10)
        self.label_action.grid(row=0, column=3, sticky='se')

        for j in range(4):
            self.painel_contadores.columnconfigure(j, weight=1)

        self.painel_entrada.rowconfigure(0, weight=1)
        self.painel_entrada.rowconfigure(1, weight=1)
        self.painel_entrada.rowconfigure(2, weight=1)
        self.painel_entrada.grid(row=2, column=0, sticky='nsew')

        self.aba_linhas.columnconfigure(0, weight=2000)
        self.aba_linhas.rowconfigure(0, weight=100)
        self.aba_linhas.columnconfigure(1, weight=1)
        self.aba_linhas.rowconfigure(1, weight=1)
        self.aba_linhas.rowconfigure(2, minsize=70, weight=0)

        self.aba_reservas.columnconfigure(0, weight=2000)
        self.aba_reservas.columnconfigure(1, weight=1)
        self.aba_reservas.rowconfigure(0, weight=1)

        self.abas.add(self.aba_linhas, text='Linhas')
        self.abas.add(self.aba_reservas, text='Reservas')
        self.abas.grid(row=0, column=0, sticky='nesw')

        self.painel_contadores.grid(row=1,
                                    column=0,
                                    sticky='we',
                                    pady=5,
                                    padx=15)

        self.frame.rowconfigure(0, weight=1)
        self.frame.columnconfigure(0, weight=1)
        if estado.data is not None:
            util.povoar_treeviews(self)