Personal Website

My Web: MindEchoes.com

Monday, December 17, 2012

UMedia Agregando Canciones

Ahora con UMedia pueden agregar temas desde su disco eligiendo archivos o carpetas, y lo que es mas interesante, puede agregar temas desde YouTube, pasandole el ID del video, o la url, y UMedia se encargara de descargarlo y convertirlo a MP3 y dejarlo en la carpeta temporal del sistema para escucharlo durante la sesion.


Sunday, December 16, 2012

UMedia Version 0.1 Liberada!

UMedia es una nueva aplicaciòn en la que estuve trabajando esta ultima semana, es un Reproductor de Musica hecho con C++, Qt y QML...

Por que?? Porque tenia una idea de cosas que queria implementar y no me podia quedar con la idea nomas :P

Brinda las features base de cualquier reproductor en este momento y en esta semana ire agregando varias cosas mas y tratare de crear un PPA para que sea facilmente instalable desde Ubuntu.

La aplicaciòn a simple vista tiene una UI bastante copada al estilo de las aplicaciones de musica de los telefonos celulares.
Pueden encontrar en el README como compilarla a mano en Ubuntu y cuales son los shortcuts de la aplicaciòn.
Esperos les guste y les sirva tanto como a mi :P

Les dejo un video de UMedia funcionando:

Friday, December 7, 2012

NINJA-IDE Nominado para los Premios PortalProgramas

Are you a proud NINJA-IDE user?

Help us win the PortalProgramas Awards voting for us:
http://www.portalprogramas.com/software-libre/premios/proyecto/NINJA-IDE

Once again NINJA-IDE is nominated for the PortalProgramas Awards, you can help us win with your vote.
The winner of each category is going to receive a financial contribution to the project to support free software!

---------------------------------------------------------------------

Sos un orgulloso usuario de NINJA-IDE
 
Ayudanos a ganar los Premios PortalProgramas:
http://www.portalprogramas.com/software-libre/premios/proyecto/NINJA-IDE

Otra vez NINJA-IDE ha quedado seleccionado para los Premios PortalProgramas, podes ayudarnos a ganar votando por nosotros.
El ganador en cada categoria recibira una ayuda economica como modo de apoyar al software libre!

Monday, December 3, 2012

NINJA-IDE "Python3 Migration" Feature

Aca les dejo un video de la nueva feature de NINJA-IDE que fue desarrollada durante el fin de semana, la cual muestra tips en el mismo editor de que partes del codigo no serian compatibles con Python3, asi como tambien indica que cambios habria que hacer para que si lo sea.
Tambien nos muestra una lista en el panel lateral y nos permite ir seleccionando las partes no compatibles e ir arreglandolas con solo presionar un boton!


Monday, August 20, 2012

Apoya la Libertad de ELEGIR QUE INSTALAR!

Microsoft ha anunciado que si los fabricantes de ordenadores quieren distribuir sus productos con el logo de compatibilidad con Windows 8, deben implementar una medida llamada “arranque seguro.” Sin embargo, en la actualidad todavía no se sabe si esa tecnología está a la altura del nombre que se le ha puesto, o en vez de eso ganará el nombre de arranque restringido.
Usándolo de manera adecuada el “arranque seguro” está diseñado para proteger los equipos contra código malicioso (malware) evitando que se ejecuten programas binarios no autorizados durante el proceso de arranque. En la práctica esto significa que los ordenadores que lo implementen no arrancarán sistemas operativos no autorizados — incluyendo tambien sistemas operativos inicialmente autorizados que hayan sido modificados sin haber sido re-aprobados.
Esto podría ser una característica que merezca ese nombre, siempre y cuando el usuario sea capaz de autorizar los programas que quiera usar, así como de ejecutar software libre escrito y modificado por ella misma, o por personas en las que confíe. Sin embargo, nos preocupa que Microsoft o los fabricantes de hardware implementen estas restricciones en el arranque como una medida para prevenir que los usuarios no puedan arrancar otra cosa que no sea Windows. En este caso, es mejor llamar a esta tecnología “arranque restringido,” ya que este requisito sería una restricción desastrosa para los usuarios de ordenadores y de ninguna manera una característica de seguridad.

------------------------------------------------------------------

En la carta de la Free Software Foundation que recibí también dice que dichas computadoras deberían limitar con que sistema operativo se puede bootear a los incluidos en una lista de pre-aprobados, y aclara que:

"Por razones de seguridad, esa lista no incluye GNU/Linux"

Para tratar de detener esto pueden ayudar firmando esta petición:
http://www.fsf.org/campaigns/secure-boot-vs-restricted-boot/statement-es

Saturday, July 14, 2012

Kanzen Liberado!

Ayer libere el modulo de Code Completion para Python que usamos en NINJA-IDE como una libreria independiente para que cualquier pueda usarla en otra aplicacion. La libreria esta bajo licencia GPLv3 y es muy simple de usar:

Usando Kanzen: 

from kanzen import code_completion
cc = code_completion.CodeCompletion()

# Get the source code to be analyzed
# str or unicode
source_code = your_editor.get_source_code()  
# The path is used to identify each module 
# after analysis inside Kanzen
path = '/path/to/the/file.py'
# If you provide a path, but an empty string 
# for source_code, Kanzen will try to read the
# file from disk and get the source code.
# Execute "analyze_file" to collect metadata 
# and resolve types
cc.analyze_file(path, source_code)

