June, 2005

  • The Old New Thing

    Google is the cute two-year-old girl with curly blond hair that gets all the attention

    • 85 Comments

    Let's see, Google Maps adds the world outside the United States, Canada and the UK, and people go ga-ga. Nevermind that Google's new "maps" have nothing beyond country boundaries. "Aww, look at Google, she's so cute and adorable!"

    I'm sure the people at the existing online map services like MapQuest and MSN MapPoint are sitting there like older siblings, wondering when exactly they turned into chopped liver. MSN MapPoint has actual maps of most of Europe, and MapQuest's library of maps is larger still. (Kathmandu anyone?) Both sites provide documentation on how to link directly to them. Yet they don't get drooled over.

    Somebody at MapQuest should take out a full page ad that goes something like this:

    Dear Google Maps,

    Welcome to the rest of the world! If you ever need driving directions, don't hesitate to ask.

    Love ever,
    MapQuest

  • The Old New Thing

    The Date/Time control panel is not a calendar

    • 85 Comments

    Although many people use the Date/Time control panel to flip through a calendar, that's not what it is for. In fact, if you use it that way, you can create all sorts of havoc!

    In its original incarnation in Windows 95, the Date/Time control panel changed your date and time. If you clicked through the calendar to see next month, you actually changed your system clock to next month. If you changed your mind and clicked "Cancel", the Date/Time control panel undid its changes and restored the date to the original date.

    In other words, here's what happened, step by step:

    • On April 1, you open the Date/Time control panel.
    • You change the month to May. The Date/Time control panel changes your system date to May 1. If you are running an appointment calendar program, all appointments from the month of April will fire. (For example, your April 15th alarm to remind you to file your income taxes.) You are annoyed by all these alerts and you cancel them.
    • You decide you didn't want to change the month to May after all and click Cancel.
    • The Date/Time control panel changes the date back to April 1.
    • On April 15th, your income tax alarm fails to fire because you cancelled it, remember?

    In other words, the Date/Time control panel was not designed for letting you flip through a calendar. It was designed for changing the system date and time.

    Unaware of its design, people have been using the Date/Time control panel as if it were a calendar, not realizing that it was doing all sorts of scary things behind the scenes. It's like using a cash register as an adding machine. Sure, it does a great job of adding numbers together, but you're also messing up the accounting back at the main office!

    For Windows 2000, in reluctant recognition of the way people had been mis-using the Date/Time control panel, it was rewritten so that it doesn't change the system time until you hit the Apply button.

    Aaron Margosis shows you how to grant a user permission to change the system date and time without having to make them a full administrator.

  • The Old New Thing

    Perhaps this is what airport security is worried about

    • 12 Comments

    As if there aren't enough things in the world to worry about. Now you have to watch out for exploding knitting needles.

  • The Old New Thing

    What's the difference between autocomplete and dropdown history?

    • 5 Comments

    One shows things that might be, the other shows things that were. Both of them try to help you type something, but they operate differently (and look confusingly similar).

    Let's take the second case first. Dropdown history, like you see in the Run dialog, common file dialogs, and the Internet Explorer address bar. The cue for dropdown history is a button with a downward-pointing arrow on it. When you click it, a list box with a scrollbar appears. Dropdown history shows you what you already typed into the dialog previously, and its contents are independent of what you've typed so far.

    Autocomplete tries to guess what you're typing based on an existing database, typically files on your hard drive or web pages you've visited. The autocomplete dropdown is a popup window that filters itself as you type.

    Since dropdown history remembers what you actually typed, the Run dialog history can remember command line arguments. On the other hand, autocomplete is based on what's on the system and what web sites you've visited, so it can suggest things you've never typed. Type "C:\" and an autocomplete window appears with everything in the root of your C drive. On the other hand, autocomplete can't remember command line arguments since that's not what it's drawing from.

  • The Old New Thing

    Is there ever such a thing as a good letter from the IRS?

    • 22 Comments

    I arrived home yesterday to find a letter from the IRS in my mailbox. Dare I open it?

    The only other interaction with the IRS I'd had previously was several years ago where they told me I had overpaid my taxes and sent me a refund check of a few hundred dollars. I went back and reviewed my return and concluded that my original return was correct and they were wrong to send me the refund check. I called the friendly IRS office and explained how I came to the numbers I did. The agent went through the same computations and agreed with me, and instructed me how to return the check with an explanatory letter. It's not worth committing tax fraud over just a few hundred dollars.

    With some trepidation, I opened the letter.

    This time, the IRS believed that in 2003, I failed to report income in the amount of over ten times my actual salary. What happened?

    There were a few errors that they picked up, on the order of a few thousand dollars, although enough of them were errors in my favor that I think I came out ahead on that part. (For example, I forgot to report my sales of WorldCom and Nortel stock. Like many other peop^H^H^H^Hsuckers, I took a bath on both, and those losses easily covered gains elsewhere.)

    But a few thousand dollars is nowhere near the tenfold alleged underreporting. Where did that come from?

    I sold a lot of stock that year as part of a larger "realignment" of my personal finances, and due to the weirdness of United States tax law, the way I sold some of the shares required the income to be reported in a special way. And that's how I reported it. The IRS happily accepted that income but simultaneously claimed that I failed to report it! Not only do they want to double-count the income/loss from those stock sales, but since they don't have cost basis information, they assumed that the sales were pure profit.

    Now I get to spend the weekend taking a few deep breaths, doing a lot of photocopying of supporting documents, and writing a friendly letter back to the IRS, explaining why I believe they over-over-overcounted my income. I hope they accept it.

  • The Old New Thing

    Why do NTFS and Explorer disagree on filename sorting?

    • 11 Comments

    Some people have noticed that NTFS automatically sorts filenames, but does so in a manner different from Explorer. Why is that?

    For illustration purposes, I created files with the following names:

    Name Code point Description
    aU+0061Latin small letter A
    bU+0062Latin small letter B
    ×U+00D7Multiplication sign
    åU+00E5Latin small letter A with ring above
    øU+00F8Latin small letter O with stroke

    And here's the sort order for various scenarios, at least on my machine. (You'll later see why it's important whose machine you test on.)

    Plain "dir" command
    aU+0061Latin small letter A
    bU+0062Latin small letter B
    åU+00E5Latin small letter A with ring above
    ×U+00D7Multiplication sign
    øU+00F8Latin small letter O with stroke
     
    "dir /on"
    ×U+00D7Multiplication sign
    aU+0061Latin small letter A
    åU+00E5Latin small letter A with ring above
    bU+0062Latin small letter B
    øU+00F8Latin small letter O with stroke
     
    Explorer sorted by name
    ×U+00D7Multiplication sign
    aU+0061Latin small letter A
    åU+00E5Latin small letter A with ring above
    bU+0062Latin small letter B
    øU+00F8Latin small letter O with stroke

    First, notice that Explorer and "dir /on" agree on the alphabetic sort order. (Once you throw digits into the mix, things diverge.) This is not a coincidence. Both are using the default locale's word sort algorithm.

    Why does the raw NTFS sort order differ?

    Because NTFS's raw sort order has different goals.

    The "dir /on" and Explorer output are sorting the items for humans. When sorting for humans, you need to respect their locale. If my computer were in Sweden, Explorer and "dir /on" would have sorted the items in a different order:

    ×U+00D7Multiplication sign
    aU+0061Latin small letter A
    bU+0062Latin small letter B
    åU+00E5Latin small letter A with ring above
    øU+00F8Latin small letter O with stroke

    You can ask a Swede why this is the correct sort order if you're that curious. My point is that different locales have different sorting rules.

    NTFS's raw sort order, on the other hand, is not for humans. As we saw above, sorting for humans can result in different results depending on which human you ask. But there is only one order for files on the disk, and NTFS needs to apply a consistent rule so that it can find a file when asked for it later.

    In order to maintain this consistency, the NTFS raw sort order cannot be dependent upon such fickle properties as the current user's locale. It needs to lock in a sort algorithm and stick to it. As Michael Kaplan pointed out earlier, NTFS captures the case mapping table at the time the drive is formatted and continues to use that table, even if the OS's case mapping tables change subsequently. Once the string has been converted to uppercase, it then needs to be sorted. Since this is not for humans, there's no need to implement the complex rules regarding secondary and tertiary keys, the interaction between alphanumerics and punctuation, and all the other things that make sorting hard. It just compares the code points as binary values, also known as an ordinal sort.

    In summary, therefore, Explorer sorts the items so you (a human) can find them. NTFS sorts the items so it (the computer) can find them. If you're writing a program and you want the results of a directory listing to be sorted, then sort it yourself according to the criteria of your choice.

  • The Old New Thing

    What is the difference between "Unpin from Start menu" and "Remove from this list"?

    • 34 Comments

    The list of programs on the left hand side of the Start menu is really two lists. (You can see the separator line between them.) The top list is the so-called "pin list". This is the list of programs you picked to be "locked" to the top of the Start menu. You can "pin" a program by right-clicking it and selecting "Pin to Start menu", or you can just drag it directly into the pin area.

    The bottom list on the left hand side of the Start menu is the "most frequently used" section of the Start menu, which selects the programs you have used the most in the past month or so.

    If you right-click an item that happens to be in the Start menu's pin list (either by right-clicking it from the pin list itself, or by right-clicking the original), one of the options is "Unpin from Start menu". If you select this option, then the item is removed from the Pin list.

    If you right-click an item on the "most frequently used" section of the Start menu, one of the options is "Remove from this list". If you select this option, then the item is removed from the "most frequently used" section of the Start menu. As far as the Start menu is concerned, you never ran that program. Of course, as you start running the program subsequently, it works its way up the popularity chain and might break into your "most frequently used" list based on your usage after you removed it originally.

    The difference, then, between the two, is that each removes the item you clicked from a different list. One removes it from the pin list, and the other removes it from the "most frequently used programs" list. There is a line separating the two lists, but most people don't realize that the line is there for a reason. It's not just a pretty face.

    To make things less (or perhaps more) confusing, if you select "Remove from this list" for an item from the pin list, it also removes it from the pin list as well as removing it from the "most frequently used programs" list.

  • The Old New Thing

    Displaying the dictionary, part 3: Using an owner-data listview

    • 26 Comments

    Owner-data listviews let you take over data management from the listview. This is useful in our case since we have over twenty thousand dictionary entries, and creating even that many blank listview items takes an unacceptably long amount of time.

    Let's convert our listview to an owner-data listview. Believe it or not, this is quite easy to do once we have the text callback technique from last time. Make the following changes:

    LRESULT RootWindow::OnCreate()
    {
      m_hwndLV = CreateWindow(WC_LISTVIEW, NULL,
                      WS_VISIBLE | WS_CHILD | WS_TABSTOP |
                      LVS_NOSORTHEADER | LVS_OWNERDATA |
                      LVS_SINGLESEL | LVS_REPORT,
                      0, 0, 0, 0,
                      m_hwnd,
                      (HMENU)IDC_LIST,
                      g_hinst,
                      NULL);
     ...
     // for (int i = 0; i < Length(); i++) {
     //  const DictionaryEntry& de = Item(i);
     //  LVITEM item;
     //  item.mask = LVIF_TEXT;
     //  item.iItem = i;
     //  item.iSubItem = COL_TRAD;
     //  item.pszText = const_cast<LPWSTR>(de.m_pszTrad);
     //  item.iItem = ListView_InsertItem(m_hwndLV, &item);
     //  if (item.iItem >= 0) {
     //   item.iSubItem = COL_PINYIN;
     //   item.pszText = const_cast<LPWSTR>(de.m_pszPinyin);
     //   ListView_SetItem(m_hwndLV, &item);
     //   item.iSubItem = COL_ENGLISH;
     //   item.pszText = const_cast<LPWSTR>(de.m_pszEnglish);
     //   ListView_SetItem(m_hwndLV, &item);
     //  }
     // }
     return 0;
    }
    

    That's right, we made things better by deleting code. Isn't that satisfying?

    Owner-data is like the text callback mechanism in the extreme: The listview doesn't record any information about the contents of your items. Whenever it needs something, it always asks. To create twenty thousand items, we just call ListView_SetItemCount and tell it that there are twenty thousand items. (There is also a ListView_SetItemCountEx macro which lets you pass flags, none of which are relevant here.)

    In many owner-data cases, the data comes from an external source, in which case the LVN_ODCACHEHINT notification can be helpful. The listview sends this notification to say, "I'm going to be asking a lot of questions about items in this range. You might want to go work on them." Note that the listview might ask questions about items outside the range, too. The notification is just a hint that most of the questions are likely to be in that range. In our case, we have all the data ahead of time, so we have no need for the hint.

    Notice that with this change to an owner-data listview, the program starts up almost instantly. Remember also the way we arranged the data in our string pool: All the strings for an item are adjacent, and strings for consecutive items follow one another. This means that all the data for one screenful of information resides in contiguous memory. Result: Better locality, fewer page faults. We'll see more benefits of the string pool later.

    That's all for this month. Next month, we'll come back to filling in the second column of data: the simplified Chinese characters.

  • The Old New Thing

    Displaying the dictionary, part 2: Using text callbacks

    • 12 Comments

    As we noted last time, adding items to the listview takes an absurd amount of time. Today, we'll make a failed attempt at improving this because it lets me illustrate a listview technique and it lays the groundwork for the real fix next time.

    Instead of creating the items in their entirety, let's set their text to LPSTR_TEXTCALLBACK. This is a placeholder value which indicates "I'm not going to tell you what the string is. If you need it, call me back."

    class RootWindow : public Window
    {
     ...
     LRESULT OnCreate();
     LRESULT OnNotify(NMHDR* pnm);
     void OnGetDispInfo(NMLVDISPINFO* pnmv);
     ...
    };
    
    LRESULT RootWindow::OnCreate()
    {
     ...
     // item.pszText = const_cast<LPWSTR>(de.m_pszTrad);
     item.pszText = LPSTR_TEXTCALLBACK;
     ...
       // item.pszText = const_cast<LPWSTR>(de.m_pszPinyin);
       item.pszText = LPSTR_TEXTCALLBACK;
     ...
       // item.pszText = const_cast<LPWSTR>(de.m_pszEnglish);
       item.pszText = LPSTR_TEXTCALLBACK;
     ...
    }
    
    LRESULT RootWindow::OnNotify(NMHDR *pnm)
    {
     switch (pnm->code) {
     case LVN_GETDISPINFO:
      OnGetDispInfo(CONTAINING_RECORD(pnm, NMLVDISPINFO, hdr));
      break;
     }
     return 0;
    }
    
    void RootWindow::OnGetDispInfo(NMLVDISPINFO* pnmv)
    {
     if (pnmv->item.iItem < 0 || // typo fixed 11am
         pnmv->item.iItem >= Length()) {
      return;         // requesting invalid item
     }
    
     if (pnmv->item.mask & LVIF_TEXT) {
      const DictionaryEntry& de = Item(pnmv->item.iItem);
      LPCWSTR pszResult = L"";
      switch (pnmv->item.iSubItem) {
       case COL_TRAD:    pszResult = de.m_pszTrad;    break;
       case COL_PINYIN:  pszResult = de.m_pszPinyin;  break;
       case COL_ENGLISH: pszResult = de.m_pszEnglish; break;
      }
      pnmv->item.pszText = const_cast<LPWSTR>(pszResult);
     }
    
     if (pnmv->item.mask & LVIF_IMAGE) {
      pnmv->item.iImage = -1;
     }
    
     if (pnmv->item.mask & LVIF_STATE) {
         pnmv->item.state = 0;
     }
    
    }
    
    LRESULT RootWindow::HandleMessage(
                              UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     ...
      case WM_NOTIFY:
       return OnNotify(reinterpret_cast<NMHDR*>(lParam));
     ...
    }
    

    Instead of setting the strings when we create the listview items, we set their texts to LPSTR_TEXTCALLBACK. When the listview needs the text, it sends us a LVN_GETDISPINFO notification, which we handle by returning the data that the listview requested.

    Sidebar: In our case, obtaining the missing data is very fast. If it were slow, we could have optimized the function further by adding the line

     pnmv->item.mask |= LVIF_DI_SETITEM;
    
    to the end. This tells the listview, "Please cache these results and don't ask me for them again." That way, we do the slow computation only once.

    After making these changes (though not the LVIF_DI_SETITEM change; that was just a sidebar), notice that the it didn't really help much. On my machine, the startup time dropped from eleven to ten seconds, but ten seconds is still way too long. This optimization turns out to have been a washout.

    (Note also that our program is now relying heavily on the fact that a vector is a fast random-access data structure.)

    We'll do better next time.

  • The Old New Thing

    Seattle International Juggling Festival 2005

    • 6 Comments

    In Seattle this weekend, you will have the opportunity to see people throwing things more things into the air than they have hands for, and even learn how to do it yourself:

    [T]he primary goal of this festival will be to teach as many people to juggle as possible.

    The Big Show is Saturday at 7pm. The events are free but donations are accepted.

Page 2 of 4 (35 items) 1234