<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4002344319997536708</id><updated>2026-09-10T12:47:41.468-04:00</updated><category term="javascript"/><category term="RoR"/><category term="iPhone"/><category term="php"/><category term="ruby on rails"/><category term="App Store"/><category term="Apple"/><category term="android"/><category term="android market"/><category term="animation"/><category term="apps"/><category term="bluetooth"/><category term="broadcom"/><category term="canvas"/><category term="combo box"/><category term="dash"/><category term="dashed line"/><category term="disabled items"/><category term="drawing"/><category term="driver"/><category term="fail"/><category term="google"/><category term="html5"/><category term="iPod Touch"/><category term="java"/><category term="javascript events"/><category term="jcombobox"/><category term="library"/><category term="line"/><category term="mkdir"/><category term="mobile"/><category term="mobile browser"/><category term="mysql"/><category term="prawn"/><category term="prawnto"/><category term="prototype"/><category term="rails"/><category term="resize"/><category term="ruby"/><category term="tethering"/><category term="touch"/><category term="utils"/><title type='text'>Random Thoughts of a Programmer</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>13</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-6311296030388164125</id><published>2017-02-08T20:45:00.002-05:00</published><updated>2017-05-18T23:41:31.604-04:00</updated><title type='text'>Linux on XPS 15 9550/9560 with TB16 Dock [Update:3/29]</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Finally got a laptop to replace my fat tower at work - Dell XPS 15 9560. I was allowed to choose which one I wanted and chose the XPS for its Linux support since Dell ships developer edition XPS&#39;s running Ubuntu so I figured Linux support would be better than other manufacturers. At first they got me the model with the 4K screen but my monitors are 2K and multi-dpi support in Linux is virtually non-existent and even hi-dpi support on its own is pretty terrible. So I got it exchanged for the model with the regular 1080p screen (which happened to also be the updated 9560 model), which works much better. I&#39;m very glad to report that pretty much everything works, including the TB16 desktop dock, with just a bit of settings tweaking. This post is to help anybody considering getting this setup or looking for help getting things working. For now, I am running Kubuntu 16.04 with KDE Neon installed.&lt;br /&gt;
&lt;br /&gt;
List of things I explicitly tested and work:&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;WiFi, Bluetooth&lt;/li&gt;
&lt;li&gt;Thunderbolt charging from TB16 dock&lt;/li&gt;
&lt;li&gt;Dual 2K monitors through dock&lt;/li&gt;
&lt;li&gt;Audio, USB, Ethernet through dock&lt;/li&gt;
&lt;li&gt;Dual boot and VM from raw Windows partition&lt;/li&gt;
&lt;li&gt;Touchscreen (on the 4k model)&lt;/li&gt;
&lt;li&gt;Keyboard backlight&lt;/li&gt;
&lt;li&gt;Power button on dock acting as the regular power button&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
Limitations/things that don&#39;t work or don&#39;t work very well (that I&#39;ve discovered so far):&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Speaker out on the dock (in the back) doesn&#39;t seem to work, but the headphone jack on the front is fine.&lt;/li&gt;
&lt;li&gt;&lt;strike&gt;KDE seems to forget my monitor layout on occasion. This is mildly annoying but I&#39;m hoping this can be fixed soon in software.&lt;/strike&gt;&amp;nbsp;&lt;i&gt;[update 3/29] &lt;/i&gt;It does not forget monitor layout anymore but most windows (Chrome, PhpStorm) don&#39;t remember their placement. Some applications (Sublime, KDE apps, Electron apps) consistently remember their preferred monitor so this seems like a matter of applications implementing window manager hooks correctly.&lt;/li&gt;
&lt;li&gt;&lt;strike&gt;Occasionally hotplugging the dock will cause KDE/Plasma to freeze. Again, mildly annoying but hoping for a fix soon.&lt;/strike&gt;&amp;nbsp;&lt;i&gt;[update 3/29]&lt;/i&gt; This rarely happens but the freeze is a complete system hang not just KDE/Plasma.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;&lt;i&gt;UPDATE 3/29:&lt;/i&gt;&lt;/b&gt;&amp;nbsp;I&#39;ve been using this setup for a couple months now and here&#39;s my experience so far:&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Overall things still work well for the most part&lt;/li&gt;
&lt;li&gt;Hotplugging the dock rarely results in a system hang (completely unresponsive). However, this has only happened a few times in the couple months and so it does not bother me that much, and I have not seen any pattern.&lt;/li&gt;
&lt;li&gt;Booting with the dock plugged in results in some funky font rendering and/or DPI/scaling issues, so I always boot with the dock unplugged and just hotplug it after logging in.&lt;/li&gt;
&lt;li&gt;Sometimes the Ethernet adapter in the dock will stop working (sometimes there will be failures related to SSL, sometimes my dnsmasq setup will stop resolving). There are bug reports for this issue (e.g.&amp;nbsp;https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1667750) but for now taking it down and bringing it up again fixes it for me without rebooting (&lt;code&gt;$ sudo ifconfig enx204747f8f471 down &amp;amp;&amp;amp; sleep 30 &amp;amp;&amp;amp; sudo ifconfig enx204747f8f471 up&lt;/code&gt; - just replace &quot;enx204747f8f471&quot; with your interface name if it&#39;s different). So, mildly annoying but tolerable.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
To help anybody in a similar situation, here is what I did to get everything working.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
First off, let&#39;s tweak some BIOS settings so the hardware plays well with Linux:&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Change SATA to AHCI mode (System Configuration &amp;gt; SATA Operation) - otherwise Linux does not recognize the drive for some reason&lt;/li&gt;
&lt;li&gt;Set Thunderbolt security level to No Security (System Configuration &amp;gt; USB Configuration) - otherwise only the monitors work through the dock&lt;/li&gt;
&lt;li&gt;Turn off Secure Boot (Secure Boot &amp;gt; Secure Boot Enable) - otherwise nvidia and virtualbox drivers do not work as they are not signed by a trusted key&lt;/li&gt;
&lt;li&gt;(personal preference) Change function keys to be F1-12 by default (POST Behavrior &amp;gt; Fn Lock Options, set to Enable/Secondary)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
At this point, due to the change in SATA mode, Windows will refuse to boot, but just go into safe mode and it will fix itself after a reboot. After that, start Windows as normal and install necessary firmware updates (it will reboot for each one):&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;BIOS update (under BIOS at &lt;a href=&quot;http://www.dell.com/support/home/us/en/04/product-support/product/xps-15-9560-laptop/drivers&quot;&gt;http://www.dell.com/support/home/us/en/04/product-support/product/xps-15-9560-laptop/drivers&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Thunderbolt chipset firmware (under Chipset &amp;gt; Thunderbolt 3 Firmware Update at &lt;a href=&quot;http://www.dell.com/support/home/us/en/04/product-support/product/xps-15-9560-laptop/drivers&quot;&gt;http://www.dell.com/support/home/us/en/04/product-support/product/xps-15-9560-laptop/drivers&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
Once that&#39;s done, you can now install Ubuntu. I am installing Kubuntu 16.04 and throwing KDE Neon on top of there to get the latest and greatest (both in terms of looks and functionality and stability). Installation is fairly straightforward, the only problem I encountered was that the Kubuntu installer was unable to automatically partition the driver properly for dual-boot but it was fairly simple to use gparted to set up the root and swap partitions (I chipped off space at the end of the main Windows partition to make space - for me it was between partitions 3 and 4).&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
After Kubuntu was installed, I installed KDE Neon to get the latest Plasma version as it bring significant multi-monitor and hotplug improvements to the desktop environment:&lt;br /&gt;
&lt;code&gt;$ sudo apt-add-repository http://archive.neon.kde.org/user&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E6D4736255751E5D&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;$ sudo apt update&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;$ sudo apt full-upgrade&lt;/code&gt;&lt;/div&gt;
&lt;div&gt;
During the upgrade, there are some conflicts with existing packages, so I had to do &lt;code&gt;$ sudo apt-get install -f&lt;/code&gt; and &lt;code&gt;$ sudo apt full-upgrade&lt;/code&gt; a few times. The install -f will remove the conflicting packages and the full-upgrade will continue the upgrade. Do this a couple times if necessary.&lt;br /&gt;
Neon also renames the distribution name which will break later calls to add-apt-repository. There is a simple fix, however, just replace &quot;neon&quot; with &quot;Ubuntu&quot; (case-sensitive) in the /etc/lsb-release file.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Optionally, if you want, you can make Virtualbox boot the raw Windows partition as a virtual machine, so you can have both dual-boot and a VM without taking up double the disk space (the only caveat is that you have to be sure to unmount the Windows partition in Linux if you have it mounted). To do so, first add yourself to the disk group so you can access raw partition information by running &lt;code&gt;$ sudo usermod -a -G disk $(whoami)&lt;/code&gt;. Log out and log back in for changes to take effect. After that, run &lt;code&gt;$ VBoxManage internalcommands createrawvmdk -filename RawDisk.vmdk -rawdisk /dev/nvme0n1&lt;/code&gt; to create the VMDK that points to the raw disk. Then set up the VM in Virtualbox as you would with any existing VMDK. The only critical setting is to enable EFI in the VM&#39;s System settings. The settings I ended up with are:&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;2 GB RAM&lt;/li&gt;
&lt;li&gt;1 CPU (PAE/NX enabled&lt;/li&gt;
&lt;li&gt;2D and 3D acceleration enabled with 256 MB VRAM&lt;/li&gt;
&lt;li&gt;USB 3.0 controller (requires Virtualbox extension pack)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
So after all that, everything is operational and I can dock the laptop with just one cable, dual-boot between Linux and Windows, and boot to the Windows partition from Virtualbox. If anyone has any particular functionality to test, let me know in the comments.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/6311296030388164125/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2017/02/linux-on-xps-15-95509560-with-tb16-dock.html#comment-form' title='32 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/6311296030388164125'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/6311296030388164125'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2017/02/linux-on-xps-15-95509560-with-tb16-dock.html' title='Linux on XPS 15 9550/9560 with TB16 Dock [Update:3/29]'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>32</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-3002895792473132397</id><published>2013-01-30T19:40:00.001-05:00</published><updated>2013-01-30T19:57:53.480-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="animation"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="library"/><title type='text'>Animating objects in JavaScript</title><content type='html'>I needed a way to animate any property of an object in JavaScript (not necessarily even an element on the page). I did not find anything satisfactory (to me) online, so I decided to write my own. This is the result.&lt;br /&gt;
&lt;br /&gt;
It&#39;s a simple animation class, loosely modeled on the Android animations API (in fact, the built-in animation interpolators are simple translations from the Android source code). It is very flexible, and supports animating &lt;b&gt;any&lt;/b&gt;&amp;nbsp;numeric property of &lt;b&gt;any&lt;/b&gt; object. Animations run at 60 Hz to be fluid if UI elements are being animated.&lt;br /&gt;
&lt;br /&gt;
Features:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Animate any numeric value&lt;/li&gt;
&lt;li&gt;Start/stop, pause/resume/restart&lt;/li&gt;
&lt;li&gt;Repeat - for a specified count or infinitely&lt;/li&gt;
&lt;li&gt;Reverse animation on repeat.&lt;/li&gt;
&lt;li&gt;Callbacks for start, stop, resume, end, repeat&lt;/li&gt;
&lt;li&gt;Custom interpolators&lt;/li&gt;
&lt;li&gt;NO dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;a href=&quot;http://vetruvet.com/demos/animation/animation.js&quot; target=&quot;_blank&quot; onclick=&quot;_gaq.push([&#39;_trackEvent&#39;,&#39;Download&#39;,&#39;JS&#39;,this.href]);&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Download the code here&lt;/span&gt;&lt;/b&gt;&lt;/a&gt; (includes documentation for all methods).&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
A demo of the code is&amp;nbsp;&lt;a href=&quot;http://vetruvet.com/demos/animation/&quot; target=&quot;_blank&quot; onclick=&quot;_gaq.push([&#39;_trackEvent&#39;,&#39;View Demo&#39;,&#39;JS&#39;,this.href]);&quot;&gt;&lt;b&gt;available here&lt;/b&gt;&lt;/a&gt;. It simply animates a box moving to the right, but demonstrates how the class is used. Prototype is used in the demo, but not required for the animation class itself.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
This class will even work with discrete properties, as long as the animated property callback is written for that (example below).&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;Example Usage:&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;//make the y property of the coords object fall and bounce from 100 to 0 in 5 seconds&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;var coords = {x: 100, y: 100};&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;new Animation(coords, function(obj, val) { obj.y = val; }, 100, 0, 5000, Animation.Interpolators.Bounce()).start();&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;//animated a box moving 500px to the right in 1 second&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;//when the animation is over, set the animation reference to null&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;var anim = new Animation($(&quot;box&quot;), function(obj, val) { obj.style.left = val + &quot;px&quot;; }, 0, 500, 1000,&amp;nbsp;Animation.Interpolators.Accelerate(),&amp;nbsp;{end: function() { anim = null; }});&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;anim.start();&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;//animate a continuously-blinking box&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;var anim = new Animation($(&quot;box&quot;), function(obj, val) { obj.setOpacity(val); }, 0, 1, 500,&amp;nbsp;Animation.Interpolators.AccelerateDecelerate());&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;anim.setRepeat(-1).setRepeatReverse(true);&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;anim.start();&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;//can do anim.stop() and anim.resume() to pause/resume the blinking&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;//animate through a set of discrete values&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;var colors = [&quot;red&quot;, &quot;yellow&quot;, &quot;green&quot;, &quot;blue&quot;, &quot;black&quot;];&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;var anim = new Animation($(&quot;box&quot;), function(obj, val) {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;obj.style.background = colors[Math.floor(val) % colors.length];&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;color: #93c47d; font-family: Courier New, Courier, monospace;&quot;&gt;}, 0, colors.length, 500 * colors.length, Animation.Interpolators.Linear()).setRepeat(-1).start();&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
I hope that this will become useful to people working with animations in JavaScript. Please leave feedback and suggestions on how to improve this, or any features that you would like to see.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/3002895792473132397/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2013/01/animating-objects-in-javascript.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/3002895792473132397'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/3002895792473132397'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2013/01/animating-objects-in-javascript.html' title='Animating objects in JavaScript'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-6847024120734088947</id><published>2012-08-09T11:39:00.000-04:00</published><updated>2012-08-09T11:39:08.037-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="mkdir"/><category scheme="http://www.blogger.com/atom/ns#" term="php"/><category scheme="http://www.blogger.com/atom/ns#" term="utils"/><title type='text'>Super mkdir helper function for php</title><content type='html'>Here&#39;s a handy little function to force-create directories in PHP.&lt;br /&gt;
&lt;br /&gt;
If the directory does not exist, it will create it. If there is a file with such a name, it will delete that file and create the directory in its place.&lt;br /&gt;
&lt;br /&gt;
Params: same as&amp;nbsp;&lt;a href=&quot;http://us.php.net/manual/en/function.mkdir.php&quot;&gt;http://us.php.net/manual/en/function.mkdir.php&lt;/a&gt;&lt;br /&gt;
Returns: true on success (the given path is now a directory), false on error&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;function super_mkdir($file, $mode = 0777, $recursive = false) {
    if (!file_exists($file)) return mkdir($file, $mode, $recursive);
    if (is_dir($file)) return true;
    if (!unlink($file)) return false;
    if (!mkdir($file, $mode, $recursive)) return false;
    return true;
}
&lt;/pre&gt;
&lt;br /&gt;
I will possibly have a better version later which would return error codes instead of just false...</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/6847024120734088947/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2012/08/super-mkdir-helper-function-for-php.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/6847024120734088947'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/6847024120734088947'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2012/08/super-mkdir-helper-function-for-php.html' title='Super mkdir helper function for php'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-8875076415987423969</id><published>2011-06-08T12:52:00.000-04:00</published><updated>2011-06-08T12:52:55.726-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bluetooth"/><category scheme="http://www.blogger.com/atom/ns#" term="broadcom"/><category scheme="http://www.blogger.com/atom/ns#" term="driver"/><category scheme="http://www.blogger.com/atom/ns#" term="fail"/><title type='text'>Broadcom Bluetooth Driver: Installer or Virus??</title><content type='html'>My primary computer at the moment is an HP dv6 laptop (with Broadcom 2070 Bluetooth chip). Upon getting my laptop, I immediately wiped the hard drive and slapped on a fresh copy of Windows 7. Installed the drivers from HP, and everything was rolling along nicely...&lt;br /&gt;
&lt;br /&gt;
Fast-forward 4 months... Now I&#39;m having some issues with Bluetooth communication between my phone (Android) and my laptop. So I reinstall the Bluetooth driver... The installation took a &lt;b&gt;long&lt;/b&gt;&amp;nbsp;time, but eventually errored-out, and quit. Of course, I had left the computer unattended during this, and came back to an almost-empty hard drive (!!!!). Restored for backup, some minor data loss, no problem... I thought I had snagged a virus in the download (even though it was direct from HP&#39;s site). Redownload, scan with multiple scanners, run it ....... SAME RESULT!!! Giving up on the official installer, I just took the drivers that the installer unpacked, and manually installed them... That didn&#39;t solve my original problem, but it did get me back to a somewhat-OK state, without the desired bluetooth functionality...&lt;br /&gt;
&lt;br /&gt;
Fast-forward another 6 months... I notice that there is an update to the Bluetooth drivers from HP. FINALLY! Excited, I download the new driver and try to install it... it gives &quot;error 1935...&quot; ...not good, but not as bad as before... I google around, and find that it would help to uninstall the old driver first. So I uninstall the drivers I manually installed, and also find a trace in Programs and Features. After starting the uninstaller, I notice my hard drive free space is going up. QUICKLY. ... SHIT. hard shutdown, lost several dozen gigs of stuff... Turns out it was mostly games (C:\Games is killed before C:\Program Files), so the good stuff was untouched. close call...&lt;br /&gt;
&lt;br /&gt;
Here&#39;s my thoughts on this at that point: &quot;The installer won&#39;t finish, unless I run the uninstaller, which will try to delete everything... great!&quot;&lt;br /&gt;
&lt;br /&gt;
At this point, I try to get the driver from Broadcom&#39;s site. Looks promising, but hangs every time on &quot;Validating Device&quot; (waited for &lt;b&gt;hours&lt;/b&gt;). Well, at least it doesn&#39;t try to delete everything...&lt;br /&gt;
&lt;br /&gt;
Now, I try to run the (new, from HP) installer again, figuring maybe something hidden has changed and it would now magically work... No such luck. In fact, the &lt;b&gt;new, virus-free, direct-from-HP &lt;/b&gt;installer also tries to delete everything from my hard drive.&lt;br /&gt;
&lt;br /&gt;
I&#39;ve also tried reinstalling the previous version (not sure if this is the original one or not), but that also tries to delete everything from my hard drive...&lt;br /&gt;
&lt;br /&gt;
Great. I got NOTHING (in regards to Bluetooth)! I&#39;ve lost count of the number of times I&#39;ve used Shadow Copy to get my crap back after watching the installers start trying to delete everything...&lt;br /&gt;
&lt;br /&gt;
So, now I&#39;m stuck without any Bluetooth on my laptop. An inconvenience, but nonetheless quite annoying...&lt;br /&gt;
&lt;br /&gt;
But the most important question I have is: If you&#39;re a developer, and you&#39;re writing drivers (for whatever device), and you package that into an (un-)installer, how is there even a remote chance of that (un-)installer deleting (or at least attempting to delete)&amp;nbsp;&lt;b&gt;EVERYTHING&lt;/b&gt;&amp;nbsp;from your hard drive. Wouldn&#39;t that come up as a critical bug during testing? Or are the developers at Broadcom/HP drunk off their asses and think &quot;ahh, sometimes this thing will delete everything off the hard drive... who cares? just ship it already... we&#39;ll just blame that on some sort of virus and tell the user to be more careful&quot; WTF?&lt;br /&gt;
&lt;br /&gt;
Has anybody had this problem? Is it possible to fix it without reinstalling Windows? Or anything else I could possibly try?&lt;br /&gt;
&lt;br /&gt;
Oh, and in case this even needs to be said at this point: DON&#39;T INSTALL BROADCOM BLUETOOTH DRIVERS FROM HP!!!&lt;br /&gt;
&lt;br /&gt;
P.S. Safe Mode didn&#39;t help -- none of the installers would run (the Installer service is not started in Safe Mode)&lt;br /&gt;
P.P.S. Yes, I have scanned my entire computer with multiple virus scanners, both local and online. Clean as a whistle.</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/8875076415987423969/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2011/06/broadcom-bluetooth-driver-installer-or.html#comment-form' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/8875076415987423969'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/8875076415987423969'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2011/06/broadcom-bluetooth-driver-installer-or.html' title='Broadcom Bluetooth Driver: Installer or Virus??'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-8568805096215962165</id><published>2011-03-31T11:51:00.000-04:00</published><updated>2011-03-31T11:51:34.332-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="combo box"/><category scheme="http://www.blogger.com/atom/ns#" term="disabled items"/><category scheme="http://www.blogger.com/atom/ns#" term="java"/><category scheme="http://www.blogger.com/atom/ns#" term="jcombobox"/><title type='text'>JComboBox with Disabled Items</title><content type='html'>Recently, I was working on a project in Java and needed to have a combo box, but with certain items in the list disabled (e.g. gray and non-selectable). At first, I simply set a custom renderer for the combo box which checked if the item was disabled. That, however, did not prevent the items from being selected. Thus, I set about to find a viable solution. There are plenty of solutions out there, but none seemed to work exactly the way I wanted. In the end, I ended up subclassing JComboBox to provide the functionality of disabling individual items. Here is my result, in under 100 lines:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;import java.awt.Component;
import java.util.ArrayList;

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

public class PartialDisableComboBox extends JComboBox {
 private static final long serialVersionUID = -1690671707274328126L;
 
 private ArrayList&amp;lt;boolean&amp;gt; itemsState = new ArrayList&amp;lt;boolean&amp;gt;();
 
 public PartialDisableComboBox() {
  super();
  this.setRenderer(new BasicComboBoxRenderer() {
   private static final long serialVersionUID = -2774241371293899669L;
   @Override
   public Component getListCellRendererComponent(JList list, Object value, 
     int index, boolean isSelected, boolean cellHasFocus) {
    Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
    boolean disabled = index &amp;gt;= 0 &amp;amp;&amp;amp; index &amp;lt; itemsState.size() &amp;amp;&amp;amp; !itemsState.get(index);
    c.setEnabled(!disabled);
    c.setFocusable(!disabled);
    return c;
   }
  });
 }
 
 @Override
 public void addItem(Object item) {
  this.addItem(item, true);
 }
 
 public void addItem(Object item, boolean enabled) {
  super.addItem(item);
  itemsState.add(enabled);
 }
 
 @Override
 public void insertItemAt(Object item, int index) {
  this.insertItemAt(item, index, true);
 }

 public void insertItemAt(Object item, int index, boolean enabled) {
  super.insertItemAt(item, index);
  itemsState.add(index, enabled);
 }
 
 @Override
 public void removeAllItems() {
  super.removeAllItems();
  itemsState.clear();
 }
 
 @Override
 public void removeItemAt(int index) {
  if (index &amp;lt; 0 || index &amp;gt;= itemsState.size()) throw new IllegalArgumentException(&quot;Item Index out of Bounds!&quot;);
  super.removeItemAt(index);
  itemsState.remove(index);
 }
 
 @Override
 public void removeItem(Object item) {
  for (int q = 0; q &amp;lt; this.getItemCount(); q++) {
   if (this.getItemAt(q) == item) itemsState.remove(q);
  }
  super.removeItem(item);
 }
 
 @Override
 public void setSelectedIndex(int index) {
  if (index &amp;lt; 0 || index &amp;gt;= itemsState.size()) throw new IllegalArgumentException(&quot;Item Index out of Bounds!&quot;);
  if (itemsState.get(index)) super.setSelectedIndex(index);
 }
 
 public void setItemEnabled(int index, boolean enabled) {
  if (index &amp;lt; 0 || index &amp;gt;= itemsState.size()) throw new IllegalArgumentException(&quot;Item Index out of Bounds!&quot;);
  itemsState.set(index, enabled);
 }
 
 public boolean isItemEnabled(int index) {
  if (index &amp;lt; 0 || index &amp;gt;= itemsState.size()) throw new IllegalArgumentException(&quot;Item Index out of Bounds!&quot;);
  return itemsState.get(index);
 }
}
&lt;/pre&gt;&lt;br /&gt;
As you can see, most of the code is pretty straightforward, adding and removing elements from the JComboBox and the list of item states. The custom renderer is still used to provide the L&amp;amp;F for the disabled items, but the key to having the disabled items non-selectable is the &lt;tt&gt;setSelectedIndex(int)&lt;/tt&gt; method. There are also plenty of index range checks thrown in there, especially in the custom renderer, as the combo box calls that method with &lt;tt&gt;index = -1&lt;/tt&gt; when rendering the combo box in its &quot;closed&quot; state (without the dropdown).&lt;br /&gt;
&lt;br /&gt;
Of course, I want give credit to some other blogs that helped lay the groundwork and point me in the right direction, but unfortunately I don&#39;t remember their names. If I find them again, I&#39;ll be sure to include them in this post.&lt;br /&gt;
&lt;br /&gt;
As always, please let me know if you find this useful, or if you find any bugs or possible improvements.</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/8568805096215962165/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2011/03/jcombobox-with-disabled-items.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/8568805096215962165'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/8568805096215962165'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2011/03/jcombobox-with-disabled-items.html' title='JComboBox with Disabled Items'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-3765725183721302282</id><published>2011-01-04T11:34:00.000-05:00</published><updated>2011-01-04T11:34:34.225-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="prototype"/><category scheme="http://www.blogger.com/atom/ns#" term="resize"/><title type='text'>Listening for Window Resize events with Prototype</title><content type='html'>Recently, I came across an issue I had with full-screen (more specifically, full-viewport) canvas drawing. It all worked fine, except that the canvas dimensions must be specified in pixels, as opposed to percentages. This means you can&#39;t just set the canvas&#39; width and height to &quot;100%&quot; and be done with it...&lt;br /&gt;
&lt;br /&gt;
The dimensions were automatically set when the page loaded, but if the user resized the browser window after that, the canvas would stay the same size, and would be either too big or too small. Thus, the canvas must be resized every time the browser window is resized.&lt;br /&gt;
&lt;br /&gt;
Prototype makes all of this easy. All that&#39;s necessary is to attach a listener to the &lt;tt&gt;onresize&lt;/tt&gt; event of the window object, and then use &lt;tt&gt;document.viewport.getDimensions()&lt;/tt&gt; to determine the new width and height.&lt;br /&gt;
&lt;br /&gt;
Here&#39;s some sample code:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;Event.observe(window, &quot;resize&quot;, function() {
    var width = document.viewport.getWidth();
    var height = document.viewport.getHeight();
    var dims = document.viewport.getDimensions(); // dims.width and dims.height
});
&lt;/pre&gt;&lt;br /&gt;
Alternatively, you can use &lt;tt&gt;document.viewport.getWidth()&lt;/tt&gt; or &lt;tt&gt;document.viewport.getHeight()&lt;/tt&gt; instead of &lt;tt&gt;document.viewport.getDimensions()&lt;/tt&gt;.&lt;br /&gt;
&lt;br /&gt;
This code works in all major browsers, IE included.</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/3765725183721302282/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2011/01/listening-for-window-resize-events-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/3765725183721302282'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/3765725183721302282'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2011/01/listening-for-window-resize-events-with.html' title='Listening for Window Resize events with Prototype'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-5449471778275962729</id><published>2010-12-22T10:59:00.000-05:00</published><updated>2010-12-22T10:59:57.243-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="iPhone"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript events"/><category scheme="http://www.blogger.com/atom/ns#" term="mobile"/><category scheme="http://www.blogger.com/atom/ns#" term="mobile browser"/><category scheme="http://www.blogger.com/atom/ns#" term="touch"/><title type='text'>Converting single-touch events to mouse events</title><content type='html'>Many times, there are various interactions that take place in a web app based on mouse movement, clicking, dragging, etc. With a mouse, this is fairly simple, as there is a single point, and events like mousedown, mouseup, and mousemove can be used to discover the state of the mouse at any given time.&lt;br /&gt;
&lt;br /&gt;
On mobile browsers, however, it&#39;s a different story. Most phones support at least some kind of multi-touch, and the mousedown and mouseup events are not fired (there is, after all, no button). In addition, mousemove will only be applicable when dragging. Instead, mobile browsers dispatch touchstart, touchmove, and touchend events. Moreover, these events keep track of all touch points, not just the first or last one, and as such make handling them a bit more difficult.&lt;br /&gt;
&lt;br /&gt;
For the purposes of an application that only requires tracking clicks and drags, it is more convenient to translate these touch events into mouse events without having code duplication. This can be accomplished quickly in several steps. First, we want to ignore events with more than one touch point, as this is used by many mobile browsers to zoom and/or scroll. Then, we want to get the information from the touch event and simulate a mouse event using that data. And finally, we dispatch the simulated event and prevent the default response to the touch event.&lt;br /&gt;
&lt;br /&gt;
In code, it looks like this (replace &lt;tt&gt;element&lt;/tt&gt; with whatever element you want the listener on):&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;var touchToMouse = function(event) {
    if (event.touches.length &gt; 1) return; //allow default multi-touch gestures to work
    var touch = event.changedTouches[0];
    var type = &quot;&quot;;
    
    switch (event.type) {
    case &quot;touchstart&quot;: 
        type = &quot;mousedown&quot;; break;
    case &quot;touchmove&quot;:  
        type=&quot;mousemove&quot;;   break;
    case &quot;touchend&quot;:   
        type=&quot;mouseup&quot;;     break;
    default: 
        return;
    }
    
    // https://developer.mozilla.org/en/DOM/event.initMouseEvent for API
    var simulatedEvent = document.createEvent(&quot;MouseEvent&quot;);
    simulatedEvent.initMouseEvent(type, true, true, window, 1, 
            touch.screenX, touch.screenY, 
            touch.clientX, touch.clientY, false, 
            false, false, false, 0, null);
    
    touch.target.dispatchEvent(simulatedEvent);
    event.preventDefault();
};
element.ontouchstart = touchToMouse;
element.ontouchmove = touchToMouse;
element.ontouchend = touchToMouse;
&lt;/pre&gt;&lt;br /&gt;
And a closure-compiled version:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;var touchToMouse=function(b){if(!(b.touches.length&gt;1)){var a=b.changedTouches[0],c=&quot;&quot;;switch(b.type){case &quot;touchstart&quot;:c=&quot;mousedown&quot;;break;case &quot;touchmove&quot;:c=&quot;mousemove&quot;;break;case &quot;touchend&quot;:c=&quot;mouseup&quot;;break;default:return}var d=document.createEvent(&quot;MouseEvent&quot;);d.initMouseEvent(c,true,true,window,1,a.screenX,a.screenY,a.clientX,a.clientY,false,false,false,false,0,null);a.target.dispatchEvent(d);b.preventDefault()}};
element.ontouchstart=touchToMouse;
element.ontouchmove=touchToMouse;
element.ontouchend=touchToMouse;
&lt;/pre&gt;&lt;br /&gt;
I hope you find this useful in any mobile web apps you&#39;re making. However, this will only track one touch point, so if you require more control over multi-touch features, I recommend checking out&amp;nbsp;&lt;a href=&quot;http://www.sitepen.com/blog/2008/07/10/touching-and-gesturing-on-the-iphone/&quot;&gt;this guide&lt;/a&gt;. Though the title suggests that this is iPhone-only, it also applies to the default browser on Android, as it fires the same touch events.</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/5449471778275962729/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2010/12/converting-single-touch-events-to-mouse.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/5449471778275962729'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/5449471778275962729'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2010/12/converting-single-touch-events-to-mouse.html' title='Converting single-touch events to mouse events'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-1085175918641739832</id><published>2010-10-27T16:00:00.000-04:00</published><updated>2010-12-15T11:36:54.316-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="canvas"/><category scheme="http://www.blogger.com/atom/ns#" term="dash"/><category scheme="http://www.blogger.com/atom/ns#" term="dashed line"/><category scheme="http://www.blogger.com/atom/ns#" term="drawing"/><category scheme="http://www.blogger.com/atom/ns#" term="html5"/><category scheme="http://www.blogger.com/atom/ns#" term="line"/><title type='text'>Drawing Dashed Lines on an HTML5 Canvas</title><content type='html'>The canvas element in HTML is great, but has one strange shortcoming: it cannot draw dashed lines (natively). However, dashed lines seem like a pretty common thing to draw, which only highlights the problem.&lt;br /&gt;
&lt;br /&gt;
Looking around, I&#39;ve noticed several solutions to this problem. Some use trig, and others use their own libraries that must be imported. So in the end, I decided to create my own method.&lt;br /&gt;
&lt;br /&gt;
This code will add the function to all canvas elements, both those already on the page, and any that are dynamically added later.&lt;br /&gt;
&lt;br /&gt;
Here is the code:&lt;br /&gt;
&lt;pre&gt;CanvasRenderingContext2D.prototype.dashedLine = function(x1, y1, x2, y2, dashLen) {
    if (dashLen == undefined) dashLen = 2;
    
    this.beginPath();
    this.moveTo(x1, y1);
    
    var dX = x2 - x1;
    var dY = y2 - y1;
    var dashes = Math.floor(Math.sqrt(dX * dX + dY * dY) / dashLen);
    var dashX = dX / dashes;
    var dashY = dY / dashes;
    
    var q = 0;
    while (q++ &amp;lt; dashes) {
     x1 += dashX;
     y1 += dashY;
     this[q % 2 == 0 ? &#39;moveTo&#39; : &#39;lineTo&#39;](x1, y1);
    }
    this[q % 2 == 0 ? &#39;moveTo&#39; : &#39;lineTo&#39;](x2, y2);
    
    this.stroke();
    this.closePath();
};
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
To use this, just plop this code into your page, so that it executes before you use it (onload, head, top of body, etc). The method takes 4 or 5 parameters: the start and end coordinate pairs, and (optionally) the dash length. If the dash length is not specified, it will default to 2 pixels. When you need to use it, simply call it on the context object (e.g. context.dashedLine(100, 100, 200, 200, 3);).&lt;br /&gt;
&lt;br /&gt;
Feel free to use this anytime you need it, and let me know in the comments if you like this or if you have a better solution.&lt;br /&gt;
&lt;br /&gt;
P.S. For those worried about size, here is the same code, but minified using the Closure Compiler:&lt;br /&gt;
&lt;pre&gt;CanvasRenderingContext2D.prototype.dashedLine=function(d,e,g,h,a){if(a==undefined)a=2;this.beginPath();this.moveTo(d,e);var b=g-d,c=h-e;a=Math.floor(Math.sqrt(b*b+c*c)/a);b=b/a;c=c/a;for(var f=0;f++&amp;lt;a;){d+=b;e+=c;this[f%2==0?&quot;moveTo&quot;:&quot;lineTo&quot;](d,e)}this[f%2==0?&quot;moveTo&quot;:&quot;lineTo&quot;](g,h);this.stroke();this.closePath()};
&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/1085175918641739832/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2010/10/drawing-dashed-lines-on-html5-canvas.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/1085175918641739832'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/1085175918641739832'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2010/10/drawing-dashed-lines-on-html5-canvas.html' title='Drawing Dashed Lines on an HTML5 Canvas'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-3290128652318723680</id><published>2010-10-18T13:58:00.000-04:00</published><updated>2010-10-18T13:58:10.681-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><category scheme="http://www.blogger.com/atom/ns#" term="android market"/><category scheme="http://www.blogger.com/atom/ns#" term="apps"/><category scheme="http://www.blogger.com/atom/ns#" term="google"/><category scheme="http://www.blogger.com/atom/ns#" term="tethering"/><title type='text'>What Google&#39;s Relationship with Vendors Should be like in Regards to Android</title><content type='html'>I&#39;ve been watching Android since it was first released, and got myself a Nexus One about half a year ago. The potential this platform has is very nice, and not just for smartphones. I would really like to see the tablets that will come with Android. But this isn&#39;t about me discussing Android itself. It&#39;s about the vendors who sell Android handsets and tablets, and their relationship with Google.&lt;br /&gt;
&lt;br /&gt;
Android is great for its customizability. Unfortunately, vendors have taken this too far and made a perversion of the platform with forced apps, home screens, missing features, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;Forced Apps&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
If I don&#39;t use/want an app on my phone, I should be able to remove it at will, unless it is a system app (e.g. removing the Dialer app would be plain stupid). But carriers and manufacturers insist on putting apps on the users&#39; phones that cannot be removed unless the user roots their phone (which will void the warranty). Why? If the app really is good, I will keep it, use it, and not delete it. If it&#39;s not, why have it waste space on my phone? Does it make any sense? Perhaps the app generates revenue by other means (e.g. music store). But if I&#39;m not using it, it&#39;s not really generating any revenue, is it?&lt;br /&gt;
&lt;br /&gt;
Google surprised me on this one by forcing the Amazon MP3 app on the Nexus One. Of course, it was no big deal because I rooted it when I got it. Nevertheless, I get all of my music on my computer, so I never have the need to use the app. Why not let me remove it? If Amazon pays Google to preload it, that&#39;s fine, but making it&amp;nbsp;irremovable&amp;nbsp;it not.&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;Custom Home Screen Apps&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
I toyed around with a Droid X my friend got, and &lt;i&gt;hated&lt;/i&gt; their home screen app from the beginning. At first, I thought some of the features were interesting and perhaps even useful, but after a couple minutes, I felt they usually got in my way and made my experience unpleasant. Maybe it&#39;s because I&#39;m used to ADW.Launcher, but that resembles the stock launcher a lot, and shouldn&#39;t all Android installs have the stock launcher at least available? And if you get ADW.Launcher on the Droid X, you still can&#39;t remove the default one, because then the phone would have no fall-back. And so it wastes space, again.&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;Missing Features&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The big name that comes up here is tethering. To me, charging extra for tethering doesn&#39;t make any sense. It&#39;s simply an extra fee on top of the already-exorbitant prices for cell phone plans. It doesn&#39;t cost the carrier &lt;b&gt;anything&lt;/b&gt;! Some people will say, &quot;But people use the Internet more on their computers than on their cell phones.&quot; That&#39;s right. At the same time, does it really cost the company more if I tether my computer to my phone and download half a gig of data than if I download half a gig of data on my phone and then transfer it to my computer? I think not. &lt;i&gt;It&#39;s just a cash cow&lt;/i&gt;. If carriers are worried about data usage, set transfer limits. It&#39;s that simple. If I&#39;m paying for &quot;unlimited&quot; data, I should be getting unlimited data. Whether my phone forwards that right on my computer is none of the carrier&#39;s business. If that data isn&#39;t really unlimited (tethering fees), then it&#39;s just a marketing scam (though that shouldn&#39;t come as too much of a surprise).&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;What Google Should Do&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
So now we know the problem, but what&#39;s the solution? Well, the solution lies with the Android&#39;s maker, Google. Google has control over what vendors/carriers can and cannot do with Android. However, Android is open-source. So if Google imposes any restrictions, the vendor will simply use the source code and modify it to suit their needs.&lt;br /&gt;
&lt;br /&gt;
There&#39;s one big level Google has over this. It has the power to &lt;i&gt;license&lt;/i&gt; (or not) the Market app (and other closed-source apps). So it can basically enforce compliance with a policy if a vendor/carrier wants to include the Android Market on their device. If they get around that and distribute anyway, it will be unauthorized use, as in the case of Augen&#39;s GenTouch tablet, and Google could potentially sue (disclaimer: IANAL).&lt;br /&gt;
&lt;br /&gt;
So there is a way for Google to enforce policies. The final part of this is what to enforce. Whether you agree or not (and please let me know), here is what I think Google should enforce:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;b&gt;Ship stock Android&lt;/b&gt;. No modifications to the core OS. Some will say this defeats the purpose of having Android be open source, but this isn&#39;t about open source. Vendors are free to modify just as before, they just can&#39;t ship it with the Android Market. My reasons for this are as follows: (1) stock Android will get updated when a new version of Android is released; (2) vendors can&#39;t do nasty things like turn off tethering; (3) this eliminates part of the problem with fragmentation.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;No irremovable apps&lt;/b&gt; (except system apps). This one is a no-brainer. If the vendor/carrier wants to include their own home screen or their own or some nifty app or whatever else, that&#39;s all good. But if the user does not want it, they have the power to remove it. And Amazon MP3 does not count as a system app - the phone works just fine without it.&lt;/li&gt;
&lt;li&gt;[optional] &lt;b&gt;no root restrictions&lt;/b&gt;. This is optional because the average user doesn&#39;t care. And one of the reasons for rooting is to remove those irremovable apps.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;The only change to this would be corporate phones. These need the centralized access control, but not from the vendor of the phone. The control needs to be in the hands of corporations. Then, corporations could force &amp;nbsp;apps on their phones for security or productivity and still not be bound by vendor limitations. This is a valid concern because: (1) the phone is typically corporate property, not personal; (2) data-sensitive corporate operations may require extra functionality; (3) obviously, security reasons.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;As I was writing this, I noticed a parallel between this and Windows. Windows PCs always ship with stock Windows (though Windows being closed-source does help this point). Windows PCs often include crapware, but it is certainly not irremovable. Windows PCs always have admin access. Windows PCs allow for centralized corporate control. Certainly, if Windows came customized with irremovable crapware and an unavailable admin account, many Windows users would jump ship, right?&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;So, do you agree with this vision? Or disagree? Maybe a bit of both? Let me know in the comments!&lt;/b&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/3290128652318723680/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2010/10/what-googles-relationship-with-vendors.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/3290128652318723680'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/3290128652318723680'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2010/10/what-googles-relationship-with-vendors.html' title='What Google&#39;s Relationship with Vendors Should be like in Regards to Android'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-1766910764499740143</id><published>2010-09-01T08:54:00.000-04:00</published><updated>2010-09-01T08:54:09.530-04:00</updated><title type='text'>My Views on Code Indentation</title><content type='html'>I have read many, &lt;i&gt;many&lt;/i&gt; articles about the whole tab vs. space indentation thing. Personally, I don&#39;t necessarily agree with most of them. They will require the coder to use a specific indentation size and stick with it, even forcing that on other coders.&lt;br /&gt;
&lt;br /&gt;
First off, let me outline my method for indenting code. Then I will explain the reasons and advantages/disadvantages.&lt;br /&gt;
&lt;br /&gt;
When I indent code, I will use tabs, but only at the beginning of a line. To align something in the rest of the line, I will use spaces. If a line spills to the next line(s), I will indent that line two tabs further.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;i&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: x-large;&quot;&gt;Rationale:&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: x-large;&quot;&gt;Tabs&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
Why tabs? First off, they&#39;re compact in the file (1 byte each). This is really insignificant with current disk sizes, but still. If you indent in spaces, then your file will be larger (unless you indent with one space).&lt;br /&gt;
Another advantage of tabs is that a tab is a tab. It doesn&#39;t specify by how many spaces the code is indented, but rather by how many tabs it is indented. That means if a coder likes 2-space indentation, they can set up their editor to display tabs as two spaces. Or 4 spaces, or even the crazy (?) Microsoft 8 space method. With tabs, you are not forcing your indentation style on other coders. Two different coders can view the same exact (byte-by-byte) file with 2, 3, 4, 8 (whatever, go nuts) spaces per tab.&lt;br /&gt;
One more advantage: the Tab key. Why would pressing a tab key insert spaces? There&#39;s a space bar for that. It makes logical sense that a Tab key would insert a tab.&lt;br /&gt;
One last advantage is that it&#39;s easier to delete the indentation. If your Tab key automatically inserts 4 spaces, then you need to hit Backspace 4 times to get rid of &lt;i&gt;one press&lt;/i&gt; of the Tab key! At least for me, that&#39;s pretty inconvenient.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: x-large;&quot;&gt;Spaces&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
If something inside of the line needs to be aligned to make code more readable, spaces should be used instead. The reason? If it needs to be lined up, you need to specify &lt;i&gt;exactly&lt;/i&gt; how many spaces are needed. That way, two editors with different tab sizes will still properly align the code inside of the line.&lt;br /&gt;
This isn&#39;t about forcing your indentation size on other coders, it&#39;s about making your code readable, which means your code has to be aligned exactly, regardless of how many spaces are specified as one tab.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: x-large;&quot;&gt;Multi-line Lines&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
One logical line can occupy multiple physical (not in the literal sense) lines in the file. When that happens, the rest of the physical lines after the first should be indented two extra tabs in. Why? It&#39;s easy to see that that line is a continuation of the previous one. If it was one extra tab, it could get confused with a block of code that gets indented one tab in. Obviously if it was no extra tabs, it wouldn&#39;t be set apart in any way and you&#39;d be stuck wondering how someone could miss so many semicolons! More than two doesn&#39;t make sense, as anything past two extra tabs will just shorten your available horizontal space without adding any more clarity to the code.&lt;br /&gt;
Some people I&#39;ve seen like to indent their code up to a&amp;nbsp;parenthesis&amp;nbsp;or some other significant mark. With tabs, of course, that&#39;s not possible because of the varying tab sizes. What about spaces after the initial indentation with tabs? Wouldn&#39;t it be the same as aligning with spaces? That is a good point, and can be done when the significant marker is not very far in the line, but what happens when that parenthesis or whatever marker is far into the line? You end up limiting your horizontal space at the expense of making your code a little more organized. From here, it&#39;s a cost-benefit analysis. How much horizontal space would you be willing to give up for a certain amount of organization? In the end, I&#39;ve found that indenting with two extra tabs gives enough clarity to the code without sacrificing a lot of horizontal space.&lt;br /&gt;
&lt;br /&gt;
Sometimes, I feel that spending more time on this issue is just a waste, but I feel that there should be a single indentation style that coders can agree upon to make all their lives easier. Can you imagine if someones editor automatically replaced all the tabs in a file with spaces? In a revision control system, it would look like &lt;i&gt;every single line&lt;/i&gt; changed!&lt;br /&gt;
That said, I&#39;m sure that my method isn&#39;t the Holy Grail of indentation. Like all methods, it can be refined and worked on until most people agree with it.&lt;br /&gt;
&lt;br /&gt;
Anyway, that was my two-cents about code indentation. Do you agree or disagree with it? Anything you would change? I would really like to hear some feedback about this.</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/1766910764499740143/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2010/09/my-views-on-code-indentation.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/1766910764499740143'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/1766910764499740143'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2010/09/my-views-on-code-indentation.html' title='My Views on Code Indentation'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-8065664718316649413</id><published>2009-12-10T11:48:00.000-05:00</published><updated>2009-12-15T20:53:49.294-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="prawn"/><category scheme="http://www.blogger.com/atom/ns#" term="prawnto"/><category scheme="http://www.blogger.com/atom/ns#" term="RoR"/><category scheme="http://www.blogger.com/atom/ns#" term="ruby on rails"/><title type='text'>Generating PDFs in Rails with Prawn and Prawnto</title><content type='html'>There are several different PDF generation libraries available for Rails apps. Some of these are PDF::Writer, Prawn, Ruport, RGhost, and FlyingSaucer (for JRuby). First off, Ruport is basically a higher-level API for PDF::Writer. FlyingSaucer is only for JRuby (because JRuby allows for loading of Java classes). Not having any experience with RGhost, I can&#39;t exactly write a guide for using it. So now it&#39;s basically between Prawn and PDF::Writer, unless there&#39;s another library I haven&#39;t hear of. However, to my dismay, there is not UTF-8 support in PDF::Writer. So Prawn ends up being the PDF generation library of choice. &lt;br /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div style=&quot;margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;One thing to keep in mind is that Prawn and Prawnto are beta-stage (sometimes even alpha) libraries, so some code hacking is going to be necessary. Also note the Prawnto is not maintained or tested by the Prawn developers, so there&#39;s no guarantee that it&#39;ll work with future versions (especially since Prawnto&#39;s developer has been &quot;below the radar&quot; for a little while). You have been warned.&lt;br /&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;&lt;br /&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;Now to easily follow the Model-View-Controller philosophy of Rails, we use Prawnto. Without Prawnto (following the official method), we would subclass &lt;tt&gt;Prawn::Document&lt;/tt&gt; (and send the PDF using the &lt;tt&gt;send_data&lt;/tt&gt; method), which sort of takes away from the &quot;automagic&quot; of Rails. Instead of doing the interaction between your PDF code and controller manually, we can use Prawnto in order to place code inside views (with a .pdf.prawn extension). This will &quot;automagically&quot; instantiate a PDF object, pass it to the view, and then send it, all behind-the-scenes. It&#39;ll also allow for cool Rails functionality like helpers and partials. Keep in mind that all of this &lt;i&gt;can&lt;/i&gt;&amp;nbsp;be done using the official method (recommended by the Prawn developers, see below for link), but it&#39;ll require more manual coding.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;So let&#39;s get started with all of this by installing Prawn and Prawnto. Prawn is available as a gem, a simple &lt;tt&gt;gem install prawn&lt;/tt&gt; will do the trick. Prawnto is not available as a gem, however, and you will have to pull the plugin from a git repository. To do that, run this command: &lt;tt&gt;script/plugin install git://github.com/thorny-sun/prawnto.git&lt;/tt&gt; in the root of your Rails project. Of course, you must have git installed in order to do this. If you don&#39;t want to install git (maybe you&#39;re running Windows), you can download the master branch at &lt;a href=&quot;http://github.com/thorny-sun/prawnto&quot;&gt;http://github.com/thorny-sun/prawnto&lt;/a&gt; and unpack that into your &lt;tt&gt;vendor/plugins/prawnto&lt;/tt&gt; directory in the root of your Rails project.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;Now that everything is installed, the Rails project has to be configured. Inside the &lt;tt&gt;config/environment.rb&lt;/tt&gt; file, in the &lt;tt&gt;Rails::Initializer.run do |config| ... end&lt;/tt&gt; block, insert these two lines:&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;code&gt;config.gem &#39;prawn-core&#39;, :lib =&amp;gt; &#39;prawn/core&#39;&lt;br /&gt;
config.gem &#39;prawn-layout&#39;, :lib =&amp;gt; &#39;prawn/layout&#39;&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;That&#39;s really all that&#39;s needed to get started with using Prawn and Prawnto. So now let&#39;s take a look into how this would actually be used.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;Let&#39;s assume that we have a Book model. It&#39;s not crucially important what information this model contains for demonstrating how to work with Prawn. So now we have a &lt;tt&gt;books_controller.rb&lt;/tt&gt; file in our &lt;tt&gt;controllers&lt;/tt&gt; directory. In our &lt;tt&gt;views/books&lt;/tt&gt; directory, we have various &lt;tt&gt;.html.erb&lt;/tt&gt; files for our actions. Let&#39;s also assume that we definitely have an &lt;tt&gt;index.html.erb&lt;/tt&gt; and &lt;tt&gt;show.html.erb&lt;/tt&gt;.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;Now, to create a link to the PDF, we can use this &lt;tt&gt;link_to&lt;/tt&gt; syntax (substitute &lt;tt&gt;:show&lt;/tt&gt; for &lt;tt&gt;:index&lt;/tt&gt; in the different views):&lt;br /&gt;
&lt;code&gt;link_to &#39;Print (PDF)&#39;, {:controller =&amp;gt; :books, :action =&amp;gt; :index, :format =&amp;gt; :pdf}&lt;/code&gt;&lt;br /&gt;
This will create a link to location with .pdf on the end.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;Now, we have to make our controller respond to the PDF format. To do so, we add the following into the &lt;tt&gt;respond_to do |format| ... end&lt;/tt&gt;:&lt;br /&gt;
&lt;code&gt;format.pdf { render :layout =&amp;gt; false } if params[:format] == &#39;pdf&#39;&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;&lt;/code&gt;&lt;code&gt;prawnto :filename =&amp;gt; &quot;print.pdf&quot;, :prawn =&amp;gt; { ... prawn options ... }&lt;/code&gt;&lt;br /&gt;
Basically, this will make prawnto render the PDF. In most cases, we do not want to render a layout, so we force &lt;tt&gt;format.pdf&lt;/tt&gt; to not render a layout. The &lt;tt&gt;if params[:format] == &#39;pdf&#39;&lt;/tt&gt; is a safeguard to only render the PDF format if the PDF format is requested. As for the prawnto options, the filename can be specified as anything you desire (doesn&#39;t even have to end with .pdf, but that&#39;s usually not a good idea). If omitted, the filename will be &lt;tt&gt;[action name].pdf&lt;/tt&gt;. The options specified by [prawn options] hash are options that will be used to create the Prawn::Document object. So the margins, page orientation, etc are specified here. For a full reference on the options, see the &lt;a href=&quot;http://prawn.majesticseacreature.com/docs/prawn-core/classes/Prawn/Document.html#M000117&quot;&gt;Prawn Documentation&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Great, now our controller can respond, but we won&#39;t get anywhere, because we don&#39;t have the PDF views yet. To do so, we create files (in the &lt;tt&gt;views/books&lt;/tt&gt; directory) with a &lt;tt&gt;.pdf.prawn&lt;/tt&gt; extension. For this example, we would have &lt;tt&gt;index.pdf.prawn&lt;/tt&gt; and &lt;tt&gt;show.pdf.prawn&lt;/tt&gt;. Past this point, it&#39;s absolutely essential to have knowledge of Prawn and how to generate PDFs with it. Please note that this is not a tutorial on Prawn (maybe in another post...).&lt;br /&gt;
&lt;br /&gt;
Prawnto will instantiate a Prawn::Document object and allow it to be accessed as the variable &lt;tt&gt;pdf&lt;/tt&gt; inside your &lt;tt&gt;.pdf.prawn&lt;/tt&gt; views. So a simple &quot;Hello World!&quot; message inside the &lt;tt&gt;index.pdf.prawn&lt;/tt&gt; file would look something like this:&lt;br /&gt;
&lt;code&gt;pdf.text &quot;Hello WORLD!!&quot;, :size =&amp;gt; 30, :align =&amp;gt; :center&lt;/code&gt;&lt;br /&gt;
This is the same code that would be used when generating a PDF file like this:&lt;br /&gt;
&lt;code&gt;pdf = Prawn::Document.new(... prawn options ...)&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;pdf.text &quot;Hello WORLD!!&quot;, :size =&amp;gt; 30, :align =&amp;gt; :center&lt;br /&gt;
&lt;/code&gt;&lt;code&gt;pdf.render&lt;/code&gt;&lt;br /&gt;
As opposed to the quick way of:&lt;br /&gt;
&lt;code&gt;Prawn::Document.generate(&quot;file.pdf&quot;) do&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;text &quot;Hello WORLD!!&quot;, :size =&amp;gt; 30, :align =&amp;gt; :center&lt;br /&gt;
&lt;/code&gt;&lt;code&gt;end&lt;/code&gt;&lt;br /&gt;
So essentially all of the Prawn methods will just be accessed through the &lt;tt&gt;pdf&lt;/tt&gt; object.&lt;br /&gt;
&lt;br /&gt;
Another cool feature of Prawnto is partials. Actually, I&#39;m not sure if this automagically performed by Rails, or if some work is required from Prawnto. But anyway, you can use partials with PDFs, just like you use partials with normal views. Just name them &lt;tt&gt;_[partial name].pdf.prawn&lt;/tt&gt;. Then, inside, let&#39;s say, &lt;tt&gt;show.pdf.prawn&lt;/tt&gt;, you could render the &lt;tt&gt;author_info&lt;/tt&gt; partial with:&lt;br /&gt;
&lt;code&gt;render :partial =&amp;gt; &#39;author_info&#39;, :locals =&amp;gt; {:ppdf =&amp;gt; pdf}&lt;/code&gt;&lt;br /&gt;
The &lt;tt&gt;:locals =&amp;gt;&amp;nbsp;{:ppdf =&amp;gt; pdf}&lt;/tt&gt; will create the &lt;tt&gt;ppdf&lt;/tt&gt; (parent-pdf) variable for use inside the partial (instead of &lt;tt&gt;pdf.&lt;/tt&gt;, use &lt;tt&gt;ppdf.&lt;/tt&gt; inside the partial). I believe that this is a limitation of Prawnto. I&#39;ve tried&amp;nbsp;&lt;tt&gt;:locals =&amp;gt;&amp;nbsp;{:pdf =&amp;gt; pdf}&lt;/tt&gt;, but then nothing rendered at all. This is a very minor inconvenience that should &lt;i&gt;not&lt;/i&gt;&amp;nbsp;prevent anybody from using partials with Prawnto.&lt;br /&gt;
&lt;br /&gt;
Other than those features, all features of Rails views apply. That means you can use helpers, instance variables (set in controller, of course), and all of the other coll Rails features. The only thing to keep in mind is that all of the code inside the &lt;tt&gt;.pdf.prawn&lt;/tt&gt; files is treated as Ruby code. So, no ERB tags like you use in &lt;tt&gt;.html.erb&lt;/tt&gt; files.&lt;br /&gt;
&lt;br /&gt;
I hope that now you are prepared to easily generate PDFs in your Rails application. A word of advice is to keep in mind that these libraries are beta-level, and could be unstable!! So to finish off, I&#39;ll leave you some sites that could prove useful when using Prawn:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;http://prawn.majesticseacreature.com/&quot;&gt;Main Prawn site&lt;/a&gt; (with documentation for &lt;a href=&quot;http://prawn.majesticseacreature.com/docs/prawn-core/&quot;&gt;core features&lt;/a&gt; and &lt;a href=&quot;http://prawn.majesticseacreature.com/docs/prawn-layout/&quot;&gt;layout stuff&lt;/a&gt;), also contains examples.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://groups.google.com/group/prawn-ruby?hl=en&quot;&gt;Prawn Google Group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://github.com/sandal/prawn&quot;&gt;Prawn GitHub repo&lt;/a&gt; (if you&#39;re feeling adventurous, use the bleeding edge - &lt;i&gt;not recommended!!&lt;/i&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.cracklabs.com/prawnto&quot;&gt;Prawnto site&lt;/a&gt; (sometimes down). This site is hard to read (bad choice of colors), but contains some useful information&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://github.com/thorny-sun/prawnto&quot;&gt;Prawnto GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;http://wiki.github.com/sandal/prawn/using-prawn-in-rails&quot;&gt;official method&lt;/a&gt; of using Prawn in Rail projects. Sometimes, Prawnto will not work, but this setup is tested by the Prawn developers to work. If you use this method, you should pretty much start with that method from scratch, instead of following any of these instructions.&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;P.S. In case you&#39;re wondering what happened to&amp;nbsp;&lt;tt&gt;Prawn::Format&lt;/tt&gt;, and why it&#39;s not in here: &lt;tt&gt;Prawn::Format&lt;/tt&gt;&amp;nbsp;does not have anyone maintaining it, and therefore has been &quot;retired&quot; for the time being. You can research how to use it on your own, but there is &lt;b&gt;absolutely no expectation of &lt;tt&gt;Prawn::Format&lt;/tt&gt; working&lt;/b&gt;&amp;nbsp;with Prawn after version 0.7 is released.&lt;br /&gt;
&lt;br /&gt;
EDITED on 12/15/2009 based on suggestions from Gregory Brown (Prawn dev).&lt;br /&gt;
&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/8065664718316649413/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2009/12/generating-pdfs-in-rails-with-prawn-and.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/8065664718316649413'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/8065664718316649413'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2009/12/generating-pdfs-in-rails-with-prawn-and.html' title='Generating PDFs in Rails with Prawn and Prawnto'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-8377118258490288030</id><published>2009-10-06T21:43:00.000-04:00</published><updated>2009-10-07T08:57:42.539-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="mysql"/><category scheme="http://www.blogger.com/atom/ns#" term="php"/><category scheme="http://www.blogger.com/atom/ns#" term="rails"/><category scheme="http://www.blogger.com/atom/ns#" term="RoR"/><category scheme="http://www.blogger.com/atom/ns#" term="ruby"/><category scheme="http://www.blogger.com/atom/ns#" term="ruby on rails"/><title type='text'>Ruby on Rails vs PHP</title><content type='html'>In my past experience with Web Apps, I have worked with PHP (with MySQL). Now I&#39;m working on my website and using PHP again. At work, however, we are using Ruby on Rails (RoR), with Oracle. This makes me want to do a comparison of the two. Obviously, I will compare RoR and PHP, but both using a MySQL database. Keep in mind that this is based on &lt;i&gt;my&lt;/i&gt; impressions, not necessarily the best technical aspects of each language.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;text-decoration: underline;&quot;&gt;&lt;span class=&quot;Apple-style-span&quot;  style=&quot;font-size:large;&quot;&gt;&lt;b&gt;RoR Advantages:&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;RubyGems - It&#39;s &lt;i&gt;extremely&lt;/i&gt; simple to install extra features (calendar date select, etc). Yeah, you can have libraries in PHP, but it&#39;s not as simple and polished as RoR. You can even define gem dependencies your app has in environment.rb and then use &quot;rake gems:install&quot; to install all gems your project requires.&lt;/li&gt;&lt;li&gt;Layouts and Partials - Let&#39;s face it: having a &quot;standard.html.erb&quot; layout (or template, if you will) and then having &quot;&quot; in it is just &lt;i&gt;beautiful&lt;/i&gt;. Whatever content you create will automatically (or automagically) replace that. Of course, it&#39;s possible to do so in PHP, but not so elegant.&lt;/li&gt;&lt;li&gt;OO form submission - having form elements get processed automatically into a Hash when they&#39;re submitted is great!  can be accessed with model[:attribute] in the controller when the form is submitted. IMHO, that&#39;s very useful. In certain cases it can be limiting, but most of the time it&#39;s good.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;text-decoration: underline; &quot;&gt;&lt;b&gt;&lt;span class=&quot;Apple-style-span&quot;  style=&quot;font-size:medium;&quot;&gt;RoR Disadvantages&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&quot;Convention over Configuration&quot; - OK, that may be good in some cases, but there&#39;s just too many conventions. Oh, I just hardcoded an HTML form, but that&#39;s supposed to &quot;conventionally&quot; done with helpers. Maybe a bad example, but it gets the point across. Many times, a developer won&#39;t know all of the conventions and will hardcode it, only to find out later (by whatever means) that it can be done some much more easily. Sometimes the convention gets in the way, but then it&#39;s hard to get around it. Don&#39;t get me wrong, I don&#39;t like excess configuration, but I&#39;m not exactly a fan of excess convention either.&lt;/li&gt;&lt;li&gt;&quot;The Rails Way&quot; - There&#39;s no ONE right way to doing something. So why does RoR assume that its Way is the right way for that language? Rails wants you to do things their way. If you&#39;re creating a brand new app, with a new database, then it&#39;s great. Super simple. But what if you already have a database, and you can&#39;t change the field names because of existing apps? Ummmm..... What if you want a primary key that&#39;s not &quot;id&quot;? &quot;The Rails Way&quot; doesn&#39;t account very well for those scenarios. &lt;/li&gt;&lt;li&gt;2+ databases - RoR is made to work with 1 database. Yes, it&#39;s possible to connect to others, but it&#39;s designed to work with 1. If you have more, then you have to do some wierd stuff that doesn&#39;t fell Rails-like.&lt;/li&gt;&lt;li&gt;So many files - There are &lt;i&gt;so many&lt;/i&gt; files to work with in RoR. Typically, a table in the DB will correspond to a controller in your code. So for each of those, you have: a controller, a helper, a model, usually 4+ views (new, edit, index, show). That&#39;s &lt;b&gt;7 &lt;/b&gt;files for a single table. Some people will prefer to work with many files just to keep things organized, but it can get confusing. Take a 20-table DB, for example. Let&#39;s say each of those tables has its own controller. Also, let&#39;s have the 4 standard views, Excel export (index.xls.builder), and a form partial, to factor the form out of the new and edit pages. Thats 20 x 9 = &lt;b&gt;180&lt;/b&gt; files!!! Not to mention the shared stuff, like JS, CSS, images, etc. So in the end, you have about 200 files for a web app.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;text-decoration: underline;&quot;&gt;&lt;span class=&quot;Apple-style-span&quot;  style=&quot;font-size:large;&quot;&gt;&lt;b&gt;PHP Advantages:&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Control - With PHP, you have a lot more control over what goes on and how it goes on. In Rails, if there&#39;s some kind of relationship between objects, there&#39;s going to be a lot more queries going on in the background, even if you don&#39;t need them. Quite possibly, the same result could be achieved with a left join in the query. &lt;/li&gt;&lt;li&gt;SQL - Yes, I know. You can write your own queries in Rails, too. But with PHP, I know exactly what my queries are, and when and how many times they&#39;re executed. Yes, there&#39;s a find_by_sql method in Rails, but that&#39;s not the preferred way to do it.&lt;/li&gt;&lt;li&gt;Documentation - IMHO, PHP has better documentation than RoR. It comes with lots of examples, and even user-submitted tips and tricks. Somehow, the 4-frame layout of RoR documentation just doesn&#39;t stick too well.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;text-decoration: underline; &quot;&gt;&lt;b&gt;&lt;span class=&quot;Apple-style-span&quot;  style=&quot;font-size:medium;&quot;&gt;PHP Disadvantages&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Learning - It takes longer to get an app running with PHP than with RoR. At a minimum, you need to learn HTML, PHP, and SQL. With RoR, you have to learn just HTML and RoR (ruby with extra libraries). Personally, I would learn SQL, because you&#39;ll probably end up using it anyway to bend RoR to your will. &lt;/li&gt;&lt;li&gt;SQL - you have to know how to write SQL queries in order to have a database-driven PHP app. I put this under disadvantages because it&#39;s another barrier to entry for newbie programmers. But actually, I think it&#39;s a good thing. I think you should know what&#39;s going on behind the scenes, rather than relying on auto-magic Rails stuff. &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot;  style=&quot;font-size:medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot;  style=&quot;font-size:medium;&quot;&gt;Overall, I would prefer to code in PHP. It feels more natural to me than RoR. For me, there&#39;s just too much automation going on in Rails in order to fully appreciate its simplicity. But that&#39;s just my personal preference. I haven&#39;t (yet) tried JSP or ASP, but I would imagine that they&#39;re closer to PHP than RoR.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot;  style=&quot;font-size:medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot;  style=&quot;font-size:medium;&quot;&gt;What are your thoughts on this?&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot;  style=&quot; text-decoration: underline;font-size:large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/8377118258490288030/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2009/10/ruby-on-rails-vs-php.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/8377118258490288030'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/8377118258490288030'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2009/10/ruby-on-rails-vs-php.html' title='Ruby on Rails vs PHP'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4002344319997536708.post-9041098976393306973</id><published>2009-03-20T22:11:00.000-04:00</published><updated>2009-03-21T14:05:47.456-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="App Store"/><category scheme="http://www.blogger.com/atom/ns#" term="Apple"/><category scheme="http://www.blogger.com/atom/ns#" term="iPhone"/><category scheme="http://www.blogger.com/atom/ns#" term="iPod Touch"/><title type='text'>Is Apple becoming another Microsoft?</title><content type='html'>&lt;p&gt;You know what pissed off Linux/Unix folk about Microsoft and Windows? My best guess is that it wasn&#39;t Open-Source. Thus, their suggestions weren&#39;t taken into consideration as much as they should have. Microsoft was controlling its own software too much for its own good. Now Apple is doing the same thing. Let me explain with the example of iPods. &lt;/p&gt;&lt;p&gt;If we take a look at any iPod model before the iPod touch, we notice that (internally in the filesystem) it&#39;s pretty simple. You have your music files and your song/other stuff database. The database(s) were plain-text and their structure could be examined and mimicked. This allowed for syncronization with software other than Apple&#39;s iTunes. &lt;/p&gt;&lt;p&gt;Now welcome the iPod touch... No more simplicity. iPhone OS keeps its databases hashed. Yes, you can put music files on there with non-iTunes, but they won&#39;t show up in your lists. It&#39;s like the files are orphaned. The only program you can sync the iPod touch with is iTunes. Oh, and if you don&#39;t have Windows or Mac, you&#39;re out of luck. Booted into Linux/Unix/non-Mac/non-Windows? You have to reboot into Mac/WIndows to sync your iPod Touch. Very convenient, Apple, very convenient indeed.&lt;/p&gt;&lt;p&gt;OK, good thing the iPod Touch has a WiFi chip. Now you can install apps without a computer. Yes!!! But wait, what apps? Yeah, there&#39;s a gazillion apps in the App Store, but how did they get there? With Apple&#39;s approval. Apple doesn&#39;t like it, and the app doesn&#39;t make it to the App Store. To illustrate the point, an update to a Twitter application had a toplist of some kind. When Apple was looking over the program, it just so happened that the toplist contained the F-bomb in one of its terms (or site names, or whatever). Didn&#39;t stay in the toplist for long, though. Nevertheless, the update was rejected! It&#39;s not like the developer controls the toplist and purposely  manipulated the rating system to put the F-bomb on the toplist. &lt;/p&gt;&lt;p&gt;Well, good thing the iPhone-Dev team exists. With their jailbreaking process, the 1st gen iPod touch was jailbroken easily and non-App Store apps could be installed. This opened up a whole slew of features and tweaks and other cool stuff. But then comes the 2nd gen iPod touch. Apple realized where the chain of trust in the boot process was broken and quickly patched it up in the hardware. Uh-oh for the jailbreakers. But finally, after months of work, the redsn0w in-RAM tethered jailbreak was released and people could jailbreak the 2nd gen iPod touch, but needed a PC to boot their iPod. Then, after another month/month-and-a-half, people came up with a jailbreak process that didn&#39;t need a PC to boot the iPod. That awesome. Excellent. Kudos to the hackers who worked on this project. But, looking at Apple&#39;s track record, that&#39;s bound to be patched up and 3rd gen iPod touch users will have an even harder time jailbreaking.&lt;/p&gt;&lt;p&gt;So, now the end-user is (perhaps nor completely) statisfied. But what about the developers? I myself have wanted to write an iPhone app, at least for just messing around. Good luck doing that without a Mac, though. So you want Mac, eh? You have to pay for both Apple hardware and Apple software. Pretty good deal for Apple. But the Linux/Unix code junkies who are used to everything being free and open source? Too bad. Apple wan&#39;t you to use the iPhone SDK, but only on its products. &lt;/p&gt;I hope I have demonstrated how Apple could become another Microsoft. It&#39;s just becoming a control freak. It&#39;s trying to control almost all aspects of its products&#39; use, from what the developers can use to what the end-users can install. The end-users far outnumber the developers, which is why we have jailbreaks, but no Windows/Linux/Unix iPhone SDK ports. I personally hope that Apple will open up its software and allow others to contribute to it and use it however they want. But that&#39;s unlikely to happen. </content><link rel='replies' type='application/atom+xml' href='http://blog.vetruvet.com/feeds/9041098976393306973/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.vetruvet.com/2009/03/is-apple-becoming-another-microsoft.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/9041098976393306973'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4002344319997536708/posts/default/9041098976393306973'/><link rel='alternate' type='text/html' href='http://blog.vetruvet.com/2009/03/is-apple-becoming-another-microsoft.html' title='Is Apple becoming another Microsoft?'/><author><name>Valera</name><uri>http://www.blogger.com/profile/18118497220672212031</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry></feed>