# To get the list of possible completions, 
# you need to call: "get_completion" providing
# the source code of the file, and the position 
# of where the cursor is.
result = cc.get_completion(source_code, offset)

get_completion devolvera una estructura como la siguiente:

(No siempre contendra los siguientes elementos, puede ser que a veces "modules" no este si el objeto del que estamos obteniendo informacion no posee modulos, etc)

{
    'modules': ["list of strings", ...],
    'classes': ["list of strings", ...],
    'functions': ["list of strings", ...],
    'attributes': ["list of strings", ...],
}

Importante:
Se puede mejorar los resultados del Autocompletado agregando la carpeta del proyecto a Kanzen, por ejemplo, haciendo algo como:
 
from kanzen import completion_daemon
completion_daemon.add_project_folder('/path/to/project/')

# This will create a map of the project structure 
# and will allow Kanzen to understand
# the imports that are related to your project 
# and look for those modules.
# Also if your project is related to another project 
# which code is in your computer, the same principle apply.

No olvides: Cuando estas usando Kanzen, Kanzen va a activar un daemon para resolver en background casos especiales de autocompletado y tener esa informacion disponible cuando sea necesario. Habiendo dicho eso, es necesario recordar apagar el daemon antes de cerrar el programa.

from kanzen import completion_daemon
completion_daemon.shutdown_daemon()

Repositorio del Proyecto: Kanzen

Monday, July 2, 2012

Y llegamos a NINJA-IDE 2.0

Hemos liberado la version 2.0 de NINJA-IDE, despues de un año y medio  trabajando en esta version, la verdad que estamos muy contentos del Release que conseguimos!!
Esta nueva version incluye muchas features que realmente cumplen con la idea que nos propusimos de hacer la vida del desarrollador Python mas facil, productiva.

http://ninja-ide.org/

La cantidad de features y mejoras incluidas en esta version la verdad que SON MUCHAS, y pronto estaremos liberando tutoriales y videos para que se le pueda sacar el maximo provecho!

Muchas gracias a toda la gente que estuvo involucrada en NINJA-IDE para llegar a este Release!

Ejemplo de Code Completion en NINJA-IDE:




Video para el Aniversario del Año de NINJA-IDE:

 

Video Evolucion de NINJA-IDE 2.0:

Wednesday, June 27, 2012

Tuesday, June 19, 2012

Vistazo al Code Completion de NINJA-IDE

Code Completion para NINJA-IDE es una de las features importantes de esta versión, ya que queremos lograr algo que sea muy muy preciso para Python, todavia nos faltan resolver variasssss cosas en las que estamos trabajando para cerrar en estos dias, pero aca hay un pequeño preview de algunas de las cosas que sabe resolver ahora:


Friday, June 15, 2012

Escribiendo un Unity Lens para NINJA-IDE

Este es un Unity Lens MUY SIMPLE, la parte mas complicada del codigo pasa en realidad por actualizar el descriptor de NINJA-IDE para que sepa la informacion del Plugin descargado y pueda eliminarse correctamente despues desde la UI si fuera necesario.

Basicamente lo que hace este Lens, es permitirle al usuario ingresar las palabras a buscar, y el Lens (o el scope del Lens) se encargara de buscar esas palabras entre el nombre, descripcion y tags de los plugins subidos a la pagina de NINJA-IDE, retornando como resultado aquellos plugins que contengan alguna de las palabras buscadas, tambien se puede ingresar el nombre de algun usuario y ver los plugins de ese usuario, y al elegir alguno de los resultados de la Dash, el Plugin sera instalado en el sistema de la misma forma que lo hariamos desde NINJA-IDE.




En este Lens, estamos capturando la emision de la uri para manejarla nosotros y descargar el Plugin sin que la dash haga nada con eso, ya que es un uso muy especifico que se quiere hacer con el resultado.

El codigo de este Lens se encuentra en:
https://github.com/diegosarmentero/Experiments/tree/master/lenses/ninjaplugins

Para probarlo deberia ser tan simple como tener instalado Quickly
($ sudo apt-get install quickly)

Y adentro de la carpeta del Lens hacer:

$ sudo quickly install
$ quickly run

Aclaracion: Si la carpeta donde se descargan los plugins de NINJA-IDE no existe (porque ninja no esta instalado o nunca fue ejecutado), el Lens va a permitir hacer las busquedas pero no va a descargar ningun Plugin.

Friday, June 8, 2012

Viaje a BsAs y UbuConLA

El fin de semana pasado viaje a BsAs para participar de la UbuConLA y de paso aproveche para hacer turismo a full por buenos aires :D

Como siempre estuvo re bueno juntarse con gente de la comunidad Python y del Software Libre, siempre hay gente muy copada y temas de conversacion interesantes!

El Viernes fui un rato a la UbuConLA, despues me retire a buscar al nuevo miembro de la familia :D


Despues paseamos un rato por unas comiquerias:



Y luego al hotel!! :D
Tengo que reconocer que me sentia como Mi Pobre Angelito 2 en ese hotel jejeje




Y aca Filly se hacia la que estaba haciendo cosas interesantes:


Y para terminar el Viernes, fuimos a comer a un lugar reee copado!!
Muy buena la comida!


 Algunas fotos de la UbuConLA:




El sabado a la noche despues de la UbuConLA, nos fuimos varios de los que estabamos ahi a comer a Orsai Bar (pude conocerlo :D... pero bastante regulares las pizzas)


Y despues mas tarde fui a una fiesta que se llamaba: "Headshot Party", pasaban musica de anime, habia pistas para bailar, arriba se podia jugar al Mortal Kombat y otros juegos (en los que soy sumamente horrible), repartian cosas luminosas, habia minas que bailaban colgadas de una tela en una parte... bastante completo.


Y para finalizar, el domingo fui a conocer el Jardin Japones y comimos ahi (muy rica la comida tambien :P)








Y despues del Jardin Japones fuimos al Museo que habia una exposicion de Cultura Oriental, pero ahi no pude sacar fotos porque no me dejaron...

Ahora a esperar con ansias el siguiente viaje que es PyCamp!!!

Haciendo un Navegador Web Desktop con Scroll Kinetico

Este es un post que tenia pendiente hace rato, la idea es mostrar como en una aplicación desktop podemos tener un Navegador Web donde podamos scrollear apretando con el mouse y moviendo el puntero en alguna dirección, de la misma forma que hacemos tradicionalmente con el dedo en los navegadores de los celulares, y tener ese efecto de Scroll Kinetico, donde el scroll de la página parece tener cierta inercia.

DISCLAIMER: No critiquen mi código en C++ :P, no programo seguido en este lenguaje.

Vamos a empezar de lo general a lo particular.
Primero paso, el punto de entrada de nuestro programa:


#include <QtGui/QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}


Ahora vamos a diseñar la ventana principal, que basicamente va a consistir en un layout con un Widget para renderizar la página web, y una barra de progreso para la carga de la página (podriamos hacerlo más completo, con la barra de direcciones, etc, pero no es la idea de este post, para eso ver: http://www.diegosarmentero.com/2010/09/navegador-web-en-menos-de-100-lineas-de.html)

"mainwindow.h"


#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui>
#include <QtCore>
#include <QtWebKit>
#include "grabdragscroll.h"

class QWidget;
class QVBoxLayout;
class QWebView;
class QProgressBar;
class GrabDragScroll;

class MainWindow : public QWidget
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);

private:
    QWebView* web;
    QVBoxLayout* v_box;
    QProgressBar* progress;
    GrabDragScroll* grab;
};

#endif // MAINWINDOW_H


Y el código de la clase:


#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QWidget(parent)
{
    v_box = new QVBoxLayout(this);

    this->web = new QWebView(this);
    this->web->load(QUrl("http://diegosarmentero.com.ar"));

    this->grab = new GrabDragScroll(this);
    this->grab->installWidget(this->web, true);
    this->progress = new QProgressBar(this);
    this->v_box->addWidget(this->web);
    this->v_box->addWidget(this->progress);

    this->connect(this->web, SIGNAL(loadProgress(int)), 
                  this->progress, SLOT(setValue(int)));
    this->connect(this->web, SIGNAL(loadFinished(bool)), 
                  this->progress, SLOT(hide()));
    this->connect(this->web, SIGNAL(loadStarted()), 
                  this->progress, SLOT(show()));
}


Como podemos ver en la linea:

    this->grab->installWidget(this->web, true);

Se esta llamando a la función "installWidget" de GrabDragScroll, esta función recibe 2 atributos, el primero es el widget web sobre el cual se va a instalar el Scroll Kinetico, y el segundo argumento es para decirle a GrabDragScroll si queremos que escondas las barras de desplazamiento del Widget Web para que la única opción de scroll sea el mouse.

Lo que va a ser nuestra clase GrabDragScroll es instalar un eventFilter sobre el Widget Web, para que todos los eventos de ese Widget pasen primero por esta clase, y podamos tomar aquellos eventos que son de mouse para hacer el efecto de Scroll kinetico, y dejar pasar el resto de los eventos para que sigan su comportamiento normal.

La siguiente parte no la voy a explicar en detalle :P porque es medio vueltero de explicar y es más fácil entender el código, básicamente lo que hace es fijarse en que estado me encuentro:

STATE { STEADY, PRESSED, MANUAL_SCROLL, AUTO_SCROLL, STOP };

Y en base al estado y el tipo de evento que se produjo con el mouse pasa a un estado u otro y hace los calculos que sean necesarios del offset del mouse para poder animar el scroll.

Se puede ver más abajo que tenemos un método "timerEvent" que se utiliza para hacer la animación de ir desacelerando el movimiento de la página, y por último tenemos los métodos "scrollOffset" y "setScrollOffset" que ejecutan funciones javascript sobre el widget web para obtener la posición en la que se encuentra el scroll o setear una posición del scroll. El efecto de scroll kinetico basicamente consiste en ir seteando distintas posiciones del scroll e ir ampliando/disminuyendo la diferencia entre asignaciones dependiendo del impulso que se le da con el cursor (el cual consiste en calcular la posición cuando se estaba en estado STEADY y la distancia de la posición final del mouse al hacer release del click):


#include "grabdragscroll.h"

