Programação Orientada para Objectos


ISCTE

Ano lectivo 2000/2001

2º Semestre


Trabalho Final


Editor Simples

O objectivo do trabalho final é o desenvolvimento de um programa que permita a edição de documentos simples.  Entende-se por documento neste trabalho uma sequência de parágrafos, consistindo cada parágrafo numa sequência de glifos de tipos variados, bem como a sequência das acções que levaram ao seu estado presente.  O trabalho deverá ser realizado em Linux, fazendo uso dos pacotes Slang++ e Pacotes, fornecidos pelos docentes.  Estes pacotes estão instalados na máquina mercurio*.  Consulte a documentação (Manual do Slang++ em linha ou em PDF e Manual do Pacotes em linha ou em PDF).

* Se desejar usar os pacotes em casa deve instalá-los seguindo as instruções incluídas no Manual do Slang++ e no Manual do Pacotes.


Esclarecimentos e recomendações

Esta secção do enunciado poderá ser actualizada se surgirem esclarecimentos importantes acerca do enunciado original ou se os docentes pretenderem dar "dicas" de resolução.

Esclarecimentos:

  1. [Vazio]

Recomendações:

  1. Dedique-se ao Problema 3 rapidamente, já que lhe permitirá resolver uma parte do problema e familiarizar-se com as ferramentas a utilizar.
  2. Trabalhe em grupo: duas ou três cabeças pensam melhor que uma, dois ou três pares de olhos detectam mais depressa os erros que um só, etc.  Evite dividir demasiado o trabalho a realizar.
  3. Faça programas de teste das ferramentas que desenvolver.  Coloque asserções logo desde o início, e não no final apenas porque o enunciado obriga.
  4. Aprenda a usar o depurador.  Poupará muitas horas perdidas.
  5. Faça o esforço de interpretar as mensagens de erro do compilador.  São crípticas, por vezes, mas não incompreensíveis.  Poupará muitas horas perdidas também.
  6. Aprenda a trabalhar com as ferramentas ao seu dispor: compilador, depurador, editor, etc., de modo a poder usá-las eficientemente.

1  Conceitos básicos

1.1  Glifos

A unidade básica de constituição dos documentos a editar é o glifo.  Existem três tipos concretos de glifos a representar:

  1. Caractere gráfico usual.  Assumindo a utilização do código ISO-8859-1 (Latin-1), correspondem aos caracteres com códigos entre 32 e 126 (inclusive) ou entre 160 e 255.
  2. Sorriso da Rede.  Os sorrisos a representar são os seguintes:
  3. Moldura.  Uma moldura para um outro qualquer glifo, que pode inclusivamente ser outra moldura.

Na Figura 1 encontra exemplos visuais destes tipos de glifos.  Na primeira linha encontra apenas caracteres, na segunda apenas sorrisos e na terceira encontra sorrisos e caracteres emoldurados. 

Figura 1:  Exemplos de glifos.

Os glifos dividem-se em quebráveis e não-quebráveis.  Um glifo quebrável possibilita a divisão de um parágrafo em várias linhas para efeitos de visualização.  Os glifos quebráveis podem ter três comportamentos:

  1. Permitem a quebra do parágrafo à sua frente, pelo que permanecem na linha original.
  2. Permitem a quebra do parágrafo à sua frente, pelo que permanecem na linha original, mas repetem-se na nova linha (caso dos hífens).
  3. Desaparecem ao se quebrar a linha (caso dos espaços).

1.2  Parágrafos

Um parágrafo é simplesmente uma qualquer sequência de glifos.  A sequência pode estar vazia, i.e., não conter nenhum glifo.

A posição do cursor faz parte integrante da definição de cada parágrafo.  A posição do cursor será útil durante a edição e indica qual o glifo corrente do parágrafo.  Este cursor pode-se localizar sobre qualquer glifo do parágrafo ou sobre um glifo fictício final, inexistente (um pouco como acontecia no caso das listas).  Deslocamentos do cursor correspondem a acções realizadas sobre o documento (ver Secções 1.3.1 e 2.5).

1.3  Documentos

Um documento é uma sequência de parágrafos.  Existe sempre pelo menos um parágrafo num documento.  Existe um cursor de parágrafo, que indica qual o parágrafo corrente.  Este cursor apenas se pode localizar sobre parágrafos existentes.  

