Tutorial tudo sobre gb.ncurses

Em revisão

Projetos gb.ncurses passo a passo

Então, você quer escrever um projeto usando gb.ncurses, mas não consegue encontrar qualquer documentação (eu sinto muito por isso!). Este tutorial pode, talvez, ajudar você a começar. (Vamos supor que você sabe como programar em Gambas e que você sabe o que ncurses realmente é.)

Este tutorial é dividido em três seções principais:
  • Um programa "Olá, mundo".

  • Janelas e caracteres de decoração.

  • Criação de outras janelas e interagindo com o usuário.

Todo o código fonte aqui apresentado que incorporam a este projeto: ncurses-basic-3.5.3.tar.gz.

Desde 3.5.3

Como o nome do arquivo sugere, ele vai trabalhar com Gambas 3.5.3 não liberado no momento em que escrevo este. Então, nesse meio tempo, faça tudo igual ou superior à revisão 6135.

O arquivo éstá hospedado em http://www.gambas-buch.de. Isto não é truque;-)

Preliminares

Primeiro, é claro, você precisa criar um novo projeto (console!) E marque o componente gb.ncurses em Project -> Properties... -> na tab Components. Agora, observe que a programação ncurses é sobre terminais e enquanto o console Gambas no IDE é apenas uma simulação, não é um emulador de terminal verdadeiro. Em vez disso, para testar o nosso programa, devemos usar um emulador de terminal verdadeiro, ou seja, um programa externo como Konsole do KDE ou Xterm. Então, vá para Project -> Properties..., selecione a tab "Options" e ative o uso de um emulador de terminal.

Você pode mudar o emulador de terminal usado, em: Tools -> Preferences -> Help & Applications. No decorrer deste tutorial, nós vamos ficar com Xterm.

Desde 3.5.3

Desde a revisão #6146, você pode optar (nas propriedades de diálogo do projeto) para redirecionar o stderr do seu projeto para fora do emulador de terminal e de volta para o IDE. Isto é realmente útil na depuração de programas gb.ncurses como em declarações DEBUG e ERROR em seu código, não atrapalhando mais a sua tela, e simplesmente pode ser lido em uma janela separada.

"Olá Mundo!" (claro)

Por ser tão clichê, nós vamos fazer isso rapidamente:

Window.Print(("Olá Mundo!"))

E isso é basicamente tudo.

Nos bastidores, quando gb.ncurses é carregado, ele inicializa o terminal para um mesmo estado (como quase todos os programas ncurses faz) para que tudo esteja pronto quando o nossa rotina Main() for executada . A classe Window em gb.ncurses é auto-criável o que significa que você pode usar o nome da classe para se referir a uma única instância da Janela (assim como você usa FMain como a única instância do seu Form principal da aplicação GUI .).

Esta única instância é uma Janela que representa toda a tela, ela encapsula em Gambas as chamadas ncurses stdsrc.

Então, Window.Print("Olá Mundo!"), Estamos imprimindo" Olá mundo! "na posição atual do cursor, (0,0) após a instanciação, na tela padrão.

No modo ncurses, você pode pensar na tela, como como um array de caracteres bidimensional de tamanho fixo: se você imprimir algo a ela, você vai substituir o que antes estava lá. Não há nenhuma mudança automática de letras como em, por exemplo, uma TextArea da GUI ..

Da mesma forma, no modo ncurses, se você imprimir algum texto longo que chega ao fim da linha atual, o texto não será quebrado automaticamente para a próxima linha. No entanto, se Window.Wrap = True (que é o padrão), a classe Window cuida disso nos bastidores. Se você quiser desabilitar a quebra automática nas extremidades da linha, defina essa propriedade para False.

"Curses" ("otimização do cursor") mantém estruturas de dados internas para otimizar qualquer saída enviada para o terminal: enviando apenas os caracteres que realmente mudaram. Então, se você usar acidentalmente um Print ou a declaração Debug em seu código Gambas, este não só irá atrapalhar sua tela, mas ficará onde foi impresso e até mesmo sobrevive a uma chamada Window.Clear() porque ncurses acha que não há nada lá (porque não imprimiu nada lá).

