November, 2006

  • The Old New Thing

    The window manager moves the mouse; applications choose the cursor

    • 34 Comments

    You can sometimes narrow down the source of a problem just by looking at the screen and moving the mouse.

    When you move the mouse, the cursor on the screen moves to match. This work is done in the window manager in kernel mode. The mouse hardware notifies the window manager, "Hey, I moved left twenty units." The window manager takes this value, accelerates or decelerates it according to your mouse acceleration settings, calls any low-level mouse hooks that are installed, and then tells the display driver, "Move that sprite left about thirty pixels" (say). It then sets the "the mouse moved" flag so that the program who owns the window under the new mouse position will get a WM_MOUSEMOVE message. The window manager also sets the cursor to the "virtual cursor state" corresponding to the window beneath the cursor. The "virtual cursor state" remembers the cursor that the thread (or threads, if input has been attached) responsible for the window most recently set. Maintaining the virtual cursor state is important, for if a thread calls SetCursor to change the cursor to an hourglass and then stops processing messages (because it is busy), you really want the cursor to change back to an hourglass when it moves over the thread's windows.

    What does it mean if the cursor doesn't move at all when you move the mouse? Could it be caused by an application? If you read through the flowchart I described above, the only place applications get involved in the "move the mouse cursor" code flow is if they are filtering out the mouse motion in a low-level mouse hook. (Another way an application can "lock up" the mouse is by calling the ClipCursor function, but vanishingly few applications do this. I'm assuming you aren't the victim of malicious software but instead are trying to figure out what program, if any, is accidentally freezing the mouse.)

    Low-level mouse hooks are comparatively uncommon since they exact a high performance penalty on the system. If you're moving your mouse and don't see the cursor move around on the screen, my guess is that there is a problem in the kernel-mode side of the equation. If you're seeing the entire system freeze up, then it's probably a device driver that has started acting up and held a lock for too long.

    A flaky hard drive can have the same effect. If the window manager itself takes a page fault, it has to wait for the hard drive to page in the data. and if the window manager happened to be holding a lock when this happened, that lock is held across the entire I/O operation. If your hard drive is flaky and, say, takes ten seconds to produce a sector of data instead of several milliseconds, then it will look like the system has frozen for ten seconds, since the window manager is stuck waiting on your disk, which is in turn grunting and recalibrating in a desperate attempt to produce the data the memory manager requested.

    In other words: If the cursor won't move, it's likely a driver or hardware problem. (Figuring out which driver/hardware will require hooking up a kernel debugger and poking around. Not for the faint of heart.)

  • The Old New Thing

    Using DIB sections to perform bulk color mapping

    • 6 Comments

    When doing dithering, one operation you have to do for every pixel is map it (more accurately, map a modified version of it) to the nearest color in your available palette. Since this is part of the dithering inner loop, you need this operation to be as fast as possible.¹ A common technique for this is to precompute the nearest palette index for a dense sampling of colors. Any time you need to convert a pixel, you can find a nearby entry in the sampling and look up the precomputed nearest palette index. This won't give you the absolute best match for colors that are very close to the halfway point between two palette colors, but error diffusion dithering is an approximation anyway; if you choose your dense sampling to be "dense enough", these errors are small and are accounted for in the error diffusion algorithm.

    But how do you build up this table mapping each color in your dense sampling to the palette? One way is to call GetNearestPaletteIndex for every pixel in the dense sampling. But the dense sampling by its nature has a large number of entries, and each call to GetNearestPaletteIndex is a ring transition. If only there were a way to do a bulk call to GetNearestPaletteIndex where you pass a whole bunch of COLORREFs at once.

    But there is a way to do that, and that's the idea kernel for today. After all, GDI does it when you do a 24-bit to 8-bit blit. You can harness this energy with the aid of DIB sections: Create a source bitmap that consists of all the color values in your dense sample and a destination bitmap that is an 8bpp DIB section with your palette as its color table. Blit the source onto the destination, and the result is a destination that is exactly the mapping table you need!

    Let's code this up. For the sake of illustration, our dense sampling will consist of 32768 data points distributed throughout the 555 color space. In that way, we can take an RGB value and map it to our 8-bit palette by means of the following expression:

    extern BYTE table[32][32][32];
    index = table[red >> 3][green >> 3][blue >> 3];
    

    Since bitmaps are two-dimensional, we can't generate a three-dimensional table like the one given above. Let's view it not as a 32 × 32 × 32 array but rather as a one-dimensional array with 32768 elements. (This is, after all, how it's represented in memory anyway, so it's not like we're really changing anything physically.) With that minor change of point of view, we're ready to generate the desired table:

    void CreateMappingTable(HPALETTE hpal)
    {
     struct {
      BITMAPINFOHEADER bmiHeader;
      union {
       RGBQUAD bmiColors[256]; // when in palette mode
       DWORD rgMasks[3];       // when in BI_BITFIELDS mode
      };
     } bmi;
    
     PALETTEENTRY rgpe[256];
     UINT cColors = GetPaletteEntries(hpal, 0, 256, rgpe);
     if (cColors) {
      for (UINT i = 0; i < cColors; i++) {
       bmi.bmiColors[i].rgbRed = rgpe[i].peRed;
       bmi.bmiColors[i].rgbBlue = rgpe[i].peBlue;
       bmi.bmiColors[i].rgbGreen = rgpe[i].peGreen;
       bmi.bmiColors[i].rgbReserved = 0;
      }
    
      bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
      bmi.bmiHeader.biWidth = 32768;
      bmi.bmiHeader.biHeight = 1;
      bmi.bmiHeader.biPlanes = 1;
      bmi.bmiHeader.biBitCount = 8;
      bmi.bmiHeader.biCompression = BI_RGB;
      bmi.bmiHeader.biSizeImage = 32768;
      bmi.bmiHeader.biClrImportant = cColors;
      bmi.bmiHeader.biClrUsed = 0;
      bmi.bmiHeader.biXPelsPerMeter = 0;
      bmi.bmiHeader.biYPelsPerMeter = 0;
    
      void *pv8bpp;
      HBITMAP hbmTable = CreateDIBSection(NULL, (BITMAPINFO*)&bmi,
                              DIB_RGB_COLORS, &pv8bpp, NULL, 0);
      if (hbmTable) {
       WORD rgw555[32768];
       for (int i = 0; i < 32768; i++) {
           rgw555[i] = (WORD)i;
       }
       bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
       bmi.bmiHeader.biWidth = 32768;
       bmi.bmiHeader.biHeight = 1;
       bmi.bmiHeader.biPlanes = 1;
       bmi.bmiHeader.biBitCount = 16;
       bmi.bmiHeader.biCompression = BI_BITFIELDS;
       bmi.bmiHeader.biSizeImage = sizeof(rgw555);
       bmi.bmiHeader.biClrImportant = 0;
       bmi.bmiHeader.biClrUsed = 0;
       bmi.bmiHeader.biXPelsPerMeter = 0;
       bmi.bmiHeader.biYPelsPerMeter = 0;
       bmi.rgMasks[0] = 0x7C00;    // 5 red
       bmi.rgMasks[1] = 0x03E0;    // 5 green
       bmi.rgMasks[2] = 0x001F;    // 5 blue
       if (SetDIBits(NULL, hbmTable, 0, 1, rgw555,
                     (BITMAPINFO*)&bmi, DIB_RGB_COLORS)) {
        CopyMemory(table, pv8bpp, 32768);
       }
       DeleteObject(hbmTable);
      }
     }
    }
    

    Nearly all of this function is just preparation for the actual work that happens at the very end.

    First, we get the colors in the palette and have the annoying job of converting them from PALETTEENTRY structures (which is what GetPaletteEntries gives you) to RGBQUAD entries (which is what CreateDIBSection wants). Why the two can't use the same format I will never know.

    Next, we create our destination bitmap, an 8bpp bitmap with the palette entries as the color table, one pixel tall and 32768 pixels wide. Since this is a DIB section, GDI gives us a pointer (pv8bpp) to the actual bits in memory. Since we specified a 1 × 32768 bitmap, the format of the pixel data is just a sequence of 32768 bytes, each one corresponding to a palette index. Wow, that's exactly the format we want for our final table!

    Building the source "bitmap" involves a few tricks. The naive approach is to have a 32768-element array of RGBQUADs, each one describing one of the pixels in our dense sample set. Filling that array would have gone something like this:

    for (r = 0; r < 31; r++)
     for (g = 0; g < 31; g++)
      for (b = 0; b < 31; b++) {
       rgrgb[(r << 10) | (g << 5) | b].rgbRed = r << 3;
       rgrgb[(r << 10) | (g << 5) | b].rgbGreen = g << 3;
       rgrgb[(r << 10) | (g << 5) | b].rgbBlue = b << 3;
       rgrgb[(r << 10) | (g << 5) | b].rgbReserved = 0;
      }
    

    The first trick is to realize that we're just manually converting our 555 pixel data into RGB, something GDI is perfectly capable of doing on its own. Why not save ourselves some effort and just give GDI the 555 bitmap and let it do the conversion from 555 to RGB itself. (Besides, it might not even need to do that conversion; who knows, maybe there's a 555-to-8bpp optimized blit code path inside GDI we can take advantage of.) Using a 555 bitmap gives us this loop instead:

    for (r = 0; r < 31; r++)
     for (g = 0; g < 31; g++)
      for (b = 0; b < 31; b++)
       rgw555[(r << 10) | (g << 5) | b] = (r << 10) | (g << 5) | b;
    

    The second trick is strength-reducing this triple loop to simply

    for (i = 0; i < 32768; i++) {
     rgw555[i] = i;
    }
    

    Now that we have the bitmap data and the BITMAPINFO that describes it, we can use SetDIBits to make GDI do all the work. The function takes our "bitmap" (one row of 32768 pixels, each in a different color and collectively exhausting our dense sample set) and sets it into our DIB section. By the magic of BitBlt, each pixel is mapped to the nearest matching color in the destination palette, and its index is stored as the pixel value.

    And wow, that's exactly the format we want in our table! A little CopyMemory action and we're home free.

    If you think about it in just the right way, this all becomes obvious. You just have to realize that BitBlt (or here one of its moral equivalents, SetDIBits) does more than just copy bits; it maps colors too. And then realize that you can extract the results of that mapping via a DIB section. Since you're handing in an entire bitmap instead of just a single color, you can map all 32768 colors at once.

    Footnote 1: You might consider taking the technique in this article in another direction and simply blitting the entire 24bpp bitmap to a palettized DIB, thereby avoiding the intermediate translation table. The problem with this technique is that parenthetical "more accurately, map a modified version of it". The colors that need to be mapped to the palette are typically not the ones in the source bitmap but instead have been modified in some way by the dithering algorithm. In the case of an error-diffusion dither, the color values being mapped aren't even known until the preceding pixels have already been dithered. As a result, you can't blit all the pixels at once since you don't even know what color values you need to map until you have the result of previous mappings.

    [Updated 9:30am to fix 6's, 3's, 5's and 10's. -Raymond]

  • The Old New Thing

    Manipulating the DIB color table for fun and profit

    • 17 Comments

    If you create a DIB section at 8bpp or lower, then it will come with a color table. Pixels in the bitmap are represented not by their red/blue/green component values, but are instead indices into the color table. For example, a 4bpp DIB section can have up to sixteen colors in its color table.

    Although displays that use 8bpp or lower are considered woefully outdated nowadays, bitmaps in that format are actually quite useful precisely due to the fact that you can manipulate colors in the bitmap, not by manipulating the bits themselves, but instead by manipulating the color table.

    Let's demonstrate this by taking the "Gone Fishing" bitmap and converting it to grayscale. Start with our scratch program and make these changes:

    HBITMAP g_hbm;
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     // change path as appropriate
     g_hbm = (HBITMAP)LoadImage(g_hinst,
                          TEXT("C:\\Windows\\Gone Fishing.bmp"),
                          IMAGE_BITMAP, 0, 0,
                          LR_CREATEDIBSECTION | LR_LOADFROMFILE);
     if (g_hbm) {
      HDC hdc = CreateCompatibleDC(NULL);
      if (hdc) {
       HBITMAP hbmPrev = SelectBitmap(hdc, g_hbm);
       RGBQUAD rgbColors[256];
       UINT cColors = GetDIBColorTable(hdc, 0, 256, rgbColors);
       for (UINT iColor = 0; iColor < cColors; iColor++) {
        BYTE b = (BYTE)((30 * rgbColors[iColor].rgbRed +
                         59 * rgbColors[iColor].rgbGreen +
                         11 * rgbColors[iColor].rgbBlue) / 100);
        rgbColors[iColor].rgbRed = b;
        rgbColors[iColor].rgbGreen = b;
        rgbColors[iColor].rgbBlue = b;
       }
       SetDIBColorTable(hdc, 0, cColors, rgbColors);
       SelectBitmap(hdc, hbmPrev);
       DeleteDC(hdc);
      }
     }
     return TRUE;
    }
    
    void
    OnDestroy(HWND hwnd)
    {
     if (g_hbm) DeleteObject(g_hbm);
     PostQuitMessage(0);
    }
    
    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
     if (g_hbm) {
      HDC hdc = CreateCompatibleDC(NULL);
      if (hdc) {
       HBITMAP hbmPrev = SelectBitmap(hdc, g_hbm);
       BITMAP bm;
       if (GetObject(g_hbm, sizeof(bm), &bm) == sizeof(bm)) {
        BitBlt(pps->hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdc, 0, 0, SRCCOPY);
       }
       SelectBitmap(hdc, hbmPrev);
       DeleteDC(hdc);
      }
     }
    }
    

    The OnDestroy function merely cleans up, and the PaintContent function simply draws the bitmap to the window's client area. All the work really happens in the OnCreate function.

    First, we load the bitmap as a DIB section by passing the LR_CREATEDIBSECTION flag. This opens up the exciting world of DIB sections, but all we care about is the color table. That happens when we call GetDIBColorTable. Since color tables are supported only up to 8bpp, a color table of size 256 is big enough to handle the worst case. Once we get the color table, we go through each color in it and convert it to grayscale, then set the new color table into the DIB section. That's all.

    Notice that we were able to change the color of every single pixel in the bitmap by modifying just 1KB of data. (Four bytes per RGBQUAD times a worst-case of 256 colors.) Even if the bitmap were 1024 × 768, modifying just the color table is enough to change all the colors in the bitmap.

    Manipulating the DIB color table is how flags like LR_LOADMAP3DCOLORS and LR_LOADTRANSPARENT do their work. They don't walk the bitmap updating every single pixel; instead, they just load the color table, look for the colors they are interested in, and change that entry in the color table. This technique of editing the color table is what I was referring to when I suggested you could use DIB sections to avoid the pesky DSna raster operation. And it's faster, too. But it only works on bitmaps that are 8bpp or lower.

    You may also have noticed that LR_LOADTRANSPARENT doesn't actually load a transparent bitmap. Rather, it loads a bitmap that appears to be transparent provided that you draw it against a window whose color is COLOR_WINDOW. Why this misleading name? Because at the time this flag was invented, GDI didn't support transparent bitmaps. (And even today, it still doesn't really support then, with the notable exception of functions like AlphaBlend.) The best you could do was fake it.

  • The Old New Thing

    Keeping classic hardware alive through emulation

    • 25 Comments

    At the Windows 2000 Conference and Expo which coincided with the operating system's launch, I paid a visit to the emulators.com booth, where they were excitedly showing off SoftMac 2000, a Mac emulator that ran on Windows 2000. Emulator trivia: MacOS booted in five seconds under Windows 2000, which was faster than the real Mac, because the emulator simulated a 1GB Mac so the Mac memory manager never had to do any paging. Now, the host computer didn't have 1GB of real RAM, so the host computer was still paging, but it turns out that you're better off letting the Windows 2000 kernel do the paging than the copy of MacOS running inside the emulator.

    Anyway, Darek Mihocka, the proprietor of emulators.com, has started posting his thoughts on Intel's new Core 2, and given the promo titles of his upcoming entries, it looks like he's going to start digging into running Vista on his Mac Pro.

    But all of this yammering about emulation is just a sideshow to the real issue: The picture of the hardware that Darek's retiring. I mean, look at it. He's retiring more computers than I own! I bet he's one of those people who relocates his computers during the winter in order to use them as space heaters.

  • The Old New Thing

    Blitting between color and monochrome DCs

    • 7 Comments

    When blitting between color and monochrome DCs, The text foreground and background colors play a role. We saw earlier that when blitting from a monochrome DC to a color DC, the color black in the source turns into the destination's text color, and the color white in the source turns into the destination's background color. This came in handy when we wanted to colorize a monochrome bitmap.

    This trick works in reverse, too. If you blit from a color DC to a monochrome DC, then all pixels in the source that are equal to the background color will turn white, and all other pixels will turn black. In other words, GDI considers a monochrome bitmap to be black pixels on a white background.

    This trick comes in handy when you want to convert a bitmap with color-keyed transparency into a color bitmap and a mask. Select the color bitmap into the DC hdcColor, and create a monochrome bitmap with the same dimensions and select it into the DC hdcMask. Then the following operations will construct the mask:

    // let's say that the upper left pixel is the transparent color
    COLORREF clrTransparent = GetPixel(hdcColor, 0, 0);
    COLORREF clrBkPrev = SetBkColor(hdcColor, clrTransparent);
    BitBlt(hdcMono, 0, 0, cx, cy, hdcColor, 0, 0, SRCCOPY);
    SetBkColor(hdcColor, clrBkPrev);
    

    We can see this in action in our scratch program by making the following changes:

    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
      
      HBITMAP hbmMono = CreateBitmap(100, 100, 1, 1, NULL);
      HDC hdcMono = CreateCompatibleDC(pps->hdc);
      HBITMAP hbmPrev = SelectBitmap(hdcMono, hbmMono);
      HDC hdcScreen = GetDC(NULL);
    
      SetBkColor(hdcScreen, GetSysColor(COLOR_DESKTOP));
      BitBlt(hdcMono, 0, 0, 100, 100, hdcScreen, 0, 0, SRCCOPY);
    
      SetTextColor(pps->hdc, RGB(0xFF,0,0));
      SetBkColor(pps->hdc, RGB(0,0x80,0));
      BitBlt(pps->hdc, 0, 0, 100, 100, hdcMono, 0, 0, SRCCOPY);
    
      ReleaseDC(NULL, hdcScreen);
      SelectBitmap(hdcMono, hbmPrev);
      DeleteDC(hdcMono);
      DeleteObject(hbmMono);
    }
    

    We start by creating a 100 × 100 monochrome bitmap and selecting it into a memory DC. This will become our mask. Next, we take a screen DC and set its background color to the desktop color and blit from the screen to the monochrome bitmap. This creates a bitmap which is white where the screen has the desktop color and black where the screen has some other color. We show off we show off this new bitmap by painting it into our client area, but just for fun, I made the foreground pixels (black pixels in the monochrome bitmap) bright red and the background pixels (white pixels in the monochrome bitmap) dark green.

    Minimize your windows so the upper left corner of the desktop is visible, and turn off your wallpaper (so the desktop color actually means something). Run this program and observe a copy of your desktop drawn in the window's client area, but with your desktop color turned to green and all the other pixels turned to red.

    The rest of the job of drawing a color bitmap with transparency is now comparatively straightforward. I'll leave it as an exercise. Hint: Raster operation 0x00220326 (DSna) will probably be handy.

    Next time, we'll look at DIB sections as a way of doing fast color manipulation, thereby avoiding the need to perform the DSna ROP entirely.

  • The Old New Thing

    New dessert lounge: Coco la ti da

    • 8 Comments

    After a Seattle Symphony concert, it has long been a tradition among my friends to walk the three blocks from Benaroya Hall, up University Street, past Luly Yang Couture to gawk at the jaw-dropping gowns in the window (the pictures on the web site fail to capture their fabulousness), arriving at the W Hotel, home of Earth & Ocean, where we would settle ourselves in for a post-concert dessert. The desserts themselves were exquisite, thanks to the inspiration of Seattle's "diva of desserts", Sue McCown.

    Or at least, that was our routine until this season.

    Sue McCown left Earth & Ocean during the summer to open her own restaurant, and when we went to Earth & Ocean after a September concert, we could tell that the magic touch was gone. We waited anxiously for Sue's new restaurant Coco la ti da to open, which it did last week. (We so adore her desserts that we feel like we're on a first-name basis with her.)

    It was the new restaurant's opening weekend and the place was so packed that a few of us had to spend some time squeezed two in one chair. Even though it's hardly walking distance from Benaroya Hall, the drive was well worth it. Here's the power Sue McCown has over us: When the waiter came to take our orders, one of my friends who has very high standards for food quality (and those who know this circle of friends will immediately recognize who it is I'm talking about) said, "I'll have one of everything on this page."

    (Now, mind you, there were seven tiny desserts on the page, and a typical order would have been for two to four of them. Therefore, ordering all seven was not too absurd, especially since they were shared with the rest of the table. But the story just sounds better when you say, "X ordered everything on page seven!")

  • The Old New Thing

    What do bitwise operations mean for colors?

    • 9 Comments

    Someday, you're going to pass a raster operation to the BitBlt function that entails bit manipulation. Something like SRCAND perhaps, or possibly the dreaded SRCINVERT. These bitwise operations make perfect sense for monochrome bitmaps, since those are one bit per pixel anyway. But what does it mean for color bitmaps? What do you get when you "and" together forest green (#228B22) and hot pink (#FF69B4)?

    The bitwise operations are performed in the pixel space of the destination. If the destination is a non-palettized bitmap format (higher than 8bpp), then the pixel values are red, blue and green components packed together (in various ways depending on the format). Those values are "and"ed together as integers to produce the result. For example, in a 565-format bitmap, the color forest green is represented as the value 0x2464 (0y00100`100011`00100) and hot pink is 0xFB56 (0y11111`011010`10110). The bitwise "and" of these two values is 0x2044, which is a very dark purple.

    With palettized bitmaps, the results are much less predictable since the values in the bitmap are not colors but color indices. For example, if a pixel has the value 6, that means that the color of the pixel is determined by the entry in slot 6 of the bitmap's color table, and that color could be anything. There is no rule that even requires that color 0 be black and that the highest available color be white, though most bitmaps adhere to this by convention.

    On an 8bpp bitmap, then, the question of what you get when you "and" together forest green and hot pink is underdetermined. If the color table for example happened to put forest green in slot 6 and hot pink in slot 18, the result of the "and" operation would be 6 & 18 = 2, and the result pixel would therefore be whatever color was in slot 2 of the bitmap's color table.

    What does this mean for you, adventuresome blitter?

    If you're going to use raster operations that involve bitwise operations, one of the pixels involved in the operation should be black or white (zero or all-bits-set) in order to obtain predictable results. You can then use identities like "x and black = black" and "x xor black = x" to predict the result, assuming the bitmap follows the convention for black and white noted above. But if you're going to be xor'ing with forest green, then the results could be anything.

  • The Old New Thing

    Converting an HRESULT to a Win32 error code: Diagram and answer to exercise

    • 60 Comments

    Here's the diagram from How do I convert an HRESULT to a Win32 error code?. If you are offended by VML, cover your ears and hum for a while.

    Win32 HRESULT

    The little sliver at the top is the mapping of zero to zero. The big white box at the bottom is the mapping of all negative numbers to corresponding negative numbers. And the rainbow represents the mapping of all the positive values, mod 65536, into the range 0x80070000 through 0x8007FFFF.

    Now let's take a look at that puzzle I left behind:

    Sometimes, when I import data from a scanner, I get the error "The directory cannot be removed." What does this mean?

    My psychic powers told me that the customer was doing something like this (error checking deleted):

    ReportError(HWND hwnd, HRESULT hr)
    {
     DWORD dwError = HRESULT_CODE(hr);
     TCHAR szMessage[256];
     FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL,
                   dwError, 0, szMessage, 256, NULL);
     MessageBox(hwnd, szMessage, TEXT("Error"), MB_OK);
    }
    

    and that the actual HRESULT was WIA_ERROR_COVER_OPEN, which is defined as

    #define WIA_ERROR_COVER_OPEN MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIA, 16)
    

    Passing this value to HRESULT_CODE would yield 16, which maps to

    //
    // MessageId: ERROR_CURRENT_DIRECTORY
    //
    // MessageText:
    //
    //  The directory cannot be removed.
    //
    #define ERROR_CURRENT_DIRECTORY          16L
    

    And that would explain why the customer reported this strange error when reading data from a scanner.

  • The Old New Thing

    A modest proposal: Solving the problem of steroids in baseball

    • 25 Comments

    In recent years, the issue of steroids in baseball has been the source of much hand-wringing. Some people argue that steroids are ruining the game. Others say that you can't go around trampling the Bill of Rights and punishing people for doing things that are perfectly legal anyway.

    To resolve this issue, I present this modest proposal.

    Today, Major League Baseball is divided into two leagues, the National League and the American League, which, aside from the designated hitter rule, are pretty much indistinguishable. (There are other minor rule differences, such as the one regarding batting helmets, 1.16(b).) What we need to do is make these two leagues more distinguished from each other.

    Enter steroids.

    One of the leagues could be the "use all the steroids you want" league, and the other could be the "use steroids and we ban you for life (from this league at least)" league. Given the fact that the American League has demonstrated that it is willing to tweak the rules to create a more exciting, higher-scoring game, it seems natural that the American League should be the one to admit steroids (all the better to pop taters out of the park), whereas the National League can fret about defensive substitutions, double-switches and whether you should pinch-hit for your starting pitcher in the sixth inning when down by one with a runner on first and one out or whether you should have him sacrifice the runner over.

    Players who are juiced up (or want to be juiced up or who don't want to submit to testing) can move to the American League. Players who are willing to be tested up the wazoo can move to the National League.

    (An alternate proposal is to allow players to take all the performance-enhancing drugs they want, but require them to disclose all the drugs they take. That way, fans could evaluate their performance in an informed manner. You could include the information on their baseball cards.)

    Hey, it worked for beauty pageants, it can work for baseball.

    Of course, this means that when there is inter-league play, the American League team will probably clobber the National League team, but then again, that's not much of a change from what we have today already anyway.

    Next time (if there is a next time), I'll solve the problem of traffic in Seattle caused by sporting events.

  • The Old New Thing

    Why can't you use the Tab key to select items from the auto-complete drop-down?

    • 17 Comments

    An anonymous commenter asked why the Tab key doesn't select items from the auto-complete drop-down list. The answer: Because that key already has some other meaning.

    The Tab key is used to navigate among controls in a dialog box. Adding auto-complete behavior to an existing dialog would disturb the tab order, which would in turn frustrate people who had "muscle-memorized" their way through the system. Instead of, "Ctrl+O text Tab Space Enter", it's "Ctrl+O text ... um... try to get the auto-complete drop-down to go away so the Tab key will take you to the next radio button, then hit Tab, Space, Enter."

    If the auto-complete drop-down list were attached to a control in a dialog box, say, the edit box in the Start menu's "Run" dialog, then it wouldn't be possible to tab from the edit box to the Browse button or the OK button. Even worse, notice that the drop-down box covered the OK, Cancel and Browse buttons, so you can't use the mouse to click on them. And since you can't see them, you can't see what their accelerator key is.

    That said, if you really want the Tab key to select items from the auto-complete drop-down, you can pass the SHACF_USETAB flag to the SHAutoComplete function. It appears that Internet Explorer's Open dialog passes this flag, because the scenario I described above actually happens when you want to open a web page as a Web Folder. You type Ctrl+O to open the Open dialog, type the URL, and then grf urgh can't get to the "Open as Web Folder" check-box...

    As for the problem of not remembering its original size: The naive solution can cause more problems than it fixes. Suppose you preserve the size of the drop-down box on Internet Explorer's Address bar. Now the user resizes the window. Does the drop-down box keep its original size? Or does it resize by some mysterious algorithm to "match" the new size of the Address bar? What if the user runs the program after changing the screen resolution to 640×480 and the drop-down becomes bigger than the screen? If you keep the saved size, the resize gripper will end up off the edge of the screen. What if there are two Internet Explorer windows on the screen; should resizing one Address bar drop-down result in the other one changing to match? I'm not asking for answers to these questions. I'm just pointing out that in many cases, coding up a "fix" is the easy step. Designing the fix is the hard step. And then testing the fix with real users may force you to go back and reconsider your original design. Eventually, you spent three weeks fine-tuning this tiny feature. Could that time have been better-spent on some other feature that has greater impact? (Not to mention all the Slashdotters who would say, "Everybody who is working on these tiny features should be punished and told to work on security instead!")

Page 2 of 3 (28 items) 123