// Implementation of QTreeWindow #include "qtreewindow.h" #include <qapplication.h> #include <qcursor.h> #include <qdragobject.h> #include <qfiledialog.h> #include <qmessagebox.h> #include <qpainter.h> #include <qpicture.h> #include <qpixmap.h> #include <qprinter.h> #include <qregexp.h> #include "img/qembed.h" #include "qtreechangeview.h" #include "clientdiffuser.h" #include "qtreeabout.h" #ifndef max #define max(a,b) (((a) > (b)) ? (a) : (b)) #endif #ifndef min #define min(a,b) (((a) < (b)) ? (a) : (b)) #endif // Once this constructor is called, you don't need to do anything else. QTreeWindow::QTreeWindow ( QWidget* parent, char* mfile, ClientApi* cli, Error* e, bool isbirds, char* cf ) :QCanvasView( parent, "P4QTree", WType_TopLevel ), client( cli ), error( e ), minime( NULL ), legend( NULL ), filelabel( NULL ), birdseyeview( isbirds ), mainfile( mfile ), tips( QTreeToolTip( this ) ), cachefile( cf ) { // Load preferences. LoadSettings(); // Set up menus and leave space for the menu bar. InitMenus(); setMargins( TREE_ICONWIDTH, menubar->height(), 0, 0 ); // Scrollbars always on. setHScrollBarMode( AlwaysOn ); setVScrollBarMode( AlwaysOn ); // The bulk of the work is done right here - fatal errors, // such as connection failures, result in an empty canvas. qtcanvas = new QTreeCanvas ( this, mainfile, client, error, havejobbar, isbirds, startchange, startdate, endcd, showall, cf ); if ( qtcanvas->IsEmpty() ) return; else setCanvas( qtcanvas ); // this is to sync horizontal scrolling of the ChangeView connect ( this, SIGNAL( contentsMoving ( int, int ) ), this, SLOT( HorizScroll( int, int ) ) ); // Enable drag 'n drop support. viewport()->setAcceptDrops( TRUE ); // Establish the ChangeView subview to hold the change/job bar. changeview = new QTreeChangeView( qtcanvas, this ); // Init icons after the ChangeView. InitIcons(); // Any nonfatal errors? Report them and move on. if ( error->Test() ) { error->Set( E_WARN, "P4QTree encountered the following errors:\n" ); HandleError(); } // Unlike most widgets, QTreeWindow shows itself on construction. show(); CenterOnMain(); // Just for fun. egg = new QTreeEasterEgg( this ); } QTreeWindow::~QTreeWindow() { } void QTreeWindow::InitIcons() { QPixmap ficon = QPixmap( qembed_findData( "file.bmp" ) ); ficon.setMask( ficon.createHeuristicMask() ); filelabel = new QLabel( this ); filelabel->setPixmap( ficon ); filelabel->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter ); filelabel->setFrameStyle( QFrame::Panel | QFrame::Plain ); filelabel->setBackgroundColor( TREE_COLSTRIPEA ); horizontalScrollBar()->raise(); //let the filelabel go under it // Set app icon and caption appicon = QPixmap( qembed_findData( "tree.bmp" ) ); appicon.setMask( appicon.createHeuristicMask() ); setIcon( appicon ); setCaption( QString( "P4QTree - " ) + qtcanvas->cache->main->name.Text() ); } // This is important - it does all the size checking that Qt doesn't. It // should be called on any event that alters the geometry of the window. void QTreeWindow::AdjustGeometry() { if ( IsEmpty() ) return; // avoid dereferencing null // Line up the changeview. HorizScroll( contentsX(), contentsY() ); // Establish base maximum dimensions as hinted by Qt. int width = sizeHint().width(); int height = sizeHint().height(); // Add the menubar to this. height += menubar->height(); // And the icons. width += TREE_ICONWIDTH; // And the scrollbars. height += horizontalScrollBar()->height(); width += verticalScrollBar()->width(); // If jobs are hidden, subtract the jobbar height. if ( havejobbar && !jobsvisible ) height -= ( qtcanvas->ChangeSize().height() - TREE_BARHEIGHT ); // Set the modified dimensions as the new maximum size. setMaximumSize( width, height ); // If jobs are hidden, tell the scrollbar not to show them. if ( havejobbar ) { if ( jobsvisible ) verticalScrollBar()->setMinValue( 0 ); else verticalScrollBar()->setMinValue ( qtcanvas->ChangeSize().height() - TREE_BARHEIGHT ); } // Play with the file label. filelabel->setGeometry( 1, changeview->height() - 3 + menubar->height(), TREE_ICONWIDTH, visibleHeight() - changeview->height() + 6 ); // Have the changeview do its own resize dance. changeview->AdjustGeometry(); } // Set up the window to look at the "main" depot file's head rev. void QTreeWindow::CenterOnMain() { if ( IsEmpty() ) return; AdjustGeometry(); center( qtcanvas->cache->main->head->revball->x(), qtcanvas->cache->main->head->revball->y() ); changeview->center( qtcanvas->cache->main->head->revball->x(), 0 ); //Hack to keep changeview from twitching after startup. changeview->scrollBy( -1, 0 ); } // Similar to its QTreeChangeView counterpart - handles drags. void QTreeWindow::contentsMousePressEvent( QMouseEvent* e ) { if ( e->button() == LeftButton ) e->accept(); else { e->ignore(); return; } if ( qtcanvas->IsEmpty() ) return; //error check // collision detection - is the item draggable? QCanvasItemList items = canvas()->collisions( e->pos() ); QCanvasItem* item = NULL; while ( !items.empty() ) { item = items.front(); if ( item->rtti() == RTTI_REV || item->rtti() == RTTI_FILE ) break; items.pop_front(); } if ( !item || ( item->rtti() != RTTI_REV && item->rtti() != RTTI_FILE ) ) { // no object hit - do a gridline instead! qtcanvas->GridLine( e->pos().x() ); return; } // start a dragging action QTextDrag* d; dragsource = item; switch ( item->rtti() ) { case RTTI_REV: d = new QTextDrag( ( (QTreeRevBall*)item )->Name().Text(), this ); d->drag(); break; case RTTI_FILE: d = new QTextDrag( ( (QTreeFileText*)item )->text(), this ); d->drag(); break; } } // Ignore drags from other apps or windows. void QTreeWindow::contentsDragEnterEvent( QDragEnterEvent* e ) { if ( e->source() == this ) e->accept(); else e->ignore(); } // Accept drops onto revs so we can diff them. void QTreeWindow::contentsDropEvent( QDropEvent* e ) { e->accept(); // Get the file#rev string from the drag. QString filerev = QString(); QTextDrag::decode( e, filerev ); StrBuf source = StrBuf(); source.Append( filerev.latin1() ); if ( *( source.Text() ) != '/' ) return; // Get the file#rev string from the drop target. QCanvasItemList items = canvas()->collisions( e->pos() ); QCanvasItem* item = NULL; while ( !items.empty() ) { item = items.front(); if ( item->rtti() == RTTI_REV || item->rtti() == RTTI_FILE ) break; items.pop_front(); } if ( !item ) return; StrBuf target; int tx; bool binary = false; switch ( item->rtti() ) { case RTTI_REV: target = ( (QTreeRevBall*)item )->Name(); binary = ( (QTreeRevBall*)item )->IsBinary(); tx = item->x(); break; case RTTI_FILE: target = ( (QTreeFileText*)item )->Name(); binary = ( (QTreeFileText*)item )->HeadBinary(); tx = ( (QTreeFileText*)item )->HeadX(); break; default: return; } int sx; switch ( dragsource->rtti() ) { case RTTI_REV: sx = dragsource->x(); break; case RTTI_FILE: sx = ( (QTreeFileText*)dragsource )->HeadX(); break; default: return; } // Ignore in-place drag 'n drop. if ( source == target ) return; // Use ClientDiffUser to handle the diff. ClientDiffUser* ui; // Put the args in chronological order. if ( tx >= sx ) ui = new ClientDiffUser( source, target, client, error ); else ui = new ClientDiffUser( target, source, client, error ); ui->DoDiff( this, binary ); delete ui; } // Spawn info dialog. void QTreeWindow::contentsMouseDoubleClickEvent( QMouseEvent* e ) { if ( e->button() == LeftButton ) e->accept(); else { e->ignore(); return; } if ( qtcanvas->IsEmpty() ) return; //error check QCanvasItemList items = canvas()->collisions( e->pos() ); QCanvasItem* item = NULL; while ( !items.empty() ) { item = items.front(); if ( item->rtti() == RTTI_REV || item->rtti() == RTTI_FILE ) break; items.pop_front(); } if ( item == NULL ) return; switch ( item->rtti() ) { case RTTI_REV: ( (QTreeRevBall*)item )->InfoDialog( this ); break; case RTTI_FILE: ( (QTreeFileText*)item )->InfoDialog( this ); break; } } // Strictly speaking, this only needs to be called on resize events.// // However, resizeEvent() doesn't get called on maximizes. bool QTreeWindow::event( QEvent* e ) { bool foo = QCanvasView::event( e ); AdjustGeometry(); HandleError(); return foo; }; // Pop up a warning about an error. void QTreeWindow::HandleError() { if ( !error->Test() ) return; StrBuf msg; error->Fmt( &msg ); error->Clear(); if ( console ) { fprintf( stderr, "P4QTree Warning:\n%s", msg.Text() ); return; } QMessageBox::warning ( this, "P4QTree Warning", msg.Text(), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton ); } // For fatal startup errors. int QTreeWindow::HandleFatalError() { StrBuf msg; error->Fmt( &msg ); if ( console ) { fprintf( stderr, "P4QTree Error:\n%s", msg.Text() ); return 1; } QMessageBox::critical ( this, "P4QTree Error", msg.Text(), QMessageBox::Abort, QMessageBox::NoButton, QMessageBox::NoButton ); return 1; } // Hide or reveal the jobbar. void QTreeWindow::ToggleJobs() { // jobheight is the height of the jobbar. int jobheight = qtcanvas->ChangeSize().height() - TREE_BARHEIGHT; if ( jobsvisible ) // Hide the bar. { // Make the window's max size smaller. setMaximumHeight( maximumHeight() - jobheight ); // Play with scrollbar as needed. if ( verticalScrollBar()->value() > jobheight ) verticalScrollBar()->setValue ( verticalScrollBar()->value() + jobheight ); // Inform the changeview. changeview->HideJobs(); // Tell the scrollbar not to show the jobs. verticalScrollBar()->setMinValue( jobheight ); } else { // Return the max height to its prior dimension. setMaximumHeight( maximumHeight() + jobheight ); // Tell the changeview. changeview->ShowJobs(); // Tell the scrollbar to go back. verticalScrollBar()->setMinValue( 0 ); // Have it compensate for the change in geometry. verticalScrollBar()->setValue ( verticalScrollBar()->value() - jobheight ); } jobsvisible = !jobsvisible; } void QTreeWindow::LoadSettings() { QSettings settings; settings.insertSearchPath( QSettings::Windows, "/Perforce" ); // Color scheme. qtrcol = settings.readNumEntry( "/P4QTree/qtrcol", TREE_OLDSKOOL ); // Jobs. havejobbar = settings.readBoolEntry( "/P4QTree/jobs", true ); if ( birdseyeview ) havejobbar = false; jobsvisible = havejobbar; // Date cropping. startdate = settings.readEntry( "/P4QTree/sdate", QString::null ); startchange = settings.readEntry( "/P4QTree/schange", QString::null ); endcd = settings.readEntry( "/P4QTree/edate", QString::null ); char sb = settings.readNumEntry( "/P4QTree/cropstart", false ); char eb = settings.readNumEntry( "/P4QTree/cropend", false ); if ( !( sb == 'd' ) ) startdate = QString::null ; if ( !( sb == 'c' ) ) startchange = QString::null ; if ( !eb ) endcd = QString::null ; //convert dates to slashes for DateOps and p4 startdate = startdate.replace( QRegExp( "-" ), "/" ); if ( eb == 'd' ) endcd = endcd.replace( QRegExp( "-" ), "/" ); // Showall. showall = settings.readBoolEntry( "/P4QTree/showall", false ); } void QTreeWindow::Refresh() { //Waitcursor. QCursor myc = cursor(); setCursor( QCursor( Qt::WaitCursor ) ); // Right as the changeview gets deleted, it does something to // give QTW an event, which then looks for the changeview and dies of // shock to find that it's been deleted. Make sure that changeview // is set to NULL before the object itself is deleted, then. if ( !IsEmpty() ) { QWidget* temp = changeview; changeview = NULL; delete temp; delete qtcanvas; } InitMenus(); qtcanvas = new QTreeCanvas ( this, mainfile, client, error, havejobbar, birdseyeview, startchange, startdate, endcd, showall, cachefile ); if ( qtcanvas->IsEmpty() ) { if ( !error->Test() ) error->Set( E_WARN, "Try disabling crop-by-date if it's on." ); error->Set( E_WARN, "Nothing to display!" ); HandleError(); return; } else setCanvas( qtcanvas ); // Enable drag 'n drop support. viewport()->setAcceptDrops( TRUE ); // Establish a new changeview. changeview = new QTreeChangeView( qtcanvas, this ); changeview->show(); // New file icon if needed. if ( !filelabel ) InitIcons(); setCursor( myc ); AdjustGeometry(); } void QTreeWindow::AboutDialog() { QTREEABOUT // see qtreeabout.h QMessageBox::about( this, "About P4QTree", about ); } void QTreeWindow::SaveImage() { // get file name to save to QString fname = QFileDialog::getSaveFileName ( QString::null, "PNG Images (*.png)", this, "P4QTree Save File Dialog", "Save Image" ); if ( fname == QString::null ) return; SaveImage( fname ); } void QTreeWindow::SaveImage( QString fname ) { if ( !fname.endsWith( ".png", FALSE ) ) fname.append( ".png" ); // paint to picture, dump to file QPixmap pix( qtcanvas->size() ); if ( pix.isNull() ) { error->Set( E_FATAL, "Image too large to fit in memory - sorry!" ); HandleError(); return; } QPainter p( &pix ); qtcanvas->UpdateFTexts( 0 ); qtcanvas->drawArea( qtcanvas->rect(), &p ); p.end(); qtcanvas->UpdateFTexts( contentsX() ); bool success = pix.save( fname, "PNG" ); if ( !success ) { error->Set( E_WARN, "Could not save image!" ); HandleError(); } } void QTreeWindow::PrintImage() { QPrinter print; print.setOrientation( QPrinter::Landscape ); print.setResolution( max( qtcanvas->width() / 10.5, qtcanvas->height() / 7.5 ) ); if ( !print.setup( this ) ) return; QPainter p( &print ); qtcanvas->UpdateFTexts( 0 ); qtcanvas->drawArea( qtcanvas->rect(), &p ); p.end(); qtcanvas->UpdateFTexts( contentsX() ); } void QTreeWindow::UsageDialog( QWidget* parent ) { QMessageBox::information ( parent, "P4QTree Usage", QString("Usage: p4qtree [g-opts] filename\n") + "The filename may be in any Perforce syntax.", QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton ); } void QTreeWindow::Preferences() { QTreePrefsDialog* dia = new QTreePrefsDialog( this ); dia->exec(); switch ( dia->Status() ) { case 0: return; case 1: LoadSettings(); if ( legend ) legend->repaint(); qtcanvas->UpdateColors(); qtcanvas->update(); break; case 2: LoadSettings(); Refresh(); } delete dia; } void QTreeWindow::Legend() { if ( !legend ) legend = new QTreeLegendDialog( this ); legend->show(); } // Note that once a "mini-me" window has been spawned, closing it // merely hides it, and calling this function shows it again. void QTreeWindow::SpawnMini() { if ( !birdseyeview ) { if ( !minime ) minime = new QTreeWindow ( this, qtcanvas->cache->main->name.Text(), client, error, true, cachefile ); if ( error->Test() ) { minime = NULL; HandleError(); return; } minime->show(); } } // Overloaded setVBarGeometry to make it fit under the menubar. void QTreeWindow::setVBarGeometry ( QScrollBar& vbar, int x, int y, int w, int h ) { QScrollView::setVBarGeometry ( vbar, x, y + menubar->height(), w, h-menubar->height() ); } // Scroll the changeview and move the filetexts. // The y param is useless, but Qt's signal/slot system requires that// // a slot have the same formal parameters as its signal. void QTreeWindow::HorizScroll( int x, int y ) { changeview->horizontalScrollBar()->setValue( x ); qtcanvas->UpdateFTexts( x ); } // Set up the menus. void QTreeWindow::InitMenus() { menubar = new QMenuBar( this ); // File menu. QPopupMenu* file = new QPopupMenu( this ); file->insertItem( "&Save", this, SLOT( SaveImage() ), CTRL+Key_S ); file->insertItem( "&Print", this, SLOT( PrintImage() ), CTRL+Key_P ); file->insertItem( "&Preferences", this, SLOT( Preferences() ) ); file->insertItem( "&Close", this, SLOT( close() ), Key_Escape ); menubar->insertItem( "&File", file ); // View menu. QPopupMenu* view = new QPopupMenu( this ); if ( havejobbar ) view->insertItem( "Toggle &Jobs", this, SLOT( ToggleJobs() ), CTRL+Key_J ); if (!birdseyeview) view->insertItem( "&Branching-Only View", this, SLOT( SpawnMini() ), CTRL+Key_B ); view->insertItem( "&Refresh", this, SLOT( Refresh() ), Key_F5 ); menubar->insertItem( "&View", view ); // Help menu. QPopupMenu* help = new QPopupMenu( this ); help->insertItem( "&Legend", this, SLOT( Legend() ), Key_F1 ); help->insertItem( "&About P4QTree", this, SLOT( AboutDialog() ) ); menubar->insertItem( "&Help", help ); menubar->show(); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#13 | 4450 | Sam Stafford |
Slight refactoring of "name sort" code... this was to lead to an implementation of "directory graphing" but it didn't pan out. Will need to kludge more than previously thought to make that happen. |
||
#12 | 4428 | Sam Stafford |
Suppress error dialogs if "-o" option specified - dump to stderr instead. (This was a quick hack - hopefully it works.) |
||
#11 | 4419 | Sam Stafford |
Framework for command line alphabetical sort option. No functional change (still need to implement the sort algorithm). |
||
#10 | 4410 | Sam Stafford |
New feature for Laura: "-o OUTPUT" option. When P4QTree is invoked with this option, it'll attempt to dump a PNG file of the graph and then exit. Along the way I removed the silly SVG format thing and switched it back to PNG - nobody can read SVG anyway. Caveat: to write to PNG format, it needs to load the entire image into memory as an uncompressed pixmap, so a large graph very well might not fit in memory. If that happens, you should get a friendly error message and the operation will fail. I can't imagine anybody outside these walls having a use for this option, so I won't bother documenting it. |
||
#9 | 2947 | Sam Stafford |
Give P4QTree the ability to read filelog data from a supplied text file. The syntax is: P4QTree [g-opts] [-F cachefile] depotfile The data in the supplied file will be read before the server is queried for a given filelog. If a required filelog is not found in the file, the server will be queried instead. The most handy application for this that I can see is quickly graphing integ scenarios based on customer-supplied (or even manually-generated) filelogs, when for whatever reason it's not feasible to access the server directly. |
||
#8 | 2457 | Sam Stafford |
Print and Save features. The Print feature is kinda wudgy - it shrinks the canvas down to fit on a single page, which can make it pretty durn small. It also makes hardcoded assumptions about page size because I was having a tough time extracting that info from QPrinter. And lastly, it does a pretty bad job scaling down the text. All that aside, I can see it being useful under a very narrow set of circumstances, so here it is. The Save feature only exports to SVG format, per QPicture's limitations, and it doesn't even do that correctly. It boggles the mind, really. |
||
#7 | 2405 | Sam Stafford | Add support for binary diff programs, as well as "-l" and "-r" for P4Diff and SQUID. | ||
#6 | 2399 | Sam Stafford | The "show all integs" feature in all its glory. | ||
#5 | 2390 | Sam Stafford | Move app icon code to InitIcons(), and maintain the QPixmap with the icon as a class member - this seems to fix that mysterious crashing bug that was introduced when I added the app icon in the first place. | ||
#4 | 2386 | Sam Stafford |
Remove minor crashing bug (crash if click in an empty window), and make infrastructure tweaks to SaveImage. (It still doesn't work.) |
||
#3 | 2381 | Sam Stafford | Make "Old School", which has always been my favorite, the default color scheme. | ||
#2 | 2379 | Sam Stafford |
Submit icon bitmaps. Add an application icon. |
||
#1 | 2377 | Sam Stafford | P4QTree. |