quinta-feira, 21 de agosto de 2014

TCL/TK - Introdução

A linguagem Tcl/Tk foi desenvolvida pelo Dr. John K. Ousterhout e sua equipe da Universidade da California, tendo sido mantida pela Sun Microsystems e posteriormente pela Scriptics e atualmente pela Ajuba Solutions.

A linguagem Tcl é essencialmente uma linguagem de script, podendo ser compilada para criar um executável em formato binário. Tk é o seu Tool Kit, sua biblioteca gráfica.

O uso mais comum para o Tcl é na criação de programa para os sistemas UNIX, Windows e Mac. Entretanto vem crescendo o número de empresas que utilizam a linguagem Tcl em seus servidores de Web, entre elas a America On-Line( AOL ).

Tcl é uma linguagem poderosa, aliando um eficiente compilador JIT, a uma sintaxe extremamente simples. O compilador Tcl é tão eficiente, que há pouca diferença em velocidade, entre um programa Tcl interpretado e sua versão compilada. 

Variáveis

Em Tcl só existe um tipo de dado: strings, ou cadeias de caracteres. Contudo, alguns comandos, especialmente os que realizam cálculos, interpretam seus arqumentos como valores numéricos ou booleanos. Nesse caso os dados estarão nos seguintes formatos:

TipoExemplo
Inteiro123( decimal ), 0xFF( hexadecimal), 0377( octal ) 
Ponto flutuante1.2, 2., 3e5, 1.23e+5 
Booleano( lógico )true, false, 0, 1, yes, no 


Uma variável pode ter qualquer nome. Para atribuir um valor a uma variável utiliza-se o comando set: set x 0 O comando anterior atribui o valor 0( zero ) à variável x. Para ler o valor de uma variável basta colocar o caractere $, antes do nome da variável: puts $x Exibe o valor da variável x no console. Você não precisa declarar variáveis, elas são criadas à medida em que são referenciadas. 1

quinta-feira, 24 de julho de 2014

Criar um menu de Recent Files no Qt.

Olá pessoal,



Ando ocupado no emprego, mas como desenvolvi alguns programas para facilitar as coisas no trabalho, estou postando dicas que usei nesses programas. Segue mais um abaixo ;-)


A biblioteca do Qt anda muito bem, gosto muito desse framework. Estava lendo algumas notícias relacionadas a desenvolvimento e me deparei com um componente chamado QSettings, na hora me veio a ideia de utiliza-lo, pesquisei na net e descobri algumas pessoas já fazendo uso. Segue abaixo meu código:

Primeiro, o QSettings usa arquivos .ini para guardar informações do formulário e recuperá-los. Por tanto criei uma lista dos ultimos arquivos abertos:

QAction *separatorAct;
enum { MaxRecentFiles = 10 };

QAction *recentFileActs[MaxRecentFiles];


Segundo, criei duas funções para guardar e recuperar essas info do arquivo .ini:

Recuperar:
void MainWindow::updateRecentFileActions()
{
    QSettings settings("demosetting.ini", QSettings::IniFormat);
    QStringList files = settings.value("recentFileList").toStringList();
    //qDebug() << "carregando de settings: " << files;
    int numRecentFiles = qMin(files.size(), (int)MaxRecentFiles);

    for (int i = 0; i < numRecentFiles; i++) {
        QString text = QString("&%1 %2").arg(i).arg(files[i]);
        recentFileActs[i]->setText(text);
        recentFileActs[i]->setData(files[i]);
        recentFileActs[i]->setVisible(true);
    }

}

Guardar:
void MainWindow::setCurrentFile(const QString &fileName)
{
    //curFile = fileName;
    setWindowFilePath(fileName);

    QSettings settings("demosetting.ini", QSettings::IniFormat);
    QStringList files = settings.value("recentFileList").toStringList();
    files.removeAll(fileName);
    files.prepend(fileName);
    while (files.size() > MaxRecentFiles)
        files.removeLast();

    settings.setValue("recentFileList", files);
    //qDebug() << "salvando em " << settings.fileName() << ": " << files;

}

