Vertical panel in GNOME

June 8th, 2008 by Alexander Kojevnikov

I’ve been playing with various desktop GNU/Linux distributions last couple of months. I’m not exactly a newbie to Linux, I have been administering a VPS box for my hobby project for several years now, but I never managed to play with it on a desktop.

So I did. And I must say I’m very impressed. Last time I checked (FreeBSD 4 back in 2000), FLOSS desktop was mostly a geek toy, these days it is ready for the average user.

I will spare the overview of the distros that I tried, as well as my take on the KDE vs. GNOME flame war for another post, here I want to talk about one particular annoyance that I really want to see fixed.

You see, these days it’s hard not to have a wide-screen monitor sitting on your desktop. They are great for watching films and playing games but this comes at a cost — you end up with fewer vertical pixels.

Vertical space is much more important for most other tasks I do on the computer, be it browsing the web, coding or writing blog posts. And the only way to maximise it is to move the everlasting task bar sitting on the bottom of most operating systems to the left or right of the screen.

Vertical layout in VistaVertical layout in KDEThis is how it looks like in Vista and KDE. I know it takes some getting used to, but it’s worth a few days of slight disorientation. And I’m not the only one who thinks so.

The vertical layout works great in XP, Vista and KDE, but not in GNOME. I want to list here all open issues along with the links to the GNOME bug database. I guess we have all these issues because not many GNOME developers are using the vertical layout, or even aware of the benefits it can give them. I hope this post will help it, even if only a little bit.

Vertical layout in GNOMEWindow List: The list of open windows is arguably the most important piece of information sitting on the panel. And the most terribly behaving in vertical layout.

First of all, the height of the window list applet is fixed, meaning the list doesn’t occupy all available vertical space.

Second, the height of the buttons that represent the open windows, stretches to fill the entire applet. The buttons should have a fixed height that depends on the font used in the buttons.

Third, after you open a few windows, the list splits to two columns and becomes irresponsible to mouse clicks. This is the most annoying bug of the three.

These issues are documented in bug 86382 that was open back in 2002! The bug has a patch, but it looks like it’s not perfect either.

Notificatioin Area: In vertical layout the notification area wastes a lot of space by placing one icon in a row. It also uses different sizes for different icons, some are really huge, e.g. 128×128. It should instead use a flow layout for icons and use the same size for all of them. This is described in bug 531371.

Quick Launch: The quick-lounge applet had a bug that made it nearly impossible to use on a vertical panel (see bug 531358). It’s fixed now in the trunk, hopefully it will be integrated into the next GNOME release.

