diff --git a/CMakeLists.txt b/CMakeLists.txt index 418d6055..986f7e3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,3 @@ - PROJECT(MacGitver) CMAKE_MINIMUM_REQUIRED(VERSION 3.0) @@ -41,15 +40,181 @@ SET(UTILS_APP_NAME MacGitver) RAD_DEFINE_VERSION(MACGITVER_CORE 0 0 1) +QT_PREPARE( Core Gui Widgets Xml WinMain ) + +INCLUDE_DIRECTORIES( BEFORE + ${MACGITVER_LIBS_SOURCE_DIR} + ${MACGITVER_SOURCE_DIR} + ${MACGITVER_BINARY_DIR} +) + +INCLUDE_DIRECTORIES( AFTER + # Ensure external libs are last + ${HEAVEN_INCLUDE_DIRS} + ${GITWRAP_INCLUDE_DIRS} +) + ADD_SUBDIRECTORY(CfgComp) ADD_SUBDIRECTORY(Libs) -ADD_SUBDIRECTORY(Main) +ADD_SUBDIRECTORY(Ui) ADD_SUBDIRECTORY(Modules) - ADD_SUBDIRECTORY(AddOns) ADD_SUBDIRECTORY(testMacGitverCore) -INCLUDE(InstallRequiredSystemLibraries) +SET( SRC_FILES + Main/main.cpp +) + +SET( RCC_FILES + Main/MacGitver.rcc +) + +QT_RCC( RCC_DATA QRC_FILES ${RCC_FILES} ) + +IF(APPLE) + SET(MACOSX_BUNDLE_GUI_IDENTIFIER "org.macgitver") + SET(MACOSX_BUNDLE_BUNDLE_NAME "MacGitver") + #SET(MACOSX_BUNDLE_NSMAIN_NIB_FILE "MainMenu") + SET(MACOSX_BUNDLE_NSPRINCIPAL_CLASS "NSApplication") + + SET(RES_DIR ${CMAKE_BINARY_DIR}/MacGitver.app/Contents/Resources) + FILE(MAKE_DIRECTORY ${RES_DIR}) + SET(ICNS_FILE ${RES_DIR}/MacGitver.icns) + + ADD_CUSTOM_COMMAND( + OUTPUT ${ICNS_FILE} + COMMAND iconutil -c icns -o ${ICNS_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/Main/MGV.iconset + ) +ENDIF() + +ADD_QT_EXECUTABLE( + MacGitver + MACOSX_BUNDLE + WIN32 + + ${SRC_FILES} + ${QRC_FILES} + ${RCC_DATA} + Main/Info-CMake.plist + ${ICNS_FILE} +) + +SET_TARGET_PROPERTIES( + MacGitver + PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Main/Info-CMake.plist +) + +TARGET_LINK_LIBRARIES( + MacGitver + + LINK_PRIVATE + GitWrap + MacGitverCore +) + +#-------------------------------------------------------------------------------- +# installation stuff below +#-------------------------------------------------------------------------------- + +INSTALL( + TARGETS MacGitver + BUNDLE DESTINATION . COMPONENT Runtime + RUNTIME DESTINATION bin COMPONENT Runtime + LIBRARY DESTINATION lib COMPONENT Runtime +) + +IF( APPLE OR WIN32 ) + # directories to look for dependencies + SET( DIRS ${QT_LIBRARY_DIR} ) + + IF( APPLE ) + SET( CPACK_BUNDLE_NAME "MacGitver-v0.1.0" ) + + LIST( APPEND DIRS "${CMAKE_BINARY_DIR}/MacGitver.app/Contents/Frameworks" ) + + SET(plugin_dest_dir MacGitver.app/Contents/MacOS) + SET(qtconf_dest_dir MacGitver.app/Contents/Resources) + SET(APPS "\${CMAKE_INSTALL_PREFIX}/MacGitver.app") + + SET_TARGET_PROPERTIES( MacGitver + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + ELSEIF( WIN32 ) + + LIST( APPEND DIRS "${CMAKE_BINARY_DIR}/bin" ) + + SET(plugin_dest_dir bin ) + SET(qtconf_dest_dir bin ) + SET(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MacGitver.exe") + + SET_TARGET_PROPERTIES( MacGitver + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin + ) + + ENDIF() + + INSTALL( + DIRECTORY ${QT_PLUGINS_DIR}/imageformats + DESTINATION ${plugin_dest_dir}/plugins/imageformats + COMPONENT Runtime + FILES_MATCHING + PATTERN "*${CMAKE_SHARED_LIBRARY_SUFFIX}" + PATTERN "*d4${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + ) + + INSTALL( + DIRECTORY ${QT_PLUGINS_DIR}/codecs + DESTINATION ${plugin_dest_dir}/plugins/codecs + COMPONENT Runtime + FILES_MATCHING + PATTERN "*${CMAKE_SHARED_LIBRARY_SUFFIX}" + PATTERN "*d4${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + ) + + INSTALL( + DIRECTORY ${QT_PLUGINS_DIR}/iconengines + DESTINATION ${plugin_dest_dir}/plugins/iconengines + COMPONENT Runtime + FILES_MATCHING + PATTERN "*${CMAKE_SHARED_LIBRARY_SUFFIX}" + PATTERN "*d4${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + ) + + INSTALL(CODE " + file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"\") + " COMPONENT Runtime) + + #-------------------------------------------------------------------------------- + # Use BundleUtilities to get all other dependencies for the application to work. + # It takes a bundle or executable along with possible plugins and inspects it + # for dependencies. If they are not system dependencies, they are copied. + + + INSTALL( CODE " + CMAKE_POLICY( SET CMP0011 NEW ) + CMAKE_POLICY( SET CMP0009 NEW ) + file(GLOB_RECURSE PLUGINS + \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/plugins/*${CMAKE_SHARED_LIBRARY_SUFFIX}\" + \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.mgv\") + include(BundleUtilities) + fixup_bundle(\"${APPS}\" \"\${PLUGINS}\" \"${DIRS}\") + " COMPONENT Runtime + ) + +ELSEIF( UNIX ) + # On unix we insall libraries to libexec/macgitver and executables to bin. + # That means, each excutable needs the "$ORIGIN/../libexec/{app}" r-path. + + SET_TARGET_PROPERTIES( MacGitver + PROPERTIES INSTALL_RPATH "\$ORIGIN/../libexec/MacGitver:\$ORIGIN/../lib" + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +) + +ENDIF() + +INCLUDE(InstallRequiredSystemLibraries) diff --git a/Main/CMakeLists.txt b/Main/CMakeLists.txt deleted file mode 100644 index 4d085c29..00000000 --- a/Main/CMakeLists.txt +++ /dev/null @@ -1,174 +0,0 @@ - -PROJECT( MACGITVER ) - -QT_PREPARE( Core Gui Widgets Xml WinMain ) - -INCLUDE_DIRECTORIES( BEFORE - ${MACGITVER_LIBS_SOURCE_DIR} - ${MACGITVER_SOURCE_DIR} - ${MACGITVER_BINARY_DIR} -) - -INCLUDE_DIRECTORIES( AFTER - # Ensure external libs are last - ${HEAVEN_INCLUDE_DIRS} - ${GITWRAP_INCLUDE_DIRS} -) - -SET( SRC_FILES - - main.cpp -) - -SET( RCC_FILES - - MacGitver.rcc -) - -QT_RCC( RCC_DATA QRC_FILES ${RCC_FILES} ) - -SET(MACOSX_BUNDLE_GUI_IDENTIFIER "org.macgitver") -SET(MACOSX_BUNDLE_BUNDLE_NAME "MacGitver") -#SET(MACOSX_BUNDLE_NSMAIN_NIB_FILE "MainMenu") -SET(MACOSX_BUNDLE_NSPRINCIPAL_CLASS "NSApplication") - -IF(APPLE) - SET(RES_DIR ${CMAKE_BINARY_DIR}/MacGitver.app/Contents/Resources) - FILE(MAKE_DIRECTORY ${RES_DIR}) - SET(ICNS_FILE ${RES_DIR}/MacGitver.icns) - - ADD_CUSTOM_COMMAND( - OUTPUT ${ICNS_FILE} - COMMAND iconutil -c icns -o ${ICNS_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/MGV.iconset - ) -ENDIF() - - -ADD_QT_EXECUTABLE( - MacGitver - - MACOSX_BUNDLE - WIN32 - - ${SRC_FILES} - ${QRC_FILES} - ${RCC_DATA} - Info-CMake.plist - ${ICNS_FILE} -) - -SET_TARGET_PROPERTIES( - MacGitver - PROPERTIES - MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info-CMake.plist -) - -TARGET_LINK_LIBRARIES( - MacGitver - - LINK_PRIVATE - GitWrap - MacGitverCore -) - -#-------------------------------------------------------------------------------- -# installation stuff below -#-------------------------------------------------------------------------------- - -INSTALL( - TARGETS MacGitver - BUNDLE DESTINATION . COMPONENT Runtime - RUNTIME DESTINATION bin COMPONENT Runtime - LIBRARY DESTINATION lib COMPONENT Runtime -) - -IF( APPLE OR WIN32 ) - # directories to look for dependencies - SET( DIRS ${QT_LIBRARY_DIR} ) - - IF( APPLE ) - SET( CPACK_BUNDLE_NAME "MacGitver-v0.1.0" ) - - LIST( APPEND DIRS "${CMAKE_BINARY_DIR}/MacGitver.app/Contents/Frameworks" ) - - SET(plugin_dest_dir MacGitver.app/Contents/MacOS) - SET(qtconf_dest_dir MacGitver.app/Contents/Resources) - SET(APPS "\${CMAKE_INSTALL_PREFIX}/MacGitver.app") - - SET_TARGET_PROPERTIES( MacGitver - PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} - ) - - ELSEIF( WIN32 ) - - LIST( APPEND DIRS "${CMAKE_BINARY_DIR}/bin" ) - - SET(plugin_dest_dir bin ) - SET(qtconf_dest_dir bin ) - SET(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MacGitver.exe") - - SET_TARGET_PROPERTIES( MacGitver - PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin - ) - - ENDIF() - - INSTALL( - DIRECTORY ${QT_PLUGINS_DIR}/imageformats - DESTINATION ${plugin_dest_dir}/plugins/imageformats - COMPONENT Runtime - FILES_MATCHING - PATTERN "*${CMAKE_SHARED_LIBRARY_SUFFIX}" - PATTERN "*d4${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE - ) - - INSTALL( - DIRECTORY ${QT_PLUGINS_DIR}/codecs - DESTINATION ${plugin_dest_dir}/plugins/codecs - COMPONENT Runtime - FILES_MATCHING - PATTERN "*${CMAKE_SHARED_LIBRARY_SUFFIX}" - PATTERN "*d4${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE - ) - - INSTALL( - DIRECTORY ${QT_PLUGINS_DIR}/iconengines - DESTINATION ${plugin_dest_dir}/plugins/iconengines - COMPONENT Runtime - FILES_MATCHING - PATTERN "*${CMAKE_SHARED_LIBRARY_SUFFIX}" - PATTERN "*d4${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE - ) - - INSTALL(CODE " - file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"\") - " COMPONENT Runtime) - - #-------------------------------------------------------------------------------- - # Use BundleUtilities to get all other dependencies for the application to work. - # It takes a bundle or executable along with possible plugins and inspects it - # for dependencies. If they are not system dependencies, they are copied. - - - INSTALL( CODE " - CMAKE_POLICY( SET CMP0011 NEW ) - CMAKE_POLICY( SET CMP0009 NEW ) - file(GLOB_RECURSE PLUGINS - \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/plugins/*${CMAKE_SHARED_LIBRARY_SUFFIX}\" - \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.mgv\") - include(BundleUtilities) - fixup_bundle(\"${APPS}\" \"\${PLUGINS}\" \"${DIRS}\") - " COMPONENT Runtime - ) - -ELSEIF( UNIX ) - # On unix we insall libraries to libexec/macgitver and executables to bin. - # That means, each excutable needs the "$ORIGIN/../libexec/{app}" r-path. - - SET_TARGET_PROPERTIES( MacGitver - PROPERTIES INSTALL_RPATH "\$ORIGIN/../libexec/MacGitver:\$ORIGIN/../lib" - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin -) - - -ENDIF() diff --git a/Modules/CMakeLists.txt b/Modules/CMakeLists.txt index b90cfe15..5c742934 100644 --- a/Modules/CMakeLists.txt +++ b/Modules/CMakeLists.txt @@ -7,7 +7,6 @@ INCLUDE_DIRECTORIES( ${HEAVEN_INCLUDE_DIRS} ) -ADD_SUBDIRECTORY(Repository) ADD_SUBDIRECTORY(Welcome) ADD_SUBDIRECTORY(Logging) ADD_SUBDIRECTORY(RepoManLogger) diff --git a/Ui/CMakeLists.txt b/Ui/CMakeLists.txt new file mode 100644 index 00000000..7b91dece --- /dev/null +++ b/Ui/CMakeLists.txt @@ -0,0 +1 @@ +ADD_SUBDIRECTORY(Repository) diff --git a/Ui/Repository/CMakeLists.txt b/Ui/Repository/CMakeLists.txt new file mode 100644 index 00000000..84f7bea5 --- /dev/null +++ b/Ui/Repository/CMakeLists.txt @@ -0,0 +1,35 @@ +SET( SRC_FILES + + CloneRepositoryDlg.cpp + CloneOptionsWdgt.cpp + CreateRepositoryDlg.cpp + ProgressDlg.cpp + RepoInfoModel.cpp + RepoTreeView.cpp + RepositoryContext.cpp +) + +SET( HDR_FILES + + CloneRepositoryDlg.h + CloneOptionsWdgt.hpp + CreateRepositoryDlg.h + ProgressDlg.hpp + RepoInfoModel.hpp + RepoTreeView.hpp + RepositoryContext.hpp +) + +SET( UI_FILES + + CloneRepositoryDlg.ui + CloneOptionsWdgt.ui + CreateRepositoryDlg.ui + ProgressDlg.ui +) + +SET( HID_FILES + + RepositoryModule.hid + RepoTreeViewCtxMenu.hid +) diff --git a/Ui/Repository/CloneOptionsWdgt.cpp b/Ui/Repository/CloneOptionsWdgt.cpp new file mode 100644 index 00000000..f596a257 --- /dev/null +++ b/Ui/Repository/CloneOptionsWdgt.cpp @@ -0,0 +1,33 @@ +#include "CloneOptionsWdgt.hpp" + +#include + + +CloneOptionsWdgt::CloneOptionsWdgt(QWidget *parent) : + QWidget(parent) +{ + setupUi(this); + + // margin is controlled by the parent widget's layout + layout()->setMargin(0); +} + +CloneOptionsWdgt::~CloneOptionsWdgt() +{ +} + +void CloneOptionsWdgt::on_txtCloneMode_currentIndexChanged(int index) +{ + // Note: clone modes are fixed + mCloneMode = static_cast( index ); + grpSubmodules->setEnabled( mCloneMode == cmCheckout ); +} + +void CloneOptionsWdgt::on_chkInitSubmodules_toggled(bool checked) +{ + chkSubmodulesRecursive->setEnabled( checked ); + if( !checked ) + { + chkSubmodulesRecursive->setChecked( false ); + } +} diff --git a/Ui/Repository/CloneOptionsWdgt.hpp b/Ui/Repository/CloneOptionsWdgt.hpp new file mode 100644 index 00000000..c96d6594 --- /dev/null +++ b/Ui/Repository/CloneOptionsWdgt.hpp @@ -0,0 +1,35 @@ +#ifndef UICLONEOPTIONS_HPP +#define UICLONEOPTIONS_HPP + +#include + +#include "ui_CloneOptionsWdgt.h" + + +class CloneOptionsWdgt : public QWidget, Ui::CloneOptionsWdgt +{ + Q_OBJECT + + friend class CloneRepositoryDlg; + +public: + enum CloneMode { + cmCheckout = 0, + cmBare, + cmMirror + }; + +public: + explicit CloneOptionsWdgt(QWidget *parent = 0); + ~CloneOptionsWdgt(); + +private slots: + void on_txtCloneMode_currentIndexChanged(int index); + + void on_chkInitSubmodules_toggled(bool checked); + +private: + CloneMode mCloneMode; +}; + +#endif // UICLONEOPTIONS_HPP diff --git a/Ui/Repository/CloneOptionsWdgt.ui b/Ui/Repository/CloneOptionsWdgt.ui new file mode 100644 index 00000000..7cf545e2 --- /dev/null +++ b/Ui/Repository/CloneOptionsWdgt.ui @@ -0,0 +1,223 @@ + + + CloneOptionsWdgt + + + + 0 + 0 + 630 + 120 + + + + Form + + + + + + + 0 + 0 + + + + + Normal + + + + + Bare + + + + + Mirror + + + + + + + + + + + (default branch) + + + + + + + + 0 + 0 + + + + Repository Type: + + + + + + + + 0 + 0 + + + + QAbstractSpinBox::PlusMinus + + + (full depth) + + + + + + 999999999 + + + 10 + + + + + + + Submodules + + + false + + + + + + + 0 + 0 + + + + Depth: + + + + + + + Initialize submodules + + + true + + + + + + + Recursive + + + true + + + + + + + + 0 + 0 + + + + QAbstractSpinBox::PlusMinus + + + (full depth) + + + Depth: + + + 999999999 + + + 10 + + + + + + + + + + + 0 + 0 + + + + Depth: + + + + + + + Single Branch + + + + + + + + 0 + 0 + + + + Branch: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Line{ color: silver; } + + + QFrame::Plain + + + Qt::Horizontal + + + + + + + + diff --git a/Ui/Repository/CloneRepositoryDlg.cpp b/Ui/Repository/CloneRepositoryDlg.cpp new file mode 100644 index 00000000..f7bbcf13 --- /dev/null +++ b/Ui/Repository/CloneRepositoryDlg.cpp @@ -0,0 +1,239 @@ +/* + * MacGitver + * Copyright (C) 2012 Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#include +#include + +#include "libMacGitverCore/Config/Config.h" + +#include "libGitWrap/Operations/CloneOperation.hpp" + +#include "libMacGitverCore/App/MacGitver.hpp" + +#include "CloneRepositoryDlg.h" +#include "CloneOptionsWdgt.hpp" +#include "ProgressDlg.hpp" + +CloneRepositoryDlg::CloneRepositoryDlg() + : mProgress( NULL ) +{ + setupUi( this ); + + connect( btnBrowseTo, SIGNAL(clicked()), SLOT(onBrowse()) ); + connect( txtPath, SIGNAL(textChanged(QString)), SLOT(checkValid()) ); + + checkValid(); +} + +void CloneRepositoryDlg::onBrowse() +{ + QString fn = txtPath->text(); + if( fn.isEmpty() ) + { + fn = Config::self().get( "Repository/lastUsedDir", QDir::homePath() ).toString(); + } + + QFileDialog* fd = new QFileDialog( this, trUtf8( "Select repository location" ) ); + + #ifdef Q_OS_MAC + fd->setFilter( QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden ); + #else + fd->setFileMode( QFileDialog::Directory ); + #endif + + fd->setDirectory( fn ); + fd->setAcceptMode( QFileDialog::AcceptSave ); + fd->open( this, SLOT(onBrowseHelper(QString)) ); +} + +void CloneRepositoryDlg::onBrowseHelper( const QString& directory ) +{ + if( directory.isEmpty() ) + { + return; + } + + Config::self().set( "Repository/lastUsedDir", directory ); + txtPath->setText( directory ); +} + +void CloneRepositoryDlg::checkValid() +{ + bool okay = !txtPath->text().isEmpty() && + !txtUrl->text().isEmpty(); + + QDir wanted( QDir::toNativeSeparators( txtPath->text() ) ); + if( wanted.exists() ) + { + QStringList sl = wanted.entryList( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot ); + if( sl.count() > 0 ) + { + okay = false; + } + } + + buttonBox->button( QDialogButtonBox::Ok )->setEnabled( okay ); +} + +void CloneRepositoryDlg::accept() +{ + Git::CloneOperation* clone = new Git::CloneOperation( this ); + QString repoName = QUrl( txtUrl->text() ).adjusted( QUrl::NormalizePathSegments | QUrl::StripTrailingSlash ).toString(); + QString targetDir = QUrl( txtPath->text() ).adjusted( QUrl::NormalizePathSegments | QUrl::StripTrailingSlash ).toString(); + + if ( chkAppendRepoName->isChecked() ) + { + targetDir += QString::fromUtf8("/%1") + .arg( QUrl( repoName ).fileName() ); + } + + clone->setBackgroundMode( true ); + clone->setUrl( repoName ); + clone->setPath( targetDir ); + clone->setRemoteAlias( txtRemoteAlias->text() ); + + if ( mCloneOpts ) + { + clone->setBare( mCloneOpts->mCloneMode == CloneOptionsWdgt::cmBare ); + clone->setReference( mCloneOpts->txtBranch->text() ); + clone->setDepth( mCloneOpts->txtCloneDepth->value() ); + } + + mProgress = new ProgressDlg; + mProgress->show(); + mProgress->setCurrent( clone ); + + if( repoName.endsWith( QStringLiteral( ".git" ) ) ) + repoName = repoName.left( repoName.length() - 4 ); + + if( repoName.lastIndexOf( QChar( L'/' ) ) != -1 ) + repoName.remove( 0, repoName.lastIndexOf( QChar( L'/' ) ) + 1 ); + + mAction = tr( "Cloning %1" ).arg( repoName ); + mProgress->beginStep( mAction ); + mStates.clear(); + mStates[ Prepare ] = Current; + mStates[ Download ] = Open; + mStates[ Index ] = Open; + mStates[ Checkout ] = Open; + updateAction(); + + connect( clone, SIGNAL(finished()), this, SLOT(rootCloneFinished()) ); + connect( clone, SIGNAL(transportProgress(quint32,quint32,quint32,quint64)), + this, SLOT(beginDownloading()) ); + connect( clone, SIGNAL(doneDownloading()), this, SLOT(doneDownload()) ); + connect( clone, SIGNAL(doneIndexing()), this, SLOT(doneIndexing()) ); + connect( clone, SIGNAL(doneCheckout()), this, SLOT(doneCheckout()) ); + clone->execute(); +} + +void CloneRepositoryDlg::beginDownloading() +{ + disconnect( sender(), SIGNAL(transportProgress(quint32,quint32,quint32,quint64)), + this, SLOT(beginDownloading()) ); + + mStates[ Prepare ] = Done; + mStates[ Download ] = Current; + mStates[ Index ] = Current; + updateAction(); +} + +void CloneRepositoryDlg::doneDownload() +{ + mStates[ Download ] = Done; + updateAction(); +} + +void CloneRepositoryDlg::doneIndexing() +{ + mStates[ Index ] = Done; + + if( mStates.contains( Checkout ) ) + mStates[ Checkout ] = Current; + + updateAction(); +} + +void CloneRepositoryDlg::doneCheckout() +{ + mStates[ Checkout ] = Done; + updateAction(); +} + +void CloneRepositoryDlg::rootCloneFinished() +{ + Git::BaseOperation* operation = static_cast( sender() ); + Q_ASSERT( operation ); + + if ( operation->result() ) + { + mProgress->setDone(); + return; + } + + QMessageBox::critical(mProgress, tr("Errors while cloning repository."), + tr("Cloning failed:\nGit Message: %1").arg(operation->result().errorText())); + mProgress->reject(); +} + +void CloneRepositoryDlg::updateAction() +{ + QStringList open, current, done; + + QHash< Tasks, QString > t; + t.insert( Prepare, tr( "Prepare" ) ); + t.insert( Index, tr( "Indexing" ) ); + t.insert( Download, tr( "Downloading" ) ); + t.insert( Checkout, tr( "Check out" ) ); + + foreach( Tasks task, mStates.keys() ) + { + QStringList* sl = NULL; + + if( mStates[ task ] == Open ) sl = &open; + else if( mStates[ task ] == Done ) sl = &done; + else if( mStates[ task ] == Current ) sl = ¤t; + + if( sl ) + sl->append( t[ task ] ); + } + + mProgress->setAction( mAction, open, current, done ); +} + +void CloneRepositoryDlg::on_btnCloneopts_toggled(bool checked) +{ + if ( checked ) + { + if ( !mCloneOpts ) + { + mCloneOpts = new CloneOptionsWdgt(); + } + + optsLayout->addWidget( mCloneOpts ); + mCloneOpts->show(); + } + else + { + optsLayout->removeWidget( mCloneOpts ); + if ( mCloneOpts ) + { + mCloneOpts->hide(); + } + layout()->activate(); + resize( width(), minimumSizeHint().height() ); + } +} diff --git a/Ui/Repository/CloneRepositoryDlg.h b/Ui/Repository/CloneRepositoryDlg.h new file mode 100644 index 00000000..0599c13e --- /dev/null +++ b/Ui/Repository/CloneRepositoryDlg.h @@ -0,0 +1,65 @@ +/* + * MacGitver + * Copyright (C) 2012 Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#ifndef CLONE_REPOSITORY_DLG_H +#define CLONE_REPOSITORY_DLG_H + +#include "ui_CloneRepositoryDlg.h" + +#include + +class ProgressDlg; +class CloneOptionsWdgt; + +class CloneRepositoryDlg : public QDialog, Ui::CloneRepositoryDlg +{ + Q_OBJECT +public: + CloneRepositoryDlg(); + +protected: + void accept(); + +private slots: + void onBrowse(); + void onBrowseHelper( const QString& directory ); + void checkValid(); + + void beginDownloading(); + void doneDownload(); + void doneIndexing(); + void doneCheckout(); + void rootCloneFinished(); + + void on_btnCloneopts_toggled(bool checked); + +private: + enum State { Unused, Open, Done, Current }; + enum Tasks { Prepare, Download, Index, Checkout }; + +private: + void updateAction(); + +private: + QPointer mCloneOpts; + + ProgressDlg* mProgress; + QString mAction; + QHash< Tasks, State > mStates; + +}; + +#endif diff --git a/Ui/Repository/CloneRepositoryDlg.ui b/Ui/Repository/CloneRepositoryDlg.ui new file mode 100644 index 00000000..79b596c8 --- /dev/null +++ b/Ui/Repository/CloneRepositoryDlg.ui @@ -0,0 +1,219 @@ + + + CloneRepositoryDlg + + + + 0 + 0 + 602 + 107 + + + + Clone repository + + + true + + + + + + Clone Options + + + true + + + + + + + Append repository name + + + true + + + + + + + + 0 + 0 + + + + To: + + + txtPath + + + + + + + + 0 + 0 + + + + true + + + + + + + ... + + + + + + + + 0 + 0 + + + + true + + + + + + + + 0 + 0 + + + + From: + + + txtUrl + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + 0 + + + + + + + + 0 + 0 + + + + Alias: + + + txtRemoteAlias + + + + + + + + 0 + 0 + + + + + + + origin + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + LineEdit + QLineEdit +
libMacGitverCore/Widgets/LineEdit.h
+
+
+ + txtUrl + txtPath + chkAppendRepoName + + + + + buttonBox + accepted() + CloneRepositoryDlg + accept() + + + 222 + 342 + + + 157 + 274 + + + + + buttonBox + rejected() + CloneRepositoryDlg + reject() + + + 290 + 348 + + + 286 + 274 + + + + +
diff --git a/Ui/Repository/CreateRepositoryDlg.cpp b/Ui/Repository/CreateRepositoryDlg.cpp new file mode 100644 index 00000000..d2d55e52 --- /dev/null +++ b/Ui/Repository/CreateRepositoryDlg.cpp @@ -0,0 +1,112 @@ +/* + * MacGitver + * Copyright (C) 2012 Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#include +#include +#include + +#include "libMacGitverCore/Config/Config.h" +#include "libMacGitverCore/App/MacGitver.hpp" +#include "libMacGitverCore/RepoMan/RepoMan.hpp" + +#include "CreateRepositoryDlg.h" + +CreateRepositoryDlg::CreateRepositoryDlg() +{ + setupUi( this ); + + connect( btnSelectPath, SIGNAL(clicked()), SLOT(onBrowse()) ); + connect( txtPath, SIGNAL(textChanged(QString)), SLOT(checkValid()) ); + + checkValid(); +} + +void CreateRepositoryDlg::onBrowse() +{ + QString fn = txtPath->text(); + if( fn.isEmpty() ) + { + fn = Config::self().get( "Repository/lastUsedDir", QDir::homePath() ).toString(); + } + + QFileDialog* fd = new QFileDialog( this, trUtf8( "Select repository location" ) ); + + #ifdef Q_OS_MAC + fd->setFilter( QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden ); + #else + fd->setFileMode( QFileDialog::Directory ); + #endif + + fd->setDirectory( fn ); + fd->setAcceptMode( QFileDialog::AcceptSave ); + fd->open( this, SLOT(onBrowseHelper(QString)) ); +} + +void CreateRepositoryDlg::onBrowseHelper( const QString& directory ) +{ + if( directory.isEmpty() ) + { + return; + } + + Config::self().set( "Repository/lastUsedDir", directory ); + txtPath->setText( directory ); +} + +void CreateRepositoryDlg::checkValid() +{ + bool okay = !txtPath->text().isEmpty(); + + QDir wanted( txtPath->text() ); + if( wanted.exists() ) + { + QStringList sl = wanted.entryList( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot ); + if( sl.count() > 0 ) + { + okay = false; + } + } + + buttonBox->button( QDialogButtonBox::Ok )->setEnabled( okay ); +} + +void CreateRepositoryDlg::accept() +{ + QString fn = QDir::toNativeSeparators( txtPath->text() ); + bool makeBare = chkMakeBare->isChecked() && chkMakeBare->isEnabled(); + + // ###REPOMAN We should make a RM::Services::InitialiseRepository, so that we can use + // the Git::Repository object that was used to create the repository to stick it + // into the RepoMan. + + Git::Result r; + Git::Repository repo = Git::Repository::create(r, fn, makeBare); + + if( !r || !repo.isValid() ) + { + QMessageBox::critical( this, + trUtf8( "Error" ), + trUtf8( "Failed to create the repository.\n%1" ) + .arg( r.errorText()) ); + return; + } + + MacGitver::log(Log::Normal, trUtf8("Created a new repository at %1").arg(fn)); + + MacGitver::repoMan().open(fn); + + QDialog::accept(); +} diff --git a/Ui/Repository/CreateRepositoryDlg.h b/Ui/Repository/CreateRepositoryDlg.h new file mode 100644 index 00000000..4bc0dac5 --- /dev/null +++ b/Ui/Repository/CreateRepositoryDlg.h @@ -0,0 +1,34 @@ +/* + * MacGitver + * Copyright (C) 2012 Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#pragma once + +#include "ui_CreateRepositoryDlg.h" + +class CreateRepositoryDlg : public QDialog, Ui::CreateRepositoryDlg +{ + Q_OBJECT +public: + CreateRepositoryDlg(); + +protected: + void accept(); + +private slots: + void onBrowse(); + void onBrowseHelper( const QString& directory ); + void checkValid(); +}; diff --git a/Ui/Repository/CreateRepositoryDlg.ui b/Ui/Repository/CreateRepositoryDlg.ui new file mode 100644 index 00000000..b9246194 --- /dev/null +++ b/Ui/Repository/CreateRepositoryDlg.ui @@ -0,0 +1,100 @@ + + + CreateRepositoryDlg + + + + 0 + 0 + 386 + 146 + + + + Create a new repository + + + + + + Location + + + + + + &Path + + + txtPath + + + + + + + + + + Bro&wse + + + + + + + &Bare Repository + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CreateRepositoryDlg + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CreateRepositoryDlg + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Ui/Repository/Module.json b/Ui/Repository/Module.json new file mode 100644 index 00000000..6fd77f7f --- /dev/null +++ b/Ui/Repository/Module.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "Repository" ] +} diff --git a/Ui/Repository/ProgressDlg.cpp b/Ui/Repository/ProgressDlg.cpp new file mode 100644 index 00000000..d6f594ea --- /dev/null +++ b/Ui/Repository/ProgressDlg.cpp @@ -0,0 +1,157 @@ + +#include +#include +#include +#include +#include + +#include "ProgressDlg.hpp" + + +ProgressDlg::ProgressDlg() + : BlueSky::Dialog() + , mDone( false ) +{ + setupUi( this ); + + QPushButton* close = buttonBox->button( QDialogButtonBox::Close ); + close->setEnabled( false ); + connect( close, SIGNAL(clicked()), this, SLOT(close ()) ); + + QPalette p; + p.setColor( QPalette::Base, p.color( QPalette::Window ) ); + p.setColor( QPalette::Text, p.color( QPalette::WindowText ) ); + txtLog->setPalette( p ); + +} + +void ProgressDlg::setAction( const QString& action, + const QStringList& open, + const QStringList& current, + const QStringList& done ) +{ + QString act = action; + + foreach( QString s, done ) + { + act += QStringLiteral( " (" ) % s % QStringLiteral( ")" ); + } + + foreach( QString s, current ) + { + act += QStringLiteral( " (" ) % s % QStringLiteral( ")" ); + } + + foreach( QString s, open ) + { + act += QStringLiteral( " (" ) % s % QStringLiteral( ")" ); + } + + lblAction->setText( act ); +} + +void ProgressDlg::setCurrent(QObject* current) +{ + mCurrent = current; + + connect( mCurrent, SIGNAL(remoteMessage(QString)), + this, SLOT(remoteMessage(QString)) ); + connect( mCurrent, SIGNAL(transportProgress(quint32, quint32, quint32, quint64)), + this, SLOT(transportProgress(quint32, quint32, quint32, quint64)) ); +} + +void ProgressDlg::closeEvent( QCloseEvent* ev ) +{ + if( !mDone ) + { + ev->ignore(); + return; + } + + QDialog::closeEvent( ev ); +} + +void ProgressDlg::transportProgress( quint32 totalObjects, + quint32 indexedObjects, + quint32 receivedObjects, + quint64 receivedBytes ) +{ + QString recv; + if( receivedBytes > 1024 * 1024 * 950 ) /* 950 is so we get 0.9 gb */ + { + recv = QString::number( receivedBytes / (1024*1024*1024.0), 'f', 2 ) % QStringLiteral( " Gb" ); + } + else if( receivedBytes > 1024 * 950 ) + { + recv = QString::number( receivedBytes / (1024*1024.0), 'f', 2 ) % QStringLiteral( " Mb" ); + } + else if( receivedBytes > 950 ) + { + recv = QString::number( receivedBytes / 1024.0, 'f', 2 ) % QStringLiteral( " Kb" ); + } + else + { + recv = QString::number( receivedBytes ); + } + lblTransferSize->setText( recv ); + + progressBar->setRange( 0, totalObjects * 2 ); + progressBar->setValue( indexedObjects + receivedObjects ); +} + +void ProgressDlg::remoteMessage( const QString& msg ) +{ + mRawRemoteMessage += msg; + + QString output; + QChar outputBuffer[ 256 ]; + int outBufPos = 0, outBufLen = 0; + + for( int i = 0; i < mRawRemoteMessage.length(); ++i ) + { + if( mRawRemoteMessage[ i ] == QChar( L'\r' ) ) + { + outBufPos = 0; + } + else if( mRawRemoteMessage[ i ] == QChar( L'\n' ) ) + { + if( outBufLen ) + output += QString( outputBuffer, outBufLen ); + output += QChar( L'\n' ); + outBufPos = outBufLen = 0; + } + else + { + outputBuffer[ outBufPos++] = mRawRemoteMessage[ i ]; + outBufLen = qMax( outBufLen, outBufPos ); + } + } + + if( outBufLen ) + output += QString( outputBuffer, outBufLen ); + + QString log = mBaseLog % QStringLiteral( "
" ) % + output.replace( QChar( L'\n' ), QStringLiteral("
") ).simplified(); + + txtLog->setHtml( log ); +} + +void ProgressDlg::beginStep( const QString& step ) +{ + mBaseLog += tr( "%1
" ).arg( step ); + txtLog->setHtml( mBaseLog ); +} + +void ProgressDlg::finalizeStep() +{ + mBaseLog = txtLog->toHtml() % QStringLiteral( "
" ); + mRawRemoteMessage = QString(); + + txtLog->setHtml( mBaseLog ); +} + +void ProgressDlg::setDone() +{ + mDone = true; + buttonBox->button( QDialogButtonBox::Close )->setEnabled( true ); +} diff --git a/Ui/Repository/ProgressDlg.hpp b/Ui/Repository/ProgressDlg.hpp new file mode 100644 index 00000000..7b1ecd69 --- /dev/null +++ b/Ui/Repository/ProgressDlg.hpp @@ -0,0 +1,43 @@ + +#ifndef MODREPO_PROGRESS_DLG_HPP +#define MODREPO_PROGRESS_DLG_HPP + +#include "libBlueSky/Dialog.hpp" + +#include "ui_ProgressDlg.h" + + +class ProgressDlg + : public BlueSky::Dialog + , private Ui::ProgressDlg +{ + Q_OBJECT +public: + ProgressDlg(); + +public: + void setAction( const QString& action, const QStringList& open, + const QStringList& current, const QStringList& done ); + void setCurrent(QObject* current); + +private slots: + void transportProgress( quint32 totalObjects, quint32 indexedObjects, + quint32 receivedObjects, quint64 receivedBytes ); + void remoteMessage( const QString& msg ); + +public: + void setDone(); + void beginStep( const QString& step ); + void finalizeStep(); + +protected: + void closeEvent( QCloseEvent* ev ); + +private: + bool mDone; + QString mBaseLog; + QObject* mCurrent; + QString mRawRemoteMessage; +}; + +#endif diff --git a/Ui/Repository/ProgressDlg.ui b/Ui/Repository/ProgressDlg.ui new file mode 100644 index 00000000..c2670eb4 --- /dev/null +++ b/Ui/Repository/ProgressDlg.ui @@ -0,0 +1,142 @@ + + + ProgressDlg + + + Qt::ApplicationModal + + + + 0 + 0 + 484 + 291 + + + + + 0 + 0 + + + + Progress + + + false + + + true + + + + + + QDialogButtonBox::Close + + + + + + + + 0 + 0 + + + + + 0 + 60 + + + + Log + + + + QLayout::SetDefaultConstraint + + + 0 + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + + + + + Current operation + + + + -1 + + + 2 + + + 3 + + + + + + 0 + 0 + + + + + + + + + + + + + + + <b>Progress</(b> + + + + + + + <b>Action</b> + + + + + + + + + + + + + + + + + + diff --git a/Ui/Repository/RepoInfoModel.cpp b/Ui/Repository/RepoInfoModel.cpp new file mode 100644 index 00000000..bcbee721 --- /dev/null +++ b/Ui/Repository/RepoInfoModel.cpp @@ -0,0 +1,231 @@ +/* + * MacGitver + * Copyright (C) 2012 Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#include "libMacGitverCore/App/MacGitver.hpp" +#include "libMacGitverCore/Config/Config.h" +#include "libMacGitverCore/RepoMan/RepoMan.hpp" + +#include "libHeavenIcons/IconRef.hpp" +#include "libHeavenIcons/Icon.hpp" + +#include "RepoInfoModel.hpp" + +RepoInfoModel::RepoInfoModel() +{ + mRepoMan = &MacGitver::repoMan(); + + connect( mRepoMan, &RM::RepoMan::repositoryDeactivated, + this, &RepoInfoModel::invalidateRepository); + + connect( mRepoMan, &RM::RepoMan::repositoryActivated, + this, &RepoInfoModel::invalidateRepository); + + connect( mRepoMan, &RM::RepoMan::repositoryOpened, + this, &RepoInfoModel::invalidateRepository); +} + +int RepoInfoModel::rowCount( const QModelIndex& parent ) const +{ + if( parent.isValid() ) + { + RM::Repo* info = index2Info( parent ); + return info ? info->submodules().count() : 0; + } + else + { + return mRepoMan->repositories().count(); + } +} + +int RepoInfoModel::columnCount( const QModelIndex& parent ) const +{ + return 1; +} + +QVariant RepoInfoModel::data( const QModelIndex& index, int role ) const +{ + if( !index.isValid() ) return QVariant(); + + RM::Repo* info = index2Info( index ); + if( info ) + { + switch(role) { + case Qt::DisplayRole: + return info->displayAlias(); + + case Qt::DecorationRole: + { + Heaven::IconRef icon = info->icon().clone(); + QFont f; + int size = (f.pixelSize() <= 0) ? f.pointSize() : f.pixelSize(); + icon.setSize( qMax(size + 2, 16) ); + return icon.icon().pixmap(); + } + + case Qt::FontRole: + { + QFont f1, f2; + f2.setBold( true ); + return info->isActive() ? f2 : f1; + } + + case IsActive: + return info->isActive(); + + case Qt::ToolTipRole: + return trUtf8( + "" + "" + "" + "" + "" + "
%1
" + "
" + "
Branch: %2
" + "" + "") + .arg(info->displayName()) + .arg(info->branchDisplay()); + } + } + + return QVariant(); +} + +QModelIndex RepoInfoModel::index( int row, int column, const QModelIndex& parent ) const +{ + Q_UNUSED(column); + RM::Repo::List list; + + if( parent.isValid() ) + { + RM::Repo* infoParent = index2Info( parent ); + if( !infoParent ) + { + return QModelIndex(); + } + + foreach (RM::Repo* r, infoParent->submodules()) { + list.append(r); + } + } + else + { + list = mRepoMan->repositories(); + } + + if( row >= list.count() || row < 0 ) + { + return QModelIndex(); + } + + return info2Index( list.at( row ) ); +} + +QModelIndex RepoInfoModel::parent( const QModelIndex& child ) const +{ + if( !child.isValid() ) + { + return QModelIndex(); + } + + RM::Repo* info = index2Info( child ); + if( !info || !info->parentRepository() ) + { + return QModelIndex(); + } + + return info2Index( info->parentRepository() ); +} + +RM::Repo* RepoInfoModel::index2Info( const QModelIndex& index ) const +{ + return static_cast< RM::Repo* >( index.internalPointer() ); +} + +QModelIndex RepoInfoModel::info2Index(RM::Repo* info) const +{ + int row = 0; + + if( !info ) + { + return QModelIndex(); + } + + if( info->parentRepository() ) + { + RM::Repo::Set sms = info->parentRepository()->submodules(); + RM::Repo::List list; + foreach (RM::Repo* r, sms) { + list.append(r); + } + row = list.indexOf(info); + } + else + { + row = mRepoMan->repositories().indexOf( info ); + } + + if( row == -1 ) + { + return QModelIndex(); + } + + return createIndex( row, 0, info ); +} + +void RepoInfoModel::invalidateRepository(RM::Repo *info) +{ + if ( !info ) return; + + QModelIndex index = info2Index( info ); + emit dataChanged( index, index ); +} + +void RepoInfoModel::repositoryOpened(RM::Repo *info) +{ + if (!info || info->parentRepository()) { + return; + } + + // This connection is wrong. There could be any children added below the repository (i.e. + // HEAD, SubModule, CollectionNode). But the slot will unconditionally reserve a new space + // for a submodule... + connect(info, &RM::Repo::childAdded, + this, &RepoInfoModel::repositoryChildAdded); + + // we add a row just at the end of the root. This is stupid. But that's the way it works when + // a model actually isn't a model... + + int row = mRepoMan->repositories().count() - 1; + emit beginInsertRows(QModelIndex(), row, row); + emit endInsertRows(); +} + +void RepoInfoModel::repositoryChildAdded(RM::Repo* parent, RM::Repo* child) +{ + QModelIndex parentIndex = info2Index(parent); + + // we add a row just at the end of the root. This is stupid. But that's the way it works when + // a model actually isn't a model... + + int row = parent->submodules().count() - 1; + emit beginInsertRows(parentIndex, row, row); + emit endInsertRows(); +} diff --git a/Ui/Repository/RepoInfoModel.hpp b/Ui/Repository/RepoInfoModel.hpp new file mode 100644 index 00000000..31e261f8 --- /dev/null +++ b/Ui/Repository/RepoInfoModel.hpp @@ -0,0 +1,59 @@ +/* + * MacGitver + * Copyright (C) 2012 Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#ifndef MGV_REPO_INFO_MODEL_HPP +#define MGV_REPO_INFO_MODEL_HPP + +#include + +namespace RM +{ + class Repo; + class RepoMan; +} + +class RepoInfoModel : public QAbstractItemModel +{ + Q_OBJECT +public: + RepoInfoModel(); + + enum ExtraRoles + { + IsActive = Qt::UserRole + }; + +public: + int rowCount( const QModelIndex& parent = QModelIndex() ) const; + int columnCount( const QModelIndex& parent = QModelIndex() ) const; + QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; + QModelIndex index( int row, int column, const QModelIndex& parent = QModelIndex() ) const; + QModelIndex parent( const QModelIndex& child ) const; + +public: + RM::Repo* index2Info( const QModelIndex& index ) const; + QModelIndex info2Index( RM::Repo* info ) const; + +public slots: + void invalidateRepository(RM::Repo* info); + void repositoryOpened(RM::Repo* info); + void repositoryChildAdded(RM::Repo* parent, RM::Repo* child); + +private: + RM::RepoMan* mRepoMan; +}; + +#endif diff --git a/Ui/Repository/RepoTreeView.cpp b/Ui/Repository/RepoTreeView.cpp new file mode 100644 index 00000000..4e22187c --- /dev/null +++ b/Ui/Repository/RepoTreeView.cpp @@ -0,0 +1,149 @@ +/* + * MacGitver + * Copyright (C) 2012 Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#include +#include + +#include "libMacGitverCore/Widgets/TreeViewCtxMenu.hpp" + +#include "libMacGitverCore/App/MacGitver.hpp" +#include "libMacGitverCore/RepoMan/RepoMan.hpp" +#include "libMacGitverCore/RepoMan/Repo.hpp" + +#include "RepoTreeView.hpp" +#include "RepoInfoModel.hpp" +#include "RepositoryContext.hpp" + +RepoTreeView::RepoTreeView() + : ContextView("RepoTreeView") +{ + setViewName( trUtf8( "Repositories" ) ); + setFlags( ProvidesContexts ); + setupActions( this ); + + mRepos = new TreeViewCtxMenu; + mRepos->setFrameShape( QFrame::NoFrame ); + #ifdef Q_OS_MACX + mRepos->setAttribute( Qt::WA_MacShowFocusRect, false ); + #endif + mModel = new RepoInfoModel(); + + QSortFilterProxyModel* sfpm = new QSortFilterProxyModel(this); + sfpm->setSourceModel(mModel); + sfpm->setSortRole(Qt::DisplayRole); + sfpm->sort(0); + mRepos->setModel(sfpm); + + mRepos->setIndentation( 12 ); + mRepos->expandAll(); + mRepos->setHeaderHidden( true ); + + setWidget( mRepos ); + + connect( mRepos, SIGNAL(contextMenu(QModelIndex,QPoint)), + this, SLOT(contextMenu(QModelIndex,QPoint)) ); + + connect( &MacGitver::repoMan(), &RM::RepoMan::repositoryActivated, + this, &RepoTreeView::onRepoActivated); + + connect( &MacGitver::repoMan(), &RM::RepoMan::repositoryDeactivated, + this, &RepoTreeView::onRepoDeactivated); +} + +QModelIndex RepoTreeView::deeplyMapToSource( QModelIndex current ) const +{ + while( current.isValid() ) + { + const QAbstractProxyModel* apm = qobject_cast< const QAbstractProxyModel* >( current.model() ); + if( !apm ) + return current; + + current = apm->mapToSource( current ); + } + + return QModelIndex(); +} + +void RepoTreeView::contextMenu( const QModelIndex& index, const QPoint& globalPos ) +{ + RM::Repo* info = mModel->index2Info(deeplyMapToSource(index)); + + if (info) { + Heaven::Menu* menu = info->isSubModule() ? menuCtxMenuSMRepo : menuCtxMenuRepo; + menu->setActivationContext( info ); + actActivate->setEnabled( !info->isActive() ); + + menu->showPopup( globalPos ); + } +} + +void RepoTreeView::onCtxActivate() +{ + Heaven::Action* action = qobject_cast< Heaven::Action* >( sender() ); + if( action ) + { + RM::Repo* info = qobject_cast< RM::Repo* >( action->activatedBy() ); + if( info ) + { + MacGitver::repoMan().activate( info ); + } + } +} + +void RepoTreeView::onCtxClose() +{ + Heaven::Action* action = qobject_cast< Heaven::Action* >( sender() ); + if( action ) + { + RM::Repo* info = qobject_cast< RM::Repo* >( action->activatedBy() ); + if( info ) + { + info->close(); + } + } +} + +void RepoTreeView::onRepoActivated(RM::Repo* repo) +{ + BlueSky::ContextKeys keys = mkKeys(); + keys.set("RepoName", repo->path()); + + bool isNewContext = false; + BlueSky::ViewContext* ctx = contextFor(keys, &isNewContext); + + if (isNewContext) { + RepositoryContext* ctx2 = qobject_cast< RepositoryContext* >(ctx); + Q_ASSERT(ctx2); + + ctx2->setRepository(repo); + } + + setCurrentContext(ctx); +} + +void RepoTreeView::onRepoDeactivated(RM::Repo* repo) +{ + RepositoryContext* ctx = qobject_cast< RepositoryContext* >(currentContext()); + + if (ctx && ctx->repository() == repo) { + setCurrentContext( NULL ); + } +} + +BlueSky::ViewContext* RepoTreeView::createContextObject() const +{ + return new RepositoryContext; +} diff --git a/Ui/Repository/RepoTreeView.hpp b/Ui/Repository/RepoTreeView.hpp new file mode 100644 index 00000000..5904fdbd --- /dev/null +++ b/Ui/Repository/RepoTreeView.hpp @@ -0,0 +1,65 @@ +/* + * MacGitver + * Copyright (C) 2012 Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#ifndef MGV_REPO_TREE_VIEW_HPP +#define MGV_REPO_TREE_VIEW_HPP + +#include "libBlueSky/Contexts.hpp" + +#include "hic_RepoTreeViewCtxMenu.h" + +class QModelIndex; + +namespace RM +{ + class Repo; +} + +class RepoInfoModel; +class TreeViewCtxMenu; + +class RepoTreeView + : public BlueSky::ContextView + , private RepoTreeViewCtxMenu +{ + Q_OBJECT +public: + RepoTreeView(); + +private: + void setupUi(); + +private slots: // from actions + void onCtxActivate(); + void onCtxClose(); + +private slots: // from mRepos + void contextMenu( const QModelIndex& index, const QPoint& globalPos ); + +private slots: // for MacGitver::repoMan() + void onRepoActivated(RM::Repo* repo); + void onRepoDeactivated(RM::Repo* repo); + +private: + QModelIndex deeplyMapToSource( QModelIndex current ) const; + BlueSky::ViewContext* createContextObject() const; + +private: + RepoInfoModel* mModel; + TreeViewCtxMenu* mRepos; +}; + +#endif diff --git a/Ui/Repository/RepoTreeViewCtxMenu.hid b/Ui/Repository/RepoTreeViewCtxMenu.hid new file mode 100644 index 00000000..b2324935 --- /dev/null +++ b/Ui/Repository/RepoTreeViewCtxMenu.hid @@ -0,0 +1,54 @@ +/* + * MacGitver + * Copyright (C) 2012 Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +Ui RepoTreeViewCtxMenu { + + Action Activate { + Text "&Activate"; + StatusToolTip "Make this repository the active one."; + ConnectTo onCtxActivate(); + }; + + Action Close { + Text "&Close"; + StatusToolTip "Close this repository"; + ConnectTo onCtxClose(); + }; + + Menu CtxMenuRepo { + + Action Activate; + Separator; + + MergePlace RemotesMP; + MergePlace FetchMP; + Separator; + + Action Close; + + }; + + Menu CtxMenuSMRepo { + + Action Activate; + Separator; + + MergePlace RemotesMP; + MergePlace FetchMP; + Separator; + }; + +}; diff --git a/Ui/Repository/RepositoryContext.cpp b/Ui/Repository/RepositoryContext.cpp new file mode 100644 index 00000000..b2a713ad --- /dev/null +++ b/Ui/Repository/RepositoryContext.cpp @@ -0,0 +1,34 @@ +/* + * libHeaven - A Qt-based ui framework for strongly modularized applications + * Copyright (C) 2012-2013 The MacGitver-Developers + * + * (C) Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#include "RepositoryContext.hpp" + +RepositoryContext::RepositoryContext() + : mRepo( NULL ) +{ +} + +void RepositoryContext::setRepository(RM::Repo* repo) +{ + mRepo = repo; +} + +RM::Repo* RepositoryContext::repository() const +{ + return mRepo; +} diff --git a/Ui/Repository/RepositoryContext.hpp b/Ui/Repository/RepositoryContext.hpp new file mode 100644 index 00000000..e26b8f12 --- /dev/null +++ b/Ui/Repository/RepositoryContext.hpp @@ -0,0 +1,42 @@ +/* + * libHeaven - A Qt-based ui framework for strongly modularized applications + * Copyright (C) 2012-2013 The MacGitver-Developers + * + * (C) Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#ifndef REPOSITORY_CONTEXT_HPP +#define REPOSITORY_CONTEXT_HPP + +#include "libMacGitverCore/MacGitver/IRepositoryContext.hpp" + +#include "libBlueSky/Contexts.hpp" + +class RepositoryContext : public BlueSky::ViewContext, public IRepositoryContext +{ + Q_OBJECT + Q_INTERFACES( IRepositoryContext ) + +public: + RepositoryContext(); + +public: + void setRepository(RM::Repo* repo); + RM::Repo* repository() const; + +private: + RM::Repo* mRepo; +}; + +#endif diff --git a/Ui/Repository/RepositoryModule.cpp b/Ui/Repository/RepositoryModule.cpp new file mode 100644 index 00000000..3b443da1 --- /dev/null +++ b/Ui/Repository/RepositoryModule.cpp @@ -0,0 +1,155 @@ +/* + * MacGitver + * Copyright (C) 2012-2013 The MacGitver-Developers + * + * (C) Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#include +#include + +#include "libBlueSky/Application.hpp" +#include "libBlueSky/Windows.hpp" + +#include "libMacGitverCore/Config/Config.h" +#include "libMacGitverCore/App/MacGitver.hpp" +#include "libMacGitverCore/RepoMan/RepoMan.hpp" + +#include "RepositoryModule.h" +#include "RepoTreeView.hpp" +#include "CloneRepositoryDlg.h" +#include "CreateRepositoryDlg.h" + +RepositoryModule::RepositoryModule() + : ConfigUser( "Repository" ) +{ +} + +void RepositoryModule::initialize() +{ + setupActions( this ); + acRepositoryMenuAC->mergeInto( "RepositoryMenuMP" ); + + connect( damRecentlyUsed, SIGNAL(entryTriggered(QVariant)), + this, SLOT(onRecentRepositoryOpen(QVariant)) ); + + mMostRecentlyUsed = configGet( "MRU", QStringList() ); + + connect(&MacGitver::repoMan(), SIGNAL(repositoryOpened(RM::Repo*)), + this, SLOT(onCoreRepoOpen(RM::Repo*))); + + updateMostRecentlyUsedMenu(); + + registerView(tr("Repository")); +} + +void RepositoryModule::deinitialize() +{ + unregisterView(); +} + +void RepositoryModule::onRepositoryCreate() +{ + CreateRepositoryDlg().exec(); +} + +void RepositoryModule::onRepositoryOpen() +{ + QWidget* parent = BlueSky::Application::instance()->primaryWindow(); + QFileDialog *fd = new QFileDialog( parent ); + #ifdef Q_OS_MAC + fd->setFilter(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden); + #else + fd->setFileMode(QFileDialog::Directory); + #endif + + QString lastUsedDir = Config::self().get( "Repository/lastUsedDir", "#" ).toString(); + if( lastUsedDir != QStringLiteral( "#" ) ) + { + fd->setDirectory( lastUsedDir ); + } + + fd->setWindowTitle( tr("Open a Git repository") ); + + fd->open( this, SLOT(onRepositoryOpenHelper()) ); + + #ifdef Q_OS_MAC + // workaround for osx sheets without a parent + if (parent == 0) + fd->exec(); + #endif +} + +void RepositoryModule::onRepositoryOpenHelper() +{ + QFileDialog *fd = qobject_cast(sender()); + Q_ASSERT(fd != 0); + + if ( fd->selectedFiles().isEmpty() ) + { + return; + } + + //! @todo error handling + Git::Result r; + QString repoDir = Git::Repository::discover(r, fd->selectedFiles().first()); + if( repoDir.isEmpty() ) + { + return; + } + + MacGitver::repoMan().open(repoDir); + + // ###REPOMAN This should move to an eventlistener, which is invoked by RepoMan for every + // opened repository. This will then also boost the priority in the LRU menu for + // repositories opened through it. + + // If we successfully loaded the repository at that directory, + // we store the repository's work dir as "lastUsedDir". + // If it is a bare repository, we store the repository's directory. + Config::self().set("Repository/lastUsedDir", repoDir); + // repo.isBare() ? repoDir : repo.basePath() ); +} + +void RepositoryModule::onRepositoryClone() +{ + CloneRepositoryDlg().exec(); +} + +void RepositoryModule::onRecentRepositoryOpen( const QVariant& path ) +{ + QString repoPath = path.toString(); + MacGitver::repoMan().open( repoPath ); +} + +void RepositoryModule::onCoreRepoOpen(RM::Repo* repo) +{ + if( repo->isSubModule() ) + { + return; + } + + QString path = repo->path(); + mMostRecentlyUsed.removeAll( path ); + mMostRecentlyUsed.prepend( path ); + + configSet( "MRU", mMostRecentlyUsed ); + updateMostRecentlyUsedMenu(); +} + +void RepositoryModule::updateMostRecentlyUsedMenu() +{ + damRecentlyUsed->setMode( Heaven::DAMergerAdvancedList ); + damRecentlyUsed->addStringList( mMostRecentlyUsed ); +} diff --git a/Ui/Repository/RepositoryModule.h b/Ui/Repository/RepositoryModule.h new file mode 100644 index 00000000..9817c4ec --- /dev/null +++ b/Ui/Repository/RepositoryModule.h @@ -0,0 +1,62 @@ +/* + * MacGitver + * Copyright (C) 2012-2013 The MacGitver-Developers + * + * (C) Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +#ifndef MGV_MODULE_REPOSITORY_H +#define MGV_MODULE_REPOSITORY_H + +#include "libMacGitverCore/Config/ConfigUser.h" + +#include "hic_RepositoryModule.h" + +namespace RM +{ + class Repo; +} + +class RepositoryModule + : public RepositoryActions + , private ConfigUser +{ + Q_OBJECT + +public: + RepositoryModule(); + +public: + void initialize(); + void deinitialize(); + +private slots: + void onRepositoryCreate(); + void onRepositoryClone(); + void onRepositoryOpen(); + void onRecentRepositoryOpen( const QVariant& path ); + +private slots: + void onRepositoryOpenHelper(); + void onCoreRepoOpen(RM::Repo* repo); + +private: + void updateMostRecentlyUsedMenu(); + +private: + QStringList mMostRecentlyUsed; + Git::Repository mRepo; +}; + +#endif diff --git a/Ui/Repository/RepositoryModule.hid b/Ui/Repository/RepositoryModule.hid new file mode 100644 index 00000000..b401adfa --- /dev/null +++ b/Ui/Repository/RepositoryModule.hid @@ -0,0 +1,69 @@ +/* + * MacGitver + * Copyright (C) 2012-2013 The MacGitver-Developers + * + * (C) Sascha Cunz + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * 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, see . + * + */ + +Ui RepositoryActions { + + Action RepositoryOpen { + Text "Open..."; + Shortcut "Ctrl+O"; + ShortcutContext ApplicationShortcut; + ConnectTo onRepositoryOpen(); + }; + + Action RepositoryClone { + Text "&Clone..."; + ConnectTo onRepositoryClone(); + }; + + Action RepositoryCreate { + Text "&New..."; + Shortcut "Ctrl+N"; + ConnectTo onRepositoryCreate(); + }; + + Menu RepoOpenRecent { + Text "Recent &Repositories"; + + DynamicActionMerger RecentlyUsed { }; + + }; + + Menu MnuActiveRepo { + Text "Active repository"; + + MergePlace RemotesMP; + Separator; + + MergePlace FetchMP; + MergePlace PushMP; + }; + + Container RepositoryMenuAC { + + Action RepositoryCreate; + Action RepositoryOpen; + Menu RepoOpenRecent; + Separator; + + Action RepositoryClone; + Separator; + + Menu MnuActiveRepo; + }; + +};