struct ScrollData
{
    enum STATE { STEADY, PRESSED, MANUAL_SCROLL, AUTO_SCROLL, 
                 STOP };
    int state;
    QWebView *widget;
    QPoint pressPos;
    QPoint offset;
    QPoint dragPos;
    QPoint speed;
};

GrabDragScroll::GrabDragScroll(QObject *parent) :
    QObject(parent)
{
    this->data = new ScrollData;
    this->data->state = ScrollData::STEADY;
    this->data->pressPos = QPoint(0, 0);
    this->data->offset = QPoint(0, 0);
    this->data->dragPos = QPoint(0, 0);
    this->data->speed = QPoint(0, 0);

    this->timer = new QBasicTimer;
}

void GrabDragScroll::installWidget(QWebView* widget, 
                                   bool withoutBars)
{
    if(withoutBars){
        QWebFrame* frame = widget->page()->mainFrame();
        frame->setScrollBarPolicy(
                    Qt::Vertical, Qt::ScrollBarAlwaysOff);
        frame->setScrollBarPolicy(
                    Qt::Horizontal, Qt::ScrollBarAlwaysOff);
    }
    widget->installEventFilter(this);
    data->widget = widget;
}

bool GrabDragScroll::eventFilter(QObject *obj, QEvent *event)
{
    if(!(obj->isWidgetType())){
        return false;
    }

    QEvent::Type eventType = event->type();
    if(eventType != QEvent::MouseButtonPress &&
       eventType != QEvent::MouseButtonRelease &&
       eventType != QEvent::MouseMove){
        return false;
    }

    QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(
                                            event);
    if(this->data->pressPos.x() == mouseEvent->pos().x() &&
       this->data->pressPos.y() == mouseEvent->pos().y()){
        this->data->state = ScrollData::STEADY;
        return false;
    }
    bool consumed = false;

    if(this->data->state == ScrollData::STEADY &&
       eventType == QEvent::MouseButtonPress &&
       mouseEvent->buttons() == Qt::LeftButton){
            consumed = false;
            data->state = ScrollData::PRESSED;
            data->pressPos = QPoint(mouseEvent->pos());
            data->offset = this->scrollOffset(data->widget);

    }else if(data->state == ScrollData::PRESSED){
        if(eventType == QEvent::MouseButtonRelease){
            consumed = true;
            data->state = ScrollData::STEADY;
            QMouseEvent* event1 = new QMouseEvent(
                        QEvent::MouseButtonPress,
                        data->pressPos, Qt::LeftButton,
                        Qt::LeftButton, Qt::NoModifier);
            QMouseEvent* event2 = new QMouseEvent(*event1);

        }else if(eventType == QEvent::MouseMove){
            consumed = true;
            data->state = ScrollData::MANUAL_SCROLL;
            data->dragPos = QCursor::pos();
            if(!this->timer->isActive()){
                this->timer->start(20, this);
            }
        }

    }else if(data->state == ScrollData::MANUAL_SCROLL){
        if(eventType == QEvent::MouseMove){
            consumed = true;
            const QPoint pos = mouseEvent->pos();
            const QPoint delta = pos - data->pressPos;
            this->setScrollOffset(this->data->widget,
                                  this->data->offset - delta);

        }else if(eventType == QEvent::MouseButtonRelease){
            consumed = true;
            data->state = ScrollData::AUTO_SCROLL;
        }

    }else if(data->state == ScrollData::AUTO_SCROLL){
        if(eventType == QEvent::MouseButtonPress){
            consumed = true;
            data->offset = this->scrollOffset(data->widget);
            data->state = ScrollData::STOP;
            data->speed = QPoint(0, 0);
        }else if(eventType == QEvent::MouseButtonRelease){
            consumed = true;
            data->state = ScrollData::STEADY;
            data->speed = QPoint(0, 0);
        }

    }else if(data->state == ScrollData::STOP){
        if(eventType == QEvent::MouseButtonRelease){
            consumed = true;
            data->state = ScrollData::STEADY;
        }else if(eventType == QEvent::MouseMove){
            consumed = false;
            data->state = ScrollData::MANUAL_SCROLL;
            const QPoint pos = mouseEvent->pos();
            this->setScrollOffset(this->data->widget, 
                                  this->data->offset);
        }
    }

    return consumed;
}

void GrabDragScroll::timerEvent(QTimerEvent *event)
{
    if(data->state == ScrollData::MANUAL_SCROLL){
        QPoint cursorPos = QCursor::pos();
        data->speed = cursorPos - data->dragPos;
        data->dragPos = cursorPos;
    }else if(data->state == ScrollData::AUTO_SCROLL){
        data->speed = this->deaccelerate(this->data->speed);
        QPoint p = this->scrollOffset(data->widget);
        this->setScrollOffset(
                    this->data->widget, p - data->speed);
        if(data->speed == QPoint(0, 0)){
            data->state = ScrollData::STEADY;
        }
    }

    QObject::timerEvent(event);
}

QPoint GrabDragScroll::scrollOffset(
        const QWebView* view) const {
    int x, y = 0;
    QWebFrame* frame = view->page()->mainFrame();
    x = frame->evaluateJavaScript("window.scrollX").toInt();
    y = frame->evaluateJavaScript("window.scrollY").toInt();

    return QPoint(x, y);
}