E o código para adicionar o menu "Recent files":

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // recent files
    QMenu *r = this->menuBar()->addMenu("Recent files");

    for (int i = 0; i < MaxRecentFiles; i++) {
        recentFileActs[i] = new QAction(this);
        recentFileActs[i]->setVisible(false);
        connect(recentFileActs[i], SIGNAL(triggered()), this, SLOT(openRecentFile()));
    }
    for (int i = 0; i < MaxRecentFiles; ++i)
        r->addAction(recentFileActs[i]);
    updateRecentFileActions();
    separatorAct = r->addSeparator();

}

void MainWindow::openRecentFile()
{
    QAction *action = qobject_cast<QAction *>(sender());
    if (action)
        loadFile(action->data().toString());

}

Implementem a função loadFile() ou substitua a chamada pela sua função e lembrando que estou usando o Qt versão 5.2 (no Qt Creator 3.1.1).
Espero ter ajudado novamente.

Até a próxima

Abç, Emanuel

terça-feira, 22 de julho de 2014

Abrindo e usando banco de dados SQLite com o Qt.

Boa tarde pessoal,


Dúvidas chegaram ao meu email, então estou postando como utilizar banco de dados SQLite com o Qt.

Primeiramente o include que deve ser feito:

// lib for read sqlite database
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlError>
#include <QtSql/QSqlQuery>

#include <QtSql/QSqlQueryModel>

Segundo o código para abrir o banco de dados:

// open database
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("database.db");
db.open();
if (dbDoni.lastError().isValid())
{
    QMessageBox::critical(0, "error:", db.lastError().text());
    return;
}

Terceiro, executando algumas consultas na base de dados:

QSqlQuery query;
query = db->exec("SELECT * FROM PLUTax");

Vocês podem verificar se resultado de um campo é vazio pelo índice
if (!query.isNull(2)) {
       // error

    }
ou pelo nome:
if (!query.isNull("campo")) {
        // error

    }

Pegar os valores

qDebug() << query.value(2).toString();


E no final fechar a conexão:

dbDoni.close();



Simples e fácil heim ;-)
Só lembrando... estou utilizando o Qt Creator 3.1.1 e Qt 5.2

Abraços

segunda-feira, 21 de julho de 2014

Deploy de programas em Qt.

Boa noite pessoal,



Estou escrevendo este post mais pelo intuito de revolta, da forma como a comunidade www.qt-project.org definiu o deploy dos programas... mas também para ajudar meu amigos programadores.
Para não ficarem sofrendo sem saber porque o programa não roda após liberarem o primeiro release.

Eu crirei um programa usando o Qt Creator 3.1.1 (versão Qt 5.2), algo bem básico, controlar alguma informações de preços, indíces e números de lojas em um arquivo xml, comparar essas informações com planilhas em excel e controlar um pequeno Banco de dados em SQLite.

Segue o link, caso alguns queiram baixar o código fonte, fuçar e comentar:
www.sourceforge.net (ótimo sistema de versionamento do site, recomendo)
https://svn.code.sf.net/p/doni/code/
Quem tiver uma conta no site posso liberar o acesso via SVN.

Bem, após terminar uma versão inicial, fui fazer o release do programa, liberar pro pessoal utilizar e reportar bugs. Lógico que algumas lib's teriam que ser copiadas para a pasta com o executável. E AQUI vem minha frustração, li um post no site do próprio Qt (qt-project.org) e eles recomandam copiar todas as libs presentes na instalação do Qt para a pasta contendo o executável.

Ou seja..

Coloquei o executável na pasta /Users/emanuel/Doni/
Copiar todo o conteúdo de /Users/emanuel/Qt/5.2/mingw_32/bin para /Users/emanuel/Doni/
Sim, o deploy foi para windows
E copiar a pasta /Users/emanuel/Qt/5.2/plugins para /Users/emanuel/Doni/

Ficamos com o diretório da seguinte forma:

/Users/emanuel/Doni/doni.exe
/Users/emanuel/Doni/*.dll
/Users/emanuel/Doni/plugins/*

Tudo isso dá um montante de 125MB... Um absurdo para mim!!! Um programinha de poucas funcionalidades ser deste tamanho! Só por causa de dependências. Se vocês utilizarem o Dependency Walker.exe (windows) ou o comando ldd (Linux) vão ver que as dependências estão todas lá.

Após fazer a cópia pode remover as libs com 'd' no final, pois são bibliotecas de debug.

E após ficar chateado com o fato de meu SIMPLES programa ter esse tamanho, obtive dois problemas e aqui vem minha contribuição para vocês programadores.

1)
Dependendo pra qual plataforma vocês vão desenvolver, no meu caso, fiz no meu Mac mas seria para usuários Windows. Tive que colocar uma biblioteca específica para windows, qwindows.dll, em uma pasta chamada platform/
Ou seja para cada plataforma tem o seu qwindows.dll e deve ser colocado na pasta plataform, se não o programa da erros e nem abre.
2)
O meu programa utiliza um drive para abrir o banco de dados SQLite. Após alguns dias sem entender o erro "DRIVER NOT LOADED" descobri que a biblioteca qsqlite.dll estava no lugar errado. Basta mover a pasta sqldrivers da pasta plugins para um diretório acima. Ficamos assim então:

/Users/emanuel/Doni/doni.exe
/Users/emanuel/Doni/*.dll
/Users/emanuel/Doni/plugins/*
/Users/emanuel/Doni/sqldrivers/*.dll


É isso ai pessoal,

espero que tenha ajudado e até a próxima.

segunda-feira, 9 de junho de 2014

Alterando arquivo xml como array de bytes.

Olá...


Recentemente tive uma demanda de um cliente, com  uma banda de acesso muito pequena. Por tanto tive que gerar um executável muito pequeno para enviar para ele.

A principio utilizei as bibliotecas do Qt para ler um Xml e fazer as modificações. Por causa da lib do qt exigir dependências externas, e não achei como remover as dependências, meu executável ficou com quase 40Mb.
Isso mesmo! 40 Mb :-/ Imagina minha decepção.

Partir para a libxml2, biblioteca bem documentada (não conhecia mas me agradou muito). Só que o executável e as bibliotecas juntas geram um montante de 10Mb.

Sei o que vocês vão perguntar? Não poderia gerar executável estático, gerar uma nova lib com só o necessário, certificar que os usuário já tenham as principais bibliotecas na máquina.

Respondendo: Era tarde da noite e não tinha como ter essas "regalias". Tinha que gerar um executável para a manhã seguinte.

Eis minha solução... gerei um executável em C com as bibliotecas básicas: stdio.h, string.h e stdlib.h
O executável ficou minusculo... 100 kb. Isso mesmo! só que deu um trabalho danado para re-escrever algumas funções triviais das bibliotecas Qt e libxml2. E outra coisa, eu tratei o arquivo xml como um array de bytes ;-)

- Achei o texto relacionado a tag
- Alterei o texto
- Salvei em um novo arquivo

Após esta história, vem o motivo deste post... foi difícil achar na net implementações das funções em C, então segue meu código abaixo para quem quiser reaproveitar:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

FILE *pFile_old = NULL,
         *pFile_new = NULL;
static char strTag_new[4096] = {};
int size_new = 0;
static char strTag_old[4096] = {};
int size_old = 0;

static char xmlData[4096] = {};
int size_xml = 0;

static char text[] = "Texto\\\0";
int size_text;

/**
 * @brief getPositionBegin
 * @param source
 * @param substr
 * @return position where start substr in source
 */
int getPositionBegin(char *source, char *substr)
{
    char *tmp = strstr(source, substr);
    int pos = (tmp - source);
    return pos;
}

/**
 * @brief getPositionEnd
 * @param source
 * @param substr
 * @return  position where end substr in source
 */
int getPositionEnd(char *source, char *substr)
{
    char *tmp = strstr(source, substr);
    int pos = (tmp - source) + strlen(substr);
    return pos;
}

/**
 * @brief readFileBytes
 * @param name
 */
