Archive for the ‘Coding’ Category

June 30th, 2010 @ 3:06pm | njames | Categories Coding | No Comments »
Dans l'arsenal de commandes de SVN il n'y a pas de commande pour supprimer une révision, la façon la plus simple de faire est de créer un dump du repository incriminé, supprimer le repository, créer un repository avec le même nom, et y importer le dump précédemment crée.
$tar czf cbiaproto3.tar.gz ./cbiaproto3
$svnadmin  dump -r1:13 cbiaproto3 > cbiaproto3.dump
$rm -fr cbiaproto3
$svnadmin create cbiaproto3
$svnadmin load cbiaproto3 < cbiaproto3.dump
January 17th, 2010 @ 4:15pm | njames | Categories Coding | No Comments »
Pour mettre au point un petit annotateur graphique d'image (placement de symboles graphique, différents mode de sélection de patch), j'utilise Apache Batik. Cela me permet de contenir les annotations en SVG, ce qui est bien pratique, autant pour la gestion en interne des annotations (e.g. les layers sont des groupes de symboles graphiques dont la propriétés SVG visibility change), un arbre XML c'est la panacée. J'avais déjà travaillé un peu là dessus, mais c'était codé à la va-vite et uniquement pour afficher les annotations LabelMe:

C'est ainsi que je me suis frotté aux obscurs rouages du pipeline graphique de Batik... et un petit problème que je viens de résoudre est celui-ci: Si un objet JSVGCanvas est dans le viewport d'un JScrollPane, comment positionner une texture de rendu sur le background du JSVGCanvas (i.e. positionner un background particulier sur l'objet qui est dans le viewport du JScrollPane ! ). Il faut faire attention à ce que fait la méthode de rendu paint(...) de JSVGCanvas. En fait, cette méthode paint(...) est héritée de JComponent:

Un objet JSVGCanvas est un JComponent qui contient une liste de CanvasGraphicsNode. Positionnner un background sur JSVGCanvas, revient à positionner un background sur un JComponent, et la méthode paintComponent(Graphics g) est redéfinie dans la classe JGVTComponent, c'est donc dans cette classe que l'on doit faire quelque chose. Il n'y a rien de prévu pour cela dans Batik, la solution que j'ai mise en place est de modifier les sources, ça se fait facilement en modifiant la classe org.apache.batik.swing.gvt.JGVTComponent:
private TexturePaint texpaint;
 
public void setTexturePaint(TexturePaint texpaint){
this.texpaint = texpaint;
}
 
 
/**
* Paints this component.
*/
public void paintComponent(Graphics g) {
   super.paintComponent(g);
 
   Graphics2D g2d = (Graphics2D)g;
 
   Rectangle visRect = getRenderRect();
   g2d.setComposite(AlphaComposite.SrcOver);
 
   //les deux lignes suivantes sont commentées.
   //g2d.setPaint(getBackground());
   //g2d.fillRect(visRect.x, visRect.y, visRect.width, visRect.height);
 
   //et on ajoute les deux lignes suivantes:
   g2d.setPaint(texpaint);
   g2d.fillRect(0, 0, getWidth(), getHeight());
 
   if (image != null) {
       if (paintingTransform != null) {
         g2d.transform(paintingTransform);
            }
            g2d.drawRenderedImage(image, null);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                 RenderingHints.VALUE_ANTIALIAS_OFF);
            Iterator it = overlays.iterator();
            while (it.hasNext()) {
                ((Overlay)it.next()).paint(g);
            }
        }
    }
Rien de bien sorcier: on ajoute de quoi positionner un TexturePaint, et on modifie un brin paintComponent(Graphics g) pour texturer le panel plutôt que peindre avec la couleur de background.

that's it !

Quelques notes à propos de Apache Batik
  • le format PGM n'est pas supporté dans un SVG Batik.