void GrabDragScroll::setScrollOffset(QWebView* view, 
                                     const QPoint& p)
{
    QWebFrame* frame = view->page()->mainFrame();
    frame->evaluateJavaScript(
     QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y()));
}

QPoint GrabDragScroll::deaccelerate(const QPoint &speed, 
                                    const int a, 
                                    const int maxVal)
{
    int x = qBound<int>(-maxVal, speed.x(), maxVal);
    int y = qBound<int>(-maxVal, speed.y(), maxVal);
    if(x > 0){
        x = qMax<int>(0, x - a);
    }else if(x < 0){
        x = qMin<int>(0, x + a);
    }
    if(y > 0){
        y = qMax(0, y - a);
    }else if(y < 0){
        y = qMin(0, y + a);
    }

    return QPoint(x, y);
}


Un pequeño problema que tuve la primera vez que lo hice, fue que como estamos capturando los eventos de mouse, no se podia navegar porque los clicks nunca llegaban a los links, y un navegador que no te deja navegar no tenia mucho chiste, así que como se puede ver al principio del método "eventFilter", agregue que si la posición del mouse es igual a la posición anterior y se estaba en un estado de STEADY, se deja pasar el evento hacia arriba para que lo agarre el widget web.

El código completo de todo el ejemplo puede encontrarse en:
https://github.com/diegosarmentero/Experiments/tree/master/KineticScroll

Para ejecutarlo, deberia ser tan simple como abrir el proyecto con Qt Creator y compilarlo y ejecutarlo desde ahí (y sino siempre esta la consola y qmake).

Espero no haber dejado nada sin explicar... y cualquier cosa el código es bastante corto y simple :D

Friday, April 6, 2012

Quick Form con Qt y Python

Esto es una idea que se me ocurrió ayer y simplemente tenia ganas de hacerlo... ÚTIL? no lo se...quizás lo pueda mejorar un poco más, agregarle un par de cosas y hacer algún plugin de ninja que te permita crear formularios de ABM de forma automática para tu aplicación basados en las tablas de la base de datos... una idea nomas... Pero bueno, les dejo el código, luego la explicación y por último el ejemplo:

from PyQt4.QtGui import QDialog
from PyQt4.QtGui import QLineEdit
from PyQt4.QtGui import QPushButton
from PyQt4.QtGui import QLabel
from PyQt4.QtGui import QMessageBox
from PyQt4.QtGui import QVBoxLayout
from PyQt4.QtGui import QHBoxLayout
from PyQt4.QtGui import QFormLayout
from PyQt4.QtGui import QSpacerItem
from PyQt4.QtGui import QSizePolicy
from PyQt4.QtCore import SIGNAL


class QuickForm(QDialog):

    def __init__(self, **kwargs):
        super(QuickForm, self).__init__()
        self.required_fields = []

        vbox = QVBoxLayout(self)
        self.form_title = QLabel()
        self.form_subtitle = QLabel()
        vbox.addWidget(self.form_title)
        vbox.addWidget(self.form_subtitle)
        vbox.addSpacerItem(QSpacerItem(0, 20))

        form_layout = QFormLayout()
        vbox.addLayout(form_layout)

        hbox = QHBoxLayout()
        self.form_accept = QPushButton('Accept')
        self.form_cancel = QPushButton('Cancel')
        hbox.addSpacerItem(QSpacerItem(20, 0,
            QSizePolicy.Expanding))
        hbox.addWidget(self.form_cancel)
        hbox.addWidget(self.form_accept)

        self.form_operations = {
        'form_window_title': lambda x: self.setWindowTitle(x),
        'form_title': lambda x: self.form_title.setText(x),
        'form_subtitle': lambda x: self.form_subtitle.setText(x),
        'form_accept': lambda x: self.form_accept.setText(x),
        'form_cancel': lambda x: self.form_cancel.setText(x),
        'form_modal': lambda x: self.setModal(x),
        }
        for key in kwargs:
            if not key.startswith('form_'):
                result = self.check_content(key, kwargs[key])
                setattr(self, key, result[1])
                form_layout.addRow(result[0], result[1])
            else:
                self.form_operations.get(
                    key, lambda x: None)(kwargs[key])
        #Hide items if they are empty
        self.form_title.setVisible(
            not self.form_title.text().isEmpty())
        self.form_subtitle.setVisible(
            not self.form_subtitle.text().isEmpty())

        vbox.addSpacerItem(QSpacerItem(0, 20))
        vbox.addLayout(hbox)

        self.connect(self.form_accept, SIGNAL("clicked()"),
            self.accept_clicked)
        self.connect(self.form_cancel, SIGNAL("clicked()"),
            self.cancel_clicked)

    def accept_clicked(self):
        accepted = True
        for attr in self.required_fields:
            if attr.text().isEmpty():
                accepted = False
                break
        if accepted:
            self.accept()
        else:
            QMessageBox.information(self, "Invalid Form",
                "Some required fields are not completed.")

    def cancel_clicked(self):
        self.reject()

    def check_content(self, key, content):
        value = None
        if isinstance(content, basestring):
            value = (content, QLineEdit())
        elif isinstance(content, (dict)):
            text = content.get('text', key)

            args = content.get('args', ())

            widget = content.get('widget', None)
            if hasattr(widget, '__call__'):
                widget = widget(*args)
            else:
                widget = QLineEdit()

            if content.get('required', False):
                self.required_fields.append(widget)
                text += ' (*)'

            value = (text, widget)

        return value