void readFileBytes(const char *name)
{
    pFile_old = fopen(name , "rb");
    if (pFile_old == NULL) {
        fputs ("File error",stderr);
        exit(1);
    }
    // obtain file size:
    fseek (pFile_old , 0 , SEEK_END);
    size_xml = ftell (pFile_old);
    rewind (pFile_old);

    // copy the file into the buffer:
    size_t result = fread (xmlData, 1, size_xml, pFile_old);
    if (result != size_xml) {
        fputs ("Reading error",stderr);
        exit(2);
    }
    xmlData[size_xml+1] = '\0';

    fclose(pFile_old);
    pFile_old = NULL;
}

/**
 * @brief writeFileData
 * @param name
 * @param data
 * @return
 */
int writeFileData (char *name, char *data)
{
    pFile_new = fopen(name, "wb");
    size_t result = fwrite (data , sizeof(char), strlen(data), pFile_new);
//    if (result > 0)
//        exit(3);
    fclose (pFile_new);
    pFile_new = NULL;
    return result;
}

char *substring(char *string, int position, int length)
{
    char *pointer;
    int c;

    pointer = malloc(length+1);

    if( pointer == NULL )
        exit(EXIT_FAILURE);

    for( c = 0 ; c < length ; c++ )
        *(pointer+c) = *((string+position-1)+c);

    *(pointer+c) = '\0';

    return pointer;
}

/**
 * @brief Insert substring into source
 * @param source
 * @param substr
 * @param position
 * @warning array start in 1
 */
void insert_substring(char *source, char *substr, int position)
{
    char *f, *e;
    int length;

    length = strlen(source);

    // get text before 'substr'
    f = substring(source, 1, position - 1 );
    // get text after 'substr'
    e = substring(source, position, (length-position)+1);

    strcpy(source, "");
    strcat(source, f);
    free(f);
    strcat(source, substr);
    strcat(source, e);
    free(e);
}

/**
 * @brief remove_substring
 * @param source
 * @param substr
 */
void remove_substring(char *source, char *substr)
{
    int begin = getPositionBegin(source, substr);
    int end = getPositionEnd(source, substr);

    while( source = strstr(source, substr) )
        memmove(source,source+strlen(substr), 1+strlen(source+strlen(substr)));
}

void *replace_str(char *source, char *_old, char *_new)
{
    int pos = getPositionBegin(source, _old);
    remove_substring(source, _old);
    insert_substring(source, _new, pos);
}


Segue a função main:

int main(int argc, char* argv[])
{
    // reading xml file
    readFileBytes("c:\\exemplo.xml");

    // find position of tag
    int begin = 15 + getPositionBegin(xmlData, "<ReceiptFooter>");

    // find position of tag
    int end = getPositionBegin(xmlData, "</ReceiptFooter>");

    // define size of text
    size_old = (end-begin);
    int tam = (end-begin) - 1;

    // check text empty
    if (tam > 0) {

        // get the text
        strncpy(strTag_old, (xmlData+begin), size_old);
        strTag_old[size_old + 1] = '\0';

        //remove old tag text from xml
        remove_substring(xmlData, strTag_old);

        // Text split - remove lines that start with '--'
        char *pch = strtok (strTag_old,"\\");
        while (pch != NULL)
        {
            // check if string start with '--'
            int pos = getPositionBegin(pch, "--");
            if (pos != 0){
                // put in new string
                strcat(strTag_new, pch);
                size_new += strlen(pch);
                strcat(strTag_new, "\\");
                size_new++;
            }
            pch = strtok (NULL, "\\");
        }
        strTag_new[size_new+1] = '\0';
    }

    // find the text
    int pos = getPositionBegin(strTag_new, text);
    if (pos < 0) {
        //add text
        insert_substring(strTag_new, text, 1);
    }

    // remove old tag text and put new in xml
    insert_substring(xmlData, strTag_new, begin+1);
    writeFileData("c:\\exemplo2.xml", xmlData);

    // delete temporary files
    system("del c:\\exemplo.xml");
    system("del c:\\exemplo2.xml");

    return(0);
}


Utilizando esse código, com includes das bibliotecas básicas do C. Pudi criar um executável sem parâmetros para o GCC.

GCC main.c


Abraços

terça-feira, 6 de maio de 2014

Funções anônimas. Quem sabe o que é?

Neste post vou falar de funções anônimas, a sua usabilidade e mostrar exemplos. Vou colocar um exemplo de python e JavaScript, mas poderia ser em C++, C#, PHP e até Delphi pelo que ouvi falar, o importante é o conceito.

Funções anônimas, também conhecidas com closures, permitem a criação de funções que não tem nome especificado.

Elas são mais uteis com os valores de parâmetros callback, mas pode ter outros usos. Isso para o programador iniciante parece complicado mas após alguma pratica fica fácil.

Talvez surja a pergunta aos novatos: mas se utilizamos as funções "normais" sem problema, qual seria a utilidade das funções anônimas?
Imagine o caso que você necessite utilizar uma função só que com parâmetros diferentes. Neste caso você precisaria, dependendo da linguagem, declarar uma outra função e fazer sua implementação, para utiliza-la uma vez só as vezes. Com as funções anônimas, e caso a linguagem suporte closure ganhamos uma maior simplicidade de código.

Cálculo do número N da sequência de Fibonacci.
- usando funções normais Python

def fibonacci(N):
return N if N < 2 else fibonacci(N-1) + fibonacci(N-2)
print fibonacci(10)


- usando funções anônimas Python

fibonacci = lambda N: N if N<2 else fibonacci(N-1) + fibonacci(N-2)
print fibonacci(10)


- usando funções normais JavaScript

function myFunction() {
alert("Hello world!");
}


- usando funções anônimas JavaScript

var myFunction = function() {
alert("Hello world!");
}


Algumas peculiaridades de Python para JavaScript mas da para ter uma ideia.
Assim que tiver mais tempo, escrevo sobre expressões lambda. De onde originou as funções anônimas.

Criando um plugin para Chrome

O browser Chrome hoje é um dos mais utilizados (para mim o mais utilizado, não conheço programadores que não o estão usando), por isso, outro dia estava pensando em como desenvolver um plugin para o meu chrome para minhas necessidades. Eis que acho um tutorial básico na revista PROGRAMAR edição 44.


Eis uma "psicografia" :-D do artigo da revista.


O plugin que vamos criar, mostrara as dimensões do browser.

Para isso você deve conhecer um pouco de HTML, CSS e JavaScript.

Todas as extensões (plugins) para o chrome, web app ou temas tem um arquivo formatado, chamado de manifest. Neste caso manifest.json, que fornece importantes informações.

Segue nosso código do manifest.json abaixo. Os únicos campos realmente necessários são: name e version.

{
"manifest_version": 2,
"name": "Ruller",
"description": "Este plugin diz-nos quais as medi- das atuais do nosso browser.",
"version": "1.0",
"icons": {
"16": "icons/16x16.png",
"48": "icons/48x48.png",
"128": "icons/128x128.png" },
!
"browser_action": {
"default_icon": {
"19": "icons/19x19.png",
"38": "icons/38x38.png" },
"default_title": "Ruller" },
! "background": {
"scripts": ["background.js"] }, !
"content_scripts": [ {
"matches": ["http://*/*", "https://*/*"],
"js": ["jquery.min.js","content.js"] } ]
} ! 


Após isso, criaremos o arquivo background.js, definido no manifest. Ele serve como "vigia", fica na espera que o utilizador o chame. Vocês podem notar que estamos a espera do evento de clique.

chrome.browserAction.onClicked.addListener
(function(tab)
{
chrome.tabs.getSelected(null, function(tab)
{
chrome.tabs.sendMessage(tab.id, "");
});
});



No código acima, como nós vimos, estamos esperando o clique do mouse. O utilizador clica no nosso plugin e envia uma mensagem, mas para quem? Para isso nós definimos o nosso arquivo content.js, segue abaixo o código.

chrome.extension.onMessage.addListener
(function()
{
var width = $(window).width();
var height = $(window).height();
alert("Largura:" + width + "\nAltura: " + height);
}); 



Agora nós já temos todos nosso código, só precisamos nos atentar a duas coisas: esse plugin necessita o framework JQuery, logo temos de importar primeiro o JQuery. E fazer um teste do nosso plugin.

Vou colocar esse dois passos no meu próximo post.