There are other related annoyances (see bug 428943 and Ubuntu idea #1906) but I can live with them if the above issues are resolved.

My Visual Studio Colour Scheme

February 8th, 2008 by Alexander Kojevnikov

In his recent post Scott is listing various Visual Studio themes. He also mentions a positive impact of good colour schemes on productivity.

I can certainly confirm this. I switched to a custom theme several month ago and I’m never going back.

Visual Studio Colour Scheme

Unfortunately I can’t remember which scheme I used as a base, but it must have been an offspring of Vibrant Ink. The code sample is by Jeff Atwood.

SQL Server: Full-Text Searching Across Multiple Tables

February 6th, 2008 by Alexander Kojevnikov

Sometimes a full-text search needs to be performed on multiple tables. For example, if we have two tables, Post and Comment, we may want to search for posts that have keywords both in the post text and in the comments.

SQL Server doesn’t allow to include multiple tables in the CONTAINS and FREETEXT predicates. We can of course use several predicates in our SQL query, but this can get pretty complex if we need to search in many columns.

Luckily there’s a workaround. Starting from version 2005, SQL Server allows to add FTS indices to views. Knowing this, we can create a view that combines text columns from both tables and then search on it.

There are a few caveats:

  1. The view must be created with the SCHEMABINDING option.
  2. Tables in the view must be referenced by their schema name, e.g. dbo.Post
  3. Before adding an FTS index, the view must have a unique clustered index. This itself limits the kinds of views that can be indexed. Among other things, the SELECT statement of the view cannot contain: UNION operator, sub-queries, derived tables, OUTER JOINs, some aggregate functions, rowset functions, etc.
  4. The FTS index cannot be added on text, ntext, image and xml columns. Thankfully, it can still be added on varchar(max), nvarchar(max) and varbinary(max) columns, introduced in version 2005.

Here is how our fictitious example can be implemented.

The tables:

CREATE TABLE Post
(
    ID int NOT NULL IDENTITY(1,1) PRIMARY KEY,
    Text nvarchar(max) NOT NULL
)
CREATE TABLE Comment
(
    ID int NOT NULL IDENTITY(1,1) PRIMARY KEY,
    PostID int NOT NULL FOREIGN KEY REFERENCES Post(ID),
    Text nvarchar(max) NOT NULL
)

The view and its unique index:

CREATE VIEW ViewSearch WITH SCHEMABINDING AS
SELECT c.ID, p.Text AS PostText, c.Text AS CommentText
FROM dbo.Comment AS c
JOIN dbo.Post AS p ON p.ID=c.PostID
GO
CREATE UNIQUE CLUSTERED INDEX IX_ViewSearch ON ViewSearch (ID)

After the full-text catalog is created and the index is added on the view, we can use this query:

SELECT DISTINCT p.ID
FROM Post AS p
JOIN Comment AS c ON c.PostID=p.ID
JOIN ViewSearch AS s ON s.ID=c.ID
WHERE CONTAINS(s.*, ‘foo’) AND CONTAINS(s.*, ‘bar’)

Unfortunately a single CONTAINS(s.*, ‘"foo" AND "bar"’) won’t work, SQL Server always uses OR when CONTAINS is applied to multiple columns.

If you have a keen eye you may have noticed a problem in the solution above. It doesn’t work for posts that have no comments at all. Ideally we would need to use a LEFT JOIN in our FTS view, but the SQL Server wouldn’t let us to create a unique index on it.

A possible workaround is to add a full-text index on Post.Text and to rewrite the query like this:

SELECT p.ID
FROM Post AS p
JOIN Comment AS c ON c.PostID=p.ID
JOIN ViewSearch AS s ON s.ID=c.ID
WHERE CONTAINS(s.*, ‘foo’) AND CONTAINS(s.*, ‘bar’)
UNION
SELECT p.ID
FROM Post AS p
WHERE CONTAINS(p.Text, ‘"foo" AND "bar"’)

Editing icons of WinForms controls.

February 1st, 2008 by Alexander Kojevnikov

I love icons! I use them all the time in my WinForms applications - anywhere from buttons and tab pages to tool strip items. Icons look nice (at least they should be), but editing them is not.

In WinForms you need to edit the original image and change the corresponding .Image or .Icon properties everywhere that image is used. Some controls, such as the TabPage, can only refer to an ImageList, so you would need to amend it and be extra careful not to screw up the image indices.

ImageResourceI wrote a small component to ease the process (of editing, not screwing up, mind you). When ImageResource is dropped into a form it adds a new string property to Forms, Buttons, TabPages and ToolStripItems. You just need to enter the resource name of the icon and the rest is done automatically.

Here’s the code:

using System;
using System.ComponentModel;
using System.Reflection;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.IO;

namespace Versia.Samples
{
    [ProvideProperty("ImageResource", typeof(object))]
    public partial class ImageResource : Component, IExtenderProvider
    {
        private readonly Dictionary<object, string> _values = new Dictionary<object, string>();
        private readonly Dictionary<string, Icon> _icons = new Dictionary<string, Icon>();

        public ImageResource()
        {
            InitializeComponent();
        }

        public ImageResource(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
        }

        public bool CanExtend(object target)
        {
            return target is ButtonBase || target is TabPage || target is Form || target is ToolStripItem;
        }

        [Category("Appearance")]
        [Description("Sets the resource name of the image to add to the control.")]
        [DefaultValue("")]
        public string GetImageResource(object target)
        {
            string value;
            if(_values.TryGetValue(target, out value))
            {
                return value;
            }
            return String.Empty;
        }

        public void SetImageResource(object target, string name)
        {
            _values[target] = name;

            if(!_imageList.Images.ContainsKey(name))
            {
                // Get the assembly from the resource name.
                Stream stream = null;
                foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    try
                    {
                        stream = assembly.GetManifestResourceStream(name);
                    }
                    catch(NotSupportedException)
                    {
                    }
                    catch(FileNotFoundException)
                    {
                    }

                    if(stream != null) break;
                }

                if(stream == null) return;

                // Read the image from the resource.
                if(name.EndsWith(".ico", StringComparison.CurrentCultureIgnoreCase))
                {
                    Icon icon = new Icon(stream);
                    _icons[name] = icon;
                    _imageList.Images.Add(name, icon);
                }
                else
                {
                    _imageList.Images.Add(name, Image.FromStream(stream));
                }
            }
            // Add the image to the control.
            if(target is ButtonBase)
            {
                ButtonBase button = (ButtonBase)target;
                button.Image = _imageList.Images[name];
            }
            else if(target is TabPage)
            {
                // ImageList is attached to the TabControl.
                TabPage tabPage = target as TabPage;
                if(tabPage.Parent != null)
                {
                    ((TabControl)tabPage.Parent).ImageList = _imageList;
                    tabPage.ImageKey = name;
                }
                else
                {
                    tabPage.ParentChanged += delegate(object sender, EventArgs e)
                    {
                        TabPage page = (TabPage)sender;
                        if(page.Parent is TabControl)
                        {
                            ((TabControl)page.Parent).ImageList = _imageList;
                            page.ImageKey = name;
                        }
                    };
                }
            }
            else if(target is Form)
            {
                Form form = (Form)target;
                if(_icons.ContainsKey(name)) form.Icon = _icons[name];
            }
            else if(target is ToolStripItem)
            {
                ToolStripItem item = (ToolStripItem)target;
                item.Image = _imageList.Images[name];
            }
        }
    }
}