Faz também parte do documento a lista das acções efectuadas para o constituir no seu estado actual. Em teoria um documento é reconstituível na íntegra a partir desta lista de acções.

1.3.1  Acções

Todas as acções que levaram a alguma alteração do documento devem ser guardadas numa lista.  Este facto permite em qualquer ocasião da vida do documento, desfazer as acções sucessivamente até se obter o documento inicial, contendo apenas um parágrafo vazio.  As acções desfeitas podem ser refeitas, pelo que a posição na lista das acções da última acção desfeita faz também parte do documento.  Normalmente não existem quaisquer acções desfeitas na lista das acções, pelo que esta posição se refere a uma acção fictícia final, inexistente.

1.3.2  Representação em ficheiro

Os documentos podem ser guardados e carregados a partir de ficheiros de texto.  Cada ficheiro devem conter toda a informação do respectivo documento, incluindo a lista das acções que o produziu.  Não há qualquer formato pré-definido para os ficheiros.

2  Requisitos da interface

Nesta secção descrevem-se brevemente as funcionalidade requeridas para o editor do ponto de vista do seu utilizador.

2.1  Preliminares

Está disponível na máquina mercurio , (no directório /usr/local/bin) uma versão executável do programa (chamada editor-oficial) que lhe possibilitará experimentar à medida que for lendo o enunciado.  Não se esqueça que o nome do programa fornecido pelos docentes é editor-oficial, pelo que não deverá usar esse nome para o seu próprio programa.

O executável está disponível quer para RedHat 6.2 quer para RedHat 7.0.

2.2  Invocando o editor

O editor desenvolvido deverá chamar-se editor.  Este deve poder ser invocado de uma de duas formas.  

A primeira forma de invocação é:

./editor doc1

Neste caso o editor deve começar por ler o documento guardado no ficheiro doc1.  Caso não seja possível a leitura deste documento o editor arranca com um documento vazio (ou melhor, com um só parágrafo vazio).

Experimente o comando acima, mas escrevendo editor-oficial em vez de ./editor, de modo a usar a resolução oficial.  Para sair do editor pressione 'f10'.

A segunda forma invocação é:

./editor

Neste caso o editor começa com um documento vazio que não está associado a nenhum ficheiro.

Experimente o comando acima (mas com editor-oficial, não se esqueça!).

2.3  Visualização

O documento deverá ser visualizado parágrafo por parágrafo.  A apresentação de cada parágrafo começa na primeira coluna do ecrã, sendo se necessário justificado à esquerda ao longo de várias linhas.  A última coluna do ecrã é reservada para colocar o cursor em situações especiais.  Os parágrafos deverão ser reformatados no ecrã sempre que a dimensão do ecrã mudar (deve-se sempre considerar que a largura mínima do ecrã é de duas colunas, mesmo quando tal não suceder na realidade).  Os parágrafos são divididos pelas várias linhas apenas em glifos quebráveis.  Os glifos quebráveis são o espaço (' ') e o hífen ('-').  Os espaços desaparecem quando quebrados, enquanto os hífens são reproduzidos em ambas as linhas.

Experimente com editor-oficial introduzir algum texto (incluindo hífens) e redimensionar o ecrã de modo a verificar o tipo de ajustamentos necessários.

Cada linha é desenhada tão próximo quanto possível da linha acima, mas considerando o facto de poderem haver nas linhas glifos que se prolongam para cima e para baixo da linha base.

Emoldure alguns glifos existentes ('ctrl-e'), e redimensione o ecrã de modo a verificar o tipo de ajustamentos realizados.  Pode emoldurar uma moldura, para aumentar a sua altura.

Quando não for possível quebrar uma linha de modo a que ocupe apenas a largura do ecrã disponível (menos a coluna final, que é reservada), o editor deve quebrar a linha no primeiro local disponível e desenhá-la mesmo fora dos limites permitidos e fora do ecrã físico.

Experimente reduzir a largura do ecrã até que haja zonas inquebráveis do texto e veja o que acontece.

