/************************* * * * * * * * * * * * * *************************** Copyright (c) 1999-2005 Ryan Bobko ryan@ostrich-emulators.com 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ************************** * * * * * * * * * * * * **************************/ #ifdef HAVE_CONFIG_H #include "config.h" #else #define VERSION 3.2.3 #endif #include #include "qhaccwidget.h" #include "qhacc.h" #include "qhacctable.h" #include "copystore.h" #include "qhaccview.h" #include "qhaccutils.h" #include "qhaccgrwin.h" #include "qhaccrecwin.h" #include "guiconstants.h" #include "qhaccdialogs.h" #include "qhaccacctdlg.h" #include "qhaccprefdlg.h" #include "qhaccacctbox.h" #include "qhaccjrnlchsr.h" #include "qhaccconstants.h" #include "qhacclineedits.h" #include "qhaccsubsetwin.h" #include "qhaccwidget.moc" #include "pixmaps/graph.xpm" #include "pixmaps/report.xpm" #include "pixmaps/prefer.xpm" #include "pixmaps/subset.xpm" #include "pixmaps/recacct.xpm" #include "pixmaps/newacct.xpm" #include "pixmaps/editacct.xpm" #include "pixmaps/filesave.xpm" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QHaccWidget::QHaccWidget( QHacc * qhacc, QWidget * p, const char * n ) : QMainWindow( p, n, WDestructiveClose ){ engine=qhacc; QHaccDateEdit::setDateFormat( ( QHaccDateEdit::DateFormat ) engine->getIP( "DATEFORMAT" ) ); QHaccDateEdit::setDateSeparator( engine->getSP( "DATESEPARATOR" ) ); // build the toolbar before the journal bar, so it shows up first tools=new QToolBar( this, "toolbar" ); // the account chooser box is dockable cdock=new QDockWindow( QDockWindow::InDock, this ); chooser=new QHaccAccountBox( qhacc, cdock ); cdock->setResizeEnabled( true ); cdock->setHorizontalStretchable( true ); cdock->setVerticalStretchable( true ); cdock->setWidget( chooser ); addDockWindow( cdock, Qt::Left ); // the journal bar is dockable jdock=new QDockWindow( QDockWindow::InDock, this ); journaler=new QHaccJournalChooser( qhacc, jdock ); jdock->setResizeEnabled( true ); jdock->setHorizontalStretchable( true ); jdock->setVerticalStretchable( true ); jdock->setWidget( journaler ); addDockWindow( jdock, Qt::Top ); // put something in the middle of this window viewer=new QHaccView( qhacc, this ); setCentralWidget( viewer ); connectView( viewer ); connect( engine, SIGNAL( updatedA( const Account&, const Account& ) ), this, SLOT( updateAccount( const Account&, const Account& ) ) ); connect( engine, SIGNAL( changedP( const QString&, QFont ) ), SLOT( changePref( const QString&, QFont ) ) ); connect( engine, SIGNAL( changedP( const QString&, QString ) ), SLOT( changePref( const QString&, QString ) ) ); connect( engine, SIGNAL( changedP( const QString&, bool ) ), SLOT( changePref( const QString&, bool ) ) ); connect( engine, SIGNAL( needSave( bool ) ), SLOT( setSaveFlag( bool ) ) ); connect( engine, SIGNAL( overBudget( const Account&, int ) ), this, SLOT( OBAccount( const Account&, int ) ) ); connect( chooser, SIGNAL( changedAccount( const Account& ) ), this, SLOT( setAccount( const Account& ) ) ); connect( journaler, SIGNAL( changedJournal( const Journal& ) ), this, SLOT( setJournal( const Journal& ) ) ); connect( chooser, SIGNAL( changedAccount( const Account& ) ), viewer, SLOT( setAccount( const Account& ) ) ); connect( chooser, SIGNAL( needReportOf( const Account& ) ), this, SLOT( report( const Account& ) ) ); connect( chooser, SIGNAL( needGraphOf( const Account& ) ), this, SLOT( graph( const Account& ) ) ); connect( chooser, SIGNAL( needSubsetOf( const Account& ) ), this, SLOT( subset( const Account& ) ) ); connect( chooser, SIGNAL( needRecWinOf( const Account& ) ), this, SLOT( reconcile( const Account& ) ) ); connect( journaler, SIGNAL( changedJournal( const Journal& ) ), viewer, SLOT( setJournal( const Journal& ) ) ); // build the status bar QStatusBar * status=statusBar(); abalance=new QLabel( status ); saveflag=new QLabel( status ); saveflag->setAlignment( AlignCenter ); tcount=new QLabel( status ); msgs=new QLabel( status ); abalance->setAlignment( AlignRight ); status->addWidget( msgs, 4, true ); status->addWidget( saveflag, 2, true ); status->addWidget( tcount, 8, true ); status->addWidget( abalance, 5, true ); // build the menubar QPopupMenu * file=new QPopupMenu( this ); QPopupMenu * help=new QPopupMenu( this ); QPopupMenu * prefm=new QPopupMenu( this ); QPopupMenu * acct=new QPopupMenu( this ); QPopupMenu * ledg=new QPopupMenu( this ); QPopupMenu * graphs=new QPopupMenu( this ); QPopupMenu * ntra=new QPopupMenu( this ); QMenuBar * menu=menuBar(); menu->insertItem( tr( "&File" ), file ); menu->insertItem( tr( "&Account" ), acct ); menu->insertItem( tr( "&Journal" ), ledg ); menu->insertItem( tr( "&Transaction" ), ntra ); menu->insertItem( tr( "&Preferences" ), prefm ); menu->insertItem( tr( "&Graphs" ), graphs ); menu->insertSeparator(); menu->insertItem( tr( "&Help" ), help ); //QPixmap reconcile( recacct ); //QPixmap create( newacct ); //QPixmap edit( editacct ); //QPixmap prefer( prefers ); //QPixmap resorty( resorter ); //QPixmap subset( subsetter ); // these are the actions we'll perform QAction * save=new QAction( tr( "Save Now" ), QPixmap( filesave ), tr( "&Save Now" ), CTRL+Key_S, this ); connect( save, SIGNAL( activated() ), this, SLOT( ssave() ) ); QAction * home=new QAction( tr( "Set QHacc Home" ), QPixmap(), tr( "Q&Hacc Home..." ), CTRL+Key_H, this ); connect( home, SIGNAL( activated() ), this, SLOT( reload() ) ); QAction * ex=new QAction( tr( "Exit" ), QPixmap(), tr( "E&xit" ), CTRL+Key_T, this ); connect( ex, SIGNAL( activated() ), this, SLOT( saveAndQuit() ) ); QAction * quit=new QAction( tr( "Quit" ), QPixmap(), tr( "&Quit" ), CTRL+Key_Q, this ); //connect( quit, SIGNAL( activated() ), this, SLOT( close() ) ); connect( quit, SIGNAL( activated() ), this, SLOT( quit() ) ); save->addTo( file ); home->addTo( file ); ex->addTo( file ); quit->addTo( file ); //file->insertItem( tr( "&Save Now" ), this, SLOT( save() ), CTRL+Key_S ); //file->insertItem( tr( "Q&Hacc Home..." ), this, // SLOT( reload() ), CTRL+Key_H ); //file->insertItem( tr( "E&xit" ), this, SLOT( saveAndQuit() ), CTRL+Key_X ); //file->insertItem( tr( "&Quit" ), this, SLOT( close() ), CTRL+Key_Q ); help->insertItem( tr( "About QHacc" ), this, SLOT( about() ) ); help->insertItem( tr( "About Plugins" ), this, SLOT( plugins() ) ); help->insertSeparator(); help->insertItem( tr( "About QT" ), this, SLOT( aboutQt() ) ); QAction * cp=new QAction( tr( "Change Prefs" ), QPixmap( prefers ), tr( "Change &Prefs" ), CTRL+Key_F, this ); connect( cp, SIGNAL( activated() ), this, SLOT( changePrefs() ) ); //QAction * ss=new QAction( this ); //ss->setText( "Switch Sides" ); //ss->setMenuText( "&Switch Sides" ); //ss->setIconSet( QPixmap( resorter ) ); //connect( ss, SIGNAL( activated() ), widget, SLOT( switchSides() ) ); QAction * rb=new QAction( tr( "Show Reconciled Balance" ), QPixmap(), tr( "Show Reconci&led Balance" ), CTRL+Key_L, this, 0, true ); rb->setOn( engine->getBP( "SHOWRECBAL" ) ); connect( rb, SIGNAL( activated() ), this, SLOT( toggleRecBal() ) ); QAction * altc=new QAction( tr( "Use Alternate Currency" ), QPixmap(), tr( "Use Alternate C&urrency" ), CTRL+Key_U, this, 0, true ); altc->setOn( engine->getBP( "USEALTCURRENCY" ) ); connect( altc, SIGNAL( activated() ), this, SLOT( toggleCurrency() ) ); cp->addTo( prefm ); //ss->addTo( prefm ); rb->addTo( prefm ); altc->addTo( prefm ); QAction * rc=new QAction( tr( "Reconcile" ), QPixmap( recacct ), tr( "&Reconcile" ), CTRL+Key_R, this ); connect( rc, SIGNAL( activated() ), this, SLOT( reconcile() ) ); QAction * vs=new QAction( tr( "View Subset" ), QPixmap( subsetter ), tr( "View Su&bset" ), CTRL+Key_B, this ); connect( vs, SIGNAL( activated() ), this, SLOT( subset() ) ); QAction * cr=new QAction( tr( "Create Account" ), QPixmap( newacct ), tr( "&Create..." ), CTRL+Key_N, this ); connect( cr, SIGNAL( activated() ), this, SLOT( newAcct() ) ); QAction * dl=new QAction( tr( "Delete Account" ), QPixmap(), tr( "&Delete" ), CTRL+SHIFT+Key_X, this ); connect( dl, SIGNAL( activated() ), SLOT( removeOpened() ) ); QAction * ed=new QAction( tr( "Edit Account" ), QPixmap( editacct ), tr( "&Edit..." ), CTRL+Key_E, this ); connect( ed, SIGNAL( activated() ), this, SLOT( editAcct() ) ); rc->addTo( acct ); vs->addTo( acct ); cr->addTo( acct ); dl->addTo( acct ); ed->addTo( acct ); QAction * lc=new QAction( tr( "Create Journal" ), QPixmap(), tr( "&Create..." ), CTRL+ALT+Key_N, this ); connect( lc, SIGNAL( activated() ), this, SLOT( newLed() ) ); QAction * ll=new QAction( tr( "Delete Journal" ), QPixmap(), tr( "&Delete" ), CTRL+SHIFT+ALT+Key_X, this ); connect( ll, SIGNAL( activated() ), this, SLOT( remLed() ) ); QAction * ld=new QAction( tr( "Edit Journal" ), QPixmap(), tr( "&Edit..." ), CTRL+ALT+Key_E, this ); connect( ld, SIGNAL( activated() ), this, SLOT( editLed() ) ); lc->addTo( ledg ); ll->addTo( ledg ); ld->addTo( ledg ); QAction * nc=new QAction( tr( "Open Memorized Editor" ), QPixmap(), tr( "&Open Memorized Editor..." ), QKeySequence(), this ); connect( nc, SIGNAL( activated() ), this, SLOT( openNTrans() ) ); QAction * scd=new QAction( tr( "Scheduler" ), QPixmap(), tr( "&Open Scheduler..." ), QKeySequence(), this ); connect( scd, SIGNAL( activated() ), this, SLOT( openSched() ) ); nc->addTo( ntra ); scd->addTo( ntra ); QAction * gr=new QAction( tr( "Graph Account..." ), QPixmap( grapher ), tr( "&Graphs" ), CTRL+Key_G, this ); connect( gr, SIGNAL( activated() ), this, SLOT( graph() ) ); QAction * rp=new QAction( tr( "Report on Account..." ), QPixmap( reporter ), tr( "&Reports" ), CTRL+Key_Y, this ); connect( rp, SIGNAL( activated() ), this, SLOT( report() ) ); gr->addTo( graphs ); rp->addTo( graphs ); save->addTo( tools ); tools->addSeparator(); cr->addTo( tools ); rc->addTo( tools ); ed->addTo( tools ); tools->addSeparator(); gr->addTo( tools ); rp->addTo( tools ); tools->addSeparator(); cp->addTo( tools ); //ss->addTo( tools ); vs->addTo( tools ); // make changes based on preferences readPrefs( true ); } QHaccWidget::~QHaccWidget(){} void QHaccWidget::readPrefs( bool initialLoad ){ // now that everything's built, customize based on prefs //setCaption( engine->getSP( "TITLEBARNAME" ) ); autosave=engine->getBP( "AUTOSAVE" ); QApplication::setFont( engine->getWP( "FONT" ), true ); readdockloc( "TOOLBARLOC", tools ); readdockloc( "CHOOSERLOC", cdock ); readdockloc( "JOURNALLOC", jdock ); // if we don't have a pre-saved location, start with a reasonable size if( initialLoad ){ QString pref=engine->getSP( "LOCATION" ); if( pref.isEmpty() ) resize( 800, 600 ); else{ QString locs[2]; Utils::parser( pref, " ", 0, locs, 2 ); resize( locs[0].toInt(), locs[1].toInt() ); } } if( engine->getBP( "HIDEJOURNALS" ) ) journaler->hide(); else journaler->show(); viewer->readPrefs( initialLoad ); chooser->readPrefs( initialLoad ); journaler->readPrefs( initialLoad ); setAccount( chooser->getSelected() ); } void QHaccWidget::setAccount( const Account& acct ){ account=acct; updateAccount( acct, acct ); } void QHaccWidget::setJournal( const Journal& j ){ journal=j; updateAccount( account, account ); } void QHaccWidget::show(){ journaler->blockSignals( true ); QMainWindow::show(); journaler->blockSignals( false ); } void QHaccWidget::updateAccount( const Account&, const Account& newy ){ if( !account.isNull() && account[QC::AID]!=newy[QC::AID] ) return; if( journal.isNull() ) return; account=newy; setCaption( engine->getSP( "TITLEBARNAME" )+" - "+journal[QC::LNAME].gets()+ "/"+engine->getFNameOfA( account ) ); const MonCon& conv=engine->converter(); int bal=0; uint count=0; QString type, name, temp; if( !newy.isNull() ){ bal=conv.converti( newy[QC::ACBAL].gets(), Engine, Engine ); vector v( 1, TableSelect( QC::SACCTID, newy[QC::AID] ) ); engine->getWhere( SPLITS, TableGet( QC::SID ), v, count ); type=tr( GUIC::TYPENAMES[newy[QC::ATYPE].getu()] ); name=engine->getFNameOfA( newy ); } else name=QT_TR_NOOP( "No Account" ); QString s=tr( "Balance" ); if( QC::LIABILITY==newy[QC::ATYPE].getu() || ( QC::REVENUE==newy[QC::ATYPE].getu() && engine->getBP( "FLIPREVENUEBALANCES" ) ) ){ bal=0-bal; } if ( bal<0 ) { s.append ( "-" ); bal=0-bal; } s.append( " " ); s.append( conv.symbol() ); s.append( conv.convert( bal, Engine, Preference ) ); if( engine->getBP( "SHOWRECBAL" ) ){ int rbal=conv.converti( newy[QC::ARBAL].gets(), Engine, Engine ); s.append( " / " ); if( QC::LIABILITY==newy[QC::ATYPE].getu() || ( QC::REVENUE==newy[QC::ATYPE].getu() && engine->getBP( "FLIPREVENUEBALANCES" ) ) ){ rbal=0-rbal; } if ( rbal<0 ) { s.append ( "-" ); rbal=0-rbal; } s.append( conv.symbol() ); s.append( conv.convert( rbal, Engine, Preference ) ); } abalance->setText( s ); // update the number of transactions // every time you update the balance tcount->setText( tr( "%1: %2 %3 transactions" ).arg( name ).arg( temp.setNum( count ) ).arg( type ) ); } void QHaccWidget::OBAccount( const Account& acct, int obBal ){ const MonCon& conv=engine->converter(); QMessageBox::information( this, tr( "Account out of Budget" ), tr( "%1 has exceeded its budget by %2" ).arg( engine->getFNameOfA( acct ) ).arg( conv.symbol()+conv.convert( obBal ) ) ); } void QHaccWidget::aboutQt(){ QMessageBox::aboutQt( this, tr( "About QT" ) );} void QHaccWidget::about(){ QString vline; vline.sprintf( tr( "The Q Home Accountant v%s\nWritten by: Ryan Bobko\nryan@ostrich-emulators.com\nwww.ostrich-emulators.com" ), VERSION ); QMessageBox::about( this, tr( "About QHacc" ), vline ); } void QHaccWidget::plugins(){ QString output; const char * titles[]={ QT_TR_NOOP( "Database" ), QT_TR_NOOP( "Import" ), QT_TR_NOOP( "Export" ) }; const int NT=3; const int types[]={ QHacc::PIDATABASE, QHacc::PIIMPORTER, QHacc::PIEXPORTER }; for( int p=0; p pis=engine->getPluginInfo( types[p], &curr ); output.append( "\n" ).append( tr( titles[p] ) ).append( ":" ); int i=0; for ( vector::iterator it=pis.begin(); it!=pis.end(); it++ ){ output+="\n "; if( i++==curr ) output+="*"; else output+=" "; output+=QString( "%1 (%2): %3" ).arg( it->stub() ).arg( it->descr() ).arg( it->fname() ); } } QMessageBox::information( this, tr( "QHacc Plugins" ), output ); } void QHaccWidget::changePref( const QString& s, bool b ){ if( s=="SHOWRECBAL" || s=="USEALTCURRENCY" || "FLIPREVENUEBALANCES"==s ){ const Account& a=chooser->getSelected(); updateAccount( a, a ); } else if( s=="AUTOSAVE" ){ autosave=b; if( autosave ) save(); } else if( s=="HIDEJOURNALS" ){ if( b ) journaler->hide(); else journaler->show(); } } void QHaccWidget::changePref( const QString&, QFont f ){ setFont( f ); QApplication::setFont( f, true ); } void QHaccWidget::changePref( const QString& s, QString v ){ if( s=="TITLEBARNAME" ){ setCaption( v+" - "+journal[QC::LNAME].gets()+ "/"+engine->getFNameOfA( account ) ); } else if( s=="CSYMBOL" || s=="ALTCSYMBOL" || s=="CURRENCYSEPARATOR" ){ const Account& a=chooser->getSelected(); updateAccount( a, a ); } } void QHaccWidget::changePrefs(){ ( new PrefsDlg( engine, 0, this ) )->show(); } void QHaccWidget::quit(){ qApp->quit(); } void QHaccWidget::saveAndQuit(){ hide(); save(); quit(); } void QHaccWidget::ssave(){ save(); } bool QHaccWidget::save(){ engine->setSP( "LOCATION", QString::number( width() )+" " +QString::number( height() ) ); savedockloc( "TOOLBARLOC", tools ); savedockloc( "CHOOSERLOC", cdock ); savedockloc( "JOURNALLOC", jdock ); viewer->save(); chooser->save(); journaler->save(); QString temp; if( !engine->save( temp ) ) msgs->setText( temp ); else msgs->setText( "" ); return true; } void QHaccWidget::savedockloc( const QString& pref, QDockWindow * dw ){ Dock tbd; int tbl; bool nl; int extra; getLocation( dw, tbd, tbl, nl, extra ); QString val, temp; val=temp.setNum( ( int )tbd )+" "+temp.setNum( tbl )+" "; val+=( nl ? "Y " : "N " )+temp.setNum( extra ); engine->setSP( pref, val ); } void QHaccWidget::readdockloc( const QString& pref, QDockWindow * dw ){ QString val=engine->getSP( pref ); QString strs[4]; Utils::parser( val, " ", 0, strs, 4 ); Dock tbd=( Dock )( strs[0].toInt() ); int tbl=strs[1].toInt(); bool nl=( strs[2]=="Y" ? true : false ); int extra=strs[3].toInt(); moveDockWindow( dw, tbd, nl, tbl, extra ); } void QHaccWidget::setSaveFlag( bool b ){ if( autosave ){ disconnect( engine, SIGNAL( needSave( bool ) ), this, SLOT( setSaveFlag( bool ) ) ); if( b ){ save(); b=false; } connect( engine, SIGNAL( needSave( bool ) ), SLOT( setSaveFlag( bool ) ) ); } saveflag->setText( ( const QString )( b ? tr( "save" ) : QString( "" ) ) ); } void QHaccWidget::reload(){ auto_ptr hd=HomeDlg::homedlg( engine, this ); if( hd->exec()==QDialog::Accepted ){ QString home( hd->home() ); std::ostream * str=0; if( Utils::debug( Utils::ERRDEFAULT, str ) ) *str<setHome( home ); readPrefs( false ); } } void QHaccWidget::newAcct(){ Account acct; ( new AccountDlg( engine, acct, this ) )->show(); } void QHaccWidget::editAcct(){ const Account& a=chooser->getSelected(); if( a.isNull() ) newAcct(); else ( new AccountDlg( engine, a, this ) )->show(); } void QHaccWidget::toggleRecBal(){ engine->setBP( "SHOWRECBAL", !engine->getBP( "SHOWRECBAL" ) ); } void QHaccWidget::toggleCurrency(){ engine->setBP( "USEALTCURRENCY", !engine->getBP( "USEALTCURRENCY" ) ); } void QHaccWidget::newLed(){ ( new JournalDlg( engine, Journal(), this ) )->show(); } void QHaccWidget::remLed(){ JournalDlg::qremove( engine, journaler->getSelected(), this ); } void QHaccWidget::editLed(){ ( new JournalDlg( engine, journaler->getSelected(), this ) )->show(); } void QHaccWidget::openNTrans(){ ( new NamedDlg( engine, journaler->getSelected().getu( QC::LID ), "", this ) )->show(); } void QHaccWidget::openSched(){ ( new SchedDlg( engine, "", this ) )->show(); } void QHaccWidget::subset(){ subset( chooser->getSelected() ); } void QHaccWidget::subset( const Account& a ){ if( !a.isNull() ){ QHaccSubsetWin * win=new QHaccSubsetWin( engine ); connectView( win->getView() ); win->show(); win->setAL( a, journaler->getSelected() ); vector ts; const QDate now=QDate::currentDate(); int gt=engine->getIP( "GRAPHTIME" ); ts.push_back( TableSelect( QC::XTDATE, TableCol( now.addMonths( 0-gt ) ), TableSelect::GE ) ); ts.push_back( TableSelect( QC::XTDATE, TableCol( now ), TableSelect::LE ) ); win->setSelectors( ts ); } } void QHaccWidget::graph( const Account& a ){ igrd( a, QHaccGRDialog::GRAPH ); } void QHaccWidget::graph(){ igrd( chooser->getSelected(), QHaccGRDialog::GRAPH ); } void QHaccWidget::report( const Account& a ){igrd( a, QHaccGRDialog::REPORT );} void QHaccWidget::report(){ igrd( chooser->getSelected(), QHaccGRDialog::REPORT ); } void QHaccWidget::reconcile(){ reconcile( chooser->getSelected() ); } void QHaccWidget::reconcile( const Account& a ){ if( !a.isNull() ){ QHaccRecWin * win=new QHaccRecWin( engine, journaler->getSelected() ); connectView( win->getView() ); win->show(); win->setAccount( a ); } } void QHaccWidget::igrd( const Account& a, int t ){ if( !a.isNull() ){ QHaccGRDialog * grd=new QHaccGRDialog( engine, ( QHaccGRDialog::DisplayType )t, a, journaler->getSelected(), this ); grd->readPrefs( true ); grd->show(); } } void QHaccWidget::connectView( QHaccView * v ){ // connect this view's copy and cut operations to the global // signal for copy and cut transactions AND connect this view // to that global signal as well connect( v, SIGNAL( transToClipboard( bool ) ), this, SLOT( transOnClipboard( bool ) ) ); connect( this, SIGNAL( transToClipboard( bool ) ), v, SLOT( transCopied( bool ) ) ); Transaction t; QHaccTable s; uint a=0; v->transCopied( QHaccTDrag::decode( QApplication::clipboard()->data(), t, s, a ) ); } void QHaccWidget::removeOpened(){ AccountDlg::qremove( engine, chooser->getSelected() ); } void QHaccWidget::transOnClipboard( bool b ){ emit transToClipboard( b ); }