← Back to team overview

linuxdcpp-team team mailing list archive

[Branch ~dcplusplus-team/dcplusplus/trunk] Rev 2812: Context-sensitive help tooltips in the settings dialog

 

------------------------------------------------------------
revno: 2812
committer: poy <poy@xxxxxxxxxx>
branch nick: trunk
timestamp: Thu 2012-01-12 17:14:38 +0100
message:
  Context-sensitive help tooltips in the settings dialog
modified:
  changelog.txt
  dwt/include/dwt/aspects/Help.h
  dwt/include/dwt/aspects/Keyboard.h
  dwt/include/dwt/widgets/ToolTip.h
  dwt/src/widgets/Table.cpp
  dwt/src/widgets/ToolTip.cpp
  dwt/src/widgets/Tree.cpp
  win32/ADLSearchFrame.cpp
  win32/CommandDlg.cpp
  win32/MainWindow.cpp
  win32/NotificationsPage.cpp
  win32/NotificationsPage.h
  win32/PropPage.cpp
  win32/PropPage.h
  win32/SettingsDialog.cpp
  win32/SettingsDialog.h
  win32/StylesPage.cpp
  win32/StylesPage.h
  win32/UserMatchDlg.cpp
  win32/WinUtil.cpp
  win32/WinUtil.h


--
lp:dcplusplus
https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk

Your team Dcplusplus-team is subscribed to branch lp:dcplusplus.
To unsubscribe from this branch go to https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk/+edit-subscription
=== modified file 'changelog.txt'
--- changelog.txt	2012-01-08 13:03:30 +0000
+++ changelog.txt	2012-01-12 16:14:38 +0000
@@ -4,6 +4,8 @@
 * Add a "Download full list" button in the file list toolbar (poy)
 * "Get file list" now defaults to partial file lists (poy)
 * Reclaim memory after a file list match