Entonces, cual es la idea de esto?
Teniendo esta clase, al instanciarla podemos pasarle una serie de argumentos para que nos cree un formulario de forma dinámica, y algunos de estos argumentos nos sirven igual para customizar el formulario, por ejemplo:

  • form_window_title: Nos deja customizar el titulo de la ventana.
  • form_title: Nos deja agregar un titulo dentro del formulario.
  • form_subtitle: Agrega un subtitulo dentro del formulario.
  • form_accept: Modifica el texto del botón de Aceptar ("Accept" por defecto)
  • form_cancel: Modifica el texto del botón de Cancelar ("Cancel" por defecto)
  • form_modal: Establece si el dialogo es de tipo modal o no.
Estas de arriba son "keys" ya definidas, pero luego, por cualquier otra Key que le pasemos al constructor de QuickForm, lo que va a hacer es crear un atributo del objeto con el nombre de esa key (por defecto ese atributo seria un QLineEdit) entonces una vez cerrado el formulario podemos consultar los valores de los campos directamente accediendo a estos widgets con el nombre de variable igual al que pusimos en las keys.

Por ejemplo:


qf = QuickForm(name="Ingrese Nombre:", surname="Ingrese Apellido:")
qf.show()

# Una vez que el usuario ingreso los datos, podemos hacer:

print qf.name.text()
print qf.surname.text()

De esta forma, podemos de crear de forma rápida, fácil y genérica, todos estos tipos de formularios que muchas veces suele ser un trabajo muy repetitivo cuando son necesarios.

Otra posibilidad también,  es la de customizar un poco más con que widget va a ser representado nuestra key, las opciones son:
  • text: Estable el texto que se mostrara al lado del widget para brindarle algún tipo de referencia al usuario.
  • widget: Recibe la clase a ser instanciada para asociar al widget.
  • args: Recibe alguna serie de argumentos (una tupla) que querríamos pasarle al constructor de este widget al ser instanciado.
  • required: Booleano para decir si este campo es requerido que este completado por el usuario para poder aceptar el formulario.
Todos estos campos son opcionales, por customizar alguno, no es necesario tener que completar todos.

Entonces podriamos tener algo al estilo:

qf = QuickForm(name={"text": "Ingrese Nombre:", "required": True}, 
               surname="Ingrese Apellido:" ,
               age={"text": "Ingrese Edad:", "widget": QSpinBox})
qf.show()

Y bueno, ahora un ejemplo completo de uso:
(Hice "import *" aunque no me gusta solo para simplificar el código...)


import sys

from PyQt4.QtGui import *
from PyQt4.QtCore import *

import quick_form


class MyWindow(QWidget):

    def __init__(self):
        super(MyWindow, self).__init__()

        vbox = QVBoxLayout(self)
        vbox.addWidget(QLabel("Completar Formulario"))

        btn = QPushButton("Abrir Formulario")
        vbox.addWidget(btn)

        self.connect(btn, SIGNAL("clicked()"), self.open_form)

    def open_form(self):
        self.qf = quick_form.QuickForm(
            form_window_title="Ingrese Usuario",
            form_title="<h2>Datos de Usuario</h2>",
            form_accept="Save",
            name={"text": "Ingrese Nombre:", "required": True},
            surname="Ingrese Apellido:",
            age={"text": "Ingrese Edad:", "widget": QSpinBox},
            weight={"text": "Ingrese peso:", "widget": QSlider,
                    "args": (Qt.Horizontal,)}
        )

        self.connect(self.qf, SIGNAL("accepted()"),
            self.print_values)
        self.qf.show()

    def print_values(self):
        print 'VALORES INGRESADOS:'
        print "Nombre:", self.qf.name.text()
        print "Apellido:", self.qf.surname.text()
        print "Edad:", self.qf.age.value()
        print "Peso:", self.qf.weight.value()


app = QApplication(sys.argv)
w = MyWindow()
w.show()

sys.exit(app.exec_())



#SALIDA al apretar "Save":
#-------
#
#VALORES INGRESADOS:
#Nombre: Diego
#Apellido: Sarmentero
#Edad: 26
#Peso: 50

Y el resultado de esto seria:


Uno de los problemas, es que las cosas al ser keys de un diccionario aparecen en cualquier orden, pero bue... eso se soluciona fácil y después lo hare si se me ocurre algún lado donde meter esto :P

Wednesday, April 4, 2012

Nostalgia de Juegos

Nunca fui muy "gamer", los que me conocen lo saben, muchas veces digo: "mmm programar o jugar??... no, jugar cansa mucho y programar es mas divertido" (/me siente las miradas asesinas de los gamers)... de hecho soy muy particular para los juegos, y hay muy pocos que me atrapen MALLLLL, la gran mayoría lo juego y en cuanto lo dejo, por ahí me lo olvido por meses! Es más, si tuviera que decir cual es mi juego favorito... probablemente eligiria "Day of Tentacle":


Pero ayer me empecé a acordar de cuando era chico, y compraba las revistas de los últimos juegos, etc. Porque siempre estuve fascinado con los gráficos de los juegos, muchas veces lo que me llamaba de un juego eran los graficos, y cada vez que salian juegos nuevos con gráficos mas potentes me re emocionaba! Y me acuerdo de esto, porque ayer Filly me paso un tema del soundtrack de Mass Effect:



MUY BUEN TEMA!
Pero eso me llevo a acordarme de otro BUENISIMOOOOOO soundtrack que escuchaba de chico, que hasta me puse a buscar los temas en las carpetas del juego y me los copie a un cd de musica (notese la vejez de las cosas que digo :P), y recordando la musica, fue que recordé los gráficos que tenían los juegos de esa época que TANTO ME FASCINABAN! (y que cada vez que salia algo mejor decia: "impresionante! esto se ve re real!"... REAL?! jeje)

PANDEMONIUM 

Después otro juego que empezaba muy bueno (digo empezaba porque solo tenia una demo que venia en las revistas que compraba... y nunca lo pude conseguir completo... en esa época no era tan fácil y ni tenia internet :P) fue el:

NORMALITY


Vale aclarar que cuando jugué ese juego... esos gráficos para mi eran: "GUAU! ESTO SE VE INCREÍBLE!"... si... lo se... :P Y ahora el juego por el que estoy escribiendo el post... el juego del que me acorde por el INCREIBLE soundtrack que tenia!

MECHWARRIOR 2 

 Intro:



Gameplay:



Cabe aclarar que cuando me sente a jugar este juego... unos 13 años atrás aproximadamente, y siendo que donde yo vivía no llegaban las cosas mas nuevas tampoco, pensé: "NOOO ESTO ES CASI REALIDAD VIRTUAL!!!" (así me emocionaba con cada gráfico copado que veía en esos tiempos jeje)

Lo peor de todo, es que no me pintaba tanto el combate, sino como estaba simulado el robot, entonces en lugar de ir a matar a todos, me ponía a explorar el terreno, alejándome de "la zona de batalla" y ver hasta donde llegaban realmente los niveles... que tanto me podía acercar a esas montañas y cosas que veía a lo lejos.




Para mi sorpresa, termine descubriendo que cuando llegaba a los limites del nivel, sin siquiera haber tenido que matar a nadie... GANABA EL NIVEL! \o/... así que prácticamente termine el juego sin tener que dispararle a nadie :P ... igual después volví a jugarlo tratando de matar robots, pero ya había visto lo que mas me interesaba jeje. Y bueno, para terminar con la nostalgia, les dejo acá el soundtrack de este buenísimo juego (CUYO SOUNDTRACK ES AUN MEJOR! y no es algo que perdió calidad con los años :P)



Para descargar: Mechwarrior 2 Soundtrack

Thursday, March 22, 2012

Video: Evolucion del Código Fuente de NINJA-IDE 2.0-Beta

Este vídeo muestra la evolución de la versión 2.0-beta de NINJA-IDE desde el 25 de Octubre del 2011 al 26 de Febrero del 2012.
El vídeo fue hecho con Gource usando este script:

gource [GIT_FOLDER] -f 1280x720 --camera-mode overview --auto-skip-seconds 1 --seconds-per-day 4 --logo [PATH-TO-LOGO] --title "YOUR TITLE" --hide progress --multi-sampling -o [OUTPUT_FILE]


Sunday, February 19, 2012

Extendiendo NINJA-IDE Symbols y Code Locator

NINJA-IDE posee 2 features que pueden ser muy comodas para navegar el código, una de ellas es el "Symbols Explorer" y la otra el "Code Locator".

El "Symbols Explorer" nos permite poder visualizar a simple vista como se encuentra compuesto el archivo (clases, funciones, atributos):


Y el "Code Locator" (Ctrl+K), que personalmente es una que uso MUCHISIMO, nos permite con un par de teclas saltar a cualquier archivo, definición de clase, atributo o método en cualquiera de todos los archivos de nuestro árbol de proyectos. Al ingresar el nombre de lo que estamos buscando, dicha búsqueda ni siquiera tiene que ser la palabra exacta, sino simplemente puede ser una parte del nombre (ya sea esta parte caracteres del principio, medio, etc).
A su vez, esta feature tiene ciertos filtros que pueden aplicarse para hacer más precisa la búsqueda:


Puede suceder que tengamos en todos nuestros proyectos cosas que tengan nombres similares, o cualquier tipo de situación que haga que el filtrado por ahí nos de muchos resultados, entonces el Code Locator nos permite combinar estos pre-filtros para poder ser más especificos en nuestra búsqueda siempre yendo de lo general a lo particular, por ejemplo:
  • Se podría querer buscar una función que contenga la palabra jump, pero solo si esta contenida en la clase Editor del archivo editor.py, de esta forma podriamos escribir en el campo de búsqueda algo del estilo: "@ed<e>jump"
  •  También podriamos evitar especificar el archivo y decir que solo queremos ver los métodos de determinada clase, para lo cual podes filtrar primero por Clase (<) y luego elegimos a que clase aplicar el filtro por funciones (>):
 (filtrado por clases)
(filtrado por funciones de la clase seleccionada)

  • También existe la opción de filtrar por el contenido del archivo actual con "." y luego sobre ese resultado aplicar el resto de los filtros para ver las funciones del archivo actual, las clases, las funciones de determinada clase, etc.

