├── .gitignore ├── AUTHORS ├── CMakeLists.txt ├── ChangeLog ├── LICENSE ├── README.md ├── TODO ├── cmake └── FindMsgfmt.cmake ├── config.h.cmake ├── icons ├── CMakeLists.txt ├── carbon.svg ├── carbon16.png ├── carbon22.png ├── carbon32.png └── carbon48.png ├── scripts ├── CMakeLists.txt ├── carbon-runner.cmake └── carbon-terminate ├── support ├── CMakeLists.txt ├── dialog.cpp ├── dialog.h ├── messagebox.cpp ├── messagebox.h ├── pagewidget.cpp ├── pagewidget.h ├── pathrequester.cpp ├── pathrequester.h ├── squeezedtextlabel.cpp ├── squeezedtextlabel.h ├── utils.cpp └── utils.h └── ui ├── CMakeLists.txt ├── basicitemdelegate.cpp ├── basicitemdelegate.h ├── carbon.desktop ├── carbon.qrc ├── default.sync ├── default.sync.exclude ├── excludefile.cpp ├── excludefile.h ├── excludewidget.cpp ├── excludewidget.h ├── excludewidget.ui ├── generaloptionswidget.cpp ├── generaloptionswidget.h ├── generaloptionswidget.ui ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── rsyncoptionswidget.cpp ├── rsyncoptionswidget.h ├── rsyncoptionswidget.ui ├── runnerdialog.cpp ├── runnerdialog.h ├── runnerwidget.ui ├── session.cpp ├── session.h ├── sessiondialog.cpp ├── sessiondialog.h ├── sessionwidget.cpp ├── sessionwidget.h ├── sessionwidget.ui ├── treewidget.cpp └── treewidget.h /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Craig Drummond 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(carbon) 2 | cmake_minimum_required(VERSION 2.4.0) 3 | # Keep CMake quiet... 4 | if(COMMAND cmake_policy) 5 | cmake_policy(SET CMP0003 NEW) 6 | endif(COMMAND cmake_policy) 7 | 8 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 9 | set(CPACK_GENERATOR "TBZ2") 10 | set(CPACK_SOURCE_GENERATOR "TBZ2") 11 | set(CPACK_PACKAGE_VERSION_MAJOR "0") 12 | set(CPACK_PACKAGE_VERSION_MINOR "1") 13 | set(CPACK_PACKAGE_VERSION_PATCH "0") 14 | set(CARBON_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") 15 | set(CARBON_VERSION_FULL "${CARBON_VERSION}.${CPACK_PACKAGE_VERSION_PATCH}") 16 | set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CARBON_VERSION_FULL}") 17 | set(CPACK_SOURCE_IGNORE_FILES "/.svn/;/.git/;.gitignore;.project;CMakeLists.txt.user") 18 | include(CPack) 19 | set(CARBON_EXTENSION ".sync") 20 | set(CARBON_INFO_EXTENSION ".info") 21 | set(CARBON_LOCK_EXTENSION ".lock") 22 | set(CARBON_LOG_EXTENSION ".log") 23 | set(CARBON_EXCLUDE_EXTENSION ".exclude") 24 | set(CARBON_PREFIX "CARBON:") 25 | set(CARBON_MSG_PREFIX "INFO:") 26 | set(CARBON_ERROR_PREFIX "ERROR:") 27 | set(CARBON_GUI_PARENT "CARBON_GUI_PARENT") 28 | set(CARBON_LOWEST_UID 1000) 29 | set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) 30 | include(CheckFunctionExists) 31 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 32 | 33 | set(SHARE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/share" 34 | CACHE PATH "Define install directory for read-only architecture-independent data") 35 | 36 | find_package(Qt5Widgets REQUIRED) 37 | set(QTLIBS ${Qt5Core_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5Gui_LIBRARIES}) 38 | set(QTINCLUDES ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ${Qt5Core_INCLUDE_DIRS}) 39 | add_definitions(${Qt5Widgets_DEFINITIONS}) 40 | set(CMAKE_CXX_FLAGS "${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") 41 | find_package(Qt5DBus) 42 | if (Qt5DBus_FOUND) 43 | set(QT_QTDBUS_FOUND 1) # required for config.h !!! 44 | set(QTLIBS ${QTLIBS} ${Qt5DBus_LIBRARIES}) 45 | set(QTINCLUDES ${QTINCLUDES} ${Qt5DBus_INCLUDE_DIRS}) 46 | add_definitions(${Qt5DBus_DEFINITIONS}) 47 | endif (Qt5DBus_FOUND) 48 | set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}") 49 | set(CMAKE_CXX_STANDARD 11) 50 | if (Qt5_POSITION_INDEPENDENT_CODE) 51 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 52 | else () 53 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") 54 | endif () 55 | 56 | configure_file(config.h.cmake ${CMAKE_BINARY_DIR}/config.h) 57 | 58 | add_subdirectory(support) 59 | add_subdirectory(ui) 60 | add_subdirectory(scripts) 61 | add_subdirectory(icons) 62 | 63 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | ----- 3 | 1. Initial release. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Carbon 2 | 3 | Carbon is a simple front-end to rsync. Carbon supports the following features: 4 | 5 | 1. Multiple sessions. (A session is a source -> destination synchronisation). 6 | 2. Dry-run a synchronisation. 7 | 3. Setting of various rsync options. 8 | 4. Ability to specify a set of patterns to exclude from synchronisation. 9 | 5. Increments older then a specified number of days are deleted. 10 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Advanced: 2 | Compress file data --compress 3 | Don't map uid/gid values --numeric-ids 4 | Copy hardlinks as hardlinks --hard-links 5 | Protect remote args --protect-args 6 | ? is this require, as script quotes args? 7 | 8 | Extra: 9 | Execute command before rsync: [......] Halt on failure [x] 10 | Execute command afger rsync: [.....] On rsync error only [x] 11 | 12 | * SessionDialog 13 | Add NOTES detailing URLs 14 | Add help text to most pages 15 | Always select 'Don't delete old increments' if dest is remote. 16 | Dont allow advanced scheduling if run-as-root 17 | If run-as-root, then cron scripts need to *not* call su 18 | 19 | Add Adanced settings page: 20 | Before sync: 21 | Run: [...................] [F] 22 | Terminate on failure [x] 23 | 24 | After sync: 25 | Run on success: [...............] [F] 26 | Run on failure: [...............] [F] 27 | 28 | Run session with admin privaleges 29 | 30 | * Use form-layout, and BuddyLabel's, for al UI??? 31 | 32 | * Dont create backup folder if there are no changes. 33 | 34 | -------------------------------------------------------------------------------- /cmake/FindMsgfmt.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # - Try to find the msgfmt executeable 3 | # 4 | # It will set the following variables: 5 | # 6 | # MSGFMT_FOUND 7 | # MSGFMT_EXECUTABLE 8 | # 9 | ################################################################### 10 | # 11 | # Copyright (c) 2006, Andreas Schneider 12 | # 13 | # This program is free software; you can redistribute it and/or 14 | # modify it under the terms of the GNU General Public License 15 | # as published by the Free Software Foundation; either version 2 16 | # of the License, or (at your option) any later version. 17 | # 18 | # This program is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, 26 | # Boston, MA 02110-1301, USA. 27 | # 28 | ################################################################### 29 | # 30 | # Copyright (c) 2006 Andreas Schneider 31 | # All rights reserved. 32 | # 33 | # Redistribution and use in source and binary forms, with or without 34 | # modification, are permitted provided that the following conditions 35 | # are met: 36 | # 37 | # * Redistributions of source code must retain the above copyright 38 | # notice, this list of conditions and the following disclaimer. 39 | # 40 | # * Redistributions in binary form must reproduce the above copyright 41 | # notice, this list of conditions and the following disclaimer in 42 | # the documentation and/or other materials provided with the 43 | # distribution. 44 | # 45 | # * Neither the name of the cmake-modules nor the names of its 46 | # contributors may be used to endorse or promote products derived 47 | # from this software without specific prior written permission. 48 | # 49 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 50 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 51 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 52 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 53 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 54 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 55 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 56 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 57 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 58 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 59 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 60 | # POSSIBILITY OF SUCH DAMAGE. 61 | # 62 | 63 | IF (MSGFMT_EXECUTABLE) 64 | # in cache alread? 65 | SET(MSGFMT_FOUND TRUE) 66 | ELSE (MSGFMT_EXECUTABLE) 67 | IF (UNIX) 68 | FIND_PROGRAM(MSGFMT_EXECUTABLE 69 | NAMES 70 | msgfmt 71 | PATHS 72 | /usr/bin 73 | /usr/local/bin 74 | ) 75 | 76 | IF(MSGFMT_EXECUTABLE) 77 | SET(MSGFMT_FOUND TRUE) 78 | ELSE(MSGFMT_EXECUTABLE) 79 | MESSAGE(FATAL_ERROR "msgfmt not found - po files can't be processed") 80 | ENDIF(MSGFMT_EXECUTABLE) 81 | 82 | MARK_AS_ADVANCED(MSGFMT_EXECUTABLE) 83 | ENDIF(UNIX) 84 | ENDIF (MSGFMT_EXECUTABLE) 85 | 86 | # vim:et ts=2 sw=2 comments=\:\# 87 | -------------------------------------------------------------------------------- /config.h.cmake: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H__ 2 | #define __CONFIG_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #define CARBON_VERSION "@CARBON_VERSION_FULL@" 26 | #define CARBON_EXTENSION "@CARBON_EXTENSION@" 27 | #define CARBON_LOG_EXTENSION "@CARBON_LOG_EXTENSION@" 28 | #define CARBON_INFO_EXTENSION "@CARBON_INFO_EXTENSION@" 29 | #define CARBON_LOCK_EXTENSION "@CARBON_LOCK_EXTENSION@" 30 | #define CARBON_EXCLUDE_EXTENSION "@CARBON_EXCLUDE_EXTENSION@" 31 | #define CARBON_PREFIX "@CARBON_PREFIX@" 32 | #define CARBON_MSG_PREFIX "@CARBON_MSG_PREFIX@" 33 | #define CARBON_ERROR_PREFIX "@CARBON_ERROR_PREFIX@" 34 | #define CARBON_GUI_PARENT "@CARBON_GUI_PARENT@" 35 | #define CARBON_RUNNER "@CMAKE_INSTALL_PREFIX@/share/@CMAKE_PROJECT_NAME@/scripts/@CMAKE_PROJECT_NAME@-runner" 36 | #define CARBON_TERMINATE "@CMAKE_INSTALL_PREFIX@/share/@CMAKE_PROJECT_NAME@/scripts/@CMAKE_PROJECT_NAME@-terminate" 37 | #define INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@" /* No CARBON_ prefix to this name, as its used in 'support' */ 38 | #define CARBON_SYSTEM_DEF_FILE "@CMAKE_INSTALL_PREFIX@/share/@CMAKE_PROJECT_NAME@/default@CARBON_EXTENSION@" 39 | #define CARBON_PACKAGE_NAME "@PROJECT_NAME@" 40 | #define SHARE_INSTALL_PREFIX "@SHARE_INSTALL_PREFIX@" 41 | #cmakedefine QT_QTDBUS_FOUND 1 42 | #endif 43 | -------------------------------------------------------------------------------- /icons/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (UNIX AND NOT APPLE) 2 | macro (update_iconcache) 3 | # Update mtime of hicolor icon theme dir. 4 | # We don't always have touch command (e.g. on Windows), so instead create 5 | # and delete a temporary file in the theme dir. 6 | install(CODE " 7 | set(DESTDIR_VALUE \"\$ENV{DESTDIR}\") 8 | if (NOT DESTDIR_VALUE) 9 | file(WRITE \"${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/temp.txt\" \"update\") 10 | file(REMOVE \"${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/temp.txt\") 11 | endif (NOT DESTDIR_VALUE) 12 | ") 13 | endmacro (update_iconcache) 14 | 15 | install(FILES carbon.svg DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps) 16 | install(FILES carbon16.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/16x16/apps RENAME carbon.png) 17 | install(FILES carbon22.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/22x22/apps RENAME carbon.png) 18 | install(FILES carbon32.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/32x32/apps RENAME carbon.png) 19 | install(FILES carbon48.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/48x48/apps RENAME carbon.png) 20 | update_iconcache() 21 | endif (UNIX AND NOT APPLE) 22 | -------------------------------------------------------------------------------- /icons/carbon.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 49 | 51 | 53 | 57 | 62 | 63 | 71 | 75 | 79 | 80 | 87 | 90 | 94 | 95 | 104 | 113 | 121 | 125 | 129 | 130 | 140 | 150 | 160 | 170 | 171 | 174 | 176 | 181 | 186 | 187 | 192 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /icons/carbon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDrummond/carbon/a42dac97f986c7311014f76687bfb2baa4327dcd/icons/carbon16.png -------------------------------------------------------------------------------- /icons/carbon22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDrummond/carbon/a42dac97f986c7311014f76687bfb2baa4327dcd/icons/carbon22.png -------------------------------------------------------------------------------- /icons/carbon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDrummond/carbon/a42dac97f986c7311014f76687bfb2baa4327dcd/icons/carbon32.png -------------------------------------------------------------------------------- /icons/carbon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDrummond/carbon/a42dac97f986c7311014f76687bfb2baa4327dcd/icons/carbon48.png -------------------------------------------------------------------------------- /scripts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file (carbon-runner.cmake ${CMAKE_CURRENT_BINARY_DIR}/carbon-runner @ONLY) 2 | install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/carbon-runner carbon-terminate 3 | DESTINATION ${CMAKE_INSTALL_PREFIX}/share/${CMAKE_PROJECT_NAME}/scripts) 4 | -------------------------------------------------------------------------------- /scripts/carbon-runner.cmake: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | appName=`basename $0` 4 | 5 | function show_help() 6 | { 7 | exName=Wibble 8 | echo 9 | echo "@CMAKE_PROJECT_NAME@ Session runner v@CARBON_VERSION_FULL@" 10 | echo 11 | echo "(C) Craig Drummond 2013 - Released under the GPL (v3 or later)" 12 | echo 13 | echo "Usage: $appName [-d] " 14 | echo " -d Perform a dry-run (i.e. show what would happen, but don't" 15 | echo " do any actual synchronisation)" 16 | echo 17 | echo "e.g. $appName $exName" 18 | echo " - This will synchronise the $exName session (as created in" 19 | echo " @CMAKE_PROJECT_NAME@). This is achieved by loading the settings" 20 | echo " from $HOME/.local/share/$projectName/$fileName@CARBON_EXTENSION@" 21 | echo 22 | echo " $appName $HOME/.local/share/$projectName/$exName@CARBON_EXTENSION@" 23 | echo " - This will perform a synchronisation using the settings" 24 | echo " within $HOME/$exName@CARBON_EXTENSION@" 25 | echo 26 | exit 101 27 | } 28 | 29 | shortOpts="dh" 30 | longOpts="dryrun,help" 31 | progName=carbon-runner 32 | args=$(getopt -s bash --options $shortOpts --longoptions $longOpts --name $progName -- "$@" ) 33 | 34 | eval set -- "$args" 35 | 36 | while true; do 37 | case $1 in 38 | -h|--help) 39 | show_help 40 | ;; 41 | -d|--dryrun) 42 | shift 43 | doDryRun=true 44 | ;; 45 | *) 46 | shift 47 | break 48 | ;; 49 | esac 50 | done 51 | 52 | fileName=$1 53 | sessionName=`basename "$fileName" | sed s^@CARBON_EXTENSION@^^g` 54 | 55 | function fix_url() 56 | { 57 | # Remove file:// from URL 58 | #fixed="`echo $1/ | sed s/file://g | sed s^//^/^g | sed s^//^/^g`" 59 | fixed="`echo $1/ | sed s^file://^^g | sed s^//^/^g`" 60 | if [[ "$fixed" != */ ]] ; then 61 | fixed=$fixed/ 62 | fi 63 | 64 | echo $fixed 65 | } 66 | 67 | function log_msg() 68 | { 69 | if [ "$CARBON_NO_MSG_PREFIX" = "true" ] ; then 70 | echo "$1" 71 | else 72 | echo "@CARBON_MSG_PREFIX@$1" 73 | fi 74 | } 75 | 76 | function log_error() 77 | { 78 | if [ "$CARBON_NO_MSG_PREFIX" = "true" ] ; then 79 | echo "$1" 80 | else 81 | echo "@CARBON_ERROR_PREFIX@$1" 82 | fi 83 | } 84 | 85 | projectName=`echo @CMAKE_PROJECT_NAME@ | tr "[:upper:]" "[:lower:]"` 86 | 87 | function notify() 88 | { 89 | notifyApp=`which notify-send` 90 | if [ "$notifyApp" != "" ] ; then 91 | if [ "$2" = "start" ] ;then 92 | $notifyApp --app-name=@CMAKE_PROJECT_NAME@ --icon=$projectName "Session Starting" "Starting synchronisation of \"$sessionName\"" > /dev/null 2>&1 93 | elif [ "$2" = "error" ] ;then 94 | $notifyApp --app-name=@CMAKE_PROJECT_NAME@ --icon=$projectName "Session Failed" "Synchronisation of \"$sessionName\" failed.
$3" > /dev/null 2>&1 95 | elif [ $1 -eq 0 ] ; then 96 | $notifyApp --app-name=@CMAKE_PROJECT_NAME@ --icon=$projectName "Session Complete" "Sucessfully completed synchronisation of \"$sessionName\"" > /dev/null 2>&1 97 | else 98 | $notifyApp --app-name=@CMAKE_PROJECT_NAME@ --icon=$projectName "Session Failed" "Synchronisation of \"$sessionName\" failed.
Please check log file." > /dev/null 2>&1 99 | fi 100 | fi 101 | } 102 | 103 | function log_error_and_exit() 104 | { 105 | notify $1 "error" "$2" 106 | log_error "$2" 107 | exit $1 108 | } 109 | 110 | if [ "$fileName" = "" ]; then 111 | show_help 112 | fi 113 | 114 | if [ ! -f "$fileName" ] ; then 115 | session="$HOME/.local/share/$projectName/$fileName@CARBON_EXTENSION@" 116 | if [ "$session" != "" ] ; then 117 | fileName=$session 118 | fi 119 | fi 120 | 121 | if [ ! -f "$fileName" ] ; then 122 | log_error_and_exit 102 "Could not read \"$fileName\"" 123 | fi 124 | 125 | if [ -e "$fileName@CARBON_LOCK_EXTENSION@" ] ; then 126 | prevPid=`cat "$fileName@CARBON_LOCK_EXTENSION@"` 127 | if ps -p $prevPid > /dev/null ; then 128 | log_error_and_exit 113 "Session is already running" 129 | fi 130 | fi 131 | 132 | # Keys used in backup info file 133 | backupTimeKey=BackupTime 134 | 135 | maxBackupAge=0 136 | maxFileSize=0 137 | 138 | if [ "$@CARBON_GUI_PARENT@" != "true" ] ; then 139 | if [ -f "$fileName@CARBON_LOG_EXTENSION@" ] ; then 140 | rm "$fileName@CARBON_LOG_EXTENSION@" 141 | fi 142 | 143 | # We are not being run via the GUI - so we need to log all output 144 | # So re-run this script, but capture all output to log file... 145 | @CARBON_GUI_PARENT@=true CARBON_NO_MSG_PREFIX=true $0 "$@" > "$fileName@CARBON_LOG_EXTENSION@" 2>&1 146 | else 147 | notify 0 start 148 | eval `cat "$fileName" | grep -A999 "\[Settings\]" | tail -n +2` 149 | src=`fix_url $src` 150 | dest=`fix_url $dest` 151 | 152 | if [ -z "$src" ] ; then 153 | log_error_and_exit 109 "Source is empty" 154 | fi 155 | 156 | srcIsRemote=0 157 | if [ "`echo $src | grep \: | wc -l`" != "0" ] ; then 158 | srcIsRemote=1 159 | fi 160 | 161 | if [ $srcIsRemote -eq 0 ] && [ ! -e "$src" ] ; then 162 | log_error_and_exit 112 "Source does not exist" 163 | fi 164 | 165 | if [ -z "$dest" ] ; then 166 | log_error_and_exit 110 "destination is empty" 167 | fi 168 | 169 | if [ "$CARBON_NO_MSG_PREFIX" = "true" ] ; then 170 | command="rsync -v --progress --include=*.exe --exclude=@CARBON_EXTENSION@@CARBON_LOG_EXTENSION@ --out-format=%f" 171 | else 172 | command="rsync -v --progress --include=*.exe --exclude=@CARBON_EXTENSION@@CARBON_LOG_EXTENSION@ --out-format=@CARBON_PREFIX@%f" 173 | fi 174 | 175 | # Map @CARBON_EXTENSION@ file options to rsync options... 176 | if [ "$archive" = "true" ] || [ "$archive" = "" ] || [ "$makeBackups" = "true" ] || [ "$makeBackups" = "" ] ; then command="$command --archive"; fi 177 | if [ "$recursive" = "true" ] || [ "$recursive" = "" ] ; then command="$command --recursive"; fi 178 | if [ "$skipFilesOnSizeMatch" = "true" ]; then command="$command --size-only"; fi 179 | if [ "$skipReceiverNewerFiles" = "true" ] || [ "$skipReceiverNewerFiles" = "" ]; then command="$command --update"; fi 180 | if [ "$keepPartial" = "true" ]; then command="$command --partial"; fi 181 | if [ "$onlyUpdate" = "true" ]; then command="$command --existing"; fi 182 | if [ "$useCompression" = "true" ]; then command="$command --compress"; fi 183 | if [ "$checksum" = "true" ]; then command="$command --checksum"; fi 184 | if [ "$windowsCompat" = "true" ]; then command="$command --modify-window=1"; fi 185 | if [ "$ignoreExisting" = "true" ]; then command="$command --ignore-existing"; fi 186 | if [ "$archive" != "true" ] && ( [ "$preservePermissions" = "true" ] || [ "$preservePermissions" = "" ] ) ; then command="$command --perms"; fi 187 | if [ "$archive" != "true" ] && ( [ "$preserveGroup" = "true" ] || [ "$preserveGroup" = "" ] ) ; then command="$command --group"; fi 188 | if [ "$modificationTimes" = "true" ] || [ "$modificationTimes" = "" ]; then command="$command --times"; fi 189 | if ( [ "$deleteExtraFilesOnReceiver" = "true" ] || [ "$deleteExtraFilesOnReceiver" = "" ] ) && [ "$recursive" != "false" ] ; then 190 | command="$command --delete"; 191 | fi 192 | if [ "$archive" != "true" ] && ( [ "$preserveOwner" = "true" ] || [ "$preserveOwner" = "" ] ) ; then command="$command --owner"; fi 193 | if [ "$archive" != "true" ] && ( [ "$preserveSpecialFiles" = "true" ] || [ "$preserveSpecialFiles" = "" ] ) ; then command="$command -D"; fi 194 | if [ "$archive" != "true" ] && ( [ "$copySymlinksAsSymlinks" = "true" ] || [ "$copySymlinksAsSymlinks" = "" ] ) ; then command="$command --links"; fi 195 | if [ "$dontLeaveFileSystem" = "true" ]; then command="$command --one-file-system"; fi 196 | if [ "$cvsExclude" = "true" ] || [ "$cvsExclude" = "" ]; then command="$command --cvs-exclude"; fi 197 | if [ "$CustomOptions" != "" ] ; then command="$command $CustomOptions"; fi 198 | if [ "$maxFileSize" != "" ] && [ $maxFileSize -gt 0 ] ; then command="$command --max-size=$maxFileSize"M ; fi 199 | 200 | destIsRemote=0 201 | if [ "`echo $dest | grep \: | wc -l`" != "0" ] ; then 202 | destIsRemote=1 203 | fi 204 | 205 | if [ $destIsRemote -eq 0 ] && [ ! -d "$dest" ] ; then 206 | log_error_and_exit 103 "$dest does not exist" 207 | fi 208 | 209 | excludeFrom="$fileName@CARBON_EXCLUDE_EXTENSION@" 210 | 211 | if [ "$doDryRun" = "true" ]; then 212 | command="$command -n" 213 | fi 214 | 215 | destFolder="$dest" 216 | linkDestFolder="$dest" 217 | 218 | if [ "$makeBackups" = "true" ]; then 219 | currentBackupTime=`date +"%Y-%m-%d %H:%M:%S"` 220 | currentBackupTimeFile="$fileName@CARBON_INFO_EXTENSION@" 221 | previousBackupTime=`cat "$currentBackupTimeFile" | grep "$backupTimeKey" | awk -F\= '{print $2}'` 222 | 223 | if [ -f "$dest$currentBackupTime" ] ; then 224 | log_error_and_exit 104 " $dest$currentBackupTime is a file!" 225 | fi 226 | 227 | if [ ! -z "$previousBackupTime" ] ; then 228 | linkDestFolder="$dest/$previousBackupTime" 229 | fi 230 | if [ ! -z "$currentBackupTime" ] ; then 231 | destFolder="$dest/$currentBackupTime" 232 | fi 233 | 234 | if [ -z "$previousBackupTime" ]; then 235 | log_msg "Full backup, as no previous backup" 236 | elif [ $destIsRemote -eq 0 ] && [ ! -d "$linkDestFolder" ] ; then 237 | log_msg "Full backup, as previous folder does not exist" 238 | linkDestFolder="" 239 | else 240 | log_msg "Incremental backup (previous $linkDestFolder)" 241 | fi 242 | fi 243 | 244 | # Store PID in lock file, to prevent multiple executions... 245 | echo $$ > "$fileName@CARBON_LOCK_EXTENSION@" 246 | 247 | if [ "$linkDestFolder" = "" ] ; then 248 | # linkDest is empty, so dont pass as arg - else rsync moans... 249 | if [ -f "$excludeFrom" ] ; then 250 | $command "$src" "$destFolder" --exclude-from="$excludeFrom" 251 | else 252 | $command "$src" "$destFolder" 253 | fi 254 | else 255 | if [ -f "$excludeFrom" ] ; then 256 | $command "$src" "$destFolder" --exclude-from="$excludeFrom" --link-dest="$linkDestFolder" 257 | else 258 | $command "$src" "$destFolder" --link-dest="$linkDestFolder" 259 | fi 260 | fi 261 | 262 | if [ "$makeBackups" = "true" ] && [ -d "$destFolder" ] ; then 263 | # Store date of this backup 264 | echo "$backupTimeKey=$currentBackupTime" > "$currentBackupTimeFile" 265 | fi 266 | 267 | rv=$? 268 | 269 | if [ $destIsRemote -eq 0 ] ; then 270 | let maxBackupAge="$maxBackupAge * 24 * 60 * 60" 271 | 272 | # Remove any old increments 273 | if [ "$makeBackups" = "true" ] && [ $maxBackupAge -gt 0 ] && [ -d "$destFolder" ] && [ "$dest" != "/" ] ; then 274 | log_msg "Cleaning old backups" 275 | touch "$destFolder" 276 | currentAge=`stat --terse --format=%Y "$destFolder"` 277 | 278 | oldIfs=$IFS 279 | IFS=" 280 | " 281 | 282 | for old in "$dest"/* ; do 283 | if [ -d "$old" ] && [ "$old" != "/" ] ; then 284 | let diff="$currentAge - `stat --terse --format=%Y $old`" 285 | 286 | if [ $diff -gt $maxBackupAge ] ; then 287 | log_msg "Erasing $old" 288 | \rm -r "$old" 289 | fi 290 | fi 291 | done 292 | IFS=$oldIfs 293 | fi 294 | fi 295 | 296 | # Remove lock 297 | rm "$fileName@CARBON_LOCK_EXTENSION@" 298 | 299 | notify $rv 300 | 301 | # Return rsyncs exit value... 302 | exit $rv 303 | fi 304 | -------------------------------------------------------------------------------- /scripts/carbon-terminate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ppid=$1 4 | 5 | if [ -z $ppid ] ; then 6 | echo "Termiante PID and all its children" 7 | echo "Usage: $0 PID"; 8 | exit; 9 | fi 10 | 11 | for i in `ps -eaf | awk '$3 == '$ppid' { print $2 }'` ; do 12 | $0 $i "child" 13 | kill -9 $i 14 | done 15 | 16 | if [ "$2" = "" ] ; then 17 | kill $ppid 18 | fi 19 | -------------------------------------------------------------------------------- /support/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SUPPORT_SRCS messagebox.cpp 2 | utils.cpp dialog.cpp 3 | squeezedtextlabel.cpp pathrequester.cpp pagewidget.cpp) 4 | 5 | set(SUPPORT_MOC_HDRS pagewidget.h dialog.h pathrequester.h) 6 | 7 | qt5_wrap_cpp(SUPPORT_MOC_SRCS ${SUPPORT_MOC_HDRS} ) 8 | 9 | include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${QTINCLUDES} ) 10 | add_library(support STATIC ${SUPPORT_MOC_SRCS} ${SUPPORT_SRCS} ${SUPPORT_UI_HDRS} ) 11 | -------------------------------------------------------------------------------- /support/dialog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2016 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #include "dialog.h" 25 | #ifdef Q_OS_MAC 26 | #include "osxstyle.h" 27 | #endif 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | Dialog::Dialog(QWidget *parent, const QString &name, const QSize &defSize) 36 | : QDialog(parent) 37 | , defButton(0) 38 | , buttonTypes(0) 39 | , mw(0) 40 | , buttonBox(0) 41 | , shown(false) { 42 | if (!name.isEmpty()) { 43 | setObjectName(name); 44 | QSettings cfg; 45 | cfg.beginGroup(metaObject()->className()); 46 | cfgSize = cfg.value(name + "/size", QSize()).toSize(); 47 | if (!cfgSize.isEmpty()) { 48 | QDialog::resize(cfgSize); 49 | } else if (!defSize.isEmpty()) { 50 | QDialog::resize(defSize); 51 | } 52 | cfg.endGroup(); 53 | } 54 | 55 | #ifdef Q_OS_MAC 56 | setWindowIcon(QIcon()); 57 | #endif 58 | } 59 | 60 | Dialog::~Dialog() { 61 | if (!objectName().isEmpty() && size() != cfgSize) { 62 | QSettings cfg; 63 | cfg.beginGroup(metaObject()->className()); 64 | cfg.setValue(objectName() + "/size", size()); 65 | cfg.endGroup(); 66 | } 67 | #ifdef Q_OS_MAC 68 | OSXStyle::self()->removeWindow(this); 69 | #endif 70 | } 71 | 72 | void Dialog::resize(const QSize &sz) { 73 | if (cfgSize.isEmpty()) { 74 | QDialog::resize(sz); 75 | cfgSize = sz; 76 | } 77 | QDialog::resize(sz); 78 | } 79 | 80 | #ifdef Q_OS_MAC 81 | Dialog::ButtonProxyStyle::ButtonProxyStyle() 82 | : QProxyStyle() { 83 | setBaseStyle(qApp->style()); 84 | } 85 | 86 | int Dialog::ButtonProxyStyle::styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const { 87 | if (QStyle::SH_DialogButtonLayout == stylehint) { 88 | return QDialogButtonBox::GnomeLayout; 89 | } else { 90 | return QProxyStyle::styleHint(stylehint, opt, widget, returnData); 91 | } 92 | } 93 | 94 | Dialog::ButtonProxyStyle * Dialog::buttonProxyStyle() { 95 | static ButtonProxyStyle *style = 0; 96 | if (!style) { 97 | style = new ButtonProxyStyle(); 98 | } 99 | return style; 100 | } 101 | #endif 102 | 103 | static QDialogButtonBox::StandardButton mapType(int btn) { 104 | switch (btn) { 105 | case Dialog::Help: return QDialogButtonBox::Help; 106 | case Dialog::Ok: return QDialogButtonBox::Ok; 107 | case Dialog::Apply: return QDialogButtonBox::Apply; 108 | case Dialog::Cancel: return QDialogButtonBox::Cancel; 109 | case Dialog::Close: return QDialogButtonBox::Close; 110 | case Dialog::No: return QDialogButtonBox::No; 111 | case Dialog::Yes: return QDialogButtonBox::Yes; 112 | case Dialog::Reset: return QDialogButtonBox::Reset; 113 | default: return QDialogButtonBox::NoButton; 114 | } 115 | } 116 | 117 | void Dialog::setButtons(ButtonCodes buttons) { 118 | if (buttonBox && buttons == buttonTypes) { 119 | return; 120 | } 121 | 122 | QFlags btns; 123 | if (buttons & Help) { 124 | btns |= QDialogButtonBox::Help; 125 | } 126 | if (buttons & Ok) { 127 | btns |= QDialogButtonBox::Ok; 128 | } 129 | if (buttons & Apply) { 130 | btns |= QDialogButtonBox::Apply; 131 | } 132 | if (buttons & Cancel) { 133 | btns |= QDialogButtonBox::Cancel; 134 | } 135 | if (buttons & Close) { 136 | btns |= QDialogButtonBox::Close; 137 | } 138 | if (buttons & No) { 139 | btns |= QDialogButtonBox::No; 140 | } 141 | if (buttons & Yes) { 142 | btns |= QDialogButtonBox::Yes; 143 | } 144 | if (buttons & Reset) { 145 | btns |= QDialogButtonBox::Reset; 146 | } 147 | 148 | buttonTypes = (int)btns; 149 | bool needToCreate = true; 150 | if (buttonBox) { 151 | needToCreate = false; 152 | buttonBox->clear(); 153 | buttonBox->setStandardButtons(btns); 154 | userButtons.clear(); 155 | } else { 156 | buttonBox = new QDialogButtonBox(btns, Qt::Horizontal, this); 157 | #ifdef Q_OS_MAC 158 | buttonBox->setStyle(buttonProxyStyle()); 159 | #endif 160 | } 161 | 162 | if (buttons & Help) { 163 | setButtonText(Help, tr("Help")); 164 | } 165 | if (buttons & Ok) { 166 | setButtonText(Ok, tr("OK")); 167 | } 168 | if (buttons & Apply) { 169 | setButtonText(Apply, tr("Apply")); 170 | } 171 | if (buttons & Cancel) { 172 | setButtonText(Cancel, tr("Cancel")); 173 | } 174 | if (buttons & Close) { 175 | setButtonText(Close, tr("Close")); 176 | } 177 | if (buttons & No) { 178 | setButtonText(No, tr("No")); 179 | } 180 | if (buttons & Yes) { 181 | setButtonText(Yes, tr("Yes")); 182 | } 183 | if (buttons & Reset) { 184 | setButtonText(Reset, tr("Reset")); 185 | } 186 | 187 | if (buttons & User3) { 188 | QPushButton *button = new QPushButton(buttonBox); 189 | userButtons.insert(User3, button); 190 | buttonBox->addButton(button, QDialogButtonBox::ActionRole); 191 | } 192 | if (buttons & User2) { 193 | QPushButton *button = new QPushButton(buttonBox); 194 | userButtons.insert(User2, button); 195 | buttonBox->addButton(button, QDialogButtonBox::ActionRole); 196 | } 197 | if (buttons & User1) { 198 | QPushButton *button = new QPushButton(buttonBox); 199 | userButtons.insert(User1, button); 200 | buttonBox->addButton(button, QDialogButtonBox::ActionRole); 201 | } 202 | 203 | if (needToCreate && mw && buttonBox) { 204 | create(); 205 | } 206 | } 207 | 208 | void Dialog::setDefaultButton(ButtonCode button) { 209 | QAbstractButton *b = getButton(button); 210 | if (b) { 211 | qobject_cast(b)->setDefault(true); 212 | } 213 | defButton = button; 214 | } 215 | 216 | void Dialog::setButtonText(ButtonCode button, const QString &text) { 217 | QAbstractButton *b = getButton(button); 218 | if (b) { 219 | b->setText(text); 220 | } 221 | } 222 | 223 | void Dialog::setButtonIcon(ButtonCode button, const QIcon &icon) { 224 | QAbstractButton *b = getButton(button); 225 | if (b) { 226 | b->setIcon(icon); 227 | } 228 | } 229 | 230 | void Dialog::setButtonMenu(ButtonCode button, QMenu *menu) { 231 | QAbstractButton *b = getButton(button); 232 | if (b) { 233 | qobject_cast(b)->setMenu(menu); 234 | } 235 | } 236 | 237 | void Dialog::enableButton(ButtonCode button, bool enable) { 238 | QAbstractButton *b = getButton(button); 239 | if (b) { 240 | b->setEnabled(enable); 241 | } 242 | } 243 | 244 | bool Dialog::isButtonEnabled(ButtonCode button) { 245 | QAbstractButton *b = getButton(button); 246 | return b ? b->isEnabled() : false; 247 | } 248 | 249 | void Dialog::setMainWidget(QWidget *widget) { 250 | if (mw) { 251 | return; 252 | } 253 | mw = widget; 254 | if (mw && buttonBox) { 255 | create(); 256 | } 257 | } 258 | 259 | void Dialog::slotButtonClicked(int button) { 260 | switch (button) { 261 | case Ok: accept(); break; 262 | case Cancel: reject(); break; 263 | case Close: reject(); break; 264 | default: break; 265 | } 266 | } 267 | 268 | void Dialog::buttonPressed(QAbstractButton *button) { 269 | if (buttonTypes & QDialogButtonBox::Help && button == buttonBox->button(QDialogButtonBox::Help)) { 270 | slotButtonClicked(Help); 271 | } else if (buttonTypes & QDialogButtonBox::Ok && button == buttonBox->button(QDialogButtonBox::Ok)) { 272 | slotButtonClicked(Ok); 273 | } else if (buttonTypes & QDialogButtonBox::Apply && button == buttonBox->button(QDialogButtonBox::Apply)) { 274 | slotButtonClicked(Apply); 275 | } else if (buttonTypes & QDialogButtonBox::Cancel && button == buttonBox->button(QDialogButtonBox::Cancel)) { 276 | slotButtonClicked(Cancel); 277 | } else if (buttonTypes & QDialogButtonBox::Close && button == buttonBox->button(QDialogButtonBox::Close)) { 278 | slotButtonClicked(Close); 279 | } else if (buttonTypes & QDialogButtonBox::No && button == buttonBox->button(QDialogButtonBox::No)) { 280 | slotButtonClicked(No); 281 | } else if (buttonTypes & QDialogButtonBox::Yes && button == buttonBox->button(QDialogButtonBox::Yes)) { 282 | slotButtonClicked(Yes); 283 | } else if (buttonTypes & QDialogButtonBox::Reset && button == buttonBox->button(QDialogButtonBox::Reset)) { 284 | slotButtonClicked(Reset); 285 | } else if (userButtons.contains(User1) && userButtons[User1] == button) { 286 | slotButtonClicked(User1); 287 | } else if (userButtons.contains(User2) && userButtons[User2] == button) { 288 | slotButtonClicked(User2); 289 | } else if (userButtons.contains(User3) && userButtons[User3] == button) { 290 | slotButtonClicked(User3); 291 | } 292 | } 293 | 294 | void Dialog::create() { 295 | QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); 296 | layout->addWidget(mw); 297 | layout->addWidget(buttonBox); 298 | connect(buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonPressed(QAbstractButton *))); 299 | } 300 | 301 | QAbstractButton *Dialog::getButton(ButtonCode button) { 302 | QDialogButtonBox::StandardButton mapped = mapType(button); 303 | QAbstractButton *b = QDialogButtonBox::NoButton == mapped ? 0 : buttonBox->button(mapped); 304 | if (!b && userButtons.contains(button)) { 305 | b = userButtons[button]; 306 | } 307 | return b; 308 | } 309 | 310 | void Dialog::showEvent(QShowEvent *e) { 311 | if (!shown) { 312 | shown = true; 313 | if (defButton) { 314 | setDefaultButton((ButtonCode)defButton); 315 | } 316 | if (buttonBox && mw) { 317 | QSize mwSize = mw->minimumSize(); 318 | if (mwSize.width() < 16 || mwSize.height() < 16) { 319 | mwSize = mw->minimumSizeHint(); 320 | } 321 | if (mwSize.width() > 15 && mwSize.height() > 15) { 322 | setMinimumHeight(qMax(minimumHeight(), buttonBox->height() + layout()->spacing() + mwSize.height() + (2 * layout()->margin()))); 323 | setMinimumWidth(qMax(minimumWidth(), mwSize.width() + (2 * layout()->margin()))); 324 | } 325 | } 326 | } 327 | #ifdef Q_OS_MAC 328 | if (!isModal()) { 329 | OSXStyle::self()->addWindow(this); 330 | } 331 | #endif 332 | QDialog::showEvent(e); 333 | } 334 | 335 | #ifdef Q_OS_MAC 336 | void Dialog::hideEvent(QHideEvent *e) { 337 | OSXStyle::self()->removeWindow(this); 338 | QDialog::hideEvent(e); 339 | } 340 | 341 | void Dialog::closeEvent(QCloseEvent *e) { 342 | OSXStyle::self()->removeWindow(this); 343 | QDialog::closeEvent(e); 344 | } 345 | #endif 346 | -------------------------------------------------------------------------------- /support/dialog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2016 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #ifndef DIALOG_H 25 | #define DIALOG_H 26 | 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #ifdef Q_OS_MAC 34 | #include 35 | #endif 36 | 37 | class QAbstractButton; 38 | class QMenu; 39 | 40 | class Dialog : public QDialog { 41 | Q_OBJECT 42 | Q_ENUMS(ButtonCode) 43 | 44 | public: 45 | 46 | #ifdef Q_OS_MAC 47 | class ButtonProxyStyle : public QProxyStyle { 48 | public: 49 | ButtonProxyStyle(); 50 | 51 | int styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const; 52 | }; 53 | 54 | static ButtonProxyStyle * buttonProxyStyle(); 55 | #endif 56 | 57 | enum ButtonCode { 58 | None = 0x00000000, 59 | Help = 0x00000001, ///< Show Help button. (this button will run the help set with setHelp) 60 | Default = 0x00000002, ///< Show Default button. 61 | Ok = 0x00000004, ///< Show Ok button. (this button accept()s the dialog; result set to QDialog::Accepted) 62 | Apply = 0x00000008, ///< Show Apply button. 63 | Try = 0x00000010, ///< Show Try button. 64 | Cancel = 0x00000020, ///< Show Cancel-button. (this button reject()s the dialog; result set to QDialog::Rejected) 65 | Close = 0x00000040, ///< Show Close-button. (this button closes the dialog) 66 | No = 0x00000080, ///< Show No button. (this button closes the dialog and sets the result to KDialog::No) 67 | Yes = 0x00000100, ///< Show Yes button. (this button closes the dialog and sets the result to KDialog::Yes) 68 | Reset = 0x00000200, ///< Show Reset button 69 | Details = 0x00000400, ///< Show Details button. (this button will show the detail widget set with setDetailsWidget) 70 | User1 = 0x00001000, ///< Show User defined button 1. 71 | User2 = 0x00002000, ///< Show User defined button 2. 72 | User3 = 0x00004000, ///< Show User defined button 3. 73 | NoDefault = 0x00008000 ///< Used when specifying a default button; indicates that no button should be marked by default. 74 | }; 75 | Q_DECLARE_FLAGS(ButtonCodes, ButtonCode) 76 | enum ButtonPopupMode { 77 | InstantPopup = 0, 78 | DelayedPopup = 1 79 | }; 80 | 81 | Dialog(QWidget *parent, const QString &name = QString(), const QSize &defSize = QSize()); 82 | virtual ~Dialog(); 83 | 84 | void setCaption(const QString &cap) { 85 | setWindowTitle(cap); 86 | } 87 | void setButtons(ButtonCodes buttons); 88 | void setDefaultButton(ButtonCode button); 89 | void setButtonText(ButtonCode button, const QString &text); 90 | void setButtonIcon(ButtonCode button, const QIcon &icon); 91 | void setButtonMenu(ButtonCode button, QMenu *menu); 92 | void enableButton(ButtonCode button, bool enable); 93 | void enableButtonOk(bool enable) { 94 | enableButton(Ok, enable); 95 | } 96 | bool isButtonEnabled(ButtonCode button); 97 | void setMainWidget(QWidget *widget); 98 | virtual void slotButtonClicked(int button); 99 | QWidget * mainWidget() { 100 | return mw; 101 | } 102 | 103 | const QSize & configuredSize() const { 104 | return cfgSize; 105 | } 106 | void resize(int w, int h) { 107 | resize(QSize(w, h)); 108 | } 109 | void resize(const QSize &sz); 110 | 111 | #ifdef Q_OS_MAC 112 | virtual void hideEvent(QHideEvent *e); 113 | virtual void closeEvent(QCloseEvent *e); 114 | #endif 115 | 116 | private Q_SLOTS: 117 | void buttonPressed(QAbstractButton *button); 118 | 119 | private: 120 | void create(); 121 | QAbstractButton *getButton(ButtonCode button); 122 | virtual void showEvent(QShowEvent *e); 123 | 124 | private: 125 | int defButton; 126 | int buttonTypes; 127 | QWidget *mw; 128 | QDialogButtonBox *buttonBox; 129 | QMap userButtons; 130 | QSize cfgSize; 131 | bool shown; 132 | }; 133 | 134 | Q_DECLARE_OPERATORS_FOR_FLAGS(Dialog::ButtonCodes) 135 | 136 | #endif 137 | -------------------------------------------------------------------------------- /support/messagebox.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2016 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #include "messagebox.h" 25 | #include "dialog.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #ifdef Q_OS_MAC 32 | static void splitMessage(const QString &orig, QString &msg, QString &sub) { 33 | static QStringList constSeps = QStringList() << QLatin1String("\n\n") << QLatin1String("

