GnuCashew ~ Web Application compatible with GnuCash sql data files.
GCW
Loading...
Searching...
No Matches
AccountsTreeView.cpp
Go to the documentation of this file.
1#line 2 "src/Gui/AccountsTreeView.cpp"
2
3//#define EDIT_FORM_AS_POPUP_DIALOG
4#define EDIT_FORM_AS_SPLIT_PAGE
5
6
7#include <any>
8
9#include <Wt/Json/Array.h>
10#include <Wt/Json/Parser.h>
11#include <Wt/Json/Serializer.h>
12#include <Wt/WText.h>
13#include <Wt/WTreeTableNode.h>
14#include <Wt/WMessageBox.h>
15
16#include "../define.h"
17#include "AccountEditor.h"
18#include "AccountsTreeView.h"
19
21AccountsTreeView( const std::string & _selectedAccountGuid, int _columnCount )
22: m_columnCount( _columnCount )
23{
24 init();
25
27
28} // endAccountsTreeView( const std::string & _selectedAccountGuid, int _columnCount )
29
31AccountsTreeView( int _columnCount )
32: m_columnCount( _columnCount )
33{
34 init();
35
36} // endAccountsTreeView( int _columnCount )
37
38auto
40init()-> void
41{
42 addStyleClass( "AccountsTreeView" );
43
44 m_gridLayout = setLayout( std::make_unique< Wt::WGridLayout >() );
45// lw-> setSpacing( 0 );
46
47 m_view = m_gridLayout-> addWidget( std::make_unique< Wt::WTreeView >(), 0, 0 );
48
49 view()-> setSelectionBehavior( Wt::SelectionBehavior::Rows );
50 view()-> setSelectionMode( Wt::SelectionMode::Single );
51 view()-> setAlternatingRowColors( true );
52 view()-> doubleClicked().connect( this, &AccountsTreeView::on_doubleClicked );
53 view()-> setAttributeValue( "oncontextmenu","event.cancelBubble=true;event.returnValue=false;return false;" );
54 view()-> mouseWentUp().connect( this, &AccountsTreeView::on_showPopup_triggered );
55
56 std::vector< std::string > cols =
57 {
58 "accountcode" ,
59 "accountcolor" ,
60 "accountname" ,
61 "balance" ,
62 "balancelimit" ,
63 "balanceperiod" ,
64 "balanceusd" ,
65 "cleared" ,
66 "clearedusd" ,
67 "commodity" ,
68 "description" ,
69 "futureminimum" ,
70 "futureminimumusd" ,
71 "hidden" ,
72 "lastnum" ,
73 "lastreconciledate" ,
74 "notes" ,
75 "openingbalance" ,
76 "placeholder" ,
77 "present" ,
78 "presentusd" ,
79 "reconciled" ,
80 "reconciledusd" ,
81 "taxinfo" ,
82 "total" ,
83 "totalperiod" ,
84 "totalusd" ,
85 "type"
86 };
87
88 for( int i=0; i< m_columnCount; i++ )
89 m_columns.push_back( TR8( "gcw.AccountsTreeView.column." + cols.at(i) ) );
90
91 setModel();
92
93 loadConfig();
94
95 view()-> collapsed ().connect( this, &AccountsTreeView::saveConfig );
96 view()-> expanded ().connect( this, &AccountsTreeView::saveConfig );
97 view()-> selectionChanged ().connect( this, &AccountsTreeView::saveConfig );
98
99} // endinit()-> void
100
101auto
103on_showPopup_triggered( const Wt::WModelIndex & _index, const Wt::WMouseEvent & _event )-> void
104{
105 if( _event.button() == Wt::MouseButton::Right )
106 {
107 std::cout << __FILE__ << ":" << __LINE__ << " right-click pop-up menu" << std::endl;
108
109#ifdef NEVER
110 /*
111 ** Set up the items in the pop-up menu
112 **
113 */
114 while( m_popup.count() )
115 m_popup.removeItem( m_popup.itemAt(0) );
116
117 m_popup.addItem("icons/folder_new.gif", "Create a New Folder", this, &WEB::FolderTableView::on_pbAddFolder_triggered );
118 m_popup.addItem("Rename", this, &WEB::FolderTableView::on_pbRenameItem_triggered ); //->setCheckable(true);
119
120 std::string fileName = Wt::asString(m_tableView.model()-> data( index.row(), 0 )).toUTF8();
121 COUT_( fileName );
122
123 if( isFolder(fileName) )
124 m_popup.addItem("Delete this Folder", this, &WEB::FolderTableView::on_pbDeleteItem_triggered );
125 else
126 m_popup.addItem("Delete this File", this, &WEB::FolderTableView::on_pbDeleteItem_triggered );
127
128 m_popup.addSeparator();
129 m_popup.addItem("Upload a file", this, &WEB::FolderTableView::on_pbUploadFile_triggered );
130 m_popup.addItem("Refresh", this, &WEB::FolderTableView::refresh );
131 m_popup.addSeparator();
132 m_popup.addItem("Properties", this, &WEB::FolderTableView::on_pbProperties_triggered );
133
134 // Select the item, if it was not yet selected.
135 if( !m_tableView.isSelected(index) )
136 {
137 m_tableView.clearSelection();
138 m_tableView.select(index);
139 }
140
141 if( m_popup.isHidden() )
142 {
143 m_popup.popup(event);
144 }
145 else
146 {
147 m_popup.hide();
148 }
149
150#endif
151
152 }
153
154} // endon_showPopup_triggered( Wt::WModelIndex _index, Wt::WMouseEvent _event )
155
156
157/*!
158** \return GUID String
159*/
160std::string
162selectedAccount() const
163{
164 std::string retVal;
165
166 /*
167 ** The tree-view should have only one selection. Grab
168 ** its index and get to the User data that carries the
169 ** GUID of the selected item.
170 **
171 */
172 auto selected = view()-> selectedIndexes();
173 if( selected.size() == 1 )
174 retVal =
176 (
177 model()-> data( *selected.begin(),
179 )
180 ).toUTF8();
181
182 return retVal;
183
184} // endstd::string GCW::Gui::AccountsTreeView::selectedAccount()
185
186void
188editAccount( const std::string & _accountGuid )
189{
190 if( _accountGuid == "" )
191 return;
192
193#ifdef EDIT_FORM_AS_POPUP_DIALOG
194 GCW::Gui::AccountEditorDialog dialog( "Edit Account" );
195 dialog.exec();
196#endif
197
198#ifdef EDIT_FORM_AS_SPLIT_PAGE
199
200 /*
201 **
202 **
203 */
204 if( m_editAccountWidget )
205 {
206// m_gridLayout-> removeWidget( m_editWidget );
207// m_editWidget = nullptr;
208 Wt::WMessageBox::show( "AccountsTree", "Please close the account you are editing", Wt::StandardButton::Ok );
209 return;
210 }
211
212 /*
213 ** Split the page to open/edit this item
214 **
215 */
216 m_editAccountWidget = m_gridLayout-> addWidget( std::make_unique< GCW::Gui::AccountEditor >(), 0, 1 );
217 m_gridLayout-> setColumnResizable( 0, true, "25%" );
218
219 m_editAccountWidget->
220 save().connect( [=]()
221 {
222// refreshViews();
223
224 if( m_editAccountWidget-> isDirty() )
225 m_editAccountWidget-> saveData( _accountGuid );
226
227 m_gridLayout-> removeWidget( m_editAccountWidget );
228 m_editAccountWidget = nullptr;
229 });
230
231 m_editAccountWidget->
232 cancel().connect( [=]()
233 {
234// refreshViews();
235
236 auto _zapWidget = [&]()
237 {
238 m_gridLayout-> removeWidget( m_editAccountWidget );
239 m_editAccountWidget = nullptr;
240 };
241
242 /*
243 ** if the form is dirty, stop and ask!
244 */
245 if( m_editAccountWidget-> isDirty() )
246 {
247 /*
248 ** ask if they are sure they want to cancel. If the answer is 'no',
249 ** then we just return here.
250 */
251 if( Wt::WMessageBox::show( "save", TR("gcw.cancelChanges"), Wt::StandardButton::Yes | Wt::StandardButton::No ) == Wt::StandardButton::No )
252 return;
253
254 /*
255 ** if they say 'yes, cancel' then we just zap it here
256 */
257 else
258 _zapWidget();
259
260 }
261
262 /*
263 ** not dirty, zap the form
264 */
265 else
266 _zapWidget();
267
268 });
269
270#endif
271
272} // endvoid GCW::Gui::AccountsTreeView::editAccount( const std::string & _accountGuid )
273
274
275void
278{
279 editAccount( selectedAccount() );
280
281} // endvoid GCW::Gui::AccountsTreeView::editAccount( const std::string & _accountGuid )
282
283
284void
286setModel()
287{
288 m_model = std::make_shared< Model >();
289
290 m_model-> load( m_columnCount );
291
292 view()-> setModel( m_model );
293
294 view()-> sortByColumn( 0, Wt::SortOrder::Ascending );
295
296} // endvoid GCW::Gui::AccountsTreeView::setModel()
297
301{
303
304 if( GCW::app()-> gnucashew_session().hasGnuCashewExtensions() )
305 {
306 Wt::Dbo::Transaction t( GCW::app()-> gnucashew_session() );
307
308 retVal = GCW::Dbo::Vars::get( "config", "AccountsTreeView" );
309 }
310
311 return retVal;
312
313} // endconfigItem()
314
315
316void
319{
320 if( !GCW::app()-> gnucashew_session().hasGnuCashewExtensions() )
321 return;
322
323 Wt::Dbo::Transaction t( GCW::app()-> gnucashew_session() );
324 configItem().modify()-> setVarField( Wt::Json::serialize( toJson() ) );
325
326} // endsaveConfig()
327
328void
331{
332 if( !GCW::app()-> gnucashew_session().hasGnuCashewExtensions() )
333 return;
334
335 Wt::Json::Object jobj;
336 try {
337 Wt::Json::parse( configItem()-> varField(), jobj );
338 }
339 catch( std::exception & e )
340 {
341// std::cout << __FILE__ << ":" << __LINE__ << " " << e.what() << std::endl;
342 }
343
344 fromJson( jobj );
345
346} // endloadConfig()
347
348/*
349** This will iterate a single a WTreeView and fill
350** a vector of every node which is the .last-expanded.
351** node of every branch.
352**
353*/
354bool
356iterate( Wt::Json::Array & _jary, Wt::WModelIndex _parent ) const
357{
358 /*
359 ** If this _parent node is not expanded, then we're basically done.
360 **
361 */
362 if( !view()-> isExpanded( _parent ) )
363 return false;
364
365 /*
366 ** This _parent node is expanded, so loop through all the
367 ** child nodes checking if any of them are expanded.
368 **
369 */
370 bool expanded = false;
371 for( int row=0; row< view()-> model()-> rowCount( _parent ); row++ )
372 expanded |= iterate( _jary, view()-> model()-> index( row, 0, _parent ) );
373
374 /*
375 ** None of the child nodes are expanded, so record this _parent
376 ** node as the 'last' node in the tree
377 **
378 */
379 if( !expanded )
380 {
381 /*
382 ** The true root node is not associated with an actual account,
383 ** it is simply the invisibleRoot of the tree itself, and only
384 ** contains the set of first-root nodes that actually get
385 ** displayed. So, there is no User data in this one, don't record it.
386 **
387 */
388 auto accountGuid = Wt::asString( _parent.data( Wt::ItemDataRole::User ) );
389 if( accountGuid != "" )
390 _jary.push_back( accountGuid );
391
392 } // endif( !expanded )
393
394 /*
395 ** Something is expanded. Either we are expanded, or
396 ** one of the sub-nodes are expanded, so return that 'someone' is
397 ** expanded.
398 **
399 */
400 return true;
401
402} // endvoid iterate( Wt::WModelIndex _index ) const
403
404
407toJson() const
408{
409 Wt::Json::Object jobj;
410
411 jobj["selected"] = Wt::WString( selectedAccount() );
412
413 for( int col=0; col< 7; col++ )
414 jobj[ Wt::WString("cw{1}").arg( col ).toUTF8() ] = Wt::WString( view()-> columnWidth( col ).cssText() );
415
416 Wt::Json::Array jary;
417 iterate( jary );
418 jobj["expanded"] = jary;
419
420 return jobj;
421
422}
423
424bool
426expandNode( const std::string & _accountGuid, Wt::WModelIndex _parent )
427{
428 bool retVal = false;
429
430 /*
431 ** Loop through all the children in this node
432 **
433 */
434// Wt::Dbo::Transaction t( GCW::app()-> gnucashew_session() );
435 for( int row=0; row< view()-> model()-> rowCount( _parent ); row++ )
436 {
437 /*
438 ** get the index for this child of this node
439 */
440 auto child = view()-> model()-> index( row, 0, _parent );
441
442 /*
443 ** get the guid of this child
444 */
445 auto nodeGuid = Wt::asString( child.data( Wt::ItemDataRole::User ) ).toUTF8();
446
447 /*
448 ** if this node matches the account, expand it and set the
449 ** return to 'found'
450 **
451 */
452 if( nodeGuid == _accountGuid )
453 {
454 view()-> expand( child );
455 retVal |= true;
456 }
457
458 /*
459 ** remember if any of the sub-nodes get expanded.
460 **
461 */
462 retVal |= expandNode( _accountGuid, child );
463
464 } // endfor( int row=0; row< view()-> model()-> rowCount( _parent ); row++ )
465
466 /*
467 ** Either this node was expanded, or any one of
468 ** the child nodes was expanded, so therefore we
469 ** need to also expand this node.
470 **
471 */
472 if( retVal )
473 view()-> expand( _parent );
474
475 /*
476 ** None of the nodes here got expanded
477 **
478 */
479 return retVal;
480
481} // endexpandNode( const std::string & _accountGuid, Wt::WModelIndex _parent )
482
483bool
486{
487 auto jary = _jobj.get("expanded").orIfNull( Wt::Json::Array() );
488
489 for( auto value : jary )
490 expandNode( value.orIfNull( "" ) );
491
492 return true;
493
494} // endexpandNodes()
495
496/*!
497** \brief Find Index by AccountGuid
498**
499** This will loop through the tree and locate a specific
500** index by it's accountGuid value.
501**
502*/
505findIndex( const std::string & _accountGuid, Wt::WModelIndex _parentIndex )
506{
507 /*
508 ** If this is the index we are looking for, then just return it.
509 **
510 */
511 if( Wt::asString( _parentIndex.data( Wt::ItemDataRole::User ) ) == _accountGuid )
512 return _parentIndex;
513
514 /*
515 ** Loop through all the child nodes checking them for
516 ** matches
517 **
518 */
519 for( int row=0; row< view()-> model()-> rowCount( _parentIndex ); row++ )
520 {
521 auto childIndex = findIndex( _accountGuid, view()-> model()-> index( row, 0, _parentIndex ) );
522
523 /*
524 ** If we get back a valid index, then we have what we
525 ** need and can just return it.
526 **
527 */
528 if( childIndex.isValid() )
529 return childIndex;
530
531 } // endfor( int row=0; row< view()-> model()-> rowCount( _parentIndex ); row++ )
532
533 /*
534 ** Return an invalid index indicating not-found.
535 **
536 */
537 return Wt::WModelIndex();
538
539} // endfindIndex( const std::string & _accountGuid, Wt::WModelIndex _parentIndex )
540
541bool
543setSelected( const std::string & _accountGuid )
544{
545 auto index = findIndex( _accountGuid );
546
547 view()-> select ( index );
548 view()-> scrollTo ( index, Wt::ScrollHint::PositionAtCenter );
549
550 return true;
551
552} // endexpandNodes()
553
554bool
556fromJson( Wt::Json::Object & _jobj )
557{
558 expandTreeNodes( _jobj );
559
560 setSelected( _jobj.get("selected").orIfNull( std::string() ) );
561
562 return true;
563
564} // endfromJson( const Wt::Json::Object & _jobj )
565
566void
568test()
569{
570 std::cout << __FILE__ << ":" << __LINE__ << " " << std::endl;
571
572 std::cout << __FILE__ << ":" << __LINE__ << " " << Wt::Json::serialize( toJson() ) << std::endl;
573
574} // endvoid GCW::Gui::AccountsTreeView::test()
575
576
577void
579on_doubleClicked( const Wt::WModelIndex & index, const Wt::WMouseEvent & event )
580{
581#ifdef NEVER
582 std::cout << std::endl << std::endl << __FILE__ << ":" << __LINE__
583 << " " << "on_doubleClicked:"
584 << " " << index.row()
585 << " " << Wt::asString( m_model-> data( index, Wt::ItemDataRole::User ) )
586 << std::endl << std::endl
587 << std::endl;
588#endif
589
590 /*
591 ** The 'model->data::User' element should return the guid of the account
592 **
593 */
594 m_doubleClicked.emit( Wt::asString( m_model-> data( index, Wt::ItemDataRole::User ) ).toUTF8() );
595
596} // endvoid GCW::Gui::AccountsTreeView::on_doubleClicked( const Wt::WModelIndex & index, const Wt::WMouseEvent & event )
597
598
static bool iterate(Wt::Json::Array &_jary, Wt::WModelIndex _parent)
Definition Core.cpp:105
AccountsTreeView(int _columnCount)
auto toJson() const -> Wt::Json::Object
auto on_doubleClicked(const Wt::WModelIndex &index, const Wt::WMouseEvent &event) -> void
auto expandNode(const std::string &_accountGuid, Wt::WModelIndex _parent=Wt::WModelIndex()) -> bool
auto configItem() -> GCW::Dbo::Vars::Item::Ptr
Config Item.
auto expandTreeNodes(Wt::Json::Object &_jobj) -> bool
auto findIndex(const std::string &_accountGuid, Wt::WModelIndex _parentIndex=Wt::WModelIndex()) -> Wt::WModelIndex
Find Index by AccountGuid.
auto on_showPopup_triggered(const Wt::WModelIndex &_index, const Wt::WMouseEvent &_event) -> void
auto iterate(Wt::Json::Array &_jary, Wt::WModelIndex _parent=Wt::WModelIndex()) const -> bool
auto fromJson(Wt::Json::Object &_jobj) -> bool
auto setSelected(const std::string &_accountGuid) -> bool
auto selectedAccount() const -> std::string
auto editAccount(const std::string &_accountGuid) -> void
static constexpr const int User
const Value & get(const std::string &name) const
const WString & orIfNull(const WString &v) const
Widget * addNew(Args &&...args)
DialogCode exec(const WAnimation &animation=WAnimation())
int row() const
cpp17::any data(ItemDataRole role=ItemDataRole::Display) const
std::string toUTF8() const
WString & arg(const std::wstring &value)
#define TR8(X)
Definition define.h:18
#define TR(X)
Definition define.h:17
void parse(const std::string &input, Value &result, bool validateUTF8=true)
std::string serialize(const Object &obj, int indentation=1)
WString asString(const cpp17::any &v, const WString &formatString=WString())
auto get(const std::string &_keyValue, const std::string &_cfyValue="*", bool _add=true) -> GCW::Dbo::Vars::Item::Ptr
Definition Vars.cpp:16
App * app()
Definition App.cpp:75