Por omissão o documento começa a ser visualizado na primeira linha do ecrã.  Aliás, a primeira linha do primeiro parágrafo do documento nunca poderá ser mostrada abaixo da primeira linha do ecrã.  É possível visualizar documentos extensos usando os comandos 'ctrl-a' (página anterior) e 'ctrl-p' (próxima página), que fazem evoluir a zona visualizada do documento para cima ou para baixo, de acordo com a altura corrente do ecrã.

Experimente introduzir um texto extenso (pode partir ou abrir novos parágrafos usando 'enter') e usar os comandos 'ctrl-a' e 'ctrl-p' para recuar ou avançar a zona visualizada dos mesmos..

A zona visualizada dos documentos pode ou não conter o cursor (ver abaixo).  É claro que esta não é uma característica desejável do editor, mas simplifica a sua implementação.

2.4  Deslocamento do cursor

O cursor do editor não tem qualquer relação com o cursor do Slang++, que é normalmente colocado no canto inferior direito do ecrã de modo a não incomodar visualmente.

Verifique este facto no editor oficial.

O cursor do editor indica qual o glifo corrente do parágrafo corrente.  Na realidade o cursor do editor é uma abstracção.  Cada parágrafo mantém o seu próprio cursor, que indica o glifo corrente nesse parágrafo, e existe um cursor de parágrafo, que indica qual o parágrafo corrente do documento.  O cursor do editor corresponde, portanto, ao cruzamento do cursor de parágrafo com o cursor de glifo do parágrafo corrente.  O cursor do editor é representado visualmente dando uma cor diferente ao glifo em causa (que pode ter mais do que uma célula do ecrã).  

Crie um glifo emoldurado e desloque o cursor de modo a passar sobre ele.

O cursor deve poder ser deslocado usando as teclas com setas (cima/baixo e esquerda/direita).  Deslocar o cursor para cima e para baixo tem como efeito recuar ou avançar no parágrafo corrente do documento.  A posição do cursor em cada parágrafo é independente.  Este facto pouco conveniente destina-se a simplificar a implementação do programa.

O cursor pode ser deslocado para fora da área visível do ecrã!  Quando o cursor se encontra no glifo fictício final de um parágrafo deve ser mostrado como se de um espaço se tratasse.  Quando o cursor se encontrar sobre um espaço quebrado, esse espaço deve ser mostrado, mesmo que para isso a largura da linha exceda o limite "legal".

Experimente deslocar o cursor ao longo de uma documento com vários parágrafos.  Verifique todos os factos apontados.

2.5  Acções

Descrevem-se brevemente as acções que se podem realizar sobre o documento.  Acções são comandos que alteram o documento.  Os comandos que não alteram o documento são descritos mais abaixo.  Nem sempre se podem fazer determinadas acções.  Quando tal suceder ocorre um aviso sonoro e o documento permanece inalterado.

Vá sempre experimentando as acções descritas no editor oficial.

2.5.1  Deslocamento

Premir as setas altera o parágrafo corrente (setas para cima e para baixo) ou o glifo corrente do parágrafo corrente (setas para a esquerda e direita).

Erros:  

  1. Premir seta para cima quando o parágrafo corrente é o primeiro.
  2. Premir seta para cima quando o parágrafo corrente é o último.
  3. Premir seta para a esquerda quando o glifo corrente do parágrafo corrente é o primeiro.
  4. Premir seta para a direita quando o glifo corrente do parágrafo corrente é o fictício final (depois do último).

2.5.2  Caracteres gráficos

Premir um caractere gráfico tem como resultado inserir o respectivo glifo antes do glifo corrente do parágrafo corrente (o efeito visual é o de "empurrar" os glifos a partir do cursor para a direita).

Erros:  Nenhum.

2.5.3  Sorrisos

Premir 'ctrl-s' faz surgir um menu onde se pode escolher um sorriso a inserir como glifo antes do glifo corrente do parágrafo corrente.

Erros:  Nenhum.

2.5.4  Apagar

Premir 'del' remove o glifo corrente do parágrafo corrente.  O cursor do parágrafo corrente desloca-se para o glifo imediatamente à direita do glifo removido (que pode ser fictício).

Erros:  Tentar remover o glifo fictício final do parágrafo corrente.

2.5.5  Emoldurar