"); 34 | msg = orig; 35 | 36 | foreach (const QString &sep, constSeps) { 37 | QStringList parts = orig.split(sep); 38 | if (parts.count() > 1) { 39 | msg = parts.takeAt(0); 40 | sub = parts.join(sep); 41 | return; 42 | } 43 | } 44 | } 45 | 46 | void MessageBox::error(QWidget *parent, const QString &message, const QString &title) { 47 | QString msg; 48 | QString sub; 49 | splitMessage(message, msg, sub); 50 | QMessageBox box(QMessageBox::Critical, title.isEmpty() ? i18n("Error") : title, msg, QMessageBox::Ok, parent, Qt::Sheet); 51 | box.setInformativeText(sub); 52 | box.exec(); 53 | } 54 | 55 | void MessageBox::information(QWidget *parent, const QString &message, const QString &title) { 56 | QString msg; 57 | QString sub; 58 | splitMessage(message, msg, sub); 59 | QMessageBox box(QMessageBox::Information, title.isEmpty() ? i18n("Information") : title, msg, QMessageBox::Ok, parent, Qt::Sheet); 60 | box.setInformativeText(sub); 61 | box.exec(); 62 | } 63 | #endif 64 | 65 | QMessageBox::StandardButton MessageBox::questionYesNoCancel(QWidget *parent, const QString &message, const QString &title, 66 | const QString &yesText, const QString &noText, bool showCancel, bool isWarning) { 67 | QMessageBox msg(parent); 68 | #ifdef Q_OS_MAC 69 | QString mainText; 70 | QString subText; 71 | splitMessage(message, mainText, subText); 72 | msg.setText(mainText); 73 | msg.setInformativeText(subText); 74 | #else 75 | msg.setText(message); 76 | #endif 77 | msg.setIcon(isWarning ? QMessageBox::Warning : QMessageBox::Question); 78 | msg.setWindowTitle(title.isEmpty() ? (isWarning ? QObject::tr("Warning") : QObject::tr("Question")) : title); 79 | QPushButton *yesBtn = msg.addButton(yesText.isEmpty() ? QObject::tr("Yes") : yesText, QMessageBox::YesRole); 80 | QPushButton *noBtn = msg.addButton(noText.isEmpty() ? QObject::tr("No") : noText, QMessageBox::NoRole); 81 | if (showCancel) { 82 | msg.addButton(QMessageBox::Cancel); 83 | } 84 | msg.exec(); 85 | 86 | QAbstractButton *clicked = msg.clickedButton(); 87 | return yesBtn == clicked 88 | ? QMessageBox::Yes 89 | : noBtn == clicked 90 | ? QMessageBox::No 91 | : QMessageBox::Cancel; 92 | } 93 | 94 | namespace MessageBox { 95 | class YesNoListDialog : public Dialog { 96 | public: 97 | YesNoListDialog(QWidget *p) : Dialog(p) { } 98 | void slotButtonClicked(int b) { 99 | switch (b) { 100 | case Dialog::Ok: 101 | case Dialog::Yes: 102 | accept(); 103 | break; 104 | case Dialog::No: 105 | reject(); 106 | break; 107 | } 108 | } 109 | }; 110 | } 111 | 112 | QMessageBox::StandardButton MessageBox::msgListEx(QWidget *parent, QMessageBox::Icon type, const QString &message, const QStringList &strlist, const QString &title) { 113 | MessageBox::YesNoListDialog *dlg = new MessageBox::YesNoListDialog(parent); 114 | dlg->setAttribute(Qt::WA_DeleteOnClose); 115 | QWidget *wid = new QWidget(dlg); 116 | QGridLayout *lay = new QGridLayout(wid); 117 | QLabel *iconLabel = new QLabel(wid); 118 | int iconSize = 64; // Icon::dlgIconSize(); 119 | iconLabel->setFixedSize(iconSize, iconSize); 120 | switch (type) { 121 | case QMessageBox::Critical: 122 | dlg->setCaption(title.isEmpty() ? QObject::tr("Error") : title); 123 | dlg->setButtons(Dialog::Ok); 124 | iconLabel->setPixmap(QIcon::fromTheme("dialog-error").pixmap(iconSize, iconSize)); 125 | break; 126 | case QMessageBox::Question: 127 | dlg->setCaption(title.isEmpty() ? QObject::tr("Question") : title); 128 | dlg->setButtons(Dialog::Yes | Dialog::No); 129 | iconLabel->setPixmap(QIcon::fromTheme("dialog-question").pixmap(iconSize, iconSize)); 130 | break; 131 | case QMessageBox::Warning: 132 | dlg->setCaption(title.isEmpty() ? QObject::tr("Warning") : title); 133 | dlg->setButtons(Dialog::Yes | Dialog::No); 134 | iconLabel->setPixmap(QIcon::fromTheme("dialog-warning").pixmap(iconSize, iconSize)); 135 | break; 136 | case QMessageBox::Information: 137 | dlg->setCaption(title.isEmpty() ? QObject::tr("Information") : title); 138 | dlg->setButtons(Dialog::Ok); 139 | iconLabel->setPixmap(QIcon::fromTheme("dialog-information").pixmap(iconSize, iconSize)); 140 | break; 141 | } 142 | lay->addWidget(iconLabel, 0, 0, 1, 1); 143 | QLabel *msgLabel = new QLabel(message, wid); 144 | msgLabel->setWordWrap(true); 145 | msgLabel->setTextInteractionFlags(Qt::NoTextInteraction); 146 | lay->addWidget(msgLabel, 0, 1, 1, 1); 147 | QListWidget *list = new QListWidget(wid); 148 | lay->addWidget(list, 1, 0, 1, 2); 149 | lay->setMargin(0); 150 | list->insertItems(0, strlist); 151 | dlg->setMainWidget(wid); 152 | #ifdef Q_OS_MAC 153 | dlg->setWindowFlags((dlg->windowFlags() & (~Qt::WindowType_Mask)) | Qt::Sheet); 154 | #endif 155 | return QDialog::Accepted == dlg->exec() ? QMessageBox::Yes : QMessageBox::No; 156 | } 157 | -------------------------------------------------------------------------------- /support/messagebox.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2016 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #ifndef MESSAGE_BOX_H 25 | #define MESSAGE_BOX_H 26 | 27 | #include 28 | 29 | namespace MessageBox { 30 | 31 | extern QMessageBox::StandardButton questionYesNoCancel(QWidget *parent, const QString &message, const QString &title = QString(), 32 | const QString &yesText = QString(), const QString &noText = QString(), bool showCancel = true, bool isWarning = false); 33 | inline QMessageBox::StandardButton questionYesNo(QWidget *parent, const QString &message, const QString &title = QString(), const QString &yesText = QString(), 34 | const QString &noText = QString()) { 35 | return questionYesNoCancel(parent, message, title, yesText, noText, false); 36 | } 37 | inline QMessageBox::StandardButton warningYesNoCancel(QWidget *parent, const QString &message, const QString &title = QString(), 38 | const QString &yesText = QString(), const QString &noText = QString()) { 39 | return questionYesNoCancel(parent, message, title, yesText, noText, true, true); 40 | } 41 | inline QMessageBox::StandardButton warningYesNo(QWidget *parent, const QString &message, const QString &title = QString(), const QString &yesText = QString(), const QString &noText = QString()) { 42 | return questionYesNoCancel(parent, message, title, yesText, noText, false, true); 43 | } 44 | #ifdef Q_OS_MAC 45 | extern void error(QWidget *parent, const QString &message, const QString &title = QString()); 46 | extern void information(QWidget *parent, const QString &message, const QString &title = QString()); 47 | #else 48 | inline void error(QWidget *parent, const QString &message, const QString &title = QString()) { 49 | QMessageBox::critical(parent, title.isEmpty() ? QObject::tr("Error") : title, message); 50 | } 51 | inline void information(QWidget *parent, const QString &message, const QString &title = QString()) { 52 | QMessageBox::information(parent, title.isEmpty() ? QObject::tr("Information") : title, message); 53 | } 54 | #endif 55 | extern QMessageBox::StandardButton msgListEx(QWidget *parent, QMessageBox::Icon type, const QString &message, const QStringList &strlist, const QString &title = QString()); 56 | inline void errorListEx(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title = QString()) { 57 | msgListEx(parent, QMessageBox::Critical, message, strlist, title); 58 | } 59 | inline void errorList(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title = QString()) { 60 | msgListEx(parent, QMessageBox::Critical, message, strlist, title); 61 | } 62 | inline QMessageBox::StandardButton questionYesNoList(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title = QString()) { 63 | return msgListEx(parent, QMessageBox::Question, message, strlist, title); 64 | } 65 | inline QMessageBox::StandardButton warningYesNoList(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title = QString()) { 66 | return msgListEx(parent, QMessageBox::Warning, message, strlist, title); 67 | } 68 | inline void informationList(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title = QString()) { 69 | msgListEx(parent, QMessageBox::Information, message, strlist, title); 70 | } 71 | 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /support/pagewidget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2014 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #include "pagewidget.h" 25 | //#include "gtkstyle.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | static int layoutText(QTextLayout *layout, int maxWidth) { 37 | qreal height = 0; 38 | int textWidth = 0; 39 | layout->beginLayout(); 40 | while (true) { 41 | QTextLine line = layout->createLine(); 42 | if (!line.isValid()) { 43 | break; 44 | } 45 | line.setLineWidth(maxWidth); 46 | line.setPosition(QPointF(0, height)); 47 | height += line.height(); 48 | textWidth = qMax(textWidth, qRound(line.naturalTextWidth() + 0.5)); 49 | } 50 | layout->endLayout(); 51 | return textWidth; 52 | } 53 | 54 | class PageWidgetItemDelegate : public QAbstractItemDelegate { 55 | public: 56 | PageWidgetItemDelegate(QObject *parent) 57 | : QAbstractItemDelegate(parent) 58 | , underMouse(false) { 59 | int height = QApplication::fontMetrics().height(); 60 | iconSize = height > 22 ? 48 : 32; 61 | } 62 | 63 | void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { 64 | if (!index.isValid()) { 65 | return; 66 | } 67 | bool mouseOver = option.state & QStyle::State_MouseOver; 68 | bool gtk = mouseOver && qApp->style()->inherits("QGtkStyle"); 69 | bool selected = option.state & QStyle::State_Selected; 70 | bool drawBgnd = true; 71 | 72 | if (!underMouse) { 73 | if (mouseOver && !selected) { 74 | drawBgnd = false; 75 | } 76 | mouseOver = false; 77 | } 78 | 79 | const QString text = index.model()->data(index, Qt::DisplayRole).toString(); 80 | const QIcon icon = index.model()->data(index, Qt::DecorationRole).value(); 81 | const QPixmap pixmap = icon.pixmap(iconSize, iconSize); 82 | 83 | QFontMetrics fm = painter->fontMetrics(); 84 | int wp = pixmap.width(); 85 | int hp = pixmap.height(); 86 | 87 | QTextLayout iconTextLayout(text, option.font); 88 | QTextOption textOption(Qt::AlignHCenter); 89 | iconTextLayout.setTextOption(textOption); 90 | int maxWidth = qMax(3 * wp, 8 * fm.height()); 91 | layoutText(&iconTextLayout, maxWidth); 92 | 93 | QPen pen = painter->pen(); 94 | QPalette::ColorGroup cg = option.state & QStyle::State_Enabled 95 | ? QPalette::Normal : QPalette::Disabled; 96 | if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { 97 | cg = QPalette::Inactive; 98 | } 99 | 100 | QStyleOptionViewItem opt(option); 101 | opt.showDecorationSelected = true; 102 | QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); 103 | 104 | if (drawBgnd) { 105 | if (mouseOver && gtk) { 106 | painter->save(); 107 | opt.state |= QStyle::State_Selected; 108 | painter->setOpacity(selected ? 0.75 : 0.25); 109 | } 110 | style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); 111 | if (mouseOver && gtk) { 112 | painter->restore(); 113 | } 114 | } 115 | 116 | if (selected) { 117 | painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); 118 | } else { 119 | painter->setPen(option.palette.color(cg, QPalette::Text)); 120 | } 121 | 122 | painter->drawPixmap(option.rect.x() + (option.rect.width() / 2) - (wp / 2), option.rect.y() + 5, pixmap); 123 | if (!text.isEmpty()) { 124 | iconTextLayout.draw(painter, QPoint(option.rect.x() + (option.rect.width() / 2) - (maxWidth / 2), option.rect.y() + hp + 7)); 125 | } 126 | painter->setPen(pen); 127 | drawFocus(painter, option, option.rect); 128 | } 129 | 130 | QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { 131 | if (!index.isValid()) { 132 | return QSize(0, 0); 133 | } 134 | 135 | const QString text = index.model()->data(index, Qt::DisplayRole).toString(); 136 | const QIcon icon = index.model()->data(index, Qt::DecorationRole).value(); 137 | const QPixmap pixmap = icon.pixmap(iconSize, iconSize); 138 | 139 | QFontMetrics fm = option.fontMetrics; 140 | int gap = fm.height(); 141 | int wp = pixmap.width(); 142 | int hp = pixmap.height(); 143 | 144 | if (hp == 0) { 145 | /** 146 | * No pixmap loaded yet, we'll use the default icon size in this case. 147 | */ 148 | hp = iconSize; 149 | wp = iconSize; 150 | } 151 | 152 | QTextLayout iconTextLayout(text, option.font); 153 | int wt = layoutText(&iconTextLayout, qMax(3 * wp, 8 * fm.height())); 154 | int ht = iconTextLayout.boundingRect().height(); 155 | 156 | int width, height; 157 | if (text.isEmpty()) { 158 | height = hp; 159 | } else { 160 | height = hp + ht + 10; 161 | 162 | } 163 | 164 | width = qMax(wt, wp) + gap; 165 | return QSize(width, height); 166 | } 167 | 168 | void drawFocus(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const { 169 | if (option.state & QStyle::State_HasFocus) { 170 | QStyleOptionFocusRect o; 171 | o.QStyleOption::operator=(option); 172 | o.rect = rect; 173 | o.state |= QStyle::State_KeyboardFocusChange; 174 | QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) 175 | ? QPalette::Normal : QPalette::Disabled; 176 | o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) 177 | ? QPalette::Highlight : QPalette::Background); 178 | QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); 179 | } 180 | } 181 | 182 | void setUnderMouse(bool u) { 183 | underMouse = u; 184 | } 185 | 186 | private: 187 | int iconSize; 188 | bool underMouse; 189 | }; 190 | 191 | class PageWidgetViewEventHandler : public QObject { 192 | public: 193 | PageWidgetViewEventHandler(PageWidgetItemDelegate *d, QListWidget *v) 194 | : QObject(v) 195 | , delegate(d) 196 | , view(v) { 197 | } 198 | 199 | protected: 200 | bool eventFilter(QObject *obj, QEvent *event) { 201 | if (delegate) { 202 | if (QEvent::Enter == event->type()) { 203 | delegate->setUnderMouse(true); 204 | view->viewport()->update(); 205 | } else if (QEvent::Leave == event->type()) { 206 | delegate->setUnderMouse(false); 207 | view->viewport()->update(); 208 | } 209 | } 210 | return QObject::eventFilter(obj, event); 211 | } 212 | 213 | protected: 214 | PageWidgetItemDelegate *delegate; 215 | QListWidget *view; 216 | }; 217 | 218 | PageWidgetItem::PageWidgetItem(QWidget *p, const QString &header, const QIcon &icon, QWidget *cfg) 219 | : QWidget(p) 220 | , wid(cfg) { 221 | static int size = -1; 222 | 223 | if (-1 == size) { 224 | size = QApplication::fontMetrics().height(); 225 | if (size > 20) { 226 | size = 32; 227 | } else { 228 | size = 22; 229 | } 230 | } 231 | 232 | QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); 233 | QBoxLayout *titleLayout = new QBoxLayout(QBoxLayout::LeftToRight, 0); 234 | titleLayout->addWidget(new QLabel("" + header + "", this)); 235 | titleLayout->addItem(new QSpacerItem(16, 16, QSizePolicy::Expanding, QSizePolicy::Minimum)); 236 | 237 | QLabel *icn = new QLabel(this); 238 | icn->setPixmap(icon.pixmap(size, size)); 239 | titleLayout->addWidget(icn); 240 | layout->addLayout(titleLayout); 241 | layout->addItem(new QSpacerItem(8, 8, QSizePolicy::Fixed, QSizePolicy::Fixed)); 242 | layout->addWidget(cfg); 243 | layout->setMargin(0); 244 | cfg->setParent(this); 245 | cfg->adjustSize(); 246 | adjustSize(); 247 | } 248 | 249 | PageWidget::PageWidget(QWidget *p) 250 | : QWidget(p) { 251 | QBoxLayout *layout = new QBoxLayout(QBoxLayout::LeftToRight, this); 252 | list = new QListWidget(p); 253 | stack = new QStackedWidget(p); 254 | connect(list, SIGNAL(currentRowChanged(int)), stack, SLOT(setCurrentIndex(int))); 255 | connect(stack, SIGNAL(currentChanged(int)), this, SIGNAL(currentPageChanged())); 256 | layout->addWidget(list); 257 | layout->addWidget(stack); 258 | layout->setMargin(0); 259 | list->setViewMode(QListView::ListMode); 260 | list->setVerticalScrollMode(QListView::ScrollPerPixel); 261 | list->setMovement(QListView::Static); 262 | PageWidgetItemDelegate *delegate = new PageWidgetItemDelegate(list); 263 | list->setItemDelegate(delegate); 264 | list->setSelectionBehavior(QAbstractItemView::SelectItems); 265 | list->setSelectionMode(QAbstractItemView::SingleSelection); 266 | list->setAttribute(Qt::WA_MouseTracking); 267 | list->installEventFilter(new PageWidgetViewEventHandler(delegate, list)); 268 | } 269 | 270 | PageWidgetItem * PageWidget::addPage(QWidget *widget, const QString &name, const QIcon &icon, const QString &header) { 271 | PageWidgetItem *page = new PageWidgetItem(stack, header, icon, widget); 272 | QListWidgetItem *listItem = new QListWidgetItem(name, list); 273 | listItem->setIcon(icon); 274 | stack->addWidget(page); 275 | list->addItem(listItem); 276 | 277 | int rows = list->model()->rowCount(); 278 | int width = 0; 279 | int height = 0; 280 | for (int i = 0; i < rows; ++i) { 281 | QSize rowSize = list->sizeHintForIndex(list->model()->index(i, 0)); 282 | width = qMax(width, rowSize.width()); 283 | height += rowSize.height(); 284 | } 285 | width += 25; 286 | list->setFixedWidth(width); 287 | list->setMinimumHeight(height); 288 | 289 | QSize stackSize = stack->size(); 290 | for (int i = 0; i < stack->count(); ++i) { 291 | const QWidget *widget = stack->widget(i); 292 | if (widget) { 293 | stackSize = stackSize.expandedTo(widget->minimumSizeHint()); 294 | } 295 | } 296 | stack->setMinimumSize(stackSize); 297 | 298 | QSize sz = size(); 299 | sz = sz.expandedTo(stackSize); 300 | sz = sz.expandedTo(QSize(width, height)); 301 | setMinimumSize(sz); 302 | list->setCurrentRow(0); 303 | stack->setCurrentIndex(0); 304 | pages.insert(listItem, page); 305 | return page; 306 | } 307 | 308 | int PageWidget::count() { 309 | return list->count(); 310 | } 311 | 312 | PageWidgetItem * PageWidget::currentPage() const { 313 | return static_cast(stack->currentWidget()); 314 | } 315 | 316 | void PageWidget::setCurrentPage(PageWidgetItem *item) { 317 | if (!item) { 318 | return; 319 | } 320 | 321 | QMap::ConstIterator it(pages.constBegin()); 322 | QMap::ConstIterator end(pages.constEnd()); 323 | 324 | for (; it != end; ++it) { 325 | if (it.value() == item) { 326 | list->setCurrentItem(it.key()); 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /support/pagewidget.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2014 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #ifndef PAGEWIDGET_H 25 | #define PAGEWIDGET_H 26 | 27 | #include 28 | #include 29 | 30 | class QIcon; 31 | class QListWidget; 32 | class QListWidgetItem; 33 | class QStackedWidget; 34 | 35 | class PageWidgetItem : public QWidget { 36 | public: 37 | PageWidgetItem(QWidget *p, const QString &header, const QIcon &icon, QWidget *cfg); 38 | virtual ~PageWidgetItem() { } 39 | QWidget * widget() const { 40 | return wid; 41 | } 42 | 43 | private: 44 | QWidget *wid; 45 | }; 46 | 47 | class PageWidget : public QWidget { 48 | Q_OBJECT 49 | 50 | public: 51 | PageWidget(QWidget *p); 52 | virtual ~PageWidget() { } 53 | PageWidgetItem * addPage(QWidget *widget, const QString &name, const QIcon &icon, const QString &header); 54 | int count(); 55 | PageWidgetItem * currentPage() const; 56 | void setCurrentPage(PageWidgetItem *item); 57 | 58 | Q_SIGNALS: 59 | void currentPageChanged(); 60 | 61 | private: 62 | QListWidget *list; 63 | QStackedWidget *stack; 64 | QMap pages; 65 | }; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /support/pathrequester.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2014 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #include "pathrequester.h" 25 | #include "utils.h" 26 | #include 27 | #include 28 | #include 29 | 30 | PathRequester::PathRequester(QWidget *parent) 31 | : QWidget(parent) 32 | , dirMode(true) { 33 | QHBoxLayout *layout = new QHBoxLayout(this); 34 | layout->setContentsMargins(0, 0, 0, 0); 35 | edit = new QLineEdit(this); 36 | btn = new QToolButton(this); 37 | layout->addWidget(edit); 38 | layout->addWidget(btn); 39 | btn->setAutoRaise(true); 40 | btn->setIcon(QIcon::fromTheme("document-open")); 41 | connect(btn, SIGNAL(clicked(bool)), SLOT(choose())); 42 | connect(edit, SIGNAL(textChanged(const QString &)), SIGNAL(textChanged(const QString &))); 43 | } 44 | 45 | void PathRequester::choose() { 46 | QString item = dirMode 47 | ? QFileDialog::getExistingDirectory(this, tr("Select Folder"), edit->text()) 48 | : QFileDialog::getOpenFileName(this, tr("Select File"), Utils::getDir(edit->text()), filter); 49 | if (!item.isEmpty()) { 50 | edit->setText(item); 51 | } 52 | } 53 | 54 | void PathRequester::setEnabled(bool e) { 55 | edit->setEnabled(e); 56 | btn->setEnabled(e); 57 | } 58 | -------------------------------------------------------------------------------- /support/pathrequester.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2014 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #ifndef PATH_REQUESER_H 25 | #define PATH_REQUESER_H 26 | 27 | #include 28 | #include 29 | 30 | class PathRequester : public QWidget { 31 | Q_OBJECT 32 | public: 33 | PathRequester(QWidget *parent); 34 | virtual ~PathRequester() { } 35 | 36 | QString text() const { 37 | return edit->text(); 38 | } 39 | void setText(const QString &t) { 40 | edit->setText(t); 41 | } 42 | void setButtonVisible(bool v) { 43 | btn->setVisible(v); 44 | } 45 | void setFocus() { 46 | edit->setFocus(); 47 | } 48 | void setDirMode(bool m) { 49 | dirMode = m; 50 | } 51 | QLineEdit * lineEdit() const { 52 | return edit; 53 | } 54 | QToolButton * button() const { 55 | return btn; 56 | } 57 | void setFilter(const QString &f) { 58 | filter = f; 59 | } 60 | 61 | public Q_SLOTS: 62 | void setEnabled(bool e); 63 | 64 | Q_SIGNALS: 65 | void textChanged(const QString &); 66 | 67 | private Q_SLOTS: 68 | void choose(); 69 | 70 | private: 71 | QLineEdit *edit; 72 | QToolButton *btn; 73 | bool dirMode; 74 | QString filter; 75 | }; 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /support/squeezedtextlabel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2016 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #include "squeezedtextlabel.h" 25 | 26 | #ifdef ENABLE_KDE_SUPPORT 27 | 28 | SqueezedTextLabel::SqueezedTextLabel(QWidget *p) 29 | : KSqueezedTextLabel(p) { 30 | bool rtl = Qt::RightToLeft == layoutDirection(); 31 | setTextElideMode(rtl ? Qt::ElideLeft : Qt::ElideRight); 32 | setAlignment((rtl ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignVCenter); 33 | } 34 | 35 | #else 36 | 37 | SqueezedTextLabel::SqueezedTextLabel(QWidget *p) 38 | : QLabel(p) { 39 | bool rtl = Qt::RightToLeft == layoutDirection(); 40 | 41 | setTextElideMode(rtl ? Qt::ElideLeft : Qt::ElideRight); 42 | setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); 43 | } 44 | 45 | void SqueezedTextLabel::setTextElideMode(Qt::TextElideMode mode) { 46 | elideMode = mode; 47 | setAlignment((elideMode & Qt::ElideLeft ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignVCenter); 48 | } 49 | 50 | void SqueezedTextLabel::elideText() { 51 | QFontMetrics fm(fontMetrics()); 52 | int labelWidth = size().width(); 53 | int lineWidth = fm.width(originalText); 54 | 55 | if (lineWidth > labelWidth) { 56 | QLabel::setText(fm.elidedText(originalText, elideMode, labelWidth)); 57 | setToolTip(originalText); 58 | } else { 59 | QLabel::setText(originalText); 60 | setToolTip(QString()); 61 | } 62 | } 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /support/squeezedtextlabel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2014 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #ifndef SQUEEZEDTEXTLABEL_H 25 | #define SQUEEZEDTEXTLABEL_H 26 | 27 | #ifdef ENABLE_KDE_SUPPORT 28 | #include 29 | class SqueezedTextLabel : public KSqueezedTextLabel { 30 | public: 31 | SqueezedTextLabel(QWidget *p); 32 | }; 33 | #else 34 | #include 35 | #include 36 | class QResizeEvent; 37 | 38 | class SqueezedTextLabel : public QLabel { 39 | public: 40 | SqueezedTextLabel(QWidget *p); 41 | 42 | void setText(const QString &text) { 43 | originalText = text; 44 | elideText(); 45 | } 46 | const QString & fullText() const { 47 | return originalText; 48 | } 49 | void setTextElideMode(Qt::TextElideMode mode); 50 | 51 | protected: 52 | QSize minimumSizeHint() const { 53 | QSize sh = QLabel::minimumSizeHint(); 54 | sh.setWidth(-1); 55 | return sh; 56 | } 57 | 58 | QSize sizeHint() const { 59 | return QSize(fontMetrics().width(originalText), QLabel::sizeHint().height()); 60 | } 61 | void resizeEvent(QResizeEvent *) { 62 | elideText(); 63 | } 64 | 65 | private: 66 | void elideText(); 67 | 68 | private: 69 | QString originalText; 70 | Qt::TextElideMode elideMode; 71 | }; 72 | #endif 73 | 74 | #endif // SQUEEZEDTEXTLABEL_H 75 | -------------------------------------------------------------------------------- /support/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2014 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #ifndef UTILS_H 25 | #define UTILS_H 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | class QString; 37 | class QWidget; 38 | 39 | namespace Utils { 40 | extern QLatin1Char constDirSep; 41 | extern QLatin1String constDirSepStr; 42 | extern const char * constDirSepCharStr; 43 | 44 | inline bool equal(double d1, double d2, double precision = 0.0001) { 45 | return (fabs(d1 - d2) < precision); 46 | } 47 | inline int random(int max) { 48 | return ::random() % max; 49 | } 50 | 51 | extern QString strippedText(QString s); 52 | extern QString stripAcceleratorMarkers(QString label); 53 | extern QString fixPath(const QString &d); 54 | // Convert directory to a format suitable fo rUI - e.g. use native separators, and remove any traling separator 55 | extern QString convertDirForDisplay(const QString &dir); 56 | // Convert directory from a UI field - convert to / separators, and add a trailing separator 57 | extern QString convertDirFromDisplay(const QString &dir); 58 | extern QString getDir(const QString &file); 59 | extern QString getFile(const QString &file); 60 | extern QString changeExtension(const QString &file, const QString &extension); 61 | extern bool makeDir(const QString &dir, int mode); 62 | extern void msleep(int msecs); 63 | inline void sleep() { 64 | msleep(100); 65 | } 66 | 67 | extern QString findExe(const QString &appname, const QString &pathstr = QString()); 68 | extern QString formatByteSize(double size); 69 | extern QString formatDuration(const quint32 totalseconds); 70 | extern QString cleanPath(const QString &p); 71 | extern QString configDir(const QString &sub = QString(), bool create = false); 72 | extern QString dataDir(const QString &sub = QString(), bool create = false); 73 | extern QString cacheDir(const QString &sub = QString(), bool create = true); 74 | extern QString systemDir(const QString &sub); 75 | extern bool moveFile(const QString &from, const QString &to); 76 | extern void moveDir(const QString &from, const QString &to); 77 | extern void clearOldCache(const QString &sub, int maxAge); 78 | extern void touchFile(const QString &fileName); 79 | extern bool isDirReadable(const QString &dir); 80 | 81 | extern int layoutSpacing(QWidget *w); 82 | extern bool isHighDpi(); 83 | extern QPainterPath buildPath(const QRectF &r, double radius); 84 | 85 | enum Desktop { 86 | KDE, 87 | Gnome, 88 | Unity, 89 | Other 90 | }; 91 | extern Desktop currentDe(); 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /ui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(carbon_SRCS 2 | excludefile.cpp 3 | excludewidget.cpp 4 | generaloptionswidget.cpp 5 | main.cpp 6 | mainwindow.cpp 7 | rsyncoptionswidget.cpp 8 | runnerdialog.cpp 9 | sessiondialog.cpp 10 | session.cpp 11 | sessionwidget.cpp 12 | treewidget.cpp 13 | basicitemdelegate.cpp) 14 | 15 | set(carbon_MOC_HDRS 16 | excludewidget.h 17 | generaloptionswidget.h 18 | mainwindow.h 19 | rsyncoptionswidget.h 20 | runnerdialog.h 21 | sessiondialog.h 22 | sessionwidget.h) 23 | 24 | set(carbon_UIS 25 | excludewidget.ui 26 | generaloptionswidget.ui 27 | rsyncoptionswidget.ui 28 | runnerwidget.ui 29 | sessionwidget.ui) 30 | 31 | set(carbon_RCS carbon.qrc) 32 | 33 | QT5_WRAP_CPP(carbon_MOC_SRCS ${carbon_MOC_HDRS}) 34 | QT5_WRAP_UI(carbon_UI_HDRS ${carbon_UIS}) 35 | QT5_ADD_RESOURCES(carbon_RC_SRCS ${carbon_RCS}) 36 | 37 | include_directories (${CMAKE_SOURCE_DIR} 38 | ${CMAKE_SOURCE_DIR}/support 39 | ${CMAKE_CURRENT_SOURCE_DIR} 40 | ${CMAKE_CURRENT_BINARY_DIR} 41 | ${CMAKE_BINARY_DIR} 42 | ${QTINCLUDES}) 43 | 44 | add_executable(carbon ${carbon_SRCS} ${carbon_MOC_SRCS} ${carbon_UI_HDRS} ${carbon_RC_SRCS}) 45 | target_link_libraries(carbon support ${QTLIBS}) 46 | install(TARGETS carbon RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) 47 | set(XDG_APPS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/share/applications") 48 | install(FILES carbon.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) 49 | install(FILES default.sync default.sync.exclude DESTINATION ${CMAKE_INSTALL_PREFIX}/share/${CMAKE_PROJECT_NAME}) 50 | -------------------------------------------------------------------------------- /ui/basicitemdelegate.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2013 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #include "basicitemdelegate.h" 25 | #include 26 | #include 27 | #include 28 | 29 | static void drawLine(QPainter *p, const QRect &r, const QColor &color, bool fadeStart, bool fadeEnd) { 30 | static const double constAlpha = 0.1; 31 | QColor col(color); 32 | QLinearGradient grad(r.bottomLeft(), r.bottomRight()); 33 | 34 | if (fadeStart || fadeEnd) { 35 | double fadeSize = (fadeStart && fadeEnd ? 64.0 : 32.0); 36 | if (r.width() < (2.2 * fadeSize)) { 37 | fadeSize = r.width() / 3.0; 38 | } 39 | double fadePos = fadeSize / r.width(); 40 | col.setAlphaF(fadeStart ? 0.0 : constAlpha); 41 | grad.setColorAt(0, col); 42 | col.setAlphaF(constAlpha); 43 | grad.setColorAt(fadePos, col); 44 | grad.setColorAt(1.0 - fadePos, col); 45 | col.setAlphaF(fadeEnd ? 0.0 : constAlpha); 46 | grad.setColorAt(1, col); 47 | p->setPen(QPen(grad, 1)); 48 | } else { 49 | col.setAlphaF(constAlpha); 50 | p->setPen(QPen(col, 1)); 51 | } 52 | p->drawLine(r.bottomLeft(), r.bottomRight()); 53 | } 54 | 55 | BasicItemDelegate::BasicItemDelegate(QObject *p) 56 | : QStyledItemDelegate(p) { 57 | } 58 | 59 | BasicItemDelegate::~BasicItemDelegate() { 60 | } 61 | 62 | void BasicItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { 63 | if (!index.isValid()) { 64 | return; 65 | } 66 | 67 | bool selected = option.state & QStyle::State_Selected; 68 | bool active = option.state & QStyle::State_Active; 69 | QStyledItemDelegate::paint(painter, option, index); 70 | QColor col(option.palette.color(active ? QPalette::Active : QPalette::Inactive, 71 | selected ? QPalette::HighlightedText : QPalette::Text)); 72 | 73 | 74 | if (4 == option.version) { 75 | const QStyleOptionViewItem &v4 = (QStyleOptionViewItem &)option; 76 | 77 | switch (v4.viewItemPosition) { 78 | case QStyleOptionViewItem::Beginning: 79 | drawLine(painter, option.rect, col, true, false); 80 | break; 81 | case QStyleOptionViewItem::Middle: 82 | drawLine(painter, option.rect, col, false, false); 83 | break; 84 | case QStyleOptionViewItem::End: 85 | drawLine(painter, option.rect, col, false, true); 86 | break; 87 | case QStyleOptionViewItem::Invalid: 88 | case QStyleOptionViewItem::OnlyOne: 89 | drawLine(painter, option.rect, col, true, true); 90 | } 91 | } else { 92 | drawLine(painter, option.rect, col, false, false); 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /ui/basicitemdelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Cantata 3 | * 4 | * Copyright (c) 2011-2013 Craig Drummond 5 | * 6 | * ---- 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; see the file COPYING. If not, write to 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | * Boston, MA 02110-1301, USA. 22 | */ 23 | 24 | #ifndef BASIC_ITEM_DELEGATE_H 25 | #define BASIC_ITEM_DELEGATE_H 26 | 27 | #include 28 | 29 | class BasicItemDelegate : public QStyledItemDelegate { 30 | public: 31 | BasicItemDelegate(QObject *p); 32 | virtual ~BasicItemDelegate(); 33 | void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /ui/carbon.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Carbon 3 | Comment=Folder Synchronisation 4 | Exec=carbon 5 | Icon=carbon 6 | Type=Application 7 | Terminal=false 8 | X-KDE-StartupNotify=true 9 | X-DBUS-StartupType=Unique 10 | Categories=Qt;System; 11 | Keywords=Sync; Synchronisation; Backup; rsync 12 | -------------------------------------------------------------------------------- /ui/carbon.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | ../icons/carbon.svg 4 | ../icons/carbon16.png 5 | ../icons/carbon22.png 6 | ../icons/carbon32.png 7 | ../icons/carbon48.png 8 | 9 | 10 | -------------------------------------------------------------------------------- /ui/default.sync: -------------------------------------------------------------------------------- 1 | [Settings] 2 | archive=true 3 | recursive=true 4 | skipFilesOnSizeMatch=false 5 | skipReceiverNewerFiles=true 6 | keepPartial=false 7 | onlyUpdate=false 8 | useCompression=false 9 | checksum=false 10 | windowsCompat=false 11 | ignoreExisting=false 12 | makeBackups=false 13 | deleteExtraFilesOnReceiver=true 14 | copySymlinksAsSymlinks=true 15 | preservePermissions=true 16 | preserveSpecialFiles=true 17 | preserveOwner=true 18 | dontLeaveFileSystem=false 19 | preserveGroup=true 20 | modificationTimes=true 21 | cvsExclude=true 22 | maxBackupAge=7 23 | customOptions= 24 | -------------------------------------------------------------------------------- /ui/default.sync.exclude: -------------------------------------------------------------------------------- 1 | # KDE3 Desktop-Communications-Protocol file 2 | .DCOPserver* 3 | # Thumnbail cache 4 | .thumbnails/ 5 | # CD Database cache 6 | .cddb/ 7 | # Cross-desktop cache 8 | .cache/ 9 | # Fonts cache 10 | .fontconfig/ 11 | # GConf Daemon 12 | .gconfd/ 13 | # GStreamer files 14 | .gstreamer* 15 | # ICE authority file 16 | .ICEauthority 17 | # KDE cache 18 | .kde*/share/cache/ 19 | # KDE3 MCOP folder 20 | .mcop/ 21 | # KDE3 MCOP resource file 22 | .mcoprc 23 | # Firefox cache 24 | .mozilla/firefox/**/Cache 25 | # Pulse Audio 26 | .pulse/ 27 | # Pulse Audio cookie 28 | .pulse-cookie 29 | # X11 errors file 30 | .xsession-errors* 31 | # X11 authority file 32 | .Xauthority 33 | # Desktop-BUS files 34 | .dbus/ 35 | # DVD-CSS 36 | .dvdcss/ 37 | # Java 38 | .java/ 39 | # Macromodia Flash 40 | .macromedia/ 41 | # Adobe Flash 42 | .adobe/ 43 | .gvfs/ 44 | [Cc]ache* 45 | [Tt]rash* 46 | *.backup* 47 | .dropbox* 48 | /proc/* 49 | /sys/* 50 | /dev/* 51 | /run/*" 52 | Ubuntu One/ 53 | -------------------------------------------------------------------------------- /ui/excludefile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "excludefile.h" 23 | #include 24 | #include 25 | #include 26 | 27 | ExcludeFile::ExcludeFile(const QString &name) 28 | : fileName(name) { 29 | load(); 30 | } 31 | 32 | void ExcludeFile::load() { 33 | QFile f(fileName); 34 | 35 | if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { 36 | QString comment; 37 | 38 | while (!f.atEnd()) { 39 | QString line(f.readLine().trimmed()); 40 | 41 | if (!line.isEmpty()) { 42 | if (QChar('#') == line[0] || QChar(';') == line[0]) { 43 | comment = line.mid(1).trimmed(); 44 | } else { 45 | patternList.append(Pattern(line, comment)); 46 | comment = QString(); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | bool ExcludeFile::save(const QString &n) { 54 | QString name = n.isEmpty() ? fileName : n; 55 | if (0 == patternList.count()) { 56 | bool rv = erase(); 57 | fileName = name; 58 | return rv; 59 | } 60 | 61 | QFile f(name); 62 | 63 | if (f.open(QIODevice::WriteOnly | QIODevice::Text)) { 64 | QTextStream fs(&f); 65 | 66 | foreach (const Pattern &p, patternList) { 67 | if (!p.comment.isEmpty()) { 68 | fs << "# " << p.comment << endl; 69 | } 70 | fs << p.value << endl; 71 | } 72 | 73 | fs.flush(); 74 | f.close(); 75 | 76 | if (fileName != name) { 77 | erase(); 78 | } 79 | fileName = name; 80 | return true; 81 | } 82 | 83 | return false; 84 | } 85 | 86 | bool ExcludeFile::erase() { 87 | return fileName.isEmpty() || !QFile::exists(fileName) || 0 ==::unlink(QFile::encodeName(fileName).constData()); 88 | } 89 | 90 | QString ExcludeFile::Pattern::toStr() { 91 | return QString(value + QString(" # ") + comment).trimmed(); 92 | } 93 | -------------------------------------------------------------------------------- /ui/excludefile.h: -------------------------------------------------------------------------------- 1 | #ifndef __EXCLUDE_FILE_H__ 2 | #define __EXCLUDE_FILE_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | class ExcludeFile { 29 | public: 30 | struct Pattern { 31 | Pattern(const QString &v = QString(), const QString &c = QString()) 32 | : value(v), comment(c) { } 33 | 34 | QString toStr(); 35 | 36 | QString value; 37 | QString comment; 38 | }; 39 | 40 | typedef QList PatternList; 41 | 42 | ExcludeFile(const QString &name); 43 | 44 | void load(); 45 | bool save(const QString &n = QString()); 46 | bool erase(); 47 | const PatternList & patterns() const { 48 | return patternList; 49 | } 50 | void setPatterns(const QList &v) { 51 | patternList = v; 52 | } 53 | 54 | private: 55 | QString fileName; 56 | PatternList patternList; 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /ui/excludewidget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "excludewidget.h" 23 | #include "excludefile.h" 24 | #include "session.h" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #define CFG_GROUP "ExcludeWidget/" 35 | #define CFG_COL_SIZES CFG_GROUP "List" 36 | 37 | ExcludeWidget::ExcludeWidget(QWidget *parent) 38 | : QWidget(parent) { 39 | setupUi(this); 40 | 41 | addButton->setText(tr("Add")); 42 | addButton->setToolTip(tr("Add an exclude pattern.")); 43 | addButton->setIcon(QIcon::fromTheme("list-add")); 44 | removeButton->setText(tr("Delete")); 45 | removeButton->setToolTip(tr("Delete the selected exclude patterns.")); 46 | removeButton->setIcon(QIcon::fromTheme("list-remove")); 47 | 48 | connect(addButton, SIGNAL(clicked()), SLOT(add())); 49 | connect(removeButton, SIGNAL(clicked()), SLOT(remove())); 50 | connect(excludeList, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), SLOT(editItem(QTreeWidgetItem *, int))); 51 | connect(excludeList, SIGNAL(itemSelectionChanged()), SLOT(controlButtons())); 52 | 53 | QStringList list; 54 | QSettings cfg; 55 | list = cfg.value(CFG_COL_SIZES, list).toStringList(); 56 | 57 | if (2 == list.count()) { 58 | for (int i = 0; i < 2; ++i) { 59 | excludeList->header()->resizeSection(i, list[i].toInt()); 60 | } 61 | } 62 | 63 | excludeList->sortItems(0, Qt::AscendingOrder); 64 | } 65 | 66 | ExcludeWidget::~ExcludeWidget() { 67 | QStringList list; 68 | for (int i = 0; i < 2; ++i) { 69 | list << QString::number(excludeList->header()->sectionSize(i)); 70 | } 71 | 72 | QSettings cfg; 73 | cfg.setValue(CFG_COL_SIZES, list); 74 | } 75 | 76 | void ExcludeWidget::set(const Session &session) { 77 | cvsExclude->setChecked(session.cvsExcludeFlag()); 78 | maxSize->setValue(session.maxSize()); 79 | excludeList->clear(); 80 | if (session.excludeFile()) { 81 | foreach (const ExcludeFile::Pattern &p, session.excludeFile()->patterns()) { 82 | addPattern(p.value, p.comment); 83 | } 84 | } 85 | controlButtons(); 86 | } 87 | 88 | void ExcludeWidget::get(Session &session) { 89 | session.setCvsExcludeFlag(cvsExclude->isChecked()); 90 | session.setMaxSize(maxSize->value()); 91 | 92 | ExcludeFile::PatternList list; 93 | for (int i = 0; i < excludeList->topLevelItemCount(); ++i) { 94 | QTreeWidgetItem *item = excludeList->topLevelItem(i); 95 | list.append(ExcludeFile::Pattern(item->text(0), item->text(1))); 96 | } 97 | session.setExcludePatterns(list); 98 | } 99 | 100 | void ExcludeWidget::addPattern(const QString &v, const QString &c) { 101 | QStringList list; 102 | 103 | list << v; 104 | if (!c.isEmpty()) { 105 | list << c; 106 | } 107 | 108 | QTreeWidgetItem *i = new QTreeWidgetItem(excludeList, list); 109 | i->setFlags(i->flags() | Qt::ItemIsEditable); 110 | } 111 | 112 | void ExcludeWidget::add() { 113 | QString items(QInputDialog::getText(this, tr("New Exclude Patterns"), 114 | tr("

Please enter a comma separated list of patterns " 115 | "to exclude.

e.g. *.sav, *.inf

")).trimmed()); 116 | 117 | if (!items.isEmpty()) { 118 | QStringList patterns(items.split(',', QString::SkipEmptyParts)); 119 | QStringList::ConstIterator it(patterns.begin()), 120 | end(patterns.end()); 121 | 122 | for (; it != end; ++it) { 123 | QString v((*it).trimmed()); 124 | 125 | if (0 == excludeList->findItems(v, Qt::MatchExactly, 0).count()) { 126 | addPattern(v); 127 | } 128 | } 129 | } 130 | } 131 | 132 | void ExcludeWidget::remove() { 133 | QList items(excludeList->selectedItems()); 134 | 135 | if (items.count() && QMessageBox::Yes == QMessageBox::warning(this, tr("Delete"), tr("Delete all the selected patterns?"), QMessageBox::Yes | QMessageBox::No)) { 136 | foreach (QTreeWidgetItem *i, items) { 137 | delete i; 138 | } 139 | 140 | controlButtons(); 141 | } 142 | } 143 | 144 | void ExcludeWidget::controlButtons() { 145 | removeButton->setEnabled(excludeList->selectedItems().count()); 146 | } 147 | 148 | void ExcludeWidget::editItem(QTreeWidgetItem *item, int col) { 149 | excludeList->editItem(item, col); 150 | } 151 | -------------------------------------------------------------------------------- /ui/excludewidget.h: -------------------------------------------------------------------------------- 1 | #ifndef __EXCLUDE_WIDGET_H__ 2 | #define __EXCLUDE_WIDGET_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include "ui_excludewidget.h" 26 | 27 | class Session; 28 | class QTreeWidgetItem; 29 | 30 | class ExcludeWidget : public QWidget, Ui::ExcludeWidget { 31 | Q_OBJECT 32 | 33 | public: 34 | ExcludeWidget(QWidget *parent); 35 | virtual ~ExcludeWidget(); 36 | 37 | void set(const Session &session); 38 | void get(Session &session); 39 | 40 | private: 41 | void addPattern(const QString &v, const QString &c = QString()); 42 | 43 | private Q_SLOTS: 44 | void add(); 45 | void remove(); 46 | void controlButtons(); 47 | void editItem(QTreeWidgetItem *item, int col); 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /ui/excludewidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ExcludeWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 384 10 | 219 11 | 12 | 13 | 14 | 15 | 0 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 28 | 29 | 30 | 0 31 | 0 32 | 33 | 34 | 35 | 36 | Pattern 37 | 38 | 39 | 40 | 41 | Comment 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 0 51 | 0 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 0 61 | 0 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Qt::Vertical 70 | 71 | 72 | 73 | 20 74 | 154 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Exclude files which would be ignored by <i>CVS</i>. This includes the following list of patterns:<br/><br/> 83 | RCS SCCS CVS CVS.adm RCSLOG cvslog.* tags TAGS .make.state .nse_depinfo *~ #* .#* ,* _$* *$ *.old *.bak *.BAK *.orig *.rej .del-* *.a *.olb *.o *.obj *.so *.exe *.Z *.elc *.ln core .svn/<br/><br/> 84 | (--cvs-exclude) 85 | 86 | 87 | CVS exclude 88 | 89 | 90 | 91 | 92 | 93 | 94 | 0 95 | 96 | 97 | 98 | 99 | Exclude files larger than: 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 0 108 | 0 109 | 110 | 111 | 112 | M 113 | 114 | 115 | 0 116 | 117 | 118 | 65535 119 | 120 | 121 | 100 122 | 123 | 124 | No Limit 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | TreeWidget 137 | QTreeWidget 138 |
treewidget.h
139 |
140 |
141 |
142 | -------------------------------------------------------------------------------- /ui/generaloptionswidget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "generaloptionswidget.h" 23 | #include "session.h" 24 | 25 | GeneralOptionsWidget::GeneralOptionsWidget(QWidget *parent, bool showName) 26 | : QWidget(parent) { 27 | setupUi(this); 28 | if (!showName) { 29 | nameEdit->setVisible(false); 30 | nameLabel->setVisible(false); 31 | } 32 | srcPath->setDirMode(true); 33 | destPath->setDirMode(true); 34 | type->insertItem(0, QObject::tr("Synchronisation")); 35 | type->insertItem(1, QObject::tr("Backup")); 36 | connect(type, SIGNAL(activated(int)), SLOT(typeChanged(int))); 37 | } 38 | 39 | void GeneralOptionsWidget::set(const Session &session, bool edit) { 40 | type->setEnabled(!edit); 41 | nameEdit->setText(session.isDefault() ? QObject::tr("New Session") : session.name()); 42 | srcPath->setText(Utils::convertDirForDisplay(session.source())); 43 | destPath->setText(Utils::convertDirForDisplay(session.destination())); 44 | type->setCurrentIndex(session.makeBackupsFlag() ? 1 : 0); 45 | typeChanged(type->currentIndex()); 46 | if (session.maxBackupDays()) { 47 | maxDays->setValue(session.maxBackupDays()); 48 | deleteOld->setChecked(true); 49 | } else { 50 | maxDays->setValue(7); 51 | dontDeleteOld->setChecked(true); 52 | } 53 | } 54 | 55 | void GeneralOptionsWidget::get(Session &session) { 56 | session.setSource(src()); 57 | session.setDestination(dest()); 58 | session.setMakeBackupsFlag(type->currentIndex() ? true : false); 59 | session.setMaxBackupDays(deleteOld->isChecked() ? maxDays->value() : 0); 60 | session.setName(name()); 61 | } 62 | 63 | void GeneralOptionsWidget::typeChanged(int idx) { 64 | ageWidget->setVisible(1 == idx); 65 | } 66 | -------------------------------------------------------------------------------- /ui/generaloptionswidget.h: -------------------------------------------------------------------------------- 1 | #ifndef __GENERALOPTIONS_WIDGET_H__ 2 | #define __GENERALOPTIONS_WIDGET_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include "ui_generaloptionswidget.h" 26 | #include "pathrequester.h" 27 | #include "utils.h" 28 | 29 | class Session; 30 | 31 | class GeneralOptionsWidget : public QWidget, Ui::GeneralOptionsWidget { 32 | Q_OBJECT 33 | 34 | public: 35 | GeneralOptionsWidget(QWidget *parent, bool showName = true); 36 | 37 | QString name() const { 38 | return nameEdit->text().trimmed(); 39 | } 40 | QString src() const { 41 | return Utils::convertDirFromDisplay(srcPath->text()); 42 | } 43 | QString dest() const { 44 | return Utils::convertDirFromDisplay(destPath->text()); 45 | } 46 | 47 | void set(const Session &session, bool edit); 48 | void get(Session &session); 49 | 50 | private Q_SLOTS: 51 | void typeChanged(int idx); 52 | }; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /ui/generaloptionswidget.ui: -------------------------------------------------------------------------------- 1 | 2 | GeneralOptionsWidget 3 | 4 | 5 | 6 | 0 7 | 0 8 | 435 9 | 243 10 | 11 | 12 | 13 | 14 | 0 15 | 16 | 17 | 18 | 19 | Qt::Vertical 20 | 21 | 22 | 23 | 20 24 | 4 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Name: 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Source: 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 0 51 | 0 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Destination: 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 0 68 | 0 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | Type: 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 0 85 | 0 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | QFrame::NoFrame 94 | 95 | 96 | QFrame::Raised 97 | 98 | 99 | 0 100 | 101 | 102 | 103 | 0 104 | 105 | 106 | 107 | 108 | Delete increments older than: 109 | 110 | 111 | true 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 0 120 | 0 121 | 122 | 123 | 124 | day(s) 125 | 126 | 127 | 1 128 | 129 | 130 | 365 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 0 139 | 0 140 | 141 | 142 | 143 | Don't delete old increments 144 | 145 | 146 | 147 | 148 | 149 | 150 | Qt::Vertical 151 | 152 | 153 | QSizePolicy::Minimum 154 | 155 | 156 | 157 | 20 158 | 22 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | PathRequester 171 | QFrame 172 |
pathrequester.h
173 |
174 |
175 | 176 | 177 | 178 | deleteOld 179 | toggled(bool) 180 | maxDays 181 | setEnabled(bool) 182 | 183 | 184 | 172 185 | 156 186 | 187 | 188 | 358 189 | 159 190 | 191 | 192 | 193 | 194 |
195 | -------------------------------------------------------------------------------- /ui/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "utils.h" 23 | #include "mainwindow.h" 24 | #include "messagebox.h" 25 | #include "config.h" 26 | #include 27 | #include 28 | #ifdef QT_QTDBUS_FOUND 29 | #include 30 | #endif 31 | #include 32 | 33 | static QString fixArg(QString a, const QString ¶m) { 34 | a = a.mid(param.length()); 35 | if (a[0] == '\'' || a[0] == '\"') { 36 | a = a.mid(1); 37 | } 38 | if (a.endsWith('\'') || a.endsWith('\"')) { 39 | a = a.left(a.length() - 1); 40 | } 41 | return a; 42 | } 43 | 44 | int main(int argc, char **argv) { 45 | QApplication app(argc, argv); 46 | 47 | #ifdef QT_QTDBUS_FOUND 48 | if (!QDBusConnection::sessionBus().registerService("org.craigd.carbon")) { 49 | return -1; 50 | } 51 | #endif 52 | 53 | QString rsync(Utils::findExe("rsync")); 54 | 55 | if (rsync.isEmpty()) { 56 | MessageBox::error(0L, QObject::tr("Carbon is a GUI front-end for \'rsync\'\nThis tool could not be found on your system." 57 | " Please install this and restart Carbon")); 58 | return -1; 59 | } else { 60 | QCoreApplication::setApplicationName(CARBON_PACKAGE_NAME); 61 | QCoreApplication::setOrganizationName(CARBON_PACKAGE_NAME); 62 | 63 | MainWindow mw(rsync); 64 | mw.show(); 65 | return app.exec(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ui/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "mainwindow.h" 23 | #include "sessionwidget.h" 24 | #include "config.h" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define CFG_GROUP "MainWindow/" 33 | #define CFG_SIZE CFG_GROUP "Size" 34 | 35 | QIcon MainWindow::appIcon; 36 | 37 | MainWindow::MainWindow(const QString &rsync) 38 | : QMainWindow(0L) 39 | , rsync(rsync) { 40 | bool root = 0 == getuid(); 41 | 42 | QList appSizes = QList() << 16 << 22 << 32 << 48; 43 | foreach (int s, appSizes) { 44 | appIcon.addFile(QString(":carbon%1%2.png").arg(root ? "-root" : "").arg(s), QSize(s, s)); 45 | } 46 | appIcon.addFile(root ? ":carbon-root.svg" : ":carbon.svg"); 47 | setWindowIcon(appIcon); 48 | 49 | setWindowTitle(root ? tr("Carbon (root)") : tr("Carbon")); 50 | sessionWidget = new SessionWidget(this); 51 | setCentralWidget(sessionWidget); 52 | setupActions(); 53 | createGUI(); 54 | 55 | QSettings cfg; 56 | resize(cfg.value(CFG_SIZE, QSize(600, 300)).toSize()); 57 | setMinimumSize(600, 200); 58 | 59 | if (root) { 60 | sessionWidget->setBackground(appIcon); 61 | } 62 | } 63 | 64 | MainWindow::~MainWindow() { 65 | QSettings cfg; 66 | cfg.setValue(CFG_SIZE, size()); 67 | } 68 | 69 | void MainWindow::setupActions() { 70 | quitAction = new QAction(QIcon::fromTheme("application-exit"), tr("Quit"), this); 71 | connect(quitAction, SIGNAL(triggered(bool)), qApp, SLOT(quit())); 72 | quitAction->setToolTip(tr("Quit application.")); 73 | 74 | aboutAction = new QAction(QIcon::fromTheme("dialog-information"), tr("About"), this); 75 | connect(aboutAction, SIGNAL(triggered(bool)), this, SLOT(about())); 76 | 77 | newSessionAction = new QAction(QIcon::fromTheme("document-new"), tr("New..."), this); 78 | connect(newSessionAction, SIGNAL(triggered(bool)), sessionWidget, SLOT(newSession())); 79 | newSessionAction->setToolTip(tr("Create a new synchronisation session (either a plain synchronisation, or a backup).")); 80 | 81 | editSessionAction = new QAction(QIcon::fromTheme("document-properties"), tr("Edit..."), this); 82 | connect(editSessionAction, SIGNAL(triggered(bool)), sessionWidget, SLOT(editSession())); 83 | editSessionAction->setToolTip(tr("Change synchronisation session properties.")); 84 | 85 | deleteSessionAction = new QAction(QIcon::fromTheme("edit-delete"), tr("Delete..."), this); 86 | connect(deleteSessionAction, SIGNAL(triggered(bool)), sessionWidget, SLOT(removeSession())); 87 | 88 | QIcon icon = QIcon::fromTheme("view-history"); 89 | if (icon.isNull()) { 90 | icon = QIcon::fromTheme("document"); 91 | } 92 | showLogAction = new QAction(icon, tr("Show Last Log..."), this); 93 | connect(showLogAction, SIGNAL(triggered(bool)), sessionWidget, SLOT(showSessionLog())); 94 | 95 | dryRunAction = new QAction(QIcon::fromTheme("system-run"), tr("Dry-run..."), this); 96 | connect(dryRunAction, SIGNAL(triggered(bool)), sessionWidget, SLOT(dryRunSession())); 97 | dryRunAction->setToolTip(tr("Show what would happen with a sync, but don't actually do anything.")); 98 | 99 | syncAction = new QAction(appIcon, tr("Synchronise..."), this); 100 | connect(syncAction, SIGNAL(triggered(bool)), sessionWidget, SLOT(syncSession())); 101 | syncAction->setToolTip(tr("Perform synchronisation.")); 102 | 103 | // restoreAction=ActionCollection::get()->createAction("restore", tr("Restore..."), QIcon::fromTheme("edit-undo")); 104 | // restoreAction->setCheckable(true); 105 | // connect(restoreAction, SIGNAL(toggled(bool)), SLOT(restoreSession(bool))); 106 | 107 | deleteSessionAction->setEnabled(false); 108 | editSessionAction->setEnabled(false); 109 | showLogAction->setEnabled(false); 110 | dryRunAction->setEnabled(false); 111 | syncAction->setEnabled(false); 112 | 113 | connect(sessionWidget, SIGNAL(itemsSelected(bool)), deleteSessionAction, SLOT(setEnabled(bool))); 114 | connect(sessionWidget, SIGNAL(singleItemSelected(bool)), editSessionAction, SLOT(setEnabled(bool))); 115 | connect(sessionWidget, SIGNAL(haveLog(bool)), showLogAction, SLOT(setEnabled(bool))); 116 | connect(sessionWidget, SIGNAL(haveSessions(bool)), dryRunAction, SLOT(setEnabled(bool))); 117 | connect(sessionWidget, SIGNAL(haveSessions(bool)), syncAction, SLOT(setEnabled(bool))); 118 | // connect(sessionWidget, SIGNAL(haveSessions(bool)), restoreAction, SLOT(setEnabled(bool))); 119 | 120 | QMenu *menu = new QMenu(sessionWidget); 121 | menu->addAction(deleteSessionAction); 122 | menu->addAction(editSessionAction); 123 | menu->addSeparator(); 124 | menu->addAction(showLogAction); 125 | menu->addSeparator(); 126 | menu->addAction(dryRunAction); 127 | menu->addAction(syncAction); 128 | sessionWidget->setMenu(menu); 129 | } 130 | 131 | class Spacer : public QWidget { 132 | public: 133 | Spacer(QWidget *p) 134 | : QWidget(p) { 135 | setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); 136 | } 137 | }; 138 | 139 | void MainWindow::createGUI() { 140 | QToolBar *tb = new QToolBar(this); 141 | tb->addAction(newSessionAction); 142 | tb->addAction(editSessionAction); 143 | tb->addAction(deleteSessionAction); 144 | // tb->addSeparator(); 145 | // tb->addAction(showLogAction); 146 | tb->addSeparator(); 147 | tb->addAction(dryRunAction); 148 | tb->addAction(syncAction); 149 | // tb->addAction(restoreAction); 150 | tb->addWidget(new Spacer(tb)); 151 | tb->addAction(aboutAction); 152 | tb->addAction(quitAction); 153 | tb->setMovable(false); 154 | tb->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 155 | tb->toggleViewAction()->setVisible(false); 156 | addToolBar(tb); 157 | } 158 | 159 | void MainWindow::restoreSession(bool on) { 160 | newSessionAction->setEnabled(!on); 161 | if (on) { 162 | editSessionAction->setEnabled(false); 163 | deleteSessionAction->setEnabled(false); 164 | showLogAction->setEnabled(false); 165 | dryRunAction->setEnabled(false); 166 | syncAction->setEnabled(false); 167 | } 168 | 169 | sessionWidget->setEnabled(!on); 170 | sessionWidget->restoreSession(on); 171 | } 172 | 173 | void MainWindow::about() { 174 | QMessageBox::about(this, tr("About Carbon"), 175 | tr("Carbon %1

rsync front-end.

" 176 | "(C) Craig Drummond 2013.
Released under the GPLv3").arg(CARBON_VERSION)); 177 | 178 | } 179 | -------------------------------------------------------------------------------- /ui/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef __MAIN_WINDOW_H__ 2 | #define __MAIN_WINDOW_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include 26 | 27 | class SessionWidget; 28 | class QAction; 29 | class QIcon; 30 | 31 | class MainWindow : public QMainWindow { 32 | Q_OBJECT 33 | 34 | public: 35 | MainWindow(const QString &rsync); 36 | virtual ~MainWindow(); 37 | 38 | static QIcon appIcon; 39 | 40 | private Q_SLOTS: 41 | void restoreSession(bool on); 42 | void about(); 43 | 44 | private: 45 | void setupActions(); 46 | void createGUI(); 47 | 48 | private: 49 | QString rsync; 50 | QAction *newSessionAction; 51 | QAction *editSessionAction; 52 | QAction *deleteSessionAction; 53 | QAction *showLogAction; 54 | QAction *dryRunAction; 55 | QAction *syncAction; 56 | QAction *restoreAction; 57 | QAction *aboutAction; 58 | QAction *quitAction; 59 | SessionWidget *sessionWidget; 60 | }; 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /ui/rsyncoptionswidget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "rsyncoptionswidget.h" 23 | #include "session.h" 24 | #include 25 | #include 26 | #include 27 | 28 | RSyncOptionsWidget::RSyncOptionsWidget(QWidget *parent) 29 | : QWidget(parent) { 30 | setupUi(this); 31 | 32 | // if (0!=getuid()) { 33 | // preserveSpecialFiles->setEnabled(false); 34 | // preserveOwner->setEnabled(false); 35 | // preserveGroup->setEnabled(false); 36 | // } 37 | 38 | connect(archive, SIGNAL(toggled(bool)), SLOT(archiveToggled(bool))); 39 | connect(copySymlinksAsSymlinks, SIGNAL(toggled(bool)), SLOT(preserveToggled(bool))); 40 | connect(preservePermissions, SIGNAL(toggled(bool)), SLOT(preserveToggled(bool))); 41 | connect(preserveSpecialFiles, SIGNAL(toggled(bool)), SLOT(preserveToggled(bool))); 42 | connect(preserveOwner, SIGNAL(toggled(bool)), SLOT(preserveToggled(bool))); 43 | connect(preserveGroup, SIGNAL(toggled(bool)), SLOT(preserveToggled(bool))); 44 | } 45 | 46 | void RSyncOptionsWidget::set(const Session &session) { 47 | archive->setChecked(session.archiveFlag()); 48 | recursive->setChecked(session.recursiveFlag()); 49 | skipFilesOnSizeMatch->setChecked(session.skipFilesOnSizeMatchFlag()); 50 | skipReceiverNewerFiles->setChecked(session.skipReceiverNewerFilesFlag()); 51 | keepPartial->setChecked(session.keepPartialFlag()); 52 | onlyUpdate->setChecked(session.onlyUpdateFlag()); 53 | useCompression->setChecked(session.useCompressionFlag()); 54 | checksum->setChecked(session.checksumFlag()); 55 | windowsCompatability->setChecked(session.windowsFlag()); 56 | ignoreExisting->setChecked(session.ignoreExistingFlag()); 57 | deleteExtraFilesOnReceiver->setChecked(session.deleteExtraFilesOnReceiverFlag()); 58 | copySymlinksAsSymlinks->setChecked(session.copySymlinksAsSymlinksFlag()); 59 | preservePermissions->setChecked(session.preservePermissionsFlag()); 60 | preserveSpecialFiles->setChecked(session.preserveSpecialFilesFlag()); 61 | preserveOwner->setChecked(session.preserveOwnerFlag()); 62 | dontLeaveFileSystem->setChecked(session.dontLeaveFileSystemFlag()); 63 | preserveGroup->setChecked(session.preserveGroupFlag()); 64 | modificationTimes->setChecked(session.modificationTimesFlag()); 65 | customOptions->setText(session.customOpts()); 66 | } 67 | 68 | void RSyncOptionsWidget::get(Session &session) { 69 | session.setArchiveFlag(archive->isChecked()); 70 | session.setRecursiveFlag(recursive->isChecked()); 71 | session.setSkipFilesOnSizeMatchFlag(skipFilesOnSizeMatch->isChecked()); 72 | session.setSkipReceiverNewerFilesFlag(skipReceiverNewerFiles->isChecked()); 73 | session.setKeepPartialFlag(keepPartial->isChecked()); 74 | session.setOnlyUpdateFlag(onlyUpdate->isChecked()); 75 | session.setUseCompressionFlag(useCompression->isChecked()); 76 | session.setChecksumFlag(checksum->isChecked()); 77 | session.setWindowsFlag(windowsCompatability->isChecked()); 78 | session.setIgnoreExistingFlag(ignoreExisting->isChecked()); 79 | session.setDeleteExtraFilesOnReceiverFlag(deleteExtraFilesOnReceiver->isChecked()); 80 | session.setCopySymlinksAsSymlinksFlag(copySymlinksAsSymlinks->isChecked()); 81 | session.setPreservePermissionsFlag(preservePermissions->isChecked()); 82 | session.setPreserveSpecialFilesFlag(preserveSpecialFiles->isChecked()); 83 | session.setPreserveOwnerFlag(preserveOwner->isChecked()); 84 | session.setDontLeaveFileSystemFlag(dontLeaveFileSystem->isChecked()); 85 | session.setPreserveGroupFlag(preserveGroup->isChecked()); 86 | session.setModificationTimesFlag(modificationTimes->isChecked()); 87 | session.setCustomOpts(customOptions->text().trimmed()); 88 | } 89 | 90 | void RSyncOptionsWidget::preserveToggled(bool on) { 91 | if (!on) { 92 | archive->setChecked(false); 93 | } 94 | } 95 | 96 | void RSyncOptionsWidget::archiveToggled(bool on) { 97 | if (on) { 98 | recursive->setChecked(true); 99 | copySymlinksAsSymlinks->setChecked(true); 100 | preservePermissions->setChecked(true); 101 | preserveSpecialFiles->setChecked(true); 102 | preserveOwner->setChecked(true); 103 | preserveGroup->setChecked(true); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ui/rsyncoptionswidget.h: -------------------------------------------------------------------------------- 1 | #ifndef __RSYNCOPTIONS_WIDGET_H__ 2 | #define __RSYNCOPTIONS_WIDGET_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include "ui_rsyncoptionswidget.h" 26 | 27 | class Session; 28 | 29 | class RSyncOptionsWidget : public QWidget, Ui::RSyncOptionsWidget { 30 | Q_OBJECT 31 | 32 | public: 33 | RSyncOptionsWidget(QWidget *parent); 34 | 35 | void set(const Session &session); 36 | void get(Session &session); 37 | 38 | QString getCustom() const { 39 | return customOptions->text(); 40 | } 41 | 42 | private Q_SLOTS: 43 | void preserveToggled(bool on); 44 | void archiveToggled(bool on); 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /ui/rsyncoptionswidget.ui: -------------------------------------------------------------------------------- 1 | 2 | RSyncOptionsWidget 3 | 4 | 5 | 6 | 0 7 | 0 8 | 478 9 | 446 10 | 11 | 12 | 13 | 14 | 0 15 | 16 | 17 | 18 | 19 | General Options 20 | 21 | 22 | 23 | 24 | 25 | Archive mode (--archive) 26 | Activates: Recursive, copy syymlinks as symlinks, preservce permissions/owner/group/device files. 27 | 28 | 29 | Archive mode 30 | 31 | 32 | 33 | 34 | 35 | 36 | Keep partially transferred files (--partial) 37 | 38 | 39 | Keep partially transferred file 40 | 41 | 42 | 43 | 44 | 45 | 46 | Recurse into folders (--recursive) 47 | 48 | 49 | Recursive 50 | 51 | 52 | 53 | 54 | 55 | 56 | Skip creating new files on receiver (--existing) 57 | 58 | 59 | Only update existing files 60 | 61 | 62 | 63 | 64 | 65 | 66 | Compress file data during the transfer (--compress) 67 | 68 | 69 | Compression 70 | 71 | 72 | Alt+O 73 | 74 | 75 | 76 | 77 | 78 | 79 | Skip files that match in size (--size-only) 80 | 81 | 82 | Size only 83 | 84 | 85 | 86 | 87 | 88 | 89 | Skip updating files that exist on receiver (--ignore-existing) 90 | 91 | 92 | Ignore existing 93 | 94 | 95 | 96 | 97 | 98 | 99 | Skip files that are newer on the receiver (--update) 100 | 101 | 102 | Skip newer 103 | 104 | 105 | 106 | 107 | 108 | 109 | Skip based on checksum, not modification time and size (--checksum) 110 | 111 | 112 | Always Checksum 113 | 114 | 115 | 116 | 117 | 118 | 119 | Windows compatibility (--modify-window=1) 120 | 121 | 122 | Windows compatibility 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | Filesystem and Preservation Options 133 | 134 | 135 | 136 | 11 137 | 138 | 139 | 6 140 | 141 | 142 | 143 | 144 | Delete extraneous files from destination folder (--delete) 145 | 146 | 147 | Delete non-existing files 148 | 149 | 150 | 151 | 152 | 153 | 154 | Don't cross filesystem boundaries (--one-file-system) 155 | 156 | 157 | Don't leave filesystem 158 | 159 | 160 | 161 | 162 | 163 | 164 | Preserve permissions (--perms) 165 | 166 | 167 | Preserve Permissions 168 | 169 | 170 | 171 | 172 | 173 | 174 | Preserve device files and preserve special files (-D) 175 | 176 | 177 | Preserve Device and special files 178 | 179 | 180 | 181 | 182 | 183 | 184 | Preserve owner (--owner) 185 | 186 | 187 | Preserve Owners 188 | 189 | 190 | 191 | 192 | 193 | 194 | Preserve group (--group) 195 | 196 | 197 | Preserve Groups 198 | 199 | 200 | 201 | 202 | 203 | 204 | Preserve modification times (--times) 205 | 206 | 207 | Preserve Times 208 | 209 | 210 | 211 | 212 | 213 | 214 | Copy symlinks as symlinks (--links) 215 | 216 | 217 | Copy symlinks as symlinks 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | Custom Options 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | Qt::Vertical 240 | 241 | 242 | 243 | 20 244 | 191 245 | 246 | 247 | 248 | 249 | 250 | GroupBox1 251 | GroupBox2 252 | groupBox5 253 | customOptions 254 | 255 | 256 | qPixmapFromMimeSource 257 | 258 | 259 | 260 | -------------------------------------------------------------------------------- /ui/runnerdialog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "runnerdialog.h" 23 | #include "session.h" 24 | #include "utils.h" 25 | #include "messagebox.h" 26 | #include 27 | #include 28 | #include 29 | #ifdef QT_QTDBUS_FOUND 30 | #include 31 | #include 32 | #include 33 | #endif 34 | 35 | static inline QString removePrefixes(const QString &str) { 36 | return QString(str).replace(CARBON_PREFIX, QString()).replace(CARBON_MSG_PREFIX, QString()).replace(CARBON_ERROR_PREFIX, QString()); 37 | } 38 | 39 | static QString updatedFiles(int v) { 40 | return 0 == v ? QObject::tr("Checking for updated files...") : QObject::tr("Checking for updated files...%1").arg(v); 41 | } 42 | 43 | static QString toStr(int exitCode) { 44 | switch (exitCode) { 45 | case 101: 46 | return QObject::tr("Usage error."); 47 | case 102: 48 | return QObject::tr("Could not read session file."); 49 | case 103: 50 | return QObject::tr("Destination folder does not exist, and could not be created."); 51 | case 104: 52 | return QObject::tr("Destination exists as a file."); 53 | case 105: 54 | return QObject::tr("Failed to mount destination folder."); 55 | case 106: 56 | return QObject::tr("Failed to unmount destination folder."); 57 | case 107: 58 | return QObject::tr("Mountpoint already exists."); 59 | case 108: 60 | return QObject::tr("Destination requires user@host"); 61 | case 109: 62 | return QObject::tr("No source supplied."); 63 | case 110: 64 | return QObject::tr("No destination supplied."); 65 | case 111: 66 | return QObject::tr("Destination parent folder does not exist."); 67 | case 112: 68 | return QObject::tr("Source does not exist."); 69 | case 113: 70 | return QObject::tr("Session is already running."); 71 | case 1: 72 | return QObject::tr("Syntax or usage error."); 73 | case 2: 74 | return QObject::tr("Protocol incompatibility."); 75 | case 3: 76 | return QObject::tr("Errors selecting input/output files/folders."); 77 | case 4: 78 | return QObject::tr("Requested action not supported: an attempt " 79 | "was made to manipulate 64-bit files on a " 80 | "platform that cannot support them; or an " 81 | "option was specified that is supported by " 82 | "the client and not by the server."); 83 | case 5: 84 | return QObject::tr("Error starting client-server protocol."); 85 | case 6: 86 | return QObject::tr("Daemon unable to append to log-file."); 87 | case 10: 88 | return QObject::tr("Error in socket I/O."); 89 | case 11: 90 | return QObject::tr("Error in file I/O."); 91 | case 12: 92 | return QObject::tr("Error in rsync protocol data stream."); 93 | case 13: 94 | return QObject::tr("Errors with program diagnostics."); 95 | case 14: 96 | return QObject::tr("Error in IPC code."); 97 | case 20: 98 | return QObject::tr("Received SIGUSR1 or SIGINT."); 99 | case 21: 100 | return QObject::tr("Some error returned by waitpid()."); 101 | case 22: 102 | return QObject::tr("Error allocating core memory buffers."); 103 | case 23: 104 | return QObject::tr("Partial transfer due to error."); 105 | case 24: 106 | return QObject::tr("Partial transfer due to vanished source files."); 107 | case 25: 108 | return QObject::tr("The --max-delete limit stopped deletions."); 109 | case 30: 110 | return QObject::tr("Timeout in data send/receive."); 111 | case 35: 112 | return QObject::tr("Timeout waiting for daemon connection."); 113 | default: 114 | return QObject::tr("Unknown error code %1.").arg(exitCode); 115 | } 116 | 117 | return QString(); 118 | } 119 | 120 | RunnerDialog::RunnerDialog(QWidget *parent) 121 | : Dialog(parent) 122 | , process(0) { 123 | QWidget *mainWidget = new QWidget(this); 124 | 125 | setupUi(mainWidget); 126 | setMainWidget(mainWidget); 127 | setButtons(Cancel); 128 | output->setReadOnly(true); 129 | output->setVisible(false); 130 | setMinimumWidth(500); 131 | connect(detailsButton, SIGNAL(toggled(bool)), this, SLOT(showDetails(bool))); 132 | #ifdef QT_QTDBUS_FOUND 133 | unityMessage = QDBusMessage::createSignal("/Carbon", "com.canonical.Unity.LauncherEntry", "Update"); 134 | #endif 135 | } 136 | 137 | RunnerDialog::~RunnerDialog() { 138 | disconnectProcess(); 139 | } 140 | 141 | void RunnerDialog::go(const QList &sessions, bool dry) { 142 | setWindowTitle(dry ? tr("Performing Dry-Run") : tr("Performing Synchronisation")); 143 | 144 | completedSessions = 0; 145 | overallProgress->setVisible(sessions.count() > 1); 146 | overallProgressLabel->setVisible(sessions.count() > 1); 147 | sessionLabel->setText(QString()); 148 | status->setText(QString()); 149 | fileProgress->setValue(0); 150 | overallProgress->setValue(0); 151 | overallProgress->setMaximum(sessions.count() * 1000); 152 | sessionProgress->setValue(0); 153 | sessionProgress->setMaximum(0); 154 | detailsButton->setChecked(false); 155 | dryRun = dry; 156 | 157 | stdErr = prevStout = QString(); 158 | output->setText(QString()); 159 | currentSession = sessions.begin(); 160 | endSession = sessions.end(); 161 | sessionCount = sessions.count(); 162 | if (!process) { 163 | process = new QProcess(this); 164 | QStringList env(QProcess::systemEnvironment()); 165 | env.append(CARBON_GUI_PARENT"=true"); 166 | process->setEnvironment(env); 167 | connect(process, SIGNAL(finished(int)), this, SLOT(processFinished(int))); 168 | connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStdOut())); 169 | connect(process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); 170 | } 171 | setButtons(Cancel); 172 | QTimer::singleShot(0, this, SLOT(doNext())); 173 | QTimer::singleShot(0, this, SLOT(showDetails())); 174 | exec(); 175 | } 176 | 177 | void RunnerDialog::doNext() { 178 | syncStatus = STARTUP; 179 | if (currentSession != endSession) { 180 | sessionLabel->setText((*currentSession)->name()); 181 | 182 | QStringList arguments; 183 | 184 | (*currentSession)->save(); 185 | arguments << (*currentSession)->fileName(); 186 | 187 | if (dryRun) { 188 | arguments << "-d"; 189 | } 190 | 191 | stdErr = QString(); 192 | prevStout = QString(); 193 | sessionProgress->setValue(0); 194 | sessionProgress->setMaximum(0); 195 | updateUnity(false); 196 | status->setText(updatedFiles(0)); 197 | fileProgress->setValue(0); 198 | logFile.close(); 199 | logFile.setFileName((*currentSession)->fileName() + CARBON_LOG_EXTENSION); 200 | logFile.open(QIODevice::WriteOnly); 201 | process->start(CARBON_RUNNER, arguments, QIODevice::ReadOnly); 202 | } else { 203 | fileProgress->setValue(fileProgress->maximum()); 204 | setButtons(Close); 205 | } 206 | } 207 | 208 | void RunnerDialog::processFinished(int exitCode) { 209 | logFile.close(); 210 | 211 | if (0 != exitCode) { 212 | QString errorMsg = tr("

The rsync backend returned the following error:

%1

").arg(toStr(exitCode)); 213 | status->setText(tr("An error ocurred")); 214 | if (currentSession == endSession) { 215 | MessageBox::error(this, errorMsg); 216 | return; 217 | } else if (QMessageBox::No == MessageBox::warningYesNo(this, errorMsg + "

" + tr("Continue with next session?") + "

")) { 218 | return; 219 | } 220 | } 221 | 222 | completedSessions++; 223 | sessionProgress->setMaximum(1000); 224 | sessionProgress->setValue(sessionProgress->maximum()); 225 | overallProgress->setValue(completedSessions * 1000); 226 | updateUnity(currentSession == endSession); 227 | if (currentSession == endSession) { 228 | status->setText(tr("Cancelled")); 229 | } else { 230 | if (0 == exitCode) { 231 | status->setText(tr("Finished")); 232 | } 233 | currentSession++; 234 | doNext(); 235 | } 236 | } 237 | 238 | void RunnerDialog::readStdOut() { 239 | if (!process) { 240 | return; 241 | } 242 | 243 | QByteArray all(process->readAllStandardOutput()); 244 | QString raw(prevStout + all); 245 | QString str(raw.replace('\r', '\n')); 246 | bool doLast(str.size() && '\n' == str[str.size() - 1]); 247 | QStringList lines(str.split('\n', QString::SkipEmptyParts)); 248 | QStringList::Iterator it(lines.begin()); 249 | QStringList::Iterator end(lines.end()); 250 | int numLines(lines.count()); 251 | 252 | for (int l = 1; it != end; ++it, ++l) { 253 | if (l == numLines) { 254 | if (doLast) { 255 | processLine(*it); 256 | } else { 257 | prevStout = *it; 258 | } 259 | } else { 260 | processLine(*it); 261 | } 262 | } 263 | str = removePrefixes(all); 264 | output->append(str); 265 | QTextStream(&logFile) << str; 266 | } 267 | 268 | void RunnerDialog::readStdErr() { 269 | if (!process) { 270 | return; 271 | } 272 | 273 | QString str(removePrefixes(process->readAllStandardError())); 274 | 275 | stdErr += str; 276 | output->append("" + str + ""); 277 | QTextStream(&logFile) << str; 278 | } 279 | 280 | void RunnerDialog::showDetails(bool show) { 281 | int w = width(); 282 | if (show) { 283 | setMaximumHeight(4096); 284 | } 285 | 286 | detailsButton->setText(show ? tr("Hide Details <<") : tr("Show Details >>")); 287 | output->setVisible(show); 288 | adjustSize(); 289 | resize(width()setText(line); 304 | fileProgress->setValue(0); 305 | } else if (isMsg) { 306 | line.replace(CARBON_MSG_PREFIX, QString()) 307 | .replace("Cleaning old backups", tr("Cleaning old backups")) 308 | .replace("Erasing", tr("Erasing")); 309 | 310 | status->setText(line); 311 | syncStatus = SYNCING; 312 | } else if (isError) { 313 | line.replace(CARBON_ERROR_PREFIX, QString()) 314 | .replace("Cleaning old backups", tr("Cleaning old backups")) 315 | .replace("Erasing", tr("Erasing")); 316 | 317 | status->setText(QString("") + line + QString("")); 318 | syncStatus = SYNCING; 319 | } /*else if (STARTUP==syncStatus && -1!=line.indexOf("files to consider")) { 320 | QStringList lst(line.split(' ', QString::SkipEmptyParts)); 321 | 322 | if (lst.size()) 323 | { 324 | bool ok; 325 | int total(lst[0].toInt(&ok)); 326 | 327 | if (ok) 328 | { 329 | sessionProgress->setMaximum(total); 330 | syncStatus=SYNCING; 331 | } 332 | } 333 | }*/ else if (STARTUP == syncStatus && -1 != line.indexOf(" files...")) { 334 | QStringList lst(line.split(' ', QString::SkipEmptyParts)); 335 | 336 | if (lst.size()) { 337 | bool ok; 338 | int total(lst[0].toInt(&ok)); 339 | 340 | if (ok) { 341 | status->setText(updatedFiles(total)); 342 | } 343 | } 344 | } else if (-1 != line.indexOf("%")) { 345 | QStringList lst(line.split(' ', QString::SkipEmptyParts)); 346 | 347 | if (lst.size() > 2) { 348 | int percent; 349 | 350 | if (1 == sscanf(lst[1].toLatin1().constData(), "%d%%", &percent)) { 351 | fileProgress->setValue(percent); 352 | syncStatus = SYNCING; 353 | } 354 | static const QString constGlobalCheck = QLatin1String("to-check="); 355 | foreach (const QString &str, lst) { 356 | if (str.startsWith(constGlobalCheck)) { 357 | lst = str.mid(constGlobalCheck.length()).split('/'); 358 | if (lst.size() >= 2) { 359 | QString totStr = lst.at(1); 360 | totStr = totStr.left(totStr.length() - 1); 361 | int total = totStr.toInt(); 362 | int left = lst.at(0).toInt(); 363 | if (0 == sessionProgress->maximum()) { 364 | sessionProgress->setMaximum(1000); 365 | } 366 | 367 | if (total > 0 && left <= total) { 368 | if (0 == left) { 369 | sessionProgress->setValue(sessionProgress->maximum()); 370 | } else { 371 | sessionProgress->setValue((((total - left) * 1000.0) / total) + 0.5); 372 | } 373 | overallProgress->setValue((1000 * completedSessions) + (sessionProgress->value())); 374 | updateUnity(false); 375 | } 376 | } 377 | break; 378 | } 379 | } 380 | } 381 | } 382 | } 383 | 384 | void RunnerDialog::slotButtonClicked(int btn) { 385 | if (Dialog::Cancel == btn && process && QProcess::NotRunning != process->state()) { 386 | switch (MessageBox::warningYesNoCancel(this, tr("Abort the current synchronisation?"), 387 | tr("Abort"), tr("Abort Now"), 388 | tr("Abort After Current Sync"))) { 389 | case QMessageBox::Yes: 390 | QProcess::startDetached(CARBON_TERMINATE, QStringList() << QString::number(process->pid())); 391 | for (int i = 0; i < 50 && QProcess::Running == process->state(); ++i) { 392 | Utils::msleep(10); 393 | } 394 | process->kill(); 395 | disconnectProcess(); 396 | if (currentSession != endSession) { 397 | (*currentSession)->removeLockFile(); 398 | } 399 | updateUnity(true); 400 | QDialog::reject(); 401 | break; 402 | case QMessageBox::No: 403 | currentSession = endSession; 404 | default: 405 | break; 406 | } 407 | } else { 408 | Dialog::slotButtonClicked(btn); 409 | } 410 | } 411 | 412 | void RunnerDialog::disconnectProcess() { 413 | if (process) { 414 | disconnect(process, SIGNAL(finished(int)), this, SLOT(processFinished(int))); 415 | disconnect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStdOut())); 416 | disconnect(process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); 417 | process->deleteLater(); 418 | process = 0; 419 | } 420 | } 421 | 422 | void RunnerDialog::updateUnity(bool finished) { 423 | #ifdef QT_QTDBUS_FOUND 424 | QList args; 425 | double progress = finished || overallProgress->maximum() < 1 ? 0.0 : (overallProgress->value() / (overallProgress->maximum() * 1.0)); 426 | bool showProgress = progress > -0.1 && progress < 100 && !finished; 427 | QMap props; 428 | props["count-visible"] = !finished && sessionCount > 1; 429 | props["count"] = (long long)(sessionCount - completedSessions); 430 | props["progress-visible"] = showProgress; 431 | props["progress"] = showProgress ? progress : 0.0; 432 | args.append(0 == getuid() ? "application://carbon-root.desktop" : "application://carbon.desktop"); 433 | args.append(props); 434 | unityMessage.setArguments(args); 435 | QDBusConnection::sessionBus().send(unityMessage); 436 | #else 437 | Q_UNUSED(finished) 438 | #endif 439 | } 440 | -------------------------------------------------------------------------------- /ui/runnerdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef __RUNNER_DIALOG_H__ 2 | #define __RUNNER_DIALOG_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include "dialog.h" 26 | #include "config.h" 27 | #include 28 | #include 29 | #ifdef QT_QTDBUS_FOUND 30 | #include 31 | #endif 32 | #include "ui_runnerwidget.h" 33 | 34 | class Session; 35 | class QProcess; 36 | 37 | 38 | class RunnerDialog : public Dialog, Ui::RunnerWidget { 39 | Q_OBJECT 40 | 41 | public: 42 | enum EStatus { 43 | STARTUP, 44 | SYNCING 45 | }; 46 | 47 | RunnerDialog(QWidget *parent); 48 | virtual ~RunnerDialog(); 49 | 50 | void go(const QList &sessions, bool dry); 51 | 52 | public Q_SLOTS: 53 | void doNext(); 54 | void processFinished(int exitCode); 55 | void readStdOut(); 56 | void readStdErr(); 57 | void showDetails(bool show = false); 58 | 59 | private: 60 | void processLine(QString &line); 61 | void slotButtonClicked(int btn); 62 | void disconnectProcess(); 63 | void updateUnity(bool finished); 64 | 65 | private: 66 | QList::ConstIterator currentSession; 67 | QList::ConstIterator endSession; 68 | QString prevStout; 69 | QString stdErr; 70 | bool dryRun; 71 | QProcess *process; 72 | QFile logFile; 73 | EStatus syncStatus; 74 | int sessionCount; 75 | int completedSessions; 76 | #ifdef QT_QTDBUS_FOUND 77 | QDBusMessage unityMessage; 78 | #endif 79 | }; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /ui/runnerwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | RunnerWidget 3 | 4 | 5 | 6 | 0 7 | 0 8 | 408 9 | 200 10 | 11 | 12 | 13 | 14 | 0 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 0 22 | 23 | 24 | 25 | Session: 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 0 34 | 0 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Status: 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | File progress: 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | 68 | 69 | 70 | 71 | 72 | 73 | Session progress: 74 | 75 | 76 | 77 | 78 | 79 | 80 | 0 81 | 82 | 83 | 84 | 85 | 86 | 87 | Overall progress: 88 | 89 | 90 | 91 | 92 | 93 | 94 | 0 95 | 96 | 97 | 98 | 99 | 100 | 101 | true 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /ui/session.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "session.h" 23 | #include "utils.h" 24 | #include "config.h" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define CFG_READ_BOOL(V, DEF) V=entries.contains(#V) ? QLatin1String("true")==entries[#V] : DEF 33 | #define CFG_READ_INT(V, DEF) V=entries.contains(#V) ? entries[#V].toInt() : DEF 34 | #define CFG_READ_STR(V, DEF) V=entries.contains(#V) ? entries[#V] : DEF 35 | #define CFG_READ_STRING(V, DEF) V=entries.contains(#V) ? entries[#V] : DEF 36 | 37 | #define CFG_WRITE_INT(V) out << #V << "=" << V << endl 38 | #define CFG_WRITE_BOOL(V) out << #V << "=" << (V ? "true" : "false") << endl 39 | #define CFG_WRITE_STR(V) out << #V << "=" << V << endl 40 | 41 | static QString getName(const QString &f) { 42 | return QFileInfo(f).fileName().remove(CARBON_EXTENSION); 43 | } 44 | 45 | Session::Session(const QString &file, bool def) 46 | : isDef(def) 47 | , exclude(0) { 48 | if (isDef) { 49 | dirName = Utils::configDir(QString(), true); 50 | sessionName = QLatin1String("default"); 51 | } else { 52 | dirName = Utils::getDir(file); 53 | sessionName = getName(file); 54 | } 55 | 56 | QString fName = fileName(); 57 | 58 | if (isDef) { 59 | if (!QFile::exists(fName)) { 60 | fName = QLatin1String(CARBON_SYSTEM_DEF_FILE); 61 | } 62 | QString exFileName = excludeFileName(); 63 | if (!QFile::exists(exFileName)) { 64 | exFileName = QLatin1String(CARBON_SYSTEM_DEF_FILE CARBON_EXCLUDE_EXTENSION); 65 | } 66 | exclude = new ExcludeFile(exFileName); 67 | } else { 68 | exclude = new ExcludeFile(excludeFileName()); 69 | } 70 | 71 | QFile f(fName); 72 | QMap entries; 73 | if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { 74 | QTextStream in(&f); 75 | while (!in.atEnd()) { 76 | QString line = in.readLine(); 77 | if (!line.startsWith("#") && !line.startsWith("[") && line.contains('=')) { 78 | QStringList parts = line.split("="); 79 | if (parts.length() > 1) { 80 | entries[parts.at(0)] = parts.at(1); 81 | } 82 | } 83 | } 84 | } 85 | 86 | CFG_READ_STR(src, QDir::homePath()); 87 | CFG_READ_STR(dest, QLatin1String("/tmp")); 88 | CFG_READ_BOOL(archive, true); 89 | CFG_READ_BOOL(recursive, true); 90 | CFG_READ_BOOL(skipFilesOnSizeMatch, false); 91 | CFG_READ_BOOL(skipReceiverNewerFiles, true); 92 | CFG_READ_BOOL(keepPartial, false); 93 | CFG_READ_BOOL(onlyUpdate, false); 94 | CFG_READ_BOOL(useCompression, false); 95 | CFG_READ_BOOL(checksum, false); 96 | CFG_READ_BOOL(windowsCompat, false); 97 | CFG_READ_BOOL(ignoreExisting, false); 98 | CFG_READ_BOOL(makeBackups, false); 99 | CFG_READ_BOOL(deleteExtraFilesOnReceiver, true); 100 | CFG_READ_BOOL(copySymlinksAsSymlinks, true); 101 | CFG_READ_BOOL(preservePermissions, true); 102 | CFG_READ_BOOL(preserveSpecialFiles, true); 103 | CFG_READ_BOOL(preserveOwner, true); 104 | CFG_READ_BOOL(dontLeaveFileSystem, false); 105 | CFG_READ_BOOL(preserveGroup, true); 106 | CFG_READ_BOOL(modificationTimes, true); 107 | CFG_READ_BOOL(cvsExclude, true); 108 | CFG_READ_INT(maxBackupAge, 7); 109 | CFG_READ_INT(maxFileSize, 0); 110 | 111 | if (archive) { 112 | copySymlinksAsSymlinks = preservePermissions = preserveSpecialFiles = preserveOwner = 113 | preserveGroup = true; 114 | } 115 | if (maxBackupAge > 365) { 116 | maxBackupAge = 365; 117 | } else if (maxBackupAge < 0) { 118 | maxBackupAge = 0; 119 | } 120 | 121 | if (maxFileSize > 65535) { 122 | maxFileSize = 65535; 123 | } else if (maxFileSize < 0) { 124 | maxFileSize = 0; 125 | } 126 | 127 | CFG_READ_STRING(customOptions, QString()); 128 | 129 | if (!customOptions.isEmpty()) { 130 | if (QLatin1Char('\"') == customOptions[0]) { 131 | customOptions = customOptions.mid(1); 132 | } 133 | if (!customOptions.isEmpty() && QChar('\"') == customOptions[customOptions.size() - 1]) { 134 | customOptions = customOptions.left(customOptions.size() - 1); 135 | } 136 | if (!customOptions.isEmpty()) { 137 | customOptions.replace("\\\"", "\"").replace("\\\'", "\'"); 138 | } 139 | } 140 | 141 | updateLast(); 142 | } 143 | 144 | Session::Session() 145 | : isDef(false) 146 | , exclude(0L) { 147 | } 148 | 149 | Session::~Session() { 150 | delete exclude; 151 | } 152 | 153 | void Session::updateLast() { 154 | QFile log(logFileName()); 155 | 156 | lastSyncDate = !log.exists() 157 | ? QString() 158 | : QFileInfo(log).created().toString(Qt::SystemLocaleShortDate); 159 | } 160 | 161 | bool Session::save(const QString &name) { 162 | QString fName = dirName + (isDef ? sessionName : name) + QLatin1String(CARBON_EXTENSION); 163 | QFile f(fName); 164 | 165 | if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { 166 | return false; 167 | } 168 | 169 | if (!isDef && name != sessionName) { 170 | removeFiles(); 171 | sessionName = name; 172 | } 173 | 174 | QTextStream out(&f); 175 | 176 | out << "[Settings]" << endl; 177 | CFG_WRITE_STR(src); 178 | CFG_WRITE_STR(dest); 179 | CFG_WRITE_BOOL(archive); 180 | CFG_WRITE_BOOL(recursive); 181 | CFG_WRITE_BOOL(skipFilesOnSizeMatch); 182 | CFG_WRITE_BOOL(skipReceiverNewerFiles); 183 | CFG_WRITE_BOOL(keepPartial); 184 | CFG_WRITE_BOOL(onlyUpdate); 185 | CFG_WRITE_BOOL(useCompression); 186 | CFG_WRITE_BOOL(checksum); 187 | CFG_WRITE_BOOL(windowsCompat); 188 | CFG_WRITE_BOOL(ignoreExisting); 189 | CFG_WRITE_BOOL(makeBackups); 190 | CFG_WRITE_BOOL(deleteExtraFilesOnReceiver); 191 | CFG_WRITE_BOOL(copySymlinksAsSymlinks); 192 | CFG_WRITE_BOOL(preservePermissions); 193 | CFG_WRITE_BOOL(preserveSpecialFiles); 194 | CFG_WRITE_BOOL(preserveOwner); 195 | CFG_WRITE_BOOL(dontLeaveFileSystem); 196 | CFG_WRITE_BOOL(preserveGroup); 197 | CFG_WRITE_BOOL(modificationTimes); 198 | CFG_WRITE_BOOL(cvsExclude); 199 | CFG_WRITE_INT(maxBackupAge); 200 | CFG_WRITE_INT(maxFileSize); 201 | 202 | QString temp = customOptions; 203 | if (!temp.isEmpty()) { 204 | temp = QChar('\"') + temp.replace('\"', "\\\"").replace('\'', "\\\'") + QChar('\"'); 205 | } 206 | out << "customOptions=" << temp << endl; 207 | 208 | if (exclude) { 209 | exclude->save(excludeFileName()); 210 | } 211 | 212 | return true; 213 | } 214 | 215 | bool Session::erase() { 216 | bool rv(removeFiles()); 217 | 218 | if (rv) { 219 | src = QString(); // Stop destructor from saving file! 220 | } 221 | 222 | return rv; 223 | } 224 | 225 | bool Session::sync(bool dryRun) { 226 | if (dryRun) { 227 | return true; // "add -d" 228 | } 229 | return false; 230 | } 231 | 232 | void Session::setName(const QString &v) { 233 | if (v != sessionName && removeFiles()) { 234 | QString oldName(sessionName), 235 | oldDir(dirName); 236 | 237 | dirName = Utils::dataDir(QString(), true); 238 | if (save(v)) { 239 | lastSyncDate = QString(); 240 | sessionName = v; 241 | } else { 242 | sessionName = oldName; 243 | dirName = oldDir; 244 | save(); 245 | } 246 | } 247 | } 248 | 249 | void Session::setExcludePatterns(const ExcludeFile::PatternList &list) { 250 | if (!exclude) { 251 | exclude = new ExcludeFile(excludeFileName()); 252 | } 253 | 254 | exclude->setPatterns(list); 255 | } 256 | 257 | static bool removeFile(const QString &file) { 258 | return file.isEmpty() || !QFile::exists(file) || QFile::remove(file); 259 | } 260 | 261 | bool Session::removeLockFile() { 262 | return removeFile(lockFileName()); 263 | } 264 | 265 | bool Session::removeFiles() { 266 | if (removeFile(logFileName()) && removeFile(infoFileName()) && removeFile(lockFileName()) && 267 | (!exclude || exclude->erase()) && removeFile(fileName())) { 268 | return true; 269 | } 270 | 271 | return false; 272 | } 273 | -------------------------------------------------------------------------------- /ui/session.h: -------------------------------------------------------------------------------- 1 | #ifndef __SESSION_H__ 2 | #define __SESSION_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "excludefile.h" 28 | #include "config.h" 29 | 30 | class Session { 31 | public: 32 | Session(const QString &name, bool def = false); 33 | Session(); 34 | ~Session(); 35 | 36 | operator bool() { 37 | return !src.isEmpty(); 38 | } 39 | void updateLast(); 40 | bool save() { 41 | return save(sessionName); 42 | } 43 | bool save(const QString &name); 44 | bool erase(); 45 | bool sync(bool dryRun); 46 | 47 | bool removeLockFile(); 48 | bool isDefault() const { 49 | return isDef; 50 | } 51 | const QString & name() const { 52 | return sessionName; 53 | } 54 | QString fileName() const { 55 | return dirName + sessionName + QLatin1String(CARBON_EXTENSION); 56 | } 57 | QString logFileName() const { 58 | return dirName + sessionName + QLatin1String(CARBON_EXTENSION CARBON_LOG_EXTENSION); 59 | } 60 | QString infoFileName() const { 61 | return dirName + sessionName + QLatin1String(CARBON_EXTENSION CARBON_INFO_EXTENSION); 62 | } 63 | QString lockFileName() const { 64 | return dirName + sessionName + QLatin1String(CARBON_EXTENSION CARBON_LOCK_EXTENSION); 65 | } 66 | QString excludeFileName() const { 67 | return dirName + sessionName + QLatin1String(CARBON_EXTENSION CARBON_EXCLUDE_EXTENSION); 68 | } 69 | const QString & last() const { 70 | return lastSyncDate; 71 | } 72 | const QString & source() const { 73 | return src; 74 | } 75 | const QString & destination() const { 76 | return dest; 77 | } 78 | const QString & customOpts() const { 79 | return customOptions; 80 | } 81 | ExcludeFile * excludeFile() const { 82 | return exclude; 83 | } 84 | void setName(const QString &v); 85 | void setDir(const QString &v) { 86 | dirName = v; 87 | } 88 | void setSource(const QString &v) { 89 | src = v; 90 | } 91 | void setDestination(const QString &v) { 92 | dest = v; 93 | } 94 | void setCustomOpts(const QString &v) { 95 | customOptions = v; 96 | } 97 | void setExcludePatterns(const ExcludeFile::PatternList &list); 98 | bool archiveFlag() const { 99 | return archive; 100 | } 101 | bool recursiveFlag() const { 102 | return recursive; 103 | } 104 | bool skipFilesOnSizeMatchFlag() const { 105 | return skipFilesOnSizeMatch; 106 | } 107 | bool skipReceiverNewerFilesFlag() const { 108 | return skipReceiverNewerFiles; 109 | } 110 | bool keepPartialFlag() const { 111 | return keepPartial; 112 | } 113 | bool onlyUpdateFlag() const { 114 | return onlyUpdate; 115 | } 116 | bool useCompressionFlag() const { 117 | return useCompression; 118 | } 119 | bool checksumFlag() const { 120 | return checksum; 121 | } 122 | bool windowsFlag() const { 123 | return windowsCompat; 124 | } 125 | bool ignoreExistingFlag() const { 126 | return ignoreExisting; 127 | } 128 | bool makeBackupsFlag() const { 129 | return makeBackups; 130 | } 131 | bool deleteExtraFilesOnReceiverFlag() const { 132 | return deleteExtraFilesOnReceiver; 133 | } 134 | bool copySymlinksAsSymlinksFlag() const { 135 | return copySymlinksAsSymlinks; 136 | } 137 | bool preservePermissionsFlag() const { 138 | return preservePermissions; 139 | } 140 | bool preserveSpecialFilesFlag() const { 141 | return preserveSpecialFiles; 142 | } 143 | bool preserveOwnerFlag() const { 144 | return preserveOwner; 145 | } 146 | bool dontLeaveFileSystemFlag() const { 147 | return dontLeaveFileSystem; 148 | } 149 | bool preserveGroupFlag() const { 150 | return preserveGroup; 151 | } 152 | bool modificationTimesFlag() const { 153 | return modificationTimes; 154 | } 155 | bool cvsExcludeFlag() const { 156 | return cvsExclude; 157 | } 158 | int maxBackupDays() const { 159 | return maxBackupAge; 160 | } 161 | int maxSize() const { 162 | return maxFileSize; 163 | } 164 | void setArchiveFlag(bool v) { 165 | archive = v; 166 | } 167 | void setRecursiveFlag(bool v) { 168 | recursive = v; 169 | } 170 | void setSkipFilesOnSizeMatchFlag(bool v) { 171 | skipFilesOnSizeMatch = v; 172 | } 173 | void setSkipReceiverNewerFilesFlag(bool v) { 174 | skipReceiverNewerFiles = v; 175 | } 176 | void setKeepPartialFlag(bool v) { 177 | keepPartial = v; 178 | } 179 | void setOnlyUpdateFlag(bool v) { 180 | onlyUpdate = v; 181 | } 182 | void setUseCompressionFlag(bool v) { 183 | useCompression = v; 184 | } 185 | void setChecksumFlag(bool v) { 186 | checksum = v; 187 | } 188 | void setWindowsFlag(bool v) { 189 | windowsCompat = v; 190 | } 191 | void setIgnoreExistingFlag(bool v) { 192 | ignoreExisting = v; 193 | } 194 | void setMakeBackupsFlag(bool v) { 195 | makeBackups = v; 196 | } 197 | void setDeleteExtraFilesOnReceiverFlag(bool v) { 198 | deleteExtraFilesOnReceiver = v; 199 | } 200 | void setCopySymlinksAsSymlinksFlag(bool v) { 201 | copySymlinksAsSymlinks = v; 202 | } 203 | void setPreservePermissionsFlag(bool v) { 204 | preservePermissions = v; 205 | } 206 | void setPreserveSpecialFilesFlag(bool v) { 207 | preserveSpecialFiles = v; 208 | } 209 | void setPreserveOwnerFlag(bool v) { 210 | preserveOwner = v; 211 | } 212 | void setDontLeaveFileSystemFlag(bool v) { 213 | dontLeaveFileSystem = v; 214 | } 215 | void setPreserveGroupFlag(bool v) { 216 | preserveGroup = v; 217 | } 218 | void setModificationTimesFlag(bool v) { 219 | modificationTimes = v; 220 | } 221 | void setCvsExcludeFlag(bool v) { 222 | cvsExclude = v; 223 | } 224 | void setMaxBackupDays(int v) { 225 | maxBackupAge = v; 226 | } 227 | void setMaxSize(int v) { 228 | maxFileSize = v; 229 | } 230 | 231 | private: 232 | Session(const Session &o); 233 | 234 | bool removeFiles(); 235 | 236 | private: 237 | bool isDef; 238 | QString dirName; 239 | QString sessionName; 240 | QString lastSyncDate; 241 | QString src; 242 | QString dest; 243 | bool archive; 244 | bool recursive; 245 | bool skipFilesOnSizeMatch; 246 | bool skipReceiverNewerFiles; 247 | bool keepPartial; 248 | bool onlyUpdate; 249 | bool useCompression; 250 | bool checksum; 251 | bool windowsCompat; 252 | bool ignoreExisting; 253 | bool makeBackups; 254 | bool deleteExtraFilesOnReceiver; 255 | bool copySymlinksAsSymlinks; 256 | bool preservePermissions; 257 | bool preserveSpecialFiles; 258 | bool preserveOwner; 259 | bool dontLeaveFileSystem; 260 | bool preserveGroup; 261 | bool modificationTimes; 262 | bool cvsExclude; 263 | int maxBackupAge; 264 | int maxFileSize; 265 | ExcludeFile *exclude; 266 | QString customOptions; 267 | }; 268 | 269 | #endif 270 | -------------------------------------------------------------------------------- /ui/sessiondialog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "session.h" 23 | #include "sessiondialog.h" 24 | #include "sessionwidget.h" 25 | #include "generaloptionswidget.h" 26 | #include "excludewidget.h" 27 | #include "rsyncoptionswidget.h" 28 | #include "mainwindow.h" 29 | #include "messagebox.h" 30 | #include "pagewidget.h" 31 | #include "utils.h" 32 | #include 33 | 34 | SessionDialog::SessionDialog(QWidget *parent) 35 | : Dialog(parent, "SessionDialog", QSize(800, 600)) { 36 | pageWidget = new PageWidget(this); 37 | setButtons(Ok | Cancel | User1 | User2); 38 | setButtonText(User1, tr("Set Defaults")); 39 | setButtonText(User2, tr("Show RSync Manual")); 40 | 41 | generalOptions = new GeneralOptionsWidget(0); 42 | excludeWidget = new ExcludeWidget(0); 43 | rSyncOptionsWidget = new RSyncOptionsWidget(0); 44 | 45 | generalPage = pageWidget->addPage(generalOptions, tr("General Options"), QIcon::fromTheme("folder"), tr("Basic Synchronisation Session Options")); 46 | excludePage = pageWidget->addPage(excludeWidget, tr("Exclusions"), QIcon::fromTheme("edit-delete"), tr("Exclude Files And Folders From Synchronisation")); 47 | rSyncOptionsPage = pageWidget->addPage(rSyncOptionsWidget, tr("Backend Options"), MainWindow::appIcon, tr("RSync Backend Options")); 48 | setMainWidget(pageWidget); 49 | } 50 | 51 | bool SessionDialog::run(Session &session, bool edit) { 52 | origName = edit ? session.name() : QString(); 53 | origSrcPath = edit ? session.source() : QString(); 54 | setCaption(edit ? tr("Edit Session") : tr("Create New Session")); 55 | generalOptions->set(session, edit); 56 | excludeWidget->set(session); 57 | rSyncOptionsWidget->set(session); 58 | pageWidget->setCurrentPage(generalPage); 59 | 60 | return QDialog::Accepted == exec(); 61 | } 62 | 63 | void SessionDialog::slotButtonClicked(int btn) { 64 | switch (btn) { 65 | case Ok: { 66 | QString newName(generalOptions->name()); 67 | QString newSrc(generalOptions->src()); 68 | 69 | if (newName.isEmpty()) { 70 | pageWidget->setCurrentPage(generalPage); 71 | MessageBox::error(this, tr("You must specify a name!")); 72 | } else if (generalOptions->src().isEmpty()) { 73 | pageWidget->setCurrentPage(generalPage); 74 | MessageBox::error(this, tr("You must specify a source!")); 75 | } /*else if (generalOptions->src().protocol()!="file") { 76 | pageWidget->setCurrentPage(generalPage); 77 | MessageBox::error(this, tr("Source must be on the local filesystem.")); 78 | }*/ else if (generalOptions->dest().isEmpty()) { 79 | pageWidget->setCurrentPage(generalPage); 80 | MessageBox::error(this, tr("You must specify a destination!")); 81 | } /*else if (generalOptions->dest().protocol()!="file" && 82 | generalOptions->dest().protocol()!="sftp" && 83 | generalOptions->dest().protocol()!="fish") { 84 | pageWidget->setCurrentPage(generalPage); 85 | MessageBox::error(this, tr("Only file:/, fish:/, and sftp:/ protocols are allowed.")); 86 | } else if ( (generalOptions->dest().protocol()=="sftp" || 87 | generalOptions->dest().protocol()=="fish") && 88 | (!generalOptions->dest().hasUser() || 89 | !generalOptions->dest().hasHost() ) ) { 90 | setCurrentPage(generalPage); 91 | MessageBox::error(this, tr("Remote URLs require user and host.")); 92 | }*/ else if (generalOptions->dest().startsWith(generalOptions->src())) { 93 | pageWidget->setCurrentPage(generalPage); 94 | MessageBox::error(this, tr("Destination must not be located within source.")); 95 | } else if (newName != origName && ((SessionWidget *)parent())->exists(newName)) { 96 | pageWidget->setCurrentPage(generalPage); 97 | MessageBox::error(this, tr("A session named %1 already exists.
" 98 | "Please choose a different name.").arg(newName)); 99 | } else if (newSrc != origSrcPath && ((SessionWidget *)parent())->srcExists(newSrc)) { 100 | pageWidget->setCurrentPage(generalPage); 101 | MessageBox::error(this, tr("A session with source %1 already exists.
" 102 | "Please choose a different path.").arg(newSrc)); 103 | } else if (-1 != rSyncOptionsWidget->getCustom().indexOf("--out-format") || 104 | -1 != rSyncOptionsWidget->getCustom().indexOf("--backup-dir")) { 105 | pageWidget->setCurrentPage(rSyncOptionsPage); 106 | MessageBox::error(this, tr("

Sorry, custom backend options cannot contain --out-format" 107 | "or --backup-dir

")); 108 | } else { 109 | QDialog::accept(); 110 | } 111 | break; 112 | } 113 | case User1: 114 | if (QMessageBox::Yes == MessageBox::warningYesNo(this, tr("Use the current session settings as the defaults for new sessions?"))) { 115 | emit setAsDefaults(); 116 | } 117 | break; 118 | case User2: 119 | if (QLatin1String("KDE") == QLatin1String(qgetenv("XDG_CURRENT_DESKTOP")) && !Utils::findExe("khelpcenter").isEmpty()) { 120 | QProcess::startDetached("khelpcenter", QStringList() << "man:/rsync"); 121 | } else if (!Utils::findExe("yelp").isEmpty()) { 122 | QProcess::startDetached("yelp", QStringList() << "info:rsync"); 123 | } 124 | break; 125 | default: 126 | Dialog::slotButtonClicked(btn); 127 | } 128 | } 129 | 130 | void SessionDialog::get(Session &session) { 131 | excludeWidget->get(session); 132 | rSyncOptionsWidget->get(session); 133 | generalOptions->get(session); 134 | } 135 | -------------------------------------------------------------------------------- /ui/sessiondialog.h: -------------------------------------------------------------------------------- 1 | #ifndef __SESSION_DIALOG_H__ 2 | #define __SESSION_DIALOG_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include "dialog.h" 26 | 27 | class Session; 28 | class GeneralOptionsWidget; 29 | class ExcludeWidget; 30 | class RSyncOptionsWidget; 31 | class PageWidget; 32 | class PageWidgetItem; 33 | 34 | class SessionDialog : public Dialog { 35 | Q_OBJECT 36 | 37 | public: 38 | SessionDialog(QWidget *parent); 39 | 40 | bool run(Session &session, bool edit); 41 | void slotButtonClicked(int btn); 42 | void get(Session &session); 43 | void getRSyncDefaults(Session &session); 44 | void getExcludeDefaults(Session &session); 45 | 46 | Q_SIGNALS: 47 | void setAsDefaults(); 48 | 49 | private: 50 | PageWidget *pageWidget; 51 | QString origName; 52 | QString origSrcPath; 53 | GeneralOptionsWidget *generalOptions; 54 | ExcludeWidget *excludeWidget; 55 | RSyncOptionsWidget *rSyncOptionsWidget; 56 | PageWidgetItem *generalPage; 57 | PageWidgetItem *excludePage; 58 | PageWidgetItem *rSyncOptionsPage; 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /ui/sessionwidget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "sessionwidget.h" 23 | #include "sessiondialog.h" 24 | #include "session.h" 25 | #include "runnerdialog.h" 26 | #include "config.h" 27 | #include "messagebox.h" 28 | #include "utils.h" 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #define CFG_GROUP "SessionWidget/" 39 | #define CFG_COL_SIZES CFG_GROUP "List" 40 | 41 | class CLogViewer : public Dialog { 42 | public: 43 | 44 | CLogViewer(QWidget *parent) 45 | : Dialog(parent) { 46 | edit = new QTextEdit(this); 47 | edit->setReadOnly(true); 48 | setMainWidget(edit); 49 | setButtons(Close); 50 | } 51 | 52 | void show(const Session &session) { 53 | QFile file(session.logFileName()); 54 | 55 | edit->setText(file.open(QIODevice::ReadOnly) 56 | ? file.readAll() 57 | : tr("Could not open log file.")); 58 | file.close(); 59 | setCaption(tr("%1 Log").arg(session.name())); 60 | resize(800, 500); 61 | QDialog::show(); 62 | } 63 | 64 | private: 65 | 66 | QTextEdit *edit; 67 | }; 68 | 69 | enum EColumns { 70 | COL_NAME, 71 | COL_TYPE, 72 | COL_LAST_RUN, 73 | 74 | NUM_COLS 75 | }; 76 | 77 | static QString typeStr(bool backup) { 78 | return backup ? QObject::tr("Backup") : QObject::tr("Synchronisation"); 79 | } 80 | 81 | class SessionWidgetItem : public QTreeWidgetItem { 82 | public: 83 | SessionWidgetItem(QTreeWidget *parent, const QString &s) 84 | : QTreeWidgetItem(parent) 85 | , session(new Session(s)) { 86 | update(); 87 | } 88 | 89 | SessionWidgetItem(QTreeWidget *parent, Session *s) 90 | : QTreeWidgetItem(parent) 91 | , session(s) { 92 | update(); 93 | session->save(); 94 | } 95 | 96 | void update() { 97 | setText(COL_NAME, session->name()); 98 | setText(COL_TYPE, typeStr(session->makeBackupsFlag())); 99 | setText(COL_LAST_RUN, session->last().isEmpty() ? QObject::tr("Never") : session->last()); 100 | setToolTip(); 101 | } 102 | 103 | void setLastRun(const QString &str) { 104 | setText(COL_LAST_RUN, str); 105 | setToolTip(); 106 | } 107 | 108 | operator bool() { 109 | return session; 110 | } 111 | 112 | Session & sessionData() { 113 | return *session; 114 | } 115 | 116 | void remove() { 117 | if (session->erase()) { 118 | delete session; 119 | delete this; 120 | } 121 | } 122 | 123 | private: 124 | void setToolTip() { 125 | QString tip(QObject::tr("

%1

" 126 | "" 127 | "" 128 | "") 129 | .arg(text(COL_NAME)) 130 | .arg(session->source()) 131 | .arg(session->destination())); 132 | 133 | for (int i = 0; i < NUM_COLS; ++i) { 134 | QTreeWidgetItem::setToolTip(i, tip); 135 | } 136 | } 137 | 138 | private: 139 | Session *session; 140 | }; 141 | 142 | static QStringList toNames(QList &items) { 143 | QStringList names; 144 | QList::ConstIterator it(items.constBegin()); 145 | QList::ConstIterator end(items.constEnd()); 146 | 147 | for (; it != end; ++it) 148 | names.append(((SessionWidgetItem *)(*it))->sessionData().name()); 149 | 150 | return names; 151 | } 152 | 153 | SessionWidget::SessionWidget(QWidget *parent) 154 | : QWidget(parent) 155 | , defSession(tr("New Session"), true) 156 | , sessionDialog(0L) 157 | , runnerDialog(0L) 158 | , logViewer(0L) 159 | , menu(0L) { 160 | setupUi(this); 161 | setupWidgets(); 162 | QTimer::singleShot(0, this, SLOT(loadSessions())); 163 | } 164 | 165 | SessionWidget::~SessionWidget() { 166 | QStringList list; 167 | for (int i = 0; i < NUM_COLS; ++i) { 168 | list << QString::number(sessions->header()->sectionSize(i)); 169 | } 170 | 171 | QSettings cfg; 172 | cfg.setValue(CFG_COL_SIZES, list); 173 | } 174 | 175 | bool SessionWidget::exists(const QString &name) const { 176 | return 0 != sessions->findItems(name, Qt::MatchExactly).count(); 177 | } 178 | 179 | bool SessionWidget::srcExists(const QString &path) const { 180 | for (int i = 0; i < sessions->topLevelItemCount(); ++i) { 181 | SessionWidgetItem *item = static_cast(sessions->topLevelItem(i)); 182 | if (item->sessionData().source() == path) { 183 | return true; 184 | } 185 | } 186 | return false; 187 | } 188 | 189 | void SessionWidget::setBackground(const QIcon &icon) { 190 | sessions->setBackground(icon); 191 | } 192 | 193 | void SessionWidget::setupWidgets() { 194 | controlSyncButtons(); 195 | controlButtons(); 196 | connect(sessions, SIGNAL(itemSelectionChanged()), SLOT(controlButtons())); 197 | } 198 | 199 | void SessionWidget::newSession() { 200 | createSessionDialog(); 201 | 202 | if (sessionDialog->run(defSession, false)) { 203 | Session *session = new Session; 204 | 205 | sessionDialog->get(*session); 206 | 207 | SessionWidgetItem *item = new SessionWidgetItem(sessions, session); 208 | 209 | if (!*item) { 210 | delete item; 211 | } 212 | 213 | controlButtons(); 214 | controlSyncButtons(); 215 | } 216 | } 217 | 218 | void SessionWidget::editSession() { 219 | QList sessionList(sessions->selectedItems()); 220 | 221 | if (1 == sessionList.count()) { 222 | createSessionDialog(); 223 | 224 | SessionWidgetItem *session = (SessionWidgetItem *)sessionList.at(0); 225 | 226 | if (sessionDialog->run(session->sessionData(), true)) { 227 | sessionDialog->get(session->sessionData()); 228 | session->sessionData().save(); 229 | session->update(); 230 | } 231 | } 232 | } 233 | 234 | void SessionWidget::removeSession() { 235 | QList sessionList(sessions->selectedItems()); 236 | QStringList names(toNames(sessionList)); 237 | 238 | if ((sessionList.count() == 1 && QMessageBox::Yes == MessageBox::warningYesNo(this, tr("Delete %1?").arg(*(names.begin())))) || 239 | (sessionList.count() > 1 && QMessageBox::Yes == MessageBox::warningYesNoList(this, tr("Delete the following sessions?"), names))) { 240 | QList::Iterator it(sessionList.begin()); 241 | QList::Iterator end(sessionList.end()); 242 | 243 | for (; it != end; ++it) { 244 | SessionWidgetItem *item = (SessionWidgetItem *)(*it); 245 | item->remove(); 246 | } 247 | 248 | controlButtons(); 249 | controlSyncButtons(); 250 | } 251 | } 252 | 253 | void SessionWidget::showSessionLog() { 254 | QList sessionList(sessions->selectedItems()); 255 | 256 | if (1 == sessionList.count()) { 257 | if (!logViewer) { 258 | logViewer = new CLogViewer(this); 259 | } 260 | 261 | logViewer->show(((SessionWidgetItem *)(*(sessionList.begin())))->sessionData()); 262 | } 263 | } 264 | 265 | void SessionWidget::dryRunSession() { 266 | doSessions(true); 267 | } 268 | 269 | void SessionWidget::syncSession() { 270 | doSessions(false); 271 | } 272 | 273 | void SessionWidget::restoreSession(bool on) { 274 | if (!on) { 275 | controlSyncButtons(); 276 | controlButtons(); 277 | } 278 | } 279 | 280 | void SessionWidget::controlButtons() { 281 | QList sessionList(sessions->selectedItems()); 282 | 283 | emit singleItemSelected(1 == sessionList.count()); 284 | emit itemsSelected(sessionList.count() > 0); 285 | emit haveLog(1 == sessionList.count() && 286 | !((SessionWidgetItem *)(*(sessionList.begin())))->sessionData().last().isEmpty()); 287 | } 288 | 289 | void SessionWidget::setAsDefaults() { 290 | sessionDialog->get(defSession); 291 | defSession.save(QString()); 292 | } 293 | 294 | void SessionWidget::controlSyncButtons() { 295 | emit haveSessions(sessions->topLevelItemCount() > 0); 296 | } 297 | 298 | void SessionWidget::loadSessions() { 299 | QFileInfoList sessionList = QDir(Utils::dataDir(QString(), true)).entryInfoList(QStringList() << "*" CARBON_EXTENSION, QDir::NoDotAndDotDot | QDir::Files); 300 | int pos; 301 | int srlen(strlen(CARBON_RUNNER)); 302 | 303 | foreach (const QFileInfo &session, sessionList) { 304 | SessionWidgetItem *item = new SessionWidgetItem(sessions, session.absoluteFilePath()); 305 | 306 | if (!*item) { 307 | delete item; 308 | } 309 | } 310 | 311 | controlSyncButtons(); 312 | 313 | QStringList list; 314 | QSettings cfg; 315 | list = cfg.value(CFG_COL_SIZES, list).toStringList(); 316 | 317 | if (NUM_COLS == list.count()) { 318 | for (int i = 0; i < NUM_COLS; ++i) { 319 | sessions->header()->resizeSection(i, list[i].toInt()); 320 | } 321 | } 322 | 323 | sessions->sortItems(0, Qt::AscendingOrder); 324 | } 325 | 326 | void SessionWidget::contextMenuEvent(QContextMenuEvent *e) { 327 | if (menu) { 328 | menu->popup(e->globalPos()); 329 | } 330 | } 331 | 332 | void SessionWidget::doSessions(bool dryRun) { 333 | QList sessionList(selectedSessions()); 334 | 335 | if (sessionList.count()) { 336 | QStringList names(toNames(sessionList)); 337 | 338 | if ((1 == names.count() && 339 | QMessageBox::Yes == MessageBox::questionYesNo(this, dryRun 340 | ? tr("Perform a dry run of %1?").arg(*(names.begin())) 341 | : tr("Synchronise %1").arg(*(names.begin())))) || 342 | (names.count() > 1 && 343 | QMessageBox::Yes == MessageBox::questionYesNoList(this, dryRun 344 | ? tr("Perform a dry run of the following sessions?") 345 | : tr("Synchronise the following sessions?"), 346 | names))) { 347 | QList sessionDataList; 348 | 349 | foreach (QTreeWidgetItem *i, sessionList) { 350 | sessionDataList.append(&(((SessionWidgetItem *)(i))->sessionData())); 351 | } 352 | 353 | if (sessionDataList.count()) { 354 | if (!runnerDialog) { 355 | runnerDialog = new RunnerDialog(this); 356 | } 357 | 358 | runnerDialog->go(sessionDataList, dryRun); 359 | 360 | for (int i = 0; i < sessions->topLevelItemCount(); ++i) { 361 | SessionWidgetItem *item = (SessionWidgetItem *)(sessions->topLevelItem(i)); 362 | 363 | item->sessionData().updateLast(); 364 | item->setLastRun(item->sessionData().last()); 365 | } 366 | } 367 | } 368 | } 369 | } 370 | 371 | void SessionWidget::createSessionDialog() { 372 | if (!sessionDialog) { 373 | sessionDialog = new SessionDialog(this); 374 | connect(sessionDialog, SIGNAL(setAsDefaults()), SLOT(setAsDefaults())); 375 | } 376 | } 377 | 378 | QList SessionWidget::selectedSessions() { 379 | QList sessionList(sessions->selectedItems()); 380 | 381 | if (0 == sessionList.count()) { 382 | for (int i = 0; i < sessions->topLevelItemCount(); ++i) { 383 | sessionList.append(sessions->topLevelItem(i)); 384 | } 385 | } 386 | return sessionList; 387 | } 388 | -------------------------------------------------------------------------------- /ui/sessionwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef __SESSION_WIDGET_H__ 2 | #define __SESSION_WIDGET_H__ 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include "ui_sessionwidget.h" 26 | #include "session.h" 27 | 28 | class QContextMenuEvent; 29 | class QMenu; 30 | class SessionDialog; 31 | class RunnerDialog; 32 | class CLogViewer; 33 | class QIcon; 34 | 35 | class SessionWidget : public QWidget, Ui::SessionWidget { 36 | Q_OBJECT 37 | 38 | public: 39 | SessionWidget(QWidget *parent); 40 | virtual ~SessionWidget(); 41 | 42 | const Session & defaultSession() const { 43 | return defSession; 44 | } 45 | 46 | bool exists(const QString &name) const; 47 | bool srcExists(const QString &path) const; 48 | void setMenu(QMenu *m) { 49 | menu = m; 50 | } 51 | void setBackground(const QIcon &icon); 52 | 53 | Q_SIGNALS: 54 | void singleItemSelected(bool); 55 | void itemsSelected(bool); 56 | void haveSessions(bool); 57 | void haveLog(bool); 58 | 59 | public Q_SLOTS: 60 | void newSession(); 61 | void editSession(); 62 | void removeSession(); 63 | void showSessionLog(); 64 | void dryRunSession(); 65 | void syncSession(); 66 | void restoreSession(bool on); 67 | void controlButtons(); 68 | void setAsDefaults(); 69 | void loadSessions(); 70 | 71 | private: 72 | void contextMenuEvent(QContextMenuEvent *e); 73 | void controlSyncButtons(); 74 | void setupWidgets(); 75 | void doSessions(bool dryRun); 76 | void createSessionDialog(); 77 | QList selectedSessions(); 78 | 79 | private: 80 | Session defSession; 81 | SessionDialog *sessionDialog; 82 | RunnerDialog *runnerDialog; 83 | CLogViewer *logViewer; 84 | QMenu *menu; 85 | }; 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /ui/sessionwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | SessionWidget 3 | 4 | 5 | 6 | 0 7 | 0 8 | 617 9 | 403 10 | 11 | 12 | 13 | 14 | 0 15 | 16 | 17 | 18 | 19 | QAbstractItemView::ExtendedSelection 20 | 21 | 22 | 23 | Session 24 | 25 | 26 | 27 | 28 | Type 29 | 30 | 31 | 32 | 33 | Last Sync 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | TreeWidget 43 | QTreeWidget 44 |
treewidget.h
45 |
46 |
47 | 48 | 49 |
50 | -------------------------------------------------------------------------------- /ui/treewidget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 3 | 4 | ---- 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; see the file COPYING. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include "treewidget.h" 23 | #include "basicitemdelegate.h" 24 | #include 25 | #include 26 | #include 27 | 28 | static QImage setOpacity(const QImage &orig, double opacity) { 29 | QImage img = QImage::Format_ARGB32 == orig.format() ? orig : orig.convertToFormat(QImage::Format_ARGB32); 30 | uchar *bits = img.bits(); 31 | for (int i = 0; i < img.height()*img.bytesPerLine(); i += 4) { 32 | if (0 != bits[i + 3]) { 33 | bits[i + 3] = opacity * 255; 34 | } 35 | } 36 | return img; 37 | } 38 | 39 | static QPixmap createBgndPixmap(const QIcon &icon) { 40 | if (icon.isNull()) { 41 | return QPixmap(); 42 | } 43 | static int bgndSize = 64; 44 | 45 | QImage img = icon.pixmap(bgndSize, bgndSize).toImage(); 46 | if (img.width() != bgndSize && img.height() != bgndSize) { 47 | img = img.scaled(bgndSize, bgndSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); 48 | } 49 | img = setOpacity(img, 0.075); 50 | QPixmap pix(bgndSize * 2, bgndSize * 2); 51 | pix.fill(Qt::transparent); 52 | QPainter p(&pix); 53 | p.drawImage(bgndSize / 2, bgndSize / 2, img); 54 | p.end(); 55 | return pix; 56 | } 57 | 58 | TreeWidget::TreeWidget(QWidget *p) 59 | : QTreeWidget(p) { 60 | setAlternatingRowColors(false); 61 | setRootIsDecorated(false); 62 | setSortingEnabled(true); 63 | setAllColumnsShowFocus(true); 64 | setIndentation(0); 65 | setItemDelegate(new BasicItemDelegate(this)); 66 | } 67 | 68 | void TreeWidget::setBackground(const QIcon &icon) { 69 | QPalette pal = parentWidget()->palette(); 70 | if (!icon.isNull()) { 71 | pal.setColor(QPalette::Base, Qt::transparent); 72 | } 73 | setPalette(pal); 74 | viewport()->setPalette(pal); 75 | bgnd = createBgndPixmap(icon); 76 | } 77 | 78 | void TreeWidget::paintEvent(QPaintEvent *e) { 79 | if (!bgnd.isNull()) { 80 | QPainter p(viewport()); 81 | p.fillRect(viewport()->rect(), bgnd); 82 | } 83 | QTreeWidget::paintEvent(e); 84 | } 85 | -------------------------------------------------------------------------------- /ui/treewidget.h: -------------------------------------------------------------------------------- 1 | #ifndef TREEWIDGET_H 2 | #define TREEWIDGET_H 3 | 4 | /* 5 | Carbon (C) Craig Drummond, 2013 craig.p.drummond@gmail.com 6 | 7 | ---- 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; see the file COPYING. If not, write to 21 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | Boston, MA 02110-1301, USA. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | class QIcon; 29 | 30 | class TreeWidget : public QTreeWidget { 31 | public: 32 | TreeWidget(QWidget *p = 0); 33 | virtual ~TreeWidget() { } 34 | 35 | void setBackground(const QIcon &icon); 36 | void paintEvent(QPaintEvent *e); 37 | 38 | private: 39 | QPixmap bgnd; 40 | }; 41 | 42 | #endif // TREEWIDGET_H 43 | --------------------------------------------------------------------------------
Source:%2
Destination:%3