This is the mail archive of the
cygwin-apps
mailing list for the Cygwin project.
[PATCH setup 07/13] Custom draw popup menus in ListView control
- From: Jon Turney <jon dot turney at dronecode dot org dot uk>
- To: cygwin-apps at cygwin dot com
- Cc: Jon Turney <jon dot turney at dronecode dot org dot uk>
- Date: Sun, 5 Aug 2018 23:08:45 +0100
- Subject: [PATCH setup 07/13] Custom draw popup menus in ListView control
- References: <20180805220851.270212-1-jon.turney@dronecode.org.uk>
Construct a menu containing the actions from the action list for the package
or category, and if one is selected, apply it.
This lets us remove packagemeta::set_action() which implements the strange
UX of cycling around actions without showing what the possibilities are.
This somewhat emulates a BS_SPLITBUTTON style control. The 'popup' cell has
a visual hint that clicking on it opens a menu (a 'combox scrollbar'), and
the popup menu is located at the position of the click.
v2:
Factor out popup_menu, for future use by keyboard accelerators
---
ListView.cc | 105 +++++++++++++++++++++++++++++++++++++++++---
ListView.h | 6 ++-
PickCategoryLine.cc | 20 ++++++++-
PickCategoryLine.h | 3 +-
PickPackageLine.cc | 16 +++++--
PickPackageLine.h | 3 +-
choose.cc | 2 +-
package_meta.cc | 64 ---------------------------
package_meta.h | 1 -
9 files changed, 139 insertions(+), 81 deletions(-)
diff --git a/ListView.cc b/ListView.cc
index e3f1e44..a555caa 100644
--- a/ListView.cc
+++ b/ListView.cc
@@ -274,18 +274,31 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
#endif
int iRow = pNmItemAct->iItem;
int iCol = pNmItemAct->iSubItem;
+ if (iRow < 0)
+ return false;
- if (iRow >= 0)
+ int update = 0;
+
+ if (headers[iCol].type == ListView::ControlType::popup)
+ {
+ POINT p;
+ // position pop-up menu at the location of the click
+ GetCursorPos(&p);
+
+ update = popup_menu(iRow, iCol, p);
+ }
+ else
{
// Inform the item of the click
- int update = (*contents)[iRow]->do_action(iCol);
+ update = (*contents)[iRow]->do_action(iCol, 0);
+ }
- // Update items, if needed
- if (update > 0)
- {
- ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
- }
+ // Update items, if needed
+ if (update > 0)
+ {
+ ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
}
+
return true;
}
break;
@@ -346,6 +359,41 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
result = CDRF_SKIPDEFAULT;
}
break;
+
+ case ListView::ControlType::popup:
+ {
+ // let the control draw the text, but notify us afterwards
+ result = CDRF_NOTIFYPOSTPAINT;
+ }
+ break;
+ }
+
+ *pResult = result;
+ return true;
+ }
+ case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT:
+ {
+ LRESULT result = CDRF_DODEFAULT;
+ int iCol = pNmLvCustomDraw->iSubItem;
+ int iRow = pNmLvCustomDraw->nmcd.dwItemSpec;
+
+ switch (headers[iCol].type)
+ {
+ default:
+ result = CDRF_DODEFAULT;
+ break;
+
+ case ListView::ControlType::popup:
+ {
+ // draw the control at the RHS of the cell
+ RECT r;
+ ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r);
+ r.left = r.right - GetSystemMetrics(SM_CXVSCROLL);
+ DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_SCROLL,DFCS_SCROLLCOMBOBOX);
+
+ result = CDRF_DODEFAULT;
+ }
+ break;
}
*pResult = result;
return true;
@@ -369,3 +417,46 @@ ListView::setEmptyText(const char *text)
{
empty_list_text = text;
}
+
+int
+ListView::popup_menu(int iRow, int iCol, POINT p)
+{
+ int update = 0;
+ // construct menu
+ HMENU hMenu = CreatePopupMenu();
+
+ MENUITEMINFO mii;
+ memset(&mii, 0, sizeof(mii));
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE | MIIM_STATE | MIIM_STRING | MIIM_ID;
+ mii.fType = MFT_STRING;
+
+ ActionList *al = (*contents)[iRow]->get_actions(iCol);
+
+ Actions::iterator i;
+ int j = 1;
+ for (i = al->list.begin (); i != al->list.end (); ++i, ++j)
+ {
+ BOOL res;
+ mii.dwTypeData = (char *)i->name.c_str();
+ mii.fState = (i->selected ? MFS_CHECKED : MFS_UNCHECKED |
+ i->enabled ? MFS_ENABLED : MFS_DISABLED);
+ mii.wID = j;
+
+ res = InsertMenuItem(hMenu, -1, TRUE, &mii);
+ if (!res) Log (LOG_BABBLE) << "InsertMenuItem failed " << endLog;
+ }
+
+ int id = TrackPopupMenu(hMenu,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_NOANIMATION,
+ p.x, p.y, 0, hWndListView, NULL);
+
+ // Inform the item of the menu choice
+ if (id)
+ update = (*contents)[iRow]->do_action(iCol, al->list[id-1].id);
+
+ DestroyMenu(hMenu);
+ delete al;
+
+ return update;
+}
diff --git a/ListView.h b/ListView.h
index 3aabc3f..b14777c 100644
--- a/ListView.h
+++ b/ListView.h
@@ -14,6 +14,7 @@
#ifndef SETUP_LISTVIEW_H
#define SETUP_LISTVIEW_H
+#include "ActionList.h"
#include "win32.h"
#include <vector>
@@ -28,7 +29,8 @@ class ListViewLine
public:
virtual ~ListViewLine() {};
virtual const std::string get_text(int col) const = 0;
- virtual int do_action(int col) = 0;
+ virtual ActionList *get_actions(int col) const = 0;
+ virtual int do_action(int col, int id) = 0;
};
typedef std::vector<ListViewLine *> ListViewContents;
@@ -40,6 +42,7 @@ class ListView
{
text,
checkbox,
+ popup,
};
class Header
@@ -75,6 +78,7 @@ class ListView
void initColumns(HeaderList hl);
void empty(void);
+ int popup_menu(int iRow, int iCol, POINT p);
};
#endif /* SETUP_LISTVIEW_H */
diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc
index 6737454..ec15b4a 100644
--- a/PickCategoryLine.cc
+++ b/PickCategoryLine.cc
@@ -17,6 +17,7 @@
#include "package_db.h"
#include "PickView.h"
#include "window.h"
+#include "package_meta.h"
const std::string
PickCategoryLine::get_text (int col_num) const
@@ -34,7 +35,7 @@ PickCategoryLine::get_text (int col_num) const
}
int
-PickCategoryLine::do_action(int col_num)
+PickCategoryLine::do_action(int col_num, int action_id)
{
if (col_num == pkgname_col)
{
@@ -44,9 +45,24 @@ PickCategoryLine::do_action(int col_num)
else if (col_num == new_col)
{
theView.GetParent ()->SetBusy ();
- int u = cat_tree->do_action((packagemeta::_actions)((cat_tree->action() + 1) % 4), theView.deftrust);
+ int u = cat_tree->do_action((packagemeta::_actions)(action_id), theView.deftrust);
theView.GetParent ()->ClearBusy ();
return u;
}
return 1;
}
+
+ActionList *
+PickCategoryLine::get_actions(int col) const
+{
+ ActionList *al = new ActionList();
+ packagemeta::_actions current_default = cat_tree->action();
+
+ al->add("Default", (int)packagemeta::Default_action, (current_default == packagemeta::Default_action), TRUE);
+ al->add("Install", (int)packagemeta::Install_action, (current_default == packagemeta::Install_action), TRUE);
+ al->add(packagedb::task == PackageDB_Install ? "Reinstall" : "Retrieve",
+ (int)packagemeta::Reinstall_action, (current_default == packagemeta::Reinstall_action), TRUE);
+ al->add("Uninstall", (int)packagemeta::Uninstall_action, (current_default == packagemeta::Uninstall_action), TRUE);
+
+ return al;
+}
diff --git a/PickCategoryLine.h b/PickCategoryLine.h
index 9423eb8..6c18018 100644
--- a/PickCategoryLine.h
+++ b/PickCategoryLine.h
@@ -33,7 +33,8 @@ public:
}
const std::string get_text(int col) const;
- int do_action(int col);
+ ActionList *get_actions(int col) const;
+ int do_action(int col, int action_id);
private:
CategoryTree * cat_tree;
diff --git a/PickPackageLine.cc b/PickPackageLine.cc
index b348ab0..67baad2 100644
--- a/PickPackageLine.cc
+++ b/PickPackageLine.cc
@@ -100,14 +100,13 @@ PickPackageLine::get_text(int col_num) const
}
int
-PickPackageLine::do_action(int col_num)
+PickPackageLine::do_action(int col_num, int action_id)
{
if (col_num == new_col)
{
- pkg.set_action (theView.deftrust);
+ pkg.select_action(action_id, theView.deftrust);
return 1;
}
-
if (col_num == bintick_col)
{
if (pkg.desired.accessible ())
@@ -133,3 +132,14 @@ PickPackageLine::do_action(int col_num)
return 0;
}
+
+ActionList *
+PickPackageLine::get_actions(int col_num) const
+{
+ if (col_num == new_col)
+ {
+ return pkg.list_actions (theView.deftrust);
+ }
+
+ return NULL;
+}
diff --git a/PickPackageLine.h b/PickPackageLine.h
index a8c3c0d..7d96d44 100644
--- a/PickPackageLine.h
+++ b/PickPackageLine.h
@@ -30,7 +30,8 @@ public:
{
};
const std::string get_text(int col) const;
- int do_action(int col);
+ ActionList *get_actions(int col_num) const;
+ int do_action(int col, int action_id);
private:
packagemeta & pkg;
PickView & theView;
diff --git a/choose.cc b/choose.cc
index c65a107..d40e824 100644
--- a/choose.cc
+++ b/choose.cc
@@ -134,7 +134,7 @@ ChooserPage::~ChooserPage ()
static ListView::Header pkg_headers[] = {
{"Package", LVCFMT_LEFT, ListView::ControlType::text},
{"Current", LVCFMT_LEFT, ListView::ControlType::text},
- {"New", LVCFMT_LEFT, ListView::ControlType::text},
+ {"New", LVCFMT_LEFT, ListView::ControlType::popup},
{"Bin?", LVCFMT_LEFT, ListView::ControlType::checkbox},
{"Src?", LVCFMT_LEFT, ListView::ControlType::checkbox},
{"Categories", LVCFMT_LEFT, ListView::ControlType::text},
diff --git a/package_meta.cc b/package_meta.cc
index f55c3f3..4f7d39a 100644
--- a/package_meta.cc
+++ b/package_meta.cc
@@ -423,70 +423,6 @@ packagemeta::action_caption () const
return desired.Canonical_version ();
}
-/* Set the next action given a current action. */
-void
-packagemeta::set_action (trusts const trust)
-{
- set<packageversion>::iterator i;
-
- /* Keep the picked settings of the former desired version, if any, and make
- sure at least one of them is picked. If both are unpicked, pick the
- binary version. */
- bool source_picked = desired && srcpicked ();
- bool binary_picked = !desired || picked () || !source_picked;
-
- /* If we're on "Keep" on the installed version, and the version is available,
- switch to "Reinstall". */
- if (desired && desired == installed && !picked ()
- && desired.accessible ())
- {
- pick (true);
- return;
- }
-
- if (!desired)
- {
- /* From "Uninstall" switch to the first version. From "Skip" switch to
- the first version as well, unless the user picks for the first time.
- In that case switch to the trustp version immediately. */
- if (installed || user_picked)
- i = versions.begin ();
- else
- for (i = versions.begin ();
- i != versions.end () && *i != trustp (false, trust);
- ++i)
- ;
- }
- else
- {
- /* Otherwise switch to the next version. */
- for (i = versions.begin (); i != versions.end () && *i != desired; ++i)
- ;
- ++i;
- }
- /* If there's another version in the list, switch to it, otherwise
- switch to "Uninstall". */
- if (i != versions.end ())
- {
- desired = *i;
- /* If the next version is the installed version, unpick it. This will
- have the desired effect to show the package in "Keep" mode. See also
- above for the code switching to "Reinstall". */
- pick (desired != installed && binary_picked);
- srcpick (desired.sourcePackage().accessible () && source_picked);
- }
- else
- {
- desired = packageversion ();
- pick(false);
- srcpick(false);
- }
-
- /* Memorize the fact that the user picked at least once. */
- if (!installed)
- user_picked = true;
-}
-
void
packagemeta::select_action (int id, trusts const deftrust)
{
diff --git a/package_meta.h b/package_meta.h
index 0f01837..8a42319 100644
--- a/package_meta.h
+++ b/package_meta.h
@@ -60,7 +60,6 @@ public:
};
static const char *action_caption (_actions value);
- void set_action (trusts const t);
void set_action (_actions, packageversion const & default_version);
ActionList *list_actions(trusts const trust);
void select_action (int id, trusts const deftrust);
--
2.17.0