Tuesday, October 13, 2009

voyage-prive.com : Attention !

Les offres de voyage-prive.com ne sont pas toujours intéressantes. Assurez-vous de toujours bien vérifier les prix en direct sur les hôtels proposés !

En ce moment, est proposé le prestigieux Warwick Champs Elysées. Si on essaye avec voyage-prive, on nous fait miroiter 69 % de réduction : 190 euros pour deux personnes au lieu de 610 euros pour une chambre standard en petit déjeuner : Je ne compte pas les 5 € de réductions sur le menu à 35 €.
Si on va sur le site de l'hôtel, on trouve à la même date une chambre standard à 227 € pour deux personnes : Le prix avant réduction correspondrait plutôt à la suite (et encore, elle est moins chère!) :A mon avis, c'est à la limite de l'arnaque ces fausses réductions qui passent pour de la publicité mensongère.

Les offres commencent à devenir intéressantes quand on prend l'avion pour aller au bout du monde... mais bon, pour une nuit romantique dans un hôtel de luxe dans les proches capitales européennes, ça reste à prouver...

Wednesday, October 7, 2009

How to import a yahoo agenda to another (yahoo) account ?

Even if this post is written in English, it targets French people. As a French, I use the French yahoo web site, which is slightly different from the American version.

EXPORT

In France, you do not have access to Import/Export functions. You need to go to your Agenda options and hack the url. The url has two parameters v (id of current page) and pv (id of previous page). You must change the v value to 81. Thanks a lot BarrJo for this info.

This will show a new page with Import/Export functions to/from Palm Desktop and Outlook.

Simply export in Outlook format.

PATCH & IMPORT

The exported agenda cannot be handled by yahoo (this might explain why yahoo hides this functionnality).

Here is what you need to fix :
  • Carriage returns : all carriage returns are missing. You must add one after the header line (just after the "Description" title). You can deal most of the lines with a decent text editor with search and replace (you need to add a new line after "true";"" or "false";""). Then, only the rows with a non-empty description remain.
  • HTML entities : for example, ", & and ', etc. must be respectively replaced by ", & and '.
  • Commas : Yahoo gives you a file with values separated by semicolons. You need to replace them with commas. Be careful of other semicolons (in descriptions for example).
Now you can import the patched csv file.

Note : I have exported again the imported agenda and checked that all events were taken into account. I have just noticed a few updates from true to false in the antelast column.

LIMITS
  • Some yahoo specific info is lost (kind of event defaults to meeting, reminder, etc.)
  • Repetitions are lost : if you update the repetition period of an event, you will end with two events in the future (the imported one and the repetition). So I advise you to remove all repetition starting from next year for yearly events (like birthdays). Then you will just need to set the repetition period to all birthdays.
OTHER POSSIBLE BUGS
  • Date formats
  • Encoding : UTF8 / ANSI / etc.

Thursday, June 25, 2009

Fringe c'est mieux en VO

Avec la censure de TF1 sur ses forums, je poste finalement ici.

