UltraScan III
us_reporter.cpp
Go to the documentation of this file.
1 
3 #include <QApplication>
4 #include <QtSvg>
5 #include <QWebView>
6 #include <QWebSettings>
7 
8 #include "us_reporter.h"
9 #include "us_sync_db.h"
10 #include "us_settings.h"
11 #include "us_gui_settings.h"
12 #include "us_constants.h"
13 #include "us_license_t.h"
14 #include "us_license.h"
15 #include "us_passwd.h"
16 #include "us_editor.h"
17 #include "us_util.h"
18 #include "us_sleep.h"
19 #include "us_dataIO.h"
20 
21 // main program
22 int main( int argc, char* argv[] )
23 {
24  QApplication application( argc, argv );
25 
26  #include "main1.inc"
27 
28  // License is OK. Start up.
29 
30  US_Reporter w;
31  w.show();
32  return application.exec();
33 }
34 
35 // US_Reporter class constructor
37 {
38  // set up the GUI
39  setWindowTitle( tr( "Report Chooser/Generator" ) );
40  setPalette( US_GuiSettings::frameColor() );
42  websetting = NULL;
44  clean_etc_dir();
45 
46  hsclogo .clear();
47  becklogo.clear();
48  us3logo .clear();
49  QString logopath = US_Settings::appBaseDir() + "/etc/";
50 
51  if ( US_Settings::debug_match( "hsclogo" ) )
52  hsclogo = logopath + "logo_hsc.png";
53 
54  if ( US_Settings::debug_match( "becklogo" ) )
55  becklogo = logopath + "beckman_logo.png";
56 
57  if ( US_Settings::debug_match( "us3logo" ) )
58  us3logo = logopath + "ultrascan3.png";
59 
60  // primary layouts
61  QHBoxLayout* mainLayout = new QHBoxLayout( this );
62  QVBoxLayout* leftLayout = new QVBoxLayout();
63  QVBoxLayout* rghtLayout = new QVBoxLayout();
64  QGridLayout* dctlLayout = new QGridLayout();
65  QGridLayout* tctlLayout = new QGridLayout();
66  mainLayout->setSpacing ( 2 );
67  mainLayout->setContentsMargins( 2, 2, 2, 2 );
68  leftLayout->setSpacing ( 0 );
69  leftLayout->setContentsMargins( 0, 1, 0, 1 );
70  rghtLayout->setSpacing ( 0 );
71  rghtLayout->setContentsMargins( 0, 1, 0, 1 );
72  dctlLayout->setSpacing ( 1 );
73  dctlLayout->setContentsMargins( 0, 0, 0, 0 );
74  tctlLayout->setSpacing ( 1 );
75  tctlLayout->setContentsMargins( 0, 0, 0, 0 );
76 
77  // fill in the GUI components
78  QLabel* lb_runids = us_label( tr( "Runs:" ) );
80  pb_view = us_pushbutton( tr( "View" ) );
81  pb_save = us_pushbutton( tr( "Save" ) );
82  QPushButton* pb_loadpr = us_pushbutton( tr( "Load Profile" ) );
83  QPushButton* pb_savepr = us_pushbutton( tr( "Save Profile" ) );
84  QPushButton* pb_syncdb = us_pushbutton( tr( "Sync with Database" ) );
85  pb_help = us_pushbutton( tr( "Help" ) );
86  pb_close = us_pushbutton( tr( "Close" ) );
87 
88  build_runids();
89 
90  int row = 0;
91  dctlLayout->addWidget( lb_runids, row, 0, 1, 1 );
92  dctlLayout->addWidget( cb_runids, row++, 1, 1, 3 );
93  dctlLayout->addWidget( pb_view, row++, 0, 1, 4 );
94  dctlLayout->addWidget( pb_save, row++, 0, 1, 4 );
95  dctlLayout->addWidget( pb_loadpr, row++, 0, 1, 4 );
96  dctlLayout->addWidget( pb_savepr, row++, 0, 1, 4 );
97  dctlLayout->addWidget( pb_syncdb, row++, 0, 1, 4 );
98  dctlLayout->addWidget( pb_help, row, 0, 1, 2 );
99  dctlLayout->addWidget( pb_close, row++, 2, 1, 2 );
100 
101  connect( cb_runids, SIGNAL( currentIndexChanged( int ) ),
102  this, SLOT( new_runid( int ) ) );
103  connect( pb_view, SIGNAL( clicked() ),
104  this, SLOT( view() ) );
105  connect( pb_save, SIGNAL( clicked() ),
106  this, SLOT( save() ) );
107  connect( pb_loadpr, SIGNAL( clicked() ),
108  this, SLOT( load_profile() ) );
109  connect( pb_savepr, SIGNAL( clicked() ),
110  this, SLOT( save_profile() ) );
111  connect( pb_syncdb, SIGNAL( clicked() ),
112  this, SLOT( sync_db() ) );
113  connect( pb_help, SIGNAL( clicked() ),
114  this, SLOT( help() ) );
115  connect( pb_close, SIGNAL( clicked() ),
116  this, SLOT( close() ) );
117 
118  cb_runids->setToolTip(
119  tr( "Select the Run for which to produce a composite report" ) );
120  pb_view ->setToolTip(
121  tr( "View the composite report of selected individual reports" ) );
122  pb_save ->setToolTip(
123  tr( "Create a composite report using selected individual reports" ) );
124  pb_loadpr->setToolTip(
125  tr( "Load a previously saved report-selection profile" ) );
126  pb_savepr->setToolTip(
127  tr( "Save a report-selection profile for later use" ) );
128  pb_help ->setToolTip(
129  tr( "Display detailed US_Reporter documentation text and images" ) );
130  pb_close ->setToolTip(
131  tr( "Close the US_Reporter window and exit" ) );
132  pb_view->setEnabled( false );
133  pb_save->setEnabled( false );
134 
135  // set up data tree; populate with sample data
136  rbtn_click = false;
137  change_tree = true;
138  tw_recs = new QTreeWidget();
139  QPalette tpal = pb_help->palette();
140  tpal.setColor( QPalette::Base, QColor( Qt::white ) );
141  tw_recs->setPalette( tpal );
142  tctlLayout->addWidget( tw_recs );
143 
144  QStringList theads;
145  theads << "Selected" << "Report" << "Item Type";
146  ntrows = 5;
147  ntcols = theads.size();
148  tw_recs->setHeaderLabels( theads );
149  tw_recs->setFont( QFont( US_Widgets::fixedFont().family(),
150  US_GuiSettings::fontSize() - 1 ) );
151  tw_recs->setObjectName( QString( "tree-widget" ) );
152  tw_recs->setAutoFillBackground( true );
153  tw_recs->installEventFilter ( this );
154 
155  connect( tw_recs, SIGNAL( itemClicked( QTreeWidgetItem*, int ) ),
156  this, SLOT( clickedItem( QTreeWidgetItem* ) ) );
157  connect( tw_recs, SIGNAL( itemChanged( QTreeWidgetItem*, int ) ),
158  this, SLOT( changedItem( QTreeWidgetItem*, int ) ) );
159 
160  // put layouts together for overall layout
161  leftLayout->addLayout( dctlLayout );
162  leftLayout->addStretch();
163  rghtLayout->addLayout( tctlLayout );
164 
165  mainLayout->addLayout( leftLayout );
166  mainLayout->addLayout( rghtLayout );
167  mainLayout->setStretchFactor( leftLayout, 2 );
168  mainLayout->setStretchFactor( rghtLayout, 8 );
169 
170  resize( 1060, 480 );
171  show();
172  changed = false;
173 }
174 
175 // Filter events to catch right-mouse-button-click on tree widget
176 bool US_Reporter::eventFilter( QObject *obj, QEvent *e )
177 {
178  if ( obj->objectName() == "tree-widget" &&
179  e->type() == QEvent::ContextMenu )
180  { // catch tree row right-mouse click
181  rbtn_click = true;
182 DbgLv(1) << "eventFilter rbtn_click" << rbtn_click;
183  return false;
184  }
185 
186  else
187  { // pass all others for normal handling
188  return US_Widgets::eventFilter( obj, e );
189  }
190 }
191 
192 // Bring up a context menu when an item click is right-mouse-button
193 void US_Reporter::clickedItem( QTreeWidgetItem* item )
194 {
195 DbgLv(1) << "clickedItem rbtn_click" << rbtn_click;
196  if ( rbtn_click )
197  {
198  row_context( item );
199  }
200  rbtn_click = false;
201 }
202 
203 // Propagate checked states when one item has its state changed
204 void US_Reporter::changedItem( QTreeWidgetItem* item, int col )
205 {
206 //DbgLv(1) << "changedItem";
207  if ( col == 0 && change_tree )
208  { // If column 0 changed (state), set new state in tree
209  int state = (int)item->checkState( 0 );
210  int row = item->type() - (int)QTreeWidgetItem::UserType;
211  DataDesc* pdesc = (DataDesc*)&adescs.at( row );
212 
213  state_children( pdesc, state ); // Children get parent's state
214  state_parents( pdesc, state ); // Parents get unchk/part-chk/chk
215 
216  mark_checked(); // Reflect checked state in the tree
217 
218  if ( row == 0 && state == 2 )
219  {
220  pb_view->setEnabled( true );
221  pb_save->setEnabled( true );
222  }
223 
224  changed = true;
225  }
226 }
227 
228 // Build and display a context menu for a right-button-selected row
229 void US_Reporter::row_context( QTreeWidgetItem* item )
230 {
231  int row = item->type() - (int)QTreeWidgetItem::UserType;
232 DbgLv(1) << " context menu row" << row + 1;
233  QMenu* cmenu = new QMenu();
234  QAction* showact = new QAction( tr( "Show Details" ), this );
235  QAction* viewact = new QAction( tr( "View Item" ), this );
236  QAction* saveact = new QAction( tr( "Save As" ), this );
237 
238  connect( showact, SIGNAL( triggered() ),
239  this, SLOT( item_show() ) );
240  connect( viewact, SIGNAL( triggered() ),
241  this, SLOT( item_view() ) );
242  connect( saveact, SIGNAL( triggered() ),
243  this, SLOT( item_save() ) );
244 
245  cmenu->addAction( showact );
246  cmenu->addAction( viewact );
247  cmenu->addAction( saveact );
248 
249  if ( adescs.at( row ).level < 3 )
250  {
251  viewact->setEnabled( false );
252  saveact->setEnabled( false );
253  }
254 
255  cmenu->exec( QCursor::pos() );
256 }
257 
258 // Build a list of runIDs from result directories with AUC files in them
260 {
261  int nruns = 0;
262  QString rdir = US_Settings::resultDir().replace( "\\", "/" );
263  QStringList rdirs = QDir( rdir )
264  .entryList( QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name );
265  QStringList aucfil( "*.auc" );
266  rdir = rdir + "/";
267 
268  for ( int ii = 0; ii < rdirs.size(); ii++ )
269  { // Examine the subdirectories of the results directory
270  QString runid = rdirs.at( ii );
271  QString subdir = rdir + runid;
272  // Get the list of AUC files in the subdirectory
273  QStringList afiles = QDir( subdir )
274  .entryList( aucfil, QDir::Files, QDir::Name );
275  int naucf = afiles.size();
276 
277  if ( naucf > 0 )
278  { // Add the Run to the list for the Runs combo box
279  if ( nruns == 0 )
280  sl_runids << "all"; // Prefix the list with "all"
281 
282  sl_runids << runid; // Add a Run to the list
283  nruns++;
284  }
285  }
286 
287  cb_runids->setMaxVisibleItems( 20 );
288  cb_runids->addItems( sl_runids ); // Populate the Runs combo box
289  cb_runids->setCurrentIndex( 0 );
290 }
291 
292 void US_Reporter::new_runid( int row )
293 {
294  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
295  qApp->processEvents();
296  QString runID = sl_runids.at( row );
297 DbgLv(1) << " new runID row runid" << row << runID;
298 
299  // Build mappings of names,labels
300  build_map( QString( "application" ), appmap );
301  build_map( QString( "extension" ), extmap );
302  build_map( QString( "report" ), rptmap );
303 
304  int linex = 0;
305 
306  if ( runID == "all" )
307  { // For run "all", loop to build descriptions for all runs
308  for ( int ii = 1; ii < sl_runids.size(); ii++ )
309  {
310  runID = sl_runids.at( ii );
311 
312  build_descs( runID, linex );
313  }
314  }
315 
316  else
317  // Build descriptions for a specific run
318  build_descs( runID, linex );
319 
320  // Rebuild the tree widget
321  build_tree();
322  changed = true;
323  QApplication::restoreOverrideCursor();
324  qApp->processEvents();
325 }
326 
327 // Build data description records for a specified Run
328 void US_Reporter::build_descs( QString& runID, int& linex )
329 {
330 DbgLv(1) << "build_descs runID" << runID << " linex" << linex;
331 
332  QStringList appnames = appmap.keys();
333  QStringList extnames = extmap.keys();
334  QStringList rptnames = rptmap.keys();
335 
336  if ( linex == 0 )
337  adescs.clear(); // If first line, clear list
338 
339  cdesc.linen = linex + 1; // Compose the Run item description
340  cdesc.level = 0;
341  cdesc.checkState = 0;
342  cdesc.children = 0;
343  cdesc.label = runID;
344  cdesc.type = "Run";
345  cdesc.filename = "";
346  cdesc.filepath = US_Settings::reportDir() + "/" + runID + "/";
347  cdesc.runid = runID;
348  cdesc.triple = "all";
349  cdesc.analysis = "all";
350  cdesc.lastmodDate = "";
351  cdesc.description = "";
352 
353  QString path = cdesc.filepath;
354  adescs << cdesc; // Update the list and line count
355  linex++;
356 DbgLv(1) << " BD: line lev label" << cdesc.linen << cdesc.level << cdesc.label;
357 
358  QString rdir = US_Settings::resultDir().replace( "\\", "/" )
359  + "/" + runID + "/";
360  QStringList aucfil( "*.auc" );
361  QStringList afiles = QDir( rdir )
362  .entryList( aucfil, QDir::Files, QDir::Name );
363  int naucf = afiles.size();
364 DbgLv(1) << " BD: naucf" << naucf << "aucfil" << aucfil[0];
365 
366  for ( int ii = 0; ii <= naucf; ii++ )
367  { // Examine the AUC files in the Run's results subdirectory
368  QString fname;
369  QString trnam;
370  QString tripl;
371 
372  if ( ii < naucf )
373  {
374  fname = afiles.at( ii );
375  trnam = fname.section( ".", -4, -2 ).replace( ".", "" );
376  tripl = fname.section( ".", -4, -4 ) + " / "
377  + fname.section( ".", -3, -3 ) + " / "
378  + fname.section( ".", -2, -2 );
379  }
380 
381  else
382  {
383  fname = "vHW.0Z9999.combo-distrib.svgz";
384  trnam = "0Z9999";
385  tripl = "0 / Z / 9999";
386  cdesc.label = "Combined Analyses";
387  }
388 
389  QStringList trifil( "*." + trnam + ".*.*" );
390  QStringList rfiles = QDir( path )
391  .entryList( trifil, QDir::Files, QDir::Name );
392  int nrptf = rfiles.size();
393 DbgLv(1) << " BD: nrptf" << nrptf << "trifil" << trifil[0];
394 
395  if ( nrptf == 0 ) continue;
396 
397  // There are reports, so add level-1 (triple) item
398  cdesc.linen = linex + 1;
399  cdesc.level = 1;
400  cdesc.label = tr( "Cell " )
401  + tripl.section( "/", 0, 0 ).simplified()
402  + tr( "/Channel " )
403  + tripl.section( "/", 1, 1 ).simplified() + "/"
404  + tripl.section( "/", 2, 2 ).simplified() + " nm";
405  cdesc.type = "Data(triple)";
406  cdesc.filename = fname;
407  cdesc.filepath = rdir + fname;
408  cdesc.triple = tripl;
409  cdesc.analysis = "all";
410  cdesc.lastmodDate = "";
411  cdesc.description = "";
412  QString trdesc;
413 
414  // Attempt to add a triple description to the label
415  if ( ii < naucf )
416  {
417  US_DataIO::RawData rdata;
418  int rstat = US_DataIO::readRawData( cdesc.filepath, rdata );
419  if ( rstat == US_DataIO::OK )
420  {
421  trdesc = rdata.description;
422  }
423  if ( ! trdesc.isEmpty() )
424  {
425  cdesc.label = QString( cdesc.label ) + " " + trdesc;
426  cdesc.description = trdesc;
427  }
428  }
429 
430  else
431  {
432  trdesc = "Combined Analyses";
433  cdesc.label = trdesc;
434  cdesc.description = trdesc;
435  }
436 
437  adescs << cdesc;
438  linex++;
439 DbgLv(1) << " BD: line lev label" << cdesc.linen << cdesc.level << cdesc.label;
440 
441  for ( int jj = 0; jj < appnames.size(); jj++ )
442  { // Examine possible application (analysis) names
443  QString appname = appnames.at( jj );
444  QString aplabel = appmap[ appname ];
445  appname = appname.section( ":", 1, 1 );
446  QString appnmLo = appname.toLower();
447  QString appnmUp = appname.toUpper();
448 
449  QStringList rafilt( appname + "." + trnam + ".*.*" );
450  rafilt << appnmLo + "." + trnam + ".*.*"
451  << appnmUp + "." + trnam + ".*.*";
452  QStringList rafiles = QDir( path )
453  .entryList( rafilt, QDir::Files, QDir::Name );
454 DbgLv(1) << " BD: nappf" << rafiles.size() << "rafilt" << rafilt[0]
455  << rafilt[1] << rafilt[2];
456 
457  if ( rafiles.size() < 1 ) continue;
458 
459  // There are reports for this application, so add a level-2 item
460  cdesc.linen = linex + 1;
461  cdesc.level = 2;
462  cdesc.label = aplabel;
463  cdesc.type = "Analysis";
464  cdesc.filename = rafilt[ 0 ];
465  cdesc.filepath = path + rafilt[ 0 ];
466  cdesc.triple = tripl;
467  cdesc.analysis = aplabel;
468  cdesc.lastmodDate = "";
469 
470  adescs << cdesc;
471  linex++;
472 
473  for ( int kk = 0; kk < extnames.size(); kk++ )
474  { // Examine possible extensions
475  QString extname = extnames.at( kk );
476  QString exlabel = extmap[ extname ];
477  extname = extname.section( ":", 1, 1 );
478 
479  for ( int mm = 0; mm < rptnames.size(); mm++ )
480  { // Examine possible reports
481  QString rptname = rptnames.at( mm );
482  QString rplabel = rptmap[ rptname ];
483  rptname = rptname.section( ":", 1, 1 );
484 
485  // Get a specific report file name
486  QString basname = "." + trnam + "." + rptname + "." + extname;
487  QString rpfname = appname + basname;
488  QString rpfnmLo = appnmLo + basname;
489  QString rpfnmUp = appnmUp + basname;
490 //DbgLv(1) << " BD: nrptf" << rafiles.size() << "rpfname" << rpfname;
491 
492  if ( rafiles.indexOf( rpfname ) < 0 )
493  { // Skip if name (even lower/upper case version) not in list
494  int jj1 = rafiles.indexOf( appnmLo + basname );
495  int jj2 = rafiles.indexOf( appnmUp + basname );
496  if ( jj1 < 0 && jj2 < 0 )
497  continue;
498  rpfname = ( jj1 < 0 ) ? rpfname : rpfnmLo;
499  rpfname = ( jj2 < 0 ) ? rpfname : rpfnmUp;
500  }
501 
502  if ( rpfname.contains( ".svg" ) )
503  { // Skip if SVG file has PNG equivalent
504  QString rpfnpng = QString( rpfname ).section( ".", 0, -2 )
505  + ".png";
506  if ( rafiles.indexOf( rpfnpng ) >= 0 )
507  continue;
508  }
509 
510  // There are reports of this type, so add a level-3 item
511  cdesc.linen = linex + 1;
512  cdesc.level = 3;
513  cdesc.label = rplabel;
514  cdesc.type = exlabel;
515  cdesc.filename = rpfname;
516  cdesc.filepath = path + rpfname;
517  cdesc.triple = tripl;
518  cdesc.analysis = aplabel;
519  cdesc.lastmodDate = US_Util::toUTCDatetimeText(
520  QFileInfo( cdesc.filepath ).lastModified()
521  .toUTC().toString( Qt::ISODate ), true );
522 
523  adescs << cdesc;
524  linex++;
525 DbgLv(1) << " BD: line lev label" << cdesc.linen << cdesc.level << cdesc.label;
526  } // END: report names loop
527  } // END: extension names loop
528  } // END: application names loop
529  } // END: triples loop
530 }
531 
532 // Build a name,label map from the reports.html file
533 void US_Reporter::build_map( QString ttag, QMap< QString, QString >& labmap )
534 {
535  int kmap = 0;
536  QString path = US_Settings::appBaseDir() + "/etc/reports.xml";
537 DbgLv(1) << "build_map: ttag" << ttag << " path" << path;
538  QFile file( path );
539 
540  if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
541  {
542  QXmlStreamReader xml( &file );
543 
544  while( ! xml.atEnd() )
545  {
546  xml.readNext();
547 
548  if ( xml.isStartElement() && xml.name() == ttag )
549  {
550  QXmlStreamAttributes a = xml.attributes();
551  QString name = a.value( "name" ).toString();
552  QString label = a.value( "label" ).toString();
553  QString anum = QString().sprintf( "%3.3i", kmap++ );
554  QString akey = anum + ":" + name;
555 DbgLv(1) << " b_m: name label" << name << label;
556 
557  labmap[ akey ] = label;
558  }
559  }
560 
561  file.close();
562  }
563 
564  else
565  {
566  QMessageBox::warning( this,
567  tr( "UltraScan Error" ),
568  tr( "Unable to open the file:\n\n" ) + path );
569  return;
570  }
571 int nmap = labmap.count();
572 QString nam0 = labmap.keys().at( 0 );
573 QString namn = labmap.keys().at( nmap-1 );
574 DbgLv(1) << "build_map: ttag" << ttag << " nmap" << nmap;
575 DbgLv(1) << " b_m: name0,label0" << nam0 << labmap[ nam0 ];
576 DbgLv(1) << " b_m: namen,labeln" << namn << labmap[ namn ];
577 }
578 
579 // Build the tree from newly created descriptions
581 {
582  QTreeWidgetItem* pitems[ 8 ];
583  QTreeWidgetItem* item;
584  int wiubase = (int)QTreeWidgetItem::UserType;
585  int wiutype = wiubase + 0;
586  int nitems = adescs.size();
587  int nl2its = 0;
588  QStringList cvals;
589  QString indent( " " );
590 
591  change_tree = false;
592  tw_recs->clear();
593 
594  for ( int ii = 0; ii < nitems; ii++ )
595  { // Loop to add items to the data tree
596  cdesc = adescs.at( ii );
597  int lev = cdesc.level;
598  cvals.clear();
599  cvals << "" << indent.left( lev * 2 ) + cdesc.label << cdesc.type;
600  wiutype = wiubase + ii;
601 
602  if ( lev > 0 )
603  { // For all beyond the first, a tree item is attached to a parent item
604  item = new QTreeWidgetItem( pitems[ lev - 1 ], cvals, wiutype );
605 
606  if ( lev == 2 ) nl2its++;
607  }
608 
609  else
610  { // The first item is attached to the base tree root
611  item = new QTreeWidgetItem( tw_recs, cvals, wiutype );
612  }
613 
614  item->setCheckState( 0, Qt::Unchecked );
615  pitems[ lev ] = item;
616  }
617 
618  tw_recs->expandAll(); // Expand all to resize by contents
619  tw_recs->resizeColumnToContents( 0 );
620  tw_recs->resizeColumnToContents( 1 );
621  tw_recs->resizeColumnToContents( 2 );
622 
623  if ( nitems > 15 )
624  { // If there are many items, collapse some or all of them
625  if ( nl2its > 10 )
626  tw_recs->collapseAll(); // Collapse all if many analyses
627 
628  else
629  { // If not so many, collapse only analysis subtrees
630  QList< QTreeWidgetItem* > items = tw_recs->findItems( QString( "" ),
631  Qt::MatchFixedString | Qt::MatchWrap | Qt::MatchRecursive, 0 );
632 
633  for ( int ii = 0; ii < nitems; ii++ )
634  if ( adescs.at( ii ).level == 2 )
635  tw_recs->collapseItem( items.at( ii ) );
636  }
637  }
638 
639  resize( 1060, 480 );
640 DbgLv(1) << "WIDGET SIZE" << size();
641 
642  change_tree = true;
643 }
644 
645 // Count total and checked children (descendants) for an item
646 void US_Reporter::count_children( DataDesc* idesc, int& nchild, int& nchkd )
647 {
648  int nitems = adescs.size();
649  int jndx = idesc->linen;
650  int plev = idesc->level;
651 DbgLv(1) << " CnCh: jndx nitems plev" << jndx << nitems << plev;
652  nchild = 0;
653  nchkd = 0;
654 
655  for ( int jj = jndx; jj < nitems; jj++ )
656  { // Examine items from next line to the end
657  DataDesc* chdesc = (DataDesc*)&adescs.at( jj );
658  int clev = chdesc->level;
659 
660  if ( clev <= plev ) break; // Break at sibling or end of siblings
661 
662  nchild++; // Bump count of descendants
663 
664  if ( chdesc->checkState == 2 )
665  nchkd++; // Bump count of checked descendants
666 DbgLv(1) << " CnCh: jj clev plev nchild nchkd" << jj << clev << plev
667  << nchild << nchkd;
668  }
669 
670  idesc->children = nchild; // Update item description with child count
671 
672  if ( nchkd == nchild && nchild > 0 )
673  idesc->checkState = 2; // All children checked: item is checked
674 
675  else if ( nchkd > 0 )
676  idesc->checkState = 1; // Some checked: item is partially checked
677 
678  else
679  idesc->checkState = 0; // None checked: item is unchecked
680 }
681 
682 // Set checked state for children (descendants) of an item
683 void US_Reporter::state_children( DataDesc* idesc, int& state )
684 {
685  int nitems = adescs.size();
686  int jndx = idesc->linen;
687  int plev = idesc->level;
688  idesc->checkState = state; // Set the item's state
689 DbgLv(1) << " StCh: items jndx plev state" << nitems << jndx << plev << state;
690 
691  if ( jndx == nitems ) return; // Nothing more to do for the last item
692 
693  for ( int jj = jndx; jj < nitems; jj++ )
694  { // Examine items from next one to the end
695  DataDesc* chdesc = (DataDesc*)&adescs.at( jj );
696  int clev = chdesc->level;
697 DbgLv(1) << " StCh: jj clev state" << jj << clev << state;
698 
699  if ( clev <= plev ) break; // Break at sibling or end of siblings
700 
701  chdesc->checkState = state; // Make descendant state same as item
702  }
703 }
704 
705 // Set checked state for parents (ancestors) of an item
706 void US_Reporter::state_parents( DataDesc* idesc, int& state )
707 {
708  int lndx = idesc->linen - 2;
709  int clev = idesc->level;
710  int nchild = 0;
711  int nchkd = 0;
712 
713  for ( int jj = lndx; jj >= 0; jj-- )
714  { // Examine items from next above back to the first item
715  DataDesc* pdesc = (DataDesc*)&adescs.at( jj );
716  int plev = pdesc->level;
717 DbgLv(1) << " StPa: jj plev clev state" << jj << plev << clev << state;
718 
719  if ( plev >= clev ) continue; // Ignore children or siblings
720 
721  count_children( pdesc, nchild, nchkd ); // Reset parent state
722 DbgLv(1) << " StPa: nchild nchkd" << nchild << nchkd;
723  }
724 
725  if ( nchkd > 0 )
726  { // If any items are checked, enable View and Save buttons
727  pb_view->setEnabled( true );
728  pb_save->setEnabled( true );
729  }
730 
731  else
732  { // If no items are checked, disable View and Save buttons
733  pb_view->setEnabled( false );
734  pb_save->setEnabled( false );
735  }
736 }
737 
738 // Mark checked state for all of tree, from updated descriptions
740 {
741  QList< QTreeWidgetItem* > items = tw_recs->findItems( QString( "" ),
742  Qt::MatchFixedString | Qt::MatchWrap | Qt::MatchRecursive, 0 );
743  QTreeWidgetItem* item;
744  change_tree = false; // Disable slot for item change
745 //DbgLv(1) << " MkCk: items size" << items.size();
746 
747  for ( int jj = 0; jj < adescs.size(); jj++ )
748  { // Set the tree item state according to the data description setting
749 //DbgLv(1) << " MkCk: jj" << jj;
750  item = items.at( jj );
751  item->setCheckState( 0, (Qt::CheckState)adescs.at( jj ).checkState );
752  }
753 
754  change_tree = true;
755 }
756 
757 // View
759 {
760  if ( write_report() ) // Write the report file
761  { // Open the PDF file for viewing with system's "open-with" app
762 #ifdef Q_WS_WIN
763  QString file_url = QString( "file:///" ) + ppdfpath;
764 #else
765  QString file_url = QString( "file://" ) + ppdfpath;
766 #endif
767  if ( ! QDesktopServices::openUrl( file_url ) )
768  QMessageBox::warning( this, tr( "Composite Report *ERROR*" ),
769  tr( "Unable to open the composite report file:\n" )
770  + ppdfpath );
771  }
772 
773  else
774  { // Report error in creating PDF
775  QMessageBox::warning( this, tr( "Composite Report *ERROR*" ),
776  tr( "Unable to create a composite report file:\n" )
777  + ppdfpath );
778  }
779 }
780 
781 // Save
783 {
784  if ( write_report() ) // Write the report file; tell user the result
785  {
786  QString pagesdir = pagedir;
787  QString compdir = pagedir;
788  QString indent = QString( " " );
789  int jj = pagedir.lastIndexOf( "/" );
790  pagesdir = pagesdir.mid( jj + 1 );
791  compdir = compdir .left( jj );
792  QMessageBox::information( this, tr( "Composite Report" ),
793  tr( "In the composite reports folder:\n" )
794  + indent + compdir + " ,\n"
795  + tr( "a composite report file has been saved:\n" )
796  + indent + "report_composite.pdf ;\n"
797  + tr( "with supporting HTML and components in subdirectory:\n" )
798  + indent + pagesdir );
799  }
800 
801  else
802  {
803  QMessageBox::warning( this, tr( "Composite Report *ERROR*" ),
804  tr( "Unable to create a composite report file:\n" )
805  + ppdfpath );
806  }
807 }
808 
809 // Write composite report page: generate HTML, then PDF
811 {
812  const int mxpageht = 1100;
813 
814  if ( !changed )
815  return true;
816 
817  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
818  qApp->processEvents();
819  load_ok = false;
820 
821  // Count checked items: reports, runs, triples, htmls, plots
822  count_reports( );
823 
824  if ( nsrpts == 0 )
825  {
826  QMessageBox::warning( this, tr( "Select Error" ),
827  tr( "No reports have been selected" ) );
828  QApplication::restoreOverrideCursor();
829  qApp->processEvents();
830  return false;
831  }
832 
833  // Create a composite directory
834  if ( nsruns == 1 )
835  pagedir = cdesc.runid;
836  else
837  pagedir = "combo";
838 
839  pagedir = pagedir + QDateTime::currentDateTime().toString( "-yyMMddhhmm" );
840 
841  QString rptpath = US_Settings::reportDir();
842  QString cmppath = rptpath + "/composite";
843  pagepath = cmppath + "/" + pagedir;
844  QDir dirrpt( rptpath );
845 
846  if ( ! dirrpt.exists( pagepath ) )
847  {
848  if ( ! dirrpt.mkpath( pagepath ) )
849  {
850  QMessageBox::warning( this, tr( "File Error" ),
851  tr( "Could not create the directory:\n" ) + pagepath );
852  QApplication::restoreOverrideCursor();
853  qApp->processEvents();
854  return false;
855  }
856  }
857 
858  pagedir = pagepath;
859  pagepath = pagedir + "/report_composite.html";
860 
861  QString rptpage;
862 
863  // Compose the report header
864  rptpage = QString( "<?xml version=\"1.0\"?>\n" );
865  rptpage += "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
866  rptpage += " \"http://www.w3.org/TR/xhtml1/DTD"
867  "/xhtml1-strict.dtd\">\n";
868  rptpage += "<html xmlns=\"http://www.w3.org/1999/xhtml\""
869  " xml:lang=\"en\" lang=\"en\">\n";
870  rptpage += " <head>\n";
871  rptpage += " <title> Ultrascan III Composite Report </title>\n";
872  rptpage += " <meta http-equiv=\"Content-Type\" content="
873  "\"text/html; charset=iso-8859-1\"/>\n";
874  rptpage += " <style type=\"text/css\" >\n";
875  rptpage += " td { padding-right: 1em; }\n";
876  rptpage += " body { background-color: white; }\n";
877  rptpage += " .pagebreak\n";
878  rptpage += " {\n";
879  rptpage += " page-break-before: always; border: 1px solid; \n";
880  rptpage += " }\n";
881  rptpage += " .parahead\n";
882  rptpage += " {\n";
883  rptpage += " font-weight: bold;\n";
884  rptpage += " font-style: italic;\n";
885  rptpage += " }\n";
886  rptpage += " .datatext\n";
887  rptpage += " {\n";
888  rptpage += " font-family: monospace;\n";
889  rptpage += " }\n";
890  rptpage += " </style>\n";
891  rptpage += " </head>\n <body>\n";
892 
893  // Possibly prefix the page with logos
894  copy_logos( cmppath ); // Possibly add ./etc with logos
895 DbgLv(1) << " Post copy_logos hsclogo" << hsclogo;
896 
897  int logof = ( hsclogo .isEmpty() ? 0 : 4 )
898  + ( becklogo.isEmpty() ? 0 : 2 )
899  + ( us3logo .isEmpty() ? 0 : 1 );
900 
901  if ( logof != 0 )
902  {
903  rptpage += " <p>\n";
904 
905  if ( ( logof & 4 ) != 0 )
906  rptpage += " <img src=\"" + hsclogo
907  + "\" alt=\"HSC logo\"/>\n";
908 
909  if ( ( logof & 2 ) != 0 )
910  rptpage += " <img src=\"" + becklogo
911  + "\" alt=\"Beckman logo\"/>";
912 
913  if ( ( logof & 1 ) != 0 )
914  {
915  if ( logof > 1 )
916  rptpage += "<br/>";
917 
918  rptpage += "\n <img src=\"" + us3logo
919  + "\" alt=\"Ultrascan III logo\"/>\n";
920  }
921 
922  else
923  rptpage += "\n";
924 
925  rptpage += " </p>\n";
926  }
927 
928  // Compose the body of the composite report
929  QString ppageclass = " <p class=\"pagebreak parahead\">\n";
930  QString pheadclass = " <p class=\"parahead\">\n";
931  QString dtextclass = "\n <p class=\"datatext\">\n";
932  int jplot = 0;
933 
934  QString tripl = "";
935  QString analys = "";
936 
937  if ( nsruns == 1 )
938  { // Single Run
939  rptpage += " <h2> Reports from Run ";
940  rptpage += se_runids.at( 0 );
941  rptpage += ": </h2>\n\n";
942  }
943 
944  else
945  { // Multiple Runs
946  rptpage += " <h2> Reports from Multiple Runs </h2>";
947  }
948 
949  int phght = 0;
950  int chght = 0;
951 
952  for ( int ii = 0; ii < nsrpts; ii++ )
953  { // Compose an entry in the composite HTML for each component item
954  DataDesc* idesc = (DataDesc*)&adescs.at( se_rptrows.at( ii ) );
955  bool is_plot = ( idesc->type.contains( "Plot" ) );
956  bool is_data = ( idesc->type.contains( "text", Qt::CaseInsensitive ) )
957  || ( idesc->type.contains( "comma", Qt::CaseInsensitive ) );
958  phght = chght;
959 
960  // Possible set for page printing
961  if ( is_plot )
962  { // Is a plot: new page if 2nd or after non-plot
963  if ( idesc->filepath.contains( ".svg" ) )
964  {
965  QSvgRenderer svgrend;
966  svgrend.load( idesc->filepath );
967  chght = svgrend.defaultSize().height();
968  }
969 
970  else
971  {
972  chght = QPixmap( idesc->filepath ).height();
973  }
974 DbgLv(1) << " plot.height" << chght << idesc->filename << "ph" << phght;
975 
976  if ( jplot != 1 && ii > 0 )
977  { // Previous was 2nd plot on the page or was a non-plot
978 //DbgLv(1) << "++jplot" << jplot << "ii" << ii << "NEW PAGE";
979  rptpage += ppageclass;
980  jplot = 1; // mark as 1st plot on page
981  }
982 
983  else
984  { // Previous was the 1st plot on the page
985  if ( ( chght + phght ) < mxpageht )
986  { // If 2 plots will fit on a page, mark this plot as second
987 //DbgLv(1) << "++comb.height (NOPAGE)" << (chght+phght) << "mxpageht" << mxpageht;
988  rptpage += pheadclass;
989  jplot = 2; // mark as 2nd plot on page
990  }
991 
992  else
993  { // If second plot on page will exceed space, force a new page
994 DbgLv(1) << "++comb.height" << (chght+phght) << "NEW PAGE" << idesc->filename;
995  rptpage += ppageclass;
996  jplot = 1; // mark as 1st plot on page
997  }
998  }
999  }
1000 
1001  else if ( ii > 0 )
1002  { // Is not a plot and not the 1st item: starts a new page
1003  rptpage += ppageclass;
1004  jplot = 0; // mark as not a plot
1005  }
1006 
1007  else
1008  {
1009  // Is not a plot and is the 1st item: not a new page
1010  rptpage += pheadclass;
1011  }
1012 
1013  QString trdesc = idesc->triple;
1014  if ( ! idesc->description.isEmpty() )
1015  {
1016  trdesc = trdesc + " &nbsp;&nbsp;&nbsp;" + idesc->description;
1017  }
1018 
1019  // Display a title for the item
1020  rptpage += " " + idesc->runid + " &nbsp;&nbsp;&nbsp;";
1021  rptpage += trdesc + "<br/>\n ";
1022  rptpage += idesc->analysis + "<br/>\n &nbsp;&nbsp;&nbsp;";
1023  rptpage += idesc->label + "\n </p>\n";
1024 
1025  if ( is_plot )
1026  { // The item is a plot, so "<img.." will be used
1027  QString fileimg = idesc->filename;
1028 
1029  if ( fileimg.contains( ".svg" ) )
1030  { // For SVG[Z], create a PNG equivalent
1031  QSvgRenderer svgrend;
1032  QString pathsvg = idesc->filepath;
1033  QString filesvg = idesc->filename;
1034  fileimg = QString( filesvg ).section( ".", 0, -2 ) + ".png";
1035  QString pathimg = pagedir + "/" + fileimg;
1036  svgrend.load( pathsvg );
1037  QSize imgsize = svgrend.defaultSize();
1038  QPixmap pixmap( imgsize );
1039  pixmap.fill( Qt::white );
1040  QPainter pa( &pixmap );
1041  svgrend.render( &pa ); // Render the SVG to a pixmap
1042 DbgLv(1) << " size" << imgsize << " filesvg" << filesvg;
1043 DbgLv(1) << " size" << pixmap.size() << " fileimg" << fileimg;
1044  if ( ! pixmap.save( pathimg ) ) // Write the pixmap as a PNG
1045  {
1046  QMessageBox::warning( this, tr( "Composite Report *ERROR*" ),
1047  tr( "Unable to create an svg-to-png file:\n" )
1048  + pathimg );
1049  }
1050 
1051  chght = imgsize.height();
1052 
1053  }
1054 
1055  // Embed the plot in the composite report
1056  rptpage += " <div><img src=\"" + fileimg
1057  + "\" alt=\"" + idesc->label + "\"/></div>\n\n";
1058  }
1059 
1060  else if ( is_data )
1061  { // The item is Data text, so copy it with line breaks added
1062  QFile fi( idesc->filepath );
1063  if ( fi.open( QIODevice::ReadOnly | QIODevice::Text ) )
1064  {
1065  rptpage += dtextclass;
1066  QTextStream ts( &fi );
1067 
1068  while ( ! ts.atEnd() )
1069  {
1070  rptpage += pad_line( ts.readLine() );
1071  }
1072 
1073  rptpage += " </p>\n\n";
1074  fi.close();
1075  }
1076  }
1077 
1078  else
1079  { // The item is HTML, so copy it with header,footer removed
1080  QFile fi( idesc->filepath );
1081  if ( fi.open( QIODevice::ReadOnly | QIODevice::Text ) )
1082  {
1083  QTextStream ts( &fi );
1084  int stage = 0;
1085 
1086  while ( ! ts.atEnd() )
1087  {
1088  QString ln = ts.readLine() + "\n";
1089 
1090  if ( stage == 0 )
1091  { // skip early part of component HTML until body
1092  if ( ln.contains( "<body>" ) )
1093  stage = 1; // mark that we are now in body
1094  continue;
1095  }
1096 
1097  else if ( stage == 2 )
1098  // skip part of component HTML beyond body end
1099  continue;
1100 
1101  else if ( ln.contains( "</body>" ) )
1102  { // mark end of body
1103  stage = 2;
1104  if ( ln.contains( "</table>" ) )
1105  ln = "</table>\n";
1106  else
1107  continue;
1108  }
1109 
1110  if ( ln.contains( "<br>" ) ) // correct BR format
1111  ln.replace( "<br>", "<br/>" );
1112 
1113  rptpage += ln;
1114  }
1115 
1116  rptpage += "\n";
1117 // rptpage += "\n <p class=\"page parahead\"></p>\n";
1118  fi.close();
1119  }
1120  }
1121  } // END: Reports loop
1122 
1123  // Complete composite page, output it to a file, and copy components
1124  rptpage += " </body>\n</html>";
1125 
1126  QFile flo( pagepath );
1127  if ( flo.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1128  { // Output the composite page to a file in the composite folder
1129  QTextStream tso( &flo );
1130  tso << rptpage;
1131  flo.close();
1132  }
1133 
1134  // Copy all report files to the current report subdir
1135  for ( int ii = 0; ii < nsrpts; ii++ )
1136  {
1137  DataDesc* idesc = (DataDesc*)&adescs.at( se_rptrows.at( ii ) );
1138 
1139  QFile::copy( idesc->filepath, pagedir + "/" + idesc->filename );
1140  }
1141 
1142  write_pdf(); // Create the PDF version of the web view
1143 
1144  changed = false;
1145  QApplication::restoreOverrideCursor();
1146  qApp->processEvents();
1147 
1148  return load_ok;
1149 }
1150 
1151 // Create a PDF from the main HTML
1153 {
1154  ppdfpath = QString( pagepath ).replace( ".html", ".pdf" );
1155  QPrinter printer( QPrinter::HighResolution );
1156  printer.setOutputFormat ( QPrinter::PdfFormat );
1157  printer.setOutputFileName( ppdfpath );
1158 // printer.setFullPage ( true );
1159  printer.setCreator ( "UltraScan" );
1160  printer.setDocName ( QString( "report_composite.html" ) );
1161  printer.setOrientation ( QPrinter::Portrait );
1162  printer.setPaperSize ( QPrinter::A2 );
1163 // printer.setPaperSize ( QPrinter::Letter );
1164 
1165  QString rpttext;
1166  QFile fili( pagepath );
1167  if ( fili.open( QIODevice::ReadOnly | QIODevice::Text ) )
1168  {
1169  QTextStream ts( &fili );
1170  rpttext = ts.readAll();
1171  fili.close();
1172  }
1173 
1174  // Use TextDocument to produce PDF
1175  QString rptpath = US_Settings::reportDir();
1176  QString cmppath = QString( pagepath ).section( "/", 0, -2 );
1177  QDir::setCurrent( cmppath );
1178  QTextDocument document;
1179  document.setDefaultFont( QFont( "serif", 14 ) );
1180  QSizeF pgsize = printer.paperSize( QPrinter::Point );
1181  document.setPageSize( pgsize );
1182  document.setHtml( rpttext );
1183  document.print( &printer );
1184 
1185  QString ppdffold = ppdfpath;
1186  ppdfpath = ppdfpath.section( "/", 0, -3 );
1187  ppdfpath = ppdfpath + "/report_composite.pdf";
1188  QFile pdff( ppdfpath );
1189 
1190  // Overwrite the PDF in the composite folder with the one
1191  // in the subdirectory where the HTML and components exist
1192 
1193  if ( pdff.exists() )
1194  pdff.remove();
1195 
1196  if ( ! QFile::copy( ppdffold , ppdfpath ) )
1197  {
1198  QMessageBox::information( this, tr( "UltraScan Error" ),
1199  tr( "Unable to (over-)write the file:\n\n" ) + ppdfpath );
1200  }
1201  load_ok = true;
1202 }
1203 
1204 // Count selected reports: runIDs, reports, htmls, plots
1206 {
1207  nsrpts = 0; // Clear counts and lists
1208  nsruns = 0;
1209  nshtmls = 0;
1210  nsplots = 0;
1211  se_reports.clear();
1212  se_rptrows.clear();
1213  se_runids .clear();
1214 
1215  for ( int ii = 0; ii < adescs.size(); ii++ )
1216  { // Review data description records
1217  DataDesc* idesc = (DataDesc*)&adescs.at( ii );
1218 
1219 DbgLv(1) << "cnt_rpt: ii lev state" << ii << idesc->level << idesc->checkState;
1220  if ( idesc->level != 3 ||
1221  idesc->checkState != 2 ) continue; // Only reports checked
1222 
1223  if ( nsrpts == 0 )
1224  cdesc = *idesc; // Save first found
1225 
1226  nsrpts++; // Update reports count,list
1227  se_reports << idesc->label;
1228  se_rptrows << ii;
1229 
1230  if ( nsruns == 0 || !se_runids.contains( idesc->runid ) )
1231  { // Save a list of unique runIDs
1232  nsruns++;
1233  se_runids << idesc->runid; // Update runs count,list
1234  }
1235 
1236  if ( idesc->type.contains( "HTML" ) )
1237  nshtmls++; // Update HTMLs count
1238 
1239  else if ( idesc->type.contains( "Plot" ) )
1240  nsplots++; // Update PLOTs count
1241 DbgLv(1) << "cnt_rpt: ns rpts,runs,htmls,plots" << nsrpts << nsruns
1242  << nshtmls << nsplots;
1243  }
1244 
1245  return ( nsrpts > 0 );
1246 }
1247 
1248 // View an individual report file
1250 {
1251  QString fileexts = tr(
1252  "HTML files (*.html);;PLOT files (*.svgz *.svg *.png);;"
1253  "Report file (*.rpt);;Data files (*.csv *.dat);;"
1254  "All files (*.*)" );
1255  int row = tw_recs->currentItem()->type() - (int)QTreeWidgetItem::UserType;
1256  cdesc = adescs.at( row );
1257  bool isHTML = true;
1258  QString mtext;
1259 
1260  if ( cdesc.type.contains( "Plot" ) )
1261  { // For plots, write an <img ...> line
1262  mtext = QString( "<head>\n<title>" ) + cdesc.filepath +
1263  QString( "</title>\n</head>\n<img src=\"" ) + cdesc.filepath +
1264  QString( "\" alt=\"" + cdesc.label + "\" />\n" );
1265  }
1266 
1267  else
1268  { // For non-plot, simply copy the file itself
1269  QFile fi( cdesc.filepath );
1270  if ( fi.open( QIODevice::ReadOnly | QIODevice::Text ) )
1271  {
1272  QTextStream ts( &fi );
1273 
1274  while ( ! ts.atEnd() )
1275  mtext += ts.readLine() + "\n";
1276 
1277  mtext += "\n";
1278  fi.close();
1279  }
1280 
1281  // Flag as HTML or plain-text
1282  isHTML = cdesc.type.contains( "HTML" );
1283  }
1284 
1285  // Display the report (plot or text) in an editor dialog
1286  US_Editor* editd = new US_Editor( US_Editor::LOAD, true, fileexts );
1287  editd->setWindowTitle( tr( "Report Tree Item View" ) );
1288  editd->move( QCursor::pos() + QPoint( 100, 100 ) );
1289  editd->resize( 600, 500 );
1290  editd->e->setFont( QFont( US_Widgets::fixedFont().family(),
1292  if ( isHTML )
1293  editd->e->setHtml( mtext );
1294  else
1295  editd->e->setText( mtext );
1296  editd->show();
1297 }
1298 
1299 // Show details for an item
1301 {
1302  QString fileexts = tr( "Text,Log files (*.txt, *.log);;All files (*)" );
1303  int row = tw_recs->currentItem()->type() - (int)QTreeWidgetItem::UserType;
1304  cdesc = adescs.at( row );
1305  int lsx = cdesc.filepath.lastIndexOf( "/" );
1306  QString filedir = cdesc.filepath.left( lsx );
1307 
1308  QString mtext =
1309  tr( "Data Tree Item at Row %1 -- \n\n" ).arg( row + 1 ) +
1310  tr( " Tree Level : " ) + QString::number( cdesc.level ) + "\n" +
1311  tr( " Check State : " ) + QString::number( cdesc.checkState ) + "\n" +
1312  tr( " Label : " ) + cdesc.label + "\n" +
1313  tr( " Type : " ) + cdesc.type + "\n" +
1314  tr( " File Name : " ) + cdesc.filename + "\n" +
1315  tr( " File Directory : " ) + filedir + "\n" +
1316  tr( " Run ID : " ) + cdesc.runid + "\n" +
1317  tr( " Triple : " ) + cdesc.triple + "\n" +
1318  tr( " Analysis : " ) + cdesc.analysis + "\n" +
1319  tr( " Last Mod Date : " ) + cdesc.lastmodDate + "\n";
1320 
1321  US_Editor* editd = new US_Editor( US_Editor::LOAD, true, fileexts );
1322  editd->setWindowTitle( tr( "Report Tree Item Details" ) );
1323  editd->move( QCursor::pos() + QPoint( 100, 100 ) );
1324  editd->resize( 600, 500 );
1325  editd->e->setFont( QFont( US_Widgets::fixedFont().family(),
1327  editd->e->setText( mtext );
1328  editd->show();
1329 }
1330 
1331 // Open a dialog to save an item data file
1333 {
1334  int row = tw_recs->currentItem()->type() - (int)QTreeWidgetItem::UserType;
1335  cdesc = adescs.at( row );
1336  int isx = cdesc.filepath.lastIndexOf( "/" ) + 1;
1337  // Determine base file name, extension, analysis prefix
1338  QString filename = cdesc.filepath.mid( isx );
1339  QString fileext = filename.mid( filename.lastIndexOf( "." ) + 1 );
1340  QString fileanp = filename.left( filename.indexOf( "." ) );
1341  // Determine file types string and default output file path
1342  QString fileexts = fileext + tr( " files (*." ) + fileext + ");;"
1343  + fileanp + tr( " files (" ) + fileanp + "*);;"
1344  + tr( "All files (*)" );
1345  QString ofilname = archdir + filename;
1346 
1347  // Open a dialog and get the Save-As file path name
1348  QString fn = QFileDialog::getSaveFileName( this,
1349  tr( "Save Report File As ..." ), ofilname, fileexts );
1350 
1351  if ( fn.isEmpty() ) return;
1352 
1353  // Copy the file to its specified archive location; save archive directory
1354  QFile::copy( cdesc.filepath, fn );
1355 
1356  archdir = fn.left( fn.lastIndexOf( "/" ) + 1 );
1357 }
1358 
1359 // Load a report-selection profile
1361 {
1362  QStringList selects;
1363  QString rselect;
1364 
1365  // Open a file dialog to get the profile file name
1366  QString fn = QFileDialog::getOpenFileName( this,
1367  tr( "Load Report-Select Parameters in:" ),
1369  tr( "ReportSelect files (rs_*.xml);;"
1370  "All XML files (*.xml);;"
1371  "All files (*)" ) );
1372 
1373  if ( fn.isEmpty() ) return;
1374 
1375  QFile xfi( fn );
1376 
1377  if ( xfi.open( QIODevice::ReadOnly | QIODevice::Text ) )
1378  {
1379  selects.clear();
1380  QXmlStreamReader xml( &xfi );
1381  QXmlStreamAttributes att;
1382 
1383  while ( ! xml.atEnd() )
1384  { // Get all unique analysis+report selections from the XML file
1385  xml.readNext();
1386 
1387  if ( xml.isStartElement() && xml.name() == "selection" )
1388  {
1389  att = xml.attributes();
1390 
1391  QString analys = att.value( "analysis" ).toString();
1392  QString report = att.value( "report" ).toString();
1393 
1394  rselect = analys + report; // Concatenated selection
1395 
1396  if ( selects.isEmpty() || ! selects.contains( rselect ) )
1397  selects << rselect; // Add a new one to the list
1398  }
1399  }
1400 
1401  xfi.close();
1402  }
1403 
1404  for ( int ii = 0; ii < adescs.size(); ii++ )
1405  { // Propagate selections to report tree
1406  DataDesc* idesc = (DataDesc*)&adescs.at( ii );
1407 
1408  if ( idesc->level != 3 ) continue; // Only reports
1409 
1410  rselect = idesc->analysis + idesc->label; // Potential select
1411  int state = ( selects.contains( rselect ) ) ? 2 : 0; // Selected?
1412 
1413  state_children( idesc, state ); // Propagate select
1414  state_parents( idesc, state );
1415 
1416  mark_checked();
1417  }
1418 }
1419 
1420 // Save a report-selection profile
1422 {
1423  // Open a file dialog to get a name for the profile save file
1424  QString fn = QFileDialog::getSaveFileName( this,
1425  tr( "Save Report-Selection Parameters in:" ),
1427  tr( "ReportSelect files (rs_*.xml);;"
1428  "All XML files (*.xml);;"
1429  "All files (*)" ) );
1430 
1431  if ( fn.isEmpty() ) return;
1432 
1433  // Allow variations with/without "rs_" and ".xml"
1434  fn = fn.replace( "\\", "/" );
1435  int jj = fn.lastIndexOf( "/" ) + 1;
1436  QString fdir = fn.left( jj );
1437  QString fnam = fn.mid( jj );
1438 
1439  if ( fn.endsWith( "." ) )
1440  {
1441  fn = fn.left( fn.length() - 1 );
1442  fnam = fnam.left( fnam.length() - 1 );
1443  }
1444 
1445  else if ( ! fn.endsWith( ".xml" ) )
1446  {
1447  fn = fn + ".xml";
1448  fnam = fnam + ".xml";
1449  }
1450 
1451  if ( fnam.startsWith( "." ) )
1452  {
1453  fn = fdir + fnam.mid( 1 );
1454  }
1455 
1456  else if ( ! fnam.startsWith( "rs_" ) )
1457  {
1458  fn = fdir + "rs_" + fnam;
1459  }
1460 
1461  QFile xfo( fn );
1462 
1463  if ( xfo.exists() )
1464  {
1465  if ( QMessageBox::No == QMessageBox::warning( this,
1466  tr( "Warning" ),
1467  tr( "Attention:\n"
1468  "This file exists already!\n\n"
1469  "Do you want to overwrite it?" ),
1470  QMessageBox::Yes, QMessageBox::No ) )
1471  return;
1472  }
1473 
1474  bool saved_ok = false;
1475 
1476  if ( xfo.open( QIODevice::WriteOnly | QIODevice::Text ) )
1477  { // Write unique selections to the XML file
1478  QXmlStreamWriter xml( &xfo );
1479  xml.setAutoFormatting( true );
1480  xml.writeStartDocument();
1481  xml.writeDTD ( "<!DOCTYPE US_ReportSelect>" );
1482  xml.writeStartElement( "ReportSelect" );
1483  xml.writeAttribute ( "version","1.0" );
1484 
1485  for ( int ii = 0; ii < adescs.size(); ii++ )
1486  { // Examine all the data records, looking for selections
1487  DataDesc* idesc = (DataDesc*)&adescs.at( ii );
1488 
1489  if ( idesc->level != 3 || idesc->checkState != 2 ) continue;
1490 
1491  // Level-3 (reports) that are selected: output an element
1492  xml.writeStartElement( "selection" );
1493  xml.writeAttribute ( "analysis", idesc->analysis );
1494  xml.writeAttribute ( "report", idesc->label );
1495  xml.writeEndElement (); // selection
1496  }
1497 
1498  xml.writeEndElement (); // ReportSelect
1499  xml.writeEndDocument ();
1500  xfo.close();
1501  saved_ok = true;
1502  }
1503 
1504  if ( saved_ok )
1505  QMessageBox::information( this,
1506  tr( "UltraScan Information" ),
1507  tr( "Please note:\n\n"
1508  "The Report-Select Profile was successfully saved to:\n\n" ) +
1509  fn );
1510 
1511  else
1512  QMessageBox::information( this,
1513  tr( "UltraScan Error" ),
1514  tr( "Please note:\n\n"
1515  "The Report-Select Profile could not be saved to:\n\n" ) +
1516  fn );
1517 }
1518 
1519 // Synchronize with the database
1521 {
1522  US_SyncWithDB* syncdb = new US_SyncWithDB();
1523 
1524  syncdb->exec();
1525 }
1526 
1527 // Create ./etc if need be and put copies of any logos there
1528 void US_Reporter::copy_logos( QString cmppath )
1529 {
1530  if ( hsclogo.isEmpty() && becklogo.isEmpty() && us3logo.isEmpty() )
1531  return; // Don't bother if no logos exist
1532 
1533  if ( hsclogo .startsWith( ".." ) &&
1534  becklogo.startsWith( ".." ) &&
1535  us3logo .startsWith( ".." ) )
1536  return; // Don't bother if already converted
1537 
1538  // Rename logo paths using a relative path
1539  QString hscorig = hsclogo;
1540  QString beckorig = becklogo;
1541  QString us3orig = us3logo;
1542  QString etcpath = US_Settings::appBaseDir() + "/etc/";
1543  QString etcnewp = cmppath + "/etc/";
1544  QString relpath = "../etc/";
1545  hsclogo = hsclogo .isEmpty() ? hsclogo :
1546  hsclogo .replace( etcpath, relpath );
1547  becklogo = becklogo.isEmpty() ? becklogo :
1548  becklogo.replace( etcpath, relpath );
1549  us3logo = us3logo .isEmpty() ? us3logo :
1550  us3logo .replace( etcpath, relpath );
1551  QString hscnewp = QString( hscorig ).replace( etcpath, etcnewp );
1552  QString becknewp = QString( beckorig ).replace( etcpath, etcnewp );
1553  QString us3newp = QString( us3orig ).replace( etcpath, etcnewp );
1554 DbgLv(1) << "hscOrig" << hscorig;
1555 DbgLv(1) << "hscLogo" << hsclogo;
1556 DbgLv(1) << "hscNewp" << hscnewp;
1557 DbgLv(1) << "etcPath" << etcpath;
1558 DbgLv(1) << "etcNewp" << etcnewp;
1559 
1560  if ( ( hsclogo .isEmpty() || QFile( hscnewp ).exists() ) &&
1561  ( becklogo.isEmpty() || QFile( becknewp ).exists() ) &&
1562  ( us3logo .isEmpty() || QFile( us3newp ).exists() ) )
1563  return; // Don't bother if logos already exist
1564 
1565  // Need to copy logos, so start by insuring ./composite/etc exists
1566  QDir dircmp( cmppath );
1567 
1568  if ( ! dircmp.mkpath( etcnewp ) )
1569  {
1570  QMessageBox::warning( this, tr( "Composite Report *ERROR*" ),
1571  tr( "Unable to create a composite report directory:\n" )
1572  + etcnewp );
1573  return;
1574  }
1575 
1576  // Copy each existing logo file
1577  if ( ! hsclogo .isEmpty() && QFile( hscorig ).exists() )
1578  QFile::copy( hscorig , hscnewp );
1579 
1580  if ( ! becklogo.isEmpty() && QFile( beckorig ).exists() )
1581  QFile::copy( beckorig, becknewp );
1582 
1583  if ( ! us3logo .isEmpty() && QFile( us3orig ).exists() )
1584  QFile::copy( us3orig , us3newp );
1585 
1586 }
1587 
1588 // Pad text line with non-blank spaces to simulate spaces and tabs
1589 QString US_Reporter::pad_line( const QString linein )
1590 {
1591  QString lineout;
1592  const QChar cbln( ' ' );
1593  const QChar ctab( '\t' );
1594  const QString s_nbsp( "&nbsp;" );
1595  QChar lchar;
1596  int kk = 3;
1597 
1598  for ( int ii = 0; ii < linein.size(); ii++ )
1599  {
1600  QChar lchar = linein.at( ii );
1601 
1602  if ( lchar == cbln ) // Replace blank with NBSP
1603  {
1604  lineout.append( s_nbsp );
1605  kk++;
1606  }
1607 
1608  else if ( lchar == ctab ) // Replace tab with 2-5 NBSPs
1609  {
1610  int nspc = 4 - ( kk & 3 );
1611  kk += nspc;
1612  lineout.append( cbln );
1613  lineout.append( s_nbsp );
1614 
1615  for ( int jj = 0; jj < nspc; jj++ )
1616  lineout.append( s_nbsp );
1617  }
1618 
1619  else // Copy all other characters
1620  {
1621  lineout.append( lchar );
1622  kk++;
1623  }
1624  }
1625 
1626  lineout += "<br/>\n";
1627 
1628  return lineout;
1629 
1630 }
1631