Janela e caracteres coringas

Você provavelmente usa ncurses para Imprimir em um projeto de console, porque torna a saída muito mais fácil. Esta sessão lida com Window decoração e atributos de caracteres - que inclui cor.

Window.Border e Window.Caption

Primeiro observar algo no programa acima: o seu texto não foi, na verdade, impresso na posição (0,0). Isto é porque a Window Singleton tem uma grande borda em torno do carácter. Você não pode desabilitar esse recurso no momento, já que as definições dos contornos são definidos a partir de instanciação objeto.

Você pode, no entanto, alterar a forma como a borda da janela parece:

Window.Border = Border.ACS ' ou: Border.Ascii ou: Border.None

Você já viu Border.None (o padrão). Border.ACS usa caracteres de um conjunto de caracteres especiais do seu emulador de terminal para o desenho da linha. Parece bom nos terminais que não são quebrados, a este respeito. Há também Border.Ascii que utiliza o caractere ASCII pontos (...) e funciona em todas as circunstâncias.

Falando sobre a borda, você também pode definir o título da janela se ela tiver uma borda. O título (ou legenda) é impresso na borda superior da janela:

Window.Caption = ("Chapter 2: Janela de caracteres curingas")

Window.Attributes e Window.Pair

As propriedades Window.Attributes e Window.Pair contém configurações padrão para todos os caracteres impressos após estas propriedades serem definidas. Eles são combinados com cada caractere, como está escrito. Em ncurses, os caracteres na tela podem ter múltiplos atributos e um par (foreground, background) de cores associada a ele. Para manipulação do carácter-wise desses atributos, veja abaixo.

Em primeiro lugar, é preciso esclarecer um pouco o termo atributo, porque em termos ncurses, as cores são também caractere de atributo. Então, vamos usar a palavra "atributos", como um super conjunto de atributos verdadeiro ou falso ordinárias e atributos de cor. Estes verdadeiro ou falso atributos (flags) estão contidos como constantes na classe Attr:

Constante Significado
Attr.Normal Atributos Sem Flag, apenas de exibição normal.
Attr.Underline Sublinha os caracteres. Isto não é suportado por todos os terminais. Por exemplo, o meu console Linux apenas enfatiza o caractere com a cor ciano, em vez de sublinhá-lo.
Attr.Reverse Vídeo inverso. Esta flag especifica para trocar a cor das letras e do fundo do caractere.
Attr.Blink Tornar o caractere intermitente. Isso não funciona no meu console Linux, ou seja, não faz nada. Você pode tentar Xterm para ver.
Attr.Bold Tornar carácter Negrito. Também não tem suporte em todos os lugares. No meu console Linux, isso faz com que a cor de primeiro plano do caractere fique mais brilhante.

Window.Attributes é uma atributo máscara de bit, verdadeiro ou falso. Se você sabe que um bit não está definido, você pode defini-lo (de forma semelhante, você pode subtrair, se você souber o que está definindo). Se você não tem certeza, é sempre mais seguro o uso dos operadores bit a bit Or e And:

Window.Attributs += Attr.Blink ' Eu, pessoalmente, prefiro não utilizar um programa usando Blink;-)

A propriedade Window.Pair codifica as cores das letras e do fundo de um caractere em um único inteiro, o "número par". Para obter um número par, você tem que usar a classe Pair (como o mapeamento de pares de cores para emparelhar números é feito internamente no gb.ncurses e você não deve se preocupar com isso) . Você dá a cor de primeiro plano como um primeiro argumento e a cor de fundo como um segundo argumento para seus assessores matriz. Constantes de cor estão na classe Color.

Window.Pair = Pair[Color.Cyan, Color.White]

Agora vamos ver como um texto impresso aparece:

Window.Print(("Renderização padrão\r\n"))

Isso agora deve imprimir o texto dado, mas, ele também deve piscar. Têm foreground ciano e background (fundo) branco.