Ayant adoré le premier épisode (malgré les deux coupures pubs), je n'ai pas pu attendre. J'ai donc regardé la série en VO (même si je n'ai pas encore fini la saison 1 à ce jour). Hier soir, je tombe par hasard sur l'épisode 4 sur TF1. C'est encore frais dans ma tête, je constate avec surprise que la version française a été censurée : 2 minutes ont été supprimées. Entre la 31è et la 33è environ, une scène a été presque entièrement coupée, quand le gus au bonnet force Peter à parler.

Je trouve ça complètement nul que l'on censure des scènes de la série. Cela nuit à l'intrigue. En plus, vu l'heure à laquelle ça commence, je ne vois pas qui risque d'être choqué.

Bref : boycottez TF1, choisissez la VO, c'est mieux. Vous ne risquez pas de rater une miette de l'histoire et ça vous fera bosser l'anglais.

Thursday, May 21, 2009

Le Vélib' c'est pourri

2h du mat. Une petite flemme. On décide de tester le vélib' pour la première fois. On passe alors 5 bonnes minutes à créer un compte pour un pass court de 1 à 7 jours. Je prends un vélo. Il me propose le 21, je préférais le 37 mais bon, pas trop le choix apparemment. Au tour de ma chérie, et là plus de vélos ! Evidemment, les autres vélos sont sur le trottoir d'en face, inutilisable pour les non-habitués.

On décide de patienter jusqu'à la prochaine station. Je roule lentement et elle marche à mes côtés. Une fois arrivés, on trouve plein de vélos. Mais tous sont marqués rouge : comprendre indisponibles.

On continue jusqu'à la station suivante. Comme on pouvait s'y attendre, son numéro d'abonné temporaire n'est pas reconnu. Dégoûté, je rends mon vélib'. Après 20 min de marche à pied, ça valait le coup.

Résultat, on a mis 30 minutes alors que d'habitude on trace en 1/4 h... Le vélib', ça marche pas. C'est pas demain la veille qu'on va retenter l'expérience.

Monday, April 20, 2009

WPF Cover Flow Tutorial : Part 7 (source)

Before disclosing sources of Part 7, here a few notes.

I provide a sample ThumbnailManager, working with an IsolatedStorageFile.
public class ThumbnailManager : IThumbnailManager
{
#region Fields
private readonly IsolatedStorageFile store;
#endregion
public ThumbnailManager()
{
store = IsolatedStorageFile.GetUserStoreForAssembly();
}
public ImageSource GetThumbnail(string host, string path)
{
string thumbName = Path.GetFileName(path);
if (store.GetFileNames(thumbName).Length == 0)
{
using (var stream = new IsolatedStorageFileStream(thumbName, FileMode.CreateNew, store))
{
byte[] data = GetThumbnail(path);
stream.Write(data, 0, data.Length);
}
}
using (var stream = new IsolatedStorageFileStream(thumbName, FileMode.Open, store))
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
return image;
}
}
}
I deal with host names because I am also working on an implementation dealing with many shares on the network. The GetThumbnail method just resizes pictures.
    private byte[] GetThumbnail(string path)
{
Image source = Image.FromFile(path);
source = AmazonCut(source);
int height = source.Height;
int width = source.Width;
int factor = (height - 1) / 250 + 1;
int smallHeight = height / factor;
int smallWidth = width / factor;
Image thumb = source.GetThumbnailImage(smallWidth, smallHeight, null, IntPtr.Zero);
using (var ms = new MemoryStream())
{
thumb.Save(ms, ImageFormat.Png);
ms.Flush();
ms.Seek(0, SeekOrigin.Begin);
var result = new byte[ms.Length];
ms.Read(result, 0, (int)ms.Length);
return result;
}
}
For the sample videos on youtube, I downloaded many covers from amazon. That's why I need the helper function that removes the blank frame around the picture:
    private static Image AmazonCut(Image image)
{
if (image.Width != image.Height)
return image;
var bmp = new Bitmap(image);
int size = image.Height;
int white = System.Drawing.Color.FromKnownColor(KnownColor.White).ToArgb();
int i = 0;
while (i < size / 2)
{
if (bmp.GetPixel(i, i).ToArgb() != white)
break;
if (bmp.GetPixel(i, size - 1 - i).ToArgb() != white)
break;
if (bmp.GetPixel(size - 1 - i, i).ToArgb() != white)
break;
if (bmp.GetPixel(size - 1 - i, size - 1 - i).ToArgb() != white)
break;
i++;
}
if (i > 0)
{
i += 8;
var zone = new Rectangle(i, i, size - 2 * i, size - 2 * i);
return bmp.Clone(zone, System.Drawing.Imaging.PixelFormat.DontCare);
}
return bmp;
}
Well this whole class is not perfect code, but it is sufficient for a demo.

If you do not know innerings of IsolatedStorage, you will find the thumbnails in a folder like C:\Documents and Settings\ded\Local Settings\Application Data\IsolatedStorage\0ypqvhod.rll\j4pydd3v.g4v\StrongName.alr3sbzvfezz2fk22sd5g0b5dxbwzr0b\AssemFiles.

Edit 2014-02-23 : Code has moved to github.

Wednesday, April 15, 2009

WPF Cover Flow Tutorial : Part 7

After some improvements in the Cover class, let's describe how virtualization works.

We do not keep all covers in memory. Remember the drawing in Part 2. At a given time, we only keep a few covers on both sides of the current cover (at position index). This means that every time we browse through the covers, we create and destroy one or more covers. If we do not keep all covers in memory, we have to keep enough information to rebuild covers : image file path and position. We also need to remember which covers are currently built.
public const int HalfRealizedCount = 6;
public const int PageSize = HalfRealizedCount;
private readonly ICoverFactory coverFactory;
private readonly Dictionary<int, ImageInfo> imageList = new Dictionary<int, ImageInfo>();
private readonly Dictionary<int, ICover> coverList = new Dictionary<int, ICover>();
private int index;
private int firstRealized = -1;
private int lastRealized = -1;
  • HalfRealizedCount is the number of covers created before and after current cover
  • I use the same value for the PageSize (we could choose a different value) when we will move by more than one cover at a time
  • imageList will map cover positions to image paths
  • coverList will be used as an array to keep realized covers
  • firstRealized and lastRealized are the positions of respectively first and last realized covers
There will be two ways to browse covers : by less than a page with animation or jumping by more than a page without animation.
    private void RotateCover(int pos, bool animate)
{
if (coverList.ContainsKey(pos))
coverList[pos].Animate(index, animate);
}
private void UpdateIndex(int newIndex)
{
if (index != newIndex)
{
bool animate = Math.Abs(newIndex - index) < PageSize;
UpdateRange(newIndex);
int oldIndex = index;
index = newIndex;
if (index > oldIndex)
{
if (oldIndex < firstRealized)
oldIndex = firstRealized;
for (int i = oldIndex; i <= index; i++)
RotateCover(i, animate);
}
else
{
if (oldIndex > lastRealized)
oldIndex = lastRealized;
for (int i = oldIndex; i >= index; i--)
RotateCover(i, animate);
}
camera.Position = new Point3D(Cover.CoverStep * index, camera.Position.Y, camera.Position.Z);
}
}
I'm not sure if the test in RotateCover is still needed. Here is how the new UpdateIndex function. We compute the animation boolean like described before. The UpdateRange function deals with cover realization : it will create and destroy covers as necessary and updates both realized indexes. Then, for all realized covers between old and new index, we rotate the covers. Finally, like before, we update camera positions.
    private void RemoveCover(int pos)
{
if (!coverList.ContainsKey(pos))
return;
coverList[pos].Destroy();
coverList.Remove(pos);
}
private void UpdateRange(int newIndex)
{
int newFirstRealized = Math.Max(newIndex - HalfRealizedCount, 0);
int newLastRealized = Math.Min(newIndex + HalfRealizedCount, imageList.Count - 1);
if (lastRealized < newFirstRealized || firstRealized > newLastRealized)
{
visualModel.Children.Clear();
coverList.Clear();
}
else if (firstRealized < newFirstRealized)
{
for (int i = firstRealized; i < newFirstRealized; i++)
RemoveCover(i);
}
else if (newLastRealized < lastRealized)
{
for (int i = lastRealized; i > newLastRealized; i--)
RemoveCover(i);
}
for (int i = newFirstRealized; i <= newLastRealized; i++)
{
if (!coverList.ContainsKey(i))
{
ICover cover = coverFactory.NewCover(imageList[i].Host, imageList[i].Path, i, newIndex);
coverList.Add(i, cover);
}
}
firstRealized = newFirstRealized;
lastRealized = newLastRealized;
}
In the UpdateRange method, we first compute realized indexes. Then, we remove necessary covers (might need all covers if we move by more than HalfRealizedCount. Finally, we create missing covers.
    public void GoToNext()
{
UpdateIndex(Math.Min(index + 1, imageList.Count - 1));
}
public void GoToPrevious()
{
UpdateIndex(Math.Max(index - 1, 0));
}
public void GoToNextPage()
{
UpdateIndex(Math.Min(index + PageSize, imageList.Count - 1));
}
public void GoToPreviousPage()
{
UpdateIndex(Math.Max(index - PageSize, 0));
}
Navigation methods are self explanatory.

Possible improvements :
  • Slider
  • Events
  • Tags (e.g. album names to jump to covers)
Source is available in next post.

Tuesday, April 14, 2009

WPF Cover Flow Tutorial : Part 6 (bis)

I'll start with some Cover refactoring :
  • Class becomes internal
  • I add the ICover interface, mainly for unit testing with a fake class implementing ICover :
    public interface ICover
    {
    void Animate(int index, bool animate);
    bool Matches(MeshGeometry3D mesh);
    void Destroy();
    }
  • You may notice the new Destroy method. This is useful to be able to create or destroy objects at will. Relatively, covers now know about the containing ModelVisual3D.
  • I also add a static IThumbnailManager to put elsewhere all the code dealing with thumbnails (like managing an IsolatedStorageFile for example)
  • Animation is slightly modified : in some cases, no rotation is needed to allow faster browsing.
Here are the changes since Part 5 :
public interface IThumbnailManager
{
ImageSource GetThumbnail(string host, string path);
}
internal class Cover : ModelVisual3D, ICover
{
...
private static IThumbnailManager thumbCache;
private readonly ModelVisual3D visualModel;
...
private readonly string imageName;
...
private static ImageSource LoadImageSource(ImageInfo info)
{
if (thumbCache == null)
throw new InvalidOperationException("No thumbnail cache.");
return thumbCache.GetThumbnail(info.Host, info.Path);
}
...
public Cover(ImageInfo info, int coverPos, int currentPos, ModelVisual3D model)
{
pos = coverPos;
imageName = new FileInfo(info.Path).Name;
visualModel = model;

imageSource = LoadImageSource(info);
modelGroup = new Model3DGroup();
modelGroup.Children.Add(new GeometryModel3D(Tessellate(), LoadImage(imageSource)));
modelGroup.Children.Add(new GeometryModel3D(TessellateMirror(), LoadImageMirror(imageSource)));

rotation = new AxisAngleRotation3D(new Vector3D(0, 1, 0), RotationAngle(currentPos));
translation = new TranslateTransform3D(TranslationX(currentPos), 0, TranslationZ(currentPos));
var transformGroup = new Transform3DGroup();
transformGroup.Children.Add(new RotateTransform3D(rotation));
transformGroup.Children.Add(translation);
modelGroup.Transform = transformGroup;

Content = modelGroup;

visualModel.Children.Add(this);
}
public static IThumbnailManager Cache
{
set { thumbCache = value; }
}
public void Animate(int index, bool animate)
{
if (animate || rotation.HasAnimatedProperties)
{
var rotateAnimation = new DoubleAnimation(RotationAngle(index), AnimationDuration);
var xAnimation = new DoubleAnimation(TranslationX(index), AnimationDuration);
var zAnimation = new DoubleAnimation(TranslationZ(index), AnimationDuration);
rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, rotateAnimation);
translation.BeginAnimation(TranslateTransform3D.OffsetXProperty, xAnimation);
translation.BeginAnimation(TranslateTransform3D.OffsetZProperty, zAnimation);
}
else
{
rotation.Angle = RotationAngle(index);
translation.OffsetX = TranslationX(index);
translation.OffsetZ = TranslationZ(index);
}
}
public void Destroy()
{
visualModel.Children.Remove(this);
}
public override string ToString()
{
return string.Format("{0} {1}", pos, imageName);
}
}
I also add an ICoverFactory (mainly for testing purposes) :
public interface ICoverFactory
{
ICover NewCover(string host, string path, int coverPos, int currentPos);
}
internal class ImageInfo
{
public ImageInfo(string host, string path)
{
Host = host;
Path = path;
}
public string Host { get; private set; }
public string Path { get; private set; }
}
internal class CoverFactory : ICoverFactory
{
private readonly ModelVisual3D visualModel;
public CoverFactory(ModelVisual3D visualModel)
{
this.visualModel = visualModel;
}
#region ICoverFactory Members
public ICover NewCover(string host, string path, int coverPos, int currentPos)
{
return new Cover(new ImageInfo(host, path), coverPos, currentPos, visualModel);
}
#endregion
}
The FlowControl class is greatly refactored to implement virtualization.

WPF Cover Flow Tutorial : 6 months intermission

Well, sorry guys for not posting since October... I didn't have enough time to go on this tutorial. Less time available for coding and I've been working on a wider project where my Flow assembly was just a small GUI component.

There will be quite a jump in the source code. But I'll try to describe what is the main idea behind virtualization...

Coming soon !