Premir 'ctrl-e' emoldura o glifo corrente do parágrafo corrente.

Erros:  Tentar emoldurar o glifo fictício final do parágrafo corrente.

2.5.6  Desemoldurar

Premir 'ctrl-d' retira a moldura ao glifo corrente do parágrafo corrente.

Erros:  Tentar desemoldurar o glifo fictício final do parágrafo corrente ou um glifo que não está emoldurado.

2.5.7  Partir parágrafo

Premir 'enter' parte em dois o parágrafo corrente.  O primeiro parágrafo fica com todos os glifos até ao glifo corrente (exclusive) e o  segundo fica com todos os glifos a partir daí.  Dependendo da posição do cursor no parágrafo corrente, qualquer dos parágrafos resultantes pode estar vazio.  O parágrafo corrente passa a ser o segundo parágrafo gerado.  O glifo corrente do primeiro parágrafo passa a ser o glifo fictício final.  O glifo corrente do segundo parágrafo passa a ser o primeiro glifo (se existir, senão será o glifo fictício final).

Erros:  Nenhum.

2.5.8  Juntar parágrafos

Premir 'ctrl-j' junta o parágrafo corrente com o que se lhe segue.  Os glifos de um e outro são simplesmente concatenados, não se acrescentando qualquer espaço entre eles.  O glifo corrente mantém-se o do primeiro parágrafo, excepto se fosse o glifo fictício final, caso em que passa a ser o primeiro glifo junto do segundo parágrafo (entretanto desaparecido).

Erros:  Esta acção não faz sentido se o parágrafo corrente for o último do documento.

2.6  Outros comandos

Incluem-se aqui descrições de comandos que alteram o documento ou que, fazendo-o, não são em si passíveis de serem desfazíveis ou refazíveis.  É o caso justamente dos comandos de desfazer e refazer.

2.6.1  Ecrã anterior

Premir 'ctrl-a' mostra o ecrã anterior do documento.

Erros:  Este comando não faz sentido se a primeira linha do primeiro parágrafo do documento já estiver visível no ecrã.  Em nenhum caso essa linha poderá ser mostrada abaixo da primeira linha do ecrã.

2.6.2  Próximo ecrã

Premir 'ctrl-p' mostra o próximo ecrã do documento.

Erros:  Este comando não faz sentido se a última linha do último parágrafo do documento já estiver visível na primeira linha do ecrã.  Em nenhum caso essa linha poderá ser mostrada acima da primeira linha do ecrã.

2.6.3  Desfazer

Premir 'ctrl-z' desfaz a última acção feita sobre o documento.  Embora desfazer seja considerado um comando e não um acção, afecta o documento.  É possível desfazer todas as acções realizadas até ao documento original com apenas um parágrafo vazio.

2.6.4  Refazer

Premir 'ctrl-x' refaz a última acção desfeita sobre o documento.  Embora refazer seja considerado um comando e não um acção, afecta o documento.  É possível refazer todas as acções desfeitas anteriormente.  Tem por isso o efeito contrário ao do comando de desfazer.  Realizar uma acção após uma sequência de comandos de desfazer elimina a possibilidade de os refazer.

2.6.5  Refrescar

Premir 'ctrl-l' refresca o ecrã.  I.e., força-o a ser redesenhado.  Existe prevendo erros do programa na visualização do documento ou interferência de programas externos no estado do ecrã.

2.6.6  Sair

Premir 'f10' sai da edição do documento.  O editor começa por perguntar se o utilizador pretende guardar o documento (esta pergunta é feita mesmo que o documento não tenha sofrido alterações).  Se a resposta for negativa, o programa termina.  Se a resposta for positiva:

  1. Se o documento tiver sido carregado originalmente a partir de ficheiro, então o documento será guardado nesse mesmo ficheiro.
  2. Se o documento tiver sido criado de novo, o editor pede o nome do ficheiro onde será guardado ao utilizador.

Se houver problemas na criação do ficheiro em qualquer destes casos, o programa permite ao utilizador introduzir nomes alternativos.  

Se existirem erros durante a gravação do ficheiro, apesar de ter sido possível estabelecer um canal para ele, o programa avisa desse facto e termina sem guardar (pouco conveniente, mas simplifica a implementação).

2.7  Teclas que não correspondam a comandos ou acções

Devem produzir um aviso sonoro e não ter efeito.

2.8  Novas funcionalidades

O aluno é livre de introduzir novas funcionalidades no editor, desde que não altere as já existentes.  Pode, nomeadamente, melhorar os aspectos apresentados como simplificações neste documento.  Tal não terá consequências na nota final.

2.9  Dúvidas

Dúvidas acerca da interface do editor devem ser esclarecidas recorrendo ao editor oficial, à secção de Esclarecimentos e Recomendações, à lista de correio electrónico da disciplina (poo-iscte@yahoogroups.com), ou aos docentes da disciplina.

3  Requisitos de implementação

  1. Devem ser usadas hierarquias de classes sempre que tal se justifique.  Pelo menos as seguintes:
    1. Deve ser usada uma hierarquia de classes para representar os vários tipos de glifos.
    2. Deve ser usada uma hierarquia de classes para representar os vários tipos de acções.
  2. Essas hierarquias devem fazer uso de polimorfismo.
  3. Deve-se evitar ao máximo todas as situações em que seja necessário saber o tipo concreto de um glifo ou acção dado um ponteiro para uma classe base.
  4. O editor deverá ser resistente a todo o tipo de erros (comandos inexistentes, ficheiros de entrada inválidos ou inexistentes, etc.).  Em circunstância alguma deve o editor simplesmente abortar!
  5. Todas as rotinas, operações e métodos devem ser claramente documentados, nomeadamente quando a pré-condição e condição objectivo.  Os invariantes das classes devem ser identificados.  Devem-se usar asserções para verificar estas condições no código.
  6. Erros em recursos externos (e.g., ficheiros) devem ser assinalados através do lançamento de excepções.  Todas as excepções lançadas devem ser capturadas.
  7. Todas as classes que representem conceitos que se pretende preservar entre edições sucessivas devem possuir o trio de métodos constituído por construtor de canal de entrada, carregaDe() (que carrega nova informação a partir de um canal de entrada) e guardaEm() (que guarda num canal de saída toda a informação).
  8. A hierarquia de classes representando glifos deve estar construída de tal maneira que seja fácil acrescentar novos tipos concretos de glifos.  Nomeadamente estes novos tipos devem poder ser emolduráveis sem o preverem à partida.
  9. Todas as ferramentas do programa relativas a documentos (glifos, parágrafos, documentos, etc.) devem ser colocadas num pacote à parte, representado por um espaço nominativo próprio.
  10. O formato exacto dos ficheiros de texto onde os documentos são guardados é arbitrário.

4  Condições de entrega e avaliação

4.1  Opções de implementação e influência na nota

A resolução integral deste enunciado está cotada para 24 valores, embora a nota final não possa exceder os 20 valores, como é natural.  É possível eliminar parte dos requisitos funcionais com as seguinte penalizações: 

  1. Divisão de parágrafos em linhas:
    1. Sem qualquer divisão: -3 valores.
    2. Divisão em qualquer lugar: -2 valores.
    3. Divisão sem duplicação de hífens: -1 valor.
  2. Glifos:
    1. Apenas com glifos do tipo caractere gráfico: -3 valores.
    2. Sem molduras: -2 valores.
    3. Sem sorrisos: -1 valor.
  3. Comandos e acções:
    1. Sem 'ctrl-a' e 'ctrl-p': -2 valores.
    2. Sem 'ctrl-e' e 'ctrl-d': ver nos glifos.
    3. Sem 'ctrl-s': ver nos glifos
    4. Sem 'ctrl-j': -1 valor.