No modo ncurses você precisa usar \r \n para ir para a coluna 0 na linha seguinte. O de sempre \n caractere único, e vai saltar para a próxima linha, mas, permanece na coluna atual, enquanto \r retorna a coluna 0 na linha atual.

No entanto, o método Window.PrintCenter() (que é como Window.Print() mas centraliza o texto verticalmente e horizontalmente) só pode funcionar como esperado se você usar \n, como ele recua automaticamente as linhas a ser impressa. O \r é interpretado posteriormente pelo emulador de terminal e arruína o recuo!

O método Window.Clear() limpa a tela, ou seja, ele grava espaços em branco para todos os caracteres na janela. Portanto, Window.Clear() colore toda a sua janela com Window.Pair além de apagar todo o texto nela.

Window.Print em detalhes

Agora, se você trabalha com muitas cores diferentes (imagine ls --color=always), sempre a definir Window.Pair entre as impressões pode ficar complicado. Por esta razão, o método window.print() permite associar atributos personalizados para a sequência impressa. As instruções cheias de window.print() é:

Sub Window.Print(Text As String, Optional X As Integer, Optional Y As Integer, Option Attr As Integer, Optional Pair As Integer)

Uma declaração é o bastante. Você já sabe, o argumento Texto, X e Y permitem que você imprima o texto para a posição especificada, sem alterar a posição do cursor da janela. Atr e Pair permitem que você defina flags e cores personalizadas:

Window.Print(("Alguns atributos personalizados"),,,, Pair[Color.Red, Color.Blue])

Observe que a omissão de X, Y e Attr impõe o uso dos valores padrão, que são: a posição atual do cursor e os flags padrão das janelas, como definido por Window.Attributes. Assim, a linha acima imprime o texto com primeiro plano vermelho e fundo azul, mas, a piscar! (Não é muito bonito, é claro.)

Window.Foreground (Window.Pen) e Window.Background (Window.Paper)

Em contraste com Window.Pair, essas duas propriedades imediatamente aplicam os valores a todos os caracteres na janela, e assim apagará todas as personalizações de Foreground ou background anteriormente atribuído a um caractere. Após as linhas acima, podemos decidir que, após exatamente um segundo, precisamos de um fundo amarelo (para espantar todos os usuários ainda sentado na frente do nosso programa):

Wait 1
Window.Background = Color.Yellow

Não existe nada mais fácil para aplicar alguns flag a todos os caracteres imediatamente.

Atributos e cores por caracteres

As said earlier, every character on screen has its attributes right to its side and the Window class allows us to change every single bit of them. The Window class implements the array accessors by which you can access a virtual object refering to the attributes of the very character at coordinates (X,Y) in the window: Window[Y, X]. This virtual class allows you to set the attribute flags and the fore- and background colour of that character immediately.

Since all the spectators left our program in the last section, we might as well do whatever we want now. How about this loop:

Como disse anteriormente, todos os caracteres na tela tem seus atributos, e a classe Window nos permite mudar cada bit destes. A classe Window implementa os assessores da matriz, através da qual você pode acessar um objeto virtual referindo-se aos atributos do próprio caráter nas coordenadas (X, Y) na janela: Window [Y, X]. Esta classe virtual permite que você defina os flags de atributos e a cor das letras e do fundo desse caractere imediatamente.

Uma vez que todos os usuários deixaram nosso programa na última seção, que poderia muito bem fazer o que queremos agora. Que tal este loop:

iRow = Window.Y
iDelta = 1
For iCol = Window.X To Window.W - 1
  Window[iRow, iCol].Background = iCol Mod Color.Count
  iRow += iDelta
  If iRow Mod Window.H = 0 Then
    iRow -= 2 * iDelta
    iDelta = - iDelta
  Endif
  Wait 0.05
Next

Experimente-o ;-)

Outras janelas e interatividades

A última grande seção deste tutorial está aqui para que você saiba duas coisas:

(1) A janela singleton não é a única janela que você pode ter. Na verdade, uma Window é apenas alguma região de memória independente da tela do terminal. Windows pode até ser empilhados e se sobrepor umas as outras.

(2) Como I/O trabalha com a janela e a classe Screen de gb.ncurses. Ambos os temas são abordados por um aplicativo: Escrever um diálogo.

Podemos começar logo com o código e explicar mais tarde:

Dim hDialog As New Window(True, 0, 0, Window.W / 2, 6)
Dim sChoice As String

hDialog.Border = Border.ACS
hDialog.Caption = ("Faça uma escolha")
hDialog.Center()
hDialog.Show()

hDialog.PrintCenter(Subst$(("O que fazer?\n\nCalcule 42^2 [&1]\nEncerrar [&2]"), ("E"), ("q")))
Screen.Echo = False
sChoice = hDialog.Ask(("Eq"))
hDialog = Null ' Destrói a janela de diálogo
If sChoice = ("e") Then
  Window.PrintCenter(Subst$(("Aqui está: 42^2 = &1"), 42 ^ 2), Attr.Bold, Pair[Color.Green, Color.Black])
Endif
Window.Print(("Pressione qualquer tecla para continuar"), 0, Window.H - 1)
Window.Read()

Você vê: Window é uma classe criável. Você especifica se a nova janela vai ter um borda frame (como explicado acima), as coordenadas do canto superior esquerdo da janela e suas dimensões. Em seguida, vamos definir as novas bordas da janela de diálogo, o título e centralizá-la na tela do terminal. Em seguida, ela é mostrada usando o método Show(). Isso faz com que a janela torne-se visível e elevada à frente, portanto, passa a abranger o objeto Window singleton. (Você tem os mesmos métodos da classe Window do componente GUI .)

O método acima mencionado PrintCenter(), obviamente apresenta ao usuário as suas opções. Como se trata de convenções, letras individuais ASCII representam opções. Uma letra maiúscula significa a opção padrão (escolhida quando o usuário pressiona Return). Note que é sempre bom manter suas strings traduzível. (Há também um documento em torno do qual explica como fazer isso [0].)

A linha Screen.Echo = False é interessante. Ele define a opção terminal global, ou seja, que os caracteres digitados não são mais ecoado no terminal já que isso arruina a nossa exibição.

Em seguida, a Window.Ask() é chamado com as letras das opções que apresentamos o usuário anterior. Window.Ask() espera até que o usuário pressione um destes caracteres ou pressione Return. Neste último caso, a sequência de opções é digitalizado para uma letra maiúscula que é então devolvida como a opção padrão. Observe que é sempre devolvida a versão em minúsculas da letra, ou seja, o método Window.Ask() é totalmente case-insensitive.

Em seguida, temos de eliminar a janela de diálogo atribuindo Null a sua última referência porque é apenas na forma como agora. Se o usuário pressiona "e" ou "E", podemos satisfazer seu desejo, o quadro de 42.

As duas últimas linhas são interativas. Para permitir que o usuário veja o resultado, o programa deve ser mantido em funcionamento. Assim, o usuário é solicitado a digitar algo e até que o faça, o programa está suspenso. Precisamente, Window.Read() espera até que pelo menos um caractere de leitura retorne.

Por padrão, Screen.Input é o modo de entrada, e definir um Input.CBreak é a entrada em tempo real, line-wise é como você está acostumado! Consulte a página do Manual Ncurses para os outros modos de entrada.

O método Window.SetFocus() e event-driven I/O

Não existe outro meio para receber a entrada (eu espero que você não tenha perdido alguns aspectos orientados a eventos nos parágrafos acima!). Primeiro, note que o nosso programa tem apenas um fluxo de entrada, como os programas de terminal sempre fazem, stdin. No entanto, usando um evento semelhante ao Application_Read não parece ser satisfatório. Podemos ter várias janelas e nosso programa pode ter vários estados como dados de entrada ou entrada de comandos para diferentes janelas (ambos seriam bom com a edição de linha, ou seja, nós gostaríamos de usar a tecla Backspace para nos corrigir - o que não é para ser dado como certo em programas ncurses). Ou queremos interceptar teclas digitadas como as que fazem parte de atalhos, ou queremos exibir temporariamente uma caixa de diálogo que deve responder ao usuário, etc .. Nós iriamos acabar com um enorme manipulador de eventos Application_Read().

