The Last NVIDIA Card I'll Ever Own Thu., April 2, 2009This is an angry rant about something that is simply plainly stupid. Be forewarned.
Bye bye, NVIDIA This computer has an NVIDIA GeForce 9600GSO video card in it. I bought this card last August (or thereabouts) to replace another NVIDIA card that was dying (sometimes it wouldn't display anything at all, at random). I had purchased that card only a year before that to replace another video card that was dying (the picture would occasionally fritz out or jump around the screen, until one day it simply displayed no picture at all). I had gotten that card as a replacement for another card that was dying that was less than a year old. In total, in the last ten years I've owned twelve cards with NVIDIA chipsets on them. And if you do the math on that, the average card lasted less than a year. In fact, there were three years in the early 2000s that I had a card with ATI chips instead, so that means the average NVIDIA card has lasted less than eight months. Simple Needs, Simply Unmet I don't know why I didn't learn my lesson before. My needs are simple. I don't do gaming. I'm just a software engineer, and I need two screens. That's it. That's all. This current card has the issue that if I put the computer in hibernation, sometimes one of my screens isn't working when I start it back up. Oh, and there's also the issue that one of the two big 22" widescreen displays I purchased for this computer doesn't work with the card at all (literally: The screen goes randomly white when I connect it to this card, as though the card is using the wrong voltage; when I connect it to my wife's computer, it's fine, so my second monitor is her old small 17" one, and she gets my second big one). The only fix for the non-working screen is to uninstall NVIDIA's drivers, reboot the computer, and completely reinstall them, and reboot again. Then it works fine until I try to put the computer into hibernation, after which there's a 10% chance I'll have to do the same stupid thing all over again. Fool me once... I've learned my lesson. A dozen dead NVIDIA cards ? I have a box of 'em here ? each from different manufacturers leads me to conclude that it's not the manufacturer but the chip vendor. So I say never again. Never again will I be held hostage to NVIDIA's broken garbage. The three years I had that ATI card it performed flawlessly. Perfect. It never asked me any questions; it never told me any lies. It just did what it was supposed to do. I've contacted NVIDIA's tech support, of course, and they don't really seem to care. They never have, not in ten years of buying their stupid crappy products. So I repeat: NEVER AGAIN. You hear that, NVIDIA? You've lost a customer for good, and not just any customer ? I'm technically saavy, and people all around me ask what they should buy and what they should avoid. I'll be teaching classes soon, and giving recommendations to dozens of novices on what kinds of computer equipment to buy ? and your name will be in the "avoid at all cost" list. You could have prevented this. You could have fixed outstanding bugs instead of focusing on new software features. You could have worked on your hardware quality control. You could have at least acted like you cared when I called or e-mailed you in the last ten years. But you didn't. And I now have a mission: To ensure that no-one I know ever buys your stupid broken garbage again. Lest you think I'm just sour over everything computers, I intend to talk in another journal posting about some of the "winner" products I've used over the last twenty years, and show what they do right that you're doing so wrong. Because this is not a rant against everything computer-related: This is a rant by someone who knows better against a company whose products define the very word fail. And so, in summary, let me say this: If you're an average user buying computer equipment, DON'T BUY NVIDIA. YOU HAVE BEEN WARNED. I learned my lesson the hard way, and I'll never make this mistake again so long as I live. ProColor, Part 2: Mouse Capturing Fri., March 20, 2009It's time for the second article in my occasional series on the ProColor color-picker. Last time, I gave a rough overview of what ProColor is and why it's designed the way it is. In this article, I'm going to cover some the topics in reverse and discuss capturing the mouse on a web page.
What's an event? Let's start at the beginning: What's an event, and why would you ever want to capture one? An event in most user-interfaces means an action performed by the user: A key press, a mouse movement, or a mouse-button press, among others. There are other kinds of events, too, but these are the ones we'll be concerned about. Events are little structures, little tiny chunks of data, that describe what happened and where it happened, in full, so that they can be sent to appropriate places to be processed. A key-press event, for example, typically includes which key was pressed, whether Ctrl or Shift or Alt was held down while it was being pressed, and maybe where the mouse was on screen at the time. Capturing and focusing are two different ways of ensuring that the events go where they need to. Usually, events go to whatever window is appropriate for them: A "this window was just hidden" event gets sent to the window that was hidden, for example. But what about keypresses? Where should they go? The keyboard is external, after all, and not necessarily attached to any one window. Focusing solves this problem: Only one window can have the focus at a time, and input from devices that have no way of directing themselves to a specific window (keyboards, joysticks, microphones, etc.) all have their input go to the focus window. So when you click on, say, a textbox, the window system assigns that textbox the focus, and then all input events get directed to that textbox. At least that's the theory: Different window systems do this with different levels of directness and precision, but all of them have the concept of focus for the keyboard device at least. Capturing solves a different problem. Some devices, like the mouse, have the concept of position built into them: When you move the mouse, the window that should receive the events for that action is obvious, because the mouse is "over" it. But what if your program doesn't want those events to go to that window? You might be wondering why events shouldn't go where they belong, but it's a common need in user interfaces. When you press the mouse button while you're over a scrollbar's thumb, the scrollbar should receive all events until you release the mouse button so that the thumb can be dragged around to follow the mouse — even if the mouse isn't over the scrollbar. Capturing mouse events, then, is a core component of modern window-system functionality, and most window systems provide some sort of tools to support it: Microsoft gives us SetCapture(), X-Windows gives us XGrabPointer(), and others have a variety of ways to handle the problem, from calling functions to attaching event handlers to creating whole new specialized event-processing loops. Unfortunately, the web doesn't. And this means that implementing page elements that track the mouse — elements that look like buttons or scrollbars or dropdowns, for example — isn't something that's built into your web browser. Fortunately, there's a very good way around this problem, and that's what we're going to talk about here. How events get dispatched Many of us programmers know how a traditional event-processing model works in a window system: The window system collects events from devices, posts those events into a queue, and your program retrieves those events and (optionally) dispatches them to some target window or processes them directly. Most window systems work like this; there's GetMessage() in Windows; there XNextEvent() in X-Windows; there's even EvtGetEvent() on the old Palm Pilot to retrieve events, and dispatching them is usually an indirect function call or giant switch statement. Sadly, the web doesn't work like that. On the web, the event-processing loop occurs outside your page in the browser itself. The browser collects events and dispatches them to the various page elements whenever it feels like doing so. In current implementations, you at least have the guarantee that the events are dispatched using a single thread, but the Document Object Model (the DOM) doesn't actually guarantee even that much. Worse, there's no function named Event.BeginCapturing or something like it. So if event dispatching is totally outside your control, how do you take control of it? Well, the good news is that every time an event arrives and needs to be dispatched, the web browser sends it to not one page element but several. The event dispatches are grouped into two categories: capturing and bubbling. During the capture phase, the web browser walks down the DOM tree until it reaches the target element, asking each parent element if it wants the event. Then, during the bubbling phase, the web browser goes back up the tree, asking each element again if it wants the event. Only then if the event gets all the way back to the top of the tree and nobody wants it does the web browser perform default actions like "select text" or "click checkbox." This is better explained visually. Consider a very simple page like the one below. It has a left sidebar with some links, and a central area with a form and an <input> field and also a <div> that has a background image that makes the <div> look like a snazzy shiny black pushbutton. ![]() Let's say the user clicks on the <div> "pushbutton." The web browser first follows the red arrows, asking the <body> and then the <div> and then the <form> and then finally the innermost <div> if they want the event (the "capturing" phase, so named because it was designed to make capturing events easy). And then the browser goes back up the blue arrows, asking the innermost <div> and then the <form> and then the <div> and then finally the <body> if they each want the event (the "bubbling" phase, so named because the event seems to "bubble" to the top of the document). "Great," you think. "So all I have to do to capture events, then, is to attach an event-handling function to the <body> and have it grab events on their way down the tree, right? Easy as pie!" Good thought, but sorry, you're wrong. You see, there's a minor problem with the above diagram, which is that's how most web browsers send events. Unfortunately, Internet Explorer does it differently: IE doesn't do the "capturing" phase at all — no red arrows, which means that an event handler attached to the <body> will never see an event if that innermost <div> — or any other element higher up — processes it first. Doesn't it figure that IE's the fly in our ointment? Capturing events, portably Since you can't use the "capturing" side of event processing — and the Prototype Javascript library doesn't even support that on non-IE browsers because they don't want you to get your hopes up that it'll actually work — how do you actually capture? The answer, as is often true with hacky technology, is that you need to cheat. In this case, we're going to do something that you can do with web browsers but that you can't readily do with window systems: We're going to use transparent objects. First, rather than thinking of your page as flat, imagine looking at it sideways. Each element is like a flat chunk of plastic and sticks out just a little bit. Elements are not "inside" other elements but "stack on top of" other elements, so that really "deep" elements in the tree are actually very "tall" when looked at from the side. You can see this depicted below: ![]() From this perspective, the browser's actions during the bubbling phase take on a new appearance: Rather than starting at the deepest element in the tree, the browser starts the event at the highest element in the stack and walks down the stack with the event until it runs out of elements or until one of the elements actually claims the event. This distinction is important, because it allows us to take control of the dispatching in a way that's almost a little magical: What if we had another element higher than all the others? If we did, that element would be the one that received the event first! In fact, if that layer covers the entire page, then the event will only "bubble" across two elements total: That "cover" element and the <body> itself — none of the other page elements would ever see the mouse event at all. ![]() This, then, is how we reliably capture mouse events reliably in Javascript: During the period where we want to capture events, we create a new topmost layer and attach the event handler to it, and then when we're done capturing, we destroy the topmost layer. Implementing MouseCapture Once you know the principle, the code is pretty easy. In fact, ProColor comes with a MouseCapture class that encapsulates this basic principle in a simple, reusable way. The complete code for the MouseCapture class is short, and is included at the end of this article. Let's start by seeing how you might use the MouseCapture class to track a pushbutton. I'm going to dispense with the usual event-handling boilerplate code and just use a onmousedown handler to keep the presentation simple (but note that in real code, you'll want to handle the "mousedown" events properly). Here's our example HTML:
<img id='mybutton' src='mybutton.png' width='120' height='30'
alt='Button' onmousedown='TrackButton("mybutton")' />
<script type='text/javascript'><!--
function TrackButton(id) {
$(id).src = 'mybutton_pressed.png';
var capture = new MouseCapture;
capture.begin(function(event) {
switch (event.type) {
case 'mouseup':
$(id).src = 'mybutton.png';
capture.end();
break;
}
});
}
--></script>Let's look at how this works. The TrackButton() function will get called when the mouse button is pressed over the image. The TrackButton() function changes the image to appear "pressed," and then creates a new MouseCapture object. MouseCapture.begin() is used to initiate the capture, and it gets passed a callback function — a function that will receive all of the events until the capture ends. Our capture function here watches the events it receives, and when the mouse button is released ("mouseup"), it changes the image back to the unpressed version and ends the capture.Simple enough, really. There are a few caveats. First, the event's coordinates (the mouse's coordinates) are relative to the entire document — not any one specific element within it: They're absolute coordinates. You can retrieve those coordinates by calling the standard Prototype functions event.pointerX() and event.pointerY(). You can also cheat by calling Element.isEventIn(), a custom function included with ProColor that tells you whether an event occurred within a given element. Let's use Element.isEventIn() to implement a second version of the tracking routine; in this version, if the user slides the mouse off of the "pushbutton," the "pushbutton" will pop up:
<img id='mybutton' src='mybutton.png' width='120' height='30'
alt='Button' onmousedown='TrackButton("mybutton")' />
<script type='text/javascript'><!--
function TrackButton(id) {
$(id).src = 'mybutton_pressed.png';
var button_is_pressed = true;
var capture = new MouseCapture;
capture.begin(function(event) {
switch (event.type) {
case 'mousemove':
var mouse_is_over = Element.isEventIn(id, event);
if (mouse_is_over != button_is_pressed) {
if (mouse_is_over) $(id).src = 'mybutton.png';
else $(id).src = 'mybutton_pressed.png';
button_is_pressed = mouse_is_over;
}
break;
case 'mouseup':
$(id).src = 'mybutton.png';
capture.end();
break;
}
});
}
--></script>This is a slightly more complex version of the same routine. This one watches when the mouse moves while the mouse button is being pressed, and if the mouse moves outside the "pushbutton", the "pushbutton" is redrawn as unpressed; and if the mouse moves back in, the "pushbutton" is redrawn as pressed.You can use similar techniques for all sorts of tricks: For example, ProColor uses the MouseCapture solution to handle dragging of the selection on the color wheel, to handle dragging of the slider next to the color wheel, to handle clicks on the color palette, and to handle clicks on the color-picker dropdown. There are lots of other ways you could potentially use it as well. Use your imagination: The sky's the limit! Conclusion I hope I've shed some light on how a very useful programming technique can be readily applied to elements on web pages as well. The invisible upper-layer technique is simple and flexible, and works on every major browser; my MouseCapture class and example scripts will run just fine on IE6, IE7, IE8, Firefox, Safari, Opera 8+, and Chrome — anywhere Prototype will run. In closing, below you can find the source code to my MouseCapture class and the associated Element.build() and Element.isEventIn() methods. This is designed to work with Prototype, but I have no doubt that with some effort, you could rework it to behave nicely with JQuery or Dojo. If you have any questions, please feel free to post them: This is a blog, and that's what the comment section is for! The Code
/*---------------------------------------------------------------------------------
** Add two useful methods to Element. Many other libraries add build(), and ours
** is, in theory, compatible with all of their versions.
*/
Element.addMethods({
/* Create a child element with the given options (attributes) and style. */
build: function(element, type, options, style) {
var e = $(document.createElement(type));
$H(options).each(function(pair) { e[pair.key] = pair.value; });
if (style) e.setStyle(style);
element.appendChild(e);
return e;
},
/* Return true if this event was a mouse event inside the given element. */
isEventIn: function(element, event) {
var d = element.getDimensions();
var p = element.cumulativeOffset();
var x = event.pointerX(), y = event.pointerY();
return (x >= p.left && y >= p.top
&& x < p.left+d.width && y < p.top+d.height);
}
});
/*---------------------------------------------------------------------------------
** This is a simple class for capturing all mouse input after a mouse button
** is held down until the mouse button is released.
*/
var MouseCapture = Class.create({
initialize: function() { },
onEvent: function(event, callback) {
if (callback && event.type != 'mouseover' && event.type != 'mouseout')
callback(event, event.type);
event.stop();
},
setCursor: function(c) {
if (this.div)
this.div.setStyle({ cursor: c });
},
begin: function(callback) {
/* Create our event listener. We'll need this object now, and later on to
be able to stop listening to events too. */
this.listener = this.onEvent.bindAsEventListener(this, callback);
/* Start observing events. */
Event.observe(document, 'mouseup', this.listener);
Event.observe(document, 'mousemove', this.listener);
Event.observe(document, 'mousedown', this.listener);
Event.observe(document, 'mouseover', this.listener);
Event.observe(document, 'mouseout', this.listener);
Event.observe(document, 'keyup', this.listener);
Event.observe(document, 'keydown', this.listener);
/* Don't let the browser perform text-selection while capturing. */
this.old_body_ondrag = document.body.ondrag;
this.old_body_onselectstart = document.body.onselectstart;
document.body.ondrag = function () { return false; };
document.body.onselectstart = function () { return false; };
var body = Element.extend(document.body);
var dim = body.getDimensions();
/* Build a (nearly) invisible <div> that covers the entire document and that
will capture all events, even those that would go to iframes. */
this.div = body.build('div', { }, {
display: 'block',
position: 'absolute',
top: '0px', left: '0px',
width: dim.width + 'px', height: dim.height + 'px',
zIndex: 999999999,
cursor: 'default',
backgroundColor: '#FFFFFF',
opacity: 0.0001
});
},
end: function() {
/* Remove our invisible event-capturing <div>. */
this.div.remove();
/* Stop event observing. */
Event.stopObserving(document, 'mouseup', this.listener);
Event.stopObserving(document, 'mousemove', this.listener);
Event.stopObserving(document, 'mousedown', this.listener);
Event.stopObserving(document, 'mouseover', this.listener);
Event.stopObserving(document, 'mouseout', this.listener);
Event.stopObserving(document, 'keyup', this.listener);
Event.stopObserving(document, 'keydown', this.listener);
/* Reenable selection. */
document.body.ondrag = this.old_body_ondrag;
document.body.onselectstart = this.old_body_onselectstart;
delete this.old_body_ondrag;
delete this.old_body_onselectstart;
}
});
The ProColor color-picker Thu., March 12, 2009I'd like to talk a little bit about web coding, specifically client-side Javascript and DOM coding.
For CMXpress, I needed a good drop-down/pop-up color-picker for use with Prototype, and it seemed to me like such a thing ought to be an easy thing to find, but in reality, it turned out to be as rare as a blue moon. There are some basic controls out there, some with irritating popups, some with tolerable design but ugly licensing, and I couldn't have any of that. CMXpress is open-source, under the BSD license, and I needed something that would play nice with that. I also needed something that would play nice with Prototype, and that ideally would be friendly to your garden-variety end-user and that would also ideally look like a control that was native to the browser. So, since nothing fit the bill, I built ProColor. You can see a screenshot below, or you can visit ProColor's website and download yourself a copy. ![]() In a future posting, I'll talk about how ProColor works internally. And if you want to learn how to use it, that's well-documented on ProColor's own website. Here, I'd just like to spend a little time elaborating on what it is, why it is that way, and what it can do for you. What is it? ProColor is a color-picker: It lets end-users choose RGB colors, and it records the chosen color either in its Javascript object or in an input field (or both). They can choose them by sliding the slider, moving around the color wheel, picking a color from a palette of "web-safe" colors, or typing in absolute values. It's smooth and fast, even on Internet Explorer, and "feels" like a built-in popup box in most web browsers (with Safari being the notable exception, since it's styled to look very Windowsy). It's implemented as a single Javascript source file with some associated images, and the Javascript source file is available in compressed form. It works properly on most modern browsers from IE6 upward, and has been tested on IE, Firefox, Opera, Chrome, and Safari. It is accessibility-friendly, too, since it responds to keyboard events, has its internal controls able to take the focus, and can readily fall back to just a simple text field if necessary. It's easy to insert into your sites, too, since it can be implemented with almost no Javascript coding (and can even be implemented with no Javascript coding on your part in many cases). Why is it designed the way it is? ProColor has several features that are designed to come together from a user-interaction perspective. First, it has the color wheel, which is arguably more artist-friendly than the color wash or color wave used in some color pickers. Yes, Photoshop uses the color wave model, but artists are taught to think in terms of the color wheel, so ProColor uses the color wheel to stick to artists' mental model. It also displays the color palette. Some people may wonder why there's a color palette when you have a color wheel, but there are good reasons: First, the color palette consists only of "web safe" colors, and some web designers may prefer those for compatibility. Second, a good user-interaction axiom is to present users with a few initial options and then let the user choose one and alter it, rather than forcing the user to choose everything from scratch, and ProColor believes in that: A user who wants a shade of dark red, for example, could go the long route of choosing red on the wheel and then dark on the slider, but that's two clicks just to get to an initial value: But with a color palette, the user can click "dark red" directly to choose the initial color, and then alter it with the other settings. The color palette, in short, makes users able to "design" their colors faster. It displays the RGB value in two different forms, the #hex form in the input field (using a variety of formatting options), and the raw decimal values in the input boxes. This lets users type in exact colors, and they can tab between the input fields. It displays the HSB value as well, since some users want to be able to work with that. It does not display a CMYK equivalent, since the web's colors do not match those from printers very well. And all of the fields respond intelligently to the keyboard: The wheel and slider move with the arrow keys and shift and ctrl and home/end/pageup/pagedown; and keys like Esc and Enter/Return close the popup, as you'd expect. What can it do for me? For one thing, it's free, open-source, and well-documented. You have no excuse not to use it, other than page-load times, and in the age of broadband, that's a poor excuse. For another, it's incredibly easy to use. It can be attached to any <input> element on a page, and it even has an incredibly easy form where you can write this: <input type='text' class='procolor' value='...' /> By simply adding "procolor" to the input field's classname, that input field will automatically have a color-picker dropdown button displayed beside it. Or you can create ProColor objects dynamically in your Javascript. The library is very flexible, and very customizable, and is designed to easily fit into any page you can throw at it. In short, if you have a page that ever asks users to type in colors, you can use ProColor to significantly improve the user-friendliness of your page. In my next few postings, I'll spend some effort talking about how ProColor works. When I built ProColor, I found that there was very little good documentation out there on how to build good, native-feeling Javascript controls, and I think it'll be useful to tell people what I learned. I think the sequence of blog postings will go something like this:
Gainless Employment :-( Sat., November 29, 2008I've been hunting for a new job since the start of October.
It started out as a light search two months ago, but I'm now searching really hard, and I'm starting to be worried. The economy's bad, and it's not a good time to be hunting for jobs. In theory, I'm in the one sector that has growth potential ? computers, especially web programming ? and I'm highly skilled and well-educated, but those facts don't seem to be helping much. My current employment consists of a lot of consulting work, and some sales of some software I wrote. That was fine when I was a bachelor, but now that I'm married, I'm earning for two, because my wife got laid off in July and hasn't successfully found full-time work yet. It's not like I'm asking for much; I'm a talented, skilled professional, darn it, but I'm willing to work for peanuts just to get off my feet: Several folks in the industry think I'm worth about $75K/year, so I started out hoping for $70K/year with benefits, and I've watched my price point slide from $65K to $60K to $50K and now I'm getting close to being willing to settle for anything over minimum wage. (Okay, I'm not <i>quite</i> that desperate yet, but ask me again in a month...) We're already not buying Christmas gifts this year, and we've cut back on everything. We don't waste money: I'm half Dutch and half Jewish! We haven't bought electronics of any kind in over a year, we haven't bought clothes, we have a small 900-square-foot apartment ? we've been thrifty. But even so, thanks to my wife's lack of income, her (large) student loan payments, the rising cost of everything (including our depressingly large health-insurance premiums), and my current meager income, about a week ago we hit negative for the first time: We owe more money than we actually have. I don't know how I'll pay December's bills when they come around, much less my $1000 quarterly tax bill in January. So the upshot is that lately I'm highly unambitious about art and writing and life in general. I'm doing little else lately but job-searching and hearing "You're a great candidate" but never hearing anything beyond that, and I'm feeling generally pummeled by the world. Maybe this'll seem like nothing in a few months, but right now, I'm at the bottom of a pit, and the narrow shaft of sunlight that's sustained me is thinning by the day, soon to vanish altogether. | Welcome to my site! This is the web site of Sean W., the Phantom Inker. I'm a 32-year-old soon-to-be-married professional programmer and amateur artist. Want to contact me? I'm friendly and I don't bite! I have a variety of contact points:
This front page is a mirror of my LiveJournal. If you're here for the art, it's over in my gallery! |
Copyright © 2003-2009 by the Phantom Inker. All rights reserved.



