GlassCalc 1.29 and Widgets!

2010

GlassCalc 1.29 doesn’t add any new functions, but it adds a number of formatting features to make GlassCalc easier to work with or just fit into your theme better.  GlassCalc now has support for thousands separators, and you can choose between commas, spaces of various widths, or enter your own separator.  GlassCalc can also replace asterisks (multiplication operators) with dots or times symbols.

You can now choose between three different formats for exponential form: 1.00e10, 1.00E10, and 1.00×1010.  GlassCalc defaults to a lowercase e to give a clear separation between the number and exponent.  More color settings are available, and the font sizes of the history, input, and other panes are now configurable as well.

Widgets!

I recently created two Opera Widgets, which are essentially desktop applications built with HTML, CSS, and JavaScript.  You need to install Opera before they will work, though Opera does not need to be running to start the applications.

Search Organizer

If you are an Opera user and wished you could reorder your custom search engines, check out my Search Organizer widget.  Feed it your search.ini file and it lets you drag and drop your search engines into a new order.  Save and restart Opera for your changes to apply.

CSS Fix

My second widget isn’t exclusively for Opera users, though it may be more useful to them.  Occasionally, I’ll run into a site that looks blocky and ugly in Opera, but smooth and pretty in Firefox and Chrome, even though Opera supports most or all of the features used *cough* Twitter *cough*.  There’s no reason these sites should look any worse in one browser compared to the others, except that the web developers left out some key CSS properties, like the unprefixed border-radius and box-shadow.

CSS Fix aims to solve this problem by generating CSS patches to fix sites.  It knows most of the equivalent CSS properties used by Opera, Firefox, Chrome/Safari, and Konqueror and can translate between them.   Simply enter the URLs of a offending sites and/or CSS files, choose the browser(s) you want a patch for, and CSS Fix will download the files and generate a new stylesheet with all the missing CSS properties.  You can export the patch as user CSS or user JavaScript, install the patch in your browser, and see sites the way they were meant to be seen.  (You could also ask the site admins to kindly add in the missing properties, but for big sites like Twitter, you’d be lucky to get anywhere)

I’ve created a Twitter patch for Opera using CSS Fix that gives Twitter the rounded corners and most of the other pretty effects it’s supposed to have.

I am also working on a web-based version of CSS Fix that lets you paste in CSS and a user JavaScript version which will fix inline styles on the fly.  I can’t estimate when either of those will be done though.

More Downtime and a New Version

2010

A few months ago, my (free) web host outgrew their servers and moved to bigger and better servers, resulting in a few weeks of downtime.  Everything seemed to be going fine until one of the new servers showed signs of hard-disk failure.  Naturally, my site was on said server, so my site suffered another week of downtime.  A week ago, my host was having trouble with cPanel and discovered an undocumented (and unchangeable) connection limit hidden within it.  The cPanel team said they couldn’t fix it quickly, so my site was down for a few more days while my host reconfigured things to work around the problem.

Everything looks to be stable now, but I predict that in about a week, a series of tornadoes will strike the building housing the servers, and the following week, x10hosting headquarters will be attacked by a giant robotic space eel.

GlassCalc 1.28

On a more serious note, I’ve uploaded version 1.28 of GlassCalc which should fix the bottoms of letters getting cut off in the menu bar.  I’ve also reorganized the settings window and added a couple features to help set up unit conversions.  Last but not least, I’ve added support for something I’m calling “Scale Factors”.  These are suffixes you stick on the end of numbers to multiply them by some number.  If you define a scale factor of K = 1000, you can write 1K for 1000. These have a higher priority than other operators, so 1/1K would be 1/1000, not 1/1*1000.

There are no scale factors defined by default, but you can define your own by editing your extensions.ini file (open More Settings->Extensions).  Add a [ScaleFactors] section and add one suffix per line under it.  The format is Suffix = Scale where Suffix is a string and Scale is a number.  Put a /i at the end of a suffix to make it case-insensitive. You can also use exponential notation when setting the scale. Here is an example ScaleFactors section which defines m (milli), c (centi), K (kilo), and M (mega) suffixes:

[ScaleFactors]
m = 1e-3
c = 1e-2
K/i = 1e3
M = 1e6

Suffixes do not have to be a single character. They can be of any length. You can also use regular expression syntax, but I don’t recommend it. Using square brackets or equals will probably confuse the ini parser, starting with digits (and various other patterns) will confuse the math parser, and everything after the first forward slash is treated as modifier flags. If anyone wants more in-depth support for regular expressions here, ask and I’ll find a way to make it work.

Aero Glass in GlassCalc (part 1)

2010

I got a couple questions about how I made the Aero Glass effects in GlassCalc. When I started making GlassCalc, I had no idea how to manage Aero Glass, but Google came to my rescue. What I eventually came up with is the amalgamation of code snippets and general knowledge from countless separate articles and examples. I’ll link to as many of those pages as I can remember, but I’ll try and bring everything together in one place here.

Note: GlassCalc is written in C# using WPF for its UI. As such, some of the code examples below are C#, .NET, or WPF specific.

Every window in Windows has a client area and a non-client area. The client area is everything you get to handle and draw on. Normally, this is everything inside the window frame. The non-client area is everything Windows handles by itself: the window frame, icon, window title, min/max/close buttons, resize handles, etc…

Using the Windows API, programs can modify the glass frame and the client/non-client areas. Generally, they either extend the glass frame into the client area, or they extend the client area into the glass frame. GlassCalc does both.

With the default interface, GlassCalc extends the top and bottom of the frame. The client area is still the same size, but the glass frame has been extended into the client area, so GlassCalc can draw the menu and the input box over glass.

[singlepic id=6 w=320 h=240 float=center]

With the full glass interface, GlassCalc extends the frame to cover the entire window, so everything is drawn over glass, but it also draws the menu bar inside the title. This is done by extending the client area upwards so GlassCalc can draw there. This effect takes quite a lot more work, because now that GlassCalc has taken over the non-client area, it must handle all the things Windows used to handle in this area.

[singlepic id=13 w=320 h=240 float=center]

Extending the glass frame

All of the code here involves Windows API calls in some way. To organize everything, I’m putting all Windows API functions and structures in a class called Interop. (some examples use the name NativeMethods) I’ll also make a class called GlassHelper which will (quite appropriately) help deal with Aero Glass.

To extend the glass frame, we need to import a function called DwmExtendFrameIntoClientArea from dwmapi.dll. Since Aero can be disabled, we also need to know whether we can extend the frame or not. For this, we import DwmIsCompositionEnabled. DwmExtendFrameIntoClientArea takes a Margins structure as one of its arguments. Since there is no Margins structure in C#, we need to define it ourselves. Much of the following code comes from this informative article.

Note: You can copy the source of these code examples by hovering over the code, then clicking the view source icon in the toolbar that appears in the upper-right corner.

public static class Interop
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Margins
    {
        public int Left;
        public int Right;
        public int Top;
        public int Bottom;
        public Margins(Thickness t)
        {
            Left = (int)t.Left;
            Right = (int)t.Right;
            Top = (int)t.Top;
            Bottom = (int)t.Bottom;
        }
    }
    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref Margins margins);
    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern bool DwmIsCompositionEnabled();
}

Now, we’ll make a class that can make use of these functions.

public static class GlassHelper
{
    /// <summary>
    /// Gets whether dwmapi.dll is present and DWM functions can be used
    /// </summary>
    public static bool IsDwmCompositionAvailable
    {
        get
        {
            // Vista is version 6.  Don't do aero stuff if not >= Vista because dwmapi.dll won't exist
            return Environment.OSVersion.Version.Major >= 6;
        }
    }
    /// <summary>
    /// Gets whether DWM is enabled
    /// </summary>
    public static bool IsDwmCompositionEnabled
    {
        get
        {
            // Make sure dwmapi.dll is present.  If not, calling DwmIsCompositionEnabled will throw an exception
            if (!IsDwmCompositionAvailable)
                return false;
            return Interop.DwmIsCompositionEnabled();
        }
    }
    /// <summary>
    /// Extends the glass frame of a window
    /// </summary>
    public static bool ExtendGlassFrame(Window window, Thickness margin)
    {
        if (!IsDwmCompositionEnabled)
            return false;
        IntPtr hwnd = new WindowInteropHelper(window).Handle;
        if (hwnd == IntPtr.Zero)
            throw new InvalidOperationException("The Window must be shown before extending glass.");
        HwndSource source = HwndSource.FromHwnd(hwnd);
        // Set the background to transparent from both the WPF and Win32 perspectives
        window.Background = Brushes.Transparent;
        source.CompositionTarget.BackgroundColor = Colors.Transparent;
        Interop.Margins margins = new Interop.Margins(margin);
        Interop.DwmExtendFrameIntoClientArea(hwnd, ref margins);
        return true;
    }
}

Finally, in override your application’s OnSourceInitialized method to call ExtendGlassFrame. The Thickness object you pass in defines how far in the frame should be extended from each of the sides. If you use -1 for each dimension (like in the code below), the glass will cover the entire window.

public sealed partial class YourApp : Window<
{
    . . .
    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        GlassHelper.ExtendGlassFrame(this, new Thickness(-1));
    }
    . . .
}

Make the glass area act like an extension of the title bar

If you extend the frame from the top such that the title bar appears bigger, (like in the default GlassCalc interface) you probably also want to make it so that when a user clicks in this empty area, they can drag the window. You can do this by detecting a MouseLeftButtonDown event inside the extended title bar area and calling your window’s DragMove function.

What if Aero gets disabled while my program is running?

There is one other thing you should take care of when extending the frame. If a user turns off Aero while your application is running, you’ll get big black areas where there used to be glass because your window background is transparent. Windows sends a WM_DWMCOMPOSITIONCHANGED message to your application when Aero is turned on or off, so you can handle this by setting up a function to listen for this message.

Add this constant to Interop.

public static class Interop
{
    public const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
    . . .
}

Now, set up a function to listen for messages. When it gets a WM_DWMCOMPOSITIONCHANGED message, check to see whether Aero was enabled or disabled. If Aero was enabled, extend the frame again. If it was disabled, set the background color back to its original value. (This example assumes your window background was white. You could improve it by saving the background color before extending the frame, then reverting it when Aero is disabled.)

public sealed partial class YourApp : Window
{
    . . .
    /// <summary>
    /// Processes messages sent to this window
    /// </summary>
    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == Interop.WM_DWMCOMPOSITIONCHANGED)
        {
            if (GlassHelper.IsDwmCompositionEnabled)
            {
                // Aero was re-enabled.  Extend the glass again.
                GlassHelper.ExtendGlassFrame(this, new Thickness(-1));
            }
            else
            {
                // Aero was disabled.  Reset the window's background color to remove black areas
                HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
                source.CompositionTarget.BackgroundColor = Colors.White;
                this.Background = Brushes.White;
            }
        }
    }
    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        // Hook up the WndProc function so it receives messages
        HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
        source.AddHook(WndProc);
        GlassHelper.ExtendGlassFrame(this, new Thickness(-1));
    }
    . . .
}

Extending the client area

Since this is already a very long post, and the part on extending the client area will probably be even longer, I’m going to split it up. Extending the client area into the frame will come in part 2… whenever I get around to writing that. For now, here are some of the links I found most useful while figuring this stuff out.

Update 8/19/2010: I know it’s been over a month since I posted part 1, and I still haven’t gotten to part 2.  I’m working on it, but school is start back up, so I can’t say when it’ll be done.