Não. Em vez disso, gb.ncurses fornece uma maneira multiplexada para o descritor de arquivo de entrada única, disponível para nós. Este é o método Window.SetFocus(). Você pode chamar a atenção para uma única janela com ele:

Private $hText As Window
Private $hCommand As Window
...

  $hText = New Window(False, 0, 0, Screen.W, Screen.H - 1) As "Text"
  $hCommand = New Window(False, 0, Screen.H - 1, Screen.W, 1) As "Command"
...

  $hText.Raise()
  $hText.SetFocus()

Há duas janelas: uma para o texto e uma para entrada de comando (como no editor vi). Note que não vai usar o objeto singleton Window nesta seção! Com $ hText.Raise(), trouxemos a janela de texto para o primeiro plano, de modo que o cursor físico do terminal está no lugar certo. Também veja que você tem os nomes de eventos da janela. $hText.SetFocus() não faz nada mais do que registrar $hText como a janela ativa. Assim como a entrada, agora será todas as janelas que geram o evento Read. Vamos observar o manipulador de eventos:

Public Sub Text_Read()
  Dim iKey As Integer = Window.Read()

  If iKey = Key.Esc Then
    $hCommand.Raise()
    $hCommand.SetFocus()
  Else If iKey < 128 Then
    $hText.Print(Chr$(iKey))
  Endif
End

Como você pode ver agora: estamos lendo o caractere digitado. Observe se ele for o caractere Escape, este é o sinal para mudar para o modo de comando, usando novamente Raise() e SetFocus(). Se não, o caractere ASCII é impresso. Não há edição de linha ou qualquer coisa aqui. Isso é deixado como um exercício para o leitor. (Dica: a tecla "Backspace" merece o seu nome.)

Depois que nós mudamos o modo de comando, cada caractere digitado irá acionar o manipulador de eventos Command_Read:

Public Sub Command_Read()
  Dim iKey As Integer = Window.Read()
  Dim sCommand As String

  If iKey = Key.Esc Then
    $hText.Raise()
    $hText.SetFocus()
  Else If iKey < 128 Then
    If iKey = Key.Return Then ' Fim de comando
      sCommand = RTrim$($hCommand.Get(0, 0))
      $hCommand.Clear()
      If sCommand = ":q" Then Quit
    Else
      $hCommand.Print(Chr$(iKey))
    Endif
  Endif
End

Basicamente é o mesmo aqui: Escape retorna para o modo texto e os caracteres ASCII são ecoados. Exceto que a chave Return também é interceptada. $HCommand.Get(0, 0) copia o conteúdo inteiro para a primeira linha na janela de comando (como você pode ver acima, a janela de comando tem apenas uma linha). Se o comando é ":q" (ignorando espaços em branco, finais), o programa termina.

Se existir preocupação com os espaços em branco, finais, teremos que usar o terceiro argumento para o método Get(), que é o tamanho da string para extrair:

sCommand = $hCommand.Get(0, 0, $hCommand.CursorX)

Como não há edição de linha (as teclas de seta não funcionam para mudar a posição do cursor, por exemplo), este é, obviamente, tudo o que o usuário digitou na linha de comando.

HWindow.Read () e hWindow.Ask () irá anexar diretamente para o descritor de arquivo de entrada e "roubar" qualquer entrada para cumprir a sua missão. Você não verá eventos Read então.

Últimas palavras

Você teve praticamente uma turnê em torno de todas as classes do componente gb.ncurses. Não é um manual completo, mas você tem uma boa visão geral do que é (ou pode ser) possível. Agora que você está apto o suficiente, você também pode olhar os exemplos gb.ncurses Gambas: Invaders e Pong na seção Jogos.


[0] /wiki/wiki/howto/translate