Precache script for Zenphoto

To make my pictures and photo’s available on the web I use Zenphoto. An ideal web-album solution that simply takes a directory and makes this available on the web. By means of a symlink that points from /srv/www/htdocs/zenphoto/albums to my pictures folder, Zenphoto displays all my pictures (note that you have to enable apache to follow symlinks for this). This directory which resides on my home server, is also mounted by nfs (network file system) on /home/_user_/pictures. Now, whenever I add a picture to this folder it will also be available through Zenphoto. The only problem I had was that my homeserver is not that fast and when I want to browse the pictures quickly, it takes ages to load or to generate a cached version. Zenphoto has this feature of precaching, but precaching your photos through the browser involves some extra manual labour which I absolutely despise ;). So I decided to write a little precache script:

#!/usr/bin/python

import os
import sys
import Image
import time

preCachSize = 25
albumfiles = []
result = []
zenphoto = '/srv/www/htdocs/zenphoto/'
cache = zenphoto + 'cache/'
albums = zenphoto + 'albums/'
small = '_100_thumb.jpg'
big = '_595.jpg'
small_size = 100
big_size = 595

def preCache(original, newSize, append):
	cachedFile = getCachedFileName(original, append)
	dirname = os.path.dirname(cachedFile)
	if not os.path.exists(dirname):
		os.makedirs(dirname)
	command = 'convert -auto-orient "' + original + '" -resize "%dx%d" "' + cachedFile + '"'
	command = command % (newSize, newSize)
	os.system(command)
	print time.asctime() + ' ' + cachedFile
	
def getCachedFileName(original, append):
		cachedFile = original.replace(albums, cache, 1).replace('.jpg', '', 1).replace('.JPG', '', 1) + append
		return cachedFile
	
# Add photo's to the list if no small cache exists
for root, subFolders, files in os.walk(albums):
    for file in files:
		albumfile = os.path.join(root, file)
		if not os.path.exists(getCachedFileName(albumfile, small)):
			albumfiles.append(albumfile)
        
# Only precache jpg
for albumfile in albumfiles:
	if (albumfile.find('jpg')"=0 or albumfile.find('JPG')"=0):
		result.append(albumfile)

# Just a few		
for uncachedFile in result[0:preCachSize]:
	max_side = max(Image.open(uncachedFile).size)
	if max_side "= big_size:
		preCache(uncachedFile, big_size, big)
	if max_side "= small_size:
		preCache(uncachedFile, small_size, small)

os.system('chown -R wwwrun:www ' + cache)

It is a combination of Python’s os module and the fabulous imagemagick suite. This will precache 25 pictures (only jpg) for which no small thumbnail exists and make a thumbnail and a smaller image for it (all my pictures are shot with the same camera and therefore have the same size). By running the following bash script through a daily cron job, I also have a rudimentary log of the precached images:

#!/bin/bash
/root/scripts/preCache.py "" /var/log/preCacheZenphoto.log

Chat application in Egroupware

As groupware server for my contacts, notes, email and calender I use Egroupware. Me and my wife are happy users of this system, I can synchronize my agenda, contacts, todo and sticky notes with my pocket pc using the Funambol ppc plugin. On the desktop we both use Thunderbird. The only thing it really lacks is a chat application. On the forums you can find several people asking for it, e.g. here, and here. I posted my solution with PHPFreechat on a forum. Hans-Jürgen Tappe made this into a nice external patch that you can download from http://www.egroupware.org/egroupware/index.php?menuaction=tracker.tracker_ui.edit&tr_id=2131&no_popup=1.

The problem I have with this solution that the chat opens in a new window and this window handles the chat separately. So how can you inform someone that you want to chat to him or her when she has not opened the chat window? You then have to send the other person an email to inform him that you want him to join the chat.

In order to have a really integrated solution I changed the code of my egroupware system so that the chat loads in an embedded div inside the website. The disadvantage of this solution is that it will break your update schedule because with every update you have to redo it. To me that is the price I will pay to get what I want. (Another disadvantage counts only from a technical viewpoint, it is really a bit of a dirty hack). Still reading? Lets go!

1 Configure Apache
In order to let all Egroupware applications load the same instance of the chat we need to alter the configuration a bit. Apache must be allowed to follow symlinks (note that this is considered a security breach). Edit the file httpd.conf and add the option FollowSymLinks to the Directory section:

"Directory /"
    Options FollowSymLinks
    AllowOverride None
    Order deny,allow
    Deny from all
"/Directory"

2 Download and install chat application
I have chosen phpfreechat as it provides a decent and pretty mature chat application. Any other might do as well, e.g. Chatty is another option and a little googling on the web will provide you with plenty alternatives.