August 11th, 2009 @ 9:13pm | njames | Categories Coding | No Comments »
Les TreeTable combinent les fonctionnalités des composants/renderers pour des données arborescentes et des données tabulaires.

http://doc.trolltech.com/4.3/images/simpletreemodel-example.png

Il en existe de très bon en Qt, comme sur l'image précédente, mais en Java, il n'existe pas de tel composant standard en swing/awt, et c'est bien dommage car il y a une forte demande pour de tels widgets.
Un billet sur le weblog Heresylabs parle du problème également.
Ainsi, il y a quelques projets tiers qui apportent ce type de composant graphique, comme:
  • pour commencer, on trouve ce (vieux) tutoriel pour créer des TreeTable avec Swing.
  • SwingLabs, SwingX - JXTreeTable: fonctionne bien mais souffre de bugs graphique (de gros bugs graphique apparaissent lors d'un changement de look'n'feel, par exemple en passant en look'n'feel GTK+... malheureusement pour moi, j'utilise uniquement ce look'n'feel...). Cependant, les classes sont simples à utiliser.
  • JEdit: semble intéressant mais le widget treetable est intégré dans le projet... pas de library pour la gui et donc difficilement utilisable dans un projet.
  • JIDE Software: semble intéressant mais très compliqué apparemment... et propriétaire et payant.
  • Netbeans: le plus stable, pas de bug graphique, et très simple à utiliser, c'est du bonheur en barre. Voir ça et ça et ça.
    Pour afficher un fichier XML, via DOM, dans un treetable NetBeans, c'est relativement simple.
    Récupérer la library org-netbeans-swing-outline.jar dans NetBeans (dans ~netbeans/platformN/modules) et l'ajouter au classpath d'un projet Java.
    Petit exemple en 4 fichiers: XmlTreeNode.java, XmlTreeModel.java, XmlRowModel.java, Test.java.
    Résultat:

    netbeans-treetable2

    Cela reste bien moins joli que du Qt mais je n'ai pas non plus écrit de renderer pour améliorer l'affichage sur ce bout de code.
  • Une autre solution pourrait être d'utiliser QtJambi, mais le binding Qt pour Java n'est plus maintenu par Trolltech, donc c'est une idée à oublier...
August 9th, 2009 @ 5:23pm | njames | Categories Coding | No Comments »
Pour un projet d'annotation de document vidéo (annotation sémantique manuelle), j'investigue du côté des différentes API existantes pour intégrer un player vidéo dans une application Java.

JMF, Java Media Framework
Bien sûr, il y a JMF, Java Media Framework, mais cette API commence à vieillir, et de plus elle cafouille un peu avec Java1.5, il est recommander d'utiliser JMF avec Java1.4 (voir ici ou ), ce qui est potentiellement embêtant.

Screenshot-JMStudio

Quelques liens sur JMF:
Les alternatives
  • des bindings avec ffmpeg, via JNI ou JNA, comme FMJ, Freedom for Media in Java, qui se veut un clone de JMF en quelque sorte. Pour le binding avec ffmpeg, JNA est utilisé.
    Cette API n'embarque pas de version de ffmpeg, et s'appuie sur la version de ffmpeg disponible sur la machine locale.

    Screenshot-FMJ Studio Screenshot-Registry Editor-1

  • jffmpeg: un plugin pour JMF, de nombreux codecs présent dans ffmpeg sont recodés en Java, voir cette page.
    Pour ajouter le plugin jffmpeg au JMFRegistry voir cette page (contrairement, à FOBS, il n'y a pas de détection des codecs, il faut enregistrer les Mime Types, Plugins et Codecs à la main dans le JMFRegistry).
  • FOBS, qui est un wrapper objet au dessus de ffmpeg, et apporte des API C++, Java et python au dessus de ce wrapper. Côté Java, c'est via un plugin JMF, FOBS4JMF, qu'opère l'API Java de FOBS, (vu les performances décevantes au décodage d'un XVID, il faut utiliser une JVM1.4 pour JMF, c'est plus fluide mais pas encore assez cependant, ce n'est pas très beau, ffplay fait beaucoup mieux).
    FOBS embarque sa propre version de ffmpeg et de jmf.
    Pour le tester, après son installation, aller dans ~FOBS/dist/jmf, et utiliser le JMStudio de JMF java -cp fobs4jmf.jar:jmf.jar JMStudio.
  • JVLC.
  • gstreamer-java: un binding-java pour gstreamer, fonctionne vraiment très bien! Le binding est basé sur JNA.
    Cependant le projet est encore assez jeune, et il y a de nombreuses fonctionnalités que j'aimerais y trouver qui ne sont pas disponibles: choisir le flux dans le container (vidéo et/ou audio), pause puis passer à la frame suivante, lecture accélérée -1 ou +1 etc..

    Screenshot-VideoPlayer-1

  • Xuggle-Xuggler: binding sur ffmpeg.
  • Quelques liens à propos de la nouvelle API de sun, la Java Media Components API:
July 29th, 2009 @ 11:05pm | njames | Categories Coding | No Comments »
Difficile de se passer d'un gestionnaire de to do lists, pour ma part en tous les cas, que ce soit qtodo (celui que j'utilise depuis un bon moment maintenant), ou via evolution, RTM, Lightning, GTodo, KOrganizer, ReminderFox, etc.

L'inconvénient de tous ces softwares est que la liste des tâches n'est disponible qu'en local (sauf pour evolution ou RTM).
Pendant quelques temps j'ai utilisé une page wiki de mon dokuwiki comme to-do list, mais il y a plus pratique.

C'est ainsi que j'avais commencé un petit projet surnommé SuperDo il y a 1 an, utilisant Google Calendar pour stocker les items des to do lists.
Il est possible de créer des hidden calendars dans Google Calendar, ce qui est très pratique pour un calendrier nommé par exemple Superdo-Tasklist et d'y stocker les items d'une to do list.

Le projet est en sommeil depuis presque 1 an, une bonne partie du code est écrit, il ne manque plus grand chose pour avoir quelque chose de pleinement fonctionnel, je l'ai ressorti aujourd'hui pour voir si je pouvais l'utiliser avec Google Tasks qui est la nouvelle application Google pour la gestion de tâches, via gmail avec une sorte de widget ou via google calendar avec un calendrier nommé Tasks (attention la synchro gmail / google calendar demande quelques secondes): et bien non, raté, il semblerait que le calendar nommé Tasks ne soit pas un Google Calendar classique, et impossible de récupérer ce calendar via la Google API, dommage...
D'autant plus qu'il n'y a pas d'URL disponible pour un ical sur le calendrier Tasks (ce qu'on a pour les autres calendrier de Google Calendar), donc on ne peut pas visualiser non plus ce calendrier via un client sur sa machine genre sunbird ou assimilé...
May 14th, 2009 @ 8:53pm | njames | Categories Coding | No Comments »
Il est dommage qu'il n'y ai pas de startpage par défaut dans DokuWiki équivalente à ce qu'on peut trouver dans d'autre wiki comme MediaWiki ou PmWiki. Ne serait-ce qu'une page d'index un peu plus sexy que celui par défaut: /wiki/doku.php?do=index.
Il y a cependant des plugins pour créer des index comme les plugins IndexMenu, IndexMenu2 ou PageIndex, mais aucuns d'entre eux ne me convient vraiment.

Du coup, j'ai écris mon premier plugin DokuWiki en me basant sur le plugin PageIndex. C'est pour le moment très sommaire mais cela va me permettre de faire une page d'accueil un peu plus élaborée qu'une liste, une longue colonne des pages du wiki:
<?php
/**
 * Plugin start page
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Nicolas James <nicolas.james@gmail.com>
 */
 
if(!defined('DOKU_INC')) {
	define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
}
if(!defined('DOKU_PLUGIN')) {
	define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
}
 
require_once(DOKU_PLUGIN.'syntax.php');
require_once(DOKU_INC.'inc/search.php');
 
 
function search_list_index2(&$data,$base,$file,$type,$lvl,$opts){
	global $ID;
	//we do nothing with directories
	if($type == 'd') return false;
	if(preg_match('#\.txt$#',$file)){
		$id = pathID($file);
 
		//check ACL if we don't want to display private pages in the list.
		/*
		if(auth_quickaclcheck($id) < AUTH_READ){
			return false;
		}
		*/
 
		if($opts['ns'].":$id" <> $ID) {
			$data[] = array( 
				'id'    => $opts['ns'].":$id",
				'type'  => $type,
				'level' => $lvl );
		}
	}
	return false;
}
 
function html_buildlist2($data,$class,$func,$lifunc='html_li_default'){
     $level = 0;
     $opens = 0;
     $ret   = '';
 
     foreach ($data as $item){
	/*
	I don't know why, but for pages with the empty namespace, the ID written like this :mpeg7 rather than mpeg7 (no colon), changes the
	behavior of the function auth_quickaclcheck(...). The colon is not allowed for the empty namespace.
	*/
	if(substr($item['id'], 0, 1) != ':'){
		$testacl = auth_quickaclcheck($item['id']);
	} else {
		$testacl = auth_quickaclcheck(substr($item['id'], 1));
	}
 
       if( $item['level'] > $level ){
         //open new list
         for($i=0; $i<($item['level'] - $level); $i++){
           if ($i) $ret .= "<li class=\"clear\">\n";
           $ret .= "\n<ul class=\"$class\">\n";
         }
       }elseif( $item['level'] < $level ){
         //close last item
         $ret .= "</li>\n";
         for ($i=0; $i<($level - $item['level']); $i++){
           //close higher lists
           $ret .= "</ul>\n</li>\n";
         }
       }else{
         //close last item
         $ret .= "</li>\n";
       }
 
       //remember current level
       $level = $item['level'];
 
       //print item
       $ret .= call_user_func($lifunc,$item);
       $ret .= '<div class="li">';
 
       $ret .= call_user_func($func,$item);
 
	if($testacl < AUTH_READ){
		$ret .= '&nbsp;<img src=\''.DOKU_BASE.'lib/plugins/startpage/private_icon4.png\'/>';
	}
 
       $ret .= '</div>';
     }
 
     //close remaining items and lists
     for ($i=0; $i < $level; $i++){
       $ret .= "</li></ul>\n";
     }
 
     return $ret;
  }
 
 
 
 
 
/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_startpage extends DokuWiki_Syntax_Plugin {
 
    /**
     * return some info
     */
    function getInfo(){
        return array(
            'author' => 'Nicolas James',
            'email'  => 'nicolas.james@gmail.com',
            'date'   => '2009-02-19',
            'name'   => 'start page',
            'desc'   => '',
            'url'    => '',
        );
    }
 
    /**
     * What kind of syntax are we?
     */
    function getType(){
        return 'substition';
    }
 
    // Just before build in links
    function getSort(){ return 299; }
 
    /**
     * What about paragraphs?
     */
    function getPType(){
        return 'block';
    }
 
    function connectTo($mode) {
       $this->Lexer->addSpecialPattern('~~STARTPAGE[^~]*~~',$mode,'plugin_startpage');
       //$this->Lexer->addSpecialPattern('~~STARTPAGE~~',$mode,'plugin_startpage');
    }
 
 
    /**
     * Handle the match
     */
    function handle($match, $state, $pos, &$handler){
		$match = preg_replace("%~~STARTPAGE(=(.*))?~~%", "\\2", $match);
		//echo "\n\t<!-- syntax_plugin_pageindex.handle() found >> $match << -->\n";
        return $match;
    }
 
    /**
     * Create output
     */
    function render($mode, &$renderer, $data) {
        if($mode == 'xhtml'){
            $text=$this->_startpage($renderer, $data);
            $renderer->doc .= $text;
            return true;
        }
        return false;
    }
 
	function _startpage(&$renderer, $data) {
	global $conf;
 
	$dir = $conf['datadir']. DIRECTORY_SEPARATOR .str_replace(':',DIRECTORY_SEPARATOR,'');
 
	$pages = array();
 
	search($pages, $dir, 'search_list_index2', array('ns' => ''));
 
	$renderer->doc .= html_buildlist2($pages,'idx','html_list_index','html_li_index');
 
	$opts1 = array();
	$opts1['ns'] = '';
	$namespaces = array();
	search($namespaces,$conf['datadir'],'search_namespaces',$opts1);
 
	foreach ($namespaces as $item){
 
		 $curns = $item['id'];
		 $curns = str_replace(DIRECTORY_SEPARATOR,':',$curns);
 
		 $dir = $conf['datadir']. DIRECTORY_SEPARATOR .str_replace(':',DIRECTORY_SEPARATOR,$curns);
 
		 $renderer->doc .= '<h1>'.$curns.'</h1>';
 
		 $pages = array();
 
		 search($pages, $dir, 'search_list_index2', array('ns' => $curns));
 
		 $renderer->doc .= html_buildlist2($pages,'idx','html_list_index','html_li_index');
 
	}
 
	} // _startpage()
} // syntax_plugin_startpage
October 6th, 2008 @ 9:59pm | njames | Categories Coding | No Comments »
Sur Ubuntu Hardy Heron la library live555 streaming media est présente dans les dépôts dans une version inférieure à celle requise pour une compilation de VLC 0.9.3 avec support du protocole RTSP:
  1. Récupérer les sources de live555 streaming media, et compiler la library, ce n'est pas un configure/make/make install classique, mais un script ./genMakefiles.sh linux, puis make. Pas de make install, donc rm -rf /usr/lib/live ; cp -r live /usr/lib.
  2. compiler VLC avec --with-live555-tree=/usr/lib/live/ dans les options du script configure.
September 2nd, 2008 @ 10:51am | njames | Categories Coding | No Comments »
Löve est un moteur de jeu 2D, le projet veut apporter du développement rapide de jeu et du prototypage en Lua. Installer les paquets suivants:
  • devIL.
  • libphysfs.
  • libboost.
March 25th, 2008 @ 2:48pm | njames | Categories Coding | No Comments »
Il peut être pratique parfois de capter les événements Java directement (i.e. capter tous les événements qui tombe dans la AWTQueue), sans passer par un Listener sur un Component. Dans le cadre d'un petit projet Java que je voudrais utiliser pour mes cours/td/tp sur les objets graphique complexes en Swing (JList, JTable, JTree etc.), j'implémente un sélecteur de fichier à la blender, dans un premier temps avec une JList (le nommé projet FSE, pour FileSystemExplorer), dans un second temps avec une JTable (le projet nommé FSE2) permettant alors avec un affichage multi-colonne. Ces deux versions doivent être les plus semblables possible conceptuellement (et graphiquement). Dans la version JTable, j'ajoute un GlobalKeyListener pour écouter les keystrokes, un '+' ajoute une colonne au modèle, un '-' soustrait une colonne au modèle. Screenshot-FSExplorer2MainTestWithDynCol Pour faire ce GlobalKeyListener:
  • la classe GlobalKeyListener: FSE2MainTest est le lanceur de l'application (ce lanceur instancie un FSExplorer2, i.e. la version JTable, dans une fenêtre Java), c'est lui également qui crée une instance de GlobalKeyListener.
    package net.trevize.gui.FSExplorer.test;
     
    import java.awt.AWTEvent;
    import java.awt.BorderLayout;
    import java.awt.event.AWTEventListener;
    import java.awt.event.KeyEvent;
    import java.io.IOException;
     
    import net.trevize.gui.FSExplorer.FSExplorer2;
     
    public class GlobalKeyListener implements AWTEventListener {
    	private FSE2MainTest mt;
     
    	public GlobalKeyListener(FSE2MainTest mt) {
    		this.mt = mt;
    	}
     
    	public void eventDispatched(AWTEvent e) {
    		switch (e.getID()) {
    			case KeyEvent.KEY_PRESSED:
    				keyPressed((KeyEvent) e);
    				break;
    			case KeyEvent.KEY_RELEASED:
    				keyReleased((KeyEvent) e);
    				break;
    			case KeyEvent.KEY_TYPED:
    				keyTyped((KeyEvent) e);
    				break;
    		}
    	}
     
    	public void keyPressed(KeyEvent e) {
    	}
     
    	public void keyReleased(KeyEvent e) {
    		if (e.getKeyChar() == '+') {
    			System.out.println("adding a column...");
    			try {
    				mt.setPath(mt.getFSExplorer2().getCurFile().getCanonicalPath());
    			} catch (IOException e1) {
    				e1.printStackTrace();
    			}
    			mt.setNumberOfColumn(mt.getNumberOfColumn() + 1);
    			FSExplorer2 fse2 = mt.createFSExplorer2();
    			mt.setFSExplorer2(fse2);
    			fse2.addFSExplorer2Listener(mt.getListener());
     
    			mt.getPanel().removeAll();
    			mt.getPanel().add(fse2, BorderLayout.CENTER);
    			mt.getPanel().paintAll(mt.getPanel().getGraphics());
    			mt.getPanel().requestFocus();
    		}
     
    		else
     
    		if (e.getKeyCode() == KeyEvent.VK_MINUS) {
    			if(mt.getNumberOfColumn() == 1){
    				return;
    			}
    			System.out.println("remove a column...");
    			try {
    				mt.setPath(mt.getFSExplorer2().getCurFile().getCanonicalPath());
    			} catch (IOException e1) {
    				e1.printStackTrace();
    			}
    			mt.setNumberOfColumn(mt.getNumberOfColumn() - 1);
    			FSExplorer2 fse2 = mt.createFSExplorer2();
    			mt.setFSExplorer2(fse2);
    			fse2.addFSExplorer2Listener(mt.getListener());
     
    			mt.getPanel().removeAll();
    			mt.getPanel().add(fse2, BorderLayout.CENTER);
    			mt.getPanel().paintAll(mt.getPanel().getGraphics());
    			mt.getPanel().requestFocus();
    		}
    	}
     
    	public void keyTyped(KeyEvent e) {
    	}
    }
  • ajouter le listener comme un AWTEventListener, ce code est dans le lanceur de l'application (FSE2MainTest).
    //adding a global key listener
    Toolkit.getDefaultToolkit().addAWTEventListener(new GlobalKeyListener(this), AWTEvent.KEY_EVENT_MASK);
March 13th, 2008 @ 5:28pm | njames | Categories Coding | No Comments »
Dernièrement dans le cadre de ma thèse je suis un train de développer un petit crawler web dédiés aux images. Certains sites web renvoient une erreur 403 si le user-agent est unknown. Ce crawler est codé en Java et utilise les bibliothèques Apache HttpClient et HTML Parser. Pour changer le user-agent, il n'y a pas de méthode dans HttpClient, il faut directement ajouter le header dans le paquet HTTP:
//change user-agent (cause of some website which reply 403 if user-agent unknown).
Header ua = new Header("User-Agent", "Mozilla");
method.setRequestHeader(ua);
Il y a un bug que je n'explique pas dans HTML Parser (ou bien n'est-ce pas un bug ? mais je ne comprends pas ce comportement): les caractères URL-encoded, par exemple %20, deviennent %2520 (double URL-encoding ???).


 Valid XHTML 1.0 Transitional Valid CSS! WordPress