PERO estas 2 features mencionadas, como NINJA-IDE es un IDE especialmente diseñado para Python solo funcionan con Python, PERO (otra vez) como NINJA-IDE puede ser fácilmente extendido para soportar nuevas funcionalidades, podemos hacer que estas mismas features sirvan para cualquier otro lenguaje con el que estemos trabajando, por ejemplo:

Supongamos que queremos usar el explorador de símbolos y el code locator para un proyecto donde estamos trabajando con Ruby y nos gustaria tener estas features para los archivos escritos en ruby. Bueno, para soportar ambas features solo tenemos que escribir un Plugin que cree un Handler para los símbolos de determinada extensión y hacer que ese handler reimplemente un método que devuelva una estructura de diccionario ya definida.

{
     'attributes':
         {name: line, name: line},
     'functions':
         {name: line, name: line},
     'classes':
         {
         name: (line, {
                     'attributes': {name: line},
                     'function': {name: line}}
             )
         }
}


Para ver un ejemplo concreto de como quedaría el Plugin, acá dejo el código (ACLARACIÓN: no programo en Ruby así que las expresiones regulares para identificar las clases y métodos obviamente pueden no ser las mejores y cubrir todos los casos, pero es un ejemplo que arme en un rato nomás para mostrar lo fácil que sería hacer esto)




# -*- coding: utf-8 *-*

import re

from ninja_ide.core import plugin
from ninja_ide.core import plugin_interfaces


EXTENSION = 'rb'


class RubySymbols(plugin.Plugin):

    def initialize(self):
        self.explorer_s = self.locator.get_service('explorer')
        # Set a project handler for NINJA-IDE Plugin
        self.explorer_s.set_symbols_handler(EXTENSION,
            RubySymbolsHandler())


class RubySymbolsHandler(plugin_interfaces.ISymbolsHandler):

    def __init__(self):
        class_ = r'^(\s)*class(\s)+(\w)+.*'
        function_ = r'^(\s)*def(\s)+((\w)+(\.)*)+(\s)*(\(.)*'
        self.patClass = re.compile(class_)
        self.patFunction = re.compile(function_)
        self.patIndent = re.compile('^\s+')

    def obtain_symbols(self, source):
        """Returns a dict with the ruby symbols for sources."""
        source = source.split('\n')
        symbols = {}
        classes = {}
        functions = {}
        last_class = ''
        clazz_indent = 0
        for nro, line in enumerate(source):
            space = self.patIndent.match(line)
            if space is not None:
                indent = len(space.group())
            else:
                indent = 0

            if self.patClass.match(line):
                clazzname = line.split()[1]
                classes[clazzname] = (nro + 1, {'functions': {}})
                last_class = clazzname
                clazz_indent = indent
            elif self.patFunction.match(line):
                funcname = line.split()[1].split('(')[0]
                if indent > clazz_indent and last_class != '':
                    class_data = classes[last_class][1]
                    class_data['functions'][funcname] = nro + 1
                else:
                    last_class = ''
                    clazz_indent = 0
                    functions[funcname] = nro + 1
        if classes:
            symbols['classes'] = classes
        if functions:
            symbols['functions'] = functions
        return symbol
Y ese es todo el código necesario para que NINJA-IDE soporte el Symbols Explorer y Code Locator para Ruby, y tengamos resultados como estos para un archivo como este:
Symbols Explorer:

Code Locator:

Todo el proyecto de este plugin se puede encontrar en: RubySymbols
Y para ver documentación de como escribir Plugins para NINJA-IDE: Plugins Tutorial

Wednesday, February 1, 2012

Instalar NINJA-IDE desde PPA

Hace mucho que no escribía en el blog y ya me estaba sintiendo muy culpable... así que quería dejar un post cortito como para volver y el fin de semana voy a postear un mini tutorial de como escribir plugins que extiendan la capacidad del explorador de simbolos y el Code Locator de NINJA-IDE para otros lenguajes.


Al asunto!
Si sos un afortunado usuario de Ubuntu, podes instalar NINJA-IDE desde PPA, con los beneficios que eso incluye: ACTUALIZACIONES!!
Si tenes NINJA-IDE instalado desde PPA, cada vez que se actualicen dichos PPAs, te van a llegar actualizaciones de NINJA-IDE junto a las actualizaciones de Ubuntu, por ende no hace falta que esperes a que hagamos releases de las distintas versiones, etc, etc.

Para instalar NINJA-IDE desde PPA los pasos son (el primer paso de agregar el ppa, hay 2 opciones de las cuales se puede elegir):

$ sudo apt-add-repository ppa:ninja-ide-developers/ninja-ide
(Este PPA genera paquetes cada ciertos tiempos con la inclusión de nuevas features o bug fixes y se considera que el código es realmente muy estable)

$ sudo apt-add-repository ppa:ninja-ide-developers/daily
(Este PPA es actualizado cada día con el código que sea introducido en el repositorio de desarrollo de NINJA-IDE, por ende tiene siempre los ultimos cambios y se pueden probar las features apenas salen... lo que implica también que pueda haber bugs, pero nunca se sube código a repositorio que imposibilite a un usuario poder seguir trabajando con NINJA-IDE)

$ sudo apt-get update
$ sudo apt-get install ninja-ide



Recibiendo actualizaciones de NINJA-IDE: