Android: Canvas.drawText() is inconsistent across multiple screen resolutions and densities

I'm not sure why this was done this way, but the default font size in the Paint object does not scale automatically when you run your app on different resolutions and screen densities.

A thing to keep in mind is that if a Paint object is to be used for Canvas.drawText() then it has to be initialised for the right screen density.

If you want to keep the current default size but scale it according to the screen density, simply add this line of code.

paint.setTextSize(paint.getTextSize() * yourActivity.getApplicationContext().getResources().getDisplayMetrics().density);

This will slightly scale it up or down, depending on whether the device is running at low or high density.

If you want to have more control over the size of the text, then use a dimension resource. In your "res/values" folder, either add to or create an XML file.

Using the editor, create a new "Dimension" value in that XML file with whatever name you want. For this example, I'm going to give it a value of "16sp". This means that it will be "16 scaled pixels" big, scaled to the device density AND the user's font size settings.

To apply that dimension:


Kudos to Marcin Gil and Joseph Earl for the help, but it ended up being my own noobish mistake for not applying it to the right Paint instance.



Convert FLV, MPEG4, QuickTime Mov, 3GP, MP4, VOB (DVD), H.263, WMV/ASF, OGM or MKV to AVI

Damn, all these years I've been using online converters like Zamzar to change from FLV/MP4 to AVI.

Despite their usefulness and ease of use, it kinda sucked having upload limitations and no real way of making sure that your video will be converted properly and quickly.

Not to mention the angst once you finally finish uploading the video and find out "format not supported".

Note: This post is a general information post and includes no details on how to install or use the mentioned programs/plugins.

This guy was certainly angry when that happened to him!

In comes AviDemux, an awesome little free program that'll solve all your problems and impotency!

Its a bit tricky to use at first, but it's a lot quicker than most video conversion programs which need to recompress the whole file which takes bloody long.

Better yet, you don't even need to install it. Just download the zip file and away you go. There's also an installer in case you're lazy.

There is an alternative way doing the same thing using VirtualDub, but I found more trouble than its worth.

Firstly, grab the latest copy of DShowInputDriver and throw it into the . This will allow VirtualDub to load files which use the DirectShow filter on your computer.

The problem is that you have to force it to use that plugin every time you want to load the video. I use VirtualDub for all my video editing purposes, but this is too clunky for my liking.


Android: Detect volume button presses to change media volume instead of ringer volume

Man, this one was bloody hard to find. Couldn't find it in the docs for the life of me.

Luckily, StackOverflow came to the rescue.

Normally when your app starts, people expect to be able to change the volume when they press the volume buttons. Surely enough, the Android platform shows the the volume level and it changes accordingly.

Sadly, it is the RINGER volume which they are changing and not your MEDIA volume (the volume level which your app uses!).

Because of this, they'll think you're stupid (but really its not your fault!)

To prevent this, during the Activity.onCreate() phase, paste in this line.


That's it! Now the app will respond to the volume button by changing the media volume rather than ringtone volume.

[ Source ]

Adobe Audition: Won't start with error 6e69575c

Bloody hell, this was one issue that annoyed me to no end. Every time I tried to load Adobe Audition 3 it'd crash.

I tried:

  • Patching/updating
  • Reinstalling
  • Uninstall and deactivate
  • Uninstall and delete files/registry

It took a while to figure out, but the installation of Bulletstorm bundled in some Games for Windows Live and Windows Live ID stuff.

After uninstalling the game, I found that Audition was still crashing. Only after uninstalling GFWL and Windows Live ID did it work again.

Since Audition is far more useful to me than the other 2, I've decided to keep them off my computer.


I'm a little short on ideas on how to fix it though.

Facebook: Press enter to post comment

One of the most stupid user interface decisions ever. Why get rid of the little "Post" button? Did you really hate it that much?

So I was posting my comment and then suddenly... ENTER!

Now people won't be able to write properly structured paragraphs unless they pressed SHIFT+Enter to go to a new line.

Seriously Facebook, fuck off this enter-to-post business and revert it back. The post button worked fine, don't fix it.

Linux: Quickly search for text within multiple file types

It's really easy to quickly search certain files in a large directory for some text.

find -name "*.html" -or -name "*.json" -or -name "*.js" | xargs grep -i "full_uri"

The "find" command searches out those specific files.

The "xargs grep" will search the files (-i makes it case insensitive).


Now its much easier to find your stuff, even with a million lolcats swarming the place.

django: Customise the admin form for models

Customising the admin form can be a bit tricky as the documentation is a bit scarce on that issue.

What I needed to do was add a checkbox which renews the object to the current timestamp.

However, the checkbox is not part of the model, and won't show up unless you specify a custom form. To specify a custom form, fill in the ModelAdmin.form attribute.

Upon saving, we override ModelAdmin.save_model() so it knows how to use the data from the new field elements.

Below is the working source for a customised form.

import datetime

from django import forms
from django.contrib import admin

class JobForm(forms.ModelForm):
renew = forms.BooleanField(label = 'Renew post')

class Meta:
model = Job

class JobAdmin(admin.ModelAdmin):
form = JobForm

def save_model(self, request, obj, form, change):
if change and form.cleaned_data['renew']:
obj.published =

return super(JobAdmin, self).save_model(request, obj, form, change), JobAdmin)

And there you have it!

Disable annoying social media bars on websites

I hate these annoying little social bars that try to be helpful but just aren't. They take up my vertical real-estate and don't really do much for me.

Not sure what I'm talking about?

This shit!

If you're using Firefox, make sure you've got AdBlock Plus installed first. It also comes in Chrome flavour if that's what you're using.

You'll soon wonder how on earth you've made it this far on the internet without it.


Once you've got it installed, go to "Tools" > "AdBlock Plus Preferences".

It'll give you a big scary list. Go to "Filters" > "Add filter".


Now prepare to do some typing. The following are a list of annoying social media bars that I've blocked. I'll occasionally come back and add more bars to this list.

  • http://* (Wibiya)
  • http://* (HelloBar)
  • http://* (Skysa)
  • (Meebo)
  • ##div#meebo.meebo-00 (Also needed for Meebo)
  • http://* (Complex Media Network)
  • ||^ (Post-Google acquisition Meebo)
  • ||^

Press OK to save. Refresh the page and the bars should be gone!

If you find any others, paste a link to a page using it and I'll update the post.


*update 16/03/2011* Added Complex Media Network

*update 06/02/2013* Added Post-Google acquisition Meebo

Django: Access current user information from anywhere

When dealing with user authentication, it's often useful to include it into the view and template context.

There is a handy way of doing this and its already built into django.

In your settings, modify the MIDDLEWARE_CLASSES so it includes these two classes.


Once it's reloaded, you'll be able to access "request.user" in the view/context.

Additionally, if you need stuff like HTTP referrer and to check what browser they're using, it is available through:

  • request.META.get('REMOTE_ADDR', '')
  • request.META.get('HTTP_USER_AGENT', '')

Using the get() method is a bit safer as these values may not be set and it'll throw an exception.

PS3: Install kmeaw CFW 3.55 with MultiMan for homebrew and backup loader support

There is a newer firmware and guide to install CFW 4.40 Rogero CEX CFW (v1.02b). It's much easier to copy and load games, so there's no point to continue reading this out-dated tutorial.

Homebrew support for the PS3 has become amazingly easy to install over the last couple of months. Now that Sony has finally put their foot down about it, the hacking progress has slowed.


To install homebrew, you'll need the following ingredients:

*update 29/05/2011* You can use multiMAN for copying games.

Optionally, you can "Awesome File Manager" for copying files over to the PS3. I prefer this over the FTP function in multiMAN because all my stuff is connected via wireless which is a bit slow.

Note: A lot of older tutorials will also say you require LV2 patcher. This is no longer required as multiMAN includes it.

Note: If you do not want to download a pre-patched firmware package, then you can follow the steps on the first link under the Sources section.


Now the slightly worrying part. It's ok, this firmware has a very slim bricking rate compared to Waninkoko's first attempt. Currently runs fine on my fat PS3.

The following steps will require you to know how to update your PS3 firmware via USB. Do not download or install any firmware newer than 3.55! They are not hackable!

First, you HAVE to upgrade to 3.55 OFW (official firmware). Do not skip this part as you will risk bricking. When ready, proceed to installing kmaew's CFW.

  • Extract "PS3UPDAT.PUP" from "PS3_355_KMEAW_CFW.RAR" and place it on your USB in the "usb://PS3\UPDATE" folder.
  • While you're at it, extract multiMAN and BDEMU-355.pkg to "usb://".

Your structure should now look like this.

  • Now you're ready to install CFW!
  • Install kmeaw the same way you install OFW. It doesn't need to be in recovery mode for this to work.
  • After the PS3 reboots, go to your "Game" menu.
  • You'll see a new option called "* Install Package Files"
  • Install multiMAN.
  • Install BDEMU-355.pkg.
  • Install Awesome File Manager.

Installing Games

Now you're ready to install games homebrew! Woohoo!

*update 29/05/2011* Mad noob, I didn't fully read the instructions for multiMAN.

* Proper structure:

So copy the games onto your USB into a folder called "GAMES".

Once it's done, plug it in and start up multiMAN. The game should appear on the list of games. Press O to copy it over to your hard drive. It'll take a while.

Make sure that games go into "/dev_hdd0/GAMES", not "/dev_hdd0/game".

Now there are two ways of doing this.

multiMAN offers an FTP feature where you can connect to your PS3 and upload directly to the GAMES folder via FTP. If this works for you, enjoy!

I find it faster and easier to copy via USB. Whatever game you've downloaded, throw it anywhere onto the USB drive and use Awesome File Manager to copy things across.

*update 29/05/2011* Now optional, since I figured out how to copy via multiMAN. Skip to the "Running games" section.

(optional) It is a little tricky to use at first though, but here's the general instructions for Awesome File Manager.

On the left you will have the source file/folders, while on the right you have the destination folder.

  • While the left side is selected, press Up/Down to navigate around and X to dig into a folder.
  • Go into /dev_usb***, which will be your USB drive.
  • Find your game folder and keep the selection there.
  • Then press Right to highlight the destination folder. It should turn yellow.
  • Go to /dev_hdd0/GAMES

Now by default, normal mode will only let you copy from /dev_usb to /dev__hdd0/game.

  • Press SELECT to change it to Advanced Mode. The screen will turn red.
  • Press O to copy the folder over.
  • Press START to confirm (or Triangle to cancel if it was accidental)
  • Once done, exit via the PS button menu.

Running Games

The fun bit now that all the setup is out of the way.

Every time you start your PS3 you'll have to do this.

  • Make sure you have a legit game disc in the PS3. This is required.
  • Start multiMAN.
  • Select the game and load it.
  • The PS3 menu will appear again, but with one difference. Instead of the official disc you have in the PS3, it'll show as the game you loaded in multiMAN instead!
  • Now just start it as you usually would with a normal disc.
  • Enjoy!

It's party time!

Problem: "multiMAN cannot enable BD-ROM emulator. Functionality may be restricted."

I got this error because I did not have BDEMU-355KM installed. As of multiMAN 1.16, deank removed those files from his releases.

Just make sure you install "BDEMU-355KM.pkg" and you'll be fine.

Note: If you're searching the web for that file, make sure it has the "KM" at the end!

We're using kmeaw (KM) CFW and not Wutangrza (WT) CFW. If you install the wrong one, things can only end badly.


*update 29/05/2011* Updated instructions on how to copy via multiMAN.

PS3: How to update firmware via USB

PS3 updates are quite large and may take a while to download.

To speed up the process a bit, you can optionally install them via USB.

  • Make sure your USB is in FAT32 format.
  • In the USB drive, create a folder called "PS3".
  • In that new PS3 folder, create another called "UPDATE".
  • Copy the system update "PS3UPDAT.PUP" file to the UPDATE folder.

The structure should now look like this.


  • Insert the USB into your PS3.
  • In the XMB, go across to the left and find "Settings".
  • Near the top, select "System Update"
  • Select "Install via storage media"
  • Follow the prompts through.

If you want to make sure if it worked properly, go to "Settings" > "System Settings" > "System Information".

ATI Radeon: Monitor does not fill up monitor

After installing a DVI to DVI/VGA splitter on my work computer, I started to notice this every morning.


For months this pissed me off to no end. Every morning I'd get in, turn on the monitor and have to resize the display settings to zoom in 2x. Not only that, it'd also resize when the monitor quickly restarts itself.

Initially I thought it was an issue with the Dell 2408WFP monitors not saving the settings properly. But when I thought about it, a couple of other guys in dev had the same setup and it was working fine.

It had to be a driver issue since I upgraded mine with Windows Update. The graphics card on the computer is an ATI Radeon HD 3400.

Eventually I got fed up and looked for a way to fix it.


This was due to the native resolution of the monitor being 1920x1200, while the resolution I set was 1680x1050.

Somewhere along the lines the signal was being resized.


  • Open up the ATI Catalyst suite.
  • Get to the configuration by clicking on "Graphics" > "Desktop & Displays".


  • At the bottom, right click the monitor you want to fix and select "Configure". The menu wont appear if you click on the bigger monitors.


  • Most likely your settings are set to "Use centered timings". Change it to "Maintain aspect ratio".


  • Change it to "Maintain aspect ratio".
    You can optionally select "Enable GPU scaling" if you want the graphics card to resize the screen rather than letting the monitor do it.


  • Apply and enjoy the results!


If that does not work, run your problems over with a bulldozer.


Android: Extract a HTTP web page content into a String

Initially I found a lot of code samples on the web explaining how to read from the IO stream of the HttpEntity. I followed that, but found the code to be fairly bloated and slow.

After some time, I found that the code sample was unable to cope with HTTP status codes such as 404 or server error 500.

Then I found a much easier way of doing it. Instead of adding 54 lines of code to go through the trouble of parsing HttpEntity, reading the stream out into a buffer and String, we just let the Android API handle it.

Original method:

HttpClient httpclient = getHttpClient();
HttpPost httppost = new HttpPost(url);
HttpResponse response;

// Add post data
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("username", username));
nameValuePairs.add(new BasicNameValuePair("password", password));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

// Execute HTTP Post Request
response = httpclient.execute(httppost);

Xml.parse(getResponseBody(response), new CustomXmlParser());

New method:

HttpClient httpclient = getHttpClient();
HttpPost httppost = new HttpPost(url);
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String xml;

// Add post data
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("username", username));
nameValuePairs.add(new BasicNameValuePair("password", password));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

// Execute HTTP Post Request
xml = httpclient.execute(httppost, responseHandler);

Xml.parse(xml, new CustomXmlParser());

As you can see, theres not much of a chance in the actual code except for the HttpClient.execute() call where the extra responseHandler is given.

The call to execute() will parse the data straight to a String for us, so no need to get into the nitty gritty anymore!

It'll also throw exceptions when the server runs into any errors such as 404 or server error 500.

A bonus is that you'll get to cull about 50+ lines of code from your project.


Android: Starting a sub-activity and send data between them


Applications (or programs) on Android are considered to be "activities". Your app itself is a single activity. Clicking on a hyperlink in your app and open up a browser means your app has started an internet browser sub-activity.

When you open up your contacts list to look for a person to call, that is also an activity. Same when an app asks you to select a file, the file selection activity will return the selected filename to the app which initiated it.

An important thing to note is that when the sub-activity starts, the initial activity will still continue to run in the background. Hitting the back button will (usually) kick you back into the initial app with the default result of Activity.RESULT_CANCELED.

You can start a sub-activity with one of the two expectations: "fire and forget" or "wait for result".

Fire and forget is usually a web browser. You send the user off to browse for something and when they're done then they can resume using your app.

Wait for result will start the sub-activity and wait until you've returned with some data. A callback is triggered in the initial activity which will let you manipulate the data accordingly.

Data can be shared between activities by sending data "bundles". You may have noticed this already when setting up your first activity in the onCreate(Bundle savedInstanceState) method.

Setting up an activity

First you'll need to configure your AndroidManifest.xml to allow for the starting of the activity. Otherwise it'll return with an error stating the activity is not found.

Using Eclipse, open up AndroidManifest.xml and go to the "Application" tab. At the bottom there is an area called "Application Nodes".

Click on the "Add" button, and select "Create a new element at the top level, in Application".


Now select that new new Activity and click on "Browse" for the "Name" field. Select the activity which this meta data will apply to.

Now to create an intent filter for it. Click "Add" again and this time select "Intent filter". Click "Add" again to add the actual intent. I used "Action" with intent "android.intent.action.VIEW" for this example.

Intent filters kind of specify what sort of activity it is. The docs can explain it way better than I can, but the basic concept behind it is to prevent you from starting a "make a call" activity with the intention of "selecting a file". It'll also allow your app to be used as various context handlers on your phone (such as "Send file to; bluetooth, messaging, dropbox, your app, Facebook, etc)".


If you want the XML for it:

<activity android:label="@string/app_name" android:name=".scoring.SubmitScoreActivity">
<action android:name="android.intent.action.VIEW" />

The "activity" element should go under "application".

Your sub-activity setup is now complete.

Firing up the activity

When you're ready, start setting up the intent to start the activity.

Intent intent = new Intent(G.activity, SubmitScoreActivity.class);

That's all there is to it!

Using the submitting score example, the snippet below is used to send information to the activity.

Bundle map = new Bundle();

map.putInt("score", G.gameState.getScore());
map.putString("username", G.settings.username);

intent.putExtra("SubmitScoreActivity", map);

Now remember the thing I said about "fire and forget" and "wait for result"? It's now time for you to choose.

To fire and forget, start the activity with:


To wait for a result, use:

yourActivity.startActivityForResult(intent, ACTIVITY_ID_SUBMIT_SCORE)

The ACTIVITY_ID_SUBMIT_SCORE integer is just an ID that we give it so the callback function can tell which activity finished. I'll explain it a little later.

Note: Your current activity will continue to run even while it is waiting for a result.

Coding new sub-activity

Coding your new sub-activity is no different from creating a normal activity. Just remember that you should be pulling data out of the intent bundle.

public class SubmitScoreActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {


Bundle map = getIntent().getExtras().getBundle("SubmitScoreActivity");

TextView txtScore = (TextView) findViewById(;
int score = map.getInt("score");

So now you know how to start an activity, pass data to it and pull data out.

Ending your sub-activity

So now that you need to end the sub-activity, you have to consider if you want to send data back or not.

If not then call Activity.finish() as you normally would and back you'll go to the main activity.

If you're waiting for a result, you've got a bit more work to do.

When you're ready to go back to the main activity:

Intent intent;

intent = new Intent();
intent.putExtra("score_submitted", true);

setResult(RESULT_OK, intent);

The RESULT_OK means this finished properly. In the same manner you send data to the sub-activity, you can use an Intent to send data back to the main activity.

Receiving the data

If you're waiting for a result, you'll also need to override onActivityResult() in your Activity so you can actually do something with the incoming data.

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // See which sub activity has finished switch (requestCode) { case ACTIVITY_ID_SUBMIT_SCORE: { // Check resultCode to see if it finished correctly if (resultCode == RESULT_OK) { boolean score_submitted = false; if (data != null) { score_submitted = data.getBooleanExtra("score_submitted", false); } if (score_submitted) { Toast toast = Toast.makeText(this.getBaseContext(), "Score submitted", Toast.LENGTH_LONG);; } return; } default: break; } } }

And there you have it. It may be a long explanation, but it took me a while to get that all working properly so I put more detail into the post.

Hope it helps!


django: Group list by date in templates

It is often useful to regroup large lists of data by a date upon displaying.

You can either do this manually in the view, or use a nifty little template tag called "regroup".

First the normal list, which shows you everything "as is" without grouping.

<ul class="ulbox">

{% for draft in drafts %}
<li><a href="#">{{ }}</a> (created {{ draft.created_on }})</li>
{% endfor %}


Now the regrouped template code will be a little trickier, but much more helpful when you're looking at the output.

{% regroup drafts by created_on|date:"d-m-Y" as drafts_by_day %} <h2>Drafts</h2> <ul class="ulbox"> {% for days in drafts_by_day %}
<li><b>{{ days.grouper }}</b></li>

{% for draft in days.list %}
<li><a href="#">{{ }}</a> (created {{ draft.created_on }})</li>
{% endfor %}
{% endfor %}


As you can see, this makes it a lot easier for your eyes to find data while skimming the list.

[ Source ]

Copyright © Twig's Tech Tips
Theme by BloggerThemes & TopWPThemes Sponsored by iBlogtoBlog