Go to the egroupware directory on your server and issue the command:

svn co https://phpfreechat.svn.sourceforge.net/svnroot/phpfreechat/tags/1.3 phpfreechat

You can also download the zip file from the website and unzip it to your egroupware directory.

Grant write access to egroupware/phpfreechat/data/ for the web server (I am on OpenSuse):

chown -R wwwrun:www egroupware/phpfreechat/data/

Now if you point a browser to http://–your server–/egroupware/phpfreechat you will see a chat window indicating that your chat is functional. Of course we don’t want people into our chat room who are not logged in, so make some files unreadable:

chmod 000 egroupware/phpfreechat/index.php;
chmod -R 000 egroupware/phpfreechat/demo/;

3 Print the chat
In order to print the chat I wrote some PHP code that prints the chat. Adjust the commented parts and save it as chat.php in the Egroupware directory.

"?php
/** Prints the chat to the screen inside the 'chatter' div */
require_once '--full path to phpfreechat.class.php --'; // On my server this is /srv/www/htdocs/egroupware/phpfreechat/src/phpfreechat.class.php';
$params = array();
$params["serverid"] = md5("--your domain name--"); // e.g. example.com/egroupware
$params["title"] = "Chat to online users";
$params["height"] = "120px";
$params["showsmileys"] = false;
$params["displaytabclosebutton"] = false;
$params["theme"] = "msn";
$params["display_ping"] = false;
$params["channels"] = array("Public room");
$params["nick"] = $GLOBALS[egw_info_cache][user][account_firstname];
$params['admins'] = array('--admin account--'  =" '--admin password--'); // Maybe a good idea to make this the same as your egroupware admin account?
$params["max_msg"] = 10;
$params["shownotice"] = 0;
$params["display_pfc_logo"] = false;
$chat = new phpFreeChat($params);
if(($GLOBALS[egw_info_cache][user][account_primary_group] == -1) &&
	(!strpos(getcwd(), 'admin')) &&
	(!strpos(getcwd(), 'home')) &&
	(!strpos(getcwd(), 'wiki'))){
	print(""div id='chatter' ondblclick='reFloatChatter()' style='position:fixed; bottom 160px; right:10px;  width:500px; height:100px; z-index:50;'");
	$chat-"printChat();
	print(""/div"");	
}
?"

This script prints a div named ‘chatter‘ with a fully customized version of PHPFreechat, except when looking at the admin, home and wiki pages.

4 Create Symlinks
As I told earlier we need some symlinks. Create a symlink to the data directory of phpfreechat from the egroupware main directory:

cd /srv/www/htdocs/egroupware
ln -s phpfreechat/data data;

We also need symlinks to phpfreechat from applications for which the working directory is not the egroupware main directory. E.g. the infolog application, check in the url of a application if this is the case and create the links:

cd /srv/www/htdocs/egroupware/infolog
ln -s ../chat.php chat.php;
ln -s ../phpfreechat phpfreechat
ln -s ../phpfreechat/data data

5 Print the ‘chatter’ div
In order to move the chat around we need some javasript:

"script language="JavaScript" type="text/javascript""
var chatterOnTop = true;

function reFloatChatter() {
   var chatterDiv = document.getElementById('chatter');
   var currentPosition = parseInt(chatterDiv.style.top);
   if(chatterOnTop){
        document.getElementById('chatter').style.top='';
        document.getElementById('chatter').style.bottom='160px';
        chatterOnTop = false;
    } else {
        document.getElementById('chatter').style.top='10px';
        document.getElementById('chatter').style.bottom='';
        chatterOnTop = true;
    }
}

window.onload=function(){
    reFloatChatter();
}
"/script"

This needs to be printed on every website of every page, so I put this inside /srv/www/htdocs/egroupware/phpgwapi/templates/default/footer.tpl right before the closing body tag.

6 Glue it together
What is left to do is create a call to the chat.php script. What we need is a php script in egroupware that is always printed. I selected /srv/www/htdocs/egroupware/phpgwapi/templates/idots/class.idots_framework.inc.php for this purpose. Currently I am using version 1.8.004. On line 685 inside the footer() function, while loading the previous mentioned footer.tpl I put the statement:

require_once '/srv/www/htdocs/egroupware/chat.php';

So that that the modified function looks like this:

	/**
	* Returns the html from the closing div of the main application area to the closing html-tag
	*
	* @return string html or null if no footer needed/wanted
	*/
	function footer()
	{
		static $footer_done;
		if ($footer_done++) return;	// prevent multiple footers, not sure we still need this (RalfBecker)

		if (!isset($GLOBALS['egw_info']['flags']['nofooter']) || !$GLOBALS['egw_info']['flags']['nofooter'])
		{
			// get the (depricated) application footer
			$content = $this-"_get_app_footer();

			// run the hook navbar_end
			// ToDo: change to return the content
			ob_start();
			$GLOBALS['egw']-"hooks-"process('navbar_end');
			$content .= ob_get_contents();
			ob_end_clean();

			// eg. javascript, which need to be at the end of the page
			if ($GLOBALS['egw_info']['flags']['need_footer'])
			{
				$content .= $GLOBALS['egw_info']['flags']['need_footer'];
			}

			// do the template sets footer, former parse_navbar_end function
			// this closes the application area AND renders the closing body- and html-tag
			if (self::$navbar_done)
			{
				$this-"tpl-"set_file(array('footer' =" 'footer.tpl'));
				$this-"tpl-"set_var($this-"_get_footer());
				$content .= $this-"tpl-"fp('out','footer');
require_once '/srv/www/htdocs/egroupware/chat.php';
			}
			elseif (!isset($GLOBALS['egw_info']['flags']['noheader']) || !$GLOBALS['egw_info']['flags']['noheader'])
			{
				$content .= ""/body"\n"/html"\n";	// close body and html tag, eg. for popups
			}
			return $content;
		}
	}

The result is a floating box that displays a list of all online users. You can chat to these and if the box gets in your way dubble click it and it will move to the top of the page. It provides all functionality of phpfreechat, so if you want to configure it, turn to the website and read the fabulous documentation 🙂 The floating box is lined out as fits me well, but if you want other margins (e.g. the smiley box is out of range when the chat is at the bottom) you can change this in chat.php and the provided javascript.

Oh, and before I forget, here is a screenshot:
Egroupware with phpfreechat embedded

Switch over all values of a Java enum

Suppose you want to switch over all values of an enum, how can you be sure you covered all cases? For example take this class with an inner enum type:

public class SwitchEnum {
   private enum values {VALUE1, VALUE2, VALUE3}
}

Now you want to switch over the elements of the enum, like this:

public class SwitchEnum {

 private void switchOverEnum(values value) {
  switch (value) {
  case VALUE1:
   // Do value 1 stuff
   break;
  case VALUE2:
   // Do value 2 stuff
   break;
  case VALUE3:
   // Do value 3 stuff
  }
 }

 private enum values {
  VALUE1, VALUE2, VALUE3
 }
}

In this academical example it is easy to see that you covered all possible values of the enum. But what if these values are in separate files? And don’t forget about the fallthrough way of switching in Java. Many devolopers consider this error-prone. I myself like this behavior but lets not start a flame war here. Imagine you are working with a couple of developers and another developer decides to create an extra value for the enum:

public class SwitchEnum {

 private void switchOverEnum(values value) {
  switch (value) {
  case VALUE1:
   // Do value 1 stuff
   break;
  case VALUE2:
   // Do value 2 stuff
   break;
  case VALUE3:
   // Do value 3 stuff
  }
 }

 private enum values {
  VALUE1, VALUE2, VALUE3, VALUE4
 }
}

Bummer, your switch statements did not cover this one and nothing will happen when the value parameter takes VALUE4. Yesterday a collegue of mine showed me a way to handle this.
First you add an Interface AbstractSwitch:

 private interface AbstractSwitch {

  public void caseValue1();

  public void caseValue2();

  public void caseValue3();
 }

Then you add an abstract method to your enum:

public abstract void enumSwitch(final AbstractSwitch switcher);

This will immediately lead to compile errors because the enum types have to override this method. When you use an IDE like eclipse you can simply type ctrl-1 an it will give you the option add unimplemented methods. After giving each enum value the right method our enum looks like this:

 private enum values {
  VALUE1 {
   @Override
   public void enumSwitch(AbstractSwitch switcher) {
    switcher.caseValue1();
   }
  },

  VALUE2 {
   @Override
   public void enumSwitch(AbstractSwitch switcher) {
    switcher.caseValue2();
   }
  },

  VALUE3 {
   @Override
   public void enumSwitch(AbstractSwitch switcher) {
    switcher.caseValue3();
   }
  };

  public abstract void enumSwitch(final AbstractSwitch switcher);
 }

Instead of switching over value, we call the enumSwitch method with an anonymous implementation of the AbstractSwitch interface:

private void switchOverEnum(values value) {
  value.enumSwitch(new AbstractSwitch() {

   @Override
   public void caseValue3() {
    // Do value 3 stuff
   }

   @Override
   public void caseValue2() {
    // Do value 2 stuff
   }

   @Override
   public void caseValue1() {
    // Do value 1 stuff
   }
  });
 }

Again this is easy with an IDE like eclipse because after typing new AbstractSwitch (maybe a ctrl-space will force this) it will provide a dropdown menu, and when you choose the right interface, the IDE will give you all methods as auto generated stubs.

What happens when you mysterious friend tries to add VALUE4? He will be forced to implement the enumSwitch method which will make him think about the AbstractSwitch interface and let him add a method to this one. But this will immediately lead to compile errors in all implementations of the AbstractSwitch interface forcing him to get in touch with you and find out what you want to do when the value parameter takes VALUE4. The complete code now looks like this:

 

public class SwitchEnum {

 private void switchOverEnum(values value) {
  value.enumSwitch(new AbstractSwitch() {

   @Override
   public void caseValue3() {
    // Do value 3 stuff
   }

   @Override
   public void caseValue2() {
    // Do value 2 stuff
   }

   @Override
   public void caseValue1() {
    // Do value 1 stuff
   }
  });
 }

 private enum values {
  VALUE1 {
   @Override
   public void enumSwitch(AbstractSwitch switcher) {
    switcher.caseValue1();
   }
  },

  VALUE2 {
   @Override
   public void enumSwitch(AbstractSwitch switcher) {
    switcher.caseValue2();
   }
  },

  VALUE3 {
   @Override
   public void enumSwitch(AbstractSwitch switcher) {
    switcher.caseValue3();
   }
  };

  public abstract void enumSwitch(final AbstractSwitch switcher);
 }

 private interface AbstractSwitch {

  public void caseValue1();

  public void caseValue2();

  public void caseValue3();
 }
}

My .conkyrc

Here is my conkyrc. The output looks like this:
Conky
It is a modified version of conky-colors. As you can see, I cheat here and there a little by printing a couple of parameters of which I know they don’t change. E.g. my internal ip is static (I set my router up that way).

# Use Xft?
use_xft yes
xftfont Terminus:size=11
xftalpha 0.8
text_buffer_size 2048

# Update interval in seconds
update_interval 1

# This is the number of times Conky will update before quitting.
# Set to zero to run forever.
total_run_times 0

# Create own window instead of using desktop (required in nautilus)
own_window yes
own_window_transparent yes
own_window_type override
#own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager

# Use double buffering (reduces flicker, may not work for everyone)
double_buffer yes

# Minimum size of text area
minimum_size 250
maximum_width 250

# Draw shades?
draw_shades no

# Draw outlines?
draw_outline no

# Draw borders around text
draw_borders no

# Stippled borders?
stippled_borders 0

# border margins
border_inner_margin 5

# border width
border_width 1

# Default colors and also border colors
default_color white
#default_shade_color black
#default_outline_color black
own_window_colour black

# Text alignment, other possible values are commented
#alignment top_left
alignment top_right
#alignment bottom_left
#alignment bottom_right

# Gap between borders of screen and text
# same thing as passing -x at command line
gap_x 35
gap_y 50

# Subtract file system buffers from used memory?
no_buffers yes

# set to yes if you want all text to be in uppercase
uppercase no

# number of cpu samples to average
# set to 1 to disable averaging
cpu_avg_samples 2

# number of net samples to average
# set to 1 to disable averaging
net_avg_samples 2

# Force UTF8? note that UTF8 support required XFT
override_utf8_locale yes

# Add spaces to keep things from moving about?  This only affects certain objects.
use_spacer none

TEXT
SYSTEM ${hr 2}
${voffset 2}${font OpenLogos:size=16}B${font}   Kernel:  ${alignr}${kernel}
${font StyleBats:size=16}q${font}   Uptime: ${alignr}${uptime}

DATE ${hr 2}
${alignc 40}${font Arial Black:size=22}${time %H:%M:%S}${font}
${alignc}${time %d - %m - %Y}

CPU ${hr 2}
${font StyleBats:size=16}A${font}   CPU: ${cpu cpu1}% ${alignr}${cpubar cpu1 8,60}
${font weather:size=16}y${font}   CPU Temp:${alignr} ${hwmon 0 temp 1}.0 oC
${font StyleBats:size=16}O${font}   CPU Fan:${alignr}${hwmon 0 fan 1} ${font}RPM

GPU ${hr 2}
${font StyleBats:size=16}E${font}${font}    nVidia Geforce${alignr}7300GT
${font weather:size=16}y${font}    GPU Temp:${alignr}${execi 30 ~/.scripts/gputemp.sh}.0${font} °C

MEM ${hr 2}
${font StyleBats:size=16}g${font}   RAM: $memperc% ${alignr}${membar 8,60}
${font StyleBats:size=16}j${font}   SWAP: $swapperc% ${alignr}${swapbar 8,60}

HD ${hr 2}
${voffset 4}${font Pie charts for maps:size=14}7${font}   ${voffset -5}Root:
${voffset 4}${fs_free /}/${fs_size /} ${alignr}${fs_bar 8,60 /}
${voffset 4}${font Pie charts for maps:size=14}7${font}   ${voffset -5}/var:
${voffset 4}${fs_free /var}/${fs_size /var} ${alignr}${fs_bar 8,60 /var}
${font Pie charts for maps:size=14}7${font}   ${voffset -5}Documents:
${voffset 4}${fs_free /home/rijk/Documents}/${fs_size /home//Documents} ${alignr}${fs_bar 8,60 /home//Documents}
${font Pie charts for maps:size=14}7${font}   ${voffset -5}Backup:
${voffset 4}${fs_free /home/rijk/Backup}/${fs_size /home//Backup} ${alignr}${fs_bar 8,60 /home//Backup}
${font Pie charts for maps:size=14}7${font}   ${voffset -5}Films:
${voffset 4}${fs_free /media/Films}/${fs_size /media/Films} ${alignr}${fs_bar 8,60 /media/Films}

NETWORK ${hr 2}

#${if_existing /proc/net/route wlan0}
#${voffset -6}${font PizzaDude Bullets:size=14}O${font}   Up: ${upspeed wlan0} kb/s ${alignr}${upspeedgraph wlan0 8,60 F57900 FCAF3E}
#${voffset 4}${font PizzaDude Bullets:size=14}U${font}   Down: ${downspeed wlan0} kb/s ${alignr}${downspeedgraph wlan0 8,60 F57900 FCAF3E}
#${voffset 4}${font PizzaDude Bullets:size=14}N${font}   Upload: ${alignr}${totalup wlan0}
#${voffset 4}${font PizzaDude Bullets:size=14}T${font}   Download: ${alignr}${totaldown wlan0}
#${voffset 4}${font PizzaDude Bullets:size=14}Z${font}   Signal: ${wireless_link_qual wlan0}% ${alignr}${wireless_link_bar 8,60 wlan0}
#${voffset 4}${font PizzaDude Bullets:size=14}a${font}   Local Ip: ${alignr}${addr wlan0}
#${voffset 4}${font PizzaDude Bullets:size=14}b${font}   Public Ip: ${alignr}${execi 1 ~/.scripts/ip.sh}
#${else}
#${if_existing /proc/net/route eth0}
${voffset -6}${font PizzaDude Bullets:size=14}O${font}   Up: ${upspeed eth0} kb/s ${alignr}${upspeedgraph eth0 8,60 F57900 FCAF3E}
${voffset 4}${font PizzaDude Bullets:size=14}U${font}   Down: ${downspeed eth0} kb/s ${alignr}${downspeedgraph eth0 8,60 F57900 FCAF3E}
${voffset 4}${font PizzaDude Bullets:size=14}N${font}   Upload: ${alignr}${totalup eth0}
${voffset 4}${font PizzaDude Bullets:size=14}T${font}   Download: ${alignr}${totaldown eth0}
#${voffset 4}${font PizzaDude Bullets:size=14}a${font}   Local Ip: ${alignr}${addr eth0}
${voffset 4}${font PizzaDude Bullets:size=14}a${font}   Local Ip: ${alignr}192.168.2.24
${voffset 4}${font PizzaDude Bullets:size=14}b${font}   Public Ip: ${alignr}${execi 3600 ~/.scripts/ip.sh}
#${endif}
#{else}
#${font PizzaDude Bullets:size=14}4${font}   Network Unavailable
#${endif}

TOP - CPU ${hr 2}
${font StyleBats:size=16}A${font} ${top name 1}${alignr}${top cpu 1}  ${top mem_res 1}
${font StyleBats:size=16}A${font} ${top name 2}${alignr}${top cpu 2}  ${top mem_res 2}
${font StyleBats:size=16}A${font} ${top name 3}${alignr}${top cpu 3}  ${top mem_res 3}

TOP - RAM ${hr 2}
${font StyleBats:size=16}g${font} ${top_mem name 1}${alignr}${top_mem cpu 1}  ${top_mem mem_res 1}
${font StyleBats:size=16}g${font} ${top_mem name 2}${alignr}${top_mem cpu 2}  ${top_mem mem_res 2}
${font StyleBats:size=16}g${font} ${top_mem name 3}${alignr}${top_mem cpu 3}  ${top_mem mem_res 3}

MAIL ${hr 2}
${font Wingdings:size=16}-${font} ${execi 10 echo `grep total ~/.scripts/mail`}
${font Martin Vogel's Symbols:size=16}B${font}  ${execi 10 echo `grep unseen ./.scripts/mail`}

I use a couple of scripts and fonts. The fonts you get when you first install conky colors. These reside in ~/.fonts/conky_colors. Two other fonts I use are Wingdings, to get the mail box and Martin Vogel’s Symbols to get the envelope. All my scripts are in a hidden directory in ~/.scripts. The scripts I use are:

gputemp.sh

#!/bin/bash
tempString=`nvclock -T|grep "GPU temperature"`
echo "${tempString:19:3}"

This one echoes the temp of nvclock.

ip.sh

#!/bin/bash
curl-s myip.dk |grep '"Box"' | egrep -o '[0-9.]+'echo 

This one echoes my ip

The mail is checked by a script I found somewhere on the internet. Through a cron job this script writes the output to a file, which is read by my conkyrc. This seems to be little devious, but I do not know how to print these special fonts with an envelope and a mailbox in python. If anyone knows, please let me know.

imap.py

#!/usr/bin/python
# Author: Bhavik Shah
# Created: 3/5/09
# Description: Simple script that uses the python imap library
# to retrieve number of messages and unread messages from your 
# imap email account. 

# Script is free. Credit to bhaviksblog.com is appreciated =)

import imaplib

# Username should be your username with '@gmail.com' added
# I think the @gmail.com is required.
# If you're trying it with something other than gmail
# you should make the username the full email address
# example@domain.com
username = 
password =  # your pw
mailbox = 'INBOX' # inbox is default
output = '/home//.scripts/mail'

# only tested with gmail and my university email
# it should work with any imap server
# change mail server and port to match your server's info
mailserver = 
port = 993 #ssl

# connect to gmail's server (uses SSL, port 993)
server = imaplib.IMAP4_SSL(mailserver,port)

# gmail uses ssl...if your imap mail server doesn't comment the above
# line and uncomment this one.
# server = imaplib.IMAP4(mailserver,port)

# login with the variables provided up top
server.login(username,password)

# select your mailbox
server.select(mailbox)

# pull info for that mailbox
data = str(server.status(mailbox, '(MESSAGES UNSEEN)'))

# print it all out to a file
tokens = data.split()
total = int(tokens[3])
unseen = int(tokens[5].replace(')\'])',''))
outputFile = open(output, 'w')
outputFile.write('unseen: ' + str(unseen) + '\n')
outputFile.write('total: ' + str(total) + '\n')
outputFile.close()

# clean up output with str_replace()
#print tokens[2].replace('(',''),tokens[3] 
#print tokens[4],tokens[5].replace(')\'])','')

# close the mailbox
server.close()

# logout of the server
server.logout()

JCombobox with extra items that are not part of the list

In my last post I spoke about a JCombobox with the ability to add disabled items. Today I muse a little more on the JCombobox. This time I wanted to add an item to the JCombobox that is not part of the list with displayed items.

ComboboxExtraItem

JCombobox has a method setSelectedItem(Object anObject) for this. When you call this method with an item that is not part of the list it will only be displayed when the JCombobox is editable, if the JCombobox is not editable it will be ignored completely. So, in order to get an JCombobox to which we can call setSelectedItem(Object anObject) with the provided Object not being part of the list and that is not editable we extend our previous DisabledItemsCombobox with our own ComboBoxEditor. Now the trick is that we set the JCombobox editable property to true, but the editable property of the ComboBoxEditor to false.

import java.awt.Component;
import java.util.HashSet;
import java.util.Set;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

public class DisabledItemsComboboxDemo extends JFrame {

 public static void main(String[] args) {
  DisabledItemsComboboxDemo frame = new DisabledItemsComboboxDemo(
    "Combobox displaying an item that is not part of the list");
  frame.setVisible(true);
 }

 public DisabledItemsComboboxDemo(String title) {
  super(title);
  this.setDefaultCloseOperation(EXIT_ON_CLOSE);
  this.init();
 }

 private void init() {
  this.setSize(500, 50);
  DisabledItemsComboBox box = new DisabledItemsComboBox();
  box.addItem("This item is disabled but selected at first appearance", true);
  box.addItem("This item is disabled and you cannot select it", true);
  box.addItem("This is the first selectable item");
  box.addItem("This is the second selectable item");
  box.addItem("This is the third selectable item");
  MyComboBoxEditor editor = new MyComboBoxEditor();
  box.setEditor(editor);
  box.setEditable(true);
  box.setSelectedItem("This item is not in the list of selectable items");
  add(box);
 }

 private class DisabledItemsComboBox extends JComboBox {

  public DisabledItemsComboBox() {
   super();
   super.setRenderer(new DisabledItemsRenderer());
  }

  private Set disabled_items = new HashSet();

  public void addItem(Object anObject, boolean disabled) {
   super.addItem(anObject);
   if (disabled) {
    disabled_items.add(getItemCount() - 1);
   }
  }

  @Override
  public void removeAllItems() {
   super.removeAllItems();
   disabled_items = new HashSet();
  }

  @Override
  public void removeItemAt(final int anIndex) {
   super.removeItemAt(anIndex);
   disabled_items.remove(anIndex);
  }

  @Override
  public void removeItem(final Object anObject) {
   for (int i = 0; i < getItemCount(); i++) {
    if (getItemAt(i) == anObject) {
     disabled_items.remove(i);
    }
   }
   super.removeItem(anObject);
  }

  @Override
  public void setSelectedIndex(int index) {
   if (!disabled_items.contains(index)) {
    super.setSelectedIndex(index);
   }
  }

  private class DisabledItemsRenderer extends BasicComboBoxRenderer {

   @Override
   public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
     boolean cellHasFocus) {

    if (isSelected) {
     setBackground(list.getSelectionBackground());
     setForeground(list.getSelectionForeground());
    } else {
     setBackground(list.getBackground());
     setForeground(list.getForeground());
    }
    if (disabled_items.contains(index)) {
     setBackground(list.getBackground());
     setForeground(UIManager.getColor("Label.disabledForeground"));
    }
    setFont(list.getFont());
    setText((value == null) ? "" : value.toString());
    return this;
   }
  }
 }

 public class MyComboBoxEditor extends BasicComboBoxEditor {

  public MyComboBoxEditor() {
   editor.setEditable(false);
  }
 }
}

JCombobox with disabled items

I saw a couple of posts (here and here) about a common problem in Java: creating a JCombobox with some items disabled. To give an idea to what I mean here is a screenshot. It shows a JCombobox with some items in gray that cannot be chosen:

DisabledComboboxItem

Here is my solution to this:

import java.awt.Component;
import java.util.HashSet;
import java.util.Set;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

public class DisabledItemsComboboxDemo extends JFrame {

 public static void main(String[] args) {
  DisabledItemsComboboxDemo frame
    = new DisabledItemsComboboxDemo("Combobox with disabled items");
  frame.setVisible(true);
 }

 public DisabledItemsComboboxDemo(String title) {
  super(title);
  this.setDefaultCloseOperation(EXIT_ON_CLOSE);
  this.init();
 }

 private void init() {
  this.setSize(500, 50);
  DisabledItemsComboBox box = new DisabledItemsComboBox();
  box.addItem("This item is disabled but selected at first appearance",
              true);
  box.addItem("This item is disabled and you cannot select it", true);
  box.addItem("This is the first selectable item");
  box.addItem("This is the second selectable item");
  box.addItem("This is the third selectable item");
  add(box);
 }

 private class DisabledItemsComboBox extends JComboBox {

  public DisabledItemsComboBox() {
   super();
   super.setRenderer(new DisabledItemsRenderer());
  }

  private Set disabled_items = new HashSet();

  public void addItem(Object anObject, boolean disabled) {
   super.addItem(anObject);
   if (disabled) {
    disabled_items.add(getItemCount() - 1);
   }
  }

  @Override
  public void removeAllItems() {
   super.removeAllItems();
   disabled_items = new HashSet();
  }

  @Override
  public void removeItemAt(final int anIndex) {
   super.removeItemAt(anIndex);
   disabled_items.remove(anIndex);
  }

  @Override
  public void removeItem(final Object anObject) {
   for (int i = 0; i < getItemCount(); i++) {
    if (getItemAt(i) == anObject) {
     disabled_items.remove(i);
    }
   }
   super.removeItem(anObject);
  }

  @Override
  public void setSelectedIndex(int index) {
   if (!disabled_items.contains(index)) {
    super.setSelectedIndex(index);
   }
  }

  private class DisabledItemsRenderer extends BasicComboBoxRenderer {

   @Override
   public Component getListCellRendererComponent(JList list,
                                                 Object value,
                                                 int index,
                                                 boolean isSelected,
                                                 boolean cellHasFocus) {

    if (isSelected) {
     setBackground(list.getSelectionBackground());
     setForeground(list.getSelectionForeground());
    } else {
     setBackground(list.getBackground());
     setForeground(list.getForeground());
    }
    if (disabled_items.contains(index)) {
     setBackground(list.getBackground());
     setForeground(UIManager.getColor("Label.disabledForeground"));
    }
    setFont(list.getFont());
    setText((value == null) ? "" : value.toString());
    return this;
   }
  }
 }
}

Conky fan number changed after upgrade of Arch

I am using Arch linux for my system. This distro has a rolling release schedule. So every now and then you have to upgrade your complete system with ‘pacman -Syu’. This is always a little risky, but the advantage of running bleeding edge software, the minimalistic approach of Arch and the fact that you really install such a system only once made this one the distrohopperstopper for me. Yesterday I upgraded the system and I noticed conky wouldn’t run anymore. Running conky from the command line revealed the problem:

Conky: can't open '/sys/class/hwmon/hwmon0/fan1_input': No such file or directory

Looking in /sys/class/hwmon/hwmon1 revealed that all my sensors where there and not in hwmon0. No idea why, but changing the line in my conkyrc to:

${font StyleBats:size=16}O${font}   CPU Fan:${alignr}${hwmon 1 fan 1} ${font}RPM

made me a happy conky user again:

Conky

Anyone knows why this happened?

Eartwallpaper revisited

Recently I found a script written by Claudio Novais to show a nice wallpaper which displays a picture of the Earth, in real time. On my system this looks like this:

Original wallpaper

Now, I recently got this huge monitor so this didn’t look so well and I decided to change it a little. On http://www.fourmilab.ch/earthview/ you can find an earth and moon viewer. You can alter the image to your liking. So I created an image of 800px x 800px with a view from 35785 km above, 53°23’N and 6°E (roughly Amsterdam, The Netherlands). With ctrl-u (firefox) I looked at the source from the website and found a line like this:

/Earth?di=09523FEE4A66A46BC0364BAA5DD73780BB5E4B6C16283003C802B34399E
7DBBBBEDD58341A5839B680DEB05E0FB23713949A6000B79A9E35A8BC41D5379746

This was clearly the image I was looking at. So my wget line in the original script now looked like this:

wget http://www.fourmilab.ch/cgi-bin/Earth?di=09523FEE4A66A46BC0364BAA5DD73780BB5E4B6C16283003C802B34399E
7DBBBBEDD58341A5839B680DEB05E0FB23713949A6000B79A9E35A8BC41D5379746 
-O world.jpg

The result was quite satisfactory:

Second attempt

But with all this space left I was wondering if I could do a little better. E.g. put a view of the moon on the left side. Using the same procedure as before I found that a picture from the moon, viewed from my place (change E to W and N to S) gave me the following id:

69325F8E2A06C40BA0562BCA3DB757E0DB3421067C425A69A268D908D8A888C8CAF7
784C6E3C34F0AAD9F61851DFC7E463FA324CEA77476FF2E406A776E182D6639D0D6F

The moon is not changing every minute, so downloading it at startup would satisfy my needs. I put the following line in /etc/rc.local (I am on Arch Linux):

wget http://www.fourmilab.ch/cgi-bin/Earth?di=69325F8E2A06C40BA0562BCA3DB757E0DB3421067C425A69A268D908D
8A888C8CAF7784C6E3C34F0AAD9F61851DFC7E463FA324CEA77476FF2E406A776
E182D6639D0D6F -O /home/-username-/.gnome2/moon.jpg

And a black picture with resolution 1820*956 as background.jpg. Find the needed resolution by subtracting the border (2*50px) from your full screen. Then altered the script to use ImageMagick and blend these pictures together:

cd ~/.gnome2/
while [  1 ]; do
 COUNTER=0
 while [  $COUNTER -lt 60 ]; do
 wget http://www.fourmilab.ch/cgi-bin/Earth?di=752E4392361AD817BC4A37D621AB4BFCC72237106A544C7FB47ECF3FE5
9BA7C7C2A12448662479F4DE80EE6C08AB9EBD383FFC80379D9932AEB952C01182D
88636F85BB29877916B -O world.jpg
 composite -bordercolor black -border 50x50 moon.jpg background.jpg backgroundWithMoon.jpg
 composite -gravity center world.jpg backgroundWithMoon.jpg completeWallpaper.jpg
 temp=$(stat -c%s world.jpg)
 if [[ $temp > 1000 ]]
 then rm world_sunlight_Wallpaper.jpg
 mv completeWallpaper.jpg world_sunlight_Wallpaper.jpg
 break
 fi
 sleep 5"/span"
"span style="color:#0000ff;"" let COUNTER=COUNTER+1
 done
 sleep 900
done

The result :):

Earth an moon together