+* Improve file reading operations
+* [L#678432] Context-sensitive help tooltips in the settings dialog (poy)
 
 -- 0.790 2011-12-29 --
 * Fav users frame becomes users frame and shows all users

=== modified file 'dwt/include/dwt/aspects/Help.h'
--- dwt/include/dwt/aspects/Help.h	2011-11-12 19:36:12 +0000
+++ dwt/include/dwt/aspects/Help.h	2012-01-12 16:14:38 +0000
@@ -47,13 +47,14 @@
 	typedef std::function<void (unsigned&)> id_function_type;
 
 public:
-	unsigned getHelpId() const {
+	unsigned getHelpId() {
 		unsigned ret = ::GetWindowContextHelpId(H());
 		if(!ret) {
 			WidgetType* parent = dynamic_cast<WidgetType*>(W().getParent());
 			if(parent)
 				ret = parent->getHelpId();
 		}
+		helpImpl(ret);
 		if(id_function)
 			id_function(ret);
 		return ret;
@@ -63,17 +64,12 @@
 		::SetWindowContextHelpId(H(), id);
 	}
 
-	/**
-	* set a callback function that can modify the id returned by getHelpId. note that the
-	* dispatcher used by onHelp doesn't call getHelpId, so this won't affect messages dispatched
-	* from WM_HELP. in order to modify help ids dispatched via the function defined in onHelp, use
-	* helpImpl.
-	*/
+	/** set a callback function that can modify the id returned by getHelpId. */
 	void setHelpId(id_function_type f) {
 		id_function = f;
 	}
 
-	void onHelp(std::function<void (WidgetType*, unsigned)> f) {
+	void onHelp(std::function<void (WidgetType*)> f) {
 		W().addCallback(Message(WM_HELP), [f, this](const MSG& msg, LRESULT& ret) -> bool {
 			LPHELPINFO lphi = reinterpret_cast<LPHELPINFO>(msg.lParam);
 			if(!lphi || lphi->iContextType != HELPINFO_WINDOW)
@@ -88,10 +84,7 @@
 			if(!widget)
 				return false;
 
-			unsigned id = lphi->dwContextId;
-			widget->helpImpl(id);
-
-			f(widget, id);
+			f(widget);
 
 			ret = TRUE;
 			return true;
@@ -102,10 +95,7 @@
 	id_function_type id_function;
 
 	/** implement this in the derived widget in order to change the help id before it is
-	* dispatched. if you are not using onHelp to define callbacks but simply calling getHelpId when
-	* necessary, then it is the setHelpId overload which takes a function as input that you are
-	* looking for.
-	*/
+	dispatched. */
 	virtual void helpImpl(unsigned& id) {
 		// empty on purpose.
 	}

=== modified file 'dwt/include/dwt/aspects/Keyboard.h'
--- dwt/include/dwt/aspects/Keyboard.h	2011-12-17 14:08:57 +0000
+++ dwt/include/dwt/aspects/Keyboard.h	2012-01-12 16:14:38 +0000
@@ -53,6 +53,19 @@
 	static bool isControlPressed() { return isKeyPressed(VK_CONTROL); }
 	static bool isAltPressed() { return isKeyPressed(VK_MENU); }
 
+	static bool isAnyKeyPressed() {
+		BYTE keys[256];
+		if(::GetKeyboardState(keys)) {
+			for(size_t i = 0; i < 256; ++i) {
+				// ignore virtual key codes for mouse buttons. for the rest, look at the high-order bit.
+				if(i != VK_LBUTTON && i != VK_RBUTTON && i != VK_MBUTTON && i != VK_XBUTTON1 && i != VK_XBUTTON2 && keys[i] & 0x80) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
 	/// Checks if Caps Lock is on
 	/** Use this function if you need to determine if Caps Lock is ON
 	  */

=== modified file 'dwt/include/dwt/widgets/ToolTip.h'
--- dwt/include/dwt/widgets/ToolTip.h	2012-01-11 21:42:27 +0000
+++ dwt/include/dwt/widgets/ToolTip.h	2012-01-12 16:14:38 +0000
@@ -77,6 +77,10 @@
 	void setTool(Widget* widget, F callback);
 
 	void setMaxTipWidth(int width);
+	/** get a delay value. see the TTM_GETDELAYTIME doc for possible params. */
+	int getDelay(int param);
+	/** set a delay value. see the TTM_GETDELAYTIME doc for possible params. use -1 for default. */
+	void setDelay(int param, int delay);
 
 	void setActive(bool b);
 	void refresh();

=== modified file 'dwt/src/widgets/Table.cpp'
--- dwt/src/widgets/Table.cpp	2012-01-08 22:14:11 +0000
+++ dwt/src/widgets/Table.cpp	2012-01-12 16:14:38 +0000
@@ -41,6 +41,8 @@
 #include <dwt/DWTException.h>
 #include <dwt/dwt_vsstyle.h>
 #include <dwt/dwt_vssym32.h>
+#include <dwt/WidgetCreator.h>
+#include <dwt/widgets/ToolTip.h>
 
 namespace dwt {
 
@@ -621,10 +623,11 @@
 
 void Table::setTooltips(TooltipF f) {
 	addRemoveTableExtendedStyle(LVS_EX_INFOTIP, true);
-	HWND tip = ListView_GetToolTips(handle());
-	if(tip) {
+	HWND tipH = ListView_GetToolTips(handle());
+	if(tipH) {
 		// make tooltips last longer
-		::SendMessage(tip, TTM_SETDELAYTIME, TTDT_AUTOPOP, MAKELPARAM(::SendMessage(tip, TTM_GETDELAYTIME, TTDT_AUTOPOP, 0) * 3, 0));
+		auto tip = WidgetCreator<ToolTip>::attach(this, tipH);
+		tip->setDelay(TTDT_AUTOPOP, tip->getDelay(TTDT_AUTOPOP) * 3);
 	}
 	addCallback(Message(WM_NOTIFY, LVN_GETINFOTIP), [f](const MSG& msg, LRESULT&) -> bool {
 		auto& tip = *reinterpret_cast<LPNMLVGETINFOTIP>(msg.lParam);

=== modified file 'dwt/src/widgets/ToolTip.cpp'
--- dwt/src/widgets/ToolTip.cpp	2012-01-11 21:42:27 +0000
+++ dwt/src/widgets/ToolTip.cpp	2012-01-12 16:14:38 +0000
@@ -72,6 +72,14 @@
 	addTool(widget);
 }
 
+int ToolTip::getDelay(int param) {
+	return sendMessage(TTM_GETDELAYTIME, param);
+}
+
+void ToolTip::setDelay(int param, int delay) {
+	sendMessage(TTM_SETDELAYTIME, param, (delay >= 0) ? MAKELPARAM(delay, 0) : -1);
+}
+
 void ToolTip::setActive(bool b) {
 	sendMessage(TTM_ACTIVATE, b ? TRUE : FALSE);
 }

=== modified file 'dwt/src/widgets/Tree.cpp'
--- dwt/src/widgets/Tree.cpp	2012-01-11 20:53:02 +0000
+++ dwt/src/widgets/Tree.cpp	2012-01-12 16:14:38 +0000
@@ -66,7 +66,7 @@
 
 void Tree::create( const Seed & cs )
 {
-	Control::Seed mySeed(WS_CHILD);
+	Control::Seed mySeed(WS_CHILD, WS_EX_CONTROLPARENT);
 
 	BaseType::create(mySeed);
 	tree = WidgetCreator<TreeView>::create(this, cs);

=== modified file 'win32/ADLSearchFrame.cpp'
--- win32/ADLSearchFrame.cpp	2011-12-22 22:14:45 +0000
+++ win32/ADLSearchFrame.cpp	2012-01-12 16:14:38 +0000
@@ -111,7 +111,7 @@
 		addWidget(button);
 
 		button = WinUtil::addHelpButton(grid);
-		button->onClicked([this] { WinUtil::help(this, IDH_ADL_SEARCH); });
+		button->onClicked([this] { WinUtil::help(this); });
 		addWidget(button);
 	}
 

=== modified file 'win32/CommandDlg.cpp'
--- win32/CommandDlg.cpp	2011-12-03 21:53:57 +0000
+++ win32/CommandDlg.cpp	2012-01-12 16:14:38 +0000
@@ -164,11 +164,11 @@
 		[this] { handleOKClicked(); },
 		[this] { endDialog(IDCANCEL); });
 
-	WinUtil::addHelpButton(grid)->onClicked([this] { WinUtil::help(this, IDH_USER_COMMAND); });
+	WinUtil::addHelpButton(grid)->onClicked([this] { WinUtil::help(this); });
 
 	if(bOpenHelp) {
 		// launch the help file, instead of having the help in the dialog
-		WinUtil::help(this, IDH_USER_COMMAND);
+		WinUtil::help(this);
 	}
 
 	int newType = -1;

=== modified file 'win32/MainWindow.cpp'
--- win32/MainWindow.cpp	2012-01-07 20:59:41 +0000
+++ win32/MainWindow.cpp	2012-01-12 16:14:38 +0000
@@ -209,7 +209,7 @@
 		callAsync([this] {
 			SystemFrame::openWindow(getTabView(), false, false);
 
-			WinUtil::help(this, IDH_GET_STARTED);
+			WinUtil::helpId(this, IDH_GET_STARTED);
 			handleSettings();
 		});
 	}
@@ -343,10 +343,10 @@
 	{
 		MenuPtr help = mainMenu->appendPopup(T_("&Help"));
 
-		help->appendItem(T_("Help &Contents\tF1"), [this] { WinUtil::help(this, IDH_INDEX); }, WinUtil::menuIcon(IDI_HELP));
-		help->appendItem(T_("Get started"), [this] { WinUtil::help(this, IDH_GET_STARTED); }, WinUtil::menuIcon(IDI_GET_STARTED));
+		help->appendItem(T_("Help &Contents\tF1"), [this] { WinUtil::helpId(this, IDH_INDEX); }, WinUtil::menuIcon(IDI_HELP));
+		help->appendItem(T_("Get started"), [this] { WinUtil::helpId(this, IDH_GET_STARTED); }, WinUtil::menuIcon(IDI_GET_STARTED));
 		help->appendSeparator();
-		help->appendItem(T_("Change Log"), [this] { WinUtil::help(this, IDH_CHANGELOG); }, WinUtil::menuIcon(IDI_CHANGELOG));
+		help->appendItem(T_("Change Log"), [this] { WinUtil::helpId(this, IDH_CHANGELOG); }, WinUtil::menuIcon(IDI_CHANGELOG));
 		help->appendItem(T_("About DC++"), [this] { handleAbout(); }, WinUtil::menuIcon(IDI_DCPP));
 		help->appendSeparator();
 
@@ -443,7 +443,7 @@
 	}
 	toolbar->setLayout(StringTokenizer<string>(SETTING(TOOLBAR), ',').getTokens());
 	toolbar->onCustomized([this] { handleToolbarCustomized(); });
-	toolbar->onCustomizeHelp([this] { WinUtil::help(toolbar, IDH_CUSTOMIZE_TB); });
+	toolbar->onCustomizeHelp([this] { WinUtil::helpId(toolbar, IDH_CUSTOMIZE_TB); });
 	toolbar->onContextMenu([this](const dwt::ScreenCoordinate &sc) { return handleToolbarContextMenu(sc); });
 
 	toolbar->onHelp(&WinUtil::help);

=== modified file 'win32/NotificationsPage.cpp'
--- win32/NotificationsPage.cpp	2011-10-08 15:21:54 +0000
+++ win32/NotificationsPage.cpp	2012-01-12 16:14:38 +0000
@@ -146,7 +146,6 @@
 	table->onSelectionChanged([this] { handleSelectionChanged(); });
 	table->onDblClicked([this] { handleDblClicked(); });
 
-	table->onHelp([this](Widget*, unsigned id) { handleTableHelp(id); });
 	table->setHelpId([this](unsigned& id) { handleTableHelpId(id); });
 }
 
@@ -167,19 +166,10 @@
 	}
 }
 
-void NotificationsPage::handleTableHelp(unsigned id) {
-	// same as PropPage::handleListHelp
-	int item =
-		isKeyPressed(VK_F1) ? table->getSelected() :
-		table->hitTest(dwt::ScreenCoordinate(dwt::Point::fromLParam(::GetMessagePos()))).first;
-	if(item >= 0 && options[item].helpId)
-		id = options[item].helpId;
-	WinUtil::help(table, id);
-}
-
 void NotificationsPage::handleTableHelpId(unsigned& id) {
 	// same as PropPage::handleListHelpId
-	int item = table->getSelected();
+	int item = isAnyKeyPressed() ? table->getSelected() :
+		table->hitTest(dwt::ScreenCoordinate(dwt::Point::fromLParam(::GetMessagePos()))).first;
 	if(item >= 0 && options[item].helpId)
 		id = options[item].helpId;
 }

=== modified file 'win32/NotificationsPage.h'
--- win32/NotificationsPage.h	2011-05-27 11:03:29 +0000
+++ win32/NotificationsPage.h	2012-01-12 16:14:38 +0000
@@ -64,7 +64,6 @@
 
 	void handleSelectionChanged();
 	void handleDblClicked();
-	void handleTableHelp(unsigned id);
 	void handleTableHelpId(unsigned& id);
 	void handleSoundClicked();
 	void handleBalloonClicked();

=== modified file 'win32/PropPage.cpp'
--- win32/PropPage.cpp	2012-01-08 22:14:11 +0000
+++ win32/PropPage.cpp	2012-01-12 16:14:38 +0000
@@ -99,7 +99,6 @@
 
 	list->setColumnWidth(0, LVSCW_AUTOSIZE);
 
-	list->onHelp([this, list](Widget*, unsigned id) { handleListHelp(list, id); });
 	list->setHelpId([this, list](unsigned& id) { handleListHelpId(list, id); });
 }
 
@@ -152,21 +151,11 @@
 	return grid->getPreferredSize() + dwt::Point(padding.left + padding.right, padding.top + padding.bottom);
 }
 
-void PropPage::handleListHelp(TablePtr list, unsigned id) {
+void PropPage::handleListHelpId(TablePtr list, unsigned& id) {
 	// we have the help id of the whole list-view; convert to the one of the specific option the user wants help for
-	int item =
-		isKeyPressed(VK_F1) ? list->getSelected() :
+	int item = isAnyKeyPressed() ? list->getSelected() :
 		list->hitTest(dwt::ScreenCoordinate(dwt::Point::fromLParam(::GetMessagePos()))).first;
 	const ListItem* listItems = lists[list];
 	if(item >= 0 && listItems[item].helpId)
 		id = listItems[item].helpId;
-	WinUtil::help(list, id);
-}
-
-void PropPage::handleListHelpId(TablePtr list, unsigned& id) {
-	// we have the help id of the whole list-view; convert to the one of the specific option the user wants help for
-	int item = list->getSelected();
-	const ListItem* listItems = lists[list];
-	if(item >= 0 && listItems[item].helpId)
-		id = listItems[item].helpId;
 }

=== modified file 'win32/PropPage.h'
--- win32/PropPage.h	2011-05-04 19:32:00 +0000
+++ win32/PropPage.h	2012-01-12 16:14:38 +0000
@@ -75,7 +75,6 @@
 private:
 	virtual dwt::Point getPreferredSize();
 
-	void handleListHelp(TablePtr list, unsigned id);
 	void handleListHelpId(TablePtr list, unsigned& id);
 
 	unordered_map<TablePtr, const ListItem*> lists;

=== modified file 'win32/SettingsDialog.cpp'
--- win32/SettingsDialog.cpp	2012-01-11 21:42:27 +0000
+++ win32/SettingsDialog.cpp	2012-01-12 16:14:38 +0000
@@ -75,7 +75,7 @@
 tip(0)
 {
 	onInitDialog([this] { return initDialog(); });
-	onHelp([this](Control* c, unsigned id) { handleHelp(c, id); });
+	onHelp(&WinUtil::help);
 	onClosing([this] { return handleClosing(); });
 }
 
@@ -98,11 +98,9 @@
 }
 
 bool SettingsDialog::initDialog() {
-	/*
-	* set this to IDH_INDEX so that clicking in an empty space of the dialog generates a WM_HELP
-	* message with no error; then SettingsDialog::handleHelp will convert IDH_INDEX to the current
-	* page's help id.
-	*/
+	/* set this to IDH_INDEX so that clicking in an empty space of the dialog generates a WM_HELP
+	message with no error; then SettingsDialog::helpImpl will convert IDH_INDEX to the current
+	page's help id. */
 	setHelpId(IDH_INDEX);
 
 	grid = addChild(Grid::Seed(1, 2));
@@ -212,13 +210,17 @@
 			[this] { handleClosing(); handleOKClicked(); },
 			[this] { handleClosing(); endDialog(IDCANCEL); });
 
-		WinUtil::addHelpButton(cur)->onClicked([this] { handleHelp(this, IDH_INDEX); });
+		WinUtil::addHelpButton(cur)->onClicked([this] { WinUtil::help(this); });
 	}
 
 	/* use a hidden tooltip to determine when to show the help tooltip, so we don't have to manage
 	timers etc. */
 	tip = addChild(ToolTip::Seed());
-	auto timeout = tip->sendMessage(TTM_GETDELAYTIME, TTDT_AUTOPOP);
+
+	// make tooltips last longer
+	auto timeout = tip->getDelay(TTDT_AUTOPOP) * 3;
+	tip->setDelay(TTDT_AUTOPOP, timeout);
+
 	tip->addCallback(dwt::Message(WM_NOTIFY, TTN_GETDISPINFO), [timeout](const MSG& msg, LRESULT&) -> bool {
 		auto& ttdi = *reinterpret_cast<LPNMTTDISPINFO>(msg.lParam);
 		auto widget = dwt::hwnd_cast<dwt::Control*>(reinterpret_cast<HWND>(ttdi.hdr.idFrom));
@@ -253,16 +255,30 @@
 	dwt::Control* widget = dwt::hwnd_cast<dwt::Control*>(hwnd);
 
 	if(widget && widget != dialog->help) {
-		widget->onFocus([=] { dialog->handleChildHelp(widget); });
+		// update the bottom help box on focus / sel change.
+		widget->onFocus([dialog, widget] { dialog->handleChildHelp(widget); });
 
 		TablePtr table = dynamic_cast<TablePtr>(widget);
 		if(table)
-			table->onSelectionChanged([=] { dialog->handleChildHelp(widget); });
+			table->onSelectionChanged([dialog, widget] { dialog->handleChildHelp(widget); });
 
+		// associate a tooltip with widgets that may provide a valid cshelp id.
 		auto id = widget->getHelpId();
-		if(id >= IDH_CSHELP_BEGIN && id <= IDH_CSHELP_END) {
-			// this widget has a valid cshelp id; associate a tooltip tool to it.
+		if((id >= IDH_CSHELP_BEGIN && id <= IDH_CSHELP_END) || table) {
 			dialog->tip->addTool(widget);
+
+			// special refresh logic for tables as they may have different help ids for each item.
+			if(table) {
+				table->onMouseMove([dialog, table](const dwt::MouseEvent&) -> bool {
+					const auto id = table->getHelpId();
+					static int prevId = -1;
+					if(static_cast<int>(id) != prevId) {
+						prevId = static_cast<int>(id);
+						dialog->tip->sendMessage(TTM_UPDATE);
+					}
+					return false;
+				});
+			}
 		}
 	}
 	return TRUE;
@@ -272,12 +288,6 @@
 	help->setText(Text::toT(WinUtil::getHelpText(widget->getHelpId())));
 }
 
-void SettingsDialog::handleHelp(dwt::Control* widget, unsigned id) {
-	if(id == IDH_INDEX && currentPage)
-		id = currentPage->getHelpId();
-	WinUtil::help(widget, id);
-}
-
 bool SettingsDialog::handleClosing() {
 	dwt::Point pt = getWindowSize();
 	SettingsManager::getInstance()->set(SettingsManager::SETTINGS_WIDTH,
@@ -352,3 +362,8 @@
 
 	currentPage->getParent()->layout();
 }
+
+void SettingsDialog::helpImpl(unsigned& id) {
+	if(id == IDH_INDEX && currentPage)
+		id = currentPage->getHelpId();
+}

=== modified file 'win32/SettingsDialog.h'
--- win32/SettingsDialog.h	2012-01-11 21:42:27 +0000
+++ win32/SettingsDialog.h	2012-01-12 16:14:38 +0000
@@ -76,11 +76,13 @@
 	bool initDialog();
 	static BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam);
 	void handleChildHelp(dwt::Control* widget);
-	void handleHelp(dwt::Control* widget, unsigned id);
 	bool handleClosing();
 	void handleSelectionChanged();
 	void handleOKClicked();
 	void handleCtrlTab(bool shift);
+
+	// aspects::Help
+	void helpImpl(unsigned& id);
 };
 
 #endif

=== modified file 'win32/StylesPage.cpp'
--- win32/StylesPage.cpp	2012-01-11 21:42:27 +0000
+++ win32/StylesPage.cpp	2012-01-12 16:14:38 +0000
@@ -170,7 +170,6 @@
 
 	table->onSelectionChanged([this] { handleSelectionChanged(); });
 
-	table->onHelp([this](Widget*, unsigned id) { handleTableHelp(id); });
 	table->setHelpId([this](unsigned& id) { handleTableHelpId(id); });
 }
 
@@ -363,19 +362,10 @@
 	bgColor->setEnabled(enable);
 }
 
-void StylesPage::handleTableHelp(unsigned id) {
-	// same as PropPage::handleListHelp
-	int item =
-		isKeyPressed(VK_F1) ? table->getSelected() :
-		table->hitTest(dwt::ScreenCoordinate(dwt::Point::fromLParam(::GetMessagePos()))).first;
-	if(item >= 0)
-		id = table->getData(item)->helpId;
-	WinUtil::help(table, id);
-}
-
 void StylesPage::handleTableHelpId(unsigned& id) {
 	// same as PropPage::handleListHelpId
-	int item = table->getSelected();
+	int item = isAnyKeyPressed() ? table->getSelected() :
+		table->hitTest(dwt::ScreenCoordinate(dwt::Point::fromLParam(::GetMessagePos()))).first;
 	if(item >= 0)
 		id = table->getData(item)->helpId;
 }

=== modified file 'win32/StylesPage.h'
--- win32/StylesPage.h	2011-12-11 18:36:51 +0000
+++ win32/StylesPage.h	2012-01-12 16:14:38 +0000
@@ -134,7 +134,6 @@
 	CheckBoxPtr showGen;
 
 	void handleSelectionChanged();
-	void handleTableHelp(unsigned id);
 	void handleTableHelpId(unsigned& id);
 
 	void handleCustomFont();

=== modified file 'win32/UserMatchDlg.cpp'
--- win32/UserMatchDlg.cpp	2011-12-18 17:04:43 +0000
+++ win32/UserMatchDlg.cpp	2012-01-12 16:14:38 +0000
@@ -113,7 +113,7 @@
 			[this] { handleOKClicked(); },
 			[this] { endDialog(IDCANCEL); });
 
-		WinUtil::addHelpButton(cur)->onClicked([this] { WinUtil::help(this, IDH_USER_MATCH); });
+		WinUtil::addHelpButton(cur)->onClicked([this] { WinUtil::help(this); });
 	}
 
 	if(initialMatcher) {

=== modified file 'win32/WinUtil.cpp'
--- win32/WinUtil.cpp	2012-01-11 21:42:27 +0000
+++ win32/WinUtil.cpp	2012-01-12 16:14:38 +0000
@@ -891,6 +891,13 @@
 	return false;
 }
 
+dwt::Control* helpPopup = nullptr; // only have 1 help popup at any given time.
+
+/** Display a help popup.
+Help popups display RTF text within a Rich Edit control. They visually look similar to a regular
+tooltip, but with a yellow background and thicker borders.
+@tparam tooltip Whether this help popup should act as an inactive tooltip that doesn't receive
+input but closes itself once its timer runs out. */
 template<bool tooltip>
 class HelpPopup : private RichTextBox {
 	typedef HelpPopup<tooltip> ThisType;
@@ -902,14 +909,14 @@
 	{
 		// where to position the popup.
 		dwt::Point pt;
-		if(isKeyPressed(VK_F1)) {
+		if(!tooltip && isAnyKeyPressed()) {
 			auto rect = parent->getWindowRect();
 			pt.x = rect.left() + rect.width() / 2;
-			pt.y = rect.top();
+			pt.y = rect.bottom() + margin;
 		} else {
 			pt = dwt::Point::fromLParam(::GetMessagePos());
 			if(tooltip) {
-				// don't cover the parent when showing as a tooltip.
+				// don't cover the parent window.
 				pt.y = parent->getWindowRect().bottom() + margin;
 			}
 		}
@@ -946,6 +953,14 @@
 			return 0;
 		}
 
+		// ok, ready to show the popup! make sure there's only 1 at any given time.
+		if(helpPopup) {
+			helpPopup->close();
+		}
+		helpPopup = this;
+		onDestroy([this] { if(this == helpPopup) helpPopup = nullptr; });
+
+		// adjust the size to account for borders and margins.
 		rect.pos = pos;
 		rect.size.x += ::GetSystemMetrics(SM_CXEDGE) * 2;
 		rect.size.y += ::GetSystemMetrics(SM_CYEDGE) * 2 + margin;
@@ -954,13 +969,15 @@
 		const auto screen = getDesktopSize();
 		if(rect.right() > screen.x) { rect.pos.x -= rect.right() - screen.x; }
 		if(rect.left() < 0) { rect.pos.x = 0; }
-		if(!tooltip && rect.bottom() > screen.y) { rect.pos.y -= rect.bottom() - screen.y; }
-		if(!tooltip && rect.top() < 0) { rect.pos.y = 0; }
+		if(rect.bottom() > screen.y) { rect.pos.y -= rect.bottom() - screen.y; }
+		if(rect.top() < 0) { rect.pos.y = 0; }
 
 		setColor(dwt::Color::predefined(COLOR_INFOTEXT), dwt::Color::predefined(COLOR_INFOBK));
 
 		if(tooltip) {
+			// this help popup acts as a tooltip; it will close by itself.
 			setTimer([this] { return !this->close(); }, timeout);
+			onMouseMove([this](const dwt::MouseEvent&) { return this->close(); });
 
 		} else {
 			// capture the mouse.
@@ -975,7 +992,7 @@
 				auto cb2 = focus->addCallback(dwt::Message(WM_HELP), [this](const MSG&, LRESULT&) { return this->close(); });
 				onDestroy([this, focus, cb1, cb2] {
 					auto cb1_ = cb1; focus->clearCallback(dwt::Message(WM_KEYDOWN), cb1_);
-					auto cb2_ = cb2; focus->clearCallback(dwt::Message(WM_KEYDOWN), cb2_);
+					auto cb2_ = cb2; focus->clearCallback(dwt::Message(WM_HELP), cb2_);
 				});
 			}
 		}
@@ -999,7 +1016,11 @@
 	unsigned timeout;
 };
 
-void WinUtil::help(dwt::Control* widget, unsigned id) {
+void WinUtil::help(dwt::Control* widget) {
+	helpId(widget, widget->getHelpId());
+}
+
+void WinUtil::helpId(dwt::Control* widget, unsigned id) {
 	if(id >= IDH_CSHELP_BEGIN && id <= IDH_CSHELP_END) {
 		// context-sensitive help
 		new HelpPopup<false>(widget, Text::toT(getHelpText(id)));

=== modified file 'win32/WinUtil.h'
--- win32/WinUtil.h	2012-01-11 21:42:27 +0000
+++ win32/WinUtil.h	2012-01-12 16:14:38 +0000
@@ -287,7 +287,8 @@
 	static bool parseDBLClick(const tstring& aString);
 	static void parseMagnetUri(const tstring& /*aUrl*/, bool aOverride = false);
 
-	static void help(dwt::Control* widget, unsigned id);
+	static void help(dwt::Control* widget);
+	static void helpId(dwt::Control* widget, unsigned id);
 	static void helpTooltip(dwt::Control* widget, unsigned timeout);
 	static string getHelpText(unsigned id);