* [PATCH setup 00/13] ListView Package Chooser @ 2018-08-05 22:09 Jon Turney 2018-08-05 22:09 ` [PATCH setup 01/13] Change packagemeta::_actions to an enum Jon Turney ` (14 more replies) 0 siblings, 15 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:09 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney Drag setup into the 1990s, by replacing the custom-drawn package chooser with a ListView common control. As well as removing a lot of Win32 GDI drawing, this also enables the following improvements to be much more straightforward: * Use standard UI elements to choose an action to take on a package or category, rather than the weird UX of clicking to cycle around a list of options of undisclosed length. * Add tooltips (initially, the ldesc as a tooltip for sdesc) * Make the package chooser keyboard accessible (not done yet) The branch for this patch series can be found at: https://github.com/jon-turney/cygwin-setup/tree/listview Jon Turney (13): Change packagemeta::_actions to an enum Add OnNotify virtual function to class Window for WM_NOTIFY notifications Drop 'using namespace std;' from PickView.cc Use a ListView common control rather than a hand-built grid Custom draw checkboxes in ListView control Add methods for listing possible actions on, and applying one to, a package Custom draw popup menus in ListView control Show the count of packages in a category Use an icon to represent expanded/collapsed state Use indents in category view Add LDesc() accessor method to SolvableVersion Restore packagemeta::LDesc() Add ldesc tooltips to sdesc column of listview ActionList.h | 55 +++ ListView.cc | 585 +++++++++++++++++++++++++++++ ListView.h | 96 +++++ Makefile.am | 12 +- PickCategoryLine.cc | 165 +++----- PickCategoryLine.h | 81 ++-- PickLine.h | 47 --- PickPackageLine.cc | 150 ++++---- PickPackageLine.h | 31 +- PickView.cc | 898 ++++++++------------------------------------ PickView.h | 217 ++++++----- check-na.bmp | Bin 106 -> 0 bytes check-no.bmp | Bin 106 -> 0 bytes check-yes.bmp | Bin 106 -> 0 bytes choose-spin.bmp | Bin 106 -> 0 bytes choose.cc | 71 ++-- choose.h | 6 +- libsolv.cc | 14 + libsolv.h | 1 + main.cc | 2 +- package_meta.cc | 155 ++++---- package_meta.h | 38 +- proppage.cc | 14 +- res.rc | 19 +- resource.h | 13 +- tree-minus.bmp | Bin 106 -> 0 bytes tree-minus.ico | Bin 0 -> 299654 bytes tree-minus.svg | 118 ++++++ tree-plus.bmp | Bin 106 -> 0 bytes tree-plus.ico | Bin 0 -> 299671 bytes tree-plus.svg | 126 +++++++ window.h | 7 + 32 files changed, 1608 insertions(+), 1313 deletions(-) create mode 100644 ActionList.h create mode 100644 ListView.cc create mode 100644 ListView.h delete mode 100644 PickLine.h delete mode 100644 check-na.bmp delete mode 100644 check-no.bmp delete mode 100644 check-yes.bmp delete mode 100644 choose-spin.bmp delete mode 100644 tree-minus.bmp create mode 100644 tree-minus.ico create mode 100755 tree-minus.svg delete mode 100644 tree-plus.bmp create mode 100644 tree-plus.ico create mode 100644 tree-plus.svg -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 01/13] Change packagemeta::_actions to an enum 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney @ 2018-08-05 22:09 ` Jon Turney 2018-08-05 22:09 ` [PATCH setup 03/13] Drop 'using namespace std;' from PickView.cc Jon Turney ` (13 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:09 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney --- PickCategoryLine.cc | 6 +++--- package_meta.cc | 35 +++++++---------------------------- package_meta.h | 29 +++++++++-------------------- 3 files changed, 19 insertions(+), 51 deletions(-) diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc index 24fecb3..e428419 100644 --- a/PickCategoryLine.cc +++ b/PickCategoryLine.cc @@ -55,7 +55,8 @@ PickCategoryLine::paint (HDC hdc, HRGN hUpdRgn, int x, int y, int row, int show_ // draw the caption ('Default', 'Install', etc) TextOut (hdc, spin_x + SPIN_WIDTH + ICON_MARGIN, r, - current_default.caption (), strlen (current_default.caption ())); + packagemeta::action_caption (current_default), + strlen (packagemeta::action_caption (current_default))); row++; } if (collapsed) @@ -110,8 +111,7 @@ PickCategoryLine::click (int const myrow, int const ClickedRow, int const x) { if ((size_t) x >= spin_x) { - ++current_default; - + current_default = (packagemeta::_actions)((current_default + 1) % 4); return set_action (current_default); } else diff --git a/package_meta.cc b/package_meta.cc index f765baf..dffe3ac 100644 --- a/package_meta.cc +++ b/package_meta.cc @@ -54,35 +54,22 @@ bool hasManualSelections = 0; /*****************/ -const - packagemeta::_actions -packagemeta::Default_action (0); -const - packagemeta::_actions -packagemeta::Install_action (1); -const - packagemeta::_actions -packagemeta::Reinstall_action (2); -const - packagemeta::_actions -packagemeta::Uninstall_action (3); - char const * -packagemeta::_actions::caption () +packagemeta::action_caption (_actions _value) { switch (_value) { - case 0: + case Default_action: return "Default"; - case 1: + case Install_action: return "Install"; - case 2: + case Reinstall_action: return "Reinstall"; - case 3: + case Uninstall_action: return "Uninstall"; } - // Pacify GCC: (all case options are checked above) - return 0; + + return "Unknown"; } packagemeta::packagemeta (packagemeta const &rhs) : @@ -96,14 +83,6 @@ packagemeta::packagemeta (packagemeta const &rhs) : } -packagemeta::_actions & packagemeta::_actions::operator++ () -{ - ++_value; - if (_value > 3) - _value = 0; - return *this; -} - template<class T> struct removeCategory : public unary_function<T, void> { removeCategory(packagemeta *pkg) : _pkg (pkg) {} diff --git a/package_meta.h b/package_meta.h index 8db10e2..df66bda 100644 --- a/package_meta.h +++ b/package_meta.h @@ -50,26 +50,15 @@ public: void setDefaultCategories(); void addToCategoryAll(); - class _actions - { - public: - _actions ():_value (0) {}; - _actions (int aInt) { - _value = aInt; - if (_value < 0 || _value > 3) - _value = 0; - } - _actions & operator ++ (); - bool operator == (_actions const &rhs) { return _value == rhs._value; } - bool operator != (_actions const &rhs) { return _value != rhs._value; } - const char *caption (); - private: - int _value; - }; - static const _actions Default_action; - static const _actions Install_action; - static const _actions Reinstall_action; - static const _actions Uninstall_action; + enum _actions + { + Default_action, + Install_action, + Reinstall_action, + Uninstall_action, + }; + static const char *action_caption (_actions value); + void set_action (trusts const t); void set_action (_actions, packageversion const & default_version); -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 03/13] Drop 'using namespace std;' from PickView.cc 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney 2018-08-05 22:09 ` [PATCH setup 01/13] Change packagemeta::_actions to an enum Jon Turney @ 2018-08-05 22:09 ` Jon Turney 2018-08-05 22:09 ` [PATCH setup 02/13] Add OnNotify virtual function to class Window for WM_NOTIFY notifications Jon Turney ` (12 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:09 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney --- PickView.cc | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/PickView.cc b/PickView.cc index c583eea..57e8f85 100644 --- a/PickView.cc +++ b/PickView.cc @@ -28,8 +28,6 @@ #include "LogSingleton.h" #include "Exception.h" -using namespace std; - static PickView::Header pkg_headers[] = { {"Current", 0, 0, true}, {"New", 0, 0, true}, @@ -248,10 +246,10 @@ PickView::mode_caption (views mode) /* meant to be called on packagemeta::categories */ bool -isObsolete (set <std::string, casecompare_lt_op> &categories) +isObsolete (std::set <std::string, casecompare_lt_op> &categories) { - set <std::string, casecompare_lt_op>::const_iterator i; - + std::set <std::string, casecompare_lt_op>::const_iterator i; + for (i = categories.begin (); i != categories.end (); ++i) if (isObsolete (*i)) return true; @@ -295,7 +293,7 @@ PickView::insert_category (Category *cat, bool collapsed) return; PickCategoryLine & catline = *new PickCategoryLine (*this, *cat, 1, collapsed); int packageCount = 0; - for (vector <packagemeta *>::iterator i = cat->second.begin (); + for (std::vector <packagemeta *>::iterator i = cat->second.begin (); i != cat->second.end () ; ++i) { if (packageFilterString.empty () \ @@ -439,7 +437,7 @@ PickView::init_headers (HDC dc) if (pkg.installed) note_width (headers, dc, pkg.installed.Canonical_version (), HMARGIN, current_col); - for (set<packageversion>::iterator i = pkg.versions.begin (); + for (std::set<packageversion>::iterator i = pkg.versions.begin (); i != pkg.versions.end (); ++i) { if (*i != pkg.installed) -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 02/13] Add OnNotify virtual function to class Window for WM_NOTIFY notifications 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney 2018-08-05 22:09 ` [PATCH setup 01/13] Change packagemeta::_actions to an enum Jon Turney 2018-08-05 22:09 ` [PATCH setup 03/13] Drop 'using namespace std;' from PickView.cc Jon Turney @ 2018-08-05 22:09 ` Jon Turney 2018-08-05 22:10 ` [PATCH setup 08/13] Show the count of packages in a category Jon Turney ` (11 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:09 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney Add OnNotify virtual function to class Window for WM_NOTIFY notifications Note that the result is returned via DWLP_MSGRESULT --- proppage.cc | 14 +++++++++++++- window.h | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/proppage.cc b/proppage.cc index 6b83640..8da1c52 100644 --- a/proppage.cc +++ b/proppage.cc @@ -140,7 +140,18 @@ PropertyPage::DialogProc (UINT message, WPARAM wParam, LPARAM lParam) return TRUE; } case WM_NOTIFY: - switch (((NMHDR FAR *) lParam)->code) + { + NMHDR *pNmHdr = (NMHDR *) lParam; + + // offer to subclass first + LRESULT result = 0; + if (OnNotify (pNmHdr, &result)) + { + SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, result); + return TRUE; + } + + switch (pNmHdr->code) { case PSN_APPLY: { @@ -261,6 +272,7 @@ PropertyPage::DialogProc (UINT message, WPARAM wParam, LPARAM lParam) return FALSE; } } + } break; case WM_COMMAND: { diff --git a/window.h b/window.h index ca6baa6..d8b712b 100644 --- a/window.h +++ b/window.h @@ -138,6 +138,13 @@ public: return false; }; + virtual bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult) + { + // Not processed by default. Override in derived classes to + // do something with command messages if you need to. + return false; + }; + RECT GetWindowRect() const; RECT GetClientRect() const; -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 08/13] Show the count of packages in a category 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (2 preceding siblings ...) 2018-08-05 22:09 ` [PATCH setup 02/13] Add OnNotify virtual function to class Window for WM_NOTIFY notifications Jon Turney @ 2018-08-05 22:10 ` Jon Turney 2018-08-05 22:10 ` [PATCH setup 09/13] Use an icon to represent expanded/collapsed state Jon Turney ` (10 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney --- PickCategoryLine.cc | 8 ++++++-- PickCategoryLine.h | 4 +++- PickView.cc | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc index ec15b4a..736e3c6 100644 --- a/PickCategoryLine.cc +++ b/PickCategoryLine.cc @@ -18,14 +18,18 @@ #include "PickView.h" #include "window.h" #include "package_meta.h" +#include <sstream> const std::string PickCategoryLine::get_text (int col_num) const { if (col_num == pkgname_col) { - std::string s = (cat_tree->collapsed() ? "[+] " : "[-] ") + cat_tree->category().first; - return s; + std::ostringstream s; + s << (cat_tree->collapsed() ? "[+] " : "[-] ") << cat_tree->category().first; + if (pkgcount) + s << " (" << pkgcount << ")"; + return s.str(); } else if (col_num == new_col) { diff --git a/PickCategoryLine.h b/PickCategoryLine.h index 6c18018..ec160f9 100644 --- a/PickCategoryLine.h +++ b/PickCategoryLine.h @@ -23,8 +23,9 @@ class PickCategoryLine: public ListViewLine { public: - PickCategoryLine (PickView & aView, CategoryTree * _tree) : + PickCategoryLine (PickView & aView, CategoryTree * _tree, int _pkgcount) : cat_tree (_tree), + pkgcount(_pkgcount), theView (aView) { }; @@ -38,6 +39,7 @@ public: private: CategoryTree * cat_tree; + int pkgcount; PickView & theView; }; diff --git a/PickView.cc b/PickView.cc index 967d53b..6e1af0c 100644 --- a/PickView.cc +++ b/PickView.cc @@ -169,12 +169,12 @@ PickView::insert_category (CategoryTree *cat_tree) return; // if it's not the "All" category + int packageCount = 0; bool hasContents = false; bool isAll = casecompare(cat->first, "All") == 0; if (!isAll) { // count the number of packages in this category - int packageCount = 0; for (std::vector <packagemeta *>::const_iterator i = cat->second.begin (); i != cat->second.end () ; ++i) { @@ -197,7 +197,7 @@ PickView::insert_category (CategoryTree *cat_tree) return; // insert line for the category - contents.push_back(new PickCategoryLine(*this, cat_tree)); + contents.push_back(new PickCategoryLine(*this, cat_tree, packageCount)); // if not collapsed if (!cat_tree->collapsed()) -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 09/13] Use an icon to represent expanded/collapsed state 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (3 preceding siblings ...) 2018-08-05 22:10 ` [PATCH setup 08/13] Show the count of packages in a category Jon Turney @ 2018-08-05 22:10 ` Jon Turney 2018-08-05 22:10 ` [PATCH setup 07/13] Custom draw popup menus in ListView control Jon Turney ` (9 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney Use a listview icon to represent expanded/collapsed state, rather than puttting '[+]'/'[-]' in front of the category name Just use the item icon directly to represent expanded/collapsed state, rather than using a state icon as otherwise we have empty space where the item icon would be, when it's size is being used for indenting. The icons were made by tracing the previously used .bmp in Inkscape to produce a .svg, then converting that to an icon with ImageMagick using 'convert -filter point -background transparent -define icon:auto-resize' --- ListView.cc | 18 ++++++- ListView.h | 3 ++ Makefile.am | 4 +- PickCategoryLine.cc | 8 ++- PickCategoryLine.h | 1 + PickPackageLine.h | 1 + res.rc | 2 + resource.h | 2 + tree-minus.ico | Bin 0 -> 299654 bytes tree-minus.svg | 118 +++++++++++++++++++++++++++++++++++++++++ tree-plus.ico | Bin 0 -> 299671 bytes tree-plus.svg | 126 ++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 tree-minus.ico create mode 100755 tree-minus.svg create mode 100644 tree-plus.ico create mode 100644 tree-plus.svg diff --git a/ListView.cc b/ListView.cc index a555caa..0c451d1 100644 --- a/ListView.cc +++ b/ListView.cc @@ -13,6 +13,7 @@ #include "ListView.h" #include "LogSingleton.h" +#include "resource.h" #include <commctrl.h> @@ -54,6 +55,15 @@ ListView::init(HWND parent, int id, HeaderList headers) // populate with columns initColumns(headers); + + // create a small icon imagelist and assign to listview control + // (the order of images matches ListViewLine::State enum) + HIMAGELIST hImgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + ILC_COLOR32, 2, 0); + ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_PLUS))); + ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_MINUS))); + ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL); } void @@ -179,10 +189,11 @@ ListView::setContents(ListViewContents *_contents) for (i = 0; i < contents->size(); i++) { LVITEM lvi; - lvi.mask = LVIF_TEXT; + lvi.mask = LVIF_TEXT | LVIF_IMAGE; lvi.iItem = i; lvi.iSubItem = 0; lvi.pszText = LPSTR_TEXTCALLBACK; + lvi.iImage = I_IMAGECALLBACK; ListView_InsertItem(hWndListView, &lvi); } @@ -247,6 +258,11 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult) static StringCache s; s = (*contents)[iRow]->get_text(iCol); pNmLvDispInfo->item.pszText = s; + + if (pNmLvDispInfo->item.iSubItem == 0) + { + pNmLvDispInfo->item.iImage = (int)((*contents)[pNmLvDispInfo->item.iItem]->get_state()); + } } return true; diff --git a/ListView.h b/ListView.h index b14777c..f5aa1d9 100644 --- a/ListView.h +++ b/ListView.h @@ -27,8 +27,11 @@ class ListViewLine { public: + enum class State { collapsed, expanded, nothing=-1 }; + virtual ~ListViewLine() {}; virtual const std::string get_text(int col) const = 0; + virtual State get_state() const = 0; virtual ActionList *get_actions(int col) const = 0; virtual int do_action(int col, int id) = 0; }; diff --git a/Makefile.am b/Makefile.am index b58c9b7..7c1f993 100644 --- a/Makefile.am +++ b/Makefile.am @@ -46,7 +46,9 @@ EXTRA_DIST = \ cygwin-setup.ico \ cygwin-terminal.ico \ setup.exe.manifest \ - setup64.exe.manifest + setup64.exe.manifest \ + tree-minus.ico \ + tree-plus.ico # iniparse.hh is generated from iniparse.yy via bison -d, so it needs to be # included here for proper tracking (but not iniparse.cc, since automake diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc index 736e3c6..21795b2 100644 --- a/PickCategoryLine.cc +++ b/PickCategoryLine.cc @@ -26,7 +26,7 @@ PickCategoryLine::get_text (int col_num) const if (col_num == pkgname_col) { std::ostringstream s; - s << (cat_tree->collapsed() ? "[+] " : "[-] ") << cat_tree->category().first; + s << cat_tree->category().first; if (pkgcount) s << " (" << pkgcount << ")"; return s.str(); @@ -70,3 +70,9 @@ PickCategoryLine::get_actions(int col) const return al; } + +ListViewLine::State +PickCategoryLine::get_state() const +{ + return cat_tree->collapsed() ? State::collapsed : State::expanded; +} diff --git a/PickCategoryLine.h b/PickCategoryLine.h index ec160f9..6b54c3f 100644 --- a/PickCategoryLine.h +++ b/PickCategoryLine.h @@ -34,6 +34,7 @@ public: } const std::string get_text(int col) const; + State get_state() const; ActionList *get_actions(int col) const; int do_action(int col, int action_id); diff --git a/PickPackageLine.h b/PickPackageLine.h index 7d96d44..dacad28 100644 --- a/PickPackageLine.h +++ b/PickPackageLine.h @@ -30,6 +30,7 @@ public: { }; const std::string get_text(int col) const; + State get_state() const { return State::nothing; } ActionList *get_actions(int col_num) const; int do_action(int col, int action_id); private: diff --git a/res.rc b/res.rc index 27f0378..10f20ba 100644 --- a/res.rc +++ b/res.rc @@ -519,6 +519,8 @@ CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "setup.exe.manifest" IDI_CYGWIN_SETUP ICON DISCARDABLE "cygwin-setup.ico" IDI_CYGWIN ICON DISCARDABLE "cygwin.ico" IDI_CYGWIN_TERMINAL ICON DISCARDABLE "cygwin-terminal.ico" +IDI_TREE_PLUS ICON DISCARDABLE "tree-plus.ico" +IDI_TREE_MINUS ICON DISCARDABLE "tree-minus.ico" ///////////////////////////////////////////////////////////////////////////// // diff --git a/resource.h b/resource.h index 2f1036b..852bdc0 100644 --- a/resource.h +++ b/resource.h @@ -76,6 +76,8 @@ #define IDI_CYGWIN_SETUP 401 #define IDI_CYGWIN 402 #define IDI_CYGWIN_TERMINAL 403 +#define IDI_TREE_PLUS 404 +#define IDI_TREE_MINUS 405 // controls diff --git a/tree-minus.ico b/tree-minus.ico new file mode 100644 index 0000000000000000000000000000000000000000..46fd3b117b0fcaffc9c12331e4ccdd1fed9f61e1 GIT binary patch literal 299654 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 IZR_j*0p@PTA^-pY literal 0 HcmV?d00001 diff --git a/tree-minus.svg b/tree-minus.svg new file mode 100755 index 0000000..124918b --- /dev/null +++ b/tree-minus.svg @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="256px" + height="256px" + viewBox="0 0 256 256" + version="1.1" + id="SVGRoot" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="tree-minus.svg"> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="2.3754368" + inkscape:cx="135.00514" + inkscape:cy="129.49343" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:window-width="1680" + inkscape:window-height="1027" + inkscape:window-x="1912" + inkscape:window-y="22" + inkscape:window-maximized="1" + inkscape:grid-bbox="true" + showguides="false" + inkscape:object-nodes="true"> + <inkscape:grid + type="xygrid" + id="grid4075" /> + </sodipodi:namedview> + <defs + id="defs3756" /> + <metadata + id="metadata3759"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:groupmode="layer" + inkscape:label="layer"> + <g + id="box" + inkscape:label="box" + style="fill:#000000;fill-opacity:1" + transform="translate(8,8)"> + <rect + y="56" + x="56" + height="16" + width="144" + id="top" + style="fill:#000000;fill-opacity:1" + inkscape:label="top" /> + <rect + y="184" + x="56" + height="16" + width="144" + id="bottom" + style="fill:#000000;fill-opacity:1" + inkscape:label="bottom" /> + <rect + transform="rotate(90)" + y="-72" + x="56" + height="16" + width="144" + id="left" + style="fill:#000000;fill-opacity:1" + inkscape:label="left" /> + <rect + transform="rotate(90)" + y="-200" + x="56" + height="16" + width="144" + id="right" + style="fill:#000000;fill-opacity:1" + inkscape:label="right" /> + </g> + <g + id="symbol" + inkscape:label="symbol" + style="fill:#000000;fill-opacity:1" + transform="translate(8,8)"> + <rect + transform="rotate(90)" + y="-168" + x="120" + height="80" + width="16" + id="horiz" + style="fill:#000000;fill-opacity:1" + inkscape:label="horiz" /> + </g> + </g> +</svg> diff --git a/tree-plus.ico b/tree-plus.ico new file mode 100644 index 0000000000000000000000000000000000000000..8ee3d5fed295f1b79639845c67a86c5145cfa5c8 GIT binary patch literal 299671 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 literal 0 HcmV?d00001 diff --git a/tree-plus.svg b/tree-plus.svg new file mode 100644 index 0000000..4d2eb3f --- /dev/null +++ b/tree-plus.svg @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="256px" + height="256px" + viewBox="0 0 256 256" + version="1.1" + id="SVGRoot" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="tree-plus.svg"> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="3.359375" + inkscape:cx="84.390698" + inkscape:cy="128" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:window-width="1680" + inkscape:window-height="1027" + inkscape:window-x="1912" + inkscape:window-y="22" + inkscape:window-maximized="1" + inkscape:grid-bbox="true" + showguides="false" + inkscape:object-nodes="true"> + <inkscape:grid + type="xygrid" + id="grid4075" /> + </sodipodi:namedview> + <defs + id="defs3756" /> + <metadata + id="metadata3759"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:groupmode="layer" + inkscape:label="layer"> + <g + id="box" + inkscape:label="box" + style="fill:#000000;fill-opacity:1" + transform="translate(8,8)"> + <rect + y="56" + x="56" + height="16" + width="144" + id="top" + style="fill:#000000;fill-opacity:1" + inkscape:label="top" /> + <rect + y="184" + x="56" + height="16" + width="144" + id="bottom" + style="fill:#000000;fill-opacity:1" + inkscape:label="bottom" /> + <rect + transform="rotate(90)" + y="-72" + x="56" + height="16" + width="144" + id="left" + style="fill:#000000;fill-opacity:1" + inkscape:label="left" /> + <rect + transform="rotate(90)" + y="-200" + x="56" + height="16" + width="144" + id="right" + style="fill:#000000;fill-opacity:1" + inkscape:label="right" /> + </g> + <g + id="symbol" + inkscape:label="symbol" + style="fill:#000000;fill-opacity:1" + transform="translate(8,8)"> + <rect + y="88" + x="120" + height="80" + width="16" + id="vert" + style="fill:#000000;fill-opacity:1" + inkscape:label="vert" /> + <rect + transform="rotate(90)" + y="-168" + x="120" + height="80" + width="16" + id="horiz" + style="fill:#000000;fill-opacity:1" + inkscape:label="horiz" /> + </g> + </g> +</svg> -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 07/13] Custom draw popup menus in ListView control 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (4 preceding siblings ...) 2018-08-05 22:10 ` [PATCH setup 09/13] Use an icon to represent expanded/collapsed state Jon Turney @ 2018-08-05 22:10 ` Jon Turney 2018-08-05 22:10 ` [PATCH setup 05/13] Custom draw checkboxes " Jon Turney ` (8 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney 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 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 05/13] Custom draw checkboxes in ListView control 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (5 preceding siblings ...) 2018-08-05 22:10 ` [PATCH setup 07/13] Custom draw popup menus in ListView control Jon Turney @ 2018-08-05 22:10 ` Jon Turney 2018-08-05 22:10 ` [PATCH setup 04/13] Use a ListView common control rather than a hand-built grid Jon Turney ` (7 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney Future work: What does this look like when a theme is in used? Does the row size need to be slightly adjusted to ensure it accomodates checkbox height? v2: erase to background colour before drawing checkbox --- ListView.cc | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ListView.h | 7 ++++++ choose.cc | 16 +++++++------- 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/ListView.cc b/ListView.cc index 97ee44c..e3f1e44 100644 --- a/ListView.cc +++ b/ListView.cc @@ -289,6 +289,69 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult) return true; } break; + + case NM_CUSTOMDRAW: + { + NMLVCUSTOMDRAW *pNmLvCustomDraw = (NMLVCUSTOMDRAW *)pNmHdr; + + switch(pNmLvCustomDraw->nmcd.dwDrawStage) + { + case CDDS_PREPAINT: + *pResult = CDRF_NOTIFYITEMDRAW; + return true; + case CDDS_ITEMPREPAINT: + *pResult = CDRF_NOTIFYSUBITEMDRAW; + return true; + case CDDS_SUBITEM | CDDS_ITEMPREPAINT: + { + LRESULT result = CDRF_DODEFAULT; + int iCol = pNmLvCustomDraw->iSubItem; + int iRow = pNmLvCustomDraw->nmcd.dwItemSpec; + + switch (headers[iCol].type) + { + default: + case ListView::ControlType::text: + result = CDRF_DODEFAULT; + break; + + case ListView::ControlType::checkbox: + { + // get the subitem text + char buf[3]; + ListView_GetItemText(hWndListView, iRow, iCol, buf, _countof(buf)); + + // map the subitem text to a checkbox state + UINT state = DFCS_BUTTONCHECK | DFCS_FLAT; + if (buf[0] == '\0') // empty + { + result = CDRF_DODEFAULT; + break; + } + else if (buf[0] == 'y') // yes + state |= DFCS_CHECKED; + else if ((buf[0] == 'n') && (buf[1] == 'o')) // no + state |= 0; + else // n/a + state |= DFCS_INACTIVE; + + // erase and draw a checkbox + RECT r; + ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r); + HBRUSH hBrush = CreateSolidBrush(ListView_GetBkColor(hWndListView)); + FillRect(pNmLvCustomDraw->nmcd.hdc, &r, hBrush); + DeleteObject(hBrush); + DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_BUTTON, state); + + result = CDRF_SKIPDEFAULT; + } + break; + } + *pResult = result; + return true; + } + } + } } // We don't care. diff --git a/ListView.h b/ListView.h index d339011..3aabc3f 100644 --- a/ListView.h +++ b/ListView.h @@ -36,11 +36,18 @@ typedef std::vector<ListViewLine *> ListViewContents; class ListView { public: + enum class ControlType + { + text, + checkbox, + }; + class Header { public: const char *text; int fmt; + ControlType type; int width; int hdr_width; }; diff --git a/choose.cc b/choose.cc index c86294a..c65a107 100644 --- a/choose.cc +++ b/choose.cc @@ -132,14 +132,14 @@ ChooserPage::~ChooserPage () } static ListView::Header pkg_headers[] = { - {"Package", LVCFMT_LEFT}, - {"Current", LVCFMT_LEFT}, - {"New", LVCFMT_LEFT}, - {"Bin?", LVCFMT_LEFT}, - {"Src?", LVCFMT_LEFT}, - {"Categories", LVCFMT_LEFT}, - {"Size", LVCFMT_RIGHT}, - {"Description", LVCFMT_LEFT}, + {"Package", LVCFMT_LEFT, ListView::ControlType::text}, + {"Current", LVCFMT_LEFT, ListView::ControlType::text}, + {"New", LVCFMT_LEFT, ListView::ControlType::text}, + {"Bin?", LVCFMT_LEFT, ListView::ControlType::checkbox}, + {"Src?", LVCFMT_LEFT, ListView::ControlType::checkbox}, + {"Categories", LVCFMT_LEFT, ListView::ControlType::text}, + {"Size", LVCFMT_RIGHT, ListView::ControlType::text}, + {"Description", LVCFMT_LEFT, ListView::ControlType::text}, {0} }; -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 04/13] Use a ListView common control rather than a hand-built grid 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (6 preceding siblings ...) 2018-08-05 22:10 ` [PATCH setup 05/13] Custom draw checkboxes " Jon Turney @ 2018-08-05 22:10 ` Jon Turney 2018-08-05 22:10 ` [PATCH setup 06/13] Add methods for listing possible actions on, and applying one to, a package Jon Turney ` (6 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney Each line is implemented by an object of a subclass of ListViewLine, either PickPackageLine, or (in catgeory view) PickCategoryLine v2: Defer constructing category tree until packagedb is available Also handle an empty category tree Remove unused bitmaps Scoping of strings returned to LVN_GETDISPINFO Avoid doubled 'all' category Update some number of lines, rather than just the current one v3: Speed up calculating column widths, by caching the DC used Don't bother recalculating column widths when obsolete packages are shown/hidden, just allow for obsolete packages always (which makes hardly any difference) Don't bother calculating column widths separately for category view since they are now always the same. v4: Store current category action in CategoryTree Recurse category action onto packages This slightly changes the behaviour: previously, a category action only effected packages which matched the name search filter. Now all packages in contained by the category are effected. v5: When updating, turn off redraw before emptying listview Make headers a parameter to init Don't leak contents Only resize columns once Remove ListView OnMessage as it has nothing to do Never use a column width less than minimum Use LVS_SINGLESEL Factor out string cache for re-use Preserve focused row over ListvIew::setContents() --- ListView.cc | 308 +++++++++++++++ ListView.h | 73 ++++ Makefile.am | 11 +- PickCategoryLine.cc | 135 +------ PickCategoryLine.h | 73 +--- PickLine.h | 47 --- PickPackageLine.cc | 127 +++---- PickPackageLine.h | 25 +- PickView.cc | 888 ++++++++------------------------------------ PickView.h | 217 ++++++----- check-na.bmp | Bin 106 -> 0 bytes check-no.bmp | Bin 106 -> 0 bytes check-yes.bmp | Bin 106 -> 0 bytes choose-spin.bmp | Bin 106 -> 0 bytes choose.cc | 71 ++-- choose.h | 6 +- main.cc | 2 +- res.rc | 17 +- resource.h | 11 - tree-minus.bmp | Bin 106 -> 0 bytes tree-plus.bmp | Bin 106 -> 0 bytes 21 files changed, 799 insertions(+), 1212 deletions(-) create mode 100644 ListView.cc create mode 100644 ListView.h delete mode 100644 PickLine.h delete mode 100644 check-na.bmp delete mode 100644 check-no.bmp delete mode 100644 check-yes.bmp delete mode 100644 choose-spin.bmp delete mode 100644 tree-minus.bmp delete mode 100644 tree-plus.bmp diff --git a/ListView.cc b/ListView.cc new file mode 100644 index 0000000..97ee44c --- /dev/null +++ b/ListView.cc @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2016 Jon Turney + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * A copy of the GNU General Public License can be found at + * http://www.gnu.org/ + * + */ + +#include "ListView.h" +#include "LogSingleton.h" + +#include <commctrl.h> + +// --------------------------------------------------------------------------- +// implements class ListView +// +// ListView Common Control +// --------------------------------------------------------------------------- + +void +ListView::init(HWND parent, int id, HeaderList headers) +{ + hWndParent = parent; + + // locate the listview control + hWndListView = ::GetDlgItem(parent, id); + + // configure the listview control + SendMessage(hWndListView, CCM_SETVERSION, 6, 0); + + ListView_SetExtendedListViewStyle(hWndListView, + LVS_EX_COLUMNSNAPPOINTS | // use cxMin + LVS_EX_FULLROWSELECT | + LVS_EX_GRIDLINES | + LVS_EX_HEADERDRAGDROP); // headers can be re-ordered + + // give the header control a border + HWND hWndHeader = ListView_GetHeader(hWndListView); + SetWindowLongPtr(hWndHeader, GWL_STYLE, + GetWindowLongPtr(hWndHeader, GWL_STYLE) | WS_BORDER); + + // ensure an initial item exists for width calculations... + LVITEM lvi; + lvi.mask = LVIF_TEXT; + lvi.iItem = 0; + lvi.iSubItem = 0; + lvi.pszText = const_cast <char *> ("Working..."); + ListView_InsertItem(hWndListView, &lvi); + + // populate with columns + initColumns(headers); +} + +void +ListView::initColumns(HeaderList headers_) +{ + // store HeaderList for later use + headers = headers_; + + // create the columns + LVCOLUMN lvc; + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + + int i; + for (i = 0; headers[i].text != 0; i++) + { + lvc.iSubItem = i; + lvc.pszText = const_cast <char *> (headers[i].text); + lvc.cx = 100; + lvc.fmt = headers[i].fmt; + + ListView_InsertColumn(hWndListView, i, &lvc); + } + + // now do some width calculations + for (i = 0; headers[i].text != 0; i++) + { + headers[i].width = 0; + + ListView_SetColumnWidth(hWndListView, i, LVSCW_AUTOSIZE_USEHEADER); + headers[i].hdr_width = ListView_GetColumnWidth(hWndListView, i); + } +} + +void +ListView::noteColumnWidthStart() +{ + dc = GetDC (hWndListView); + + // we must set the font of the DC here, otherwise the width calculations + // will be off because the system will use the wrong font metrics + HANDLE sysfont = GetStockObject (DEFAULT_GUI_FONT); + SelectObject (dc, sysfont); + + int i; + for (i = 0; headers[i].text != 0; i++) + { + headers[i].width = 0; + } +} + +void +ListView::noteColumnWidth(int col_num, const std::string& string) +{ + SIZE s = { 0, 0 }; + + // A margin of 3*GetSystemMetrics(SM_CXEDGE) is used at each side of the + // header text. + int addend = 2*3*GetSystemMetrics(SM_CXEDGE); + + if (string.size()) + GetTextExtentPoint32 (dc, string.c_str(), string.size(), &s); + + int width = addend + s.cx; + + if (width > headers[col_num].width) + headers[col_num].width = width; +} + +void +ListView::noteColumnWidthEnd() +{ + ReleaseDC(hWndListView, dc); +} + +void +ListView::resizeColumns(void) +{ + // ensure the last column stretches all the way to the right-hand side of the + // listview control + int i; + int total = 0; + for (i = 0; headers[i].text != 0; i++) + total = total + headers[i].width; + + RECT r; + GetClientRect(hWndListView, &r); + int width = r.right - r.left; + + if (total < width) + headers[i-1].width += width - total; + + // size each column + LVCOLUMN lvc; + lvc.mask = LVCF_WIDTH | LVCF_MINWIDTH; + for (i = 0; headers[i].text != 0; i++) + { + lvc.iSubItem = i; + lvc.cx = (headers[i].width < headers[i].hdr_width) ? headers[i].hdr_width : headers[i].width; + lvc.cxMin = headers[i].hdr_width; +#if DEBUG + Log (LOG_BABBLE) << "resizeColumns: " << i << " cx " << lvc.cx << " cxMin " << lvc.cxMin <<endLog; +#endif + + ListView_SetColumn(hWndListView, i, &lvc); + } +} + +void +ListView::setContents(ListViewContents *_contents) +{ + contents = _contents; + + // disable redrawing of ListView + // (otherwise it will redraw every time a row is added, which makes this very slow) + SendMessage(hWndListView, WM_SETREDRAW, FALSE, 0); + + // preserve focus/selection + int iRow = ListView_GetSelectionMark(hWndListView); + + empty(); + + size_t i; + for (i = 0; i < contents->size(); i++) + { + LVITEM lvi; + lvi.mask = LVIF_TEXT; + lvi.iItem = i; + lvi.iSubItem = 0; + lvi.pszText = LPSTR_TEXTCALLBACK; + + ListView_InsertItem(hWndListView, &lvi); + } + + if (iRow >= 0) + { + ListView_SetItemState(hWndListView, iRow, LVNI_SELECTED | LVNI_FOCUSED, LVNI_SELECTED | LVNI_FOCUSED); + ListView_EnsureVisible(hWndListView, iRow, false); + } + + // enable redrawing of ListView and redraw + SendMessage(hWndListView, WM_SETREDRAW, TRUE, 0); + RedrawWindow(hWndListView, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); +} + +// Helper class: The char * pointer we hand back needs to remain valid for some +// time after OnNotify returns, when the std::string we have retrieved has gone +// out of scope, so a static instance of this class maintains a local cache. +class StringCache +{ +public: + StringCache() : cache(NULL), cache_size(0) { } + StringCache & operator = (const std::string & s) + { + if ((s.length() + 1) > cache_size) + { + cache_size = s.length() + 1; + cache = (char *)realloc(cache, cache_size); + } + strcpy(cache, s.c_str()); + return *this; + } + operator char *() const + { + return cache; + } +private: + char *cache; + size_t cache_size; +}; + +bool +ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult) +{ +#if DEBUG + Log (LOG_BABBLE) << "ListView::OnNotify id:" << pNmHdr->idFrom << " hwnd:" << pNmHdr->hwndFrom << " code:" << (int)pNmHdr->code << endLog; +#endif + + switch (pNmHdr->code) + { + case LVN_GETDISPINFO: + { + NMLVDISPINFO *pNmLvDispInfo = (NMLVDISPINFO *)pNmHdr; +#if DEBUG + Log (LOG_BABBLE) << "LVN_GETDISPINFO " << pNmLvDispInfo->item.iItem << endLog; +#endif + if (contents) + { + int iRow = pNmLvDispInfo->item.iItem; + int iCol = pNmLvDispInfo->item.iSubItem; + + static StringCache s; + s = (*contents)[iRow]->get_text(iCol); + pNmLvDispInfo->item.pszText = s; + } + + return true; + } + break; + + case LVN_GETEMPTYMARKUP: + { + NMLVEMPTYMARKUP *pNmMarkup = (NMLVEMPTYMARKUP*) pNmHdr; + + MultiByteToWideChar(CP_UTF8, 0, + empty_list_text, -1, + pNmMarkup->szMarkup, L_MAX_URL_LENGTH); + + *pResult = true; + return true; + } + break; + + case NM_CLICK: + { + NMITEMACTIVATE *pNmItemAct = (NMITEMACTIVATE *) pNmHdr; +#if DEBUG + Log (LOG_BABBLE) << "NM_CLICK: pnmitem->iItem " << pNmItemAct->iItem << " pNmItemAct->iSubItem " << pNmItemAct->iSubItem << endLog; +#endif + int iRow = pNmItemAct->iItem; + int iCol = pNmItemAct->iSubItem; + + if (iRow >= 0) + { + // Inform the item of the click + int update = (*contents)[iRow]->do_action(iCol); + + // Update items, if needed + if (update > 0) + { + ListView_RedrawItems(hWndListView, iRow, iRow + update -1); + } + } + return true; + } + break; + } + + // We don't care. + return false; +} + +void +ListView::empty(void) +{ + ListView_DeleteAllItems(hWndListView); +} + +void +ListView::setEmptyText(const char *text) +{ + empty_list_text = text; +} diff --git a/ListView.h b/ListView.h new file mode 100644 index 0000000..d339011 --- /dev/null +++ b/ListView.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016 Jon Turney + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * A copy of the GNU General Public License can be found at + * http://www.gnu.org/ + * + */ + +#ifndef SETUP_LISTVIEW_H +#define SETUP_LISTVIEW_H + +#include "win32.h" +#include <vector> + +// --------------------------------------------------------------------------- +// interface to class ListView +// +// ListView Common Control +// --------------------------------------------------------------------------- + +class ListViewLine +{ + public: + virtual ~ListViewLine() {}; + virtual const std::string get_text(int col) const = 0; + virtual int do_action(int col) = 0; +}; + +typedef std::vector<ListViewLine *> ListViewContents; + +class ListView +{ + public: + class Header + { + public: + const char *text; + int fmt; + int width; + int hdr_width; + }; + typedef Header *HeaderList; + + void init(HWND parent, int id, HeaderList headers); + + void noteColumnWidthStart(); + void noteColumnWidth(int col_num, const std::string& string); + void noteColumnWidthEnd(); + void resizeColumns(void); + + void setContents(ListViewContents *contents); + void setEmptyText(const char *text); + + bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult); + + private: + HWND hWndParent; + HWND hWndListView; + HDC dc; + ListViewContents *contents; + HeaderList headers; + const char *empty_list_text; + + void initColumns(HeaderList hl); + void empty(void); +}; + +#endif /* SETUP_LISTVIEW_H */ diff --git a/Makefile.am b/Makefile.am index 7bd7c57..bce4c8c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -42,17 +42,11 @@ EXTRA_DIST = \ CONTRIBUTORS \ COPYING \ bootstrap.sh \ - check-na.bmp \ - check-no.bmp \ - check-yes.bmp \ - choose-spin.bmp \ cygwin.ico \ cygwin-setup.ico \ cygwin-terminal.ico \ setup.exe.manifest \ - setup64.exe.manifest \ - tree-minus.bmp \ - tree-plus.bmp + setup64.exe.manifest # iniparse.hh is generated from iniparse.yy via bison -d, so it needs to be # included here for proper tracking (but not iniparse.cc, since automake @@ -172,6 +166,8 @@ inilint_SOURCES = \ KeysSetting.h \ libsolv.cc \ libsolv.h \ + ListView.cc \ + ListView.h \ localdir.cc \ localdir.h \ LogFile.cc \ @@ -207,7 +203,6 @@ inilint_SOURCES = \ PackageTrust.h \ PickCategoryLine.cc \ PickCategoryLine.h \ - PickLine.h \ PickPackageLine.cc \ PickPackageLine.h \ PickView.cc \ diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc index e428419..6737454 100644 --- a/PickCategoryLine.cc +++ b/PickCategoryLine.cc @@ -16,134 +16,37 @@ #include "PickCategoryLine.h" #include "package_db.h" #include "PickView.h" +#include "window.h" -void -PickCategoryLine::empty (void) +const std::string +PickCategoryLine::get_text (int col_num) const { - while (bucket.size ()) + if (col_num == pkgname_col) { - PickLine *line = *bucket.begin (); - delete line; - bucket.erase (bucket.begin ()); + std::string s = (cat_tree->collapsed() ? "[+] " : "[-] ") + cat_tree->category().first; + return s; } -} - -void -PickCategoryLine::paint (HDC hdc, HRGN hUpdRgn, int x, int y, int row, int show_cat) -{ - int r = y + row * theView.row_height; - if (show_label) - { - int x2 = x + theView.headers[theView.cat_col].x + HMARGIN / 2 + depth * TREE_INDENT; - int by = r + (theView.tm.tmHeight / 2) - 5; - - // draw the '+' or '-' box - theView.DrawIcon (hdc, x2, by, (collapsed ? theView.bm_treeplus : theView.bm_treeminus)); - - // draw the category name - TextOut (hdc, x2 + 11 + ICON_MARGIN, r, cat.first.c_str(), cat.first.size()); - if (!labellength) - { - SIZE s; - GetTextExtentPoint32 (hdc, cat.first.c_str(), cat.first.size(), &s); - labellength = s.cx; - } - - // draw the 'spin' glyph - spin_x = x2 + 11 + ICON_MARGIN + labellength + ICON_MARGIN; - theView.DrawIcon (hdc, spin_x, by, theView.bm_spin); - - // draw the caption ('Default', 'Install', etc) - TextOut (hdc, spin_x + SPIN_WIDTH + ICON_MARGIN, r, - packagemeta::action_caption (current_default), - strlen (packagemeta::action_caption (current_default))); - row++; - } - if (collapsed) - return; - - // are the siblings containers? - if (bucket.size () && bucket[0]->IsContainer ()) - { - for (size_t n = 0; n < bucket.size (); n++) - { - bucket[n]->paint (hdc, hUpdRgn, x, y, row, show_cat); - row += bucket[n]->itemcount (); - } - } - else + else if (col_num == new_col) { - // calculate the maximum y value we expect for this group of lines - int max_y = y + (row + bucket.size ()) * theView.row_height; - - // paint all contained rows, columnwise - for (int i = 0; theView.headers[i].text; i++) - { - RECT r; - r.left = x + theView.headers[i].x; - r.right = r.left + theView.headers[i].width; - - // set up a clipping mask if necessary - if (theView.headers[i].needs_clip) - IntersectClipRect (hdc, r.left, y, r.right, max_y); - - // draw each row in this column - for (unsigned int n = 0; n < bucket.size (); n++) - { - // test for visibility - r.top = y + ((row + n) * theView.row_height); - r.bottom = r.top + theView.row_height; - if (RectVisible (hdc, &r) != 0) - bucket[n]->paint (hdc, hUpdRgn, (int)r.left, (int)r.top, i, show_cat); - } - - // restore original clipping area - if (theView.headers[i].needs_clip) - SelectClipRgn (hdc, hUpdRgn); - } + return packagemeta::action_caption (cat_tree->action()); } + return ""; } int -PickCategoryLine::click (int const myrow, int const ClickedRow, int const x) +PickCategoryLine::do_action(int col_num) { - if (myrow == ClickedRow && show_label) + if (col_num == pkgname_col) { - if ((size_t) x >= spin_x) - { - current_default = (packagemeta::_actions)((current_default + 1) % 4); - return set_action (current_default); - } - else - { - collapsed = !collapsed; - int accum_row = 0; - for (size_t n = 0; n < bucket.size (); ++n) - accum_row += bucket[n]->itemcount (); - return collapsed ? accum_row : -accum_row; - } + cat_tree->collapsed() = ! cat_tree->collapsed(); + theView.refresh(); } - else + else if (col_num == new_col) { - int accum_row = myrow + (show_label ? 1 : 0); - for (size_t n = 0; n < bucket.size (); ++n) - { - if (accum_row + bucket[n]->itemcount () > ClickedRow) - return bucket[n]->click (accum_row, ClickedRow, x); - accum_row += bucket[n]->itemcount (); - } - return 0; + theView.GetParent ()->SetBusy (); + int u = cat_tree->do_action((packagemeta::_actions)((cat_tree->action() + 1) % 4), theView.deftrust); + theView.GetParent ()->ClearBusy (); + return u; } -} - -int -PickCategoryLine::set_action (packagemeta::_actions action) -{ - theView.GetParent ()->SetBusy (); - current_default = action; - int accum_diff = 0; - for (size_t n = 0; n < bucket.size (); n++) - accum_diff += bucket[n]->set_action (current_default); - theView.GetParent ()->ClearBusy (); - return accum_diff; + return 1; } diff --git a/PickCategoryLine.h b/PickCategoryLine.h index dcffbac..9423eb8 100644 --- a/PickCategoryLine.h +++ b/PickCategoryLine.h @@ -16,75 +16,28 @@ #ifndef SETUP_PICKCATEGORYLINE_H #define SETUP_PICKCATEGORYLINE_H - -class PickView; -#include <vector> -#include "PickLine.h" #include "package_meta.h" +#include "ListView.h" +#include "PickView.h" -class PickCategoryLine:public PickLine +class PickCategoryLine: public ListViewLine { public: - PickCategoryLine (PickView & aView, Category & _cat, size_t thedepth = 0, bool aBool = - true, bool aBool2 = - true):PickLine (_cat.first), - current_default (packagemeta::Default_action), cat (_cat), labellength (0), - depth (thedepth), theView (aView) + PickCategoryLine (PickView & aView, CategoryTree * _tree) : + cat_tree (_tree), + theView (aView) { - if (aBool) - { - collapsed = true; - show_label = true; - } - else - { - collapsed = false; - show_label = aBool2; - } }; ~PickCategoryLine () { - empty (); - } - void ShowLabel (bool aBool = true) - { - show_label = aBool; - if (!show_label) - collapsed = false; - } - virtual void paint (HDC hdc, HRGN hUpdRgn, int x, int y, int row, int show_cat); - virtual int click (int const myrow, int const ClickedRow, int const x); - virtual int itemcount () const - { - if (collapsed) - return 1; - int t = show_label ? 1 : 0; - for (size_t n = 0; n < bucket.size (); ++n) - t += bucket[n]->itemcount (); - return t; - }; - virtual bool IsContainer (void) const - { - return true; - } - virtual void insert (PickLine & aLine) - { - bucket.push_back (&aLine); } - void empty (); - virtual int set_action (packagemeta::_actions); + + const std::string get_text(int col) const; + int do_action(int col); + private: - packagemeta::_actions - current_default; - Category & cat; - bool collapsed; - bool show_label; - size_t labellength; - size_t spin_x; // x-coord where the spin button starts - size_t depth; - PickCategoryLine (PickCategoryLine const &); - PickCategoryLine & operator= (PickCategoryLine const &); - std::vector < PickLine * > bucket; - PickView& theView; + CategoryTree * cat_tree; + PickView & theView; }; + #endif /* SETUP_PICKCATEGORYLINE_H */ diff --git a/PickLine.h b/PickLine.h deleted file mode 100644 index 7cf84cb..0000000 --- a/PickLine.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2002 Robert Collins. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * A copy of the GNU General Public License can be found at - * http://www.gnu.org/ - * - * Written by Robert Collins <robertc@hotmail.com> - * - */ - -#ifndef SETUP_PICKLINE_H -#define SETUP_PICKLINE_H - -#include "win32.h" -#include "package_meta.h" - -class PickLine -{ -public: - virtual void paint (HDC hdc, HRGN hUpdRgn, int x, int y, int col_num, int show_cat) = 0; - virtual int click (int const myrow, int const ClickedRow, int const x) = 0; - virtual int set_action (packagemeta::_actions) = 0; - virtual int itemcount () const = 0; - // this may indicate bad inheritance model. - virtual bool IsContainer (void) const = 0; - virtual void insert (PickLine &) = 0; - const std::string key; - virtual ~ PickLine () - { - }; -protected: - PickLine () - { - }; - PickLine (const std::string& aKey) : key (aKey) - { - }; - PickLine (PickLine const &); - PickLine & operator= (PickLine const &); -}; - -#endif /* SETUP_PICKLINE_H */ diff --git a/PickPackageLine.cc b/PickPackageLine.cc index 2caeafe..b348ab0 100644 --- a/PickPackageLine.cc +++ b/PickPackageLine.cc @@ -17,70 +17,54 @@ #include "PickView.h" #include "package_db.h" -void -PickPackageLine::paint (HDC hdc, HRGN unused, int x, int y, int col_num, int show_cat) +const std::string +PickPackageLine::get_text(int col_num) const { - int rb = y + theView.tm.tmHeight; - int by = rb - 11; // top of box images - std::string s; - - if (col_num == theView.current_col && pkg.installed) + if (col_num == pkgname_col) + { + return pkg.name; + } + else if (col_num == current_col) { - TextOut (hdc, x + HMARGIN/2, y, pkg.installed.Canonical_version ().c_str(), - pkg.installed.Canonical_version ().size()); + return pkg.installed.Canonical_version (); } - else if (col_num == theView.new_col) + else if (col_num == new_col) { - // TextOut (hdc, x + HMARGIN/2 + NEW_COL_SIZE_SLOP, y, s.c_str(), s.size()); - // theView.DrawIcon (hdc, x + HMARGIN/2 + ICON_MARGIN/2 + RTARROW_WIDTH, by, theView.bm_spin); - TextOut (hdc, x + HMARGIN/2 + ICON_MARGIN/2 + SPIN_WIDTH , y, - pkg.action_caption ().c_str(), pkg.action_caption ().size()); - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_spin); + return pkg.action_caption (); } - else if (col_num == theView.bintick_col) + else if (col_num == bintick_col) { + const char *bintick = "?"; if (/* uninstall or skip */ !pkg.desired || /* current version */ pkg.desired == pkg.installed || /* no source */ !pkg.desired.accessible()) - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkna); + bintick = "n/a"; else if (pkg.picked()) - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkyes); + bintick = "yes"; else - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkno); + bintick = "no"; + + return bintick; } - else if (col_num == theView.srctick_col) + else if (col_num == srctick_col) { + const char *srctick = "?"; if ( /* uninstall */ !pkg.desired || - -#if 0 - /* note: I'm not sure what the logic here is. With this following - check enabled, clicking on the "source" box for a package that - is already installed results it in showing "n/a", instead of a - cross-box. That seems very unintuitive, it should show a cross- - box to indicate that the source is going to be downloaded and - unpacked. Disabling this, but leaving the code as reference - in case there is some reason I'm missing for having it. --b.d. */ - /* source only */ (!pkg.desired.picked() - && pkg.desired.sourcePackage().picked() && pkg.desired == pkg.installed) || -#endif - /* when no source mirror available */ - !pkg.desired.sourcePackage().accessible()) - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkna); + /* when no source mirror available */ + !pkg.desired.sourcePackage().accessible()) + srctick = "n/a"; else if (pkg.srcpicked()) - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkyes); + srctick = "yes"; else - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkno); + srctick = "no"; + + return srctick; } - else if (col_num == theView.cat_col) + else if (col_num == cat_col) { - /* shows "first" category - do we want to show any? */ - if (pkg.categories.size () && show_cat) - { - s = pkg.getReadableCategoryList(); - TextOut (hdc, x + HMARGIN / 2, y, s.c_str(), s.size()); - } + return pkg.getReadableCategoryList(); } - else if (col_num == theView.size_col) + else if (col_num == size_col) { int sz = 0; packageversion picked; @@ -103,62 +87,49 @@ PickPackageLine::paint (HDC hdc, HRGN unused, int x, int y, int col_num, int sho sz += picked.sourcePackage().source()->size; /* If size still 0, size must be unknown. */ - s = (sz == 0) ? "?" : format_1000s((sz+1023)/1024) + "k"; - SIZE tw; - GetTextExtentPoint32 (hdc, s.c_str(), s.size(), &tw); - int cw = theView.headers[col_num].width - HMARGIN - tw.cx; - TextOut (hdc, x + cw + HMARGIN / 2, y, s.c_str(), s.size()); + std::string size = (sz == 0) ? "?" : format_1000s((sz+1023)/1024) + "k"; + + return size; } - else if (col_num == theView.pkg_col) + else if (col_num == pkg_col) { - s = pkg.name; - if (pkg.SDesc ().size()) - s += std::string(": ") + std::string(pkg.SDesc()); - TextOut (hdc, x + HMARGIN / 2, y, s.c_str(), s.size()); + return pkg.SDesc(); } + + return "unknown"; } int -PickPackageLine::click (int const myrow, int const ClickedRow, int const x) +PickPackageLine::do_action(int col_num) { - // assert (myrow == ClickedRow); - if (x >= theView.headers[theView.new_col].x - HMARGIN / 2 - && x <= theView.headers[theView.new_col + 1].x - HMARGIN / 2) + if (col_num == new_col) { pkg.set_action (theView.deftrust); - return 0; + return 1; } - if (x >= theView.headers[theView.bintick_col].x - HMARGIN / 2 - && x <= theView.headers[theView.bintick_col + 1].x - HMARGIN / 2) + + if (col_num == bintick_col) { if (pkg.desired.accessible ()) - pkg.pick (!pkg.picked ()); + pkg.pick (!pkg.picked ()); } - else if (x >= theView.headers[theView.srctick_col].x - HMARGIN / 2 - && x <= theView.headers[theView.srctick_col + 1].x - HMARGIN / 2) + else if (col_num == srctick_col) { if (pkg.desired.sourcePackage ().accessible ()) - pkg.srcpick (!pkg.srcpicked ()); + pkg.srcpick (!pkg.srcpicked ()); } - /* Unchecking binary while source is unchecked or vice versa is equivalent - to uninstalling. It's essential to set desired correctly, otherwise the + /* Unchecking binary while source is unchecked or vice versa is equivalent to + uninstalling. It's essential to set desired correctly, otherwise the package gets uninstalled without visual feedback to the user. The package will not even show up in the "Pending" view! */ - if ((x >= theView.headers[theView.bintick_col].x - HMARGIN / 2 - && x <= theView.headers[theView.bintick_col + 1].x - HMARGIN / 2) || - (x >= theView.headers[theView.srctick_col].x - HMARGIN / 2 - && x <= theView.headers[theView.srctick_col + 1].x - HMARGIN / 2)) + if ((col_num == bintick_col) || (col_num == srctick_col)) { if (!pkg.picked () && !pkg.srcpicked ()) pkg.desired = packageversion (); + + return 1; } return 0; } - -int PickPackageLine::set_action (packagemeta::_actions action) -{ - pkg.set_action (action, pkg.trustp (true, theView.deftrust)); - return 1; -} diff --git a/PickPackageLine.h b/PickPackageLine.h index 612bf38..a8c3c0d 100644 --- a/PickPackageLine.h +++ b/PickPackageLine.h @@ -17,29 +17,20 @@ #define SETUP_PICKPACKAGELINE_H class PickView; + #include "package_meta.h" -#include "PickLine.h" +#include "ListView.h" -class PickPackageLine:public PickLine +class PickPackageLine: public ListViewLine { public: - PickPackageLine (PickView &aView, packagemeta & apkg):PickLine (apkg.key), pkg (apkg), theView (aView) - { - }; - virtual void paint (HDC hdc, HRGN unused, int x, int y, int col_num, int show_cat); - virtual int click (int const myrow, int const ClickedRow, int const x); - virtual int itemcount () const - { - return 1; - } - virtual bool IsContainer (void) const - { - return false; - } - virtual void insert (PickLine &) + PickPackageLine (PickView &aView, packagemeta & apkg) : + pkg (apkg), + theView (aView) { }; - virtual int set_action (packagemeta::_actions); + const std::string get_text(int col) const; + int do_action(int col); private: packagemeta & pkg; PickView & theView; diff --git a/PickView.cc b/PickView.cc index 57e8f85..967d53b 100644 --- a/PickView.cc +++ b/PickView.cc @@ -14,12 +14,12 @@ */ #include "PickView.h" +#include "PickPackageLine.h" +#include "PickCategoryLine.h" #include <algorithm> #include <limits.h> -#include <commctrl.h> #include <shlwapi.h> -#include "PickPackageLine.h" -#include "PickCategoryLine.h" + #include "package_db.h" #include "dialog.h" #include "resource.h" @@ -28,135 +28,25 @@ #include "LogSingleton.h" #include "Exception.h" -static PickView::Header pkg_headers[] = { - {"Current", 0, 0, true}, - {"New", 0, 0, true}, - {"Bin?", 0, 0, false}, - {"Src?", 0, 0, false}, - {"Categories", 0, 0, true}, - {"Size", 0, 0, true}, - {"Package", 0, 0, true}, - {0, 0, 0, false} -}; - -static PickView::Header cat_headers[] = { - {"Category", 0, 0, true}, - {"Current", 0, 0, true}, - {"New", 0, 0, true}, - {"Bin?", 0, 0, false}, - {"Src?", 0, 0, false}, - {"Size", 0, 0, true}, - {"Package", 0, 0, true}, - {0, 0, 0, false} -}; - -ATOM PickView::WindowClassAtom = 0; - -// DoInsertItem - inserts an item into a header control. -// Returns the index of the new item. -// hwndHeader - handle to the header control. -// iInsertAfter - index of the previous item. -// nWidth - width of the new item. -// lpsz - address of the item string. -static int -DoInsertItem (HWND hwndHeader, int iInsertAfter, int nWidth, LPSTR lpsz) -{ - HDITEM hdi; - int index; - - hdi.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH; - hdi.pszText = lpsz; - hdi.cxy = nWidth; - hdi.cchTextMax = lstrlen (hdi.pszText); - hdi.fmt = HDF_LEFT | HDF_STRING; - - index = SendMessage (hwndHeader, HDM_INSERTITEM, - (WPARAM) iInsertAfter, (LPARAM) & hdi); - - return index; -} - -int -PickView::set_header_column_order (views vm) -{ - if (vm == views::PackageFull || vm == views::PackagePending - || vm == views::PackageKeeps || vm == views::PackageSkips - || vm == views::PackageUserPicked) - { - headers = pkg_headers; - current_col = 0; - new_col = 1; - bintick_col = new_col + 1; - srctick_col = bintick_col + 1; - cat_col = srctick_col + 1; - size_col = cat_col + 1; - pkg_col = size_col + 1; - last_col = pkg_col; - } - else if (vm == views::Category) - { - headers = cat_headers; - cat_col = 0; - current_col = 1; - new_col = current_col + 1; - bintick_col = new_col + 1; - srctick_col = bintick_col + 1; - size_col = srctick_col + 1; - pkg_col = size_col + 1; - last_col = pkg_col; - } - else - return -1; - return last_col; -} - -void -PickView::set_headers () -{ - if (set_header_column_order (view_mode) == -1) - return; - while (int n = SendMessage (listheader, HDM_GETITEMCOUNT, 0, 0)) - { - SendMessage (listheader, HDM_DELETEITEM, n - 1, 0); - } - int i; - for (i = 0; i <= last_col; i++) - DoInsertItem (listheader, i, headers[i].width, (char *) headers[i].text); -} - -void -PickView::note_width (PickView::Header *hdrs, HDC dc, - const std::string& string, int addend, int column) -{ - SIZE s = { 0, 0 }; - - if (string.size()) - GetTextExtentPoint32 (dc, string.c_str(), string.size(), &s); - if (hdrs[column].width < s.cx + addend) - hdrs[column].width = s.cx + addend; -} - void PickView::setViewMode (views mode) { view_mode = mode; - set_headers (); packagedb db; - contents.empty (); + size_t i; + for (i = 0; i < contents.size(); i++) + { + delete contents[i]; + } + contents.clear(); + if (view_mode == PickView::views::Category) { - contents.ShowLabel (true); - /* start collapsed. TODO: make this a chooser flag */ - for (packagedb::categoriesType::iterator n = - packagedb::categories.begin(); n != packagedb::categories.end(); - ++n) - insert_category (&*n, (*n).first.c_str()[0] == '.' - ? CATEGORY_EXPANDED : CATEGORY_COLLAPSED); + insert_category (cat_tree_root); } else { - contents.ShowLabel (false); // iterate through every package for (packagedb::packagecollection::iterator i = db.packages.begin (); i != db.packages.end (); ++i) @@ -175,7 +65,7 @@ PickView::setViewMode (views mode) (pkg.desired && (pkg.picked () || // install bin pkg.srcpicked ())))) // src - + // "Up to date" : installed packages that will not be changed || (view_mode == PickView::views::PackageKeeps && (pkg.installed && pkg.desired && !pkg.picked () @@ -191,29 +81,13 @@ PickView::setViewMode (views mode) { // Filter by package name if (packageFilterString.empty () - || StrStrI (pkg.name.c_str (), packageFilterString.c_str ())) + || StrStrI (pkg.name.c_str (), packageFilterString.c_str ())) insert_pkg (pkg); } } } - RECT r = GetClientRect (); - SCROLLINFO si; - memset (&si, 0, sizeof (si)); - si.cbSize = sizeof (si); - si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; - si.nMin = 0; - si.nMax = headers[last_col].x + headers[last_col].width; // + HMARGIN; - si.nPage = r.right; - SetScrollInfo (GetHWND(), SB_HORZ, &si, TRUE); - - si.nMax = contents.itemcount () * row_height; - si.nPage = r.bottom - header_height; - SetScrollInfo (GetHWND(), SB_VERT, &si, TRUE); - - scroll_ulc_x = scroll_ulc_y = 0; - - InvalidateRect (GetHWND(), &r, TRUE); + listview->setContents(&contents); } PickView::views @@ -259,7 +133,7 @@ isObsolete (std::set <std::string, casecompare_lt_op> &categories) bool isObsolete (const std::string& catname) { - if (casecompare(catname, "ZZZRemovedPackages") == 0 + if (casecompare(catname, "ZZZRemovedPackages") == 0 || casecompare(catname, "_", 1) == 0) return true; return false; @@ -273,120 +147,84 @@ PickView::setObsolete (bool doit) refresh (); } - void PickView::insert_pkg (packagemeta & pkg) { if (!showObsolete && isObsolete (pkg.categories)) return; - PickLine & line = *new PickPackageLine (*this, pkg); - contents.insert (line); + contents.push_back(new PickPackageLine(*this, pkg)); } void -PickView::insert_category (Category *cat, bool collapsed) +PickView::insert_category (CategoryTree *cat_tree) { - // Urk, special case - if (casecompare(cat->first, "All") == 0 || - (!showObsolete && isObsolete (cat->first))) + if (!cat_tree) return; - PickCategoryLine & catline = *new PickCategoryLine (*this, *cat, 1, collapsed); - int packageCount = 0; - for (std::vector <packagemeta *>::iterator i = cat->second.begin (); - i != cat->second.end () ; ++i) - { - if (packageFilterString.empty () \ - || (*i - && StrStrI ((*i)->name.c_str (), packageFilterString.c_str ()))) - { - PickLine & line = *new PickPackageLine (*this, **i); - catline.insert (line); - packageCount++; - } - } - - if (packageFilterString.empty () || packageCount) - contents.insert (catline); - else - delete &catline; -} - -int -PickView::click (int row, int x) -{ - return contents.click (0, row, x); -} + const Category *cat = &(cat_tree->category()); -void -PickView::scroll (HWND hwnd, int which, int *var, int code, int howmany = 1) -{ - SCROLLINFO si; - si.cbSize = sizeof (si); - si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; - GetScrollInfo (hwnd, which, &si); + // Suppress obsolete category when not showing obsolete + if ((!showObsolete && isObsolete (cat->first))) + return; - switch (code) + // if it's not the "All" category + bool hasContents = false; + bool isAll = casecompare(cat->first, "All") == 0; + if (!isAll) { - case SB_THUMBTRACK: - si.nPos = si.nTrackPos; - break; - case SB_THUMBPOSITION: - break; - case SB_BOTTOM: - si.nPos = si.nMax; - break; - case SB_TOP: - si.nPos = 0; - break; - case SB_LINEDOWN: - si.nPos += (row_height * howmany); - break; - case SB_LINEUP: - si.nPos -= (row_height * howmany); - break; - case SB_PAGEDOWN: - si.nPos += si.nPage * 9 / 10; - break; - case SB_PAGEUP: - si.nPos -= si.nPage * 9 / 10; - break; + // count the number of packages in this category + int packageCount = 0; + for (std::vector <packagemeta *>::const_iterator i = cat->second.begin (); + i != cat->second.end () ; ++i) + { + if (packageFilterString.empty () \ + || (*i + && StrStrI ((*i)->name.c_str (), packageFilterString.c_str ()))) + { + packageCount++; + } + } + + // if there are some packages in the category, or we are showing everything, + if (packageFilterString.empty () || packageCount) + { + hasContents = true; + } } - if ((int) si.nPos < 0) - si.nPos = 0; - if (si.nPos + si.nPage > (unsigned int) si.nMax) - si.nPos = si.nMax - si.nPage; - - si.fMask = SIF_POS; - SetScrollInfo (hwnd, which, &si, TRUE); - - int ox = scroll_ulc_x; - int oy = scroll_ulc_y; - *var = si.nPos; - - RECT cr, sr; - ::GetClientRect (hwnd, &cr); - sr = cr; - sr.top += header_height; - UpdateWindow (hwnd); - ScrollWindow (hwnd, ox - scroll_ulc_x, oy - scroll_ulc_y, &sr, &sr); - /* - sr.bottom = sr.top; - sr.top = cr.top; - ScrollWindow (hwnd, ox - scroll_ulc_x, 0, &sr, &sr); - */ - if (ox - scroll_ulc_x) + if (!isAll && !hasContents) + return; + + // insert line for the category + contents.push_back(new PickCategoryLine(*this, cat_tree)); + + // if not collapsed + if (!cat_tree->collapsed()) { - ::GetClientRect (listheader, &cr); - sr = cr; -// UpdateWindow (htmp); - ::MoveWindow (listheader, -scroll_ulc_x, 0, - headers[last_col].x + - headers[last_col].width, header_height, TRUE); + // insert lines for the packages in this category + if (!isAll) + { + for (std::vector <packagemeta *>::const_iterator i = cat->second.begin (); + i != cat->second.end () ; ++i) + { + if (packageFilterString.empty () \ + || (*i + && StrStrI ((*i)->name.c_str (), packageFilterString.c_str ()))) + { + insert_pkg(**i); + } + } + } + + // recurse for contained categories + for (std::vector <CategoryTree *>::iterator i = cat_tree->bucket().begin (); + i != cat_tree->bucket().end(); + i++) + { + insert_category(*i); + } } - UpdateWindow (hwnd); } /* this means to make the 'category' column wide enough to fit the first 'n' @@ -394,72 +232,56 @@ PickView::scroll (HWND hwnd, int which, int *var, int code, int howmany = 1) #define NUM_CATEGORY_COL_WIDTH 2 void -PickView::init_headers (HDC dc) +PickView::init_headers (void) { - int i; - - for (i = 0; headers[i].text; i++) - { - headers[i].width = 0; - headers[i].x = 0; - } - - // A margin of 3*GetSystemMetrics(SM_CXEDGE) is used at each side of the - // header text. (Probably should use that rather than hard-coding HMARGIN - // everywhere) - int addend = 2*3*GetSystemMetrics(SM_CXEDGE); + listview->noteColumnWidthStart(); - // accommodate widths of the 'bin' and 'src' checkbox columns - note_width (headers, dc, headers[bintick_col].text, addend, bintick_col); - note_width (headers, dc, headers[srctick_col].text, addend, srctick_col); + // widths of the 'bin' and 'src' checkbox columns just need to accommodate the + // column name + listview->noteColumnWidth (bintick_col, ""); + listview->noteColumnWidth (srctick_col, ""); - // accomodate the width of each category name + // (In category view) accommodate the width of each category name packagedb db; for (packagedb::categoriesType::iterator n = packagedb::categories.begin(); n != packagedb::categories.end(); ++n) { - if (!showObsolete && isObsolete (n->first)) - continue; - note_width (headers, dc, n->first, HMARGIN, cat_col); + listview->noteColumnWidth (cat_col, n->first); } /* For each package, accomodate the width of the installed version in the - current_col, the widths of all other versions in the new_col, and the - width of the sdesc for the pkg_col. Also, if this is not a Category - view, adjust the 'category' column so that the first NUM_CATEGORY_COL_WIDTH - categories from each package fits. */ + current_col, the widths of all other versions in the new_col, and the width + of the sdesc for the pkg_col and the first NUM_CATEGORY_COL_WIDTH + categories in the category column. */ for (packagedb::packagecollection::iterator n = db.packages.begin (); n != db.packages.end (); ++n) { packagemeta & pkg = *(n->second); - if (!showObsolete && isObsolete (pkg.categories)) - continue; if (pkg.installed) - note_width (headers, dc, pkg.installed.Canonical_version (), - HMARGIN, current_col); + listview->noteColumnWidth (current_col, pkg.installed.Canonical_version ()); for (std::set<packageversion>::iterator i = pkg.versions.begin (); - i != pkg.versions.end (); ++i) - { + i != pkg.versions.end (); ++i) + { if (*i != pkg.installed) - note_width (headers, dc, i->Canonical_version (), - HMARGIN + SPIN_WIDTH, new_col); - std::string z = format_1000s(i->source ()->size); - note_width (headers, dc, z, HMARGIN, size_col); - z = format_1000s(i->sourcePackage ().source ()->size); - note_width (headers, dc, z, HMARGIN, size_col); - } + listview->noteColumnWidth (new_col, i->Canonical_version ()); + std::string z = format_1000s(i->source ()->size); + listview->noteColumnWidth (size_col, z); + z = format_1000s(i->sourcePackage ().source ()->size); + listview->noteColumnWidth (size_col, z); + } std::string s = pkg.name; - if (pkg.SDesc ().size()) - s += std::string (": ") + std::string(pkg.SDesc ()); - note_width (headers, dc, s, HMARGIN, pkg_col); - - if (view_mode != PickView::views::Category && pkg.categories.size () > 2) + listview->noteColumnWidth (pkgname_col, s); + + s = pkg.SDesc (); + listview->noteColumnWidth (pkg_col, s); + + if (pkg.categories.size () > 2) { - std::string compound_cat(""); + std::string compound_cat(""); std::set<std::string, casecompare_lt_op>::const_iterator cat; size_t cnt; - - for (cnt = 0, cat = pkg.categories.begin (); + + for (cnt = 0, cat = pkg.categories.begin (); cnt < NUM_CATEGORY_COL_WIDTH && cat != pkg.categories.end (); ++cat) { @@ -470,507 +292,95 @@ PickView::init_headers (HDC dc) compound_cat += *cat; cnt++; } - note_width (headers, dc, compound_cat, HMARGIN, cat_col); + listview->noteColumnWidth (cat_col, compound_cat); } } - + // ensure that the new_col is wide enough for all the labels - const char *captions[] = { "Uninstall", "Skip", "Reinstall", "Retrieve", + const char *captions[] = { "Uninstall", "Skip", "Reinstall", "Retrieve", "Source", "Keep", NULL }; for (int i = 0; captions[i]; i++) - note_width (headers, dc, captions[i], HMARGIN + SPIN_WIDTH, new_col); - - // finally, compute the actual x values based on widths - headers[0].x = 0; - for (i = 1; i <= last_col; i++) - headers[i].x = headers[i - 1].x + headers[i - 1].width; - // and allow for resizing to ensure the last column reaches - // all the way to the end of the chooser box. - headers[last_col].width += total_delta_x; + listview->noteColumnWidth (cat_col, captions[i]); + + listview->noteColumnWidthEnd(); + listview->resizeColumns(); } -PickView::PickView (Category &cat) : deftrust (TRUST_CURR), -contents (*this, cat, 0, false, true), showObsolete (false), -packageFilterString (), hasWindowRect (false), total_delta_x (0) +PickView::PickView() : + deftrust (TRUST_UNKNOWN), + showObsolete (false), + packageFilterString (), + cat_tree_root (NULL) { } void -PickView::init(views _mode) +PickView::init(views _mode, ListView *_listview, Window *_parent) { - HDC dc = GetDC (GetHWND()); - sysfont = GetStockObject (DEFAULT_GUI_FONT); - SelectObject (dc, sysfont); - GetTextMetrics (dc, &tm); - - bitmap_dc = CreateCompatibleDC (dc); -#define LI(x) LoadImage (hinstance, MAKEINTRESOURCE (x), IMAGE_BITMAP, 0, 0, 0); - bm_spin = LI (IDB_SPIN); - bm_checkyes = LI (IDB_CHECK_YES); - bm_checkno = LI (IDB_CHECK_NO); - bm_checkna = LI (IDB_CHECK_NA); - bm_treeplus = LI (IDB_TREE_PLUS); - bm_treeminus = LI (IDB_TREE_MINUS); -#undef LI - icon_dc = CreateCompatibleDC (dc); - bm_icon = CreateCompatibleBitmap (dc, 11, 11); - SelectObject (icon_dc, bm_icon); - rect_icon = CreateRectRgn (0, 0, 11, 11); - - row_height = (tm.tmHeight + tm.tmExternalLeading + ROW_MARGIN); - int irh = tm.tmExternalLeading + tm.tmDescent + 11 + ROW_MARGIN; - if (row_height < irh) - row_height = irh; - - HDLAYOUT hdl; - WINDOWPOS wp; - - // Ensure that the common control DLL is loaded, and then create - // the header control. - INITCOMMONCONTROLSEX controlinfo = { sizeof (INITCOMMONCONTROLSEX), - ICC_LISTVIEW_CLASSES }; - InitCommonControlsEx (&controlinfo); - - if ((listheader = CreateWindowEx (0, WC_HEADER, (LPCTSTR) NULL, - WS_CHILD | WS_BORDER | CCS_NORESIZE | - // | HDS_BUTTONS - HDS_HORZ, 0, 0, 0, 0, GetHWND(), - (HMENU) IDC_CHOOSE_LISTHEADER, hinstance, - (LPVOID) NULL)) == NULL) - throw new Exception (TOSTRING(__LINE__) " " __FILE__, - "Unable to create list header window", - APPERR_WINDOW_ERROR); - - // Retrieve the bounding rectangle of the parent window's - // client area, and then request size and position values - // from the header control. - RECT rcParent = GetClientRect (); - - hdl.prc = &rcParent; - hdl.pwpos = ℘ - if (!SendMessage (listheader, HDM_LAYOUT, 0, (LPARAM) & hdl)) - throw new Exception (TOSTRING(__LINE__) " " __FILE__, - "Unable to get size and position of rectangle", - APPERR_WINDOW_ERROR); - - // Set the font of the listheader, but don't redraw, because its not shown - // yet.This message does not return a value, so we are not checking it as we - // do above. - SendMessage (listheader, WM_SETFONT, (WPARAM) sysfont, FALSE); - - // Set the size, position, and visibility of the header control. - SetWindowPos (listheader, wp.hwndInsertAfter, wp.x, wp.y, - wp.cx, wp.cy, wp.flags | SWP_SHOWWINDOW); - - header_height = wp.cy; - ReleaseDC (GetHWND (), dc); - view_mode = _mode; - refresh (); -} - -PickView::~PickView() -{ - DeleteDC (bitmap_dc); - DeleteObject (bm_spin); - DeleteObject (bm_checkyes); - DeleteObject (bm_checkno); - DeleteObject (bm_checkna); - DeleteObject (bm_treeplus); - DeleteObject (bm_treeminus); - DeleteObject (rect_icon); - DeleteObject (bm_icon); - DeleteDC (icon_dc); -} - -bool PickView::registerWindowClass () -{ - if (WindowClassAtom != 0) - return true; - - // We're not registered yet - WNDCLASSEX wc; - - wc.cbSize = sizeof (wc); - // Some sensible style defaults - wc.style = CS_HREDRAW | CS_VREDRAW; - // Our default window procedure. This replaces itself - // on the first call with the simpler Window::WindowProcReflector(). - wc.lpfnWndProc = Window::FirstWindowProcReflector; - // No class bytes - wc.cbClsExtra = 0; - // One pointer to REFLECTION_INFO in the extra window instance bytes - wc.cbWndExtra = 4; - // The app instance - wc.hInstance = hinstance; //GetInstance (); - // Use a bunch of system defaults for the GUI elements - wc.hIcon = LoadIcon (0, IDI_APPLICATION); - wc.hIconSm = NULL; - wc.hCursor = LoadCursor (0, IDC_ARROW); - wc.hbrBackground = NULL; - // No menu - wc.lpszMenuName = NULL; - // We'll get a little crazy here with the class name - wc.lpszClassName = "listview"; - - // All set, try to register - WindowClassAtom = RegisterClassEx (&wc); - if (WindowClassAtom == 0) - Log (LOG_BABBLE) << "Failed to register listview " << GetLastError () << endLog; - return WindowClassAtom != 0; -} - -LRESULT CALLBACK -PickView::list_vscroll (HWND hwnd, HWND hctl, UINT code, int pos) -{ - scroll (hwnd, SB_VERT, &scroll_ulc_y, code); - return 0; -} - -LRESULT CALLBACK -PickView::list_hscroll (HWND hwnd, HWND hctl, UINT code, int pos) -{ - scroll (hwnd, SB_HORZ, &scroll_ulc_x, code); - return 0; + listview = _listview; + parent = _parent; } void -PickView::set_vscroll_info (const RECT &r) +PickView::build_category_tree() { - SCROLLINFO si; - memset (&si, 0, sizeof (si)); - si.cbSize = sizeof (si); - si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; /* SIF_RANGE was giving strange behaviour */ - si.nMin = 0; - - si.nMax = contents.itemcount () * row_height; - si.nPage = r.bottom - header_height; - - /* if we are under the minimum display count , - * set the offset to 0 - */ - if ((unsigned int) si.nMax <= si.nPage) - scroll_ulc_y = 0; - si.nPos = scroll_ulc_y; - - SetScrollInfo (GetHWND(), SB_VERT, &si, TRUE); -} + /* Build the category tree */ -LRESULT CALLBACK -PickView::list_click (HWND hwnd, BOOL dblclk, int x, int y, UINT hitCode) -{ - int row, refresh __attribute__ ((unused)); - - if (contents.itemcount () == 0) - return 0; - - if (y < header_height) - return 0; - x += scroll_ulc_x; - y += scroll_ulc_y - header_height; - - row = (y + ROW_MARGIN / 2) / row_height; + /* Start collapsed. TODO: make that a flag */ + bool collapsed = true; - if (row < 0 || row >= contents.itemcount ()) - return 0; - - refresh = click (row, x); - - // XXX we need a method to query the database to see if more - // than just one package has changed! Until then... -#if 0 - if (refresh) + /* Dispose of any existing category tree */ + if (cat_tree_root) { -#endif - RECT r = GetClientRect (); - set_vscroll_info (r); - InvalidateRect (GetHWND(), &r, TRUE); -#if 0 - } - else - { - RECT rect; - rect.left = - headers[new_col].x - scroll_ulc_x; - rect.right = - headers[src_col + 1].x - scroll_ulc_x; - rect.top = - header_height + row * row_height - - scroll_ulc_y; - rect.bottom = rect.top + row_height; - InvalidateRect (hwnd, &rect, TRUE); + for (std::vector <CategoryTree *>::const_iterator i = cat_tree_root->bucket().begin(); + i != cat_tree_root->bucket().end(); + i++) + delete *i; + + delete cat_tree_root; + cat_tree_root = NULL; } -#endif - return 0; -} -/* - * LRESULT CALLBACK - * PickView::listview_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) - */ -LRESULT -PickView::WindowProc (UINT message, WPARAM wParam, LPARAM lParam) -{ - int wheel_notches; - UINT wheel_lines; - - switch (message) + /* Find the 'All' category */ + for (packagedb::categoriesType::iterator n = + packagedb::categories.begin(); n != packagedb::categories.end(); + ++n) { - case WM_HSCROLL: - list_hscroll (GetHWND(), (HWND)lParam, LOWORD(wParam), HIWORD(wParam)); - return 0; - case WM_VSCROLL: - list_vscroll (GetHWND(), (HWND)lParam, LOWORD(wParam), HIWORD(wParam)); - return 0; - case WM_MOUSEWHEEL: - // this is how many 'notches' the wheel scrolled, forward/up = positive - wheel_notches = GET_WHEEL_DELTA_WPARAM(wParam) / 120; - - // determine how many lines the user has configred for a mouse scroll - SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_lines, 0); - - if (wheel_lines == 0) // do no scrolling - return 0; - else if (wheel_lines == WHEEL_PAGESCROLL) - scroll (GetHWND (), SB_VERT, &scroll_ulc_y, (wheel_notches > 0) ? - SB_PAGEUP : SB_PAGEDOWN); - else - scroll (GetHWND (), SB_VERT, &scroll_ulc_y, (wheel_notches > 0) ? - SB_LINEUP : SB_LINEDOWN, wheel_lines * abs (wheel_notches)); - return 0; // handled - case WM_LBUTTONDOWN: - list_click (GetHWND(), FALSE, LOWORD(lParam), HIWORD(lParam), wParam); - return 0; - case WM_PAINT: - paint (GetHWND()); - return 0; - case WM_NOTIFY: - { - // pnmh = (LPNMHDR) lParam - LPNMHEADER phdr = (LPNMHEADER) lParam; - switch (phdr->hdr.code) - { - case HDN_ITEMCHANGED: - if (phdr->hdr.hwndFrom == ListHeader ()) - { - if (phdr->pitem && phdr->pitem->mask & HDI_WIDTH) - headers[phdr->iItem].width = phdr->pitem->cxy; - - for (int i = 1; i <= last_col; i++) - headers[i].x = headers[i - 1].x + headers[i - 1].width; - - RECT r = GetClientRect (); - SCROLLINFO si; - si.cbSize = sizeof (si); - si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; - GetScrollInfo (GetHWND(), SB_HORZ, &si); - - int oldMax = si.nMax; - si.nMax = headers[last_col].x + headers[last_col].width; - if (si.nTrackPos && oldMax > si.nMax) - si.nTrackPos += si.nMax - oldMax; - - si.nPage = r.right; - SetScrollInfo (GetHWND(), SB_HORZ, &si, TRUE); - InvalidateRect (GetHWND(), &r, TRUE); - if (si.nTrackPos && oldMax > si.nMax) - scroll (GetHWND(), SB_HORZ, &scroll_ulc_x, SB_THUMBTRACK); - } - break; - } + if (casecompare(n->first, "All") == 0) + { + cat_tree_root = new CategoryTree(*n, collapsed); + break; } - break; - case WM_SIZE: - { - // Note: WM_SIZE msgs only appear when 'just' scrolling the window - RECT windowRect = GetWindowRect (); - if (hasWindowRect) - { - int dx; - if ((dx = windowRect.right - windowRect.left - - lastWindowRect.width ()) != 0) - { - cat_headers[set_header_column_order (views::Category)].width += dx; - pkg_headers[set_header_column_order (views::PackagePending)].width += dx; - set_header_column_order (view_mode); - set_headers (); - ::MoveWindow (listheader, -scroll_ulc_x, 0, - headers[last_col].x + - headers[last_col].width, header_height, TRUE); - total_delta_x += dx; - } - if (windowRect.bottom - windowRect.top - lastWindowRect.height ()) - set_vscroll_info (GetClientRect ()); - } - else - hasWindowRect = true; - - lastWindowRect = windowRect; - return 0; - } - case WM_MOUSEACTIVATE: - SetFocus(GetHWND()); - return MA_ACTIVATE; } - - // default: can't handle this message - return DefWindowProc (GetHWND(), message, wParam, lParam); -} -//// -// Turn black into foreground color and white into background color by -// 1) Filling a square with ~(FG^BG) -// 2) Blitting the bitmap on it with NOTSRCERASE (white->black; black->FG^BG) -// 3) Blitting the result on BG with SRCINVERT (white->BG; black->FG) -void -PickView::DrawIcon (HDC hdc, int x, int y, HANDLE hIcon) -{ - SelectObject (bitmap_dc, hIcon); - FillRgn (icon_dc, rect_icon, bg_fg_brush); - BitBlt (icon_dc, 0, 0, 11, 11, bitmap_dc, 0, 0, NOTSRCERASE); - BitBlt (hdc, x, y, 11, 11, icon_dc, 0, 0, SRCINVERT); -///////////// On WinNT-based systems, we could've done the below instead -///////////// See http://support.microsoft.com/default.aspx?scid=kb;en-us;79212 -// SelectObject (hdc, GetSysColorBrush (COLOR_WINDOWTEXT)); -// HBITMAP bm_icon = CreateBitmap (11, 11, 1, 1, NULL); -// SelectObject (icon_dc, bm_icon); -// BitBlt (icon_dc, 0, 0, 11, 11, bitmap_dc, 0, 0, SRCCOPY); -// MaskBlt (hdc, x2, by, 11, 11, bitmap_dc, 0, 0, bm_icon, 0, 0, MAKEROP4 (SRCAND, PATCOPY)); -// DeleteObject (bm_icon); -} - -void -PickView::paint (HWND hwnd) -{ - // we want to retrieve the update region before calling BeginPaint, - // because after we do that the update region is validated and we can - // no longer retrieve it - HRGN hUpdRgn = CreateRectRgn (0, 0, 0, 0); - - if (GetUpdateRgn (hwnd, hUpdRgn, FALSE) == 0) + /* Add all the other categories as children */ + for (packagedb::categoriesType::iterator n = + packagedb::categories.begin(); n != packagedb::categories.end(); + ++n) { - // error? - return; - } - - // tell the system that we're going to begin painting our window - // it will prevent further WM_PAINT messages from arriving until we're - // done, and if any part of our window was invalidated while we are - // painting, it will retrigger us so that we can fix it - PAINTSTRUCT ps; - HDC hdc = BeginPaint (hwnd, &ps); - - SelectObject (hdc, sysfont); - SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)); - SetBkColor (hdc, GetSysColor (COLOR_WINDOW)); - FillRgn (hdc, hUpdRgn, GetSysColorBrush(COLOR_WINDOW)); - - COLORREF clr = ~GetSysColor (COLOR_WINDOW) ^ GetSysColor (COLOR_WINDOWTEXT); - clr = RGB (GetRValue (clr), GetGValue (clr), GetBValue (clr)); // reconvert - bg_fg_brush = CreateSolidBrush (clr); - - RECT cr; - ::GetClientRect (hwnd, &cr); - - int x = cr.left - scroll_ulc_x; - int y = cr.top - scroll_ulc_y + header_height; - - contents.paint (hdc, hUpdRgn, x, y, 0, (view_mode == - PickView::views::Category) ? 0 : 1); + if (casecompare(n->first, "All") == 0) + continue; - if (contents.itemcount () == 0) - { - static const char *msg = "Nothing to Install/Update"; - if (source == IDC_SOURCE_DOWNLOAD) - msg = "Nothing to Download"; - TextOut (hdc, x + HMARGIN, y, msg, strlen (msg)); + CategoryTree *cat_tree = new CategoryTree(*n, collapsed); + cat_tree_root->bucket().push_back(cat_tree); } - DeleteObject (hUpdRgn); - DeleteObject (bg_fg_brush); - EndPaint (hwnd, &ps); + refresh (); } - -bool -PickView::Create (Window * parent, DWORD Style, RECT *r) +PickView::~PickView() { - - // First register the window class, if we haven't already - if (!registerWindowClass ()) - { - // Registration failed - return false; - } - - // Save our parent, we'll probably need it eventually. - setParent(parent); - - // Create the window instance - CreateWindowEx (// Extended Style - WS_EX_CLIENTEDGE, - // window class atom (name) - "listview", //MAKEINTATOM(WindowClassAtom), - "listviewwindow", // no title-bar string yet - // Style bits - Style, - r ? r->left : CW_USEDEFAULT, - r ? r->top : CW_USEDEFAULT, - r ? r->right - r->left + 1 : CW_USEDEFAULT, - r ? r->bottom - r->top + 1 : CW_USEDEFAULT, - // Parent Window - parent == NULL ? (HWND)NULL : parent->GetHWND (), - // use class menu - (HMENU) MAKEINTRESOURCE (IDC_CHOOSE_LIST), - // The application instance - GetInstance (), - // The this ptr, which we'll use to set up - // the WindowProc reflection. - reinterpret_cast<void *>((Window *)this)); - if (GetHWND() == NULL) - { - Log (LOG_BABBLE) << "Failed to create PickView " << GetLastError () << endLog; - return false; - } - - return true; } void PickView::defaultTrust (trusts trust) { this->deftrust = trust; - - // force the picker to redraw - RECT r = GetClientRect (); - InvalidateRect (this->GetHWND(), &r, TRUE); } -/* This recalculates all column widths and resets the view */ void PickView::refresh() { - HDC dc = GetDC (GetHWND ()); - - // we must set the font of the DC here, otherwise the width calculations - // will be off because the system will use the wrong font metrics - sysfont = GetStockObject (DEFAULT_GUI_FONT); - SelectObject (dc, sysfont); - - // init headers for the current mode - set_headers (); - init_headers (dc); - - // save the current mode - views cur_view_mode = view_mode; - - // switch to the other type and do those headers - view_mode = (view_mode == PickView::views::Category) ? - PickView::views::PackageFull : PickView::views::Category; - set_headers (); - init_headers (dc); - ReleaseDC (GetHWND (), dc); - - view_mode = cur_view_mode; setViewMode (view_mode); } diff --git a/PickView.h b/PickView.h index 298f844..9777d15 100644 --- a/PickView.h +++ b/PickView.h @@ -17,84 +17,17 @@ #define SETUP_PICKVIEW_H #include <string> -#include "win32.h" -#include "window.h" -#include "RECTWrapper.h" - -#define HMARGIN 10 -#define ROW_MARGIN 5 -#define ICON_MARGIN 4 -#define SPIN_WIDTH 11 -#define CHECK_SIZE 11 -#define TREE_INDENT 12 - -#define CATEGORY_EXPANDED 0 -#define CATEGORY_COLLAPSED 1 - -class PickView; -#include "PickCategoryLine.h" + #include "package_meta.h" +#include "ListView.h" + +class Window; +class CategoryTree; -class PickView : public Window +class PickView { public: - virtual bool Create (Window * Parent = NULL, DWORD Style = WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, RECT * r = NULL); - virtual bool registerWindowClass (); - enum class views; - class Header; - int num_columns; - void defaultTrust (trusts trust); - void setViewMode (views mode); - views getViewMode (); - void DrawIcon (HDC hdc, int x, int y, HANDLE hIcon); - void paint (HWND hwnd); - LRESULT CALLBACK list_click (HWND hwnd, BOOL dblclk, int x, int y, UINT hitCode); - LRESULT CALLBACK list_hscroll (HWND hwnd, HWND hctl, UINT code, int pos); - LRESULT CALLBACK list_vscroll (HWND hwnd, HWND hctl, UINT code, int pos); - void set_vscroll_info (const RECT &r); - virtual LRESULT WindowProc (UINT uMsg, WPARAM wParam, LPARAM lParam); - Header *headers; - PickView (Category & cat); - void init(views _mode); - ~PickView(); - static const char *mode_caption (views mode); - void setObsolete (bool doit); - void insert_pkg (packagemeta &); - void insert_category (Category *, bool); - int click (int row, int x); - void refresh(); - int current_col; - int new_col; - int bintick_col; - int srctick_col; - int cat_col; - int size_col; - int pkg_col; - int last_col; - int row_height; - TEXTMETRIC tm; - HDC bitmap_dc, icon_dc; - HBITMAP bm_icon; - HRGN rect_icon; - HBRUSH bg_fg_brush; - HANDLE bm_spin, bm_checkyes, bm_checkno, bm_checkna, bm_treeplus, bm_treeminus; - trusts deftrust; - HANDLE sysfont; - int scroll_ulc_x, scroll_ulc_y; - int header_height; - PickCategoryLine contents; - void scroll (HWND hwnd, int which, int *var, int code, int howmany); - - void SetPackageFilter (const std::string &filterString) - { - packageFilterString = filterString; - } - - - HWND ListHeader (void) const - { - return listheader; - } + trusts deftrust; // XXX: needs accessor enum class views { @@ -106,35 +39,135 @@ public: Category, }; - class Header + PickView (); + ~PickView(); + void defaultTrust (trusts trust); + void setViewMode (views mode); + views getViewMode (); + void init(views _mode, ListView *_listview, Window *parent); + void build_category_tree(); + static const char *mode_caption (views mode); + void setObsolete (bool doit); + void refresh(); + void init_headers (); + + void SetPackageFilter (const std::string &filterString) { - public: - const char *text; - int width; - int x; - bool needs_clip; - }; + packageFilterString = filterString; + } + + Window *GetParent(void) { return parent; } private: - static ATOM WindowClassAtom; - HWND listheader; views view_mode; + ListView *listview; bool showObsolete; std::string packageFilterString; + ListViewContents contents; + CategoryTree *cat_tree_root; + Window *parent; - // Stuff needed to handle resizing - bool hasWindowRect; - RECTWrapper lastWindowRect; - int total_delta_x; + void insert_pkg (packagemeta &); + void insert_category (CategoryTree *); +}; - int set_header_column_order (views vm); - void set_headers (); - void init_headers (HDC dc); - void note_width (Header *hdrs, HDC dc, const std::string& string, - int addend, int column); +enum +{ + pkgname_col = 0, // package/category name + current_col = 1, + new_col = 2, // action + bintick_col = 3, + srctick_col = 4, + cat_col = 5, + size_col = 6, + pkg_col = 7, // desc }; bool isObsolete (std::set <std::string, casecompare_lt_op> &categories); bool isObsolete (const std::string& catname); +// +// Helper class which stores the contents and collapsed/expanded state for each +// category (and the pseudo-category 'All') +// + +class CategoryTree +{ +public: + CategoryTree(Category & cat, bool collapsed) : + _cat (cat), + _collapsed(collapsed), + _action (packagemeta::Default_action) + { + } + + ~CategoryTree() + { + } + + std::vector <CategoryTree *> & bucket() + { + return _bucket; + } + + bool &collapsed() + { + return _collapsed; + } + + const Category &category() + { + return _cat; + } + + packagemeta::_actions & action() + { + return _action; + } + + int do_action(packagemeta::_actions action_id, trusts const deftrust) + { + int u = 1; + _action = action_id; + + if (_bucket.size()) + { + for (std::vector <CategoryTree *>::const_iterator i = _bucket.begin(); + i != _bucket.end(); + i++) + { + // recurse for all contained categories + int l = (*i)->do_action(action_id, deftrust); + + if (!_collapsed) + u += l; + } + } + else + { + // otherwise, this is a leaf category, so apply action to all packages + // in this category + int l = 0; + for (std::vector <packagemeta *>::const_iterator pkg = _cat.second.begin(); + pkg != _cat.second.end(); + ++pkg) + { + (*pkg)->set_action(action_id, (*pkg)->trustp(true, deftrust)); + l++; + } + + // these lines need to be updated, if displayed + if (!_collapsed) + u += l; + } + return u; + } + +private: + Category & _cat; + bool _collapsed; + std::vector <CategoryTree *> _bucket; + packagemeta::_actions _action; +}; + #endif /* SETUP_PICKVIEW_H */ diff --git a/check-na.bmp b/check-na.bmp deleted file mode 100644 index c139e54d514a5b00997d9888d47f9f6d1fdd410b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q8U8<DVE7Kj*$xa0Sq(sZ0D`3z7#RLO IfMAe10G1vT>i_@% diff --git a/check-no.bmp b/check-no.bmp deleted file mode 100644 index 3639605be72d99fb7d2947b24832629609e8007d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 qcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q!GiS%7#Ij)kU{`6UJuv+ diff --git a/check-yes.bmp b/check-yes.bmp deleted file mode 100644 index f328dc2fe2899e6a350fb743b3820941ab1c68db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q!GiS%7#QLm7#Q+^bSV&5Lun8nBo2}X F0RRy04qgBN diff --git a/choose-spin.bmp b/choose-spin.bmp deleted file mode 100644 index 8779f6dc20ebfb35c22b7b97afda8fcad8d93664..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q89qK>V0Z___Y)Wx*nyZ$fq|h9h<g|q O814WuNE{>&QU?G_z7YTb diff --git a/choose.cc b/choose.cc index 51d2fb6..c86294a 100644 --- a/choose.cc +++ b/choose.cc @@ -81,7 +81,6 @@ static ControlAdjuster::ControlInfo ChooserControlsInfo[] = { {IDC_CHOOSE_SYNC, CP_RIGHT, CP_TOP}, {IDC_CHOOSE_EXP, CP_RIGHT, CP_TOP}, {IDC_CHOOSE_VIEW, CP_LEFT, CP_TOP}, - {IDC_LISTVIEW_POS, CP_RIGHT, CP_TOP}, {IDC_CHOOSE_VIEWCAPTION, CP_LEFT, CP_TOP}, {IDC_CHOOSE_LIST, CP_STRETCH, CP_STRETCH}, {IDC_CHOOSE_HIDE, CP_LEFT, CP_BOTTOM}, @@ -132,23 +131,33 @@ ChooserPage::~ChooserPage () } } +static ListView::Header pkg_headers[] = { + {"Package", LVCFMT_LEFT}, + {"Current", LVCFMT_LEFT}, + {"New", LVCFMT_LEFT}, + {"Bin?", LVCFMT_LEFT}, + {"Src?", LVCFMT_LEFT}, + {"Categories", LVCFMT_LEFT}, + {"Size", LVCFMT_RIGHT}, + {"Description", LVCFMT_LEFT}, + {0} +}; + void ChooserPage::createListview () { SetBusy (); - static std::vector<packagemeta *> empty_cat; - static Category dummy_cat (std::string ("All"), empty_cat); - chooser = new PickView (dummy_cat); - RECT r = getDefaultListViewSize(); - if (!chooser->Create(this, WS_CHILD | WS_HSCROLL | WS_VSCROLL | WS_VISIBLE,&r)) - throw new Exception (TOSTRING(__LINE__) " " __FILE__, - "Unable to create chooser list window", - APPERR_WINDOW_ERROR); - chooser->init(PickView::views::Category); - chooser->Show(SW_SHOW); + + listview = new ListView(); + listview->init(GetHWND (), IDC_CHOOSE_LIST, pkg_headers); + + chooser = new PickView(); + chooser->init(PickView::views::Category, listview, this); chooser->setViewMode (!is_new_install || UpgradeAlsoOption || hasManualSelections ? - PickView::views::PackagePending : PickView::views::Category); + PickView::views::PackagePending : PickView::views::Category); + SendMessage (GetDlgItem (IDC_CHOOSE_VIEW), CB_SETCURSEL, (WPARAM)chooser->getViewMode(), 0); + ClearBusy (); } @@ -240,16 +249,6 @@ ChooserPage::setPrompt(char const *aString) ::SetWindowText (GetDlgItem (IDC_CHOOSE_INST_TEXT), aString); } -RECT -ChooserPage::getDefaultListViewSize() -{ - RECT result; - getParentRect (GetHWND (), GetDlgItem (IDC_LISTVIEW_POS), &result); - result.top += 2; - result.bottom -= 2; - return result; -} - void ChooserPage::OnInit () { @@ -336,6 +335,17 @@ ChooserPage::OnActivate() activated = true; } + packagedb::categoriesType::iterator it = db.categories.find("All"); + if (it == db.categories.end ()) + listview->setEmptyText("No packages found."); + if (source == IDC_SOURCE_DOWNLOAD) + listview->setEmptyText("Nothing to download."); + else + listview->setEmptyText("Nothing to install or update."); + + chooser->build_category_tree(); + chooser->init_headers(); + ClearBusy(); chooser->refresh(); @@ -447,7 +457,7 @@ bool ChooserPage::OnMessageCmd (int id, HWND hwndctl, UINT code) { #if DEBUG - Log (LOG_BABBLE) << "OnMessageCmd " << id << " " << hwndctl << " " << std::hex << code << endLog; + Log (LOG_BABBLE) << "ChooserPage::OnMessageCmd " << id << " " << hwndctl << " " << std::hex << code << endLog; #endif if (id == IDC_CHOOSE_SEARCH_EDIT) @@ -549,10 +559,19 @@ ChooserPage::OnMessageCmd (int id, HWND hwndctl, UINT code) return false; } -INT_PTR CALLBACK -ChooserPage::OnMouseWheel (UINT message, WPARAM wParam, LPARAM lParam) +bool +ChooserPage::OnNotify (NMHDR *pNmHdr, LRESULT *pResult) { - return chooser->WindowProc (message, wParam, lParam); +#if DEBUG + Log (LOG_BABBLE) << "ChooserPage::OnNotify id:" << pNmHdr->idFrom << " hwnd:" << pNmHdr->hwndFrom << " code:" << (int)pNmHdr->code << endLog; +#endif + + // offer messages to the listview + if (listview->OnNotify(pNmHdr, pResult)) + return true; + + // we don't care + return false; } INT_PTR CALLBACK diff --git a/choose.h b/choose.h index 32a1650..e4953b7 100644 --- a/choose.h +++ b/choose.h @@ -20,6 +20,7 @@ #include "proppage.h" #include "package_meta.h" #include "PickView.h" +#include "ListView.h" #define DEFAULT_TIMER_ID 5 //value doesn't matter, as long as it's unique #define SEARCH_TIMER_DELAY 500 //in milliseconds @@ -33,8 +34,7 @@ public: ~ChooserPage (); virtual bool OnMessageCmd (int id, HWND hwndctl, UINT code); - virtual INT_PTR CALLBACK OnMouseWheel (UINT message, WPARAM wParam, - LPARAM lParam); + virtual bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult); virtual INT_PTR CALLBACK OnTimerMessage (UINT message, WPARAM wParam, LPARAM lparam); @@ -51,7 +51,6 @@ public: } private: void createListview (); - RECT getDefaultListViewSize(); void getParentRect (HWND parent, HWND child, RECT * r); void keepClicked(); void changeTrust(int button, bool test, bool initial); @@ -63,6 +62,7 @@ private: void initialUpdateState(); PickView *chooser; + ListView *listview; static HWND ins_dialog; bool cmd_show_set; bool saved_geom; diff --git a/main.cc b/main.cc index 1374fb6..8ceaa4d 100644 --- a/main.cc +++ b/main.cc @@ -145,7 +145,7 @@ main_display () // Initialize common controls INITCOMMONCONTROLSEX icce = { sizeof (INITCOMMONCONTROLSEX), - ICC_WIN95_CLASSES }; + ICC_WIN95_CLASSES | ICC_LISTVIEW_CLASSES }; InitCommonControlsEx (&icce); // Initialize COM and ShellLink instance here. For some reason diff --git a/res.rc b/res.rc index 02b60cf..27f0378 100644 --- a/res.rc +++ b/res.rc @@ -1,5 +1,6 @@ #include "resource.h" #include "windows.h" +#include "commctrl.h" #define SETUP_STANDARD_DIALOG_W 339 #define SETUP_STANDARD_DIALOG_H 179 @@ -357,8 +358,8 @@ BEGIN SETUP_EXP_X, 30, SETUP_KPCE_W, 14 CONTROL "", IDC_HEADSEPARATOR, "Static", SS_BLACKFRAME | SS_SUNKEN, 0, 28, SETUP_STANDARD_DIALOG_W, 1 - CONTROL "", IDC_LISTVIEW_POS, "Static", SS_BLACKFRAME | NOT - WS_VISIBLE, 7, 45, SETUP_STANDARD_DIALOG_W - 14, 122 + CONTROL "", IDC_CHOOSE_LIST, WC_LISTVIEW, LVS_NOSORTHEADER | LVS_REPORT | LVS_SINGLESEL, + 7, 47, SETUP_STANDARD_DIALOG_W - 14, 120, WS_EX_CLIENTEDGE CONTROL "&Hide obsolete packages", IDC_CHOOSE_HIDE, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 7, 167, 160, 14 ICON IDI_CYGWIN, IDC_HEADICON, SETUP_HEADICON_X, 0, 21, 20 @@ -528,18 +529,6 @@ CYGWIN-SETUP.ICON FILE DISCARDABLE "cygwin-setup.ico" CYGWIN.ICON FILE DISCARDABLE "cygwin.ico" CYGWIN-TERMINAL.ICON FILE DISCARDABLE "cygwin-terminal.ico" -///////////////////////////////////////////////////////////////////////////// -// -// Bitmap -// - -IDB_SPIN BITMAP DISCARDABLE "choose-spin.bmp" -IDB_CHECK_YES BITMAP DISCARDABLE "check-yes.bmp" -IDB_CHECK_NO BITMAP DISCARDABLE "check-no.bmp" -IDB_CHECK_NA BITMAP DISCARDABLE "check-na.bmp" -IDB_TREE_PLUS BITMAP DISCARDABLE "tree-plus.bmp" -IDB_TREE_MINUS BITMAP DISCARDABLE "tree-minus.bmp" - ///////////////////////////////////////////////////////////////////////////// // // String Table diff --git a/resource.h b/resource.h index 421a24c..2f1036b 100644 --- a/resource.h +++ b/resource.h @@ -71,15 +71,6 @@ #define IDD_DOWNLOAD_ERROR 224 #define IDD_CONFIRM 225 -// Bitmaps - -#define IDB_SPIN 300 -#define IDB_CHECK_YES 301 -#define IDB_CHECK_NO 302 -#define IDB_CHECK_NA 303 -#define IDB_TREE_PLUS 304 -#define IDB_TREE_MINUS 305 - // icons #define IDI_CYGWIN_SETUP 401 @@ -118,7 +109,6 @@ #define IDC_NET_USER 527 #define IDC_NET_PASSWD 528 #define IDC_VERSION 529 -#define IDC_LISTVIEW_POS 530 #define IDC_CHOOSE_VIEW 531 #define IDC_CHOOSE_EXP 532 #define IDC_CHOOSE_BEST 533 @@ -135,7 +125,6 @@ #define IDC_DLS_IPROGRESS_TEXT 545 #define IDC_CHOOSE_INST_TEXT 546 #define IDC_CHOOSE_VIEWCAPTION 547 -#define IDC_CHOOSE_LISTHEADER 548 #define IDC_INS_BL_PACKAGE 549 #define IDC_INS_BL_TOTAL 550 #define IDC_INS_BL_DISK 551 diff --git a/tree-minus.bmp b/tree-minus.bmp deleted file mode 100644 index 35b2221b89542e9c1b37b10969107f3fc0f72362..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q8U8<DU}#WaVA#I^ffpdLLE<2JkU9W? C))N^3 diff --git a/tree-plus.bmp b/tree-plus.bmp deleted file mode 100644 index e0335d970ffda606e31f76a84d1c56298106e4bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q8U8<DU}#WaVA#KafnhfkFF<00#6j{P FbpU`?6A1tS -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 06/13] Add methods for listing possible actions on, and applying one to, a package 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (7 preceding siblings ...) 2018-08-05 22:10 ` [PATCH setup 04/13] Use a ListView common control rather than a hand-built grid Jon Turney @ 2018-08-05 22:10 ` Jon Turney 2018-08-05 22:11 ` [PATCH setup 10/13] Use indents in category view Jon Turney ` (5 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney The 'action id' is a value of the _actions enum, if positive, otherwise it's absolute value is a 0-based index into the 'versions' collection to identify a specific version to install. v2: Fix select_action() to consider deftrust v3: Fix select_action() for keep --- ActionList.h | 55 +++++++++++++++++++++++++++++++++++++++ Makefile.am | 1 + PickView.h | 2 +- package_meta.cc | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ package_meta.h | 5 +++- 5 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 ActionList.h diff --git a/ActionList.h b/ActionList.h new file mode 100644 index 0000000..2e2d424 --- /dev/null +++ b/ActionList.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 Jon Turney + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * A copy of the GNU General Public License can be found at + * http://www.gnu.org/ + * + */ + +#ifndef SETUP_ACTIONLIST_H +#define SETUP_ACTIONLIST_H + +#include <string> +#include <vector> + +// --------------------------------------------------------------------------- +// interface to class ActionList +// +// a list of Actions possible on a package +// --------------------------------------------------------------------------- + +class Action +{ + public: + Action(const std::string &_name, int _id, bool _selected, bool _enabled) : + name(_name), + id(_id), + selected(_selected), + enabled(_enabled) + { }; + + std::string name; + int id; + bool selected; + bool enabled; +}; + +typedef std::vector<Action> Actions; + +class ActionList +{ + public: + void add(const std::string &name, int id, bool selected, bool enabled) + { + Action act(name, id, selected, enabled); + list.push_back(act); + }; + Actions list; +}; + +#endif /* SETUP_ACTIONLIST_H */ diff --git a/Makefile.am b/Makefile.am index bce4c8c..b58c9b7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -95,6 +95,7 @@ inilint_SOURCES = \ -lshlwapi -lcomctl32 -lole32 -lpsapi -luuid -lntdll -lwininet -lws2_32 -lmingw32 @SETUP@_LDFLAGS = -mwindows -Wc,-static -static-libtool-libs @SETUP@_SOURCES = \ + actionlist.h \ AntiVirus.cc \ AntiVirus.h \ archive.cc \ diff --git a/PickView.h b/PickView.h index 9777d15..fc9216e 100644 --- a/PickView.h +++ b/PickView.h @@ -152,7 +152,7 @@ public: pkg != _cat.second.end(); ++pkg) { - (*pkg)->set_action(action_id, (*pkg)->trustp(true, deftrust)); + (*pkg)->select_action(action_id, deftrust); l++; } diff --git a/package_meta.cc b/package_meta.cc index dffe3ac..f55c3f3 100644 --- a/package_meta.cc +++ b/package_meta.cc @@ -487,6 +487,75 @@ packagemeta::set_action (trusts const trust) user_picked = true; } +void +packagemeta::select_action (int id, trusts const deftrust) +{ + if (id <= 0) + { + // Install a specific version + set<packageversion>::iterator i = versions.begin (); + for (int j = -id; j > 0; j--) + i++; + + set_action(Install_action, *i); + } + else + { + if (id == packagemeta::Default_action) + set_action((packagemeta::_actions)id, installed); + else + set_action((packagemeta::_actions)id, trustp (true, deftrust)); + } + + /* Memorize the fact that the user picked at least once. */ + if (!installed) + user_picked = true; +} + +ActionList * +packagemeta::list_actions(trusts const trust) +{ + // first work out the current action, so we can indicate that + _actions action; + + if (!desired && installed) + action = Uninstall_action; + else if (!desired) + action = Default_action; // skip + else if (desired == installed && picked()) + action = Reinstall_action; + else if (desired == installed) + action = Default_action; // keep + else + action = Install_action; + + // now build the list of possible actions + ActionList *al = new ActionList(); + + al->add("Uninstall", (int)Uninstall_action, (action == Uninstall_action), bool(installed)); + al->add("Skip", (int)Default_action, (action == Default_action) && !installed, !installed); + + set<packageversion>::iterator i; + for (i = versions.begin (); i != versions.end (); ++i) + { + if (*i == installed) + { + al->add("Keep", (int)Default_action, (action == Default_action), TRUE); + al->add(packagedb::task == PackageDB_Install ? "Reinstall" : "Retrieve", + (int)Reinstall_action, (action == Reinstall_action), TRUE); + } + else + { + al->add(i->Canonical_version().c_str(), + -std::distance(versions.begin (), i), + (action == Install_action) && (*i == desired), + TRUE); + } + } + + return al; +} + // Set a particular type of action. void packagemeta::set_action (_actions action, packageversion const &default_version) diff --git a/package_meta.h b/package_meta.h index df66bda..0f01837 100644 --- a/package_meta.h +++ b/package_meta.h @@ -26,6 +26,7 @@ class packagemeta; #include "package_version.h" #include "package_message.h" #include "script.h" +#include "ActionList.h" typedef std::pair<const std::string, std::vector<packagemeta *> > Category; @@ -52,7 +53,7 @@ public: enum _actions { - Default_action, + Default_action = 1, Install_action, Reinstall_action, Uninstall_action, @@ -61,6 +62,8 @@ public: 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); void set_message (const std::string& message_id, const std::string& message_string) { -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 10/13] Use indents in category view 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (8 preceding siblings ...) 2018-08-05 22:10 ` [PATCH setup 06/13] Add methods for listing possible actions on, and applying one to, a package Jon Turney @ 2018-08-05 22:11 ` Jon Turney 2018-08-05 22:12 ` [PATCH setup 13/13] Add ldesc tooltips to sdesc column of listview Jon Turney ` (4 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:11 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney We keep around an empty imagelist for 1x1 images, to reset the indent when not in tree view mode (This isn't quite right as the listview will allocate a 1-pixel space before column 0 for those images, but this seems the best we can do as 0x0 imagelists aren't allowed...) --- ListView.cc | 29 +++++++++++++++++++++-------- ListView.h | 7 ++++++- PickCategoryLine.cc | 6 ++++++ PickCategoryLine.h | 7 +++++-- PickPackageLine.cc | 6 ++++++ PickPackageLine.h | 7 +++++-- PickView.cc | 10 +++++----- PickView.h | 2 +- 8 files changed, 55 insertions(+), 19 deletions(-) diff --git a/ListView.cc b/ListView.cc index 0c451d1..b0351cd 100644 --- a/ListView.cc +++ b/ListView.cc @@ -56,14 +56,17 @@ ListView::init(HWND parent, int id, HeaderList headers) // populate with columns initColumns(headers); - // create a small icon imagelist and assign to listview control + // create a small icon imagelist // (the order of images matches ListViewLine::State enum) - HIMAGELIST hImgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), - GetSystemMetrics(SM_CYSMICON), - ILC_COLOR32, 2, 0); + hImgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + ILC_COLOR32, 2, 0); ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_PLUS))); ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_MINUS))); - ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL); + + // create an empty imagelist, used to reset the indent + hEmptyImgList = ImageList_Create(1, 1, + ILC_COLOR32, 2, 0); } void @@ -172,7 +175,7 @@ ListView::resizeColumns(void) } void -ListView::setContents(ListViewContents *_contents) +ListView::setContents(ListViewContents *_contents, bool tree) { contents = _contents; @@ -185,15 +188,25 @@ ListView::setContents(ListViewContents *_contents) empty(); + // assign imagelist to listview control (this also sets the size for indents) + if (tree) + ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL); + else + ListView_SetImageList(hWndListView, hEmptyImgList, LVSIL_SMALL); + size_t i; for (i = 0; i < contents->size(); i++) { LVITEM lvi; - lvi.mask = LVIF_TEXT | LVIF_IMAGE; + lvi.mask = LVIF_TEXT | (tree ? LVIF_IMAGE | LVIF_INDENT : 0); lvi.iItem = i; lvi.iSubItem = 0; lvi.pszText = LPSTR_TEXTCALLBACK; - lvi.iImage = I_IMAGECALLBACK; + if (tree) + { + lvi.iImage = I_IMAGECALLBACK; + lvi.iIndent = (*contents)[i]->get_indent(); + } ListView_InsertItem(hWndListView, &lvi); } diff --git a/ListView.h b/ListView.h index f5aa1d9..34d6267 100644 --- a/ListView.h +++ b/ListView.h @@ -16,6 +16,7 @@ #include "ActionList.h" #include "win32.h" +#include <commctrl.h> #include <vector> // --------------------------------------------------------------------------- @@ -32,6 +33,7 @@ class ListViewLine virtual ~ListViewLine() {}; virtual const std::string get_text(int col) const = 0; virtual State get_state() const = 0; + virtual int get_indent() const = 0; virtual ActionList *get_actions(int col) const = 0; virtual int do_action(int col, int id) = 0; }; @@ -66,7 +68,7 @@ class ListView void noteColumnWidthEnd(); void resizeColumns(void); - void setContents(ListViewContents *contents); + void setContents(ListViewContents *contents, bool tree = false); void setEmptyText(const char *text); bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult); @@ -75,6 +77,9 @@ class ListView HWND hWndParent; HWND hWndListView; HDC dc; + HIMAGELIST hImgList; + HIMAGELIST hEmptyImgList; + ListViewContents *contents; HeaderList headers; const char *empty_list_text; diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc index 21795b2..e206aee 100644 --- a/PickCategoryLine.cc +++ b/PickCategoryLine.cc @@ -76,3 +76,9 @@ PickCategoryLine::get_state() const { return cat_tree->collapsed() ? State::collapsed : State::expanded; } + +int +PickCategoryLine::get_indent() const +{ + return indent; +} diff --git a/PickCategoryLine.h b/PickCategoryLine.h index 6b54c3f..a5daa8c 100644 --- a/PickCategoryLine.h +++ b/PickCategoryLine.h @@ -23,10 +23,11 @@ class PickCategoryLine: public ListViewLine { public: - PickCategoryLine (PickView & aView, CategoryTree * _tree, int _pkgcount) : + PickCategoryLine (PickView & aView, CategoryTree * _tree, int _pkgcount, int _indent) : cat_tree (_tree), pkgcount(_pkgcount), - theView (aView) + theView (aView), + indent(_indent) { }; ~PickCategoryLine () @@ -35,6 +36,7 @@ public: const std::string get_text(int col) const; State get_state() const; + int get_indent() const; ActionList *get_actions(int col) const; int do_action(int col, int action_id); @@ -42,6 +44,7 @@ private: CategoryTree * cat_tree; int pkgcount; PickView & theView; + int indent; }; #endif /* SETUP_PICKCATEGORYLINE_H */ diff --git a/PickPackageLine.cc b/PickPackageLine.cc index 67baad2..a602c90 100644 --- a/PickPackageLine.cc +++ b/PickPackageLine.cc @@ -143,3 +143,9 @@ PickPackageLine::get_actions(int col_num) const return NULL; } + +int +PickPackageLine::get_indent() const +{ + return indent; +} diff --git a/PickPackageLine.h b/PickPackageLine.h index dacad28..53c389b 100644 --- a/PickPackageLine.h +++ b/PickPackageLine.h @@ -24,18 +24,21 @@ class PickView; class PickPackageLine: public ListViewLine { public: - PickPackageLine (PickView &aView, packagemeta & apkg) : + PickPackageLine (PickView &aView, packagemeta & apkg, int aindent) : pkg (apkg), - theView (aView) + theView (aView), + indent (aindent) { }; const std::string get_text(int col) const; State get_state() const { return State::nothing; } + int get_indent() const; ActionList *get_actions(int col_num) const; int do_action(int col, int action_id); private: packagemeta & pkg; PickView & theView; + int indent; }; #endif /* SETUP_PICKPACKAGELINE_H */ diff --git a/PickView.cc b/PickView.cc index 6e1af0c..c7ff054 100644 --- a/PickView.cc +++ b/PickView.cc @@ -87,7 +87,7 @@ PickView::setViewMode (views mode) } } - listview->setContents(&contents); + listview->setContents(&contents, view_mode == PickView::views::Category); } PickView::views @@ -148,12 +148,12 @@ PickView::setObsolete (bool doit) } void -PickView::insert_pkg (packagemeta & pkg) +PickView::insert_pkg (packagemeta & pkg, int indent) { if (!showObsolete && isObsolete (pkg.categories)) return; - contents.push_back(new PickPackageLine(*this, pkg)); + contents.push_back(new PickPackageLine(*this, pkg, indent)); } void @@ -197,7 +197,7 @@ PickView::insert_category (CategoryTree *cat_tree) return; // insert line for the category - contents.push_back(new PickCategoryLine(*this, cat_tree, packageCount)); + contents.push_back(new PickCategoryLine(*this, cat_tree, packageCount, isAll ? 0 : 1)); // if not collapsed if (!cat_tree->collapsed()) @@ -212,7 +212,7 @@ PickView::insert_category (CategoryTree *cat_tree) || (*i && StrStrI ((*i)->name.c_str (), packageFilterString.c_str ()))) { - insert_pkg(**i); + insert_pkg(**i, 2); } } } diff --git a/PickView.h b/PickView.h index fc9216e..3a6c602 100644 --- a/PickView.h +++ b/PickView.h @@ -67,7 +67,7 @@ private: CategoryTree *cat_tree_root; Window *parent; - void insert_pkg (packagemeta &); + void insert_pkg (packagemeta &, int indent = 0); void insert_category (CategoryTree *); }; -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 13/13] Add ldesc tooltips to sdesc column of listview 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (9 preceding siblings ...) 2018-08-05 22:11 ` [PATCH setup 10/13] Use indents in category view Jon Turney @ 2018-08-05 22:12 ` Jon Turney 2018-08-05 22:12 ` [PATCH setup 12/13] Restore packagemeta::LDesc() Jon Turney ` (3 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:12 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney Listview's built-in tooltip support doesn't support subitems, so we have to build our own v2: Use string cache --- ListView.cc | 94 +++++++++++++++++++++++++++++++++++++++++++++ ListView.h | 4 ++ PickCategoryLine.cc | 6 +++ PickCategoryLine.h | 1 + PickPackageLine.cc | 11 ++++++ PickPackageLine.h | 1 + 6 files changed, 117 insertions(+) diff --git a/ListView.cc b/ListView.cc index b0351cd..e287270 100644 --- a/ListView.cc +++ b/ListView.cc @@ -67,6 +67,34 @@ ListView::init(HWND parent, int id, HeaderList headers) // create an empty imagelist, used to reset the indent hEmptyImgList = ImageList_Create(1, 1, ILC_COLOR32, 2, 0); + + // LVS_EX_INFOTIP/LVN_GETINFOTIP doesn't work for subitems, so we have to do + // our own tooltip handling + hWndTip = CreateWindowEx (0, + (LPCTSTR) TOOLTIPS_CLASS, + NULL, + WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + hWndParent, + (HMENU) 0, + GetModuleHandle(NULL), + NULL); + // must be topmost so that tooltips will display on top + SetWindowPos(hWndTip, HWND_TOPMOST,0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + + TOOLINFO ti; + memset ((void *)&ti, 0, sizeof(ti)); + ti.cbSize = sizeof(ti); + ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + ti.hwnd = hWndParent; + ti.uId = (UINT_PTR)hWndListView; + ti.lpszText = LPSTR_TEXTCALLBACK; // use TTN_GETDISPINFO + SendMessage(hWndTip, TTM_ADDTOOL, 0, (LPARAM)&ti); + + // match long delay for tooltip to disappear used elsewhere (30s) + SendMessage(hWndTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, (LPARAM) MAKELONG (30000, 0)); + // match tip width used elsewhere + SendMessage(hWndTip, TTM_SETMAXTIPWIDTH, 0, 450); } void @@ -429,6 +457,72 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult) } } } + break; + + case LVN_HOTTRACK: + { + NMLISTVIEW *pNmListView = (NMLISTVIEW *)pNmHdr; + int iRow = pNmListView->iItem; + int iCol = pNmListView->iSubItem; +#if DEBUG + Log (LOG_BABBLE) << "LVN_HOTTRACK " << iRow << " " << iCol << endLog; +#endif + if (iRow < 0) + return true; + + // if we've tracked off to a different cell + if ((iRow != iRow_track) || (iCol != iCol_track)) + { +#if DEBUG + Log (LOG_BABBLE) << "LVN_HOTTRACK changed cell" << endLog; +#endif + + // if the tooltip for previous cell is displayed, remove it + // restart the tooltip AUTOPOP timer for this cell + SendMessage(hWndTip, TTM_ACTIVATE, FALSE, 0); + SendMessage(hWndTip, TTM_ACTIVATE, TRUE, 0); + + iRow_track = iRow; + iCol_track = iCol; + } + + return true; + } + break; + + case TTN_GETDISPINFO: + { + // convert mouse position to item/subitem + LVHITTESTINFO lvHitTestInfo; + lvHitTestInfo.flags = LVHT_ONITEM; + GetCursorPos(&lvHitTestInfo.pt); + ::ScreenToClient(hWndListView, &lvHitTestInfo.pt); + ListView_SubItemHitTest(hWndListView, &lvHitTestInfo); + + int iRow = lvHitTestInfo.iItem; + int iCol = lvHitTestInfo.iSubItem; + if (iRow < 0) + return false; + +#if DEBUG + Log (LOG_BABBLE) << "TTN_GETDISPINFO " << iRow << " " << iCol << endLog; +#endif + + // get the tooltip text for that item/subitem + static StringCache tooltip; + tooltip = ""; + if (contents) + tooltip = (*contents)[iRow]->get_tooltip(iCol); + + // set the tooltip text + NMTTDISPINFO *pNmTTDispInfo = (NMTTDISPINFO *)pNmHdr; + pNmTTDispInfo->lpszText = tooltip; + pNmTTDispInfo->hinst = NULL; + pNmTTDispInfo->uFlags = 0; + + return true; + } + break; } // We don't care. diff --git a/ListView.h b/ListView.h index 34d6267..b4fd1fd 100644 --- a/ListView.h +++ b/ListView.h @@ -33,6 +33,7 @@ class ListViewLine virtual ~ListViewLine() {}; virtual const std::string get_text(int col) const = 0; virtual State get_state() const = 0; + virtual const std::string get_tooltip(int col) const = 0; virtual int get_indent() const = 0; virtual ActionList *get_actions(int col) const = 0; virtual int do_action(int col, int id) = 0; @@ -76,6 +77,7 @@ class ListView private: HWND hWndParent; HWND hWndListView; + HWND hWndTip; HDC dc; HIMAGELIST hImgList; HIMAGELIST hEmptyImgList; @@ -83,6 +85,8 @@ class ListView ListViewContents *contents; HeaderList headers; const char *empty_list_text; + int iRow_track; + int iCol_track; void initColumns(HeaderList hl); void empty(void); diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc index e206aee..1dbecf2 100644 --- a/PickCategoryLine.cc +++ b/PickCategoryLine.cc @@ -82,3 +82,9 @@ PickCategoryLine::get_indent() const { return indent; } + +const std::string +PickCategoryLine::get_tooltip(int col_num) const +{ + return ""; +} diff --git a/PickCategoryLine.h b/PickCategoryLine.h index a5daa8c..9c5e996 100644 --- a/PickCategoryLine.h +++ b/PickCategoryLine.h @@ -36,6 +36,7 @@ public: const std::string get_text(int col) const; State get_state() const; + const std::string get_tooltip(int col) const; int get_indent() const; ActionList *get_actions(int col) const; int do_action(int col, int action_id); diff --git a/PickPackageLine.cc b/PickPackageLine.cc index a602c90..133720b 100644 --- a/PickPackageLine.cc +++ b/PickPackageLine.cc @@ -99,6 +99,17 @@ PickPackageLine::get_text(int col_num) const return "unknown"; } +const std::string +PickPackageLine::get_tooltip(int col_num) const +{ + if (col_num == pkg_col) + { + return pkg.LDesc(); + } + + return ""; +} + int PickPackageLine::do_action(int col_num, int action_id) { diff --git a/PickPackageLine.h b/PickPackageLine.h index 53c389b..12c7636 100644 --- a/PickPackageLine.h +++ b/PickPackageLine.h @@ -32,6 +32,7 @@ public: }; const std::string get_text(int col) const; State get_state() const { return State::nothing; } + const std::string get_tooltip(int col) const; int get_indent() const; ActionList *get_actions(int col_num) const; int do_action(int col, int action_id); -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 12/13] Restore packagemeta::LDesc() 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (10 preceding siblings ...) 2018-08-05 22:12 ` [PATCH setup 13/13] Add ldesc tooltips to sdesc column of listview Jon Turney @ 2018-08-05 22:12 ` Jon Turney 2018-08-05 22:12 ` [PATCH setup 11/13] Add LDesc() accessor method to SolvableVersion Jon Turney ` (2 subsequent siblings) 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:12 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney --- package_meta.cc | 15 +++++++++++++++ package_meta.h | 5 +++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/package_meta.cc b/package_meta.cc index 4f7d39a..85aaaf9 100644 --- a/package_meta.cc +++ b/package_meta.cc @@ -404,6 +404,21 @@ packagemeta::SDesc () const return std::string(); } +static bool +hasLDesc(packageversion const &pkg) +{ + return pkg.LDesc().size(); +} + +const std::string +packagemeta::LDesc () const +{ + set<packageversion>::iterator i = find_if (versions.begin(), versions.end(), hasLDesc); + if (i == versions.end()) + return std::string(); + return i->LDesc (); +}; + /* Return an appropriate caption given the current action. */ std::string packagemeta::action_caption () const diff --git a/package_meta.h b/package_meta.h index 8a42319..0eff8d0 100644 --- a/package_meta.h +++ b/package_meta.h @@ -108,9 +108,10 @@ public: bool isManuallyWanted() const; /* true if package was deleted on command-line. */ bool isManuallyDeleted() const; - /* SDesc is global in theory, across all package versions. - LDesc is not: it can be different per version */ + const std::string SDesc () const; + const std::string LDesc () const; + /* what categories does this package belong in. Note that if multiple versions * of a package disagree.... the first one read in will take precedence. */ -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH setup 11/13] Add LDesc() accessor method to SolvableVersion 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (11 preceding siblings ...) 2018-08-05 22:12 ` [PATCH setup 12/13] Restore packagemeta::LDesc() Jon Turney @ 2018-08-05 22:12 ` Jon Turney 2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown 2018-08-06 16:40 ` Achim Gratz 14 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-08-05 22:12 UTC (permalink / raw) To: cygwin-apps; +Cc: Jon Turney --- libsolv.cc | 14 ++++++++++++++ libsolv.h | 1 + 2 files changed, 15 insertions(+) diff --git a/libsolv.cc b/libsolv.cc index 955a1b2..ba54fc5 100644 --- a/libsolv.cc +++ b/libsolv.cc @@ -192,6 +192,20 @@ SolvableVersion::SDesc () const return sdesc; } +const std::string +SolvableVersion::LDesc () const +{ + if (!id) + return ""; + Solvable *solvable = pool_id2solvable(pool, id); + const char *ldesc = repo_lookup_str(solvable->repo, id, SOLVABLE_DESCRIPTION); + + if (!ldesc) + return ""; + + return ldesc; +} + const std::string SolvableVersion::sourcePackageName () const { diff --git a/libsolv.h b/libsolv.h index f394e65..2eb1f24 100644 --- a/libsolv.h +++ b/libsolv.h @@ -54,6 +54,7 @@ class SolvableVersion const std::string Name () const; const std::string SDesc () const; + const std::string LDesc () const; // In setup-speak, 'Canonical' version means 'e:v-r', the non-decomposed version const std::string Canonical_version () const; // Return the dependency list -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH setup 00/13] ListView Package Chooser 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (12 preceding siblings ...) 2018-08-05 22:12 ` [PATCH setup 11/13] Add LDesc() accessor method to SolvableVersion Jon Turney @ 2018-08-06 14:15 ` Ken Brown 2018-08-06 16:41 ` Achim Gratz 2018-08-06 19:19 ` Ken Brown 2018-08-06 16:40 ` Achim Gratz 14 siblings, 2 replies; 20+ messages in thread From: Ken Brown @ 2018-08-06 14:15 UTC (permalink / raw) To: cygwin-apps [-- Attachment #1: Type: text/plain, Size: 948 bytes --] On 8/5/2018 6:08 PM, Jon Turney wrote: > Drag setup into the 1990s, by replacing the custom-drawn package chooser > with a ListView common control. > > As well as removing a lot of Win32 GDI drawing, this also enables the > following improvements to be much more straightforward: > > * Use standard UI elements to choose an action to take on a package or > category, rather than the weird UX of clicking to cycle around a list of > options of undisclosed length. This is a huge improvement over the existing UX. > * Add tooltips (initially, the ldesc as a tooltip for sdesc) This is also a big improvement. It's nice to see ldesc finally being used. I have one minor fix (patch attached) and one suggestion: It would be good, if possible, for setup to remember the column widths if the user changes them, just as it now remembers the size and position of the chooser window. Thanks for doing this. It must have been a lot of work. Ken [-- Attachment #2: 0001-Ensure-that-an-installed-packageversion-has-an-ldesc.patch --] [-- Type: text/plain, Size: 960 bytes --] From bd16602c08e6edba7ef7f91b149a8e9a0fe45587 Mon Sep 17 00:00:00 2001 From: Ken Brown <kbrown@cornell.edu> Date: Mon, 6 Aug 2018 10:05:12 -0400 Subject: [PATCH] Ensure that an installed packageversion has an ldesc if possible In packagedb::read(), copy the ldesc from setup.ini to the packageversion read from installed.db. Otherwise, an installed package with only one version will not have an ldesc to use as a tooltip. --- package_db.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/package_db.cc b/package_db.cc index b74aafd..9aa3b8e 100644 --- a/package_db.cc +++ b/package_db.cc @@ -149,6 +149,7 @@ packagedb::read () if (pv) { data.sdesc = pv.SDesc(); + data.ldesc = pv.LDesc(); data.archive = *pv.source(); data.stability = pv.Stability(); data.spkg_id = pv.sourcePackage(); -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH setup 00/13] ListView Package Chooser 2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown @ 2018-08-06 16:41 ` Achim Gratz 2018-08-06 16:47 ` Achim Gratz 2018-08-06 19:19 ` Ken Brown 1 sibling, 1 reply; 20+ messages in thread From: Achim Gratz @ 2018-08-06 16:41 UTC (permalink / raw) To: cygwin-apps Ken Brown writes: > I have one minor fix (patch attached) and one suggestion: It would be > good, if possible, for setup to remember the column widths if the user > changes them, just as it now remembers the size and position of the > chooser window. The attached patch seems to do something else entirely. Regards, Achim. -- +<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+ SD adaptations for Waldorf Q V3.00R3 and Q+ V3.54R2: http://Synth.Stromeko.net/Downloads.html#WaldorfSDada ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH setup 00/13] ListView Package Chooser 2018-08-06 16:41 ` Achim Gratz @ 2018-08-06 16:47 ` Achim Gratz 0 siblings, 0 replies; 20+ messages in thread From: Achim Gratz @ 2018-08-06 16:47 UTC (permalink / raw) To: cygwin-apps Achim Gratz writes: > Ken Brown writes: >> I have one minor fix (patch attached) and one suggestion: It would be >> good, if possible, for setup to remember the column widths if the user >> changes them, just as it now remembers the size and position of the >> chooser window. > > The attached patch seems to do something else entirely. Apparently I can't read, please disregard that comment. Regards, Achim. -- +<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+ DIY Stuff: http://Synth.Stromeko.net/DIY.html ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH setup 00/13] ListView Package Chooser 2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown 2018-08-06 16:41 ` Achim Gratz @ 2018-08-06 19:19 ` Ken Brown 2018-10-13 18:46 ` Jon Turney 1 sibling, 1 reply; 20+ messages in thread From: Ken Brown @ 2018-08-06 19:19 UTC (permalink / raw) To: cygwin-apps [-- Attachment #1: Type: text/plain, Size: 114 bytes --] On 8/6/2018 10:15 AM, Ken Brown wrote: > I have one minor fix (patch attached) Slightly revised patch attached. [-- Attachment #2: 0001-Ensure-that-an-installed-packageversion-has-an-ldesc.patch --] [-- Type: text/plain, Size: 1486 bytes --] From 7a166b04523b0d3e4547e937cad429d31ef55e90 Mon Sep 17 00:00:00 2001 From: Ken Brown <kbrown@cornell.edu> Date: Mon, 6 Aug 2018 10:05:12 -0400 Subject: [PATCH] Ensure that an installed packageversion has an ldesc if possible In packagedb::read(), copy the ldesc from setup.ini to the packageversion read from installed.db. Otherwise, an installed package with only one version will not have an ldesc to use as a tooltip. v2: If the installed version is no longer available, copy the ldesc from the current version of the package. --- package_db.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package_db.cc b/package_db.cc index b74aafd..03874ec 100644 --- a/package_db.cc +++ b/package_db.cc @@ -149,6 +149,7 @@ packagedb::read () if (pv) { data.sdesc = pv.SDesc(); + data.ldesc = pv.LDesc(); data.archive = *pv.source(); data.stability = pv.Stability(); data.spkg_id = pv.sourcePackage(); @@ -172,6 +173,7 @@ packagedb::read () if (pkgm) { data.sdesc = pkgm->curr.SDesc(); + data.ldesc = pkgm->curr.LDesc(); if (pkgm->curr && version_compare (f.ver, pkgm->curr.Canonical_version()) > 0) data.stability = TRUST_TEST; -- 2.17.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH setup 00/13] ListView Package Chooser 2018-08-06 19:19 ` Ken Brown @ 2018-10-13 18:46 ` Jon Turney 0 siblings, 0 replies; 20+ messages in thread From: Jon Turney @ 2018-10-13 18:46 UTC (permalink / raw) To: cygwin-apps On 06/08/2018 20:19, Ken Brown wrote: > On 8/6/2018 10:15 AM, Ken Brown wrote: >> I have one minor fix (patch attached) > > Slightly revised patch attached. Thanks. I added that patch. On 06/08/2018 17:39, Achim Gratz wrote: > Jon Turney writes: > >> create mode 100755 tree-minus.svg > > Why not mode 644? My mistake. Fixed. I'll make a build with this added, so people have a chance to whine^H evaluate this change, without having to build it themselves. ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH setup 00/13] ListView Package Chooser 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney ` (13 preceding siblings ...) 2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown @ 2018-08-06 16:40 ` Achim Gratz 14 siblings, 0 replies; 20+ messages in thread From: Achim Gratz @ 2018-08-06 16:40 UTC (permalink / raw) To: cygwin-apps Jon Turney writes: > Drag setup into the 1990s, by replacing the custom-drawn package chooser > with a ListView common control. Oh, like the new millenium is already knocking on the door? How about the Y2K issues? ;-) Joking aside, I won't be able to test this until at least the next weekend, but that's all-around good news to hear. Thank you! > create mode 100755 tree-minus.svg Why not mode 644? Regards, Achim. -- +<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+ SD adaptation for Waldorf rackAttack V1.04R1: http://Synth.Stromeko.net/Downloads.html#WaldorfSDada ^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2018-10-13 18:46 UTC | newest] Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney 2018-08-05 22:09 ` [PATCH setup 01/13] Change packagemeta::_actions to an enum Jon Turney 2018-08-05 22:09 ` [PATCH setup 03/13] Drop 'using namespace std;' from PickView.cc Jon Turney 2018-08-05 22:09 ` [PATCH setup 02/13] Add OnNotify virtual function to class Window for WM_NOTIFY notifications Jon Turney 2018-08-05 22:10 ` [PATCH setup 08/13] Show the count of packages in a category Jon Turney 2018-08-05 22:10 ` [PATCH setup 09/13] Use an icon to represent expanded/collapsed state Jon Turney 2018-08-05 22:10 ` [PATCH setup 07/13] Custom draw popup menus in ListView control Jon Turney 2018-08-05 22:10 ` [PATCH setup 05/13] Custom draw checkboxes " Jon Turney 2018-08-05 22:10 ` [PATCH setup 04/13] Use a ListView common control rather than a hand-built grid Jon Turney 2018-08-05 22:10 ` [PATCH setup 06/13] Add methods for listing possible actions on, and applying one to, a package Jon Turney 2018-08-05 22:11 ` [PATCH setup 10/13] Use indents in category view Jon Turney 2018-08-05 22:12 ` [PATCH setup 13/13] Add ldesc tooltips to sdesc column of listview Jon Turney 2018-08-05 22:12 ` [PATCH setup 12/13] Restore packagemeta::LDesc() Jon Turney 2018-08-05 22:12 ` [PATCH setup 11/13] Add LDesc() accessor method to SolvableVersion Jon Turney 2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown 2018-08-06 16:41 ` Achim Gratz 2018-08-06 16:47 ` Achim Gratz 2018-08-06 19:19 ` Ken Brown 2018-10-13 18:46 ` Jon Turney 2018-08-06 16:40 ` Achim Gratz
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).