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