4.2  Condições de entrega

  1. Este trabalho será resolvido em grupo (ver página da disciplina).  
  2. A resolução deste trabalho deve ser entregue até às 18:30h de segunda-feira, 11 de Junho de 2001.  
  3. A resolução só se considera entregue depois de:
    1. ser entregue pessoalmente a um dos docentes da disciplina uma disquete, devidamente identificada, contendo apenas os ficheiros fonte onde se encontra o código C++ (extensões .C, ._impl.H e .H) e o respectivo ficheiro de construção (Makefile);
    2. ser entregue pessoalmente a um dos docentes da disciplina um relatório em papel, usando o modelo publicado para os problemas (StarOffice, Word, ou RTF), contendo obrigatoriamente a listagem de todos os ficheiros colocados na disquete e contendo adicionalmente um pequeno texto de no máximo uma página A4 explicando as opções tomadas para a resolução; e
    3. serem enviados via correio electrónico para Luis.Nunes@iscte.pt todos os ficheiros colocados na disquete.
  4. O relatório em texto tem de obrigatoriamente indicar quais dos requisitos funcionais não foram implementados, de acordo com a Secção 4.1.
  5. Imprima o código em C++ sempre de uma forma bem legível: use sempre um tipo não proporcional (e.g., Courier) e, caso seja possível fazê-lo, imprima em papel reciclado, frente-e-verso.
  6. A entrega do problema fora do prazo implica a penalização de um valor por cada dia útil de atraso (i.e., 12 de Junho -1 valor, 13 de Junho -2 valores, 14 de Junho -3 valores, 15 de Junho -4 valores e 18 de Junho -5 valores) não se aceitando trabalhos após segunda-feira, 18 de Junho de 2001, até às 18:00h.
  7. A não observação dos modos de entrega descritos acima poderá ser penalizada.
  8. Exija ao docente a entrega de um recibo comprovativo da entrega do trabalho.  Este recibo é a única prova que tem da entrega do seu trabalho.

Os horários das orais e a forma de inscrição serão anunciados logo que possível.

4.3  Avaliação

A avaliação do trabalho terá em conta não só a correcta execução do programa, mas também, e principalmente, a correcta estruturação das classes, operações, métodos, e rotinas que compõem o programa e ainda a sua legibilidade.  Valorizar-se-á mais a simplicidade que a eficiência.  Valorizar-se-á também código C++ correctamente comentado (mas não excessivamente, não vale a pena comentar o óbvio).  Comentários e documentação são conceitos diferentes.  O código tem de ser totalmente documentado, mas deverá ser comentado apenas onde for indispensável.

A nota deste trabalho será dada apenas após uma discussão, individual, com cada um dos elementos do grupo. Nesta discussão qualquer elemento do grupo terá de demonstrar um total conhecimento do programa e ser capaz de operar as alterações que forem pedidas.  Nessa oral serão também feitas perguntas sobre a matéria em geral.  A nota final dependerá não só da qualidade do trabalho, mas também, e principalmente, do conhecimento do programa e da matéria em geral e da capacidade de resolver problemas em C++ demonstrados nessa discussão.


Anexos

A  Depuração com o Slang++

A depuração de programas usando o Slang++ apresenta algumas complicações.  Acontece que estas aplicações não podem ser executadas dentro do depurador, pois fazem uso de capacidades do terminal que este não disponibiliza.  Assim, é necessário executar o programa a depurar numa janela xterm normal, fora do gdb, e depois convencer o gdb a "agarrar-se" ao programa já em execução.  Para isso:
  1. Coloque logo no início do programa a depurar uma instrução de leitura do teclado, de modo a que este não avance antes de o desejar.
  2. Lance o gdb dentro do Emacs, como habitualmente, especificando o ficheiro a depurar.  Não dê o comando r[un]!
  3. Execute o programa dentro de uma janela xterm.
  4. Cada instância de um programa em execução é um processo.  Cada processo, em Linux, tem um número de processo (PID, de Process IDentification).  É necessário identificar o número do processo em execução.  Para isso dê (numa consola) o comando:
    1. ps -auxww | grep nome_do_programa | grep annnnn
    onde nnnnn é o seu número de aluno e nome_do_programa é o nome do programa a depurar.  Surgir-lhe-á uma lista dos processos que são instâncias do seu programa e que lhe pertencem.  É conveniente que tenha apenas um programa em execução de modo a identificar claramente qual o PID que lhe interessa.
  5. No gdb coloque um ponto de paragem onde for conveniente.  Em seguida dê o comando:
    1. attach pid
    onde pid é o PID do processo identificado na alínea anterior.  Este comando faz com que o gdb passe a controlar o processo.  Dê o comando:
      c[ontinue]
  6. A partir deste momento a depuração prossegue como habitualmente.