• The Old New Thing

    Bowling coming to Bellevue, and given the location, it's naturally upscale

    • 7 Comments

    The Lincoln Square mall in Bellevue will have a new tenant: An upscale bowling all^H^H^Hlounge. Expected to open before the Christmas holiday season, there will be two bars, full-service dining, lots of big plasma screens, all the stuff that makes bowling better. The lounge be positioned on the second floor, beneath Parlor Billiards, a business which by a fantastic coincidence has exactly the same business model as the bowling all^H^H^Hlounge, but with billiards instead of bowling. By Christmastime, the 23,900 square foot venue will be the site of strikes, splits, and, no doubt, lots of failed pick-up attempts.

  • The Old New Thing

    From the research journal Duh: To lose weight, eat less

    • 28 Comments

    Researchers have determined that the key to losing weight is to consume fewer calories.

    Okay, it's actually more interesting than the summary suggests. The researchers compared a variety of different popular diets and found that it didn't matter what diet you were on; the weight loss (and regain) was the same. The controlling factor was how many calories you consumed.

  • The Old New Thing

    Why is there no support in the window manager for mouse button chording?

    • 21 Comments

    Commenter Nekto2 asks why there is no mouse action associated with "click both buttons at the same time".

    The window manager doesn't fire a special event for both mouse buttons held down simultaneously like it does for double-clicks. As with higher-order clicks, mouse chording is something that you have to put together yourself from the basic mouse events that the window manager generates. Add these lines to our scratch program:

    void OnButtonDown(HWND hwnd, BOOL fDoubleClick,
                      int x, int y, UINT keyFlags)
    {
      if ((keyFlags & (MK_LBUTTON | MK_RBUTTON)) ==
                          (MK_LBUTTON | MK_RBUTTON))
      {
        MessageBeep(IDOK);
      }
    }
    
    // Add to WndProc
        HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnButtonDown);
        HANDLE_MSG(hwnd, WM_RBUTTONDOWN, OnButtonDown);
    

    When you run this program, it beeps when both the left and right mouse buttons are pressed.

    Note that the program does not require the two button presses take place simultaneously. Most people do not have the dexterity to push the two buttons at precisely the same instant in time. (Especially since Einstein taught us that simultaneity is relative anyway.)

    Why don't more programs use chording?

    Recall that the semantics of double-clicking should be an extension of the single-click so that your program can perform the single-click action immediately (providing positive feedback to the user that the click was recognized), and then continue to the double-click action if a second click comes in. For example, a common pattern is for the single-click to select the clicked-on item and the double-click to launch it. You can stop at the first click and the result still makes sense. For chords, you have to have two stopping points, one if the user left-clicks and stops, and another if the user right-clicks and stops. Coming up with a chord action that is compatible with both stopping points requires more effort.

    Another reason why many people choose to avoid chords in their user interface design is that chording requires more dexterity, and many users simply don't have the fine motor control necessary to pull it off without accidentally invoking some other action (such as a drag). Chording is also cumbersome to emulate with MouseKeys, so you run afoul of accessibility issues.

    Still, there's nothing technically preventing you from using chording in your program. If you want to code it up, then more power to you.

  • The Old New Thing

    Microspeak: Science project

    • 31 Comments

    A science project is a feature that is really cool and challenging from a technological standpoint but is way overkill for the end-user scenario at hand.

    Back in the late 1990's, a bunch of us cooked up this idea for a networked screen saver that ran at night after most people had gone home from work. You told it the physical location and compass orientation of everybody's monitor. The networked screen saver created a virtual red bouncing ball that traveled around the building in three dimensions. The screen saver showed a viewport into the three-dimensional virtual world that contained the bouncing ball.

    This is a clear example of a science project: Nobody's going to sit there and type the positions and orientations of every computer monitor in the building. Even if we wrote this screen saver, nobody would actually use it. Most of the enjoyment is in actually writing the screen saver than in actually running it.

    One type of science project has high set-up costs for low benefit, like our bouncing ball screen saver.

    Another type of science project requires hardware that very few people have right now. For example, "If you have a tablet connected to at least two touch-enabled external monitors, then you can..."

    A third type of science project is simply trying to solve a problem that nobody really considers to be a problem. You're doing it just for the Gee Whiz factor. For example, "If you have a pair of Bluetooth headphones, and you walk back into range of your computer, the computer can automatically unpause your music." Yeah, I guess you could do that, but it also means that while you are away from your computer, you're walking around looking like an idiot because you're wearing headphones.

    Now, there may be an actual useful feature hiding inside a science project, but until you find that feature and bring it to the surface, what you basically have is a science project.

  • The Old New Thing

    If my window hosts multiple windowless ActiveX controls, which one do I call IOleInPlaceActiveObject::TranslateAccelerator and IOleInPlaceObjectWindowless::OnWindowMessage on?

    • 15 Comments

    Commenter Farproc asks how one goes about hosting two windowless ActiveX controls in a single window. In particular, "none of the documentation explains how to choose which control to send IOle­In­Place­Active­Object::Translate­Accelerator and IOle­In­Place­Object­Windowless::On­Window­Message on?"

    Actually, the documentation does say.

    The documentation for IOle­In­Place­Active­Object::Translate­Accelerator says, "Active in-place objects must always be given the first chance at translating accelerator keystrokes." So you pass the message to the active in-place object. Your window may host multiple windowless ActiveX controls, but at most one of them is the active object at a time. And most of the time, none of them will be active. For example, in Word, most of the time the insertion point is in the text part of the document. Only occasionally do you activate an in-place object by, say, double-clicking on an embedded Excel spreadsheet, at which point Excel adds its menu items to your menu bar and basically takes over your application window for a while.

    Here's an example of Windows 95's Wordpad hosting Paint as an in-place active object.

    Source: 2.1.6 OLE/COM example: using compound documents

    If you have an in-place active object, then it's the one that gets the IOle­In­Place­Active­Object::Translate­Accelerator If, as is usually the case, you don't have an in-place active object, then nobody's IOle­In­Place­Active­Object::Translate­Accelerator gets called because they aren't the in-place active object. (It's right there in the interface name.)

    For IOle­In­Place­Object­Windowless::On­Window­Message, the documentation is even more explicit. It contains pretty much a checklist of what you need to do.

    For the following messages, the container should first dispatch the message to the windowless object that has captured the mouse, if any. Otherwise, the container should dispatch the message to the windowless object under the mouse cursor. If there is no such object, the container is free to process the message itself:

    • WM_MOUSEMOVE
    • WM_SETCURSOR
    • WM_XBUTTONDOWN
    • WM_XBUTTONUP
    • WM_XBUTTONDBLCLK

    The container should dispatch the message to the windowless object with the keyboard focus for the following messages:

    • WM_CANCELMODE
    • WM_CHAR
    • WM_DEADCHAR
    • WM_HELP
    • WM_IMExxx
    • WM_KEYDOWN
    • WM_KEYUP
    • WM_SYSDEADCHAR
    • WM_SYSKEYDOWN
    • WM_SYSKEYUP

    For all other messages, the container should process the message on its own.

    There it is, plain as day.

    Farproc's last question was "how to track or set 'focus' if there is at least one windowless control."

    Um, in a variable?

    I was kind of confused by this question because it's part of the deal that when you use windowless controls, you don't have the window manager to take care of keeping track of which sub-object has focus. That now becomes your job.

    The user clicked on an object. I guess that's the focus object now. Oh wait, now the user hit the left arrow. I guess the object to the left of that object has focus now. It's just like any other control with windowless sub-components, like list boxes. You have to keep track yourself of the currently-selected item and other properties which the window manager normally does for you. If you don't have any windows, then there is nothing for the window manager to manage. From the window manager's point of view, focus is on your container. You then have to manage focus within your window yourself by keeping track of which of your sub-objects is the focus object.

  • The Old New Thing

    Finding the shortest path to the ground while avoiding obstacles

    • 9 Comments

    Today's Little Program solves the following problem:

    Consider a two-dimensional board, tall and narrow. Into the board are nailed a number of horizontal obstacles. Place a water faucet at the top of the board and turn it on. The water will dribble down, and when it hits an obstacle, some of the water will go left and some will go right. The goal is to find the shortest path to the ground from a given starting position, counting both horizontal and vertical distance traveled.

    In the above diagram, the water falls three units of distance until it encounters Obstacle 1, at which some goes to the left and some goes to the right. The water that goes to the left travels three units of distance before it reaches the end of the obstacle, then falls three units and encounters Obstacle 2. Upon reaching Obstable 2, the water can again choose to flow either left or right. The water that flows to the left falls to the ground; the water that flows to the right falls and encounters a third obstacle. From the third obstacle, the water can flow left or right, and either way it goes, it falls to the ground. On the other hand, the water that chose to flow to the right when it encountered Obstable 1 iwould fall past Obstacle 2 (which is not in a position to intercept the water) and land directly on Obstacle 3.

    In the above scenario, there are five paths to the ground.

    • From Obstacle 1, flow left, then from Obstacle 2, flow left again. Total distance traveled: 17 units.
    • From Obstacle 1, flow left, then from Obstacle 2, flow right, then from Obstacle 3, flow left. Total distance traveled: 18 units.
    • From Obstacle 1, flow left, then from Obstacle 2, flow right, then from Obstacle 3, flow right. Total distance traveled: 20 units.
    • From Obstacle 1, flow right, then from Obstacle 3, flow left. Total distance traveled: 16 units.
    • From Obstacle 1, flow right, then from Obstacle 3, flow right. Total distance traveled: 14 units.

    In this case, the shortest path to the ground is the last path.

    There are many ways to attack this problem. The brute force solution would be to enumerate all the possible paths to the ground, then pick the shortest one.

    A more clever solution would use a path-finding algorithm like A*, where the altitude above the ground is the heuristic.

    In both cases, you can add an optimization where once you discover two paths to the same point, you throw out the longer one. This may short-circuit future computations.

    But I'm going to use an incremental solution, since it has the advantage of incorporating the optimization as a convenient side-effect. Instead of studying individual drops of water, I'm going to study all of them at once. At each step in the algorithm, the data structures represent a horizontal cross-section of the above diagram, representing all possible droplet positions at a fixed altitude.

    In addition to collapsing redundant paths automatically, this algorithm has the nice property that it can be done as an on-line algorithm: You don't need to provide all the obstacles in advance, as long as the obstacles are provided in order of decreasing altitude.

    Instead of presenting the raw code and discussing it later (as is my wont), I'll explain the code as we go via code comments. We'll see how well that works.

    I originally wrote the program in C# because I thought I would need one of the fancy collection classes provided by the BCL, but it turns out that I didn't need anything fancier than a hash table. After I wrote the original C# version, I translated it to JavaScript, which is what I present here.

    The inputs which correspond to the diagram above are

    • Initial X position = 6, Initial Y position = 12
    • Obstacle: Left = 3, Right = 7, height = 9
    • Obstacle: Left = 1, Right = 5, height = 6
    • Obstacle: Left = 4, Right = 8, height = 3

    And here's the program.

    function Obstacle(left, right, y) {
     this.left = left;
     this.right = right;
     this.y = y;
    }
    
    // A single step in a path, representing the cost to reach that point.
    function Step(x, y, cost) {
     this.x = x;
     this.y = y;
     this.cost = cost;
    }
    
     // Add a step to an existing step
    Step.prototype.to = function to(x, y) {
     var dx = Math.abs(this.x - x);
     var dy = Math.abs(this.y - y);
     return new Step(x, y, this.cost + dx + dy);
    }
    
    // Record a droplet position
    function addDroplet(l, step) {
     // If no previous droplet at this position or the new droplet
     // has a cheaper path, then remember this droplet.
     var existingStep = l[step.x];
     if (!existingStep || step.cost < existingStep.cost) {
      l[step.x] = step;
     }
    }
    
    // Take an existing collection of locations and updates them to account
    // for a new obstacle. Obstacles must be added in decreasing altitude.
    // (Consecutive duplicate altitudes allowed.)
    function fallTo(oldLocations, obstacle) {
     var newLocations = {};
     for (var x in oldLocations) {
      var step = oldLocations[x];
    
      // fall to the obstacle's altitude
      step = step.to(step.x, obstacle.y);
        
      // If the falling object does not hit the obstacle,
      // then there is no horizontal displacement.
      if (step.x <= obstacle.left || step.x >= obstacle.right) {
       addDroplet(newLocations, step);
      } else {
       // The falling object hit the obstacle.
       // Split into two droplets, one that goes left
       // and one that goes right.
       addDroplet(newLocations, step.to(obstacle.left, obstacle.y));
       addDroplet(newLocations, step.to(obstacle.right, obstacle.y));
      }
     }
     return newLocations;
    }
    
    function printStep(step) {
     console.log("Cost = " + step.cost + ": " + step.x + "," + step.y);
    }
    
    // Debugging function
    function printLocations(l) {
     for (var x in l) printStep(l[x]);
    }
    
    function shortestPath(x, y, obstacles) {
     var l = {};
     l[x] = new Step(x, y, 0);
     printLocations(l);
    
     obstacles.forEach(function (obstacle) {
      l = fallTo(l, obstacle);
      console.log(["after", obstacle.left, obstacle.right, obstacle.y].join(" "));
      printLocations(l);
      console.log("===");
     });
    
     // Find the cheapest step.
     var best;
     for (x in l) {
      if (!best || l[x].cost < best.cost) best = l[x];
     }
    
     // Fall to the floor and print the result.
     printStep(best.to(best.x, 0));
    }
    
    shortestPath(6,12,[new Obstacle(3,7,9),
                       new Obstacle(1,5,6),
                       new Obstacle(4,8,3)]);
    

    This program finds the cost of the cheapest path to the floor, but it merely tells you the cost and not how the cost was determined. To include the winning path, we need to record the history of how the cost was determined. This is a standard technique in dynamic programming: In addition to remembering the best solution so far, you also remember how that solution was arrived at by remembering the previous step in the solution. You can then walk backward through all the previous steps to recover the full path.

    // A single step in a path, representing the cost to reach that point
    // and the previous step in the path.
    function Step(x, y, cost, previous) {
     this.x = x;
     this.y = y;
     this.cost = cost;
     this.previous = previous;
    }
    
     // Add a step to an existing step
    Step.prototype.to = function to(x, y) {
     var dx = Math.abs(this.x - x);
     var dy = Math.abs(this.y - y);
     // These next two test are not strictly necessary. They are for style points.
     if (dx == 0 && dy == 0) {
      // no movement
      return this;
     } else if (dx == 0 && this.previous && this.previous.x == x) {
      // collapse consecutive vertical movements into one
      return new Step(x, y, this.cost + dx + dy, this.previous);
     } else {
      return new Step(x, y, this.cost + dx + dy, this);
     }
    }
    
    function printStep(firstStep) {
     // Walk the path backwards, then reverse it so we can print
     // the results forward.
     var path = [];
     for (var step = firstStep; step; step = step.previous) {
      path.push("(" + step.x + "," + step.y + ")");
     }
     path.reverse();
     console.log("Cost = " + firstStep.cost + ": " + path.join(" "));
    }
    

    Notice that we didn't change any of the program logic. All we did was improve our record-keeping so that the final result prints the full path from the starting point to the ending point.

  • The Old New Thing

    Ken Levine teaches us about television script writing

    • 2 Comments

    Ken Levine has written for some of the most well-known television programs of the past few decades. M*A*S*H, Cheers (for which he won an Emmy), Frasier. And that's not counting his second (third? fourth?) career as a baseball announcer. He writes about whatever is on his mind, be it Barry Bonds' 715th home run, what not to put into your television script, more Cheers trivia than you know what to do with, or Thanksgiving travel tips, and every single one is filled with his personality and attitude. It's a joy to read.

  • The Old New Thing

    Why don't the shortcuts I put in the CSIDL_COMMON_FAVORITES folder show up in the Favorites menu?

    • 25 Comments

    A customer created some shortcuts in the CSIDL_COMMON_FAVORITES folder, expecting them to appear in the Favorites menu for all users. Instead, they appeared in the Favorites menu for no users. Why isn't CSIDL_COMMON_FAVORITES working?

    The CSIDL_COMMON_FAVORITES value was added at the same time as the other CSIDL_COMMON_* values, and its name strongly suggests that its relationship to CSIDL_FAVORITES is the same as the relationship between CSIDL_COMMON_STARTMENU and CSIDL_STARTMENU, or between CSIDL_COMMON_PROGRAMS and CSIDL_PROGRAMS, or between CSIDL_COMMON_DESKTOP­DIRECTORY and CSIDL_DESKTOP­DIRECTORY.

    That suggestion is a false one.

    In fact, CSIDL_COMMON_FAVORITES is not hooked up to anything. It's another of those vestigial values that got created with the intent of actually doing something but that thing never actually happened. I don't think any version of Internet Explorer ever paid any attention to that folder. Maybe the designers decided that it was a bad idea and cut the feature. Maybe it was an oversight. Whatever the reason, it's just sitting there wasting space.

    Sorry for the fake-out.

    Exercise: Another customer wanted to know why creating a %ALL­USERS­PROFILE%\Microsoft\Internet Explorer\Quick Launch directory and putting shortcuts into it did not result in those shortcuts appearing in every user's Quick Launch bar. Explain.

  • The Old New Thing

    When asked to choose among multiple options, the politician will pick all of them

    • 14 Comments

    During the run-up to a local election some time ago, the newspaper posed the same set of questions to each of the candidates and published the responses in a grid format so the readers could easily compare them.

    The candidates agreed on some issues, had opposing positions on others, but the question whose answers struck me was one of the form "If budget cuts forced you to eliminate one of the following four programs, which would you cut?"

    • Candidate 1: "I have no intention of letting our budget get into a situation in which this would become an issue. All of these programs are very important to our community, and under my leadership, they will continue to be funded."
    • Candidate 2: "I don't believe we need to eliminate any of these popular programs. If we review our financial situation, we will find that we can continue to provide for all of them."
    • Candidate 3: "Much as I personally enjoy Program X, it ranks as a lower priority to me than the other options. Program X was originally a community-run program, and I would encourage residents and the business community to step forward and keep alive this program which has greatly benefited our community over the years."

    Notice that the first two candidates, when asked to make a tough decision, opted to make no decision at all. (Compare another election in which the mainstream candidates rated everything as high priority.) The first candidate said, "This would never happen." The second candidate said, "It's not happening." The third candidate is the only one who sat down and made the call to cut one of the programs. The first two were playing politics, afraid to make a decision for fear that it would alienate some portion of the electorate. The third understood the situation and made the hard decision.

    I voted for the third candidate.

    Today is Election Day in the United States. Don't forget to vote. (Void where prohibited.)

  • The Old New Thing

    Raymond misreads newspaper headlines, episode 2

    • 13 Comments

    I have a strange tendency to misread newspaper headlines. This week, I read the headline Rogan Sets 200 Back World Record, and I thought, "There's a world record for running 200 meters backward?"

    Well, actually there is, but this article was actually about the 200 meter backstroke (swimming).

    Once I figured that out, I was not confused by the next headline: Lochte Sets 100 IM World Record.

Page 370 of 430 (4,292 items) «368369370371372»