Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
1 MB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/.gitignore b/.gitignore
index b3fe137..c28fbc2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,24 +1,25 @@
*.user
win.rc
Info.plist
version.c
_notes
_release
.DS_Store
image/about.psd
extra/_build_*
misc/win32tools
_bin/
build/
src/version.h
*.autosave
src/version.h
*.deb
packaging/win/packages/jp.soramimi.guitar/data/
packaging/win/GuitarSetup.exe
packaging/mac/Guitar.app
packaging/mac/Guitar.app/Contents/Frameworks/QtCore.framework/QtCore
packaging/mac/*.zip
packaging/mac/Guitar-macos.zip
docs/src_docs/
packaging/deb/Guitar
+packaging/win/packages/jp.soramimi.guitar/
diff --git a/.travis.yml b/.travis.yml
index 654ad87..69bcdfc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,37 +1,32 @@
language: cpp
compiler: gcc
sudo: require
-dist: trusty
+dist: focal
before_install:
- - sudo add-apt-repository ppa:beineri/opt-qt593-trusty -y
- - sudo apt-get update -qq
+ - sudo add-apt-repository ppa:beineri/opt-qt-5.14.2-focal -y
+ - sudo apt-get update -q
install:
- - sudo apt-get -y install qt59base qt59svg libgl1-mesa-dev libssl-dev zlib1g-dev ruby # openssl-1.1.x is not supported!
+ - sudo apt-get -y install qt514base qt514svg libgl1-mesa-dev libssl-dev zlib1g-dev ruby patchelf automake
- source /opt/qt*/bin/qt*-env.sh
script:
+ - export PATH=./packaging/continuous-x86_64:$PATH
- ruby prepare.rb
- - mkdir -p _bin/ && cp /usr/lib/x86_64-linux-gnu/libz.a _bin/ # FIXME: Why is this needed?
+ - mkdir -p _bin/ && cp /usr/lib/x86_64-linux-gnu/libz.a _bin/
- qmake CONFIG+=release PREFIX=/usr
- make -j$(nproc)
- # FIXME: make: Nothing to be done for `install'.
- # make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/
- mkdir -p appdir/usr/bin && cp _bin/Guitar appdir/usr/bin/guitar && strip appdir/usr/bin/guitar && chmod a+x appdir/usr/bin/guitar
- mkdir -p appdir/usr/share/icons/hicolor/scalable/apps && cp LinuxDesktop/Guitar.svg appdir/usr/share/icons/hicolor/scalable/apps/guitar.svg
- mkdir -p appdir/usr/share/applications && cp LinuxDesktop/guitar.desktop appdir/usr/share/applications/
- mkdir -p appdir/usr/share/metainfo && cp LinuxDesktop/guitar.appdata.xml appdir/usr/share/metainfo/
- - find appdir/
- - wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
- - chmod a+x linuxdeployqt-continuous-x86_64.AppImage
- - ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/*.desktop -appimage
+ - linuxdeployqt appdir/usr/share/applications/*.desktop -appimage
after_success:
- - wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh
- - bash upload.sh Guitar*.AppImage*
+ - ./packaging/continuous-x86_64/upload.sh Guitar*.AppImage*
branches:
except:
- # Do not build tags that we create when we upload to GitHub Releases
- /^(?i:continuous)/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff6eedc..018f38a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,47 +1,47 @@
cmake_minimum_required(VERSION 3.2)
project(Guitar
VERSION 1.0.0
LANGUAGES CXX C
)
set(CMAKE_CXX_EXTENSIONS OFF)
-set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
# Disable in-source-build
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR})
find_package(Qt5 COMPONENTS Core Widgets Network Svg REQUIRED)
find_package(zlib REQUIRED )
find_package(OpenSSL REQUIRED )
find_package(Qt5LinguistTools REQUIRED)
if(WIN32)
# check package at
# https://github.com/rprichard/winpty
find_package(winpty REQUIRED )
endif()
# extract version information
string(TIMESTAMP Guitar_copyright_year "%Y")
execute_process(
COMMAND git rev-parse --short=7 HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE Guitar_git_hash
OUTPUT_STRIP_TRAILING_WHITESPACE
)
configure_file(version.h.in version.h)
configure_file(win.rc.in win.rc.h)
configure_file(Info.plist.in Info.plist)
add_subdirectory(src)
#add_subdirectory(extra)
diff --git a/ColorEditWidget.h b/ColorEditWidget.h
new file mode 100644
index 0000000..20888df
--- /dev/null
+++ b/ColorEditWidget.h
@@ -0,0 +1,2 @@
+#include "coloredit/ColorEditWidget.h"
+
diff --git a/ColorSlider.h b/ColorSlider.h
new file mode 100644
index 0000000..b2ff560
--- /dev/null
+++ b/ColorSlider.h
@@ -0,0 +1,2 @@
+#include "coloredit/ColorSlider.h"
+
diff --git a/Guitar.pro b/Guitar.pro
index 9375ab7..0a0563f 100644
--- a/Guitar.pro
+++ b/Guitar.pro
@@ -1,426 +1,466 @@
-QT += core gui widgets svg network
+QT += core gui widgets svg network
win32:QT += winextras
CONFIG(debug,debug|release):TARGET = Guitard
CONFIG(release,debug|release):TARGET = Guitar
TEMPLATE = app
-CONFIG += c++11
+CONFIG += c++1z nostrip debug_info
DESTDIR = $$PWD/_bin
TRANSLATIONS = $$PWD/src/resources/translations/Guitar_ja.ts
TRANSLATIONS += $$PWD/src/resources/translations/Guitar_ru.ts
TRANSLATIONS += $$PWD/src/resources/translations/Guitar_zh-CN.ts
TRANSLATIONS += $$PWD/src/resources/translations/Guitar_zh-TW.ts
DEFINES += APP_GUITAR
DEFINES += HAVE_POSIX_OPENPT
macx:DEFINES += HAVE_SYS_TIME_H
macx:DEFINES += HAVE_UTMPX
gcc:QMAKE_CXXFLAGS += -Wall -Wextra -Werror=return-type -Werror=trigraphs -Wno-switch -Wno-reorder
linux:QMAKE_RPATHDIR += $ORIGIN
macx:QMAKE_RPATHDIR += @executable_path/../Frameworks
-linux:QTPLUGIN += ibusplatforminputcontextplugin
+#linux:QTPLUGIN += ibusplatforminputcontextplugin
#linux:QTPLUGIN += fcitxplatforminputcontextplugin
INCLUDEPATH += $$PWD/src
+INCLUDEPATH += $$PWD/src/coloredit
INCLUDEPATH += $$PWD/src/texteditor
win32:INCLUDEPATH += $$PWD/misc/winpty
win32:LIBS += $$PWD/misc/winpty/winpty.lib
# OpenSSL
linux {
static_link_openssl {
LIBS += $$OPENSSL_LIB_DIR/libssl.a $$OPENSSL_LIB_DIR/libcrypto.a -ldl
} else {
LIBS += -lssl -lcrypto
}
}
haiku:LIBS += -lssl -lcrypto -lnetwork
macx:INCLUDEPATH += /usr/local/include
macx:LIBS += /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a
win32:msvc {
- INCLUDEPATH += C:\openssl\include
+ INCLUDEPATH += "C:\Program Files (x86)\OpenSSL\include"
INCLUDEPATH += $$PWD/../zlib
- LIBS += -LC:\openssl\lib
+ LIBS += "-LC:\Program Files (x86)\OpenSSL\lib"
# OpenSSL 1.0
- LIBS += -llibeay32 -lssleay32
+# LIBS += -llibeay32 -lssleay32
# OpenSSL 1.1
-# LIBS += -llibcrypto -llibssl
+ LIBS += -llibcrypto -llibssl
}
win32:gcc {
INCLUDEPATH += C:\Qt\Tools\mingw530_32\opt\include
LIBS += -LC:\Qt\Tools\mingw530_32\opt\lib
LIBS += -lcrypto -lssl
}
# execute 'ruby prepare.rb' automatically
prepare.target = prepare
prepare.commands = cd $$PWD && ruby -W0 prepare.rb
QMAKE_EXTRA_TARGETS += prepare
PRE_TARGETDEPS += prepare
# zlib
win32:msvc {
CONFIG(debug, debug|release):LIBS += $$PWD/_bin/libz.lib
CONFIG(release, debug|release):LIBS += $$PWD/_bin/libz.lib
}
win32:gcc {
CONFIG(debug, debug|release):LIBS += $$PWD/_bin/liblibz.a
CONFIG(release, debug|release):LIBS += $$PWD/_bin/liblibz.a
}
!haiku {
use_system_zlib {
unix:LIBS += -lz
} else {
unix:CONFIG(debug, debug|release):LIBS += $$PWD/_bin/libzd.a
unix:CONFIG(release, debug|release):LIBS += $$PWD/_bin/libz.a
}
}
haiku:LIBS += -lz
win32 {
LIBS += -ladvapi32 -lshell32 -luser32 -lws2_32
RC_FILE = win.rc
QMAKE_SUBSYSTEM_SUFFIX=,5.01
}
macx {
QMAKE_INFO_PLIST = Info.plist
ICON += src/resources/Guitar.icns
t.path=Contents/Resources
QMAKE_BUNDLE_DATA += t
}
SOURCES += \
src/AboutDialog.cpp \
src/AbstractProcess.cpp \
src/AbstractSettingForm.cpp \
src/ApplicationGlobal.cpp \
src/AreYouSureYouWantToContinueConnectingDialog.cpp \
src/AvatarLoader.cpp \
src/BasicMainWindow.cpp \
src/BasicRepositoryDialog.cpp \
src/BigDiffWindow.cpp \
src/BlameWindow.cpp \
+ src/BranchLabel.cpp \
src/CheckoutDialog.cpp \
src/CherryPickDialog.cpp \
src/ClearButton.cpp \
src/CloneDialog.cpp \
src/CloneFromGitHubDialog.cpp \
+ src/ColorButton.cpp \
src/CommitDialog.cpp \
src/CommitExploreWindow.cpp \
src/CommitPropertyDialog.cpp \
src/CommitViewWindow.cpp \
src/ConfigCredentialHelperDialog.cpp \
src/ConfigSigningDialog.cpp \
src/CreateRepositoryDialog.cpp \
src/DeleteBranchDialog.cpp \
src/DeleteTagsDialog.cpp \
src/DialogHeaderFrame.cpp \
src/DirectoryLineEdit.cpp \
src/DoYouWantToInitDialog.cpp \
src/EditGitIgnoreDialog.cpp \
src/EditRemoteDialog.cpp \
src/EditTagsDialog.cpp \
src/ExperimentDialog.cpp \
src/FileDiffSliderWidget.cpp \
src/FileDiffWidget.cpp \
src/FileHistoryWindow.cpp \
src/FilePropertyDialog.cpp \
src/FileUtil.cpp \
src/FileViewWidget.cpp \
src/FilesListWidget.cpp \
src/FindCommitDialog.cpp \
src/Git.cpp \
src/GitDiff.cpp \
src/GitHubAPI.cpp \
src/GitObjectManager.cpp \
src/GitPack.cpp \
src/GitPackIdxV2.cpp \
src/HyperLinkLabel.cpp \
src/ImageViewWidget.cpp \
src/InputNewTagDialog.cpp \
src/JumpDialog.cpp \
src/Languages.cpp \
src/LineEditDialog.cpp \
src/LocalSocketReader.cpp \
src/LogTableWidget.cpp \
src/MainWindow.cpp \
src/MaximizeButton.cpp \
src/MemoryReader.cpp \
src/MenuButton.cpp \
src/MergeDialog.cpp \
src/MyImageViewWidget.cpp \
src/MyProcess.cpp \
src/MySettings.cpp \
src/MyTableWidgetDelegate.cpp \
src/MyTextEditorWidget.cpp \
src/MyToolButton.cpp \
src/ObjectBrowserDialog.cpp \
src/Photoshop.cpp \
src/PushDialog.cpp \
src/ReadOnlyLineEdit.cpp \
src/ReadOnlyPlainTextEdit.cpp \
src/ReflogWindow.cpp \
+ src/RemoteAdvancedOptionWidget.cpp \
src/RemoteRepositoriesTableWidget.cpp \
- src/RemoteWatcher.cpp \
src/RepositoriesTreeWidget.cpp \
src/RepositoryData.cpp \
src/RepositoryInfoFrame.cpp \
src/RepositoryLineEdit.cpp \
src/RepositoryPropertyDialog.cpp \
+ src/RepositoryWrapperFrame.cpp \
src/SearchFromGitHubDialog.cpp \
src/SelectCommandDialog.cpp \
src/SelectGpgKeyDialog.cpp \
src/SelectItemDialog.cpp \
src/SetGlobalUserDialog.cpp \
src/SetGpgSigningDialog.cpp \
src/SetRemoteUrlDialog.cpp \
src/SetUserDialog.cpp \
src/SettingBehaviorForm.cpp \
src/SettingExampleForm.cpp \
src/SettingGeneralForm.cpp \
src/SettingNetworkForm.cpp \
src/SettingProgramsForm.cpp \
+ src/SettingVisualForm.cpp \
src/SettingsDialog.cpp \
src/StatusLabel.cpp \
+ src/SubmoduleAddDialog.cpp \
+ src/SubmoduleMainWindow.cpp \
+ src/SubmoduleUpdateDialog.cpp \
+ src/SubmodulesDialog.cpp \
src/Terminal.cpp \
src/TextEditDialog.cpp \
src/Theme.cpp \
+ src/UserEvent.cpp \
src/WelcomeWizardDialog.cpp \
src/charvec.cpp \
+ src/coloredit/ColorDialog.cpp \
+ src/coloredit/ColorEditWidget.cpp \
+ src/coloredit/ColorPreviewWidget.cpp \
+ src/coloredit/ColorSlider.cpp \
+ src/coloredit/ColorSquareWidget.cpp \
+ src/coloredit/RingSlider.cpp \
src/common/joinpath.cpp \
src/common/misc.cpp \
src/darktheme/DarkStyle.cpp \
src/darktheme/NinePatch.cpp \
src/darktheme/StandardStyle.cpp \
src/darktheme/TraditionalWindowsStyleTreeControl.cpp \
src/gpg.cpp \
src/gunzip.cpp \
src/main.cpp\
src/texteditor/AbstractCharacterBasedApplication.cpp \
src/texteditor/InputMethodPopup.cpp \
src/texteditor/TextEditorTheme.cpp \
src/texteditor/TextEditorWidget.cpp \
src/texteditor/UnicodeWidth.cpp \
src/texteditor/unicode.cpp \
src/urlencode.cpp \
src/webclient.cpp \
HEADERS += \
src/AboutDialog.h \
src/AbstractProcess.h \
src/AbstractSettingForm.h \
src/ApplicationGlobal.h \
src/AreYouSureYouWantToContinueConnectingDialog.h \
src/AvatarLoader.h \
src/BasicMainWindow.h \
src/BasicRepositoryDialog.h \
src/BigDiffWindow.h \
src/BlameWindow.h \
+ src/BranchLabel.h \
src/CheckoutDialog.h \
src/CherryPickDialog.h \
src/ClearButton.h \
src/CloneDialog.h \
src/CloneFromGitHubDialog.h \
+ src/ColorButton.h \
src/CommitDialog.h \
src/CommitExploreWindow.h \
src/CommitPropertyDialog.h \
src/CommitViewWindow.h \
src/ConfigCredentialHelperDialog.h \
src/ConfigSigningDialog.h \
src/CreateRepositoryDialog.h \
src/Debug.h \
src/DeleteBranchDialog.h \
src/DeleteTagsDialog.h \
src/DialogHeaderFrame.h \
src/DirectoryLineEdit.h \
src/DoYouWantToInitDialog.h \
src/EditGitIgnoreDialog.h \
src/EditRemoteDialog.h \
src/EditTagsDialog.h \
src/ExperimentDialog.h \
src/FileDiffSliderWidget.h \
src/FileDiffWidget.h \
src/FileHistoryWindow.h \
src/FilePropertyDialog.h \
src/FileUtil.h \
src/FilesListWidget.h \
src/FindCommitDialog.h \
src/Git.h \
src/GitDiff.h \
src/GitHubAPI.h \
src/GitObjectManager.h \
src/GitPack.h \
src/GitPackIdxV2.h \
src/HyperLinkLabel.h \
src/ImageViewWidget.h \
src/InputNewTagDialog.h \
src/JumpDialog.h \
src/Languages.h \
src/LineEditDialog.h \
src/LocalSocketReader.h \
src/LogTableWidget.h \
src/MainWindow.h \
src/MaximizeButton.h \
src/MemoryReader.h \
src/MenuButton.h \
src/MergeDialog.h \
src/MyImageViewWidget.h \
src/MyProcess.h \
src/MySettings.h \
src/MyTableWidgetDelegate.h \
src/MyTextEditorWidget.h \
src/MyToolButton.h \
src/ObjectBrowserDialog.h \
src/Photoshop.h \
src/PushDialog.h \
src/ReadOnlyLineEdit.h \
src/ReadOnlyPlainTextEdit.h \
src/ReflogWindow.h \
+ src/RemoteAdvancedOptionWidget.h \
src/RemoteRepositoriesTableWidget.h \
- src/RemoteWatcher.h \
src/RepositoriesTreeWidget.h \
src/RepositoryData.h \
src/RepositoryInfoFrame.h \
src/RepositoryLineEdit.h \
src/RepositoryPropertyDialog.h \
+ src/RepositoryWrapperFrame.h \
+ src/SaturationBrightnessWidget.h \
src/SearchFromGitHubDialog.h \
src/SelectCommandDialog.h \
src/SelectGpgKeyDialog.h \
src/SelectItemDialog.h \
src/SetGlobalUserDialog.h \
src/SetGpgSigningDialog.h \
src/SetRemoteUrlDialog.h \
src/SetUserDialog.h \
src/SettingBehaviorForm.h \
src/SettingExampleForm.h \
src/SettingGeneralForm.h \
src/SettingNetworkForm.h \
src/SettingProgramsForm.h \
+ src/SettingVisualForm.h \
src/SettingsDialog.h \
src/StatusLabel.h \
+ src/SubmoduleAddDialog.h \
+ src/SubmoduleMainWindow.h \
+ src/SubmoduleUpdateDialog.h \
+ src/SubmodulesDialog.h \
src/Terminal.h \
src/TextEditDialog.h \
src/Theme.h \
+ src/UserEvent.h \
src/WelcomeWizardDialog.h \
src/charvec.h \
+ src/coloredit/ColorDialog.h \
+ src/coloredit/ColorEditWidget.h \
+ src/coloredit/ColorPreviewWidget.h \
+ src/coloredit/ColorSlider.h \
+ src/coloredit/ColorSquareWidget.h \
+ src/coloredit/RingSlider.h \
src/common/joinpath.h \
src/common/misc.h \
src/darktheme/DarkStyle.h \
src/darktheme/NinePatch.h \
src/darktheme/StandardStyle.h \
src/darktheme/TraditionalWindowsStyleTreeControl.h \
src/gpg.h \
src/gunzip.h \
src/main.h \
src/platform.h \
src/texteditor/AbstractCharacterBasedApplication.h \
src/texteditor/InputMethodPopup.h \
src/texteditor/TextEditorTheme.h \
src/texteditor/TextEditorWidget.h \
src/texteditor/UnicodeWidth.h \
src/texteditor/unicode.h \
src/urlencode.h \
src/webclient.h
HEADERS += src/version.h
FORMS += \
src/AboutDialog.ui \
src/AreYouSureYouWantToContinueConnectingDialog.ui \
src/BigDiffWindow.ui \
src/BlameWindow.ui \
src/CheckoutDialog.ui \
src/CherryPickDialog.ui \
src/CloneDialog.ui \
src/CloneFromGitHubDialog.ui \
src/CommitDialog.ui \
src/CommitExploreWindow.ui \
src/CommitPropertyDialog.ui \
src/CommitViewWindow.ui \
src/ConfigCredentialHelperDialog.ui \
src/ConfigSigningDialog.ui \
src/CreateRepositoryDialog.ui \
src/DeleteBranchDialog.ui \
src/DeleteTagsDialog.ui \
src/DoYouWantToInitDialog.ui \
src/EditGitIgnoreDialog.ui \
src/EditRemoteDialog.ui \
src/EditTagsDialog.ui \
src/ExperimentDialog.ui \
src/FileDiffWidget.ui \
src/FileHistoryWindow.ui \
src/FilePropertyDialog.ui \
src/FindCommitDialog.ui \
src/InputNewTagDialog.ui \
src/JumpDialog.ui \
src/LineEditDialog.ui \
src/MainWindow.ui \
src/MergeDialog.ui \
src/ObjectBrowserDialog.ui \
src/PushDialog.ui \
src/ReflogWindow.ui \
+ src/RemoteAdvancedOptionWidget.ui \
src/RepositoryPropertyDialog.ui \
src/SearchFromGitHubDialog.ui \
src/SelectCommandDialog.ui \
src/SelectGpgKeyDialog.ui \
src/SelectItemDialog.ui \
src/SetGlobalUserDialog.ui \
src/SetGpgSigningDialog.ui \
src/SetRemoteUrlDialog.ui \
src/SetUserDialog.ui \
src/SettingBehaviorForm.ui \
src/SettingExampleForm.ui \
src/SettingGeneralForm.ui \
src/SettingNetworkForm.ui \
src/SettingProgramsForm.ui \
+ src/SettingVisualForm.ui \
src/SettingsDialog.ui \
+ src/SubmoduleAddDialog.ui \
+ src/SubmoduleMainWindow.ui \
+ src/SubmoduleUpdateDialog.ui \
+ src/SubmodulesDialog.ui \
src/TextEditDialog.ui \
- src/WelcomeWizardDialog.ui
+ src/WelcomeWizardDialog.ui \
+ src/coloredit/ColorDialog.ui \
+ src/coloredit/ColorEditWidget.ui
RESOURCES += \
src/resources/resources.qrc
unix {
SOURCES += \
src/unix/UnixProcess.cpp \
src/unix/UnixPtyProcess.cpp
HEADERS += \
src/unix/UnixProcess.h \
src/unix/UnixPtyProcess.h
}
win32 {
SOURCES += \
src/win32/Win32Process.cpp \
src/win32/Win32PtyProcess.cpp \
src/win32/event.cpp \
src/win32/thread.cpp \
src/win32/win32.cpp
HEADERS += \
src/win32/Win32Process.h \
src/win32/Win32PtyProcess.h \
src/win32/event.h \
src/win32/mutex.h \
src/win32/thread.h \
src/win32/win32.h
}
diff --git a/README.md b/README.md
index 088a0aa..b8bc429 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1,55 @@
[![Build Status](https://travis-ci.com/soramimi/Guitar.svg?branch=master)](https://travis-ci.com/soramimi/Guitar)
# Guitar
Git GUI Client
## Summary
* Open Source, Free Software (GPL v2)
* Of course, commercial purposes is also free.
* Run on Windows, macOS and Linux
* Written in C++
* Powered by Qt 5
## Download
* https://files.soramimi.jp/guitar/
-* [Countinuous build (x86_64.AppImage)](https://github.com/soramimi/Guitar/releases/tag/continuous)
+* [Continuous build (x86_64.AppImage)](https://github.com/soramimi/Guitar/releases/tag/continuous)
## Documents
* how to build : https://soramimi.github.io/Guitar/html/build-en.html (in English)
* more information : https://soramimi.github.io/Guitar/ (in Japanese)
### Markdown format:
* how to build: https://github.com/soramimi/Guitar/blob/master/docs/build_guide-en/build_guide_origin.md (in English)
### Doxygen
* http://files.soramimi.jp/guitar/doxy/html/
## Screenshots
### Windows
![Windows](https://soramimi.github.io/Guitar/html/screenshots/windows.png "Windows")
### macOS
![macOS](https://soramimi.github.io/Guitar/html/screenshots/macos.png "macOS")
### Ubuntu
![Ubuntu](https://soramimi.github.io/Guitar/html/screenshots/ubuntu.png "Ubuntu")
### Raspbian
![Raspbian](https://soramimi.github.io/Guitar/html/screenshots/raspberrypi0.jpg "Raspbian")
### Dark Theme
![Dark Theme](https://soramimi.github.io/Guitar/html/screenshots/raspberrypi1.jpg "Dark Theme")
### Haiku
![Haiku](https://soramimi.github.io/Guitar/html/screenshots/haiku.png "Haiku")
### Log and Graph
![Log and Graph](https://soramimi.github.io/Guitar/html/screenshots/commitgraph.png "Log and Graph")
### Diff View
![Diff View](https://soramimi.github.io/Guitar/html/screenshots/diffview.png "Diff View")
diff --git a/RELEASE-WINDOWS.rb b/RELEASE-WINDOWS.rb
index 69c1632..b45bfd0 100644
--- a/RELEASE-WINDOWS.rb
+++ b/RELEASE-WINDOWS.rb
@@ -1,89 +1,36 @@
#!/usr/bin/ruby
-$qt = "C:/Qt/Qt5.7.1/5.7/msvc2013"
+$qt = "C:/Qt/5.15.0/msvc2019"
+$windeployqt = "C:/Qt/5.15.0/msvc2019/bin/windeployqt.exe"
#$qt = "" # スタティックリンクのとき
-$openssl = "C:/openssl"
+$openssl = "C:/Program Files (x86)/OpenSSL"
require 'fileutils'
load 'version.rb'
$workdir = "_release"
$dstdir = $workdir + "/" + $product_name
-$dstdir_iconengines = $dstdir + "/iconengines"
-$dstdir_imageformats = $dstdir + "/imageformats"
-$dstdir_platforms = $dstdir + "/platforms"
-
FileUtils.rm_rf($workdir)
FileUtils.mkpath($dstdir)
FileUtils.cp("_bin/#{$product_name}.exe", $dstdir)
#FileUtils.cp("src/resources/translations/Guitar_ja.qm", $dstdir)
-FileUtils.cp($openssl + "/bin/libeay32.dll", $dstdir)
-FileUtils.cp($openssl + "/bin/ssleay32.dll", $dstdir)
+FileUtils.cp($openssl + "/bin/libssl-1_1.dll", $dstdir)
+FileUtils.cp($openssl + "/bin/libcrypto-1_1.dll", $dstdir)
`7z x -o#{$dstdir} misc/win32tools.zip`
`move #{$dstdir}\\win32tools\\* #{$dstdir}`
FileUtils.rmdir("#{$dstdir}\\win32tools")
-if $qt != ''
-
- FileUtils.mkpath($dstdir_iconengines)
- FileUtils.mkpath($dstdir_imageformats)
- FileUtils.mkpath($dstdir_platforms)
-
- def cp_qt_lib(name)
- src = $qt + "/bin/" + name
- FileUtils.cp(src, $dstdir)
- end
-
- cp_qt_lib("Qt5Core.dll")
- cp_qt_lib("Qt5Gui.dll")
- cp_qt_lib("Qt5Svg.dll")
- cp_qt_lib("Qt5Widgets.dll")
- cp_qt_lib("Qt5Xml.dll")
- cp_qt_lib("Qt5Network.dll")
- cp_qt_lib("Qt5WinExtras.dll")
-
- def cp_qt_imageformat(name)
- src = $qt + "/plugins/imageformats/" + name
- FileUtils.cp(src, $dstdir_imageformats)
- end
-
- cp_qt_imageformat("qgif.dll")
- cp_qt_imageformat("qicns.dll")
- cp_qt_imageformat("qico.dll")
- cp_qt_imageformat("qjpeg.dll")
- cp_qt_imageformat("qsvg.dll")
-
- def cp_qt_iconengine(name)
- src = $qt + "/plugins/iconengines/" + name
- FileUtils.cp(src, $dstdir_iconengines)
- end
-
- cp_qt_iconengine("qsvgicon.dll")
-
- def cp_qt_platform(name)
- src = $qt + "/plugins/platforms/" + name
- FileUtils.cp(src, $dstdir_platforms)
- end
-
- cp_qt_platform("qwindows.dll")
- cp_qt_platform("qminimal.dll")
- cp_qt_platform("qoffscreen.dll")
-
-end
+`#{$windeployqt} #{$dstdir}/#{$product_name}.exe`
pkgfilename = "#{$product_name}-#{$version_a}.#{$version_b}.#{$version_c}-win32.zip"
Dir.chdir($workdir) {
`7z a #{pkgfilename} #{$product_name}`
}
-Dir.chdir("packaging/win") {
- `mk.bat #{pkgfilename}`
-}
-
diff --git a/packaging/continuous-x86_64/appimagetool b/packaging/continuous-x86_64/appimagetool
new file mode 100755
index 0000000..872c650
Binary files /dev/null and b/packaging/continuous-x86_64/appimagetool differ
diff --git a/packaging/continuous-x86_64/linuxdeployqt b/packaging/continuous-x86_64/linuxdeployqt
new file mode 100755
index 0000000..b1bcb55
Binary files /dev/null and b/packaging/continuous-x86_64/linuxdeployqt differ
diff --git a/packaging/continuous-x86_64/upload.sh b/packaging/continuous-x86_64/upload.sh
new file mode 100755
index 0000000..bd8f412
--- /dev/null
+++ b/packaging/continuous-x86_64/upload.sh
@@ -0,0 +1,269 @@
+#!/bin/bash
+
+set +x # Do not leak information
+
+# Exit immediately if one of the files given as arguments is not there
+# because we don't want to delete the existing release if we don't have
+# the new files that should be uploaded
+for file in "$@"
+do
+ if [ ! -e "$file" ]
+ then echo "$file is missing, giving up." >&2; exit 1
+ fi
+done
+
+if [ $# -eq 0 ]; then
+ echo "No artifacts to use for release, giving up."
+ exit 0
+fi
+
+if command -v sha256sum >/dev/null 2>&1 ; then
+ shatool="sha256sum"
+elif command -v shasum >/dev/null 2>&1 ; then
+ shatool="shasum -a 256" # macOS fallback
+else
+ echo "Neither sha256sum nor shasum is available, cannot check hashes"
+fi
+
+# The calling script (usually .travis.yml) can set a suffix to be used for
+# the tag and release name. This way it is possible to have a release for
+# the output of the CI/CD pipeline (marked as 'continuous') and also test
+# builds for other branches.
+# If this build was triggered by a tag, call the result a Release
+if [ ! -z "$UPLOADTOOL_SUFFIX" ] ; then
+ if [ "$UPLOADTOOL_SUFFIX" = "$TRAVIS_TAG" ] ; then
+ RELEASE_NAME="$TRAVIS_TAG"
+ RELEASE_TITLE="Release build ($TRAVIS_TAG)"
+ is_prerelease="false"
+ else
+ RELEASE_NAME="continuous-$UPLOADTOOL_SUFFIX"
+ RELEASE_TITLE="Continuous build ($UPLOADTOOL_SUFFIX)"
+ is_prerelease="true"
+ fi
+else
+ # ,, is a bash-ism to convert variable to lower case
+ case $(tr '[:upper:]' '[:lower:]' <<< "$TRAVIS_TAG") in
+ "")
+ # Do not use "latest" as it is reserved by GitHub
+ RELEASE_NAME="continuous"
+ RELEASE_TITLE="Continuous build"
+ is_prerelease="true"
+ ;;
+ *-alpha*|*-beta*|*-rc*)
+ RELEASE_NAME="$TRAVIS_TAG"
+ RELEASE_TITLE="Pre-release build ($TRAVIS_TAG)"
+ is_prerelease="true"
+ ;;
+ *)
+ RELEASE_NAME="$TRAVIS_TAG"
+ RELEASE_TITLE="Release build ($TRAVIS_TAG)"
+ is_prerelease="false"
+ ;;
+ esac
+fi
+
+if [ "$ARTIFACTORY_BASE_URL" != "" ]; then
+ echo "ARTIFACTORY_BASE_URL set, trying to upload to artifactory"
+
+ if [ "$ARTIFACTORY_API_KEY" == "" ]; then
+ echo "Please set ARTIFACTORY_API_KEY"
+ exit 1
+ fi
+
+ files="$@"
+
+ # artifactory doesn't support any kind of "artifact description", so we're uploading a text file containing the
+ # relevant details along with the other artifacts
+ tempdir=$(mktemp -d)
+ info_file="$tempdir"/build-info.txt
+ echo "Travis CI build log: ${TRAVIS_BUILD_WEB_URL}" > "$info_file"
+ files+=("$info_file")
+
+ set +x
+
+ for file in ${files[@]}; do
+ url="${ARTIFACTORY_BASE_URL}/travis-${TRAVIS_BUILD_NUMBER}/"$(basename "$file")
+ md5sum=$(md5sum "$file" | cut -d' ' -f1)
+ sha1sum=$(sha1sum "$file" | cut -d' ' -f1)
+ sha256sum=$(sha256sum "$file" | cut -d' ' -f1)
+ echo "Uploading $file to $url"
+ hashsums=(-H "X-Checksum-Md5:$md5sum")
+ hashsums+=(-H "X-Checksum-Sha1:$sha1sum")
+ hashsums+=(-H "X-Checksum-Sha256:$sha256sum")
+ if ! curl -H 'X-JFrog-Art-Api:'"$ARTIFACTORY_API_KEY" "${hashsums[@]}" -T "$file" "$url"; then
+ echo "Failed to upload file, exiting"
+ rm -r "$tempdir"
+ exit 1
+ fi
+ echo
+ echo "MD5 checksum: $md5sum"
+ echo "SHA1 checksum: $sha1sum"
+ echo "SHA256 checksum: $sha256sum"
+ done
+ rm -r "$tempdir"
+fi
+
+# Do not upload non-master branch builds
+# if [ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ] && [ "$TRAVIS_BRANCH" != "master" ]; then export TRAVIS_EVENT_TYPE=pull_request; fi
+if [ "$TRAVIS_EVENT_TYPE" == "pull_request" ] ; then
+ echo "Release uploading disabled for pull requests"
+ if [ "$ARTIFACTORY_BASE_URL" != "" ]; then
+ echo "Releases have already been uploaded to Artifactory, exiting"
+ exit 0
+ else
+ echo "Release uploading disabled for pull requests, uploading to transfersh.com instead"
+ rm -f ./uploaded-to
+ for FILE in "$@" ; do
+ BASENAME="$(basename "${FILE}")"
+ curl --upload-file $FILE "https://transfersh.com/$BASENAME" > ./one-upload
+ echo "$(cat ./one-upload)" # this way we get a newline
+ echo -n "$(cat ./one-upload)\\n" >> ./uploaded-to # this way we get a \n but no newline
+ done
+ fi
+# review_url="https://api.github.com/repos/${TRAVIS_REPO_SLUG}/pulls/${TRAVIS_PULL_REQUEST}/reviews"
+# if [ -z $UPLOADTOOL_PR_BODY ] ; then
+# body="Travis CI has created build artifacts for this PR here:"
+# else
+# body="$UPLOADTOOL_PR_BODY"
+# fi
+# body="$body\n$(cat ./uploaded-to)\nThe link(s) will expire 14 days from now."
+# review_comment=$(curl -X POST \
+# --header "Authorization: token ${GITHUB_TOKEN}" \
+# --data '{"commit_id": "'"$TRAVIS_COMMIT"'","body": "'"$body"'","event": "COMMENT"}' \
+# $review_url)
+# if echo $review_comment | grep -q "Bad credentials" 2>/dev/null ; then
+# echo '"Bad credentials" response for --data {"commit_id": "'"$TRAVIS_COMMIT"'","body": "'"$body"'","event": "COMMENT"}'
+# fi
+ $shatool "$@"
+ exit 0
+fi
+
+if [ ! -z "$TRAVIS_REPO_SLUG" ] ; then
+ # We are running on Travis CI
+ echo "Running on Travis CI"
+ echo "TRAVIS_COMMIT: $TRAVIS_COMMIT"
+ REPO_SLUG="$TRAVIS_REPO_SLUG"
+ if [ -z "$GITHUB_TOKEN" ] ; then
+ echo "\$GITHUB_TOKEN missing, please set it in the Travis CI settings of this project"
+ echo "You can get one from https://github.com/settings/tokens"
+ exit 1
+ fi
+else
+ # We are not running on Travis CI
+ echo "Not running on Travis CI"
+ if [ -z "$REPO_SLUG" ] ; then
+ read -r -p "Repo Slug (GitHub and Travis CI username/reponame): " REPO_SLUG
+ fi
+ if [ -z "$GITHUB_TOKEN" ] ; then
+ read -r -s -p "Token (https://github.com/settings/tokens): " GITHUB_TOKEN
+ fi
+fi
+
+tag_url="https://api.github.com/repos/$REPO_SLUG/git/refs/tags/$RELEASE_NAME"
+tag_infos=$(curl -XGET --header "Authorization: token ${GITHUB_TOKEN}" "${tag_url}")
+echo "tag_infos: $tag_infos"
+tag_sha=$(echo "$tag_infos" | grep '"sha":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1)
+echo "tag_sha: $tag_sha"
+
+release_url="https://api.github.com/repos/$REPO_SLUG/releases/tags/$RELEASE_NAME"
+echo "Getting the release ID..."
+echo "release_url: $release_url"
+release_infos=$(curl -XGET --header "Authorization: token ${GITHUB_TOKEN}" "${release_url}")
+echo "release_infos: $release_infos"
+release_id=$(echo "$release_infos" | grep "\"id\":" | head -n 1 | tr -s " " | cut -f 3 -d" " | cut -f 1 -d ",")
+echo "release ID: $release_id"
+upload_url=$(echo "$release_infos" | grep '"upload_url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1)
+echo "upload_url: $upload_url"
+release_url=$(echo "$release_infos" | grep '"url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1)
+echo "release_url: $release_url"
+target_commit_sha=$(echo "$release_infos" | grep '"target_commitish":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1)
+echo "target_commit_sha: $target_commit_sha"
+
+if [ "$TRAVIS_COMMIT" != "$target_commit_sha" ] ; then
+
+ echo "TRAVIS_COMMIT != target_commit_sha, hence deleting $RELEASE_NAME..."
+
+ if [ ! -z "$release_id" ]; then
+ delete_url="https://api.github.com/repos/$REPO_SLUG/releases/$release_id"
+ echo "Delete the release..."
+ echo "delete_url: $delete_url"
+ curl -XDELETE \
+ --header "Authorization: token ${GITHUB_TOKEN}" \
+ "${delete_url}"
+ fi
+
+ # echo "Checking if release with the same name is still there..."
+ # echo "release_url: $release_url"
+ # curl -XGET --header "Authorization: token ${GITHUB_TOKEN}" \
+ # "$release_url"
+
+ if [ "$RELEASE_NAME" == "continuous" ] ; then
+ # if this is a continuous build tag, then delete the old tag
+ # in preparation for the new release
+ echo "Delete the tag..."
+ delete_url="https://api.github.com/repos/$REPO_SLUG/git/refs/tags/$RELEASE_NAME"
+ echo "delete_url: $delete_url"
+ curl -XDELETE \
+ --header "Authorization: token ${GITHUB_TOKEN}" \
+ "${delete_url}"
+ fi
+
+ echo "Create release..."
+
+ if [ -z "$TRAVIS_BRANCH" ] ; then
+ TRAVIS_BRANCH="master"
+ fi
+
+ if [ ! -z "$TRAVIS_JOB_ID" ] ; then
+ if [ -z "${UPLOADTOOL_BODY+x}" ] ; then
+ BODY="Travis CI build log: ${TRAVIS_BUILD_WEB_URL}"
+ else
+ BODY="$UPLOADTOOL_BODY"
+ fi
+ else
+ BODY="$UPLOADTOOL_BODY"
+ fi
+
+ release_infos=$(curl -H "Authorization: token ${GITHUB_TOKEN}" \
+ --data '{"tag_name": "'"$RELEASE_NAME"'","target_commitish": "'"$TRAVIS_COMMIT"'","name": "'"$RELEASE_TITLE"'","body": "'"$BODY"'","draft": false,"prerelease": '$is_prerelease'}' "https://api.github.com/repos/$REPO_SLUG/releases")
+
+ echo "$release_infos"
+
+ unset upload_url
+ upload_url=$(echo "$release_infos" | grep '"upload_url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1)
+ echo "upload_url: $upload_url"
+
+ unset release_url
+ release_url=$(echo "$release_infos" | grep '"url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1)
+ echo "release_url: $release_url"
+
+fi # if [ "$TRAVIS_COMMIT" != "$tag_sha" ]
+
+if [ -z "$release_url" ] ; then
+ echo "Cannot figure out the release URL for $RELEASE_NAME"
+ exit 1
+fi
+
+echo "Upload binaries to the release..."
+
+for FILE in "$@" ; do
+ FULLNAME="${FILE}"
+ BASENAME="$(basename "${FILE}")"
+ curl -H "Authorization: token ${GITHUB_TOKEN}" \
+ -H "Accept: application/vnd.github.manifold-preview" \
+ -H "Content-Type: application/octet-stream" \
+ --data-binary @$FULLNAME \
+ "$upload_url?name=$BASENAME"
+ echo ""
+done
+
+$shatool "$@"
+
+if [ "$TRAVIS_COMMIT" != "$tag_sha" ] ; then
+ echo "Publish the release..."
+
+ release_infos=$(curl -H "Authorization: token ${GITHUB_TOKEN}" \
+ --data '{"draft": false}' "$release_url")
+
+ echo "$release_infos"
+fi # if [ "$TRAVIS_COMMIT" != "$tag_sha" ]
diff --git a/packaging/deb/jenkins-raspi/config.xml b/packaging/deb/jenkins-raspi/config.xml
new file mode 100644
index 0000000..4a97baa
--- /dev/null
+++ b/packaging/deb/jenkins-raspi/config.xml
@@ -0,0 +1,65 @@
+<?xml version='1.1' encoding='UTF-8'?>
+<project>
+ <actions/>
+ <description></description>
+ <keepDependencies>false</keepDependencies>
+ <properties>
+ <com.coravy.hudson.plugins.github.GithubProjectProperty plugin="github@1.31.0">
+ <projectUrl>https://github.com/soramimi/Guitar/</projectUrl>
+ <displayName></displayName>
+ </com.coravy.hudson.plugins.github.GithubProjectProperty>
+ </properties>
+ <scm class="hudson.plugins.git.GitSCM" plugin="git@4.4.1">
+ <configVersion>2</configVersion>
+ <userRemoteConfigs>
+ <hudson.plugins.git.UserRemoteConfig>
+ <url>https://github.com/soramimi/Guitar/</url>
+ </hudson.plugins.git.UserRemoteConfig>
+ </userRemoteConfigs>
+ <branches>
+ <hudson.plugins.git.BranchSpec>
+ <name>*/master</name>
+ </hudson.plugins.git.BranchSpec>
+ </branches>
+ <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
+ <submoduleCfg class="list"/>
+ <extensions/>
+ </scm>
+ <canRoam>true</canRoam>
+ <disabled>false</disabled>
+ <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
+ <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
+ <triggers>
+ <hudson.triggers.TimerTrigger>
+ <spec>H(0-3) 5 * * *</spec>
+ </hudson.triggers.TimerTrigger>
+ </triggers>
+ <concurrentBuild>false</concurrentBuild>
+ <builders>
+ <hudson.tasks.Shell>
+ <command>ruby prepare.rb
+mkdir _bin
+cp /usr/lib/arm-linux-gnueabihf/libz.a _bin
+cd packaging/deb
+mkdir build
+cd build
+qmake ../../../Guitar.pro
+make -j2
+cd ..
+cp ../../_bin/Guitar .
+ruby mk-deb.rb
+file=`./debname.rb`
+curl -T $file ftp://10.10.10.5/Public/pub/nightlybuild/
+</command>
+ </hudson.tasks.Shell>
+ </builders>
+ <publishers/>
+ <buildWrappers>
+ <hudson.plugins.ws__cleanup.PreBuildCleanup plugin="ws-cleanup@0.38">
+ <deleteDirs>false</deleteDirs>
+ <cleanupParameter></cleanupParameter>
+ <externalDelete></externalDelete>
+ <disableDeferredWipeout>false</disableDeferredWipeout>
+ </hudson.plugins.ws__cleanup.PreBuildCleanup>
+ </buildWrappers>
+</project>
\ No newline at end of file
diff --git a/packaging/deb/mk-deb.rb b/packaging/deb/mk-deb.rb
index 074832e..60a4b2a 100755
--- a/packaging/deb/mk-deb.rb
+++ b/packaging/deb/mk-deb.rb
@@ -1,49 +1,49 @@
#!/usr/bin/ruby
require 'fileutils'
load '../../version.rb'
$package = "guitar"
$maintainer = "nobody <nobody@example.com>"
$version = "#{$version_a}.#{$version_b}.#{$version_c}"
$workdir = "build"
$bindir = "build"
$dstdir = $workdir + "/#{$package}"
$arch = `./arch.rb`.strip
FileUtils.mkpath($dstdir + "/DEBIAN")
FileUtils.mkpath($dstdir + "/usr/bin")
FileUtils.mkpath($dstdir + "/usr/share/applications")
FileUtils.mkpath($dstdir + "/usr/share/icons/guitar")
system "strip Guitar"
FileUtils.cp("Guitar", $dstdir + "/usr/bin/")
FileUtils.cp("../../LinuxDesktop/Guitar.svg", $dstdir + "/usr/share/icons/guitar/")
File.open($dstdir + "/usr/share/applications/Guitar.desktop", "w") {|f|
f.puts <<___
[Desktop Entry]
Type=Application
Name=Guitar
Categories=Development
Exec=/usr/bin/Guitar
Icon=/usr/share/icons/guitar/Guitar.svg
Terminal=false
___
}
File.open($dstdir + "/DEBIAN/control", "w") {|f|
f.puts <<___
Package: #{$package}
Section: vcs
Maintainer: #{$maintainer}
Architecture: #{$arch}
Version: #{$version}
-Depends: libqt5widgets5 (>= 5.5.0), libqt5xml5 (>= 5.5.0), libqt5svg5 (>= 5.5.0), zlib1g, git, file
+Depends: libqt5widgets5 (>= 5.12.0), libqt5xml5 (>= 5.12.0), libqt5svg5 (>= 5.12.0), zlib1g, git, file
Description: Git GUI Client
___
}
FileUtils.cp("postinst", $dstdir + "/DEBIAN/")
system "fakeroot dpkg-deb --build #{$workdir}/#{$package} ."
diff --git a/packaging/win/deploy.rb b/packaging/win/deploy.rb
new file mode 100644
index 0000000..cfb28c9
--- /dev/null
+++ b/packaging/win/deploy.rb
@@ -0,0 +1,56 @@
+
+require 'fileutils'
+$script_dir = __dir__
+
+FileUtils.rm_rf $script_dir + "/build"
+FileUtils.rm_rf $script_dir + "/packages/jp.soramimi.guitar/data"
+
+def mkcd(dir)
+ FileUtils.mkpath dir
+ FileUtils.chdir dir
+end
+
+def run(cmd)
+ r = system cmd
+ if !r then
+ abort "FATAL: " + cmd
+ end
+end
+
+Dir.chdir $script_dir
+FileUtils.rm_rf "zlib"
+run "git clone https://github.com/madler/zlib"
+FileUtils.cp "../../zlib.pro", "zlib/"
+Dir.chdir("zlib") {
+ run "C:/Qt/5.15.0/msvc2019/bin/qmake.exe zlib.pro"
+ run "C:/Qt/Tools/QtCreator/bin/jom.exe"
+}
+ENV["INCLUDE"] = $script_dir + "/zlib;" + ENV["INCLUDE"]
+
+Dir.chdir $script_dir + "/../../"
+FileUtils.mkpath "_bin"
+run "ruby prepare.rb"
+
+FileUtils.cp $script_dir + "/zlib/_bin/libz.lib", "_bin/"
+
+mkcd $script_dir + "/build"
+run "C:/Qt/5.15.0/msvc2019/bin/qmake.exe ../../../Guitar.pro"
+run "C:/Qt/Tools/QtCreator/bin/jom.exe"
+
+Dir.chdir $script_dir + "/../../"
+run "ruby RELEASE-WINDOWS.rb"
+
+Dir.chdir $script_dir
+load '../../version.rb'
+
+pkgname = "Guitar-#{$version_a}.#{$version_b}.#{$version_c}-win32.zip"
+
+run "7z x ../../_release/#{pkgname} -opackages/jp.soramimi.guitar"
+
+mkcd $script_dir + "/packages/jp.soramimi.guitar"
+FileUtils.mv "Guitar", "data"
+
+Dir.chdir $script_dir
+run "C:/Qt/QtIFW-3.2.2/bin/binarycreator.exe -c config/config.xml -p packages GuitarSetup.exe"
+
+run "curl -T GuitarSetup.exe ftp://10.10.10.5:/Public/pub/nightlybuild/"
diff --git a/packaging/win/mk.bat b/packaging/win/mk.bat
deleted file mode 100644
index 7d0a738..0000000
--- a/packaging/win/mk.bat
+++ /dev/null
@@ -1,5 +0,0 @@
-rd /s /q packages\jp.soramimi.guitar\data
-rem 7z x ../../_release/Guitar-1.0.0-win32.zip -opackages\jp.soramimi.guitar
-7z x ../../_release/%1 -opackages\jp.soramimi.guitar
-ren packages\jp.soramimi.guitar\Guitar data
-C:\Qt\Tools\QtInstallerFramework\3.1\bin\binarycreator.exe -c config/config.xml -p packages GuitarSetup.exe
diff --git a/src/AbstractProcess.h b/src/AbstractProcess.h
index 3b8ebc2..1964a02 100644
--- a/src/AbstractProcess.h
+++ b/src/AbstractProcess.h
@@ -1,32 +1,32 @@
#ifndef ABSTRACTPROCESS_H
#define ABSTRACTPROCESS_H
#include <QObject>
#include <QVariant>
#include <functional>
class QString;
class AbstractPtyProcess : public QObject {
Q_OBJECT
protected:
QString change_dir;
QVariant user_data;
public:
void setChangeDir(QString const &dir);
void setVariant(QVariant const &value);
QVariant const &userVariant() const;
virtual bool isRunning() const = 0;
virtual void writeInput(char const *ptr, int len) = 0;
virtual int readOutput(char *ptr, int len) = 0;
- virtual void start(QString const &cmd, QVariant const &userdata = QVariant()) = 0;
+ virtual void start(QString const &cmd, QString const &env, QVariant const &userdata = QVariant()) = 0;
virtual bool wait(unsigned long time = ULONG_MAX) = 0;
virtual void stop() = 0;
virtual int getExitCode() const = 0;
virtual QString getMessage() const = 0;
virtual void readResult(std::vector<char> *out) = 0;
signals:
void completed(bool, QVariant);
};
#endif // ABSTRACTPROCESS_H
diff --git a/src/ApplicationGlobal.cpp b/src/ApplicationGlobal.cpp
index 354cf09..1d41a5b 100644
--- a/src/ApplicationGlobal.cpp
+++ b/src/ApplicationGlobal.cpp
@@ -1,3 +1,11 @@
#include "ApplicationGlobal.h"
+
+ApplicationGlobal::ApplicationGlobal()
+{
+// branch_label_color.head = QColor(255, 192, 224); // pink
+// branch_label_color.local = QColor(192, 224, 255); // blue
+// branch_label_color.remote = QColor(192, 240, 224); // green
+// branch_label_color.tag = QColor(255, 224, 192); // orange
+}
diff --git a/src/ApplicationGlobal.h b/src/ApplicationGlobal.h
index 238ae6e..59a7324 100644
--- a/src/ApplicationGlobal.h
+++ b/src/ApplicationGlobal.h
@@ -1,28 +1,40 @@
#ifndef APPLICATIONGLOBAL_H
#define APPLICATIONGLOBAL_H
#include <QColor>
#include <QString>
#include "Theme.h"
+#include "main.h"
class MainWindow;
struct ApplicationGlobal {
MainWindow *mainwindow = nullptr;
bool start_with_shift_key = false;
QString organization_name;
QString application_name;
QString language_id;
QString theme_id;
QString generic_config_dir;
QString app_config_dir;
QString config_file_path;
QString file_command;
QString gpg_command;
QColor panel_bg_color;
ThemePtr theme;
+
+ ApplicationSettings appsettings;
+
+// struct {
+// QColor head;
+// QColor local;
+// QColor remote;
+// QColor tag;
+// } branch_label_color;
+
+ ApplicationGlobal();
};
extern ApplicationGlobal *global;
#endif // APPLICATIONGLOBAL_H
diff --git a/src/AvatarLoader.cpp b/src/AvatarLoader.cpp
index 5e6a6ac..394c364 100644
--- a/src/AvatarLoader.cpp
+++ b/src/AvatarLoader.cpp
@@ -1,161 +1,162 @@
#include "AvatarLoader.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "MemoryReader.h"
#include "webclient.h"
#include <QCryptographicHash>
#include <QDebug>
#include <QWaitCondition>
namespace {
const int MAX_CACHE_COUNT = 1000;
const int ICON_SIZE = 128;
}
using WebClientPtr = std::shared_ptr<WebClient>;
struct AvatarLoader::Private {
QMutex mutex;
QWaitCondition condition;
std::deque<RequestItem> requested;
std::deque<RequestItem> completed;
- BasicMainWindow *mainwindow = nullptr;
+ MainWindow *mainwindow = nullptr;
WebClientPtr web;
};
AvatarLoader::AvatarLoader()
: m(new Private)
{
}
AvatarLoader::~AvatarLoader()
{
delete m;
}
-void AvatarLoader::start(BasicMainWindow *mainwindow)
+void AvatarLoader::start(MainWindow *mainwindow)
{
m->mainwindow = mainwindow;
QThread::start();
}
void AvatarLoader::run()
{
m->web = std::make_shared<WebClient>(m->mainwindow->webContext());
while (1) {
if (isInterruptionRequested()) return;
std::deque<RequestItem> requests;
{
QMutexLocker lock(&m->mutex);
if (m->requested.empty()) {
m->condition.wait(&m->mutex);
}
if (!m->requested.empty()) {
std::swap(requests, m->requested);
}
}
for (RequestItem &item : requests) {
if (isInterruptionRequested()) return;
if (strchr(item.email.c_str(), '@')) {
QString id;
{
QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(item.email.c_str(), item.email.size());
QByteArray ba = hash.result();
char tmp[100];
for (int i = 0; i < ba.size(); i++) {
sprintf(tmp + i * 2, "%02x", ba.data()[i] & 0xff);
}
id = tmp;
}
QString url = "https://www.gravatar.com/avatar/%1?s=%2";
url = url.arg(id).arg(ICON_SIZE);
if (m->web->get(WebClient::Request(url.toStdString())) == 200) {
if (!m->web->response().content.empty()) {
MemoryReader reader(m->web->response().content.data(), m->web->response().content.size());
reader.open(MemoryReader::ReadOnly);
QImage image;
image.load(&reader, nullptr);
int w = image.width();
int h = image.height();
if (w > 0 && h > 0) {
item.image = image;
{
QMutexLocker lock(&m->mutex);
size_t i = m->requested.size();
while (i > 0) {
i--;
if (m->requested[i].email == item.email) {
m->requested.erase(m->requested.begin() + i);
}
}
while (m->completed.size() >= MAX_CACHE_COUNT) {
m->completed.pop_back();
}
m->completed.push_front(item);
}
- emit updated();
+ emit updated(item.frame);
continue;
}
}
} else {
m->mainwindow->emitWriteLog(QString("Failed to fetch the avatar.\n").toUtf8());
QString msg = QString::fromStdString(m->web->error().message() + '\n');
m->mainwindow->emitWriteLog(msg.toUtf8());
}
}
}
}
}
-QIcon AvatarLoader::fetch(std::string const &email, bool request) const
+QIcon AvatarLoader::fetch(RepositoryWrapperFrame *frame, std::string const &email, bool request) const
{
QMutexLocker lock(&m->mutex);
RequestItem item;
+ item.frame = frame;
item.email = email;
for (size_t i = 0; i < m->completed.size(); i++) {
if (item.email == m->completed[i].email) {
item = m->completed[i];
m->completed.erase(m->completed.begin() + i);
m->completed.insert(m->completed.begin(), item);
return QIcon(QPixmap::fromImage(item.image));
}
}
if (request) {
bool waiting = false;
for (RequestItem const &r : m->requested) {
if (item.email == r.email) {
waiting = true;
break;
}
}
if (!waiting) {
m->requested.push_back(item);
m->condition.wakeOne();
}
}
return QIcon();
}
void AvatarLoader::stop()
{
{
QMutexLocker lock(&m->mutex);
requestInterruption();
m->requested.clear();
m->condition.wakeAll();
}
if (!wait(3000)) {
terminate();
}
if (m->web) {
m->web->close();
m->web.reset();
}
m->completed.clear();
}
diff --git a/src/AvatarLoader.h b/src/AvatarLoader.h
index b43c433..fe8e913 100644
--- a/src/AvatarLoader.h
+++ b/src/AvatarLoader.h
@@ -1,37 +1,39 @@
#ifndef AVATARLOADER_H
#define AVATARLOADER_H
#include "GitHubAPI.h"
+#include "RepositoryWrapperFrame.h"
#include <QIcon>
#include <QThread>
#include <QMutex>
#include <deque>
#include <set>
#include <string>
-class BasicMainWindow;
+class MainWindow;
class WebContext;
class AvatarLoader : public QThread {
Q_OBJECT
private:
struct RequestItem {
+ RepositoryWrapperFrame *frame = nullptr;
std::string email;
QImage image;
};
struct Private;
Private *m;
protected:
void run() override;
public:
AvatarLoader();
~AvatarLoader() override;
- QIcon fetch(std::string const &email, bool request) const;
+ QIcon fetch(RepositoryWrapperFrame *frame, std::string const &email, bool request) const;
void stop();
- void start(BasicMainWindow *mainwindow);
+ void start(MainWindow *mainwindow);
signals:
- void updated();
+ void updated(RepositoryWrapperFrameP frame);
};
#endif // AVATARLOADER_H
diff --git a/src/BasicMainWindow.cpp b/src/BasicMainWindow.cpp
index aa3904c..e38195d 100644
--- a/src/BasicMainWindow.cpp
+++ b/src/BasicMainWindow.cpp
@@ -1,2935 +1,2822 @@
#include "ApplicationGlobal.h"
#include "BasicMainWindow.h"
#include "CheckoutDialog.h"
#include "CloneDialog.h"
#include "CommitDialog.h"
#include "CommitExploreWindow.h"
#include "CommitViewWindow.h"
#include "CreateRepositoryDialog.h"
#include "DeleteBranchDialog.h"
#include "DoYouWantToInitDialog.h"
#include "FileHistoryWindow.h"
#include "FilePropertyDialog.h"
#include "FileUtil.h"
#include "Git.h"
#include "GitDiff.h"
#include "MemoryReader.h"
#include "MySettings.h"
#include "PushDialog.h"
#include "RepositoryPropertyDialog.h"
#include "SelectCommandDialog.h"
#include "SetGlobalUserDialog.h"
#include "SetUserDialog.h"
#include "SettingsDialog.h"
#include "Terminal.h"
#include "TextEditDialog.h"
+#include "UserEvent.h"
#include "WelcomeWizardDialog.h"
#include "common/joinpath.h"
#include "common/misc.h"
+#include "SubmoduleAddDialog.h"
#include "gpg.h"
#include "gunzip.h"
#include "platform.h"
#include "webclient.h"
#include <QDesktopServices>
#include <QDir>
#include <QDirIterator>
#include <QEvent>
#include <QFileIconProvider>
#include <QListWidget>
#include <QMenu>
#include <QMessageBox>
#include <QStandardPaths>
#include <QTreeWidgetItem>
#include <functional>
#include <memory>
class AsyncExecGitThread_ : public QThread {
private:
GitPtr g;
std::function<void(GitPtr g)> callback;
public:
AsyncExecGitThread_(GitPtr const &g, std::function<void(GitPtr const &g)> const &callback)
: g(g)
, callback(callback)
{
}
protected:
void run() override
{
callback(g);
}
};
//
-struct BasicMainWindow::Private {
- ApplicationSettings appsettings;
-
- QIcon repository_icon;
- QIcon folder_icon;
- QIcon signature_good_icon;
- QIcon signature_dubious_icon;
- QIcon signature_bad_icon;
- QPixmap transparent_pixmap;
-
- QString starting_dir;
- Git::Context gcx;
- RepositoryItem current_repo;
-
- QList<RepositoryItem> repos;
- Git::CommitItemList logs;
- QList<Git::Diff> diff_result;
-
- QStringList added;
- QStringList remotes;
- QString current_remote_name;
- Git::Branch current_branch;
- unsigned int temp_file_counter = 0;
-
- std::string ssh_passphrase_user;
- std::string ssh_passphrase_pass;
-
- std::string http_uid;
- std::string http_pwd;
-
- std::map<QString, GitHubAPI::User> committer_map; // key is email
-
- PtyProcess pty_process;
- bool pty_process_ok = false;
- PtyCondition pty_condition = PtyCondition::None;
-
- WebContext webcx;
-
- AvatarLoader avatar_loader;
- int update_files_list_counter = 0;
- int update_commit_table_counter = 0;
-
- bool interaction_canceled = false;
- InteractionMode interaction_mode = InteractionMode::None;
-
- QString repository_filter_text;
- bool uncommited_changes = false;
-
- std::map<QString, QList<Git::Branch>> branch_map;
- std::map<QString, QList<Git::Tag>> tag_map;
- std::map<int, QList<Label>> label_map;
- std::map<QString, Git::Diff> diff_cache;
- GitObjectCache objcache;
-
- bool remote_changed = false;
-
- ServerType server_type = ServerType::Standard;
- GitHubRepositoryInfo github;
-
- QString head_id;
- bool force_fetch = false;
-
- RepositoryItem temp_repo_for_clone_complete;
- QVariant pty_process_completion_data;
-};
BasicMainWindow::BasicMainWindow(QWidget *parent)
: QMainWindow(parent)
- , m(new Private)
+// , m1(new Private1)
{
- SettingsDialog::loadSettings(&m->appsettings);
- m->starting_dir = QDir::current().absolutePath();
-
- { // load graphic resources
- QFileIconProvider icons;
- m->folder_icon = icons.icon(QFileIconProvider::Folder);
- m->repository_icon = QIcon(":/image/repository.png");
- m->signature_good_icon = QIcon(":/image/signature-good.png");
- m->signature_bad_icon = QIcon(":/image/signature-bad.png");
- m->signature_dubious_icon = QIcon(":/image/signature-dubious.png");
- m->transparent_pixmap = QPixmap(":/image/transparent.png");
- }
}
BasicMainWindow::~BasicMainWindow()
{
- setRemoteMonitoringEnabled(false);
- deleteTempFiles();
- delete m;
+// delete m1;
}
-ApplicationSettings *BasicMainWindow::appsettings()
+ApplicationSettings *MainWindow::appsettings()
{
- return &m->appsettings;
+ return &global->appsettings;
+// return &m1->appsettings;
}
-const ApplicationSettings *BasicMainWindow::appsettings() const
+const ApplicationSettings *MainWindow::appsettings() const
{
- return &m->appsettings;
+ return &global->appsettings;
+// return &m1->appsettings;
}
-WebContext *BasicMainWindow::webContext()
+WebContext *MainWindow::webContext()
{
- return &m->webcx;
+ return &m1->webcx;
}
-QString BasicMainWindow::gitCommand() const
+QString MainWindow::gitCommand() const
{
- return m->gcx.git_command;
+ return m1->gcx.git_command;
}
-namespace {
-
-enum UserEvent {
- EventUserFunction = QEvent::User,
-};
-
-class UserFunctionEvent : public QEvent {
-public:
- std::function<void(QVariant &)> func;
- QVariant var;
-
- explicit UserFunctionEvent(std::function<void(QVariant const &)> const &func, QVariant const &var)
- : QEvent((QEvent::Type)EventUserFunction)
- , func(func)
- , var(var)
- {
- }
-};
-
-} // namespace
-
-bool BasicMainWindow::event(QEvent *event)
-{
- if (event->type() == (QEvent::Type)EventUserFunction) {
- if (auto *e = dynamic_cast<UserFunctionEvent *>(event)) {
- e->func(e->var);
- return true;
- }
- }
- return QMainWindow::event(event);
-}
-
-void BasicMainWindow::postUserFunctionEvent(const std::function<void(QVariant const &)> &fn, QVariant const &v)
-{
- qApp->postEvent(this, new UserFunctionEvent(fn, v));
-}
-
-void BasicMainWindow::autoOpenRepository(QString dir)
+void MainWindow::autoOpenRepository(QString dir)
{
auto Open = [&](RepositoryItem const &item){
setCurrentRepository(item, true);
openRepository(false, true);
};
RepositoryItem const *repo = findRegisteredRepository(&dir);
if (repo) {
Open(*repo);
return;
}
RepositoryItem newitem;
- GitPtr g = git(dir);
+ GitPtr g = git(dir, {}, {});
if (isValidWorkingCopy(g)) {
ushort const *left = dir.utf16();
ushort const *right = left + dir.size();
if (right[-1] == '/' || right[-1] == '\\') {
right--;
}
ushort const *p = right;
while (left + 1 < p && !(p[-1] == '/' || p[-1] == '\\')) p--;
if (p < right) {
newitem.local_dir = dir;
newitem.name = QString::fromUtf16(p, right - p);
saveRepositoryBookmark(newitem);
Open(newitem);
return;
}
} else {
DoYouWantToInitDialog dlg(this, dir);
if (dlg.exec() == QDialog::Accepted) {
createRepository(dir);
}
}
}
-GitPtr BasicMainWindow::git(QString const &dir) const
+GitPtr MainWindow::git(QString const &dir, QString const &submodpath, QString const &sshkey) const
{
-// const_cast<BasicMainWindow *>(this)->checkGitCommand();
-
- GitPtr g = std::make_shared<Git>(m->gcx, dir);
+ GitPtr g = std::make_shared<Git>(m1->gcx, dir, submodpath, sshkey);
if (g && QFileInfo(g->gitCommand()).isExecutable()) {
g->setLogCallback(git_callback, (void *)this);
return g;
} else {
QString text = tr("git command not specified") + '\n';
- const_cast<BasicMainWindow *>(this)->writeLog(text);
+ const_cast<MainWindow *>(this)->writeLog(text);
return GitPtr();
}
}
-GitPtr BasicMainWindow::git()
+GitPtr MainWindow::git()
+{
+ RepositoryItem const &item = currentRepository();
+ return git(item.local_dir, {}, item.ssh_key);
+}
+
+
+GitPtr MainWindow::git(Git::SubmoduleItem const &submod)
{
- return git(currentWorkingCopyDir());
+ if (!submod) return {};
+ RepositoryItem const &item = currentRepository();
+ return git(item.local_dir, submod.path, item.ssh_key);
}
-QPixmap BasicMainWindow::getTransparentPixmap()
+QPixmap MainWindow::getTransparentPixmap()
{
- return m->transparent_pixmap;
+ return m1->transparent_pixmap;
}
-QIcon BasicMainWindow::committerIcon(int row) const
+QIcon MainWindow::committerIcon(RepositoryWrapperFrame *frame, int row) const
{
QIcon icon;
if (isAvatarEnabled() && isOnlineMode()) {
- auto const &logs = getLogs();
+ auto const &logs = getLogs(frame);
if (row >= 0 && row < (int)logs.size()) {
Git::CommitItem const &commit = logs[row];
if (commit.email.indexOf('@') > 0) {
std::string email = commit.email.toStdString();
- icon = getAvatarLoader()->fetch(email, true); // from gavatar
+ icon = getAvatarLoader()->fetch(frame, email, true); // from gavatar
}
}
}
return icon;
}
-Git::CommitItem const *BasicMainWindow::commitItem(int row) const
+Git::CommitItem const *MainWindow::commitItem(RepositoryWrapperFrame *frame, int row) const
{
- auto const &logs = getLogs();
+ auto const &logs = getLogs(frame);
if (row >= 0 && row < (int)logs.size()) {
return &logs[row];
}
return nullptr;
}
-QIcon BasicMainWindow::verifiedIcon(char s) const
+QIcon MainWindow::verifiedIcon(char s) const
{
Git::SignatureGrade g = Git::evaluateSignature(s);
switch (g) {
case Git::SignatureGrade::Good:
- return m->signature_good_icon;
+ return m1->signature_good_icon;
case Git::SignatureGrade::Bad:
- return m->signature_bad_icon;
+ return m1->signature_bad_icon;
case Git::SignatureGrade::Unknown:
case Git::SignatureGrade::Dubious:
case Git::SignatureGrade::Missing:
- return m->signature_dubious_icon;
+ return m1->signature_dubious_icon;
}
return QIcon();
}
-QString BasicMainWindow::currentWorkingCopyDir() const
+QString MainWindow::currentWorkingCopyDir() const
{
- return m->current_repo.local_dir;
+ return m1->current_repo.local_dir;
}
-bool BasicMainWindow::isRepositoryOpened() const
+bool MainWindow::isRepositoryOpened() const
{
return Git::isValidWorkingCopy(currentWorkingCopyDir());
}
-QList<BasicMainWindow::Label> const *BasicMainWindow::label(int row) const
+QList<BranchLabel> const *MainWindow::label(RepositoryWrapperFrame const *frame, int row) const
{
- auto it = getLabelMap()->find(row);
- if (it != getLabelMap()->end()) {
+ auto it = getLabelMap(frame)->find(row);
+ if (it != getLabelMap(frame)->end()) {
return &it->second;
}
return nullptr;
}
-QList<BasicMainWindow::Label> BasicMainWindow::sortedLabels(int row) const
+QList<BranchLabel> MainWindow::sortedLabels(RepositoryWrapperFrame *frame, int row) const
{
- QList<BasicMainWindow::Label> list;
- auto const *p = const_cast<BasicMainWindow *>(this)->label(row);
+ QList<BranchLabel> list;
+ auto const *p = const_cast<MainWindow *>(this)->label(frame, row);
if (p && !p->empty()) {
list = *p;
- std::sort(list.begin(), list.end(), [](BasicMainWindow::Label const &l, BasicMainWindow::Label const &r){
- auto Compare = [](BasicMainWindow::Label const &l, BasicMainWindow::Label const &r){
+ std::sort(list.begin(), list.end(), [](BranchLabel const &l, BranchLabel const &r){
+ auto Compare = [](BranchLabel const &l, BranchLabel const &r){
if (l.kind < r.kind) return -1;
if (l.kind > r.kind) return 1;
if (l.text < r.text) return -1;
if (l.text > r.text) return 1;
return 0;
};
return Compare(l, r) < 0;
});
}
return list;
}
-bool BasicMainWindow::saveAs(QString const &id, QString const &dstpath)
+bool MainWindow::saveAs(RepositoryWrapperFrame *frame, QString const &id, QString const &dstpath)
{
if (id.startsWith(PATH_PREFIX)) {
return saveFileAs(id.mid(1), dstpath);
} else {
- return saveBlobAs(id, dstpath);
+ return saveBlobAs(frame, id, dstpath);
}
}
-bool BasicMainWindow::testRemoteRepositoryValidity(QString const &url)
+bool MainWindow::testRemoteRepositoryValidity(QString const &url, QString const &sshkey)
{
bool ok;
{
OverrideWaitCursor;
- ok = isValidRemoteURL(url);
+ ok = isValidRemoteURL(url, sshkey);
}
QString pass = tr("The URL is a valid repository");
QString fail = tr("Failed to access the URL");
QString text = "%1\n\n%2";
text = text.arg(url).arg(ok ? pass : fail);
QString title = tr("Remote Repository");
if (ok) {
QMessageBox::information(this, title, text);
} else {
QMessageBox::critical(this, title, text);
}
return ok;
}
-void BasicMainWindow::addWorkingCopyDir(QString const &dir, bool open)
+void MainWindow::addWorkingCopyDir(QString const &dir, bool open)
{
addWorkingCopyDir(dir, QString(), open);
}
-bool BasicMainWindow::queryCommit(QString const &id, Git::CommitItem *out)
+bool MainWindow::queryCommit(QString const &id, Git::CommitItem *out)
{
*out = Git::CommitItem();
GitPtr g = git();
return g->queryCommit(id, out);
}
-QAction *BasicMainWindow::addMenuActionProperty(QMenu *menu)
+QAction *MainWindow::addMenuActionProperty(QMenu *menu)
{
return menu->addAction(tr("&Property"));
}
-void BasicMainWindow::checkout(QWidget *parent, Git::CommitItem const *commit, std::function<void()> accepted_callback)
-{
- if (!commit) return;
-
- GitPtr g = git();
- if (!isValidWorkingCopy(g)) return;
-
- QStringList tags;
- QStringList all_local_branches;
- QStringList local_branches;
- QStringList remote_branches;
- {
- NamedCommitList named_commits = namedCommitItems(Branches | Tags | Remotes);
- for (NamedCommitItem const &item : named_commits) {
- QString name = item.name;
- if (item.id == commit->commit_id) {
- if (item.type == NamedCommitItem::Type::Tag) {
- tags.push_back(name);
- } else if (item.type == NamedCommitItem::Type::BranchLocal || item.type == NamedCommitItem::Type::BranchRemote) {
- int i = name.lastIndexOf('/');
- if (i < 0 && name == "HEAD") continue;
- if (i > 0 && name.mid(i + 1) == "HEAD") continue;
- if (item.type == NamedCommitItem::Type::BranchLocal) {
- local_branches.push_back(name);
- } else if (item.type == NamedCommitItem::Type::BranchRemote) {
- remote_branches.push_back(name);
- }
- }
- }
- if (item.type == NamedCommitItem::Type::BranchLocal) {
- all_local_branches.push_back(name);
- }
- }
- }
-
- CheckoutDialog dlg(parent, tags, all_local_branches, local_branches, remote_branches);
- if (dlg.exec() == QDialog::Accepted) {
- if (accepted_callback) {
- accepted_callback();
- }
- CheckoutDialog::Operation op = dlg.operation();
- QString name = dlg.branchName();
- QString id = commit->commit_id;
- if (id.isEmpty() && !commit->parent_ids.isEmpty()) {
- id = commit->parent_ids.front();
- }
- bool ok = false;
- setLogEnabled(g, true);
- if (op == CheckoutDialog::Operation::HeadDetached) {
- if (!id.isEmpty()) {
- ok = g->git(QString("checkout \"%1\"").arg(id), true);
- }
- } else if (op == CheckoutDialog::Operation::CreateLocalBranch) {
- if (!name.isEmpty() && !id.isEmpty()) {
- ok = g->git(QString("checkout -b \"%1\" \"%2\"").arg(name).arg(id), true);
- }
- } else if (op == CheckoutDialog::Operation::ExistingLocalBranch) {
- if (!name.isEmpty()) {
- ok = g->git(QString("checkout \"%1\"").arg(name), true);
- }
- }
- if (ok) {
- openRepository(true);
- }
- }
-}
-
-void BasicMainWindow::jumpToCommit(QString id)
+void MainWindow::jumpToCommit(RepositoryWrapperFrame *frame, QString id)
{
GitPtr g = git();
id = g->rev_parse(id);
if (!id.isEmpty()) {
- int row = rowFromCommitId(id);
- setCurrentLogRow(row);
+ int row = rowFromCommitId(frame, id);
+ setCurrentLogRow(frame, row);
}
}
-void BasicMainWindow::execCommitViewWindow(Git::CommitItem const *commit)
+void MainWindow::execCommitViewWindow(Git::CommitItem const *commit)
{
CommitViewWindow win(this, commit);
win.exec();
}
-void BasicMainWindow::execCommitExploreWindow(QWidget *parent, Git::CommitItem const *commit)
+void MainWindow::execCommitExploreWindow(RepositoryWrapperFrame *frame, QWidget *parent, Git::CommitItem const *commit)
{
- CommitExploreWindow win(parent, this, getObjCache(), commit);
+ CommitExploreWindow win(parent, this, getObjCache(frame), commit);
win.exec();
}
-void BasicMainWindow::execFileHistory(QString const &path)
+void MainWindow::execFileHistory(QString const &path)
{
if (path.isEmpty()) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
FileHistoryWindow dlg(this);
dlg.prepare(g, path);
dlg.exec();
}
-void BasicMainWindow::execFilePropertyDialog(QListWidgetItem *item)
-{
- if (item) {
- QString path = getFilePath(item);
- QString id = getObjectID(item);
- FilePropertyDialog dlg(this);
- dlg.exec(this, path, id);
- }
-}
-QString BasicMainWindow::selectGitCommand(bool save)
+
+QString MainWindow::selectGitCommand(bool save)
{
char const *exe = GIT_COMMAND;
QString path = gitCommand();
auto fn = [&](QString const &path){
setGitCommand(path, save);
};
QStringList list = whichCommand_(exe);
#ifdef Q_OS_WIN
{
QStringList newlist;
QString suffix1 = "\\bin\\" GIT_COMMAND;
QString suffix2 = "\\cmd\\" GIT_COMMAND;
for (QString const &s : list) {
newlist.push_back(s);
auto DoIt = [&](QString const &suffix){
if (s.endsWith(suffix)) {
QString t = s.mid(0, s.size() - suffix.size());
QString t1 = t + "\\mingw64\\bin\\" GIT_COMMAND;
if (misc::isExecutable(t1)) newlist.push_back(t1);
QString t2 = t + "\\mingw\\bin\\" GIT_COMMAND;
if (misc::isExecutable(t2)) newlist.push_back(t2);
}
};
DoIt(suffix1);
DoIt(suffix2);
}
std::sort(newlist.begin(), newlist.end());
auto end = std::unique(newlist.begin(), newlist.end());
list.clear();
for (auto it = newlist.begin(); it != end; it++) {
list.push_back(*it);
}
}
#endif
return selectCommand_("Git", exe, list, path, fn);
}
-QString BasicMainWindow::selectFileCommand(bool save)
+QString MainWindow::selectFileCommand(bool save)
{
char const *exe = FILE_COMMAND;
QString path = global->file_command;
auto fn = [&](QString const &path){
setFileCommand(path, save);
};
QStringList list = whichCommand_(exe);
#ifdef Q_OS_WIN
QString dir = misc::getApplicationDir();
QString path1 = dir / FILE_COMMAND;
QString path2;
int i = dir.lastIndexOf('/');
int j = dir.lastIndexOf('\\');
if (i < j) i = j;
if (i > 0) {
path2 = dir.mid(0, i) / FILE_COMMAND;
}
path1 = misc::normalizePathSeparator(path1);
path2 = misc::normalizePathSeparator(path2);
if (misc::isExecutable(path1)) list.push_back(path1);
if (misc::isExecutable(path2)) list.push_back(path2);
#endif
return selectCommand_("File", exe, list, path, fn);
}
-QString BasicMainWindow::selectGpgCommand(bool save)
+QString MainWindow::selectGpgCommand(bool save)
{
QString path = global->gpg_command;
auto fn = [&](QString const &path){
setGpgCommand(path, save);
};
QStringList list = whichCommand_(GPG_COMMAND, GPG2_COMMAND);
QStringList cmdlist;
cmdlist.push_back(GPG_COMMAND);
cmdlist.push_back(GPG2_COMMAND);
return selectCommand_("GPG", cmdlist, list, path, fn);
}
-Git::Branch const &BasicMainWindow::currentBranch() const
+QString MainWindow::selectSshCommand(bool save)
+{
+ QString path = m1->gcx.ssh_command;
+
+ auto fn = [&](QString const &path){
+ setSshCommand(path, save);
+ };
+
+ QStringList list = whichCommand_(SSH_COMMAND);
+
+ QStringList cmdlist;
+ cmdlist.push_back(SSH_COMMAND);
+ return selectCommand_("ssh", cmdlist, list, path, fn);
+}
+
+Git::Branch const &MainWindow::currentBranch() const
{
- return m->current_branch;
+ return m1->current_branch;
}
-void BasicMainWindow::setCurrentBranch(Git::Branch const &b)
+void MainWindow::setCurrentBranch(Git::Branch const &b)
{
- m->current_branch = b;
+ m1->current_branch = b;
}
-RepositoryItem const &BasicMainWindow::currentRepository() const
+RepositoryItem const &MainWindow::currentRepository() const
{
- return m->current_repo;
+ return m1->current_repo;
}
-QString BasicMainWindow::currentRepositoryName() const
+QString MainWindow::currentRepositoryName() const
{
return currentRepository().name;
}
-QString BasicMainWindow::currentRemoteName() const
+QString MainWindow::currentRemoteName() const
{
- return m->current_remote_name;
+ return m1->current_remote_name;
}
-QString BasicMainWindow::currentBranchName() const
+QString MainWindow::currentBranchName() const
{
return currentBranch().name;
}
-bool BasicMainWindow::isValidWorkingCopy(const GitPtr &g) const
+bool MainWindow::isValidWorkingCopy(const GitPtr &g) const
{
return g && g->isValidWorkingCopy();
}
-QString BasicMainWindow::determinFileType_(QString const &path, bool mime, std::function<void (QString const &, QByteArray *)> const &callback) const
+QString MainWindow::determinFileType_(QString const &path, bool mime, std::function<void (QString const &, QByteArray *)> const &callback) const
{
- const_cast<BasicMainWindow *>(this)->checkFileCommand();
+ const_cast<MainWindow *>(this)->checkFileCommand();
return misc::determinFileType(global->file_command, path, mime, callback);
}
-QString BasicMainWindow::determinFileType(QString const &path, bool mime)
+QString MainWindow::determinFileType(QString const &path, bool mime)
{
return determinFileType_(path, mime, [](QString const &cmd, QByteArray *ba){
misc2::runCommand(cmd, ba);
});
}
-QString BasicMainWindow::determinFileType(QByteArray in, bool mime)
+QString MainWindow::determinFileType(QByteArray in, bool mime)
{
if (in.isEmpty()) return QString();
if (in.size() > 10) {
if (memcmp(in.data(), "\x1f\x8b\x08", 3) == 0) { // gzip
QBuffer buf;
MemoryReader reader(in.data(), in.size());
reader.open(MemoryReader::ReadOnly);
buf.open(QBuffer::WriteOnly);
gunzip z;
z.set_maximul_size(100000);
z.decode(&reader, &buf);
in = buf.buffer();
}
}
// ファイル名を "-" にすると、リダイレクトで標準入力へ流し込める。
return determinFileType_("-", mime, [&](QString const &cmd, QByteArray *ba){
int r = misc2::runCommand(cmd, &in, ba);
if (r != 0) {
ba->clear();
}
});
}
-QList<Git::Tag> BasicMainWindow::queryTagList()
+QList<Git::Tag> MainWindow::queryTagList(RepositoryWrapperFrame *frame)
{
QList<Git::Tag> list;
- Git::CommitItem const *commit = selectedCommitItem();
+ Git::CommitItem const *commit = selectedCommitItem(frame);
if (commit && Git::isValidID(commit->commit_id)) {
- list = findTag(commit->commit_id);
+ list = findTag(frame, commit->commit_id);
}
return list;
}
-int BasicMainWindow::limitLogCount() const
+int MainWindow::limitLogCount() const
{
int n = appsettings()->maximum_number_of_commit_item_acquisitions;
return (n >= 1 && n <= 100000) ? n : 10000;
}
-TextEditorThemePtr BasicMainWindow::themeForTextEditor()
+TextEditorThemePtr MainWindow::themeForTextEditor()
{
return global->theme->text_editor_theme;
}
-Git::Object BasicMainWindow::cat_file_(GitPtr const &g, QString const &id)
+Git::Object MainWindow::cat_file_(RepositoryWrapperFrame *frame, GitPtr const &g, QString const &id)
{
if (isValidWorkingCopy(g)) {
QString path_prefix = PATH_PREFIX;
if (id.startsWith(path_prefix)) {
- QString path = g->workingRepositoryDir();
+ QString path = g->workingDir();
path = path / id.mid(path_prefix.size());
QFile file(path);
if (file.open(QFile::ReadOnly)) {
Git::Object obj;
obj.content = file.readAll();
return obj;
}
} else if (Git::isValidID(id)) {
- return getObjCache()->catFile(id);;
+ return getObjCache(frame)->catFile(id);;
}
}
return Git::Object();
}
-Git::Object BasicMainWindow::cat_file(QString const &id)
+Git::Object MainWindow::cat_file(RepositoryWrapperFrame *frame, QString const &id)
{
- return cat_file_(git(), id);
+ return cat_file_(frame, git(), id);
}
-QString BasicMainWindow::newTempFilePath()
+QString MainWindow::newTempFilePath()
{
QString tmpdir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
- QString path = tmpdir / tempfileHeader() + QString::number(m->temp_file_counter);
- m->temp_file_counter++;
+ QString path = tmpdir / tempfileHeader() + QString::number(m1->temp_file_counter);
+ m1->temp_file_counter++;
return path;
}
-QString BasicMainWindow::findFileID(QString const &commit_id, QString const &file)
+QString MainWindow::findFileID(RepositoryWrapperFrame *frame, QString const &commit_id, QString const &file)
{
- return lookupFileID(getObjCache(), commit_id, file);
+ return lookupFileID(getObjCache(frame), commit_id, file);
}
-void BasicMainWindow::updateFilesList(QString const &id, QList<Git::Diff> *diff_list, QListWidget *listwidget)
+void MainWindow::setAppSettings(const ApplicationSettings &appsettings)
{
- GitPtr g = git();
- if (!isValidWorkingCopy(g)) return;
-
- listwidget->clear();
-
- auto AddItem = [&](QString const &filename, QString header, int idiff){
- if (header.isEmpty()) {
- header = "(??\?) "; // damn trigraph
- }
- QListWidgetItem *item = new QListWidgetItem(filename);
- item->setData(FilePathRole, filename);
- item->setData(DiffIndexRole, idiff);
- item->setData(HunkIndexRole, -1);
- item->setData(HeaderRole, header);
- listwidget->addItem(item);
- };
-
- GitDiff dm(getObjCache());
- if (!dm.diff(id, diff_list)) return;
-
- addDiffItems(diff_list, AddItem);
+ global->appsettings = appsettings;
+// m1->appsettings = appsettings;
}
-void BasicMainWindow::setAppSettings(const ApplicationSettings &appsettings)
+QPixmap MainWindow::getTransparentPixmap() const
{
- m->appsettings = appsettings;
+ return m1->transparent_pixmap;
}
-QPixmap BasicMainWindow::getTransparentPixmap() const
+QIcon MainWindow::getSignatureBadIcon() const
{
- return m->transparent_pixmap;
+ return m1->signature_bad_icon;
}
-QIcon BasicMainWindow::getSignatureBadIcon() const
+QIcon MainWindow::getSignatureDubiousIcon() const
{
- return m->signature_bad_icon;
+ return m1->signature_dubious_icon;
}
-QIcon BasicMainWindow::getSignatureDubiousIcon() const
+QIcon MainWindow::getSignatureGoodIcon() const
{
- return m->signature_dubious_icon;
+ return m1->signature_good_icon;
}
-QIcon BasicMainWindow::getSignatureGoodIcon() const
+QIcon MainWindow::getFolderIcon() const
{
- return m->signature_good_icon;
+ return m1->folder_icon;
}
-QIcon BasicMainWindow::getFolderIcon() const
+QIcon MainWindow::getRepositoryIcon() const
{
- return m->folder_icon;
+ return m1->repository_icon;
}
-QIcon BasicMainWindow::getRepositoryIcon() const
-{
- return m->repository_icon;
-}
-const Git::CommitItemList &BasicMainWindow::getLogs() const
-{
- return m->logs;
-}
-Git::CommitItem const *BasicMainWindow::getLog(int index) const
-{
- return (index >= 0 && index < (int)m->logs.size()) ? &m->logs[index] : nullptr;
-}
-Git::CommitItemList *BasicMainWindow::getLogsPtr()
-{
- return &m->logs;
-}
-void BasicMainWindow::setLogs(const Git::CommitItemList &logs)
-{
- m->logs = logs;
-}
-void BasicMainWindow::clearLogs()
+
+
+
+
+
+
+
+BasicMainWindow::PtyCondition MainWindow::getPtyCondition()
{
- m->logs.clear();
+ return m1->pty_condition;
}
-BasicMainWindow::PtyCondition BasicMainWindow::getPtyCondition()
+void MainWindow::setPtyUserData(QVariant const &userdata)
{
- return m->pty_condition;
+ m1->pty_process.setVariant(userdata);
}
-void BasicMainWindow::setPtyUserData(QVariant const &userdata)
+void MainWindow::setPtyCondition(const PtyCondition &ptyCondition)
{
- m->pty_process.setVariant(userdata);
+ m1->pty_condition = ptyCondition;
}
-void BasicMainWindow::setPtyCondition(const PtyCondition &ptyCondition)
+
+PtyProcess *MainWindow::getPtyProcess()
{
- m->pty_condition = ptyCondition;
+ return &m1->pty_process;
}
-
-PtyProcess *BasicMainWindow::getPtyProcess()
+bool MainWindow::getPtyProcessOk() const
{
- return &m->pty_process;
+ return m1->pty_process_ok;
}
-bool BasicMainWindow::getPtyProcessOk() const
+void MainWindow::setPtyProcessOk(bool pty_process_ok)
{
- return m->pty_process_ok;
+ m1->pty_process_ok = pty_process_ok;
}
-void BasicMainWindow::setPtyProcessOk(bool pty_process_ok)
+const QList<RepositoryItem> &MainWindow::getRepos() const
{
- m->pty_process_ok = pty_process_ok;
+ return m1->repos;
}
-const QList<RepositoryItem> &BasicMainWindow::getRepos() const
+QList<RepositoryItem> *MainWindow::getReposPtr()
{
- return m->repos;
+ return &m1->repos;
}
-QList<RepositoryItem> *BasicMainWindow::getReposPtr()
+void MainWindow::setCurrentRemoteName(QString const &name)
{
- return &m->repos;
+ m1->current_remote_name = name;
}
-void BasicMainWindow::setCurrentRemoteName(QString const &name)
+AvatarLoader *MainWindow::getAvatarLoader()
{
- m->current_remote_name = name;
+ return &m1->avatar_loader;
}
-AvatarLoader *BasicMainWindow::getAvatarLoader()
+AvatarLoader const *MainWindow::getAvatarLoader() const
{
- return &m->avatar_loader;
+ return &m1->avatar_loader;
}
-AvatarLoader const *BasicMainWindow::getAvatarLoader() const
+//int *MainWindow::ptrUpdateFilesListCounter()
+//{
+// return &m1->update_files_list_counter;
+//}
+
+//int *MainWindow::ptrUpdateCommitTableCounter()
+//{
+// return &m1->update_commit_table_counter;
+//}
+
+bool MainWindow::interactionCanceled() const
{
- return &m->avatar_loader;
+ return m1->interaction_canceled;
}
-int *BasicMainWindow::ptrUpdateFilesListCounter()
+void MainWindow::setInteractionCanceled(bool canceled)
{
- return &m->update_files_list_counter;
+ m1->interaction_canceled = canceled;
}
-int *BasicMainWindow::ptrUpdateCommitTableCounter()
+BasicMainWindow::InteractionMode MainWindow::interactionMode() const
{
- return &m->update_commit_table_counter;
+ return m1->interaction_mode;
}
-bool BasicMainWindow::interactionCanceled() const
+void MainWindow::setInteractionMode(const InteractionMode &im)
{
- return m->interaction_canceled;
+ m1->interaction_mode = im;
}
-void BasicMainWindow::setInteractionCanceled(bool canceled)
+QString MainWindow::getRepositoryFilterText() const
{
- m->interaction_canceled = canceled;
+ return m1->repository_filter_text;
}
-BasicMainWindow::InteractionMode BasicMainWindow::interactionMode() const
+void MainWindow::setRepositoryFilterText(QString const &text)
{
- return m->interaction_mode;
+ m1->repository_filter_text = text;
}
-void BasicMainWindow::setInteractionMode(const InteractionMode &im)
+void MainWindow::setUncommitedChanges(bool uncommited_changes)
{
- m->interaction_mode = im;
+ m1->uncommited_changes = uncommited_changes;
}
-QString BasicMainWindow::getRepositoryFilterText() const
+QList<Git::Diff> *MainWindow::diffResult()
{
- return m->repository_filter_text;
+ return &m1->diff_result;
}
-void BasicMainWindow::setRepositoryFilterText(QString const &text)
+void MainWindow::setDiffResult(const QList<Git::Diff> &diffs)
{
- m->repository_filter_text = text;
+ m1->diff_result = diffs;
}
-void BasicMainWindow::setUncommitedChanges(bool uncommited_changes)
+const QList<Git::SubmoduleItem> &MainWindow::submodules() const
{
- m->uncommited_changes = uncommited_changes;
+ return m1->submodules;
}
-QList<Git::Diff> *BasicMainWindow::diffResult()
+void MainWindow::setSubmodules(const QList<Git::SubmoduleItem> &submodules)
{
- return &m->diff_result;
+ m1->submodules = submodules;
}
-std::map<QString, Git::Diff> *BasicMainWindow::getDiffCacheMap()
+std::map<QString, Git::Diff> *MainWindow::getDiffCacheMap(RepositoryWrapperFrame *frame)
{
- return &m->diff_cache;
+// return &m1->diff_cache;
+ return &frame->diff_cache;
}
-bool BasicMainWindow::getRemoteChanged() const
+bool MainWindow::getRemoteChanged() const
{
- return m->remote_changed;
+ return m1->remote_changed;
}
-void BasicMainWindow::setRemoteChanged(bool remote_changed)
+void MainWindow::setRemoteChanged(bool remote_changed)
{
- m->remote_changed = remote_changed;
+ m1->remote_changed = remote_changed;
}
-void BasicMainWindow::setServerType(const ServerType &server_type)
+void MainWindow::setServerType(const ServerType &server_type)
{
- m->server_type = server_type;
+ m1->server_type = server_type;
}
-GitHubRepositoryInfo *BasicMainWindow::ptrGitHub()
+GitHubRepositoryInfo *MainWindow::ptrGitHub()
{
- return &m->github;
+ return &m1->github;
}
-std::map<int, QList<BasicMainWindow::Label>> *BasicMainWindow::getLabelMap()
+std::map<int, QList<BranchLabel>> *MainWindow::getLabelMap(RepositoryWrapperFrame *frame)
{
- return &m->label_map;
+// return &m1->label_map;
+ return &frame->label_map;
}
-std::map<int, QList<BasicMainWindow::Label>> const *BasicMainWindow::getLabelMap() const
+std::map<int, QList<BranchLabel>> const *MainWindow::getLabelMap(RepositoryWrapperFrame const *frame) const
{
- return &m->label_map;
+// return &m1->label_map;
+ return &frame->label_map;
}
-void BasicMainWindow::clearLabelMap()
+void MainWindow::clearLabelMap(RepositoryWrapperFrame *frame)
{
- m->label_map.clear();
+ frame->label_map.clear();
+// m1->label_map.clear();
}
-GitObjectCache *BasicMainWindow::getObjCache()
+GitObjectCache *MainWindow::getObjCache(RepositoryWrapperFrame *frame)
{
- return &m->objcache;
+ return &frame->objcache;
+// return &m1->objcache;
}
-bool BasicMainWindow::getForceFetch() const
+bool MainWindow::getForceFetch() const
{
- return m->force_fetch;
+ return m1->force_fetch;
}
-void BasicMainWindow::setForceFetch(bool force_fetch)
+void MainWindow::setForceFetch(bool force_fetch)
{
- m->force_fetch = force_fetch;
+ m1->force_fetch = force_fetch;
}
-std::map<QString, QList<Git::Tag>> *BasicMainWindow::ptrTagMap()
+std::map<QString, QList<Git::Tag>> *MainWindow::ptrTagMap(RepositoryWrapperFrame *frame)
{
- return &m->tag_map;
+// return &m1->tag_map;
+ return &frame->tag_map;
}
-QString BasicMainWindow::getHeadId() const
+QString MainWindow::getHeadId() const
{
- return m->head_id;
+ return m1->head_id;
}
-void BasicMainWindow::setHeadId(QString const &head_id)
+void MainWindow::setHeadId(QString const &head_id)
{
- m->head_id = head_id;
+ m1->head_id = head_id;
}
-void BasicMainWindow::setPtyProcessCompletionData(QVariant const &value)
+void MainWindow::setPtyProcessCompletionData(QVariant const &value)
{
- m->pty_process_completion_data = value;
+ m1->pty_process_completion_data = value;
}
-QVariant const &BasicMainWindow::getTempRepoForCloneCompleteV() const
+QVariant const &MainWindow::getTempRepoForCloneCompleteV() const
{
- return m->pty_process_completion_data;
+ return m1->pty_process_completion_data;
}
-void BasicMainWindow::updateCommitGraph()
+void MainWindow::updateCommitGraph(RepositoryWrapperFrame *frame)
{
- auto const &logs = getLogs();
- auto *logsp = getLogsPtr();
+ auto const &logs = getLogs(frame);
+ auto *logsp = getLogsPtr(frame);
const size_t LogCount = logs.size();
// 樹形図情報を構築する
if (LogCount > 0) {
auto LogItem = [&](size_t i)->Git::CommitItem &{ return logsp->at(i); };
enum { // 有向グラフを構築するあいだ CommitItem::marker_depth をフラグとして使用する
UNKNOWN = 0,
KNOWN = 1,
};
for (Git::CommitItem &item : *logsp) {
item.marker_depth = UNKNOWN;
}
// コミットハッシュを検索して、親コミットのインデックスを求める
for (size_t i = 0; i < LogCount; i++) {
Git::CommitItem *item = &LogItem(i);
item->parent_lines.clear();
if (item->parent_ids.empty()) {
item->resolved = true;
} else {
for (int j = 0; j < item->parent_ids.size(); j++) { // 親の数だけループ
QString const &parent_id = item->parent_ids[j]; // 親のハッシュ値
for (size_t k = i + 1; k < LogCount; k++) { // 親を探す
if (LogItem(k).commit_id == parent_id) { // ハッシュ値が一致したらそれが親
item->parent_lines.emplace_back(k); // インデックス値を記憶
LogItem(k).has_child = true;
LogItem(k).marker_depth = KNOWN;
item->resolved = true;
break;
}
}
}
}
}
std::vector<Element> elements; // 線分リスト
{ // 線分リストを作成する
std::deque<Task> tasks; // 未処理タスクリスト
{
for (size_t i = 0; i < LogCount; i++) {
Git::CommitItem *item = &LogItem(i);
if (item->marker_depth == UNKNOWN) {
int n = item->parent_lines.size(); // 最初のコミットアイテム
for (int j = 0; j < n; j++) {
tasks.emplace_back(i, j); // タスクを追加
}
}
item->marker_depth = UNKNOWN;
}
}
while (!tasks.empty()) { // タスクが残っているならループ
Element e;
Task task;
{ // 最初のタスクを取り出す
task = tasks.front();
tasks.pop_front();
}
e.indexes.push_back(task.index); // 先頭のインデックスを追加
int index = LogItem(task.index).parent_lines[task.parent].index; // 開始インデックス
while (index > 0 && (size_t)index < LogCount) { // 最後に到達するまでループ
e.indexes.push_back(index); // インデックスを追加
int n = LogItem(index).parent_lines.size(); // 親の数
if (n == 0) break; // 親がないなら終了
Git::CommitItem *item = &LogItem(index);
if (item->marker_depth == KNOWN) break; // 既知のアイテムに到達したら終了
item->marker_depth = KNOWN; // 既知のアイテムにする
for (int i = 1; i < n; i++) {
tasks.emplace_back(index, i); // タスク追加
}
index = LogItem(index).parent_lines[0].index; // 次の親(親リストの先頭の要素)
}
if (e.indexes.size() >= 2) {
elements.push_back(e);
}
}
}
// 線情報をクリア
for (Git::CommitItem &item : *logsp) {
item.marker_depth = -1;
item.parent_lines.clear();
}
// マークと線の深さを決める
if (!elements.empty()) {
{ // 優先順位を調整する
std::sort(elements.begin(), elements.end(), [](Element const &left, Element const &right){
int i = 0;
{ // 長いものを優先して左へ
int l = left.indexes.back() - left.indexes.front();
int r = right.indexes.back() - right.indexes.front();
i = r - l; // 降順
}
if (i == 0) {
// コミットが新しいものを優先して左へ
int l = left.indexes.front();
int r = right.indexes.front();
i = l - r; // 昇順
}
return i < 0;
});
// 子の無いブランチ(タグ等)が複数連続しているとき、古いコミットを右に寄せる
{
for (size_t i = 0; i + 1 < elements.size(); i++) {
Element *e = &elements[i];
int index1 = e->indexes.front();
if (index1 > 0 && !LogItem(index1).has_child) { // 子がない
// 新しいコミットを探す
for (size_t j = i + 1; j < elements.size(); j++) { // 現在位置より後ろを探す
Element *f = &elements[j];
int index2 = f->indexes.front();
if (index1 == index2 + 1) { // 一つだけ新しいコミット
Element t = std::move(*f);
elements.erase(elements.begin() + j); // 移動元を削除
elements.insert(elements.begin() + i, std::move(t)); // 現在位置に挿入
}
}
// 古いコミットを探す
size_t j = 0;
while (j < i) { // 現在位置より前を探す
Element *f = &elements[j];
int index2 = f->indexes.front();
if (index1 + 1 == index2) { // 一つだけ古いコミット
Element t = std::move(*f);
elements.erase(elements.begin() + j); // 移動元を削除
elements.insert(elements.begin() + i, std::move(t)); // 現在位置の次に挿入
index1 = index2;
f = e;
} else {
j++;
}
}
}
}
}
}
{ // 最初の線は深さを0にする
Element *e = &elements.front();
for (size_t i = 0; i < e->indexes.size(); i++) {
int index = e->indexes[i];
LogItem(index).marker_depth = 0; // マークの深さを設定
e->depth = 0; // 線の深さを設定
}
}
// 最初以外の線分の深さを決める
for (size_t i = 1; i < elements.size(); i++) { // 最初以外をループ
Element *e = &elements[i];
int depth = 1;
while (1) { // 失敗したら繰り返し
for (size_t j = 0; j < i; j++) { // 既に処理済みの線を調べる
Element const *f = &elements[j]; // 検査対象
if (e->indexes.size() == 2) { // 二つしかない場合
int from = e->indexes[0]; // 始点
int to = e->indexes[1]; // 終点
if (LogItem(from).has_child) {
for (size_t k = 0; k + 1 < f->indexes.size(); k++) { // 検査対象の全ての線分を調べる
int curr = f->indexes[k];
int next = f->indexes[k + 1];
if (from > curr && to == next) { // 決定済みの線に直結できるか判定
e->indexes.back() = from + 1; // 現在の一行下に直結する
e->depth = elements[j].depth; // 接続先の深さ
goto DONE; // 決定
}
}
}
}
if (depth == f->depth) { // 同じ深さ
if (e->indexes.back() > f->indexes.front() && e->indexes.front() < f->indexes.back()) { // 重なっている
goto FAIL; // この深さには線を置けないのでやりなおし
}
}
}
for (size_t j = 0; j < e->indexes.size(); j++) {
int index = e->indexes[j];
Git::CommitItem *item = &LogItem(index);
if (j == 0 && item->has_child) { // 最初のポイントで子がある場合
// nop
} else if ((j > 0 && j + 1 < e->indexes.size()) || item->marker_depth < 0) { // 最初と最後以外、または、未確定の場合
item->marker_depth = depth; // マークの深さを設定
}
}
e->depth = depth; // 深さを決定
goto DONE; // 決定
FAIL:;
depth++; // 一段深くして再挑戦
}
DONE:;
}
// 線情報を生成する
for (auto &e : elements) {
auto ColorNumber = [&](){ return e.depth; };
size_t count = e.indexes.size();
for (size_t i = 0; i + 1 < count; i++) {
int curr = e.indexes[i];
int next = e.indexes[i + 1];
TreeLine line(next, e.depth);
line.color_number = ColorNumber();
line.bend_early = (i + 2 < count || !LogItem(next).resolved);
if (i + 2 == count) {
int join = false;
if (count > 2) { // 直結ではない
join = true;
} else if (!LogItem(curr).has_child) { // 子がない
join = true;
int d = LogItem(curr).marker_depth; // 開始点の深さ
for (int j = curr + 1; j < next; j++) {
Git::CommitItem *item = &LogItem(j);
if (item->marker_depth == d) { // 衝突する
join = false; // 迂回する
break;
}
}
}
if (join) {
line.depth = LogItem(next).marker_depth; // 合流する先のアイテムの深さと同じにする
}
}
LogItem(curr).parent_lines.push_back(line); // 線を追加
}
}
} else {
if (LogCount == 1) { // コミットが一つだけ
LogItem(0).marker_depth = 0;
}
}
}
}
-bool BasicMainWindow::fetch(GitPtr const &g, bool prune)
+bool MainWindow::fetch(GitPtr const &g, bool prune)
{
setPtyCondition(PtyCondition::Fetch);
setPtyProcessOk(true);
g->fetch(getPtyProcess(), prune);
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
return getPtyProcessOk();
}
-bool BasicMainWindow::fetch_tags_f(GitPtr const &g)
+bool MainWindow::fetch_tags_f(GitPtr const &g)
{
setPtyCondition(PtyCondition::Fetch);
setPtyProcessOk(true);
g->fetch_tags_f(getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
return getPtyProcessOk();
}
-bool BasicMainWindow::git_callback(void *cookie, const char *ptr, int len)
+bool MainWindow::git_callback(void *cookie, const char *ptr, int len)
{
- auto *mw = (BasicMainWindow *)cookie;
+ auto *mw = (MainWindow *)cookie;
mw->emitWriteLog(QByteArray(ptr, len));
return true;
}
-QString BasicMainWindow::selectCommand_(QString const &cmdname, QStringList const &cmdfiles, QStringList const &list, QString path, std::function<void (QString const &)> const &callback)
+QString MainWindow::selectCommand_(QString const &cmdname, QStringList const &cmdfiles, QStringList const &list, QString path, std::function<void (QString const &)> const &callback)
{
QString window_title = tr("Select %1 command");
window_title = window_title.arg(cmdfiles.front());
SelectCommandDialog dlg(this, cmdname, cmdfiles, path, list);
dlg.setWindowTitle(window_title);
if (dlg.exec() == QDialog::Accepted) {
path = dlg.selectedFile();
path = misc::normalizePathSeparator(path);
QFileInfo info(path);
if (info.isExecutable()) {
callback(path);
return path;
}
}
return QString();
}
-QString BasicMainWindow::selectCommand_(QString const &cmdname, QString const &cmdfile, QStringList const &list, QString const &path, std::function<void (QString const &)> const &callback)
+QString MainWindow::selectCommand_(QString const &cmdname, QString const &cmdfile, QStringList const &list, QString const &path, std::function<void (QString const &)> const &callback)
{
QStringList cmdfiles;
cmdfiles.push_back(cmdfile);
return selectCommand_(cmdname, cmdfiles, list, path, callback);
}
-bool BasicMainWindow::checkGitCommand()
+bool MainWindow::checkGitCommand()
{
while (1) {
if (misc::isExecutable(gitCommand())) {
return true;
}
if (selectGitCommand(true).isEmpty()) {
close();
return false;
}
}
}
-bool BasicMainWindow::checkFileCommand()
+bool MainWindow::checkFileCommand()
{
while (1) {
if (misc::isExecutable(global->file_command)) {
return true;
}
if (selectFileCommand(true).isEmpty()) {
close();
return false;
}
}
}
-bool BasicMainWindow::checkExecutable(QString const &path)
+bool MainWindow::checkExecutable(QString const &path)
{
if (QFileInfo(path).isExecutable()) {
return true;
}
QString text = "The specified program '%1' is not executable.\n";
text = text.arg(path);
writeLog(text);
return false;
}
-void BasicMainWindow::internalSetCommand(QString const &path, bool save, QString const &name, QString *out)
+void MainWindow::internalSaveCommandPath(QString const &path, bool save, QString const &name)
{
- if (!path.isEmpty() && checkExecutable(path)) {
+ if (checkExecutable(path)) {
if (save) {
MySettings s;
s.beginGroup("Global");
s.setValue(name, path);
s.endGroup();
}
- *out = path;
- } else {
- *out = QString();
}
}
-QStringList BasicMainWindow::whichCommand_(QString const &cmdfile1, QString const &cmdfile2)
+QStringList MainWindow::whichCommand_(QString const &cmdfile1, QString const &cmdfile2)
{
QStringList list;
if (!cmdfile1.isEmpty()){
std::vector<std::string> vec;
FileUtil::qwhich(cmdfile1.toStdString(), &vec);
for (std::string const &s : vec) {
list.push_back(QString::fromStdString(s));
}
}
if (!cmdfile2.isEmpty()){
std::vector<std::string> vec;
FileUtil::qwhich(cmdfile2.toStdString(), &vec);
for (std::string const &s : vec) {
list.push_back(QString::fromStdString(s));
}
}
return list;
}
-
-
-
-
-void BasicMainWindow::setWindowTitle_(Git::User const &user)
+void MainWindow::setWindowTitle_(Git::User const &user)
{
if (user.name.isEmpty() && user.email.isEmpty()) {
setWindowTitle(qApp->applicationName());
} else {
setWindowTitle(QString("%1 : %2 <%3>")
.arg(qApp->applicationName())
.arg(user.name)
.arg(user.email)
);
}
}
-bool BasicMainWindow::execSetGlobalUserDialog()
+bool MainWindow::execSetGlobalUserDialog()
{
SetGlobalUserDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
GitPtr g = git();
Git::User user = dlg.user();
g->setUser(user, true);
updateWindowTitle(g);
return true;
}
return false;
}
-bool BasicMainWindow::saveByteArrayAs(const QByteArray &ba, QString const &dstpath)
+bool MainWindow::saveByteArrayAs(const QByteArray &ba, QString const &dstpath)
{
QFile file(dstpath);
if (file.open(QFile::WriteOnly)) {
file.write(ba);
file.close();
return true;
}
QString msg = "Failed to open the file '%1' for write";
msg = msg.arg(dstpath);
qDebug() << msg;
return false;
}
-bool BasicMainWindow::saveFileAs(QString const &srcpath, QString const &dstpath)
+bool MainWindow::saveFileAs(QString const &srcpath, QString const &dstpath)
{
QFile f(srcpath);
if (f.open(QFile::ReadOnly)) {
QByteArray ba = f.readAll();
if (saveByteArrayAs(ba, dstpath)) {
return true;
}
} else {
QString msg = "Failed to open the file '%1' for read";
msg = msg.arg(srcpath);
qDebug() << msg;
}
return false;
}
-bool BasicMainWindow::saveBlobAs(QString const &id, QString const &dstpath)
+bool MainWindow::saveBlobAs(RepositoryWrapperFrame *frame, QString const &id, QString const &dstpath)
{
- Git::Object obj = cat_file(id);
+ Git::Object obj = cat_file(frame, id);
if (!obj.content.isEmpty()) {
if (saveByteArrayAs(obj.content, dstpath)) {
return true;
}
} else {
QString msg = "Failed to get the content of the object '%1'";
msg = msg.arg(id);
qDebug() << msg;
}
return false;
}
-void BasicMainWindow::revertAllFiles()
+void MainWindow::revertAllFiles()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QString cmd = "git reset --hard HEAD";
if (askAreYouSureYouWantToRun(tr("Revert all files"), "> " + cmd)) {
g->resetAllFiles();
openRepository(true);
}
}
-void BasicMainWindow::deleteTags(Git::CommitItem const &commit)
+void MainWindow::deleteTags(RepositoryWrapperFrame *frame, Git::CommitItem const &commit)
{
- auto it = ptrTagMap()->find(commit.commit_id);
- if (it != ptrTagMap()->end()) {
+ auto it = ptrTagMap(frame)->find(commit.commit_id);
+ if (it != ptrTagMap(frame)->end()) {
QStringList names;
QList<Git::Tag> const &tags = it->second;
for (Git::Tag const &tag : tags) {
names.push_back(tag.name);
}
- deleteTags(names);
+ deleteTags(frame, names);
}
}
-bool BasicMainWindow::isAvatarEnabled() const
+bool MainWindow::isAvatarEnabled() const
{
return appsettings()->get_committer_icon;
}
-bool BasicMainWindow::isGitHub() const
+bool MainWindow::isGitHub() const
{
- return m->server_type == ServerType::GitHub;
+ return m1->server_type == ServerType::GitHub;
}
-QStringList BasicMainWindow::remotes() const
+QStringList MainWindow::remotes() const
{
- return m->remotes;
+ return m1->remotes;
}
-QList<Git::Branch> BasicMainWindow::findBranch(QString const &id)
+QList<Git::Branch> MainWindow::findBranch(RepositoryWrapperFrame *frame, QString const &id)
{
- auto it = branchMapRef().find(id);
- if (it != branchMapRef().end()) {
+ auto it = branchMapRef(frame).find(id);
+ if (it != branchMapRef(frame).end()) {
return it->second;
}
return QList<Git::Branch>();
}
-QString BasicMainWindow::saveAsTemp(QString const &id)
+QString MainWindow::saveAsTemp(RepositoryWrapperFrame *frame, QString const &id)
{
QString path = newTempFilePath();
- saveAs(id, path);
+ saveAs(frame, id, path);
return path;
}
-QString BasicMainWindow::tempfileHeader() const
+QString MainWindow::tempfileHeader() const
{
QString name = "jp_soramimi_Guitar_%1_";
return name.arg(QApplication::applicationPid());
}
-void BasicMainWindow::deleteTempFiles()
+void MainWindow::deleteTempFiles()
{
QString dir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QString name = tempfileHeader();
QDirIterator it(dir, { name + "*" });
while (it.hasNext()) {
QString path = it.next();
QFile::remove(path);
}
}
-QString BasicMainWindow::getCommitIdFromTag(QString const &tag)
+QString MainWindow::getCommitIdFromTag(RepositoryWrapperFrame *frame, QString const &tag)
{
- return getObjCache()->getCommitIdFromTag(tag);
+ return getObjCache(frame)->getCommitIdFromTag(tag);
}
-bool BasicMainWindow::isValidRemoteURL(QString const &url)
+bool MainWindow::isValidRemoteURL(QString const &url, QString const &sshkey)
{
if (url.indexOf('\"') >= 0) {
return false;
}
stopPtyProcess();
- GitPtr g = git(QString());
+ GitPtr g = git({}, {}, sshkey);
QString cmd = "ls-remote \"%1\" HEAD";
cmd = cmd.arg(url);
bool f = g->git(cmd, false, false, getPtyProcess());
{
QTime time;
time.start();
while (!getPtyProcess()->wait(10)) {
if (time.elapsed() > 10000) {
f = false;
break;
}
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
stopPtyProcess();
}
if (f) {
f = (getPtyProcess()->getExitCode() == 0);
}
QString line;
{
std::vector<char> v;
getPtyProcess()->readResult(&v);
if (!v.empty()) {
line = QString::fromUtf8(&v[0], v.size()).trimmed();
}
}
if (f) {
qDebug() << "This is a valid repository.";
int i = -1;
for (int j = 0; j < line.size(); j++) {
ushort c = line.utf16()[j];
if (QChar(c).isSpace()) {
i = j;
break;
}
}
QString head;
if (i == GIT_ID_LENGTH) {
QString id = line.mid(0, i);
QString name = line.mid(i + 1).trimmed();
qDebug() << id << name;
if (name == "HEAD" && Git::isValidID(id)) {
head = id;
}
}
if (head.isEmpty()) {
qDebug() << "But HEAD not found";
}
return true;
}
qDebug() << "This is not a repository.";
return false;
}
-QString BasicMainWindow::getObjectID(QListWidgetItem *item)
-{
- int i = indexOfDiff(item);
- if (i >= 0 && i < diffResult()->size()) {
- Git::Diff const &diff = diffResult()->at(i);
- return diff.blob.a_id;
- }
- return QString();
-}
-void BasicMainWindow::addWorkingCopyDir(QString dir, QString name, bool open)
+
+void MainWindow::addWorkingCopyDir(QString dir, QString name, bool open)
{
if (dir.endsWith(".git")) {
int i = dir.size();
if (i > 4) {
ushort c = dir.utf16()[i - 5];
if (c == '/' || c == '\\') {
dir = dir.mid(0, i - 5);
}
}
}
if (!Git::isValidWorkingCopy(dir)) {
if (QFileInfo(dir).isDir()) {
QString text;
text += tr("The folder is not a valid git repository.") + '\n';
text += '\n';
text += dir + '\n';
text += '\n';
text += tr("Do you want to initialize it as a git repository ?") + '\n';
int r = QMessageBox::information(this, tr("Initialize Repository") , text, QMessageBox::Yes, QMessageBox::No);
if (r == QMessageBox::Yes) {
createRepository(dir);
}
}
return;
}
if (name.isEmpty()) {
name = makeRepositoryName(dir);
}
RepositoryItem item;
item.local_dir = dir;
item.name = name;
saveRepositoryBookmark(item);
if (open) {
setCurrentRepository(item, true);
- GitPtr g = git(item.local_dir);
+ GitPtr g = git(item.local_dir, {}, {});
openRepository_(g);
}
}
-QString BasicMainWindow::makeRepositoryName(QString const &loc)
+QString MainWindow::makeRepositoryName(QString const &loc)
{
int i = loc.lastIndexOf('/');
int j = loc.lastIndexOf('\\');
if (i < j) i = j;
if (i >= 0) {
i++;
j = loc.size();
if (loc.endsWith(".git")) {
j -= 4;
}
return loc.mid(i, j - i);
}
return QString();
}
-void BasicMainWindow::clearAuthentication()
+void MainWindow::clearAuthentication()
{
clearSshAuthentication();
- m->http_uid.clear();
- m->http_pwd.clear();
+ m1->http_uid.clear();
+ m1->http_pwd.clear();
}
-void BasicMainWindow::onAvatarUpdated()
-{
- updateCommitTableLater();
-}
-QStringList BasicMainWindow::findGitObject(const QString &id) const
+
+QStringList MainWindow::findGitObject(const QString &id) const
{
QStringList list;
std::set<QString> set;
if (Git::isValidID(id)) {
{
QString a = id.mid(0, 2);
QString b = id.mid(2);
- QString dir = m->current_repo.local_dir / ".git/objects" / a;
+ QString dir = m1->current_repo.local_dir / ".git/objects" / a;
QDirIterator it(dir);
while (it.hasNext()) {
it.next();
QFileInfo info = it.fileInfo();
if (info.isFile()) {
QString c = info.fileName();
if (c.startsWith(b)) {
set.insert(set.end(), a + c);
}
}
}
}
{
- QString dir = m->current_repo.local_dir / ".git/objects/pack";
+ QString dir = m1->current_repo.local_dir / ".git/objects/pack";
QDirIterator it(dir);
while (it.hasNext()) {
it.next();
QFileInfo info = it.fileInfo();
if (info.isFile() && info.fileName().startsWith("pack-") && info.fileName().endsWith(".idx")) {
GitPackIdxV2 idx;
idx.parse(info.absoluteFilePath());
idx.each([&](GitPackIdxItem const *item){
if (item->id.startsWith(id)) {
set.insert(item->id);
}
return true;
});
}
}
}
for (QString const &s : set) {
list.push_back(s);
}
}
return list;
}
-void BasicMainWindow::writeLog(const char *ptr, int len)
+void MainWindow::writeLog(const char *ptr, int len)
{
internalWriteLog(ptr, len);
}
-void BasicMainWindow::writeLog(const QString &str)
+void MainWindow::writeLog(const QString &str)
{
std::string s = str.toStdString();
writeLog(s.c_str(), s.size());
}
-void BasicMainWindow::emitWriteLog(QByteArray const &ba)
+void MainWindow::emitWriteLog(QByteArray const &ba)
{
emit signalWriteLog(ba);
}
-void BasicMainWindow::writeLog_(QByteArray ba)
+void MainWindow::writeLog_(QByteArray ba)
{
if (!ba.isEmpty()) {
writeLog(ba.data(), ba.size());
}
}
-void BasicMainWindow::queryRemotes(GitPtr const &g)
+void MainWindow::queryRemotes(GitPtr const &g)
{
if (!g) return;
- m->remotes = g->getRemotes();
- std::sort(m->remotes.begin(), m->remotes.end());
+ m1->remotes = g->getRemotes();
+ std::sort(m1->remotes.begin(), m1->remotes.end());
}
-void BasicMainWindow::msgNoRepositorySelected()
+void MainWindow::msgNoRepositorySelected()
{
QMessageBox::warning(this, qApp->applicationName(), tr("No repository selected"));
}
-bool BasicMainWindow::runOnRepositoryDir(std::function<void (QString)> const &callback, RepositoryItem const *repo)
+bool MainWindow::runOnRepositoryDir(std::function<void (QString)> const &callback, RepositoryItem const *repo)
{
if (!repo) {
- repo = &m->current_repo;
+ repo = &m1->current_repo;
}
QString dir = repo->local_dir;
dir.replace('\\', '/');
if (QFileInfo(dir).isDir()) {
callback(dir);
return true;
}
msgNoRepositorySelected();
return false;
}
-void BasicMainWindow::clearSshAuthentication()
+void MainWindow::clearSshAuthentication()
{
- m->ssh_passphrase_user.clear();
- m->ssh_passphrase_pass.clear();
+ m1->ssh_passphrase_user.clear();
+ m1->ssh_passphrase_pass.clear();
}
-QString BasicMainWindow::getFilePath(QListWidgetItem *item)
-{
- if (!item) return QString();
- return item->data(FilePathRole).toString();
-}
-bool BasicMainWindow::isGroupItem(QTreeWidgetItem *item)
+
+bool MainWindow::isGroupItem(QTreeWidgetItem *item)
{
if (item) {
int index = item->data(0, IndexRole).toInt();
if (index == GroupItem) {
return true;
}
}
return false;
}
-int BasicMainWindow::indexOfLog(QListWidgetItem *item)
+int MainWindow::indexOfLog(QListWidgetItem *item)
{
if (!item) return -1;
return item->data(IndexRole).toInt();
}
-int BasicMainWindow::indexOfDiff(QListWidgetItem *item)
+int MainWindow::indexOfDiff(QListWidgetItem *item)
{
if (!item) return -1;
return item->data(DiffIndexRole).toInt();
}
-int BasicMainWindow::getHunkIndex(QListWidgetItem *item)
-{
- if (!item) return -1;
- return item->data(HunkIndexRole).toInt();
-}
+//int MainWindow::getHunkIndex(QListWidgetItem *item)
+//{
+// if (!item) return -1;
+// return item->data(HunkIndexRole).toInt();
+//}
-void BasicMainWindow::initNetworking()
+void MainWindow::initNetworking()
{
std::string http_proxy;
std::string https_proxy;
if (appsettings()->proxy_type == "auto") {
#ifdef Q_OS_WIN
http_proxy = misc::makeProxyServerURL(getWin32HttpProxy().toStdString());
#else
auto getienv = [](std::string const &name)->char const *{
char **p = environ;
while (*p) {
if (strncasecmp(*p, name.c_str(), name.size()) == 0) {
char const *e = *p + name.size();
if (*e == '=') {
return e + 1;
}
}
p++;
}
return nullptr;
};
char const *p;
p = getienv("http_proxy");
if (p) {
http_proxy = misc::makeProxyServerURL(std::string(p));
}
p = getienv("https_proxy");
if (p) {
https_proxy = misc::makeProxyServerURL(std::string(p));
}
#endif
} else if (appsettings()->proxy_type == "manual") {
http_proxy = appsettings()->proxy_server.toStdString();
}
webContext()->set_http_proxy(http_proxy);
webContext()->set_https_proxy(https_proxy);
}
-QString BasicMainWindow::abbrevCommitID(Git::CommitItem const &commit)
+QString MainWindow::abbrevCommitID(Git::CommitItem const &commit)
{
return commit.commit_id.mid(0, 7);
}
-bool BasicMainWindow::saveRepositoryBookmarks() const
+bool MainWindow::saveRepositoryBookmarks() const
{
QString path = getBookmarksFilePath();
return RepositoryBookmark::save(path, &getRepos());
}
-QString BasicMainWindow::getBookmarksFilePath() const
+QString MainWindow::getBookmarksFilePath() const
{
return global->app_config_dir / "bookmarks.xml";
}
-void BasicMainWindow::stopPtyProcess()
+void MainWindow::stopPtyProcess()
{
getPtyProcess()->stop();
- QDir::setCurrent(m->starting_dir);
+ QDir::setCurrent(m1->starting_dir);
}
-void BasicMainWindow::abortPtyProcess()
+void MainWindow::abortPtyProcess()
{
stopPtyProcess();
setPtyProcessOk(false);
setInteractionCanceled(true);
}
-bool BasicMainWindow::execWelcomeWizardDialog()
+void MainWindow::saveApplicationSettings()
+{
+ SettingsDialog::saveSettings(appsettings());
+// SettingsDialog::saveSettings(&m1->appsettings);
+}
+
+void MainWindow::loadApplicationSettings()
+{
+ SettingsDialog::loadSettings(appsettings());
+}
+
+bool MainWindow::execWelcomeWizardDialog()
{
WelcomeWizardDialog dlg(this);
dlg.set_git_command_path(appsettings()->git_command);
dlg.set_file_command_path(appsettings()->file_command);
dlg.set_default_working_folder(appsettings()->default_working_dir);
if (misc::isExecutable(appsettings()->git_command)) {
gitCommand() = appsettings()->git_command;
- Git g(m->gcx, QString());
+ Git g(m1->gcx, {}, {}, {});
Git::User user = g.getUser(Git::Source::Global);
dlg.set_user_name(user.name);
dlg.set_user_email(user.email);
}
if (dlg.exec() == QDialog::Accepted) {
- appsettings()->git_command = gitCommand() = dlg.git_command_path();
- appsettings()->file_command = global->file_command = dlg.file_command_path();
+ setGitCommand(dlg.git_command_path(), false);
+ setFileCommand(dlg.file_command_path(), false);
+// appsettings()->git_command = dlg.git_command_path();
+ appsettings()->file_command = dlg.file_command_path();
+// m->gcx.git_command = appsettings()->git_command;
+ global->file_command = appsettings()->file_command;
appsettings()->default_working_dir = dlg.default_working_folder();
- SettingsDialog::saveSettings(&m->appsettings);
+ saveApplicationSettings();
if (misc::isExecutable(appsettings()->git_command)) {
GitPtr g = git();
Git::User user;
user.name = dlg.user_name();
user.email = dlg.user_email();
g->setUser(user, true);
}
return true;
}
return false;
}
-void BasicMainWindow::execRepositoryPropertyDialog(QString workdir, bool open_repository_menu)
+void MainWindow::execRepositoryPropertyDialog(RepositoryItem const &repo, bool open_repository_menu)
{
+ QString workdir = repo.local_dir;
+
if (workdir.isEmpty()) {
workdir = currentWorkingCopyDir();
}
- QString name;
- RepositoryItem const *repo = findRegisteredRepository(&workdir);
- if (repo) {
- name = repo->name;
- } else {
- QMessageBox::warning(this, tr("Repository Property"), tr("Not a valid git repository") + "\n\n" + workdir);
- return;
- }
+ QString name = repo.name;
if (name.isEmpty()) {
name = makeRepositoryName(workdir);
}
- GitPtr g = git(workdir);
- RepositoryPropertyDialog dlg(this, g, *repo, open_repository_menu);
+ GitPtr g = git(workdir, {}, repo.ssh_key);
+ RepositoryPropertyDialog dlg(this, &m1->gcx, g, repo, open_repository_menu);
dlg.exec();
if (dlg.isRemoteChanged()) {
emit remoteInfoChanged();
}
+ if (dlg.isNameChanged()) {
+ this->changeRepositoryBookmarkName(repo, dlg.getName());
+ }
}
-void BasicMainWindow::execSetUserDialog(Git::User const &global_user, Git::User const &repo_user, QString const &reponame)
+void MainWindow::execSetUserDialog(Git::User const &global_user, Git::User const &repo_user, QString const &reponame)
{
SetUserDialog dlg(this, global_user, repo_user, reponame);
if (dlg.exec() == QDialog::Accepted) {
GitPtr g = git();
Git::User user = dlg.user();
if (dlg.isGlobalChecked()) {
g->setUser(user, true);
}
if (dlg.isRepositoryChecked()) {
g->setUser(user, false);
}
updateWindowTitle(g);
}
}
-void BasicMainWindow::setGitCommand(QString const &path, bool save)
+QString MainWindow::executableOrEmpty(QString const &path)
+{
+ return checkExecutable(path) ? path : QString();
+}
+
+void MainWindow::setGitCommand(QString path, bool save)
{
- internalSetCommand(path, save, "GitCommand", &m->gcx.git_command);
+ appsettings()->git_command = m1->gcx.git_command = executableOrEmpty(path);
+
+ internalSaveCommandPath(path, save, "GitCommand");
}
-void BasicMainWindow::setFileCommand(QString const &path, bool save)
+void MainWindow::setFileCommand(QString path, bool save)
{
- internalSetCommand(path, save, "FileCommand", &global->file_command);
+ appsettings()->file_command = global->file_command = executableOrEmpty(path);
+
+ internalSaveCommandPath(path, save, "FileCommand");
}
-void BasicMainWindow::setGpgCommand(QString const &path, bool save)
+void MainWindow::setGpgCommand(QString path, bool save)
{
- internalSetCommand(path, save, "GpgCommand", &global->gpg_command);
+ appsettings()->gpg_command = global->gpg_command = executableOrEmpty(path);
+
+ internalSaveCommandPath(path, save, "GpgCommand");
if (!global->gpg_command.isEmpty()) {
GitPtr g = git();
g->configGpgProgram(global->gpg_command, true);
}
}
-void BasicMainWindow::logGitVersion()
+void MainWindow::setSshCommand(QString path, bool save)
+{
+ appsettings()->ssh_command = executableOrEmpty(path);
+
+ internalSaveCommandPath(path, save, "SshCommand");
+}
+
+void MainWindow::logGitVersion()
{
GitPtr g = git();
QString s = g->version();
if (!s.isEmpty()) {
s += '\n';
writeLog(s);
}
}
-void BasicMainWindow::setUnknownRepositoryInfo()
+void MainWindow::setUnknownRepositoryInfo()
{
setRepositoryInfo("---", "");
- Git g(m->gcx, QString());
+ Git g(m1->gcx, {}, {}, {});
Git::User user = g.getUser(Git::Source::Global);
setWindowTitle_(user);
}
-void BasicMainWindow::internalClearRepositoryInfo()
+void MainWindow::internalClearRepositoryInfo()
{
setHeadId(QString());
setCurrentBranch(Git::Branch());
setServerType(ServerType::Standard);
- m->github = GitHubRepositoryInfo();
+ m1->github = GitHubRepositoryInfo();
}
-void BasicMainWindow::checkUser()
+void MainWindow::checkUser()
{
- Git g(m->gcx, QString());
+ Git g(m1->gcx, {}, {}, {});
while (1) {
Git::User user = g.getUser(Git::Source::Global);
if (!user.name.isEmpty() && !user.email.isEmpty()) {
return; // ok
}
if (!execSetGlobalUserDialog()) {
return;
}
}
}
-void BasicMainWindow::openRepository(bool validate, bool waitcursor, bool keep_selection)
+void MainWindow::openRepository(bool validate, bool waitcursor, bool keep_selection)
{
if (validate) {
QString dir = currentWorkingCopyDir();
if (!QFileInfo(dir).isDir()) {
int r = QMessageBox::warning(this, tr("Open Repository"), dir + "\n\n" + tr("No such folder") + "\n\n" + tr("Remove from bookmark ?"), QMessageBox::Ok, QMessageBox::Cancel);
if (r == QMessageBox::Ok) {
removeSelectedRepositoryFromBookmark(false);
}
return;
}
if (!Git::isValidWorkingCopy(dir)) {
QMessageBox::warning(this, tr("Open Repository"), tr("Not a valid git repository") + "\n\n" + dir);
return;
}
}
if (waitcursor) {
OverrideWaitCursor;
openRepository(false, false, keep_selection);
return;
}
GitPtr g = git(); // ポインタの有効性チェックはしない(nullptrでも続行)
openRepository_(g, keep_selection);
}
-void BasicMainWindow::updateRepository()
+void MainWindow::updateRepository()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
OverrideWaitCursor;
openRepository_(g);
}
-void BasicMainWindow::reopenRepository(bool log, std::function<void(GitPtr const &)> const &callback)
+void MainWindow::reopenRepository(bool log, std::function<void(GitPtr const &)> const &callback)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
- setRemoteMonitoringEnabled(false);
-
OverrideWaitCursor;
if (log) {
setLogEnabled(g, true);
AsyncExecGitThread_ th(g, callback);
th.start();
while (1) {
if (th.wait(1)) break;
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
setLogEnabled(g, false);
} else {
callback(g);
}
openRepository_(g);
-
- setRemoteMonitoringEnabled(true);
}
-void BasicMainWindow::openSelectedRepository()
+void MainWindow::openSelectedRepository()
{
RepositoryItem const *repo = selectedRepositoryItem();
if (repo) {
setCurrentRepository(*repo, true);
openRepository(true);
}
}
-//void BasicMainWindow::checkRemoteUpdate()
-//{
-// if (getPtyProcess()->isRunning()) return;
-
-// emit signalCheckRemoteUpdate();
-//}
-
-bool BasicMainWindow::isThereUncommitedChanges() const
+bool MainWindow::isThereUncommitedChanges() const
{
- return m->uncommited_changes;
+ return m1->uncommited_changes;
}
-bool BasicMainWindow::makeDiff(QString id, QList<Git::Diff> *out)
+/**
+ * @brief コミットに対応する差分情報を作成
+ * @param id コミットID
+ * @param ok
+ * @return
+ */
+QList<Git::Diff> MainWindow::makeDiffs(RepositoryWrapperFrame *frame, QString id, bool *ok)
{
+ QList<Git::Diff> out;
+
GitPtr g = git();
- if (!isValidWorkingCopy(g)) return false;
+ if (!isValidWorkingCopy(g)) {
+ if (ok) *ok = false;
+ return {};
+ }
Git::FileStatusList list = g->status_s();
setUncommitedChanges(!list.empty());
if (id.isEmpty() && !isThereUncommitedChanges()) {
- id = getObjCache()->revParse("HEAD");
+ id = getObjCache(frame)->revParse("HEAD");
}
+ QList<Git::SubmoduleItem> mods;
+ updateSubmodules(g, id, &mods);
+ setSubmodules(mods);
+
bool uncommited = (id.isEmpty() && isThereUncommitedChanges());
- GitDiff dm(getObjCache());
+ GitDiff dm(getObjCache(frame));
if (uncommited) {
- dm.diff_uncommited(out);
+ dm.diff_uncommited(submodules(), &out);
} else {
- dm.diff(id, out);
+ dm.diff(id, submodules(), &out);
}
- return true; // success
-}
-
-void BasicMainWindow::addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (QString const &, QString, int)> &add_item)
-{
- for (int idiff = 0; idiff < diff_list->size(); idiff++) {
- Git::Diff const &diff = diff_list->at(idiff);
- QString header;
-
- switch (diff.type) {
- case Git::Diff::Type::Modify: header = "(chg) "; break;
- case Git::Diff::Type::Copy: header = "(cpy) "; break;
- case Git::Diff::Type::Rename: header = "(ren) "; break;
- case Git::Diff::Type::Create: header = "(add) "; break;
- case Git::Diff::Type::Delete: header = "(del) "; break;
- case Git::Diff::Type::ChType: header = "(chg) "; break;
- case Git::Diff::Type::Unmerged: header = "(unmerged) "; break;
- }
-
- add_item(diff.path, header, idiff);
- }
+ if (ok) *ok = true;
+ return out;
}
-Git::CommitItemList BasicMainWindow::retrieveCommitLog(GitPtr const &g)
+/**
+ * @brief コミットログを取得する
+ * @param g
+ * @return
+ */
+Git::CommitItemList MainWindow::retrieveCommitLog(GitPtr const &g)
{
Git::CommitItemList list = g->log(limitLogCount());
// 親子関係を調べて、順番が狂っていたら、修正する。
std::set<QString> set;
const size_t count = list.size();
size_t limit = count;
size_t i = 0;
while (i < count) {
size_t newpos = -1;
for (QString const &parent : list[i].parent_ids) {
if (set.find(parent) != set.end()) {
for (size_t j = 0; j < i; j++) {
if (parent == list[j].commit_id) {
if (newpos == (size_t)-1 || j < newpos) {
newpos = j;
}
qDebug() << "fix commit order" << list[i].commit_id;
break;
}
}
}
}
set.insert(set.end(), list[i].commit_id);
if (newpos != (size_t)-1) {
if (limit == 0) break; // まず無いと思うが、もし、無限ループに陥ったら
Git::CommitItem t = list[i];
t.strange_date = true;
list.erase(list.begin() + i);
list.insert(list.begin() + newpos, t);
i = newpos;
limit--;
}
i++;
}
return list;
}
-void BasicMainWindow::queryBranches(GitPtr const &g)
+void MainWindow::queryBranches(RepositoryWrapperFrame *frame, GitPtr const &g)
{
Q_ASSERT(g);
- m->branch_map.clear();
+// m1->branch_map.clear();
+ frame->branch_map.clear();
QList<Git::Branch> branches = g->branches();
for (Git::Branch const &b : branches) {
if (b.isCurrent()) {
setCurrentBranch(b);
}
- branchMapRef()[b.id].append(b);
+ branchMapRef(frame)[b.id].append(b);
}
}
-std::map<QString, QList<Git::Branch>> &BasicMainWindow::branchMapRef()
+std::map<QString, QList<Git::Branch>> &MainWindow::branchMapRef(RepositoryWrapperFrame *frame)
{
- return m->branch_map;
+// return m1->branch_map;
+ return frame->branch_map;
}
-void BasicMainWindow::updateCommitTableLater()
-{
- *ptrUpdateCommitTableCounter() = 200;
-}
-
-void BasicMainWindow::updateRemoteInfo()
+void MainWindow::updateRemoteInfo()
{
queryRemotes(git());
- m->current_remote_name = QString();
+ m1->current_remote_name = QString();
{
Git::Branch const &r = currentBranch();
- m->current_remote_name = r.remote;
+ m1->current_remote_name = r.remote;
}
- if (m->current_remote_name.isEmpty()) {
- if (m->remotes.size() == 1) {
- m->current_remote_name = m->remotes[0];
+ if (m1->current_remote_name.isEmpty()) {
+ if (m1->remotes.size() == 1) {
+ m1->current_remote_name = m1->remotes[0];
}
}
emit remoteInfoChanged();
}
-void BasicMainWindow::updateWindowTitle(GitPtr const &g)
+void MainWindow::updateWindowTitle(GitPtr const &g)
{
if (isValidWorkingCopy(g)) {
Git::User user = g->getUser(Git::Source::Default);
setWindowTitle_(user);
} else {
setUnknownRepositoryInfo();
}
}
-QString BasicMainWindow::makeCommitInfoText(int row, QList<BasicMainWindow::Label> *label_list)
+QString MainWindow::makeCommitInfoText(RepositoryWrapperFrame *frame, int row, QList<BranchLabel> *label_list)
{
QString message_ex;
- Git::CommitItem const *commit = &getLogs()[row];
+ Git::CommitItem const *commit = &getLogs(frame)[row];
{ // branch
if (label_list) {
if (commit->commit_id == getHeadId()) {
- Label label(Label::Head);
+ BranchLabel label(BranchLabel::Head);
label.text = "HEAD";
label_list->push_back(label);
}
}
- QList<Git::Branch> list = findBranch(commit->commit_id);
+ QList<Git::Branch> list = findBranch(frame, commit->commit_id);
for (Git::Branch const &b : list) {
if (b.flags & Git::Branch::HeadDetachedAt) continue;
if (b.flags & Git::Branch::HeadDetachedFrom) continue;
- Label label(Label::LocalBranch);
+ BranchLabel label(BranchLabel::LocalBranch);
label.text = b.name;
if (!b.remote.isEmpty()) {
- label.kind = Label::RemoteBranch;
+ label.kind = BranchLabel::RemoteBranch;
label.text = "remotes" / b.remote / label.text;
}
if (b.ahead > 0) {
label.info += tr(", %1 ahead").arg(b.ahead);
}
if (b.behind > 0) {
label.info += tr(", %1 behind").arg(b.behind);
}
message_ex += " {" + label.text + label.info + '}';
if (label_list) label_list->push_back(label);
}
}
{ // tag
- QList<Git::Tag> list = findTag(commit->commit_id);
+ QList<Git::Tag> list = findTag(frame, commit->commit_id);
for (Git::Tag const &t : list) {
- Label label(Label::Tag);
+ BranchLabel label(BranchLabel::Tag);
label.text = t.name;
message_ex += QString(" {#%1}").arg(label.text);
if (label_list) label_list->push_back(label);
}
}
return message_ex;
}
-void BasicMainWindow::removeRepositoryFromBookmark(int index, bool ask)
+void MainWindow::removeRepositoryFromBookmark(int index, bool ask)
{
if (ask) {
int r = QMessageBox::warning(this, tr("Confirm Remove"), tr("Are you sure you want to remove the repository from bookmarks ?") + '\n' + tr("(Files will NOT be deleted)"), QMessageBox::Ok, QMessageBox::Cancel);
if (r != QMessageBox::Ok) return;
}
auto *repos = getReposPtr();
if (index >= 0 && index < repos->size()) {
repos->erase(repos->begin() + index);
saveRepositoryBookmarks();
updateRepositoriesList();
}
}
-void BasicMainWindow::clone(QString url, QString dir)
+void MainWindow::clone(QString url, QString dir)
{
if (!isOnlineMode()) return;
- while (1) {
+ if (dir.isEmpty()) {
dir = defaultWorkingDir();
- CloneDialog dlg(this, url, dir);
+ }
+
+ while (1) {
+ QString ssh_key;
+ CloneDialog dlg(this, url, dir, &m1->gcx);
if (dlg.exec() != QDialog::Accepted) {
return;
}
-
+ const CloneDialog::Action action = dlg.action();
url = dlg.url();
dir = dlg.dir();
+ ssh_key = dlg.overridedSshKey();
+
+ RepositoryItem repos_item_data;
+ repos_item_data.local_dir = dir;
+ repos_item_data.local_dir.replace('\\', '/');
+ repos_item_data.name = makeRepositoryName(dir);
+ repos_item_data.ssh_key = ssh_key;
+
+ // クローン先ディレクトリを求める
+
+ Git::CloneData clone_data = Git::preclone(url, dir);
- if (dlg.action() == CloneDialog::Action::Clone) {
+ if (action == CloneDialog::Action::Clone) {
// 既存チェック
QFileInfo info(dir);
if (info.isFile()) {
QString msg = dir + "\n\n" + tr("A file with same name already exists");
QMessageBox::warning(this, tr("Clone"), msg);
continue;
}
if (info.isDir()) {
QString msg = dir + "\n\n" + tr("A folder with same name already exists");
QMessageBox::warning(this, tr("Clone"), msg);
continue;
}
- // クローン先ディレクトリを求める
-
- Git::CloneData clone_data = Git::preclone(url, dir);
-
// クローン先ディレクトリの存在チェック
QString basedir = misc::normalizePathSeparator(clone_data.basedir);
if (!QFileInfo(basedir).isDir()) {
int i = basedir.indexOf('/');
int j = basedir.indexOf('\\');
if (i < j) i = j;
if (i < 0) {
QString msg = basedir + "\n\n" + tr("Invalid folder");
QMessageBox::warning(this, tr("Clone"), msg);
continue;
}
QString msg = basedir + "\n\n" + tr("No such folder. Create it now ?");
if (QMessageBox::warning(this, tr("Clone"), msg, QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) {
continue;
}
// ディレクトリを作成
QString base = basedir.mid(0, i + 1);
QString sub = basedir.mid(i + 1);
QDir(base).mkpath(sub);
}
- RepositoryItem data;
- data.local_dir = dir;
- data.local_dir.replace('\\', '/');
- data.name = makeRepositoryName(dir);
-
- GitPtr g = git(QString());
- setPtyUserData(QVariant::fromValue<RepositoryItem>(data));
+ GitPtr g = git({}, {}, repos_item_data.ssh_key);
+ setPtyUserData(QVariant::fromValue<RepositoryItem>(repos_item_data));
setPtyCondition(PtyCondition::Clone);
setPtyProcessOk(true);
g->clone(clone_data, getPtyProcess());
- } else if (dlg.action() == CloneDialog::Action::AddExisting) {
+ } else if (action == CloneDialog::Action::AddExisting) {
addWorkingCopyDir(dir, true);
}
return; // done
}
}
-void BasicMainWindow::checkout()
+void MainWindow::submodule_add(QString url, QString local_dir)
{
- checkout(this, selectedCommitItem());
+ if (!isOnlineMode()) return;
+ if (local_dir.isEmpty()) return;
+
+ QString dir = local_dir;
+
+ while (1) {
+ SubmoduleAddDialog dlg(this, url, dir, &m1->gcx);
+ if (dlg.exec() != QDialog::Accepted) {
+ return;
+ }
+ url = dlg.url();
+ dir = dlg.dir();
+ const QString ssh_key = dlg.overridedSshKey();
+
+ RepositoryItem repos_item_data;
+ repos_item_data.local_dir = dir;
+ repos_item_data.local_dir.replace('\\', '/');
+ repos_item_data.name = makeRepositoryName(dir);
+ repos_item_data.ssh_key = ssh_key;
+
+ Git::CloneData data = Git::preclone(url, dir);
+ bool force = dlg.isForce();
+
+ GitPtr g = git(local_dir, {}, repos_item_data.ssh_key);
+
+ auto callback = [&](GitPtr const &g){
+ g->submodule_add(data, force, getPtyProcess());
+ };
+
+ {
+ OverrideWaitCursor;
+ {
+ setLogEnabled(g, true);
+ AsyncExecGitThread_ th(g, callback);
+ th.start();
+ while (1) {
+ if (th.wait(1)) break;
+ QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+ }
+ setLogEnabled(g, false);
+ }
+ openRepository_(g);
+ }
+
+ return; // done
+ }
}
-void BasicMainWindow::commit(bool amend)
+
+
+void MainWindow::commit(RepositoryWrapperFrame *frame, bool amend)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QString message;
+ QString previousMessage;
if (amend) {
- message = getLogs()[0].message;
+ message = getLogs(frame)[0].message;
} else {
QString id = g->getCherryPicking();
if (Git::isValidID(id)) {
message = g->getMessage(id);
+ } else {
+ for (Git::CommitItem const &item : getLogs(frame)) {
+ if (!item.commit_id.isEmpty()) {
+ previousMessage = item.message;
+ break;
+ }
+ }
}
}
while (1) {
Git::User user = g->getUser(Git::Source::Default);
QString sign_id = g->signingKey(Git::Source::Default);
gpg::Data key;
{
QList<gpg::Data> keys;
gpg::listKeys(global->gpg_command, &keys);
for (gpg::Data const &k : keys) {
if (k.id == sign_id) {
key = k;
}
}
}
- CommitDialog dlg(this, currentRepositoryName(), user, key);
+ CommitDialog dlg(this, currentRepositoryName(), user, key, previousMessage);
dlg.setText(message);
if (dlg.exec() == QDialog::Accepted) {
QString text = dlg.text();
if (text.isEmpty()) {
QMessageBox::warning(this, tr("Commit"), tr("Commit message can not be omitted."));
continue;
}
bool sign = dlg.isSigningEnabled();
bool ok;
- if (amend) {
+ if (amend || dlg.isAmend()) {
ok = g->commit_amend_m(text, sign, getPtyProcess());
} else {
ok = g->commit(text, sign, getPtyProcess());
}
if (ok) {
setForceFetch(true);
- updateStatusBarText();
+ updateStatusBarText(frame);
openRepository(true);
} else {
QString err = g->errorMessage().trimmed();
err += "\n*** ";
err += tr("Failed to commit");
err += " ***\n";
writeLog(err);
}
}
break;
}
}
-void BasicMainWindow::commitAmend()
+void MainWindow::commitAmend(RepositoryWrapperFrame *frame)
{
- commit(true);
+ commit(frame, true);
}
-void BasicMainWindow::pushSetUpstream(QString const &remote, QString const &branch)
+void MainWindow::pushSetUpstream(QString const &remote, QString const &branch)
{
if (remote.isEmpty()) return;
if (branch.isEmpty()) return;
int exitcode = 0;
QString errormsg;
reopenRepository(true, [&](GitPtr g){
g->push_u(remote, branch, getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
exitcode = getPtyProcess()->getExitCode();
errormsg = getPtyProcess()->getMessage();
});
if (exitcode == 128) {
if (errormsg.indexOf("Connection refused") >= 0) {
QMessageBox::critical(this, qApp->applicationName(), tr("Connection refused."));
return;
}
}
updateRemoteInfo();
}
-bool BasicMainWindow::pushSetUpstream(bool testonly)
+bool MainWindow::pushSetUpstream(bool testonly)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return false;
QStringList remotes = g->getRemotes();
QString current_branch = currentBranchName();
QStringList branches;
for (Git::Branch const &b : g->branches()) {
branches.push_back(b.name);
}
if (remotes.isEmpty() || branches.isEmpty()) {
return false;
}
if (testonly) {
return true;
}
PushDialog dlg(this, remotes, branches, PushDialog::RemoteBranch(QString(), current_branch));
if (dlg.exec() == QDialog::Accepted) {
PushDialog::Action a = dlg.action();
if (a == PushDialog::PushSimple) {
push();
} else if (a == PushDialog::PushSetUpstream) {
QString remote = dlg.remote();
QString branch = dlg.branch();
pushSetUpstream(remote, branch);
}
return true;
}
return false;
}
-void BasicMainWindow::push()
+void MainWindow::push()
{
if (!isOnlineMode()) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (g->getRemotes().isEmpty()) {
QMessageBox::warning(this, qApp->applicationName(), tr("No remote repository is registered."));
- execRepositoryPropertyDialog(QString(), true);
+ RepositoryItem const &repo = currentRepository();
+ execRepositoryPropertyDialog(repo, true);
return;
}
int exitcode = 0;
QString errormsg;
reopenRepository(true, [&](GitPtr g){
g->push(false, getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
exitcode = getPtyProcess()->getExitCode();
errormsg = getPtyProcess()->getMessage();
});
if (exitcode == 128) {
if (errormsg.indexOf("no upstream branch") >= 0) {
QString brname = currentBranchName();
QString msg = tr("The current branch %1 has no upstream branch.");
msg = msg.arg(brname);
msg += '\n';
msg += tr("You try push --set-upstream");
QMessageBox::warning(this, qApp->applicationName(), msg);
pushSetUpstream(false);
return;
}
if (errormsg.indexOf("Connection refused") >= 0) {
QMessageBox::critical(this, qApp->applicationName(), tr("Connection refused."));
return;
}
}
}
#ifdef Q_OS_MAC
namespace {
bool isValidDir(QString const &dir)
{
if (dir.indexOf('\"') >= 0 || dir.indexOf('\\') >= 0) return false;
return QFileInfo(dir).isDir();
}
}
#include <QProcess>
#endif
-void BasicMainWindow::openTerminal(RepositoryItem const *repo)
+void MainWindow::openTerminal(RepositoryItem const *repo)
{
runOnRepositoryDir([](QString dir){
#ifdef Q_OS_MAC
if (!isValidDir(dir)) return;
QString cmd = "open -n -a /Applications/Utilities/Terminal.app --args \"%1\"";
cmd = cmd.arg(dir);
QProcess::execute(cmd);
#else
Terminal::open(dir);
#endif
}, repo);
}
-void BasicMainWindow::openExplorer(RepositoryItem const *repo)
+void MainWindow::openExplorer(RepositoryItem const *repo)
{
runOnRepositoryDir([](QString dir){
#ifdef Q_OS_MAC
if (!isValidDir(dir)) return;
QString cmd = "open \"%1\"";
cmd = cmd.arg(dir);
QProcess::execute(cmd);
#else
QDesktopServices::openUrl(dir);
#endif
}, repo);
}
-Git::CommitItem const *BasicMainWindow::selectedCommitItem() const
+Git::CommitItem const *MainWindow::selectedCommitItem(RepositoryWrapperFrame *frame) const
{
- int i = selectedLogIndex();
- return commitItem(i);
+ int i = selectedLogIndex(frame);
+ return commitItem(frame, i);
}
-void BasicMainWindow::deleteBranch(Git::CommitItem const *commit)
+void MainWindow::deleteBranch(RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QStringList all_branch_names;
QStringList current_local_branch_names;
{
- NamedCommitList named_commits = namedCommitItems(Branches);
+ NamedCommitList named_commits = namedCommitItems(frame, Branches);
for (NamedCommitItem const &item : named_commits) {
if (item.name == "HEAD") continue;
if (item.id == commit->commit_id) {
current_local_branch_names.push_back(item.name);
}
all_branch_names.push_back(item.name);
}
}
DeleteBranchDialog dlg(this, false, all_branch_names, current_local_branch_names);
if (dlg.exec() == QDialog::Accepted) {
setLogEnabled(g, true);
QStringList names = dlg.selectedBranchNames();
int count = 0;
for (QString const &name : names) {
if (g->git(QString("branch -D \"%1\"").arg(name))) {
count++;
} else {
writeLog(tr("Failed to delete the branch '%1'").arg(name) + '\n');
}
}
if (count > 0) {
openRepository(true, true, true);
}
}
}
-void BasicMainWindow::deleteBranch()
+void MainWindow::deleteBranch(RepositoryWrapperFrame *frame)
{
- deleteBranch(selectedCommitItem());
+ deleteBranch(frame, selectedCommitItem(frame));
}
-QStringList MainWindow::remoteBranches(QString const &id, QStringList *all)
+QStringList MainWindow::remoteBranches(RepositoryWrapperFrame *frame, QString const &id, QStringList *all)
{
if (all) all->clear();
QStringList list;
GitPtr g = git();
if (isValidWorkingCopy(g)) {
- NamedCommitList named_commits = namedCommitItems(Branches | Remotes);
+ NamedCommitList named_commits = namedCommitItems(frame, Branches | Remotes);
for (NamedCommitItem const &item : named_commits) {
if (item.id == id && !item.remote.isEmpty()) {
list.push_back(item.remote / item.name);
}
if (all && !item.remote.isEmpty() && item.name != "HEAD") {
all->push_back(item.remote / item.name);
}
}
}
return list;
}
-void MainWindow::deleteRemoteBranch(Git::CommitItem const *commit)
+void MainWindow::deleteRemoteBranch(RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QStringList all_branches;
- QStringList remote_branches = remoteBranches(commit->commit_id, &all_branches);
+ QStringList remote_branches = remoteBranches(frame, commit->commit_id, &all_branches);
if (remote_branches.isEmpty()) return;
DeleteBranchDialog dlg(this, true, all_branches, remote_branches);
if (dlg.exec() == QDialog::Accepted) {
setLogEnabled(g, true);
QStringList names = dlg.selectedBranchNames();
for (QString const &name : names) {
int i = name.indexOf('/');
if (i > 0) {
QString remote = name.mid(0, i);
QString branch = ':' + name.mid(i + 1);
pushSetUpstream(remote, branch);
}
}
}
}
-bool BasicMainWindow::askAreYouSureYouWantToRun(QString const &title, QString const &command)
+bool MainWindow::askAreYouSureYouWantToRun(QString const &title, QString const &command)
{
QString message = tr("Are you sure you want to run the following command ?");
QString text = "%1\n\n%2";
text = text.arg(message).arg(command);
return QMessageBox::warning(this, title, text, QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok;
}
-bool BasicMainWindow::editFile(QString const &path, QString const &title)
+bool MainWindow::editFile(QString const &path, QString const &title)
{
return TextEditDialog::editFile(this, path, title);
}
-void BasicMainWindow::resetFile(QStringList const &paths)
+void MainWindow::resetFile(QStringList const &paths)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (paths.isEmpty()) {
// nop
} else {
QString cmd = "git checkout -- \"%1\"";
cmd = cmd.arg(paths[0]);
if (askAreYouSureYouWantToRun(tr("Reset a file"), "> " + cmd)) {
for (QString const &path : paths) {
g->resetFile(path);
}
openRepository(true);
}
}
}
-void BasicMainWindow::saveRepositoryBookmark(RepositoryItem item)
+void MainWindow::saveRepositoryBookmark(RepositoryItem item)
{
if (item.local_dir.isEmpty()) return;
if (item.name.isEmpty()) {
item.name = tr("Unnamed");
}
auto *repos = getReposPtr();
bool done = false;
for (auto &repo : *repos) {
RepositoryItem *p = &repo;
if (item.local_dir == p->local_dir) {
*p = item;
done = true;
break;
}
}
if (!done) {
repos->push_back(item);
}
saveRepositoryBookmarks();
updateRepositoriesList();
}
-void BasicMainWindow::setCurrentRepository(RepositoryItem const &repo, bool clear_authentication)
+void MainWindow::changeRepositoryBookmarkName(RepositoryItem item, QString new_name)
+{
+ item.name = new_name;
+ saveRepositoryBookmark(item);
+}
+
+void MainWindow::setCurrentRepository(RepositoryItem const &repo, bool clear_authentication)
{
if (clear_authentication) {
clearAuthentication();
}
- m->current_repo = repo;
+ m1->current_repo = repo;
}
-void BasicMainWindow::internalDeleteTags(QStringList const &tagnames)
+void MainWindow::internalDeleteTags(QStringList const &tagnames)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (!tagnames.isEmpty()) {
reopenRepository(false, [&](GitPtr g){
for (QString const &name : tagnames) {
g->delete_tag(name, true);
}
});
}
}
-bool BasicMainWindow::internalAddTag(QString const &name)
+bool MainWindow::internalAddTag(RepositoryWrapperFrame *frame, QString const &name)
{
if (name.isEmpty()) return false;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return false;
QString commit_id;
- Git::CommitItem const *commit = selectedCommitItem();
+ Git::CommitItem const *commit = selectedCommitItem(frame);
if (commit && !commit->commit_id.isEmpty()) {
commit_id = commit->commit_id;
}
if (!Git::isValidID(commit_id)) return false;
bool ok = false;
reopenRepository(false, [&](GitPtr g){
ok = g->tag(name, commit_id);
});
return ok;
}
-NamedCommitList BasicMainWindow::namedCommitItems(int flags)
+NamedCommitList MainWindow::namedCommitItems(RepositoryWrapperFrame *frame, int flags)
{
NamedCommitList items;
if (flags & Branches) {
- for (auto const &pair: branchMapRef()) {
+ for (auto const &pair: branchMapRef(frame)) {
QList<Git::Branch> const &list = pair.second;
for (Git::Branch const &b : list) {
if (b.isHeadDetached()) continue;
if (flags & NamedCommitFlag::Remotes) {
// nop
} else {
if (!b.remote.isEmpty()) continue;
}
NamedCommitItem item;
if (b.remote.isEmpty()) {
if (b.name == "HEAD") {
item.type = NamedCommitItem::Type::None;
} else {
item.type = NamedCommitItem::Type::BranchLocal;
}
} else {
item.type = NamedCommitItem::Type::BranchRemote;
item.remote = b.remote;
}
item.name = b.name;
item.id = b.id;
items.push_back(item);
}
}
}
if (flags & Tags) {
- for (auto const &pair: *ptrTagMap()) {
+ for (auto const &pair: *ptrTagMap(frame)) {
QList<Git::Tag> const &list = pair.second;
for (Git::Tag const &t : list) {
NamedCommitItem item;
item.type = NamedCommitItem::Type::Tag;
item.name = t.name;
item.id = t.id;
if (item.name.startsWith("refs/tags/")) {
item.name = item.name.mid(10);
}
items.push_back(item);
}
}
}
return items;
}
-int BasicMainWindow::rowFromCommitId(QString const &id)
+int MainWindow::rowFromCommitId(RepositoryWrapperFrame *frame, QString const &id)
{
- auto const &logs = getLogs();
+ auto const &logs = getLogs(frame);
for (size_t i = 0; i < logs.size(); i++) {
Git::CommitItem const &item = logs[i];
if (item.commit_id == id) {
return (int)i;
}
}
return -1;
}
-void BasicMainWindow::createRepository(QString const &dir)
+void MainWindow::createRepository(QString const &dir)
{
CreateRepositoryDialog dlg(this, dir);
if (dlg.exec() == QDialog::Accepted) {
QString path = dlg.path();
if (QFileInfo(path).isDir()) {
if (Git::isValidWorkingCopy(path)) {
// A valid git repository already exists there.
} else {
- GitPtr g = git(path);
+ GitPtr g = git(path, {}, {});
if (g->init()) {
QString name = dlg.name();
if (!name.isEmpty()) {
addWorkingCopyDir(path, name, true);
}
QString remote_name = dlg.remoteName();
QString remote_url = dlg.remoteURL();
if (!remote_name.isEmpty() && !remote_url.isEmpty()) {
- g->addRemoteURL(remote_name, remote_url);
+ Git::Remote r;
+ r.name = remote_name;
+ r.url = remote_url;
+ g->addRemoteURL(r);
}
}
}
} else {
// not dir
}
}
}
-void BasicMainWindow::setLogEnabled(GitPtr const &g, bool f)
+void MainWindow::setLogEnabled(GitPtr const &g, bool f)
{
if (f) {
g->setLogCallback(git_callback, this);
} else {
g->setLogCallback(nullptr, nullptr);
}
}
-QList<Git::Tag> BasicMainWindow::findTag(QString const &id)
+QList<Git::Tag> MainWindow::findTag(RepositoryWrapperFrame *frame, QString const &id)
{
- auto it = ptrTagMap()->find(id);
- if (it != ptrTagMap()->end()) {
+ auto it = ptrTagMap(frame)->find(id);
+ if (it != ptrTagMap(frame)->end()) {
return it->second;
}
return QList<Git::Tag>();
}
-void BasicMainWindow::sshSetPassphrase(std::string const &user, std::string const &pass)
+void MainWindow::sshSetPassphrase(std::string const &user, std::string const &pass)
{
- m->ssh_passphrase_user = user;
- m->ssh_passphrase_pass = pass;
+ m1->ssh_passphrase_user = user;
+ m1->ssh_passphrase_pass = pass;
}
-std::string BasicMainWindow::sshPassphraseUser() const
+std::string MainWindow::sshPassphraseUser() const
{
- return m->ssh_passphrase_user;
+ return m1->ssh_passphrase_user;
}
-std::string BasicMainWindow::sshPassphrasePass() const
+std::string MainWindow::sshPassphrasePass() const
{
- return m->ssh_passphrase_pass;
+ return m1->ssh_passphrase_pass;
}
-void BasicMainWindow::httpSetAuthentication(std::string const &user, std::string const &pass)
+void MainWindow::httpSetAuthentication(std::string const &user, std::string const &pass)
{
- m->http_uid = user;
- m->http_pwd = pass;
+ m1->http_uid = user;
+ m1->http_pwd = pass;
}
-std::string BasicMainWindow::httpAuthenticationUser() const
+std::string MainWindow::httpAuthenticationUser() const
{
- return m->http_uid;
+ return m1->http_uid;
}
-std::string BasicMainWindow::httpAuthenticationPass() const
+std::string MainWindow::httpAuthenticationPass() const
{
- return m->http_pwd;
+ return m1->http_pwd;
}
-void BasicMainWindow::doGitCommand(std::function<void(GitPtr g)> const &callback)
+void MainWindow::doGitCommand(std::function<void(GitPtr g)> const &callback)
{
GitPtr g = git();
if (isValidWorkingCopy(g)) {
OverrideWaitCursor;
callback(g);
openRepository(false, false);
}
}
+
diff --git a/src/BasicMainWindow.h b/src/BasicMainWindow.h
index d421b95..3454a30 100644
--- a/src/BasicMainWindow.h
+++ b/src/BasicMainWindow.h
@@ -1,387 +1,393 @@
-#ifndef BASICMAINWINDOW_H
-#define BASICMAINWINDOW_H
-
-#include "Git.h"
-#include "GitObjectManager.h"
-#include "MyProcess.h"
-#include "RepositoryData.h"
-#include "TextEditorTheme.h"
+#ifndef BASICH
+#define BASICH
+
+//#include "AvatarLoader.h"
+//#include "Git.h"
+//#include "GitHubAPI.h"
+//#include "GitObjectManager.h"
+//#include "RepositoryData.h"
+//#include "TextEditorTheme.h"
+//#include "main.h"
+//#include "webclient.h"
#include <QMainWindow>
-#include <functional>
+//#include <functional>
-class ApplicationSettings;
-class AvatarLoader;
-class QListWidget;
-class QListWidgetItem;
-class QTreeWidgetItem;
-class WebContext;
+//class ApplicationSettings;
+//class AvatarLoader;
+//class QListWidget;
+//class QListWidgetItem;
+//class QTreeWidgetItem;
+//class WebContext;
struct GitHubRepositoryInfo {
QString owner_account_name;
QString repository_name;
};
#define PATH_PREFIX "*"
class BasicMainWindow : public QMainWindow {
Q_OBJECT
+// friend class RepositoryPropertyDialog;
public:
- struct Label {
- enum {
- Head,
- LocalBranch,
- RemoteBranch,
- Tag,
- };
- int kind;
- QString text;
- QString info;
- Label(int kind = LocalBranch)
- : kind(kind)
- {
-
- }
- };
enum {
IndexRole = Qt::UserRole,
FilePathRole,
DiffIndexRole,
- HunkIndexRole,
+ ObjectIdRole,
+// HunkIndexRole,
HeaderRole,
+ SubmodulePathRole,
};
+ enum CloneOperation {
+ Clone,
+ SubmoduleAdd,
+ };
+
protected:
enum class PtyCondition {
None,
Clone,
Fetch,
Pull,
Push,
};
enum InteractionMode {
None,
Busy,
};
enum NamedCommitFlag {
Branches = 0x0001,
Tags = 0x0002,
Remotes = 0x0100,
};
enum class FilesListType {
SingleList,
SideBySide,
};
struct Task {
int index = 0;
int parent = 0;
Task() = default;
Task(int index, int parent)
: index(index)
, parent(parent)
{
}
};
struct Element {
int depth = 0;
std::vector<int> indexes;
};
enum {
GroupItem = -1,
};
- struct Private;
- Private *m;
private:
- static bool git_callback(void *cookie, const char *ptr, int len);
-
- QString selectCommand_(QString const &cmdname, QStringList const &cmdfiles, QStringList const &list, QString path, std::function<void (QString const &)> const &callback);
- QString selectCommand_(QString const &cmdname, QString const &cmdfile, QStringList const &list, QString const &path, std::function<void (QString const &)> const &callback);
-
- bool checkGitCommand();
- bool checkFileCommand();
- bool checkExecutable(QString const &path);
- void internalSetCommand(QString const &path, bool save, QString const &name, QString *out);
- QStringList whichCommand_(QString const &cmdfile1, QString const &cmdfile2 = QString());
-
- void setWindowTitle_(Git::User const &user);
- bool execSetGlobalUserDialog();
-
- bool saveByteArrayAs(const QByteArray &ba, QString const &dstpath);
- bool saveFileAs(QString const &srcpath, QString const &dstpath);
- bool saveBlobAs(QString const &id, QString const &dstpath);
-
- void updateFilesList(Git::CommitItem const &commit, bool wait);
-
- void revertAllFiles();
-
- void setCurrentRemoteName(QString const &name);
-
- void deleteTags(Git::CommitItem const &commit);
-
- bool isAvatarEnabled() const;
- bool isGitHub() const;
- QStringList remotes() const;
- QList<Git::Branch> findBranch(QString const &id);
- QString saveAsTemp(QString const &id);
- QString tempfileHeader() const;
- void deleteTempFiles();
- QString getCommitIdFromTag(QString const &tag);
- bool isValidRemoteURL(QString const &url);
- QString getObjectID(QListWidgetItem *item);
- void addWorkingCopyDir(QString dir, QString name, bool open);
- static QString makeRepositoryName(QString const &loc);
- void clearAuthentication();
- RepositoryItem const *findRegisteredRepository(QString *workdir) const;
- void queryRemotes(const GitPtr &g);
- bool runOnRepositoryDir(std::function<void (QString)> const &callback, RepositoryItem const *repo);
- void clearSshAuthentication();
+// static bool git_callback(void *cookie, const char *ptr, int len);
+
+// QString selectCommand_(QString const &cmdname, QStringList const &cmdfiles, QStringList const &list, QString path, std::function<void (QString const &)> const &callback);
+// QString selectCommand_(QString const &cmdname, QString const &cmdfile, QStringList const &list, QString const &path, std::function<void (QString const &)> const &callback);
+
+// bool checkGitCommand();
+// bool checkFileCommand();
+// bool checkExecutable(QString const &path);
+// void internalSaveCommandPath(QString const &path, bool save, QString const &name);
+// QStringList whichCommand_(QString const &cmdfile1, QString const &cmdfile2 = QString());
+
+// void setWindowTitle_(Git::User const &user);
+// bool execSetGlobalUserDialog();
+
+// bool saveByteArrayAs(const QByteArray &ba, QString const &dstpath);
+// bool saveFileAs(QString const &srcpath, QString const &dstpath);
+// bool saveBlobAs(QString const &id, QString const &dstpath);
+
+// void updateFilesList(Git::CommitItem const &commit, bool wait);
+
+// void revertAllFiles();
+
+// void setCurrentRemoteName(QString const &name);
+
+// void deleteTags(Git::CommitItem const &commit);
+
+// bool isAvatarEnabled() const;
+// bool isGitHub() const;
+// QStringList remotes() const;
+// QList<Git::Branch> findBranch(QString const &id);
+// QString saveAsTemp(QString const &id);
+// QString tempfileHeader() const;
+// void deleteTempFiles();
+// QString getCommitIdFromTag(QString const &tag);
+// bool isValidRemoteURL(QString const &url, const QString &sshkey);
+// QString getObjectID(QListWidgetItem *item);
+// void addWorkingCopyDir(QString dir, QString name, bool open);
+// static QString makeRepositoryName(QString const &loc);
+// void clearAuthentication();
+// RepositoryItem const *findRegisteredRepository(QString *workdir) const;
+// void queryRemotes(const GitPtr &g);
+// bool runOnRepositoryDir(std::function<void (QString)> const &callback, RepositoryItem const *repo);
+// void clearSshAuthentication();
+ // QString executableOrEmpty(const QString &path);
+ // bool runOnRepositoryDir(const std::function<void (QString)> &callback, const RepositoryItem *repo);
protected:
- static QString getFilePath(QListWidgetItem *item);
- static bool isGroupItem(QTreeWidgetItem *item);
- static int indexOfLog(QListWidgetItem *item);
- static int indexOfDiff(QListWidgetItem *item);
- static int getHunkIndex(QListWidgetItem *item);
-
- void initNetworking();
- bool saveRepositoryBookmarks() const;
- QString getBookmarksFilePath() const;
-
- void stopPtyProcess();
- void abortPtyProcess();
-
- bool execWelcomeWizardDialog();
- void execRepositoryPropertyDialog(QString workdir, bool open_repository_menu = false);
- void execSetUserDialog(Git::User const &global_user, Git::User const &repo_user, QString const &reponame);
- void setGitCommand(QString const &path, bool save);
- void setFileCommand(QString const &path, bool save);
- void setGpgCommand(QString const &path, bool save);
- void logGitVersion();
- void setUnknownRepositoryInfo();
- void internalClearRepositoryInfo();
- void checkUser();
- void openRepository(bool validate, bool waitcursor = true, bool keep_selection = false);
- void updateRepository();
- void reopenRepository(bool log, const std::function<void(GitPtr const &)> &callback);
- void openSelectedRepository();
+// static QString getFilePath(QListWidgetItem *item);
+// static bool isGroupItem(QTreeWidgetItem *item);
+// static int indexOfLog(QListWidgetItem *item);
+// static int indexOfDiff(QListWidgetItem *item);
+// static int getHunkIndex(QListWidgetItem *item);
+
+// void initNetworking();
+// bool saveRepositoryBookmarks() const;
+// QString getBookmarksFilePath() const;
+
+// void stopPtyProcess();
+// void abortPtyProcess();
+
+// bool execWelcomeWizardDialog();
+// void execRepositoryPropertyDialog(const RepositoryItem &repo, bool open_repository_menu = false);
+// void execSetUserDialog(Git::User const &global_user, Git::User const &repo_user, QString const &reponame);
+// void setGitCommand(QString path, bool save);
+// void setFileCommand(QString path, bool save);
+// void setGpgCommand(QString path, bool save);
+// void setSshCommand(QString path, bool save);
+// void logGitVersion();
+// void setUnknownRepositoryInfo();
+// void internalClearRepositoryInfo();
+// void checkUser();
+// void openRepository(bool validate, bool waitcursor = true, bool keep_selection = false);
+// void updateRepository();
+// void reopenRepository(bool log, const std::function<void(GitPtr const &)> &callback);
+// void openSelectedRepository();
// void checkRemoteUpdate();
- bool isThereUncommitedChanges() const;
- bool makeDiff(QString id, QList<Git::Diff> *out);
- void addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (QString const &, QString, int)> &add_item);
- Git::CommitItemList retrieveCommitLog(const GitPtr &g);
-
- void queryBranches(const GitPtr &g);
- std::map<QString, QList<Git::Branch>> &branchMapRef();
-
- void updateCommitTableLater();
- void updateRemoteInfo();
- void updateWindowTitle(const GitPtr &g);
- QString makeCommitInfoText(int row, QList<Label> *label_list);
- void removeRepositoryFromBookmark(int index, bool ask);
-
- void clone(QString url = QString(), QString dir = QString());
- void checkout();
- void commit(bool amend = false);
- void commitAmend();
- void pushSetUpstream(QString const &remote, QString const &branch);
- bool pushSetUpstream(bool testonly);
- void push();
-
- void openTerminal(RepositoryItem const *repo);
- void openExplorer(RepositoryItem const *repo);
- Git::CommitItem const *selectedCommitItem() const;
- void deleteBranch(Git::CommitItem const *commit);
- void deleteBranch();
- bool askAreYouSureYouWantToRun(QString const &title, QString const &command);
- bool editFile(QString const &path, QString const &title);
- void resetFile(QStringList const &paths);
- void saveRepositoryBookmark(RepositoryItem item);
- void setCurrentRepository(RepositoryItem const &repo, bool clear_authentication);
- void internalDeleteTags(QStringList const &tagnames);
- bool internalAddTag(QString const &name);
- NamedCommitList namedCommitItems(int flags);
- int rowFromCommitId(QString const &id);
- void createRepository(QString const &dir);
- void setLogEnabled(const GitPtr &g, bool f);
- QList<Git::Tag> findTag(QString const &id);
- void sshSetPassphrase(std::string const &user, std::string const &pass);
- std::string sshPassphraseUser() const;
- std::string sshPassphrasePass() const;
- void httpSetAuthentication(std::string const &user, std::string const &pass);
- std::string httpAuthenticationUser() const;
- std::string httpAuthenticationPass() const;
+// bool isThereUncommitedChanges() const;
+// QList<Git::Diff> makeDiffs(QString id, bool *ok);
+// void addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (ObjectData const &data)> &add_item);
+// Git::CommitItemList retrieveCommitLog(const GitPtr &g);
+
+// void queryBranches(const GitPtr &g);
+// std::map<QString, QList<Git::Branch>> &branchMapRef();
+
+// void updateCommitTableLater();
+// void updateRemoteInfo();
+// void updateWindowTitle(const GitPtr &g);
+// QString makeCommitInfoText(int row, QList<Label> *label_list);
+// void removeRepositoryFromBookmark(int index, bool ask);
+
+// void clone(QString url = {}, QString dir = {});
+// void submodule_add(QString url = {}, QString local_dir = {});
+// void checkout();
+// void commit(bool amend = false);
+// void commitAmend();
+// void pushSetUpstream(QString const &remote, QString const &branch);
+// bool pushSetUpstream(bool testonly);
+// void push();
+
+// void openTerminal(RepositoryItem const *repo);
+// void openExplorer(RepositoryItem const *repo);
+// Git::CommitItem const *selectedCommitItem() const;
+// void deleteBranch(Git::CommitItem const *commit);
+// void deleteBranch();
+// bool askAreYouSureYouWantToRun(QString const &title, QString const &command);
+// bool editFile(QString const &path, QString const &title);
+// void resetFile(QStringList const &paths);
+// void saveRepositoryBookmark(RepositoryItem item);
+// void set(RepositoryItem const &repo, bool clear_authentication);
+// void internalDeleteTags(QStringList const &tagnames);
+// bool internalAddTag(QString const &name);
+// NamedCommitList namedCommitItems(int flags);
+// int rowFromCommitId(QString const &id);
+// void createRepository(QString const &dir);
+// void setLogEnabled(const GitPtr &g, bool f);
+// QList<Git::Tag> findTag(QString const &id);
+// void sshSetPassphrase(std::string const &user, std::string const &pass);
+// std::string sshPassphraseUser() const;
+// std::string sshPassphrasePass() const;
+// void httpSetAuthentication(std::string const &user, std::string const &pass);
+// std::string httpAuthenticationUser() const;
+// std::string httpAuthenticationPass() const;
public:
- Git::CommitItemList const &getLogs() const;
- Git::CommitItem const *getLog(int index) const;
+// Git::CommitItemList const &getLogs() const;
+// Git::CommitItem const *getLog(int index) const;
protected:
- bool event(QEvent *event) override;
- void postUserFunctionEvent(const std::function<void(QVariant const &)> &fn, QVariant const &v = QVariant());
+// bool event(QEvent *event) override;
+// void postUserFunctionEvent(const std::function<void(QVariant const &)> &fn, QVariant const &v = QVariant());
- void doGitCommand(std::function<void(GitPtr)> const &callback);
+// void doGitCommand(std::function<void(GitPtr)> const &callback);
- Git::CommitItemList *getLogsPtr();
- void setLogs(const Git::CommitItemList &logs);
- void clearLogs();
+// Git::CommitItemList *getLogsPtr();
+// void setLogs(const Git::CommitItemList &logs);
+// void clearLogs();
- PtyProcess *getPtyProcess();
- PtyCondition getPtyCondition();
- bool getPtyProcessOk() const;
- void setPtyProcessOk(bool pty_process_ok);
- void setPtyUserData(QVariant const &userdata);
- void setPtyCondition(const PtyCondition &ptyCondition);
+// PtyProcess *getPtyProcess();
+// PtyCondition getPtyCondition();
+// bool getPtyProcessOk() const;
+// void setPtyProcessOk(bool pty_process_ok);
+// void setPtyUserData(QVariant const &userdata);
+// void setPtyCondition(const PtyCondition &ptyCondition);
- QList<RepositoryItem> const &getRepos() const;
- QList<RepositoryItem> *getReposPtr();
+// QList<RepositoryItem> const &getRepos() const;
+// QList<RepositoryItem> *getReposPtr();
- QString getCurrentRemoteName() const;
+// QString getCurrentRemoteName() const;
- AvatarLoader *getAvatarLoader();
- const AvatarLoader *getAvatarLoader() const;
+// AvatarLoader *getAvatarLoader();
+// const AvatarLoader *getAvatarLoader() const;
- int *ptrUpdateFilesListCounter();
- int *ptrUpdateCommitTableCounter();
+// int *ptrUpdateFilesListCounter();
+// int *ptrUpdateCommitTableCounter();
- bool interactionCanceled() const;
- void setInteractionCanceled(bool canceled);
+// bool interactionCanceled() const;
+// void setInteractionCanceled(bool canceled);
- InteractionMode interactionMode() const;
- void setInteractionMode(const InteractionMode &im);
+// InteractionMode interactionMode() const;
+// void setInteractionMode(const InteractionMode &im);
- QString getRepositoryFilterText() const;
- void setRepositoryFilterText(QString const &text);
+// QString getRepositoryFilterText() const;
+// void setRepositoryFilterText(QString const &text);
- void setUncommitedChanges(bool uncommited_changes);
+// void setUncommitedChanges(bool uncommited_changes);
- QList<Git::Diff> *diffResult();
- std::map<QString, Git::Diff> *getDiffCacheMap();
+// QList<Git::Diff> *diffResult();
+// std::map<QString, Git::Diff> *getDiffCacheMap();
- bool getRemoteChanged() const;
- void setRemoteChanged(bool remote_changed);
+// bool getRemoteChanged() const;
+// void setRemoteChanged(bool remote_changed);
- void setServerType(const ServerType &server_type);
+// void setServerType(const ServerType &server_type);
- GitHubRepositoryInfo *ptrGitHub();
+// GitHubRepositoryInfo *ptrGitHub();
- std::map<int, QList<Label>> *getLabelMap();
- const std::map<int, QList<Label>> *getLabelMap() const;
- void clearLabelMap();
+// std::map<int, QList<Label>> *getLabelMap();
+// const std::map<int, QList<Label>> *getLabelMap() const;
+// void clearLabelMap();
- GitObjectCache *getObjCache();
+// GitObjectCache *getObjCache();
- bool getForceFetch() const;
- void setForceFetch(bool force_fetch);
+// bool getForceFetch() const;
+// void setForceFetch(bool force_fetch);
- std::map<QString, QList<Git::Tag>> *ptrTagMap();
+// std::map<QString, QList<Git::Tag>> *ptrTagMap();
- QString getHeadId() const;
- void setHeadId(QString const &head_id);
+// QString getHeadId() const;
+// void setHeadId(QString const &head_id);
- void setPtyProcessCompletionData(QVariant const &value);
- QVariant const &getTempRepoForCloneCompleteV() const;
+// void setPtyProcessCompletionData(QVariant const &value);
+// QVariant const &getTempRepoForCloneCompleteV() const;
- void updateCommitGraph();
- bool fetch(const GitPtr &g, bool prune);
- bool fetch_tags_f(const GitPtr &g);
+// void updateCommitGraph();
+// bool fetch(const GitPtr &g, bool prune);
+// bool fetch_tags_f(const GitPtr &g);
protected:
- virtual void setCurrentLogRow(int row) = 0;
- virtual void updateFilesList(QString id, bool wait) = 0;
- virtual void setRepositoryInfo(QString const &reponame, QString const &brname) = 0;
- virtual void deleteTags(QStringList const &tagnames) = 0;
- virtual void internalWriteLog(const char *ptr, int len) = 0;
- virtual void removeSelectedRepositoryFromBookmark(bool ask) = 0;
- virtual void openRepository_(GitPtr g, bool keep_selection = false) = 0;
- virtual void updateRepositoriesList() = 0;
- virtual void clearFileList() = 0;
- virtual RepositoryItem const *selectedRepositoryItem() const = 0;
- virtual void setRemoteMonitoringEnabled(bool enable) { (void)enable; }
- virtual void updateStatusBarText() {}
- void msgNoRepositorySelected();
- bool isRepositoryOpened() const;
+// virtual void setCurrentLogRow(int row) = 0;
+// virtual void updateFilesList(QString id, bool wait) = 0;
+// virtual void setRepositoryInfo(QString const &reponame, QString const &brname) = 0;
+// virtual void deleteTags(QStringList const &tagnames) = 0;
+// virtual void internalWriteLog(const char *ptr, int len) = 0;
+// virtual void removeSelectedRepositoryFromBookmark(bool ask) = 0;
+// virtual void openRepository_(GitPtr g, bool keep_selection = false) = 0;
+// virtual void updateRepositoriesList() = 0;
+// virtual void clearFileList() = 0;
+// virtual RepositoryItem const *selectedRepositoryItem() const = 0;
+// virtual void updateStatusBarText() {}
+// void msgNoRepositorySelected();
+// bool isRepositoryOpened() const;
+// void updateSubmodules(GitPtr g, QString id);
+// static std::pair<QString, QString> makeFileItemText(const ObjectData &data);
public:
explicit BasicMainWindow(QWidget *parent = nullptr);
~BasicMainWindow() override;
- ApplicationSettings *appsettings();
- const ApplicationSettings *appsettings() const;
- WebContext *webContext();
- QString gitCommand() const;
- void autoOpenRepository(QString dir);
- GitPtr git(QString const &dir) const;
- GitPtr git();
- QPixmap getTransparentPixmap();
- QIcon committerIcon(int row) const;
- Git::CommitItem const *commitItem(int row) const;
- QIcon verifiedIcon(char s) const;
- virtual QString currentWorkingCopyDir() const;
- const QList<Label> *label(int row) const;
- bool saveAs(QString const &id, QString const &dstpath);
- bool testRemoteRepositoryValidity(QString const &url);
- QString defaultWorkingDir() const;
- void addWorkingCopyDir(const QString &dir, bool open);
- bool queryCommit(QString const &id, Git::CommitItem *out);
- QAction *addMenuActionProperty(QMenu *menu);
- void checkout(QWidget *parent, Git::CommitItem const *commit, std::function<void()> accepted_callback = nullptr);
- void jumpToCommit(QString id);
- void execCommitViewWindow(Git::CommitItem const *commit);
- void execCommitExploreWindow(QWidget *parent, Git::CommitItem const *commit);
- void execCommitPropertyDialog(QWidget *parent, Git::CommitItem const *commit);
- void execFileHistory(QString const &path);
- void execFileHistory(QListWidgetItem *item);
- void execFilePropertyDialog(QListWidgetItem *item);
- QString selectGitCommand(bool save);
- QString selectFileCommand(bool save);
- QString selectGpgCommand(bool save);
- Git::Branch const &currentBranch() const;
- void setCurrentBranch(Git::Branch const &b);
- const RepositoryItem &currentRepository() const;
- QString currentRepositoryName() const;
- QString currentRemoteName() const;
- QString currentBranchName() const;
- bool isValidWorkingCopy(const GitPtr &g) const;
- QString determinFileType_(QString const &path, bool mime, std::function<void (QString const &, QByteArray *)> const &callback) const;
- QString determinFileType(QString const &path, bool mime);
- QString determinFileType(QByteArray in, bool mime);
- QList<Git::Tag> queryTagList();
- int limitLogCount() const;
- TextEditorThemePtr themeForTextEditor();
- Git::Object cat_file_(const GitPtr &g, QString const &id);
- Git::Object cat_file(QString const &id);
- QString newTempFilePath();
- QString findFileID(QString const &commit_id, QString const &file);
- void updateFilesList(QString const &id, QList<Git::Diff> *diff_list, QListWidget *listwidget);
- void setAppSettings(const ApplicationSettings &appsettings);
-
- QIcon getRepositoryIcon() const;
- QIcon getFolderIcon() const;
- QIcon getSignatureGoodIcon() const;
- QIcon getSignatureDubiousIcon() const;
- QIcon getSignatureBadIcon() const;
- QPixmap getTransparentPixmap() const;
-
- static QString abbrevCommitID(Git::CommitItem const &commit);
- QStringList findGitObject(const QString &id) const;
-
- void writeLog(const char *ptr, int len);
- void writeLog(QString const &str);
- void emitWriteLog(const QByteArray &ba);
- QList<Label> sortedLabels(int row) const;
-
- virtual bool isOnlineMode() const = 0;
- virtual int selectedLogIndex() const = 0;
+// ApplicationSettings *appsettings();
+// const ApplicationSettings *appsettings() const;
+// WebContext *webContext();
+// QString gitCommand() const;
+// void autoOpenRepository(QString dir);
+// GitPtr git(QString const &dir, const QString &sshkey = {}) const;
+// GitPtr git();
+// GitPtr git(const Git::Submodule &submod);
+// QPixmap getTransparentPixmap();
+// QIcon committerIcon(int row) const;
+// Git::CommitItem const *commitItem(int row) const;
+// QIcon verifiedIcon(char s) const;
+// virtual QString currentWorkingCopyDir() const;
+// const QList<Label> *label(int row) const;
+// bool saveAs(QString const &id, QString const &dstpath);
+// bool testRemoteRepositoryValidity(QString const &url, const QString &sshkey);
+// QString defaultWorkingDir() const;
+// void addWorkingCopyDir(const QString &dir, bool open);
+// bool queryCommit(QString const &id, Git::CommitItem *out);
+// QAction *addMenuActionProperty(QMenu *menu);
+// void checkout(QWidget *parent, Git::CommitItem const *commit, std::function<void()> accepted_callback = nullptr);
+// void jumpToCommit(QString id);
+// void execCommitViewWindow(Git::CommitItem const *commit);
+// void execCommitExploreWindow(QWidget *parent, Git::CommitItem const *commit);
+// void execCommitPropertyDialog(QWidget *parent, Git::CommitItem const *commit);
+// void (QString const &path);
+// void (QListWidgetItem *item);
+// void execFilePropertyDialog(QListWidgetItem *item);
+// QString selectGitCommand(bool save);
+// QString selectFileCommand(bool save);
+// QString selectGpgCommand(bool save);
+// QString selectSshCommand(bool save);
+// Git::Branch const &currentBranch() const;
+// void setCurrentBranch(Git::Branch const &b);
+// const RepositoryItem &() const;
+// QString Name() const;
+// QString currentRemoteName() const;
+// QString currentBranchName() const;
+// bool isValidWorkingCopy(const GitPtr &g) const;
+// QString determinFileType_(QString const &path, bool mime, std::function<void (QString const &, QByteArray *)> const &callback) const;
+// QString determinFileType(QString const &path, bool mime);
+// QString determinFileType(QByteArray in, bool mime);
+// QList<Git::Tag> queryTagList();
+// int limitLogCount() const;
+// TextEditorThemePtr themeForTextEditor();
+// Git::Object cat_file_(const GitPtr &g, QString const &id);
+// Git::Object cat_file(QString const &id);
+// QString newTempFilePath();
+// QString findFileID(QString const &commit_id, QString const &file);
+// void updateFilesList2(QString const &id, QList<Git::Diff> *diff_list, QListWidget *listwidget);
+// void setAppSettings(const ApplicationSettings &appsettings);
+
+// QIcon getRepositoryIcon() const;
+// QIcon getFolderIcon() const;
+// QIcon getSignatureGoodIcon() const;
+// QIcon getSignatureDubiousIcon() const;
+// QIcon getSignatureBadIcon() const;
+// QPixmap getTransparentPixmap() const;
+
+// static QString abbrevCommitID(Git::CommitItem const &commit);
+// QStringList findGitObject(const QString &id) const;
+
+// void writeLog(const char *ptr, int len);
+// void writeLog(QString const &str);
+// void emitWriteLog(const QByteArray &ba);
+// QList<Label> sortedLabels(int row) const;
+
+// void changeSshKey(const QString &localdir, const QString &sshkey);
+// void saveApplicationSettings();
+// void loadApplicationSettings();
+// void setDiffResult(const QList<Git::Diff> &diffs);
+// const QList<Git::Submodule> &submodules() const;
+// void setSubmodules(const QList<Git::Submodule> &submodules);
protected slots:
- void onAvatarUpdated();
+// void onAvatarUpdated();
public slots:
- void writeLog_(QByteArray ba);
-signals:
- void signalWriteLog(QByteArray ba);
- void remoteInfoChanged();
-// void signalCheckRemoteUpdate();
+ // void writeLog_(QByteArray ba);
+//signals:
+// void signalWriteLog(QByteArray ba);
+// void remoteInfoChanged();
+ // void signalCheckRemoteUpdate();
+private slots:
+// void onAvatarUpdated();
};
-#endif // BASICMAINWINDOW_H
+#endif // BASICH
diff --git a/src/BasicRepositoryDialog.cpp b/src/BasicRepositoryDialog.cpp
index 7c2729e..1abeaf9 100644
--- a/src/BasicRepositoryDialog.cpp
+++ b/src/BasicRepositoryDialog.cpp
@@ -1,89 +1,100 @@
#include "BasicRepositoryDialog.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include <QTableWidget>
#include <QHeaderView>
struct BasicRepositoryDialog::Private {
- BasicMainWindow *mainwindow = nullptr;
+ MainWindow *mainwindow = nullptr;
GitPtr git;
QList<Git::Remote> remotes;
};
-BasicRepositoryDialog::BasicRepositoryDialog(BasicMainWindow *mainwindow, GitPtr const &g)
+BasicRepositoryDialog::BasicRepositoryDialog(MainWindow *mainwindow, GitPtr const &g)
: QDialog(mainwindow)
, m(new Private)
{
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
m->mainwindow = mainwindow;
m->git = g;
}
BasicRepositoryDialog::~BasicRepositoryDialog()
{
delete m;
}
-BasicMainWindow *BasicRepositoryDialog::mainwindow()
+MainWindow *BasicRepositoryDialog::mainwindow()
{
return m->mainwindow;
}
GitPtr BasicRepositoryDialog::git()
{
return m->git;
}
QList<Git::Remote> const *BasicRepositoryDialog::remotes() const
{
return &m->remotes;
}
-QString BasicRepositoryDialog::updateRemotesTable(QTableWidget *tablewidget)
+void BasicRepositoryDialog::getRemotes_()
{
- tablewidget->clear();
- m->remotes.clear();
GitPtr g = git();
if (g->isValidWorkingCopy()) {
g->getRemoteURLs(&m->remotes);
}
+}
+
+void BasicRepositoryDialog::setSshKey_(QString const &sshkey)
+{
+ m->git->setSshKey(sshkey);
+}
+
+
+QString BasicRepositoryDialog::updateRemotesTable(QTableWidget *tablewidget)
+{
+ tablewidget->clear();
+ m->remotes.clear();
+ getRemotes_();
QString url;
QString alturl;
int rows = m->remotes.size();
tablewidget->setColumnCount(3);
tablewidget->setRowCount(rows);
auto newQTableWidgetItem = [](QString const &text){
auto *item = new QTableWidgetItem;
item->setSizeHint(QSize(20, 20));
item->setText(text);
return item;
};
auto SetHeaderItem = [&](int col, QString const &text){
tablewidget->setHorizontalHeaderItem(col, newQTableWidgetItem(text));
};
SetHeaderItem(0, tr("Name"));
SetHeaderItem(1, tr("Purpose"));
SetHeaderItem(2, tr("URL"));
for (int row = 0; row < rows; row++) {
Git::Remote const &r = m->remotes[row];
if (r.purpose == "push") {
url = r.url;
} else {
alturl = r.url;
}
auto SetItem = [&](int col, QString const &text){
tablewidget->setItem(row, col, newQTableWidgetItem(text));
};
SetItem(0, r.name);
SetItem(1, r.purpose);
SetItem(2, r.url);
}
tablewidget->horizontalHeader()->setStretchLastSection(true);
if (url.isEmpty()) {
url = alturl;
}
return url;
}
diff --git a/src/BasicRepositoryDialog.h b/src/BasicRepositoryDialog.h
index 2076bf9..f4efc5b 100644
--- a/src/BasicRepositoryDialog.h
+++ b/src/BasicRepositoryDialog.h
@@ -1,26 +1,28 @@
#ifndef BASICREPOSITORYDIALOG_H
#define BASICREPOSITORYDIALOG_H
#include <QDialog>
#include "Git.h"
class QTableWidget;
-class BasicMainWindow;
+class MainWindow;
class BasicRepositoryDialog : public QDialog {
public:
- explicit BasicRepositoryDialog(BasicMainWindow *mainwindow, const GitPtr &g);
+ explicit BasicRepositoryDialog(MainWindow *mainwindow, const GitPtr &g);
~BasicRepositoryDialog() override;
private:
struct Private;
Private *m;
protected:
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
GitPtr git();
QString updateRemotesTable(QTableWidget *tablewidget);
const QList<Git::Remote> *remotes() const;
+ void getRemotes_();
+ void setSshKey_(const QString &sshkey);
};
#endif // BASICREPOSITORYDIALOG_H
diff --git a/src/BigDiffWindow.cpp b/src/BigDiffWindow.cpp
index 03fb935..67c795c 100644
--- a/src/BigDiffWindow.cpp
+++ b/src/BigDiffWindow.cpp
@@ -1,84 +1,84 @@
#include <memory>
#include "BigDiffWindow.h"
#include "ui_BigDiffWindow.h"
struct BigDiffWindow::Private {
TextEditorEnginePtr text_editor_engine;
FileDiffWidget::InitParam_ param;
};
BigDiffWindow::BigDiffWindow(QWidget *parent)
: QDialog(parent)
, ui(new Ui::BigDiffWindow)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
flags |= Qt::WindowMaximizeButtonHint;
setWindowFlags(flags);
ui->widget_diff->setMaximizeButtonEnabled(false);
connect(ui->widget_diff, &FileDiffWidget::textcodecChanged, [&](){ updateDiffView(); });
}
BigDiffWindow::~BigDiffWindow()
{
delete m;
delete ui;
}
void BigDiffWindow::setTextCodec(QTextCodec *codec)
{
m->text_editor_engine = std::make_shared<TextEditorEngine>();
ui->widget_diff->setTextCodec(codec);
}
void BigDiffWindow::updateDiffView()
{
ui->widget_diff->updateDiffView(m->param.diff, m->param.uncommited);
}
-void BigDiffWindow::init(BasicMainWindow *mw, FileDiffWidget::InitParam_ const &param)
+void BigDiffWindow::init(MainWindow *mw, FileDiffWidget::InitParam_ const &param)
{
ui->widget_diff->bind(mw);
m->param = param;
{
QString name = m->param.diff.path;
int i = name.lastIndexOf('/');
if (i >= 0) {
name = name.mid(i + 1);
}
ui->lineEdit_center->setText(name);
}
auto Text = [](QString id){
if (id.startsWith(PATH_PREFIX)) {
id = id.mid(1);
}
return id;
};
ui->lineEdit_left->setText(Text(m->param.diff.blob.a_id));
ui->lineEdit_right->setText(Text(m->param.diff.blob.b_id));
switch (m->param.view_style) {
case FileDiffWidget::ViewStyle::LeftOnly:
ui->widget_diff->setLeftOnly(m->param.bytes_a, m->param.diff);
break;
case FileDiffWidget::ViewStyle::RightOnly:
ui->widget_diff->setRightOnly(m->param.bytes_b, m->param.diff);
break;
case FileDiffWidget::ViewStyle::SideBySideText:
ui->widget_diff->setSideBySide(m->param.bytes_a, m->param.diff, m->param.uncommited, m->param.workingdir);
break;
case FileDiffWidget::ViewStyle::SideBySideImage:
ui->widget_diff->setSideBySide_(m->param.bytes_a, m->param.bytes_b, m->param.workingdir);
break;
}
}
diff --git a/src/BigDiffWindow.h b/src/BigDiffWindow.h
index 95e75ec..dec3d3b 100644
--- a/src/BigDiffWindow.h
+++ b/src/BigDiffWindow.h
@@ -1,29 +1,29 @@
#ifndef BIGDIFFWINDOW_H
#define BIGDIFFWINDOW_H
#include <QDialog>
#include "Git.h"
#include "FileDiffWidget.h"
namespace Ui {
class BigDiffWindow;
}
class BigDiffWindow : public QDialog {
Q_OBJECT
private:
struct Private;
Private *m;
public:
explicit BigDiffWindow(QWidget *parent = nullptr);
~BigDiffWindow() override;
- void init(BasicMainWindow *mw, const FileDiffWidget::InitParam_ &param);
+ void init(MainWindow *mw, const FileDiffWidget::InitParam_ &param);
void setTextCodec(QTextCodec *codec);
private:
Ui::BigDiffWindow *ui;
void updateDiffView();
QString fileName() const;
};
#endif // BIGDIFFWINDOW_H
diff --git a/src/BlameWindow.cpp b/src/BlameWindow.cpp
index 746a1d1..ec9dc82 100644
--- a/src/BlameWindow.cpp
+++ b/src/BlameWindow.cpp
@@ -1,248 +1,248 @@
#include "BlameWindow.h"
#include "ui_BlameWindow.h"
#include "common/misc.h"
#include "CommitPropertyDialog.h"
#include "Git.h"
#include "MainWindow.h"
#include <QMenu>
#include <QToolTip>
enum {
CommidIdRole = Qt::UserRole,
};
namespace {
struct CommitInfo {
QString datetime;
QString author;
QString email;
QString message;
};
}
struct BlameWindow::Private {
QList<BlameItem> list;
std::map<QString, CommitInfo> commit_cache;
};
BlameWindow::BlameWindow(MainWindow *parent, QString const &filename, const QList<BlameItem> &list)
: QDialog(parent)
, ui(new Ui::BlameWindow)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
{
QString s = "Blame : %1";
s = s.arg(filename);
setWindowTitle(s);
}
m->list = list;
int rows = 0;
for (BlameItem const &item : m->list) {
if (rows < item.line_number) {
rows = item.line_number;
}
}
int n = m->list.size();
if (rows < n) rows = n;
QStringList cols = {
"Commit",
"Time",
"Line",
"Text",
};
ui->tableWidget->setColumnCount(cols.size());
ui->tableWidget->setRowCount(rows);
for (int col = 0; col < cols.size(); col++) {
auto *item = new QTableWidgetItem(cols[col]);
ui->tableWidget->setHorizontalHeaderItem(col, item);
}
int row = 0;
for (BlameItem const &blame: m->list) {
QString id;
if (Git::isValidID(blame.commit_id)) {
id = blame.commit_id.mid(0, 8);
}
int col = 0;
auto NewItem = [](QString const &text){
return new QTableWidgetItem(text);
};
auto SetItem = [&](QTableWidgetItem *item){
ui->tableWidget->setItem(row, col, item);
col++;
};
QTableWidgetItem *item;
item = NewItem(id);
item->setData(CommidIdRole, blame.commit_id);
SetItem(item);
item = NewItem(misc::makeDateTimeString(blame.time));
SetItem(item);
item = NewItem(QString::number(blame.line_number));
item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
SetItem(item);
item = NewItem(blame.text);
SetItem(item);
ui->tableWidget->setRowHeight(row, 24);
row++;
}
ui->tableWidget->resizeColumnsToContents();
ui->tableWidget->horizontalHeader()->stretchLastSection();
ui->tableWidget->selectRow(0);
}
BlameWindow::~BlameWindow()
{
delete m;
delete ui;
}
MainWindow *BlameWindow::mainwindow()
{
return qobject_cast<MainWindow *>(parent());
}
QList<BlameItem> BlameWindow::parseBlame(char const *begin, char const *end)
{
QList<BlameItem> list;
std::vector<std::string> lines;
misc::splitLines(begin, end, &lines, false);
BlameItem item;
for (std::string const &line : lines) {
if (line[0] == '\t') {
item.text = QString::fromUtf8(line.c_str() + 1);
list.push_back(item);
item.commit_id = QString();
item.text = QString();
} else {
char const *p = line.c_str();
char const *q = strchr(p, ' ');
if (q) {
QString label = QString::fromLatin1(p, q - p);
if (item.commit_id.isEmpty()) {
item.commit_id = label;
int a, b, c;
if (sscanf(q + 1, "%d %d %d", &a, &b, &c) >= 2) {
item.line_number = b;
}
} else {
auto Value = [&](){
return QString::fromLatin1(q + 1);
};
if (label == "author") {
item.author = Value();
} else if (label == "author-time") {
qint64 t = Value().toLong();
item.time = QDateTime::fromMSecsSinceEpoch(t * 1000);
}
}
}
}
}
return list;
}
QString BlameWindow::getCommitId(QTableWidgetItem *item) const
{
return item ? item->data(CommidIdRole).toString() : QString();
}
QString BlameWindow::currentCommitId() const
{
QString id;
int row = ui->tableWidget->currentRow();
if (row >= 0 && row < m->list.size()) {
QTableWidgetItem *item = ui->tableWidget->item(row, 0);
id = getCommitId(item);
}
return id;
}
void BlameWindow::on_tableWidget_itemDoubleClicked(QTableWidgetItem *)
{
QString id = currentCommitId();
if (Git::isValidID(id)) {
- CommitPropertyDialog dlg(this, mainwindow(), id);
+ CommitPropertyDialog dlg(this, mainwindow(), mainwindow()->frame(), id);
dlg.showCheckoutButton(false);
dlg.showJumpButton(true);
if (dlg.exec() == QDialog::Accepted) {
close();
}
}
}
void BlameWindow::on_tableWidget_customContextMenuRequested(const QPoint &pos)
{
(void)pos;
int row = ui->tableWidget->currentRow();
if (row < 0 || row >= m->list.size()) return;
Git::CommitItem commit;
BlameItem blame = m->list[row];
GitPtr g = mainwindow()->git();
if (!g->queryCommit(blame.commit_id, &commit)) return;
QMenu menu;
QAction *a_property = mainwindow()->addMenuActionProperty(&menu);
QAction *a = menu.exec(QCursor::pos() + QPoint(8, -8));
if (a) {
if (a == a_property) {
- mainwindow()->execCommitPropertyDialog(this, &commit);
+ mainwindow()->execCommitPropertyDialog(this, mainwindow()->frame(), &commit);
return;
}
}
}
void BlameWindow::on_tableWidget_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous)
{
(void)current;
(void)previous;
QString id = currentCommitId();
CommitInfo info;
if (Git::isValidID(id)) {
auto it = m->commit_cache.find(id);
if (it != m->commit_cache.end()) {
info = it->second;
} else {
GitPtr g = mainwindow()->git();
Git::CommitItem commit;
if (g->queryCommit(id, &commit)) {
info.datetime = misc::makeDateTimeString(commit.commit_date);
info.author = commit.author;
info.email = commit.email;
info.message = commit.message;
}
}
} else {
id = QString();
}
QString author = info.author;
if (!info.email.isEmpty()) {
author = author + " <" + info.email + '>';
}
ui->lineEdit_commit_id->setText(id);
ui->lineEdit_date->setText(info.datetime);
ui->lineEdit_author->setText(author);
ui->lineEdit_message->setText(info.message);
}
diff --git a/src/BranchLabel.cpp b/src/BranchLabel.cpp
new file mode 100644
index 0000000..6f34b1e
--- /dev/null
+++ b/src/BranchLabel.cpp
@@ -0,0 +1,3 @@
+#include "BranchLabel.h"
+
+
diff --git a/src/BranchLabel.h b/src/BranchLabel.h
new file mode 100644
index 0000000..088bc73
--- /dev/null
+++ b/src/BranchLabel.h
@@ -0,0 +1,40 @@
+#ifndef BRANCHLABEL_H
+#define BRANCHLABEL_H
+
+#include <QColor>
+#include "ApplicationGlobal.h"
+
+
+/**
+ * @brief ログテーブルウィジェットのブランチ名ラベル
+ */
+class BranchLabel {
+public:
+ enum Type {
+ Head,
+ LocalBranch,
+ RemoteBranch,
+ Tag,
+ };
+ Type kind;
+ QString text;
+ QString info;
+ BranchLabel(Type kind = LocalBranch)
+ : kind(kind)
+ {
+ }
+
+ static QColor color(Type type)
+ {
+
+ switch (type) {
+ case Head: return global->appsettings.branch_label_color.head;
+ case LocalBranch: return global->appsettings.branch_label_color.local;
+ case RemoteBranch: return global->appsettings.branch_label_color.remote;
+ case Tag: return global->appsettings.branch_label_color.tag;
+ }
+ return QColor(224, 224, 224); // gray
+ }
+};
+
+#endif // BRANCHLABEL_H
diff --git a/src/CheckoutDialog.ui b/src/CheckoutDialog.ui
index 1409b7f..904f2e4 100644
--- a/src/CheckoutDialog.ui
+++ b/src/CheckoutDialog.ui
@@ -1,120 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CheckoutDialog</class>
<widget class="QDialog" name="CheckoutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>366</width>
- <height>135</height>
+ <height>251</height>
</rect>
</property>
<property name="windowTitle">
<string>Checkout</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="radioButton_head_detached">
<property name="text">
<string>RadioButton</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_existing_local_branch">
<property name="text">
<string>RadioButton</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_create_local_branch">
<property name="text">
<string>RadioButton</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox_branch_name">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>radioButton_head_detached</tabstop>
<tabstop>radioButton_existing_local_branch</tabstop>
<tabstop>radioButton_create_local_branch</tabstop>
+ <tabstop>comboBox_branch_name</tabstop>
<tabstop>pushButton</tabstop>
<tabstop>pushButton_2</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>CheckoutDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>162</x>
<y>165</y>
</hint>
<hint type="destinationlabel">
<x>134</x>
<y>160</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_2</sender>
<signal>clicked()</signal>
<receiver>CheckoutDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>271</x>
<y>171</y>
</hint>
<hint type="destinationlabel">
<x>243</x>
<y>177</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/CloneDialog.cpp b/src/CloneDialog.cpp
index 9dacf42..54a175a 100644
--- a/src/CloneDialog.cpp
+++ b/src/CloneDialog.cpp
@@ -1,143 +1,156 @@
#include "CloneDialog.h"
#include "ui_CloneDialog.h"
-#include "common/misc.h"
-#include "common/joinpath.h"
-#include "BasicMainWindow.h"
+#include "ApplicationGlobal.h"
+#include "MainWindow.h"
#include "SearchFromGitHubDialog.h"
-
+#include "common/joinpath.h"
+#include "common/misc.h"
#include <QFileDialog>
#include <QMessageBox>
+#include <QStandardPaths>
#include <QThread>
enum SearchRepository {
None,
GitHub,
};
struct CloneDialog::Private {
QString url;
QString repo_name;
QString default_working_dir;
bool ok = false;
QString errmsg;
CloneDialog::Action action = CloneDialog::Action::Clone;
};
-CloneDialog::CloneDialog(BasicMainWindow *parent, QString const &url, QString const &defworkdir)
+CloneDialog::CloneDialog(MainWindow *parent, QString const &url, QString const &defworkdir, Git::Context const *gcx)
: QDialog(parent)
, ui(new Ui::CloneDialog)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
m->default_working_dir = defworkdir;
ui->lineEdit_working_dir->setText(m->default_working_dir);
ui->lineEdit_repo_location->setText(url);
ui->comboBox->addItem(tr("Search"));
ui->comboBox->addItem(tr("GitHub"));
+ ui->advanced_option->setSshKeyOverrigingEnabled(!gcx->ssh_command.isEmpty());
#ifdef Q_OS_MACX
ui->comboBox->setMinimumWidth(100);
#endif
+
+ ui->lineEdit_repo_location->setFocus();
}
CloneDialog::~CloneDialog()
{
delete m;
delete ui;
}
CloneDialog::Action CloneDialog::action() const
{
return m->action;
}
-BasicMainWindow *CloneDialog::mainwindow()
+MainWindow *CloneDialog::mainwindow()
{
- return qobject_cast<BasicMainWindow *>(parent());
+ return qobject_cast<MainWindow *>(parent());
}
QString CloneDialog::url()
{
return ui->lineEdit_repo_location->text();
}
QString CloneDialog::dir()
{
return ui->lineEdit_working_dir->text();
}
void CloneDialog::on_lineEdit_repo_location_textChanged(QString const &text)
{
QString path;
int i = text.lastIndexOf('/');
int j = text.lastIndexOf('\\');
if (i < j) i = j;
j = text.size();
if (text.endsWith(".git")) {
j -= 4;
}
if (i >= 0 && i < j) {
path = text.mid(i, j - i);
}
m->repo_name = path;
path = m->default_working_dir / m->repo_name;
path = misc::normalizePathSeparator(path);
ui->lineEdit_working_dir->setText(path);
}
void CloneDialog::on_comboBox_currentIndexChanged(int index)
{
if (index == GitHub) {
SearchFromGitHubDialog dlg(this, mainwindow());
if (dlg.exec() == QDialog::Accepted) {
ui->lineEdit_repo_location->setText(dlg.url());
}
}
ui->comboBox->setCurrentIndex(0);
}
void CloneDialog::on_pushButton_test_clicked()
{
- mainwindow()->testRemoteRepositoryValidity(url());
+ mainwindow()->testRemoteRepositoryValidity(url(), overridedSshKey());
}
void CloneDialog::on_pushButton_browse_clicked()
{
QString path = ui->lineEdit_working_dir->text();
path = QFileDialog::getExistingDirectory(this, tr("Checkout into"), path);
if (!path.isEmpty()) {
m->default_working_dir = path;
path = m->default_working_dir / m->repo_name;
path = misc::normalizePathSeparator(path);
ui->lineEdit_working_dir->setText(path);
}
}
void CloneDialog::on_pushButton_open_existing_clicked()
{
QString dir = mainwindow()->defaultWorkingDir();
dir = QFileDialog::getExistingDirectory(this, tr("Open existing directory"), dir);
if (QFileInfo(dir).isDir()) {
QString url;
- GitPtr g = mainwindow()->git(dir);
+ GitPtr g = mainwindow()->git(dir, {}, {});
QList<Git::Remote> vec;
if (g->isValidWorkingCopy()) {
g->getRemoteURLs(&vec);
}
for (Git::Remote const &r : vec) {
if (r.purpose == "push" || url.isEmpty()) {
url = r.url;
}
}
ui->lineEdit_repo_location->setText(url);
ui->lineEdit_working_dir->setText(dir);
m->action = CloneDialog::Action::AddExisting;
done(Accepted);
}
}
+
+QString CloneDialog::overridedSshKey() const
+{
+ return ui->advanced_option->sshKey();
+}
+
+
+
+
diff --git a/src/CloneDialog.h b/src/CloneDialog.h
index d4f79d6..e3e12cc 100644
--- a/src/CloneDialog.h
+++ b/src/CloneDialog.h
@@ -1,45 +1,45 @@
#ifndef CLONEDIALOG_H
#define CLONEDIALOG_H
#include <QDialog>
#include <QThread>
#include "Git.h"
namespace Ui {
class CloneDialog;
}
-class BasicMainWindow;
+class MainWindow;
class CloneDialog : public QDialog {
Q_OBJECT
private:
+ Ui::CloneDialog *ui;
struct Private;
Private *m;
using GitPtr = std::shared_ptr<Git>;
+private:
+ MainWindow *mainwindow();
public:
- explicit CloneDialog(BasicMainWindow *parent, QString const &url, QString const &defworkdir);
+ explicit CloneDialog(MainWindow *parent, QString const &url, QString const &defworkdir, const Git::Context *gcx);
~CloneDialog() override;
enum class Action {
Clone,
AddExisting,
};
Action action() const;
QString url();
QString dir();
-private:
- Ui::CloneDialog *ui;
-
- BasicMainWindow *mainwindow();
+ QString overridedSshKey() const;
private slots:
void on_lineEdit_repo_location_textChanged(QString const &text);
void on_pushButton_test_clicked();
void on_comboBox_currentIndexChanged(int index);
void on_pushButton_browse_clicked();
void on_pushButton_open_existing_clicked();
};
#endif // CLONEDIALOG_H
diff --git a/src/CloneDialog.ui b/src/CloneDialog.ui
index d82f778..2d14dba 100644
--- a/src/CloneDialog.ui
+++ b/src/CloneDialog.ui
@@ -1,175 +1,194 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CloneDialog</class>
<widget class="QDialog" name="CloneDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>545</width>
- <height>140</height>
+ <width>632</width>
+ <height>290</height>
</rect>
</property>
<property name="windowTitle">
<string>Clone</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Remote</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="RepositoryLineEdit" name="lineEdit_repo_location">
<property name="minimumSize">
<size>
<width>320</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="pushButton_test">
<property name="text">
<string>&amp;Test</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Local</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="DirectoryLineEdit" name="lineEdit_working_dir"/>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pushButton_browse">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
+ <item>
+ <widget class="RemoteAdvancedOptionWidget" name="advanced_option" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>110</height>
+ </size>
+ </property>
+ </widget>
+ </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_open_existing">
<property name="text">
<string>Open existing local directory...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Clone</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RepositoryLineEdit</class>
<extends>QLineEdit</extends>
<header>RepositoryLineEdit.h</header>
</customwidget>
<customwidget>
<class>DirectoryLineEdit</class>
<extends>QLineEdit</extends>
<header>DirectoryLineEdit.h</header>
</customwidget>
+ <customwidget>
+ <class>RemoteAdvancedOptionWidget</class>
+ <extends>QWidget</extends>
+ <header>RemoteAdvancedOptionWidget.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<tabstops>
<tabstop>lineEdit_repo_location</tabstop>
<tabstop>pushButton_test</tabstop>
<tabstop>lineEdit_working_dir</tabstop>
+ <tabstop>pushButton_browse</tabstop>
+ <tabstop>comboBox</tabstop>
+ <tabstop>pushButton_open_existing</tabstop>
<tabstop>pushButton</tabstop>
<tabstop>pushButton_2</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>CloneDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>152</x>
<y>269</y>
</hint>
<hint type="destinationlabel">
<x>104</x>
<y>320</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_2</sender>
<signal>clicked()</signal>
<receiver>CloneDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>244</x>
<y>270</y>
</hint>
<hint type="destinationlabel">
<x>238</x>
<y>317</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/ColorButton.cpp b/src/ColorButton.cpp
new file mode 100644
index 0000000..5f847b9
--- /dev/null
+++ b/src/ColorButton.cpp
@@ -0,0 +1,68 @@
+#include "ColorButton.h"
+
+#include <QPainter>
+#include <QProxyStyle>
+#include <QStyleOption>
+#include "coloredit/ColorDialog.h"
+#include "common/misc.h"
+
+ColorButton::ColorButton(QWidget *parent)
+ : QToolButton(parent)
+{
+ color_ = Qt::red;
+
+ connect(this, &ColorButton::clicked, [&](){
+ QColor c = color();
+ ColorDialog dlg(this, c);
+ if (dlg.exec() == QDialog::Accepted) {
+ c = dlg.color();
+ setColor(c);
+ }
+ });
+}
+
+/**
+ * @brief 色を取得
+ * @return
+ */
+QColor ColorButton::color() const
+{
+ return color_;
+}
+
+/**
+ * @brief 色を設定
+ * @param color
+ */
+void ColorButton::setColor(QColor const &color)
+{
+ color_ = color;
+ update();
+}
+
+/**
+ * @brief 描画
+ * @param event
+ */
+void ColorButton::paintEvent(QPaintEvent *event)
+{
+ QPainter pr(this);
+ QStyleOptionToolButton o;
+ QStyleOptionButton o2;
+ initStyleOption(&o);
+ o2.rect = o.rect;
+ o2.state = o.state;
+ int h = o2.rect.height();
+
+ // 色の矩形を描画
+ misc::drawFrame(&pr, 0, 1, h - 2, h - 2, Qt::black);
+ pr.fillRect(1, 2, h - 4, h - 4, color());
+
+ // ボタン枠を描画
+ o2.rect.adjust(h + 2, 0, 0, 0);
+ style()->drawControl(QStyle::CE_PushButtonBevel, &o2, &pr, this);
+
+ // ボタンテキストを描画
+ o2.rect.adjust(4, 0, 0, 0);
+ style()->drawItemText(&pr, o2.rect, Qt::AlignLeft | Qt::AlignVCenter, palette(), isEnabled(), o.text);
+}
diff --git a/src/ColorButton.h b/src/ColorButton.h
new file mode 100644
index 0000000..c351e23
--- /dev/null
+++ b/src/ColorButton.h
@@ -0,0 +1,19 @@
+#ifndef COLORBUTTON_H
+#define COLORBUTTON_H
+
+#include <QToolButton>
+
+class ColorButton : public QToolButton {
+ Q_OBJECT
+private:
+ QColor color_;
+protected:
+ void paintEvent(QPaintEvent *event);
+public:
+ explicit ColorButton(QWidget *parent = nullptr);
+
+ QColor color() const;
+ void setColor(const QColor &color);
+};
+
+#endif // COLORBUTTON_H
diff --git a/src/CommitDialog.cpp b/src/CommitDialog.cpp
index b4af67d..20efef2 100644
--- a/src/CommitDialog.cpp
+++ b/src/CommitDialog.cpp
@@ -1,95 +1,111 @@
#include "CommitDialog.h"
#include "ui_CommitDialog.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "ConfigSigningDialog.h"
#include <QDir>
-CommitDialog::CommitDialog(BasicMainWindow *parent, QString const &reponame, Git::User const &user, gpg::Data const &key)
+CommitDialog::CommitDialog(MainWindow *parent, QString const &reponame, Git::User const &user, gpg::Data const &key, QString const &previousMessage)
: QDialog(parent)
, ui(new Ui::CommitDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
key_ = key;
+ previousMessage_ = previousMessage;
ui->label_reponame->setText(reponame);
ui->label_commit_author->setText(user.name);
ui->label_commit_mail->setText(user.email);
+ ui->checkbox_amend->setChecked(false);
+ if (previousMessage_.isEmpty()) {
+ ui->checkbox_amend->hide();
+ }
+
updateSigningInfo();
ui->plainTextEdit->setFocus();
}
CommitDialog::~CommitDialog()
{
delete ui;
}
-BasicMainWindow *CommitDialog::mainwindow()
+MainWindow *CommitDialog::mainwindow()
{
- return qobject_cast<BasicMainWindow *>(parent());
+ return qobject_cast<MainWindow *>(parent());
}
void CommitDialog::updateSigningInfo()
{
GitPtr g = mainwindow()->git();
Git::SignPolicy pol = g->signPolicy(Git::Source::Default);
if (!key_.id.isEmpty()) {
if (pol == Git::SignPolicy::True) {
ui->groupBox_gpg_sign->setCheckable(false);
} else {
ui->groupBox_gpg_sign->setCheckable(true);
ui->groupBox_gpg_sign->setChecked(false);
}
ui->label_sign_id->setText(key_.id);
ui->label_sign_name->setText(key_.name);
ui->label_sign_mail->setText(key_.mail);
} else {
ui->groupBox_gpg_sign->setChecked(false);
ui->groupBox_gpg_sign->setEnabled(false);
}
}
bool CommitDialog::isSigningEnabled() const
{
return ui->groupBox_gpg_sign->isChecked();
}
+bool CommitDialog::isAmend() const
+{
+ return ui->checkbox_amend->isChecked();
+}
+
void CommitDialog::setText(QString const &text)
{
ui->plainTextEdit->setPlainText(text);
QTextCursor cur = ui->plainTextEdit->textCursor();
cur.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
ui->plainTextEdit->setTextCursor(cur);
}
QString CommitDialog::text() const
{
return ui->plainTextEdit->toPlainText();
}
void CommitDialog::keyPressEvent(QKeyEvent *event)
{
if (event->modifiers() & Qt::ControlModifier) {
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
event->accept();
accept();
return;
}
}
QDialog::keyPressEvent(event);
}
void CommitDialog::on_pushButton_config_signing_clicked()
{
ConfigSigningDialog dlg(this, mainwindow(), true);
if (dlg.exec() == QDialog::Accepted) {
updateSigningInfo();
}
}
+
+void CommitDialog::on_checkbox_amend_stateChanged(int state)
+{
+ setText(state == Qt::Checked ? previousMessage_ : QString(""));
+}
diff --git a/src/CommitDialog.h b/src/CommitDialog.h
index dcbbad1..dee858c 100644
--- a/src/CommitDialog.h
+++ b/src/CommitDialog.h
@@ -1,37 +1,40 @@
#ifndef COMMITDIALOG_H
#define COMMITDIALOG_H
#include "Git.h"
#include "gpg.h"
#include <QDialog>
-class BasicMainWindow;
+class MainWindow;
namespace Ui {
class CommitDialog;
}
class CommitDialog : public QDialog {
Q_OBJECT
public:
- explicit CommitDialog(BasicMainWindow *parent, QString const &reponame, Git::User const &user, gpg::Data const &key);
+ explicit CommitDialog(MainWindow *parent, QString const &reponame, Git::User const &user, gpg::Data const &key, QString const &previousMessage);
~CommitDialog() override;
void setText(QString const &text);
QString text() const;
bool isSigningEnabled() const;
+ bool isAmend() const;
protected:
void keyPressEvent(QKeyEvent *event) override;
private:
Ui::CommitDialog *ui;
gpg::Data key_;
+ QString previousMessage_;
// QDialog interface
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
void updateSigningInfo();
private slots:
void on_pushButton_config_signing_clicked();
+ void on_checkbox_amend_stateChanged(int state);
};
#endif // COMMITDIALOG_H
diff --git a/src/CommitDialog.ui b/src/CommitDialog.ui
index 075d1a5..866dd8d 100644
--- a/src/CommitDialog.ui
+++ b/src/CommitDialog.ui
@@ -1,276 +1,290 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CommitDialog</class>
<widget class="QDialog" name="CommitDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>568</width>
<height>389</height>
</rect>
</property>
<property name="windowTitle">
<string>Commit</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_reponame">
<property name="font">
<font>
<pointsize>16</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Author</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_commit_author">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>---</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Mail</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_commit_mail">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>---</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_gpg_sign">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>GPG Signing</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>ID</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_sign_id">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>---</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_sign_name">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>---</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Mail</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_sign_mail">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>---</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_config_signing">
<property name="text">
<string>Configure...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Message</string>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
+ <item alignment="Qt::AlignLeft">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Message</string>
+ </property>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignRight">
+ <widget class="QCheckBox" name="checkbox_amend">
+ <property name="text">
+ <string>Amend</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item>
<widget class="QPlainTextEdit" name="plainTextEdit"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>OK</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>CommitDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>398</x>
<y>345</y>
</hint>
<hint type="destinationlabel">
<x>370</x>
<y>346</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_2</sender>
<signal>clicked()</signal>
<receiver>CommitDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>544</x>
<y>344</y>
</hint>
<hint type="destinationlabel">
<x>562</x>
<y>342</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/CommitExploreWindow.cpp b/src/CommitExploreWindow.cpp
index 1b1ef95..0e6b7a5 100644
--- a/src/CommitExploreWindow.cpp
+++ b/src/CommitExploreWindow.cpp
@@ -1,279 +1,279 @@
#include "CommitExploreWindow.h"
#include "ui_CommitExploreWindow.h"
#include "GitObjectManager.h"
#include "ImageViewWidget.h"
#include "MainWindow.h"
#include "common/misc.h"
#include "main.h"
#include "platform.h"
#include <QFileIconProvider>
#include <QMenu>
#include <memory>
static QTreeWidgetItem *newQTreeWidgetItem()
{
auto *item = new QTreeWidgetItem;
item->setSizeHint(0, QSize(20, 20));
return item;
}
enum {
ItemTypeRole = Qt::UserRole,
ObjectIdRole,
FilePathRole,
};
struct CommitExploreWindow::Private {
- BasicMainWindow *mainwindow;
+ MainWindow *mainwindow;
GitObjectCache *objcache;
Git::CommitItem const *commit;
QString root_tree_id;
GitTreeItemList tree_item_list;
Git::Object content_object;
ObjectContent content;
TextEditorEnginePtr text_editor_engine;
};
-CommitExploreWindow::CommitExploreWindow(QWidget *parent, BasicMainWindow *mainwin, GitObjectCache *objcache, Git::CommitItem const *commit)
+CommitExploreWindow::CommitExploreWindow(QWidget *parent, MainWindow *mainwin, GitObjectCache *objcache, Git::CommitItem const *commit)
: QDialog(parent)
, ui(new Ui::CommitExploreWindow)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
flags |= Qt::WindowMaximizeButtonHint;
setWindowFlags(flags);
m->mainwindow = mainwin;
m->objcache = objcache;
m->commit = commit;
m->text_editor_engine = std::make_shared<TextEditorEngine>();
ui->widget_fileview->bind(mainwin, nullptr, ui->verticalScrollBar, ui->horizontalScrollBar, mainwin->themeForTextEditor());
ui->widget_fileview->setDiffMode(m->text_editor_engine, ui->verticalScrollBar, ui->horizontalScrollBar);
ui->splitter->setSizes({100, 100, 200});
// set text
ui->lineEdit_commit_id->setText(commit->commit_id);
ui->lineEdit_date->setText(misc::makeDateTimeString(commit->commit_date));
ui->lineEdit_author->setText(commit->author);
{
GitCommit c;
- c.parseCommit(objcache, m->commit->commit_id);
+ GitCommit::parseCommit(objcache, m->commit->commit_id, &c);
m->root_tree_id = c.tree_id;
}
{
GitCommitTree tree(objcache);
tree.parseTree(m->root_tree_id);
}
{
QTreeWidgetItem *rootitem = newQTreeWidgetItem();
rootitem->setText(0, tr("Commit"));
rootitem->setData(0, ItemTypeRole, (int)GitTreeItem::TREE);
rootitem->setData(0, ObjectIdRole, m->root_tree_id);
ui->treeWidget->addTopLevelItem(rootitem);
loadTree(m->root_tree_id);
rootitem->setExpanded(true);
}
}
CommitExploreWindow::~CommitExploreWindow()
{
delete m;
delete ui;
}
-BasicMainWindow *CommitExploreWindow::mainwindow()
+MainWindow *CommitExploreWindow::mainwindow()
{
return m->mainwindow;
}
void CommitExploreWindow::clearContent()
{
m->content = ObjectContent();
}
void CommitExploreWindow::expandTreeItem_(QTreeWidgetItem *item)
{
if (item->childCount() == 1) {
if (item->child(0)->text(0).isEmpty()) {
delete item->takeChild(0);
}
}
if (item->childCount() == 0) {
QFileIconProvider icons;
QString path = item->data(0, FilePathRole).toString();
QString tree_id = item->data(0, ObjectIdRole).toString();
loadTree(tree_id);
for (GitTreeItem const &ti : m->tree_item_list) {
if (ti.type == GitTreeItem::TREE) {
QTreeWidgetItem *child = newQTreeWidgetItem();
child->setIcon(0, icons.icon(QFileIconProvider::Folder));
child->setText(0, ti.name);
child->setData(0, ItemTypeRole, (int)ti.type);
child->setData(0, ObjectIdRole, ti.id);
child->setData(0, FilePathRole, misc::joinWithSlash(path, ti.name));
QTreeWidgetItem *placeholder = newQTreeWidgetItem();
child->addChild(placeholder);
item->addChild(child);
}
}
}
}
void CommitExploreWindow::on_treeWidget_itemExpanded(QTreeWidgetItem *item)
{
expandTreeItem_(item);
}
void CommitExploreWindow::loadTree(QString const &tree_id)
{
GitCommitTree tree(m->objcache);
tree.parseTree(tree_id);
m->tree_item_list = *tree.treelist();
std::sort(m->tree_item_list.begin(), m->tree_item_list.end(), [](GitTreeItem const &left, GitTreeItem const &right){
int l = (left.type == GitTreeItem::TREE) ? 0 : 1;
int r = (right.type == GitTreeItem::TREE) ? 0 : 1;
if (l != r) return l < r;
return left.name.compare(right.name, Qt::CaseInsensitive) < 0;
});
}
void CommitExploreWindow::doTreeItemChanged_(QTreeWidgetItem *current)
{
ui->listWidget->clear();
QString path = current->data(0, FilePathRole).toString();
QString tree_id = current->data(0, ObjectIdRole).toString();
loadTree(tree_id);
QFileIconProvider icons;
for (GitTreeItem const &ti : m->tree_item_list) {
QIcon icon;
if (ti.type == GitTreeItem::TREE) {
icon = icons.icon(QFileIconProvider::Folder);
} else {
#ifdef Q_OS_WIN
{
int i = ti.name.lastIndexOf('.');
if (i > 0) {
QString ext = ti.name.mid(i + 1);
icon = winIconFromExtensionLarge(ext);
}
}
#endif
if (icon.isNull()) {
icon = icons.icon(QFileIconProvider::File);
}
}
auto *p = new QListWidgetItem;
p->setIcon(icon);
p->setText(ti.name);
p->setData(ItemTypeRole, (int)ti.type);
p->setData(ObjectIdRole, ti.id);
p->setData(FilePathRole, misc::joinWithSlash(path, ti.name));
ui->listWidget->addItem(p);
}
}
void CommitExploreWindow::on_treeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem * /*previous*/)
{
clearContent();
doTreeItemChanged_(current);
}
void CommitExploreWindow::on_listWidget_itemDoubleClicked(QListWidgetItem *item)
{
Q_ASSERT(item);
GitTreeItem::Type type = (GitTreeItem::Type)item->data(ItemTypeRole).toInt();
if (type == GitTreeItem::TREE) {
QString tree_id = item->data(ObjectIdRole).toString();
clearContent();
QTreeWidgetItem *parent = ui->treeWidget->currentItem();
expandTreeItem_(parent);
parent->setExpanded(true);
int n = parent->childCount();
for (int i = 0; i < n; i++) {
QTreeWidgetItem *child = parent->child(i);
if (child->data(0, ItemTypeRole).toInt() == GitTreeItem::TREE) {
QString tid = child->data(0, ObjectIdRole).toString();
if (tid == tree_id) {
child->setExpanded(true);
ui->treeWidget->setCurrentItem(child);
break;
}
}
}
}
}
void CommitExploreWindow::on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem * /*previous*/)
{
if (!current) return;
GitTreeItem::Type type = (GitTreeItem::Type)current->data(ItemTypeRole).toInt();
if (type == GitTreeItem::BLOB) {
QString id = current->data(ObjectIdRole).toString();
m->content_object = m->objcache->catFile(id);
QString path = current->data(FilePathRole).toString();
clearContent();
QString mimetype = mainwindow()->determinFileType(m->content_object.content, true);
if (misc::isImage(mimetype)) {
ui->widget_fileview->setImage(mimetype, m->content_object.content, id, path);
} else {
ui->widget_fileview->setText(m->content_object.content, mainwindow(), id, path);
}
} else {
clearContent();
}
}
void CommitExploreWindow::on_verticalScrollBar_valueChanged(int)
{
ui->widget_fileview->refrectScrollBar();
}
void CommitExploreWindow::on_horizontalScrollBar_valueChanged(int)
{
ui->widget_fileview->refrectScrollBar();
}
void CommitExploreWindow::on_listWidget_customContextMenuRequested(const QPoint &pos)
{
(void)pos;
QListWidgetItem *current = ui->listWidget->currentItem();
if (!current) return;
GitTreeItem::Type type = (GitTreeItem::Type)current->data(ItemTypeRole).toInt();
if (type == GitTreeItem::BLOB) {
QMenu menu;
QAction *a_history = menu.addAction("History");
QAction *a = menu.exec(QCursor::pos() + QPoint(8, -8));
if (a) {
if (a == a_history) {
QString path = current->data(FilePathRole).toString();
mainwindow()->execFileHistory(path);
return;
}
}
}
}
diff --git a/src/CommitExploreWindow.h b/src/CommitExploreWindow.h
index 2a887e1..5b05f9d 100644
--- a/src/CommitExploreWindow.h
+++ b/src/CommitExploreWindow.h
@@ -1,42 +1,42 @@
#ifndef COMMITEXPLOREWINDOW_H
#define COMMITEXPLOREWINDOW_H
#include <QDialog>
#include "FileDiffWidget.h"
namespace Ui {
class CommitExploreWindow;
}
class QTreeWidgetItem;
class QListWidgetItem;
class GitObjectCache;
class CommitExploreWindow : public QDialog {
Q_OBJECT
private:
Ui::CommitExploreWindow *ui;
struct Private;
Private *m;
void loadTree(QString const &tree_id);
void doTreeItemChanged_(QTreeWidgetItem *current);
void expandTreeItem_(QTreeWidgetItem *item);
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
public:
- explicit CommitExploreWindow(QWidget *parent, BasicMainWindow *mainwin, GitObjectCache *objcache, Git::CommitItem const *commit);
+ explicit CommitExploreWindow(QWidget *parent, MainWindow *mainwin, GitObjectCache *objcache, Git::CommitItem const *commit);
~CommitExploreWindow() override;
void clearContent();
private slots:
void on_treeWidget_itemExpanded(QTreeWidgetItem *item);
void on_treeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
void on_listWidget_itemDoubleClicked(QListWidgetItem *item);
void on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
void on_verticalScrollBar_valueChanged(int);
void on_horizontalScrollBar_valueChanged(int);
void on_listWidget_customContextMenuRequested(const QPoint &pos);
};
#endif // COMMITEXPLOREWINDOW_H
diff --git a/src/CommitPropertyDialog.cpp b/src/CommitPropertyDialog.cpp
index 68299af..35a64e1 100644
--- a/src/CommitPropertyDialog.cpp
+++ b/src/CommitPropertyDialog.cpp
@@ -1,171 +1,171 @@
#include "CommitPropertyDialog.h"
#include "ui_CommitPropertyDialog.h"
#include "ApplicationGlobal.h"
#include "AvatarLoader.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "common/misc.h"
#include "gpg.h"
#include "main.h"
struct CommitPropertyDialog::Private {
- BasicMainWindow *mainwindow;
+ MainWindow *mainwindow;
Git::CommitItem commit;
AvatarLoader avatar_loader;
};
-void CommitPropertyDialog::init(BasicMainWindow *mw)
+void CommitPropertyDialog::init(MainWindow *mw, RepositoryWrapperFrame *frame)
{
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
ui->pushButton_jump->setVisible(false);
m->mainwindow = mw;
ui->lineEdit_message->setText(m->commit.message);
ui->lineEdit_commit_id->setText(m->commit.commit_id);
ui->lineEdit_date->setText(misc::makeDateTimeString(m->commit.commit_date));
ui->lineEdit_author->setText(m->commit.author);
ui->lineEdit_mail->setText(m->commit.email);
QString text;
for (QString const &id : m->commit.parent_ids) {
text += id + '\n';
}
ui->plainTextEdit_parent_ids->setPlainText(text);
gpg::Data key;
int n1 = m->commit.fingerprint.size();
if (n1 > 0) {
QList<gpg::Data> keys;
if (gpg::listKeys(global->gpg_command, &keys)) {
for (gpg::Data const &k : keys) {
int n2 = k.fingerprint.size();
if (n2 > 0) {
int n = std::min(n1, n2);
char const *p1 = m->commit.fingerprint.data() + n1 - n;
char const *p2 = k.fingerprint.data() + n2 - n;
if (memcmp(p1, p2, n) == 0) {
key = k;
break;
}
}
}
} else {
qDebug() << "Failed to get gpg keys";
}
if (key.id.isEmpty()) {
// gpgコマンドが登録されていないなど、keyidが取得できなかったとき
key.id = tr("<Unknown>");
}
}
if (key.id.isEmpty()) {
ui->frame_sign->setVisible(false);
} else {
{
int w = ui->label_signature_icon->width();
int h = ui->label_signature_icon->width();
QIcon icon = mainwindow()->verifiedIcon(m->commit.signature);
ui->label_signature_icon->setPixmap(icon.pixmap(w, h));
}
ui->lineEdit_sign_id->setText(key.id);
ui->lineEdit_sign_name->setText(key.name);
ui->lineEdit_sign_mail->setText(key.mail);
}
m->avatar_loader.start(mainwindow());
- connect(&m->avatar_loader, &AvatarLoader::updated, [&](){
- updateAvatar(false);
+ connect(&m->avatar_loader, &AvatarLoader::updated, [&](RepositoryWrapperFrameP frame){
+ updateAvatar(frame.pointer, false);
});
- updateAvatar(true);
+ updateAvatar(frame, true);
}
-void CommitPropertyDialog::updateAvatar(bool request)
+void CommitPropertyDialog::updateAvatar(RepositoryWrapperFrame *frame, bool request)
{
if (!mainwindow()->isOnlineMode()) return;
- auto SetAvatar = [&](QString const &email, QLabel *label){
+ auto SetAvatar = [&](RepositoryWrapperFrame *frame, QString const &email, QLabel *label){
if (mainwindow()->appsettings()->get_committer_icon) {
label->setFixedSize(QSize(48, 48));
- QIcon icon = m->avatar_loader.fetch(email.toStdString(), request);
+ QIcon icon = m->avatar_loader.fetch(frame, email.toStdString(), request);
setAvatar(icon, label);
} else {
label->setVisible(false);
}
};
- SetAvatar(ui->lineEdit_mail->text(), ui->label_user_avatar);
- SetAvatar(ui->lineEdit_sign_mail->text(), ui->label_sign_avatar);
+ SetAvatar(frame, ui->lineEdit_mail->text(), ui->label_user_avatar);
+ SetAvatar(frame, ui->lineEdit_sign_mail->text(), ui->label_sign_avatar);
}
-CommitPropertyDialog::CommitPropertyDialog(QWidget *parent, BasicMainWindow *mw, Git::CommitItem const *commit)
+CommitPropertyDialog::CommitPropertyDialog(QWidget *parent, MainWindow *mw, RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
: QDialog(parent)
, ui(new Ui::CommitPropertyDialog)
, m(new Private)
{
ui->setupUi(this);
m->commit = *commit;
- init(mw);
+ init(mw, frame);
}
-CommitPropertyDialog::CommitPropertyDialog(QWidget *parent, BasicMainWindow *mw, QString const &commit_id)
+CommitPropertyDialog::CommitPropertyDialog(QWidget *parent, MainWindow *mw, RepositoryWrapperFrame *frame, QString const &commit_id)
: QDialog(parent)
, ui(new Ui::CommitPropertyDialog)
, m(new Private)
{
ui->setupUi(this);
mw->queryCommit(commit_id, &m->commit);
- init(mw);
+ init(mw, frame);
}
CommitPropertyDialog::~CommitPropertyDialog()
{
m->avatar_loader.stop();
delete m;
delete ui;
}
-BasicMainWindow *CommitPropertyDialog::mainwindow()
+MainWindow *CommitPropertyDialog::mainwindow()
{
return m->mainwindow;
}
void CommitPropertyDialog::setAvatar(QIcon const &icon, QLabel *label)
{
QPixmap pm = icon.pixmap(label->size());
label->setPixmap(pm);
}
void CommitPropertyDialog::showCheckoutButton(bool f)
{
ui->pushButton_checkout->setVisible(f);
}
void CommitPropertyDialog::showJumpButton(bool f)
{
ui->pushButton_jump->setVisible(f);
}
void CommitPropertyDialog::on_pushButton_checkout_clicked()
{
- mainwindow()->checkout(this, &m->commit, [&](){ hide(); });
+ mainwindow()->checkout(mainwindow()->frame(), this, &m->commit, [&](){ hide(); });
done(QDialog::Rejected);
}
void CommitPropertyDialog::on_pushButton_jump_clicked()
{
- mainwindow()->jumpToCommit(m->commit.commit_id);
+ mainwindow()->jumpToCommit(mainwindow()->frame(), m->commit.commit_id);
done(QDialog::Accepted);
}
void CommitPropertyDialog::on_pushButton_details_clicked()
{
mainwindow()->execCommitViewWindow(&m->commit);
}
void CommitPropertyDialog::on_pushButton_explorer_clicked()
{
- mainwindow()->execCommitExploreWindow(this, &m->commit);
+ mainwindow()->execCommitExploreWindow(mainwindow()->frame(), this, &m->commit);
}
diff --git a/src/CommitPropertyDialog.h b/src/CommitPropertyDialog.h
index fe8af68..5e99fba 100644
--- a/src/CommitPropertyDialog.h
+++ b/src/CommitPropertyDialog.h
@@ -1,41 +1,43 @@
#ifndef COMMITPROPERTYDIALOG_H
#define COMMITPROPERTYDIALOG_H
#include <QDialog>
#include "Git.h"
+class MainWindow;
+class RepositoryWrapperFrame;
class QLabel;
namespace Ui {
class CommitPropertyDialog;
}
class BasicMainWindow;
class CommitPropertyDialog : public QDialog {
Q_OBJECT
private:
struct Private;
Private *m;
public:
- explicit CommitPropertyDialog(QWidget *parent, BasicMainWindow *mw, Git::CommitItem const *commit);
- explicit CommitPropertyDialog(QWidget *parent, BasicMainWindow *mw, QString const &commit_id);
+ explicit CommitPropertyDialog(QWidget *parent, MainWindow *mw, RepositoryWrapperFrame *frame, Git::CommitItem const *commit);
+ explicit CommitPropertyDialog(QWidget *parent, MainWindow *mw, RepositoryWrapperFrame *frame, QString const &commit_id);
~CommitPropertyDialog() override;
void showCheckoutButton(bool f);
void showJumpButton(bool f);
private slots:
void on_pushButton_checkout_clicked();
void on_pushButton_details_clicked();
void on_pushButton_explorer_clicked();
void on_pushButton_jump_clicked();
private:
Ui::CommitPropertyDialog *ui;
- void init(BasicMainWindow *mw);
- BasicMainWindow *mainwindow();
+ void init(MainWindow *mw, RepositoryWrapperFrame *frame);
+ MainWindow *mainwindow();
void setAvatar(const QIcon &icon, QLabel *label);
- void updateAvatar(bool request);
+ void updateAvatar(RepositoryWrapperFrame *frame, bool request);
};
#endif // COMMITPROPERTYDIALOG_H
diff --git a/src/CommitViewWindow.cpp b/src/CommitViewWindow.cpp
index f7cdea4..5b5c0b4 100644
--- a/src/CommitViewWindow.cpp
+++ b/src/CommitViewWindow.cpp
@@ -1,71 +1,71 @@
#include "CommitViewWindow.h"
#include "ui_CommitViewWindow.h"
#include <QMenu>
struct CommitViewWindow::Private {
Git::CommitItem const *commit = nullptr;
QList<Git::Diff> diff_list;
};
-CommitViewWindow::CommitViewWindow(BasicMainWindow *parent, Git::CommitItem const *commit)
+CommitViewWindow::CommitViewWindow(MainWindow *parent, Git::CommitItem const *commit)
: QDialog(parent)
, ui(new Ui::CommitViewWindow)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
m->commit = commit;
ui->widget_diff->bind(mainwindow());
ui->lineEdit_message->setText(m->commit->message);
ui->lineEdit_id->setText(m->commit->commit_id);
- mainwindow()->updateFilesList(m->commit->commit_id, &m->diff_list, ui->listWidget_files);
+ mainwindow()->updateFilesList2(mainwindow()->frame(), m->commit->commit_id, &m->diff_list, ui->listWidget_files);
ui->listWidget_files->setCurrentRow(0);
}
CommitViewWindow::~CommitViewWindow()
{
delete m;
delete ui;
}
-BasicMainWindow *CommitViewWindow::mainwindow()
+MainWindow *CommitViewWindow::mainwindow()
{
- return qobject_cast<BasicMainWindow *>(parent());
+ return qobject_cast<MainWindow *>(parent());
}
void CommitViewWindow::on_listWidget_files_currentRowChanged(int currentRow)
{
if (currentRow >= 0 && currentRow < m->diff_list.size()) {
Git::Diff const &diff = m->diff_list[currentRow];
ui->widget_diff->updateDiffView(diff, false);
}
}
void CommitViewWindow::on_listWidget_files_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = mainwindow()->git();
if (!mainwindow()->isValidWorkingCopy(g)) return;
QMenu menu;
QAction *a_history = menu.addAction(tr("History"));
QAction *a_properties = mainwindow()->addMenuActionProperty(&menu);
QPoint pt = ui->listWidget_files->mapToGlobal(pos) + QPoint(8, -8);
QAction *a = menu.exec(pt);
if (a) {
QListWidgetItem *item = ui->listWidget_files->currentItem();
if (a == a_history) {
mainwindow()->execFileHistory(item);
} else if (a == a_properties) {
- mainwindow()->execFilePropertyDialog(item);
+ mainwindow()->showObjectProperty(item);
}
}
}
diff --git a/src/CommitViewWindow.h b/src/CommitViewWindow.h
index 6097663..8c5b3c1 100644
--- a/src/CommitViewWindow.h
+++ b/src/CommitViewWindow.h
@@ -1,33 +1,33 @@
#ifndef COMMITVIEWWINDOW_H
#define COMMITVIEWWINDOW_H
#include "Git.h"
#include <QDialog>
-class BasicMainWindow;
+class MainWindow;
namespace Ui {
class CommitViewWindow;
}
class CommitViewWindow : public QDialog {
Q_OBJECT
private:
struct Private;
Private *m;
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
public:
- explicit CommitViewWindow(BasicMainWindow *parent, Git::CommitItem const *commit);
+ explicit CommitViewWindow(MainWindow *parent, Git::CommitItem const *commit);
~CommitViewWindow() override;
private slots:
void on_listWidget_files_currentRowChanged(int currentRow);
void on_listWidget_files_customContextMenuRequested(const QPoint &pos);
private:
Ui::CommitViewWindow *ui;
};
#endif // COMMITVIEWWINDOW_H
diff --git a/src/ConfigSigningDialog.cpp b/src/ConfigSigningDialog.cpp
index 6f005ae..2c8d67c 100644
--- a/src/ConfigSigningDialog.cpp
+++ b/src/ConfigSigningDialog.cpp
@@ -1,76 +1,76 @@
#include "ConfigSigningDialog.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "ui_ConfigSigningDialog.h"
-ConfigSigningDialog::ConfigSigningDialog(QWidget *parent, BasicMainWindow *mw, bool local_enable)
+ConfigSigningDialog::ConfigSigningDialog(QWidget *parent, MainWindow *mw, bool local_enable)
: QDialog(parent)
, ui(new Ui::ConfigSigningDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
mainwindow_ = mw;
if (!mainwindow()->git()->isValidWorkingCopy()) {
local_enable = false;
}
ui->label_local->setVisible(local_enable);
ui->comboBox_sign_local->setVisible(local_enable);
updateSigningInfo();
}
ConfigSigningDialog::~ConfigSigningDialog()
{
delete ui;
}
-BasicMainWindow *ConfigSigningDialog::mainwindow()
+MainWindow *ConfigSigningDialog::mainwindow()
{
return mainwindow_;
}
void ConfigSigningDialog::updateSigningInfo()
{
GitPtr g = mainwindow()->git();
auto InitComboBox = [](QComboBox *cb, Git::SignPolicy pol){
cb->addItem("unset");
cb->addItem("false");
cb->addItem("true");
QString t;
if (pol == Git::SignPolicy::Unset) {
t = "unset";
} else if (pol == Git::SignPolicy::False) {
t = "false";
} else if (pol == Git::SignPolicy::True) {
t = "true";
}
cb->setCurrentText(t);
};
gpol_ = g->signPolicy(Git::Source::Global);
lpol_ = g->signPolicy(Git::Source::Local);
InitComboBox(ui->comboBox_sign_global, gpol_);
InitComboBox(ui->comboBox_sign_local, lpol_);
}
void ConfigSigningDialog::accept()
{
GitPtr g = mainwindow()->git();
auto SetSignPolicy = [&](QComboBox *cb, Git::Source src, Git::SignPolicy oldpol){
Git::SignPolicy pol = Git::SignPolicy::Unset;
QString s = cb->currentText();
if (s == "false") {
pol = Git::SignPolicy::False;
} else if (s == "true") {
pol = Git::SignPolicy::True;
}
if (pol != oldpol) g->setSignPolicy(src, pol);
};
SetSignPolicy(ui->comboBox_sign_global, Git::Source::Global, gpol_);
SetSignPolicy(ui->comboBox_sign_local, Git::Source::Local, lpol_);
QDialog::accept();
}
diff --git a/src/ConfigSigningDialog.h b/src/ConfigSigningDialog.h
index 40bc4d5..5ce9722 100644
--- a/src/ConfigSigningDialog.h
+++ b/src/ConfigSigningDialog.h
@@ -1,35 +1,35 @@
#ifndef CONFIGSIGNINGDIALOG_H
#define CONFIGSIGNINGDIALOG_H
#include "Git.h"
#include "gpg.h"
#include <QDialog>
-class BasicMainWindow;
+class MainWindow;
namespace Ui {
class ConfigSigningDialog;
}
class ConfigSigningDialog : public QDialog {
Q_OBJECT
public:
- explicit ConfigSigningDialog(QWidget *parent, BasicMainWindow *mw, bool local_enable);
+ explicit ConfigSigningDialog(QWidget *parent, MainWindow *mw, bool local_enable);
~ConfigSigningDialog() override;
private:
Ui::ConfigSigningDialog *ui;
- BasicMainWindow *mainwindow_;
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow_;
+ MainWindow *mainwindow();
Git::SignPolicy gpol_;
Git::SignPolicy lpol_;
void updateSigningInfo();
public slots:
void accept() override;
};
#endif // CONFIGSIGNINGDIALOG_H
diff --git a/src/CreateRepositoryDialog.cpp b/src/CreateRepositoryDialog.cpp
index 3720836..9722413 100644
--- a/src/CreateRepositoryDialog.cpp
+++ b/src/CreateRepositoryDialog.cpp
@@ -1,132 +1,132 @@
#include "CreateRepositoryDialog.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "common/misc.h"
#include "ui_CreateRepositoryDialog.h"
#include <QFileDialog>
#include <QMessageBox>
#include "Git.h"
-CreateRepositoryDialog::CreateRepositoryDialog(BasicMainWindow *parent, QString const &dir) :
+CreateRepositoryDialog::CreateRepositoryDialog(MainWindow *parent, QString const &dir) :
QDialog(parent),
ui(new Ui::CreateRepositoryDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
already_exists_ = tr("A valid git repository already exists there.");
ui->lineEdit_path->setText(dir);
ui->groupBox_remote->setChecked(false);
ui->lineEdit_remote_name->setText("origin");
validate(false);
}
CreateRepositoryDialog::~CreateRepositoryDialog()
{
delete ui;
}
-BasicMainWindow *CreateRepositoryDialog::mainwindow()
+MainWindow *CreateRepositoryDialog::mainwindow()
{
- return qobject_cast<BasicMainWindow *>(parent());
+ return qobject_cast<MainWindow *>(parent());
}
void CreateRepositoryDialog::accept()
{
QString path = ui->lineEdit_path->text();
if (!QFileInfo(path).isDir()) {
QMessageBox::warning(this, tr("Create Repository"), tr("The specified path is not a directory."));
return;
}
if (Git::isValidWorkingCopy(path)) {
QMessageBox::warning(this, tr("Create Repository"), already_exists_);
return;
}
if (!QFileInfo(path).isDir()) {
QMessageBox::warning(this, tr("Create Repository"), tr("The specified path is not a directory."));
return;
}
if (remoteName().indexOf('\"') >= 0 || remoteURL().indexOf('\"') >= 0) { // 手抜き
QMessageBox::warning(this, tr("Create Repository"), tr("Remote name is invalid."));
return;
}
done(QDialog::Accepted);
}
void CreateRepositoryDialog::on_pushButton_browse_path_clicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Destination Path"), mainwindow()->defaultWorkingDir());
if (!path.isEmpty()) {
path = misc::normalizePathSeparator(path);
ui->lineEdit_path->setText(path);
}
}
QString CreateRepositoryDialog::path() const
{
return ui->lineEdit_path->text();
}
QString CreateRepositoryDialog::name() const
{
return ui->lineEdit_name->text();
}
QString CreateRepositoryDialog::remoteName() const
{
return ui->groupBox_remote->isChecked() ? ui->lineEdit_remote_name->text() : QString();
}
QString CreateRepositoryDialog::remoteURL() const
{
return ui->groupBox_remote->isChecked() ? ui->lineEdit_remote_url->text() : QString();
}
void CreateRepositoryDialog::validate(bool change_name)
{
QString path = this->path();
{
QString text;
if (Git::isValidWorkingCopy(path)) {
text = already_exists_;
}
ui->label_warning->setText(text);
}
if (change_name) {
int i = path.lastIndexOf('/');
int j = path.lastIndexOf('\\');
if (i < j) i = j;
if (i >= 0) {
QString name = path.mid(i + 1);
ui->lineEdit_name->setText(name);
}
}
}
void CreateRepositoryDialog::on_lineEdit_path_textChanged(QString const &)
{
validate(true);
}
void CreateRepositoryDialog::on_lineEdit_name_textChanged(QString const &)
{
validate(false);
}
void CreateRepositoryDialog::on_groupBox_remote_toggled(bool)
{
validate(false);
}
void CreateRepositoryDialog::on_pushButton_test_repo_clicked()
{
QString url = ui->lineEdit_remote_url->text();
- mainwindow()->testRemoteRepositoryValidity(url);
+ mainwindow()->testRemoteRepositoryValidity(url, {});
validate(false);
}
diff --git a/src/CreateRepositoryDialog.h b/src/CreateRepositoryDialog.h
index 8156ff3..be8c7c4 100644
--- a/src/CreateRepositoryDialog.h
+++ b/src/CreateRepositoryDialog.h
@@ -1,42 +1,42 @@
#ifndef CREATEREPOSITORYDIALOG_H
#define CREATEREPOSITORYDIALOG_H
#include <QDialog>
namespace Ui {
class CreateRepositoryDialog;
}
-class BasicMainWindow;
+class MainWindow;
class CreateRepositoryDialog : public QDialog {
Q_OBJECT
private:
QString already_exists_;
public:
- explicit CreateRepositoryDialog(BasicMainWindow *parent, QString const &dir = QString());
+ explicit CreateRepositoryDialog(MainWindow *parent, QString const &dir = QString());
~CreateRepositoryDialog() override;
QString path() const;
QString name() const;
QString remoteName() const;
QString remoteURL() const;
private slots:
void on_lineEdit_path_textChanged(QString const &arg1);
void on_pushButton_browse_path_clicked();
void on_lineEdit_name_textChanged(QString const &arg1);
void on_groupBox_remote_toggled(bool arg1);
void on_pushButton_test_repo_clicked();
private:
Ui::CreateRepositoryDialog *ui;
void validate(bool change_name);
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
public slots:
void accept() override;
};
#endif // CREATEREPOSITORYDIALOG_H
diff --git a/src/CreateRepositoryDialog.ui b/src/CreateRepositoryDialog.ui
index 47af97d..31de916 100644
--- a/src/CreateRepositoryDialog.ui
+++ b/src/CreateRepositoryDialog.ui
@@ -1,208 +1,208 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CreateRepositoryDialog</class>
<widget class="QDialog" name="CreateRepositoryDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>343</width>
- <height>243</height>
+ <width>664</width>
+ <height>442</height>
</rect>
</property>
<property name="windowTitle">
<string>Create Repository</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Path</string>
</property>
</widget>
</item>
<item>
<widget class="DirectoryLineEdit" name="lineEdit_path"/>
</item>
<item>
<widget class="QPushButton" name="pushButton_browse_path">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_warning">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_bookmark">
<property name="title">
<string>Bookmark</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_name"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_remote">
<property name="title">
<string>Remote</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_remote_name"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>URL</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="RepositoryLineEdit" name="lineEdit_remote_url"/>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pushButton_test_repo">
<property name="text">
<string>Test</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_ok">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RepositoryLineEdit</class>
<extends>QLineEdit</extends>
<header>RepositoryLineEdit.h</header>
</customwidget>
<customwidget>
<class>DirectoryLineEdit</class>
<extends>QLineEdit</extends>
<header>DirectoryLineEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>pushButton_cancel</sender>
<signal>clicked()</signal>
<receiver>CreateRepositoryDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>389</x>
<y>206</y>
</hint>
<hint type="destinationlabel">
<x>268</x>
<y>139</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_ok</sender>
<signal>clicked()</signal>
<receiver>CreateRepositoryDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>308</x>
<y>206</y>
</hint>
<hint type="destinationlabel">
<x>346</x>
<y>135</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/EditGitIgnoreDialog.cpp b/src/EditGitIgnoreDialog.cpp
index 3345e40..18e1093 100644
--- a/src/EditGitIgnoreDialog.cpp
+++ b/src/EditGitIgnoreDialog.cpp
@@ -1,65 +1,65 @@
#include "EditGitIgnoreDialog.h"
#include "MainWindow.h"
#include "TextEditDialog.h"
#include "ui_EditGitIgnoreDialog.h"
#include <QFileInfo>
EditGitIgnoreDialog::EditGitIgnoreDialog(MainWindow *parent, QString const &gitignore_path, QString const &file)
: QDialog(parent)
, ui(new Ui::EditGitIgnoreDialog)
, gitignore_path(gitignore_path)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
QFileInfo info(file);
ui->radioButton_1->setText(file);
ui->radioButton_2->setText("*." + info.suffix());
int i = file.indexOf('/');
if (i > 0) {
ui->radioButton_3->setText(file.mid(0, i + 1));
int j = file.lastIndexOf('/');
if (i < j) {
ui->radioButton_4->setText(file.mid(0, j + 1));
} else {
ui->radioButton_4->setVisible(false);
}
} else {
ui->radioButton_3->setVisible(false);
ui->radioButton_4->setVisible(false);
}
ui->radioButton_1->click();
}
EditGitIgnoreDialog::~EditGitIgnoreDialog()
{
delete ui;
}
MainWindow *EditGitIgnoreDialog::mainwindow()
{
return qobject_cast<MainWindow *>(parent());
}
QString EditGitIgnoreDialog::text() const
{
if (ui->radioButton_1->isChecked()) return ui->radioButton_1->text();
if (ui->radioButton_2->isChecked()) return ui->radioButton_2->text();
if (ui->radioButton_3->isChecked()) return ui->radioButton_3->text();
if (ui->radioButton_4->isChecked()) return ui->radioButton_4->text();
return QString();
}
void EditGitIgnoreDialog::on_pushButton_edit_file_clicked()
{
if (TextEditDialog::editFile(this, gitignore_path, ".gitignore", text() + '\n')) {
- mainwindow()->updateCurrentFilesList();
+ mainwindow()->updateCurrentFilesList(mainwindow()->frame());
done(QDialog::Rejected);
}
}
diff --git a/src/EditRemoteDialog.cpp b/src/EditRemoteDialog.cpp
index 4632fa2..e7653b4 100644
--- a/src/EditRemoteDialog.cpp
+++ b/src/EditRemoteDialog.cpp
@@ -1,66 +1,84 @@
#include "EditRemoteDialog.h"
#include "ui_EditRemoteDialog.h"
+#include "MainWindow.h"
+#include <QFileDialog>
+#include <QStandardPaths>
-#include "BasicMainWindow.h"
-
-EditRemoteDialog::EditRemoteDialog(BasicMainWindow *parent, Operation op) :
- QDialog(parent),
- ui(new Ui::EditRemoteDialog)
+EditRemoteDialog::EditRemoteDialog(MainWindow *parent, Operation op, const Git::Context *gcx)
+ : QDialog(parent)
+ , ui(new Ui::EditRemoteDialog)
{
ui->setupUi(this);
if (op == RemoteSet) {
ui->lineEdit_name->setReadOnly(true);
ui->lineEdit_name->setEnabled(false);
}
+
+ ui->advanced_option->setSshKeyOverrigingEnabled(!gcx->ssh_command.isEmpty());
}
EditRemoteDialog::~EditRemoteDialog()
{
delete ui;
}
-BasicMainWindow *EditRemoteDialog::mainwindow()
+MainWindow *EditRemoteDialog::mainwindow()
{
- return qobject_cast<BasicMainWindow *>(parent());
+ return qobject_cast<MainWindow *>(parent());
}
void EditRemoteDialog::setName(QString const &s) const
{
ui->lineEdit_name->setText(s);
}
void EditRemoteDialog::setUrl(QString const &s) const
{
ui->lineEdit_url->setText(s);
}
+void EditRemoteDialog::setSshKey(QString const &s) const
+{
+ ui->advanced_option->setSshKey(s);
+}
+
QString EditRemoteDialog::name() const
{
return ui->lineEdit_name->text();
}
QString EditRemoteDialog::url() const
{
return ui->lineEdit_url->text();
}
+QString EditRemoteDialog::sshKey() const
+{
+ return ui->advanced_option->sshKey();
+}
+
int EditRemoteDialog::exec()
{
if (ui->lineEdit_name->text().isEmpty()) {
ui->lineEdit_name->setFocus();
} else {
ui->lineEdit_url->setFocus();
}
return QDialog::exec();
}
void EditRemoteDialog::on_pushButton_test_clicked()
{
QString url = ui->lineEdit_url->text();
- if (mainwindow()->testRemoteRepositoryValidity(url)) {
+ if (mainwindow()->testRemoteRepositoryValidity(url, sshKey())) {
ui->pushButton_ok->setFocus();
} else {
ui->lineEdit_url->setFocus();
}
}
+
+
+
+
+
diff --git a/src/EditRemoteDialog.h b/src/EditRemoteDialog.h
index 52d8cfd..9bb1e00 100644
--- a/src/EditRemoteDialog.h
+++ b/src/EditRemoteDialog.h
@@ -1,36 +1,39 @@
#ifndef EDITREMOTEDIALOG_H
#define EDITREMOTEDIALOG_H
#include <QDialog>
+#include "Git.h"
-class BasicMainWindow;
+class MainWindow;
namespace Ui {
class EditRemoteDialog;
}
class EditRemoteDialog : public QDialog {
Q_OBJECT
public:
enum Operation {
RemoteAdd,
RemoteSet,
};
private:
Ui::EditRemoteDialog *ui;
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
public:
- explicit EditRemoteDialog(BasicMainWindow *parent, Operation op);
+ explicit EditRemoteDialog(MainWindow *parent, Operation op, const Git::Context *gcx);
~EditRemoteDialog() override;
void setName(QString const &s) const;
void setUrl(QString const &s) const;
+ void setSshKey(const QString &s) const;
QString name() const;
QString url() const;
+ QString sshKey() const;
int exec() override;
private slots:
void on_pushButton_test_clicked();
};
#endif // EDITREMOTEDIALOG_H
diff --git a/src/EditRemoteDialog.ui b/src/EditRemoteDialog.ui
index 1085381..52dc8db 100644
--- a/src/EditRemoteDialog.ui
+++ b/src/EditRemoteDialog.ui
@@ -1,145 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditRemoteDialog</class>
<widget class="QDialog" name="EditRemoteDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>574</width>
- <height>129</height>
+ <width>700</width>
+ <height>347</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit Remote</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Remote</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>URL</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="lineEdit_name">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="RepositoryLineEdit" name="lineEdit_url"/>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pushButton_test">
<property name="text">
<string>&amp;Test</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+ <item>
+ <widget class="RemoteAdvancedOptionWidget" name="advanced_option" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>106</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_ok">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RepositoryLineEdit</class>
<extends>QLineEdit</extends>
<header>RepositoryLineEdit.h</header>
</customwidget>
+ <customwidget>
+ <class>RemoteAdvancedOptionWidget</class>
+ <extends>QWidget</extends>
+ <header>RemoteAdvancedOptionWidget.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<tabstops>
<tabstop>lineEdit_name</tabstop>
<tabstop>lineEdit_url</tabstop>
<tabstop>pushButton_test</tabstop>
<tabstop>pushButton_ok</tabstop>
<tabstop>pushButton_close</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>pushButton_ok</sender>
<signal>clicked()</signal>
<receiver>EditRemoteDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>446</x>
<y>104</y>
</hint>
<hint type="destinationlabel">
<x>394</x>
<y>97</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_close</sender>
<signal>clicked()</signal>
<receiver>EditRemoteDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>542</x>
<y>101</y>
</hint>
<hint type="destinationlabel">
<x>568</x>
<y>97</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/EditTagsDialog.cpp b/src/EditTagsDialog.cpp
index c2bf8e5..3a1c313 100644
--- a/src/EditTagsDialog.cpp
+++ b/src/EditTagsDialog.cpp
@@ -1,78 +1,78 @@
#include "InputNewTagDialog.h"
#include "MainWindow.h"
#include "EditTagsDialog.h"
#include "ui_EditTagsDialog.h"
EditTagsDialog::EditTagsDialog(MainWindow *parent, Git::CommitItem const *commit) :
QDialog(parent),
ui(new Ui::EditTagsDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
commit_ = commit;
ui->lineEdit_commit_id->setText(commit_->commit_id);
ui->lineEdit_message->setText(commit_->message);
updateTagList();
}
EditTagsDialog::~EditTagsDialog()
{
delete ui;
}
MainWindow *EditTagsDialog::mainwindow()
{
return qobject_cast<MainWindow *>(parent());
}
QList<Git::Tag> EditTagsDialog::queryTagList()
{
- return mainwindow()->queryTagList();
+ return mainwindow()->queryTagList(mainwindow()->frame());
}
void EditTagsDialog::updateTagList()
{
ui->listWidget->clear();
QList<Git::Tag> list = queryTagList();
for (Git::Tag const &t : list) {
auto item = new QListWidgetItem(t.name);
ui->listWidget->addItem(item);
}
ui->listWidget->sortItems();
}
QStringList EditTagsDialog::selectedTags()
{
QStringList list;
int n = ui->listWidget->count();
for (int i = 0; i < n; i++) {
QListWidgetItem *item = ui->listWidget->item(i);
if (item->isSelected()) {
list.push_back(item->text());
}
}
return list;
}
void EditTagsDialog::on_pushButton_add_clicked()
{
InputNewTagDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
QString text = dlg.text();
- mainwindow()->addTag(text);
+ mainwindow()->addTag(mainwindow()->frame(), text);
updateTagList();
}
}
void EditTagsDialog::on_pushButton_delete_clicked()
{
QStringList list = selectedTags();
- mainwindow()->deleteTags(list);
+ mainwindow()->deleteTags(mainwindow()->frame(), list);
updateTagList();
}
diff --git a/src/FileDiffWidget.cpp b/src/FileDiffWidget.cpp
index 604cd8e..bb6a5d0 100644
--- a/src/FileDiffWidget.cpp
+++ b/src/FileDiffWidget.cpp
@@ -1,745 +1,793 @@
#include "ApplicationGlobal.h"
#include "BigDiffWindow.h"
#include "FileDiffWidget.h"
#include "GitDiff.h"
#include "MainWindow.h"
#include "Theme.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include "ui_FileDiffWidget.h"
#include <QBuffer>
#include <QDebug>
#include <QKeyEvent>
#include <QMenu>
#include <QPainter>
#include <QStyle>
#include <QTextCodec>
#include <memory>
enum {
DiffIndexRole = Qt::UserRole,
};
struct FileDiffWidget::Private {
- BasicMainWindow *mainwindow = nullptr;
+ MainWindow *mainwindow = nullptr;
FileDiffWidget::InitParam_ init_param_;
Git::CommitItemList commit_item_list;
std::vector<std::string> original_lines;
TextEditorEnginePtr engine_left;
TextEditorEnginePtr engine_right;
TextDiffLineList left_lines;
TextDiffLineList right_lines;
int max_line_length = 0;
int term_cursor_row = 0;
int term_cursor_col = 0;
QTextCodec *text_codec = nullptr;
};
FileDiffWidget::FileDiffWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::FileDiffWidget)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
ui->widget_diff_slider->init([&](DiffPane pane, int width, int height){
return makeDiffPixmap(pane, width, height);
}, global->theme);
connect(ui->widget_diff_slider, &FileDiffSliderWidget::valueChanged, this, &FileDiffWidget::scrollTo);
connect(ui->widget_diff_left->texteditor(), &TextEditorWidget::moved, this, &FileDiffWidget::onMoved);
connect(ui->widget_diff_right->texteditor(), &TextEditorWidget::moved, this, &FileDiffWidget::onMoved);
setFocusAcceptable(Qt::ClickFocus);
QWidget::setTabOrder(ui->widget_diff_slider, ui->widget_diff_left);
QWidget::setTabOrder(ui->widget_diff_left, ui->widget_diff_right);
int n = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
ui->toolButton_fullscreen->setFixedSize(n, n);
setMaximizeButtonEnabled(true);
m->engine_left = std::make_shared<TextEditorEngine>();
m->engine_right = std::make_shared<TextEditorEngine>();
ui->widget_diff_left->setDiffMode(m->engine_left, ui->verticalScrollBar, ui->horizontalScrollBar);
ui->widget_diff_right->setDiffMode(m->engine_right, ui->verticalScrollBar, ui->horizontalScrollBar);
setViewType(FileViewType::None);
}
FileDiffWidget::~FileDiffWidget()
{
delete m;
delete ui;
}
void FileDiffWidget::setViewType(FileViewType type)
{
ui->widget_diff_left->setViewType(type);
ui->widget_diff_right->setViewType(type);
}
void FileDiffWidget::setMaximizeButtonEnabled(bool f)
{
ui->toolButton_fullscreen->setVisible(f);
ui->toolButton_fullscreen->setEnabled(f);
}
FileDiffWidget::ViewStyle FileDiffWidget::viewstyle() const
{
return m->init_param_.view_style;
}
-void FileDiffWidget::bind(BasicMainWindow *mw)
+void FileDiffWidget::bind(MainWindow *mw)
{
Q_ASSERT(mw);
m->mainwindow = mw;
ui->widget_diff_left->bind(mw, this, ui->verticalScrollBar, ui->horizontalScrollBar, mw->themeForTextEditor());
ui->widget_diff_right->bind(mw, this, ui->verticalScrollBar, ui->horizontalScrollBar, mw->themeForTextEditor());
connect(ui->verticalScrollBar, &QAbstractSlider::valueChanged, this, &FileDiffWidget::onVerticalScrollValueChanged);
connect(ui->horizontalScrollBar, &QAbstractSlider::valueChanged, this, &FileDiffWidget::onHorizontalScrollValueChanged);
}
-BasicMainWindow *FileDiffWidget::mainwindow()
+MainWindow *FileDiffWidget::mainwindow()
{
return m->mainwindow;
}
GitPtr FileDiffWidget::git()
{
if (!mainwindow()) {
qDebug() << "Maybe, you forgot to call FileDiffWidget::bind() ?";
return GitPtr();
}
return mainwindow()->git();
}
Git::Object FileDiffWidget::cat_file(GitPtr const &/*g*/, QString const &id)
{
- return mainwindow()->cat_file(id);
+ return mainwindow()->cat_file(mainwindow()->frame(), id);
}
int FileDiffWidget::totalTextLines() const
{
return m->engine_left->document.lines.size();
}
void FileDiffWidget::clearDiffView()
{
ui->widget_diff_slider->clear(false);
}
int FileDiffWidget::fileviewHeight() const
{
return ui->widget_diff_left->height();
}
void FileDiffWidget::resetScrollBarValue()
{
ui->verticalScrollBar->setValue(0);
ui->horizontalScrollBar->setValue(0);
}
void FileDiffWidget::scrollToBottom()
{
QScrollBar *sb = ui->verticalScrollBar;
sb->setValue(sb->maximum());
}
void FileDiffWidget::updateSliderCursor()
{
if (viewstyle() == SideBySideText) {
ui->widget_diff_slider->clear(true);
}
}
void FileDiffWidget::updateControls()
{
updateSliderCursor();
}
void FileDiffWidget::makeSideBySideDiffData(Git::Diff const &diff, std::vector<std::string> const &original_lines, TextDiffLineList *left_lines, TextDiffLineList *right_lines)
{
left_lines->clear();
right_lines->clear();
m->original_lines = original_lines;
size_t linenum = original_lines.size();
std::vector<HunkItem> hunks;
int number = 0;
for (auto it = diff.hunks.begin(); it != diff.hunks.end(); it++, number++) {
std::string at = it->at;
if (strncmp(at.c_str(), "@@ -", 4) == 0) {
size_t pos = 0;
size_t len = 0;
char const *p = at.c_str() + 4;
auto ParseNumber = [&](){
size_t v = 0;
while (isdigit(*p & 0xff)) {
v = v * 10 + (*p - '0');
p++;
}
return v;
};
pos = ParseNumber();
if (*p == ',') {
p++;
len = ParseNumber();
} else {
len = 1;
}
if (pos > 0) pos--;
HunkItem item;
item.hunk_number = number;
item.pos = pos;
item.len = len;
for (std::string const &line : it->lines) {
item.lines.push_back(line);
}
hunks.push_back(item);
}
}
std::sort(hunks.begin(), hunks.end(), [](HunkItem const &l, HunkItem const &r){
return l.pos + l.len < r.pos + r.len;
});
size_t h = hunks.size();
while (linenum > 0 || h > 0) {
while (h > 0) {
int hunk_number = h - 1;
HunkItem const &hi = hunks[hunk_number];
if (hi.pos + hi.len < linenum) {
break;
}
std::vector<TextDiffLine> tmp_left;
std::vector<TextDiffLine> tmp_right;
int minus = 0;
int plus = 0;
auto FlushBlank = [&](){
while (minus < plus) {
tmp_left.emplace_back();
minus++;
}
while (minus > plus) {
tmp_right.emplace_back();
plus++;
}
minus = plus = 0;
};
for (auto line : hi.lines) {
int c = line[0] & 0xff;
line = line.substr(1);
if (c == '-') {
minus++;
TextDiffLine l(line, TextDiffLine::Del);
l.hunk_number = hunk_number;
tmp_left.push_back(l);
} else if (c == '+') {
plus++;
TextDiffLine l(line, TextDiffLine::Add);
l.hunk_number = hunk_number;
tmp_right.push_back(l);
} else {
FlushBlank();
TextDiffLine l(line, TextDiffLine::Normal);
l.hunk_number = hunk_number;
tmp_left.push_back(l);
tmp_right.push_back(l);
}
}
FlushBlank();
auto ComplementNewLine = [](std::vector<TextDiffLine> *lines){
for (TextDiffLine &line : *lines) {
int n = line.text.size();
if (n > 0) {
int c = line.text[n - 1] & 0xff;
if (c != '\r' && c != '\n') {
line.text.push_back('\n');
}
}
}
};
ComplementNewLine(&tmp_left);
ComplementNewLine(&tmp_right);
for (auto it = tmp_left.rbegin(); it != tmp_left.rend(); it++) {
TextDiffLine l(*it);
if (m->text_codec) {
if (!l.text.isEmpty()) {
QString s = QString::fromUtf8(l.text.data(), l.text.size());
l.text = m->text_codec->fromUnicode(s);
}
}
left_lines->push_back(l);
}
for (auto it = tmp_right.rbegin(); it != tmp_right.rend(); it++) {
TextDiffLine l(*it);
if (m->text_codec) {
if (!l.text.isEmpty()) {
QString s = QString::fromUtf8(l.text.data(), l.text.size());
l.text = m->text_codec->fromUnicode(s);
}
}
right_lines->push_back(l);
}
linenum = hi.pos;
h--;
}
if (linenum > 0) {
linenum--;
if (linenum < (size_t)original_lines.size()) {
std::string line = original_lines[linenum];
left_lines->push_back(TextDiffLine(line, TextDiffLine::Normal));
right_lines->push_back(TextDiffLine(line, TextDiffLine::Normal));
}
}
}
std::reverse(left_lines->begin(), left_lines->end());
std::reverse(right_lines->begin(), right_lines->end());
}
void FileDiffWidget::setDiffText(Git::Diff const &diff, TextDiffLineList const &left, TextDiffLineList const &right)
{
m->max_line_length = 0;
enum Pane {
Left,
Right,
};
auto SetLineNumber = [&](TextDiffLineList const &lines, Pane pane, TextDiffLineList *out){
out->clear();
int linenum = 1;
for (TextDiffLine const &line : lines) {
TextDiffLine item = line;
switch (item.type) {
case TextDiffLine::Normal:
item.line_number = linenum++;
break;
case TextDiffLine::Add:
if (pane == Pane::Right) {
item.line_number = linenum++;
}
break;
case TextDiffLine::Del:
if (pane == Pane::Left) {
item.line_number = linenum++;
}
break;
default:
item.line_number = linenum; // 行番号は設定するが、インクリメントはしない
break;
}
out->push_back(item);
}
};
SetLineNumber(left, Pane::Left, &m->left_lines);
SetLineNumber(right, Pane::Right, &m->right_lines);
ui->widget_diff_left->setText(&m->left_lines, mainwindow(), diff.blob.a_id, diff.path);
ui->widget_diff_right->setText(&m->right_lines, mainwindow(), diff.blob.b_id, diff.path);
refrectScrollBar();
ui->widget_diff_slider->clear(true);
}
FileViewType FileDiffWidget::setupPreviewWidget()
{
clearDiffView();
QString mimetype_l = mainwindow()->determinFileType(m->init_param_.bytes_a, true);
QString mimetype_r = mainwindow()->determinFileType(m->init_param_.bytes_b, true);
if (misc::isImage(mimetype_l) || misc::isImage(mimetype_r)) { // image
ui->verticalScrollBar->setVisible(false);
ui->horizontalScrollBar->setVisible(false);
ui->widget_diff_slider->setVisible(false);
ui->widget_diff_left->setImage(mimetype_l, m->init_param_.bytes_a, m->init_param_.diff.blob.a_id, m->init_param_.diff.path);
ui->widget_diff_right->setImage(mimetype_r, m->init_param_.bytes_b, m->init_param_.diff.blob.b_id, m->init_param_.diff.path);
return FileViewType::Image;
} else { // text
ui->verticalScrollBar->setVisible(true);
ui->horizontalScrollBar->setVisible(true);
ui->widget_diff_slider->setVisible(true);
setViewType(FileViewType::Text);
return FileViewType::Text;
}
}
void FileDiffWidget::setSingleFile(QByteArray const &ba, QString const &id, QString const &path)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::SingleFile;
m->init_param_.bytes_a = ba;
m->init_param_.diff.path = path;
m->init_param_.diff.blob.a_id = id;
}
-void FileDiffWidget::setOriginalLines_(QByteArray const &ba)
+
+
+void FileDiffWidget::setOriginalLines_(QByteArray const &ba, Git::SubmoduleItem const *submodule, Git::CommitItem const *submodule_commit)
{
m->original_lines.clear();
+
if (!ba.isEmpty()) {
char const *begin = ba.data();
char const *end = begin + ba.size();
misc::splitLines(begin, end, &m->original_lines, true);
}
}
void FileDiffWidget::setLeftOnly(QByteArray const &ba, Git::Diff const &diff)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::LeftOnly;
m->init_param_.bytes_a = ba;
m->init_param_.diff = diff;
- setOriginalLines_(ba);
+ setOriginalLines_(ba, &diff.a_submodule.item, &diff.a_submodule.commit);
if (setupPreviewWidget() == FileViewType::Text) {
TextDiffLineList left_lines;
TextDiffLineList right_lines;
for (std::string const &line : m->original_lines) {
left_lines.push_back(TextDiffLine(line, TextDiffLine::Del));
right_lines.push_back(TextDiffLine());
}
setDiffText(diff, left_lines, right_lines);
}
}
+bool FileDiffWidget::setSubmodule(Git::Diff const &diff)
+{
+ Git::SubmoduleItem const &submod_a = diff.a_submodule.item;
+ Git::SubmoduleItem const &submod_b = diff.b_submodule.item;
+ Git::CommitItem const &submod_commit_a = diff.a_submodule.commit;
+ Git::CommitItem const &submod_commit_b = diff.b_submodule.commit;
+ if (submod_a || submod_b) {
+ auto Text = [](Git::SubmoduleItem const *submodule, Git::CommitItem const *submodule_commit, TextDiffLineList *out){
+ *out = {};
+ if (submodule && *submodule) {
+ QString text;
+ text += "name: " + submodule->name + '\n';
+ text += "path: " + submodule->path + '\n';
+ text += "url: " + submodule->url + '\n';
+ text += "commit: " + submodule->id + '\n';
+ text += "date: " + misc::makeDateTimeString(submodule_commit->commit_date) + '\n';
+ text += "author: " + submodule_commit->author + '\n';
+ text += "email: " + submodule_commit->email + '\n';
+ text += '\n';
+ text += submodule_commit->message;
+ for (QString const &line : misc::splitLines(text)) {
+ out->push_back(Document::Line(line.toStdString()));
+ }
+ }
+ };
+ TextDiffLineList left_lines;
+ TextDiffLineList right_lines;
+ if (submod_a) {
+ Text(&submod_a, &submod_commit_a, &left_lines);
+ }
+ if (submod_b) {
+ Text(&submod_b, &submod_commit_b, &right_lines);
+ }
+ setDiffText(diff, left_lines, right_lines);
+ return true;
+ }
+ return false;
+}
+
void FileDiffWidget::setRightOnly(QByteArray const &ba, Git::Diff const &diff)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::RightOnly;
m->init_param_.bytes_b = ba;
m->init_param_.diff = diff;
- setOriginalLines_(ba);
+ setOriginalLines_(ba, &diff.b_submodule.item, &diff.b_submodule.commit);
- if (setupPreviewWidget() == FileViewType::Text) {
+ if (setSubmodule(diff)) {
+ // ok
+ } else if (setupPreviewWidget() == FileViewType::Text) {
TextDiffLineList left_lines;
TextDiffLineList right_lines;
for (std::string const &line : m->original_lines) {
left_lines.push_back(TextDiffLine());
right_lines.push_back(TextDiffLine(line, TextDiffLine::Add));
}
setDiffText(diff, left_lines, right_lines);
}
}
void FileDiffWidget::setSideBySide(QByteArray const &ba, Git::Diff const &diff, bool uncommited, QString const &workingdir)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::SideBySideText;
m->init_param_.bytes_a = ba;
m->init_param_.diff = diff;
m->init_param_.uncommited = uncommited;
m->init_param_.workingdir = workingdir;
- setOriginalLines_(ba);
+ setOriginalLines_(ba, {}, {});
- if (setupPreviewWidget() == FileViewType::Text) {
+ if (setSubmodule(diff)) {
+ // ok
+ } else {
+ if (setupPreviewWidget() == FileViewType::Text) {
- TextDiffLineList left_lines;
- TextDiffLineList right_lines;
+ TextDiffLineList left_lines;
+ TextDiffLineList right_lines;
- makeSideBySideDiffData(diff, m->original_lines, &left_lines, &right_lines);
+ makeSideBySideDiffData(diff, m->original_lines, &left_lines, &right_lines);
- setDiffText(diff, left_lines, right_lines);
+ setDiffText(diff, left_lines, right_lines);
+ }
}
}
void FileDiffWidget::setSideBySide_(QByteArray const &ba_a, QByteArray const &ba_b, QString const &workingdir)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::SideBySideImage;
m->init_param_.bytes_a = ba_a;
m->init_param_.bytes_b = ba_b;
m->init_param_.workingdir = workingdir;
- setOriginalLines_(ba_a);
+ setOriginalLines_(ba_a, {}, {});
if (setupPreviewWidget() == FileViewType::Text) {
TextDiffLineList left_lines;
TextDiffLineList right_lines;
makeSideBySideDiffData(m->init_param_.diff, m->original_lines, &left_lines, &right_lines);
setDiffText(m->init_param_.diff, left_lines, right_lines);
}
}
QString FileDiffWidget::diffObjects(GitPtr const &g, QString const &a_id, QString const &b_id)
{
if (m->text_codec) {
- Git::Object obj_a = mainwindow()->cat_file_(g, a_id);
- Git::Object obj_b = mainwindow()->cat_file_(g, b_id);
+ Git::Object obj_a = mainwindow()->cat_file_(mainwindow()->frame(), g, a_id);
+ Git::Object obj_b = mainwindow()->cat_file_(mainwindow()->frame(), g, b_id);
if (obj_b.type == Git::Object::Type::UNKNOWN) {
obj_b.type = Git::Object::Type::BLOB;
}
if (obj_a.type == Git::Object::Type::BLOB && obj_b.type == Git::Object::Type::BLOB) {
QString path_a = mainwindow()->newTempFilePath();
QString path_b = mainwindow()->newTempFilePath();
QFile file_a(path_a);
QFile file_b(path_b);
if (file_a.open(QFile::WriteOnly) && file_b.open(QFile::WriteOnly)) {
file_a.write(m->text_codec->toUnicode(obj_a.content).toUtf8());
file_b.write(m->text_codec->toUnicode(obj_b.content).toUtf8());
file_a.close();
file_b.close();
QString s = g->diff_file(path_a, path_b);
file_a.remove();
file_b.remove();
return s;
}
}
}
return GitDiff::diffObjects(g, a_id, b_id);
}
bool FileDiffWidget::isValidID_(QString const &id)
{
if (id.startsWith(PATH_PREFIX)) {
return true;
}
return Git::isValidID(id);
}
void FileDiffWidget::updateDiffView(Git::Diff const &info, bool uncommited)
{
GitPtr g = git();
if (!g) return;
if (!g->isValidWorkingCopy()) return;
if (isValidID_(info.blob.a_id) && isValidID_(info.blob.b_id)) {
Git::Object obj_a = cat_file(g, info.blob.a_id);
Git::Object obj_b = cat_file(g, info.blob.b_id);
QString mime_a = mainwindow()->determinFileType(obj_a.content, true);
QString mime_b = mainwindow()->determinFileType(obj_b.content, true);
if (misc::isImage(mime_a) && misc::isImage(mime_b)) {
- setSideBySide_(obj_a.content, obj_b.content, g->workingRepositoryDir());
+ setSideBySide_(obj_a.content, obj_b.content, g->workingDir());
return;
}
}
{
Git::Diff diff;
if (isValidID_(info.blob.a_id) && isValidID_(info.blob.b_id)) {
std::string text = diffObjects(g, info.blob.a_id, info.blob.b_id).toStdString();
GitDiff::parseDiff(text, &info, &diff);
} else {
diff = info;
}
Git::Object obj;
if (isValidID_(diff.blob.a_id)) { // 左が有効
obj = cat_file(g, diff.blob.a_id);
if (isValidID_(diff.blob.b_id)) { // 右が有効
- setSideBySide(obj.content, diff, uncommited, g->workingRepositoryDir()); // 通常のdiff表示
+ setSideBySide(obj.content, diff, uncommited, g->workingDir()); // 通常のdiff表示
} else {
setLeftOnly(obj.content, diff); // 右が無効の時は、削除されたファイル
}
} else if (isValidID_(diff.blob.b_id)) { // 左が無効で右が有効の時は、追加されたファイル
obj = cat_file(g, diff.blob.b_id);
setRightOnly(obj.content, diff);
}
}
}
void FileDiffWidget::updateDiffView(QString const &id_left, QString const &id_right, QString const &path)
{
GitPtr g = git();
if (!g) return;
if (!g->isValidWorkingCopy()) return;
Git::Diff diff;
diff.path = path;
diff.blob.a_id = id_left;
diff.blob.b_id = id_right;
diff.mode = "0";
std::string text = diffObjects(g, diff.blob.a_id, diff.blob.b_id).toStdString();
GitDiff::parseDiff(text, &diff, &diff);
Git::Object obj = cat_file(g, diff.blob.a_id);
- setSideBySide(obj.content, diff, false, g->workingRepositoryDir());
+ setSideBySide(obj.content, diff, false, g->workingDir());
ui->widget_diff_slider->clear(false);
resetScrollBarValue();
updateControls();
ui->widget_diff_slider->update();
}
void FileDiffWidget::resizeEvent(QResizeEvent *)
{
refrectScrollBar();
}
void FileDiffWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
event->ignore(); // Escが押されたとき、親ウィジェットに処理させる。(通常、ウィンドウを閉じる、など)
return;
}
if (focusWidget() == ui->widget_diff_left->texteditor()) {
ui->widget_diff_left->write(event);
} else if (focusWidget() == ui->widget_diff_right->texteditor()) {
ui->widget_diff_right->write(event);
}
}
void FileDiffWidget::scrollTo(int value)
{
ui->verticalScrollBar->setValue(value);
}
void FileDiffWidget::onVerticalScrollValueChanged(int)
{
refrectScrollBar();
}
void FileDiffWidget::onHorizontalScrollValueChanged(int)
{
refrectScrollBar();
}
void FileDiffWidget::onDiffWidgetWheelScroll(int lines)
{
while (lines > 0) {
ui->verticalScrollBar->triggerAction(QScrollBar::SliderSingleStepAdd);
lines--;
}
while (lines < 0) {
ui->verticalScrollBar->triggerAction(QScrollBar::SliderSingleStepSub);
lines++;
}
}
void FileDiffWidget::onScrollValueChanged2(int value)
{
ui->verticalScrollBar->setValue(value);
}
void FileDiffWidget::onDiffWidgetResized()
{
updateControls();
}
void FileDiffWidget::on_toolButton_fullscreen_clicked()
{
if (m->init_param_.diff.blob.a_id.isEmpty() && m->init_param_.diff.blob.b_id.isEmpty()) {
return;
}
BigDiffWindow win(mainwindow());
win.setWindowState(Qt::WindowMaximized);
win.init(mainwindow(), m->init_param_);
win.exec();
}
void FileDiffWidget::setFocusAcceptable(Qt::FocusPolicy focuspolicy)
{
// Qt::FocusPolicy focuspolicy = f ? Qt::ClickFocus : Qt::NoFocus;
ui->widget_diff_left->setFocusPolicy(focuspolicy);
ui->widget_diff_right->setFocusPolicy(focuspolicy);
}
void FileDiffWidget::onUpdateSliderBar()
{
int total = m->engine_left->document.lines.size();
int value = ui->verticalScrollBar->value();
int page = ui->verticalScrollBar->pageStep();
ui->widget_diff_slider->setScrollPos(total, value, page);
}
void FileDiffWidget::refrectScrollBar()
{
ui->widget_diff_left->refrectScrollBar();
ui->widget_diff_right->refrectScrollBar();
onUpdateSliderBar();
}
QPixmap FileDiffWidget::makeDiffPixmap(DiffPane pane, int width, int height)
{
auto Do = [&](TextDiffLineList const &lines){
return FileDiffSliderWidget::makeDiffPixmap(width, height, lines, global->theme);
};
if (pane == DiffPane::Left) return Do(m->left_lines);
if (pane == DiffPane::Right) return Do(m->right_lines);
return QPixmap();
}
void FileDiffWidget::onMoved(int cur_row, int cur_col, int scr_row, int scr_col)
{
(void)cur_col;
(void)cur_row;
ui->widget_diff_left->move(-1, -1, scr_row, scr_col, false);
ui->widget_diff_right->move(-1, -1, scr_row, scr_col, false);
refrectScrollBar();
onUpdateSliderBar();
}
void FileDiffWidget::setTextCodec(QTextCodec *codec)
{
m->text_codec = codec;
ui->widget_diff_left->setTextCodec(codec);
ui->widget_diff_right->setTextCodec(codec);
emit textcodecChanged();
}
void FileDiffWidget::setTextCodec(char const *name)
{
QTextCodec *codec = name ? QTextCodec::codecForName(name) : nullptr;
setTextCodec(codec);
}
void FileDiffWidget::on_toolButton_menu_clicked()
{
QMenu menu;
QAction *a_utf8 = menu.addAction("UTF-8");
QAction *a_sjis = menu.addAction("SJIS (CP932)");
QAction *a_eucjp = menu.addAction("EUC-JP");
QAction *a_iso2022jp = menu.addAction("JIS (ISO-2022-JP)");
QAction *a = menu.exec(QCursor::pos() + QPoint(8, -8));
if (a) {
if (a == a_utf8) {
setTextCodec((char const *)nullptr);
return;
}
if (a == a_sjis) {
setTextCodec("Shift_JIS");
return;
}
if (a == a_eucjp) {
setTextCodec("EUC-JP");
return;
}
if (a == a_iso2022jp) {
setTextCodec("ISO-2022-JP");
return;
}
}
}
diff --git a/src/FileDiffWidget.h b/src/FileDiffWidget.h
index 8fb0e52..23e2b3a 100644
--- a/src/FileDiffWidget.h
+++ b/src/FileDiffWidget.h
@@ -1,167 +1,176 @@
#ifndef FILEDIFFWIDGET_H
#define FILEDIFFWIDGET_H
#include "FileDiffSliderWidget.h"
#include "FileViewWidget.h"
#include "Git.h"
#include "MainWindow.h"
#include "texteditor/AbstractCharacterBasedApplication.h"
#include <QDialog>
#include <memory>
namespace Ui {
class FileDiffWidget;
}
enum class ViewType {
None,
Left,
Right
};
using TextDiffLine = Document::Line;
using TextDiffLineList = QList<Document::Line>;
struct ObjectContent {
QString id;
QString path;
QByteArray bytes;
TextDiffLineList lines;
};
using ObjectContentPtr = std::shared_ptr<ObjectContent>;
class QTableWidgetItem;
class FileDiffWidget : public QWidget {
Q_OBJECT
friend class BigDiffWindow;
public:
struct DiffData {
ObjectContentPtr left;
ObjectContentPtr right;
std::vector<std::string> original_lines;
DiffData()
{
clear();
}
void clear()
{
left = std::make_shared<ObjectContent>();
right = std::make_shared<ObjectContent>();
original_lines.clear();
}
};
- struct DrawData {
- int v_scroll_pos = 0;
- int h_scroll_pos = 0;
- int char_width = 0;
- int line_height = 0;
- QColor bgcolor_text;
- QColor bgcolor_add;
- QColor bgcolor_del;
- QColor bgcolor_add_dark;
- QColor bgcolor_del_dark;
- QColor bgcolor_gray;
- QWidget *forcus = nullptr;
- DrawData();
- };
+// struct DrawData {
+// int v_scroll_pos = 0;
+// int h_scroll_pos = 0;
+// int char_width = 0;
+// int line_height = 0;
+// QColor bgcolor_text;
+// QColor bgcolor_add;
+// QColor bgcolor_del;
+// QColor bgcolor_add_dark;
+// QColor bgcolor_del_dark;
+// QColor bgcolor_gray;
+// QWidget *forcus = nullptr;
+// DrawData()
+// {
+// bgcolor_text = QColor(255, 255, 255);
+// bgcolor_gray = QColor(224, 224, 224);
+// bgcolor_add = QColor(192, 240, 192);
+// bgcolor_del = QColor(255, 224, 224);
+// bgcolor_add_dark = QColor(64, 192, 64);
+// bgcolor_del_dark = QColor(240, 64, 64);
+// }
+// };
enum ViewStyle {
None,
SingleFile,
LeftOnly,
RightOnly,
SideBySideText,
SideBySideImage,
};
private:
Ui::FileDiffWidget *ui;
struct Private;
Private *m;
struct InitParam_ {
ViewStyle view_style = ViewStyle::None;
QByteArray bytes_a;
QByteArray bytes_b;
Git::Diff diff;
bool uncommited = false;
QString workingdir;
};
ViewStyle viewstyle() const;
GitPtr git();
Git::Object cat_file(const GitPtr &g, QString const &id);
int totalTextLines() const;
void resetScrollBarValue();
void updateSliderCursor();
int fileviewHeight() const;
void setDiffText(const Git::Diff &diff, TextDiffLineList const &left, TextDiffLineList const &right);
void setLeftOnly(QByteArray const &ba, const Git::Diff &diff);
void setRightOnly(QByteArray const &ba, const Git::Diff &diff);
void setSideBySide(QByteArray const &ba, const Git::Diff &diff, bool uncommited, QString const &workingdir);
void setSideBySide_(QByteArray const &ba_a, QByteArray const &ba_b, QString const &workingdir);
bool isValidID_(QString const &id);
FileViewType setupPreviewWidget();
void makeSideBySideDiffData(const Git::Diff &diff, const std::vector<std::string> &original_lines, TextDiffLineList *left_lines, TextDiffLineList *right_lines);
void onUpdateSliderBar();
void refrectScrollBar();
- void setOriginalLines_(QByteArray const &ba);
+ void setOriginalLines_(QByteArray const &ba, const Git::SubmoduleItem *submodule, const Git::CommitItem *submodule_commit);
QString diffObjects(const GitPtr &g, QString const &a_id, QString const &b_id);
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
+ bool setSubmodule(const Git::Diff &diff);
protected:
void resizeEvent(QResizeEvent *) override;
void keyPressEvent(QKeyEvent *event) override;
public:
explicit FileDiffWidget(QWidget *parent = nullptr);
~FileDiffWidget() override;
- void bind(BasicMainWindow *mw);
+ void bind(MainWindow *mw);
void clearDiffView();
void setSingleFile(QByteArray const &ba, QString const &id, QString const &path);
void updateControls();
void scrollToBottom();
void updateDiffView(const Git::Diff &info, bool uncommited);
void updateDiffView(const QString &id_left, const QString &id_right, QString const &path = QString());
void setMaximizeButtonEnabled(bool f);
void setFocusAcceptable(Qt::FocusPolicy focuspolicy);
QPixmap makeDiffPixmap(DiffPane pane, int width, int height);
void setViewType(FileViewType type);
void setTextCodec(QTextCodec *codec);
void setTextCodec(char const *name);
private slots:
void onVerticalScrollValueChanged(int);
void onHorizontalScrollValueChanged(int);
void onDiffWidgetWheelScroll(int lines);
void onScrollValueChanged2(int value);
void onDiffWidgetResized();
void on_toolButton_fullscreen_clicked();
void scrollTo(int value);
void onMoved(int cur_row, int cur_col, int scr_row, int scr_col);
void on_toolButton_menu_clicked();
signals:
// void moveNextItem();
// void movePreviousItem();
void textcodecChanged();
};
#endif // FILEDIFFWIDGET_H
diff --git a/src/FileHistoryWindow.cpp b/src/FileHistoryWindow.cpp
index 9292c58..6e6179a 100644
--- a/src/FileHistoryWindow.cpp
+++ b/src/FileHistoryWindow.cpp
@@ -1,250 +1,250 @@
#include "FileHistoryWindow.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "FileDiffWidget.h"
#include "GitDiff.h"
#include "MyTableWidgetDelegate.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include "ui_FileHistoryWindow.h"
#include <QMenu>
#include <QPainter>
#include <QStyledItemDelegate>
#include <QThread>
struct FileHistoryWindow::Private {
GitPtr g;
QString path;
Git::CommitItemList commit_item_list;
FileDiffWidget::DiffData diff_data;
- FileDiffWidget::DrawData draw_data;
+// FileDiffWidget::DrawData draw_data;
};
FileDiffWidget::DiffData *FileHistoryWindow::diffdata()
{
return &m->diff_data;
}
const FileDiffWidget::DiffData *FileHistoryWindow::diffdata() const
{
return &m->diff_data;
}
-FileDiffWidget::DrawData *FileHistoryWindow::drawdata()
-{
- return &m->draw_data;
-}
+//FileDiffWidget::DrawData *FileHistoryWindow::drawdata()
+//{
+// return &m->draw_data;
+//}
-const FileDiffWidget::DrawData *FileHistoryWindow::drawdata() const
-{
- return &m->draw_data;
-}
+//const FileDiffWidget::DrawData *FileHistoryWindow::drawdata() const
+//{
+// return &m->draw_data;
+//}
int FileHistoryWindow::totalTextLines() const
{
return diffdata()->left->lines.size();
}
-int FileHistoryWindow::fileviewScrollPos() const
-{
- return drawdata()->v_scroll_pos;
-}
+//int FileHistoryWindow::fileviewScrollPos() const
+//{
+// return drawdata()->v_scroll_pos;
+//}
FileHistoryWindow::FileHistoryWindow(BasicMainWindow *parent)
: QDialog(parent)
, ui(new Ui::FileHistoryWindow)
, m(new Private)
{
ui->setupUi(this);
ui->tableWidget_log->setItemDelegate(new MyTableWidgetDelegate(this));
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
flags |= Qt::WindowMaximizeButtonHint;
setWindowFlags(flags);
ui->splitter->setSizes({100, 200});
ui->widget_diff_view->bind(mainwindow());
// connect(ui->widget_diff_view, &FileDiffWidget::moveNextItem, this, &FileHistoryWindow::onMoveNextItem);
// connect(ui->widget_diff_view, &FileDiffWidget::movePreviousItem, this, &FileHistoryWindow::onMovePreviousItem);
}
FileHistoryWindow::~FileHistoryWindow()
{
delete m;
delete ui;
}
-BasicMainWindow *FileHistoryWindow::mainwindow()
+MainWindow *FileHistoryWindow::mainwindow()
{
- return qobject_cast<BasicMainWindow *>(parent());
+ return qobject_cast<MainWindow *>(parent());
}
void FileHistoryWindow::prepare(GitPtr const &g, QString const &path)
{
Q_ASSERT(g);
Q_ASSERT(g->isValidWorkingCopy());
this->m->g = g;
this->m->path = path;
QString reponame = mainwindow()->currentRepositoryName();
QString brname = mainwindow()->currentBranch().name;
QString text = "%1 (%2)";
text = text.arg(reponame).arg(brname);
ui->label_repo->setText(text);
ui->label_path->setText(path);
{
OverrideWaitCursor;
m->commit_item_list = m->g->log_all(m->path, mainwindow()->limitLogCount());
}
collectFileHistory();
updateDiffView();
}
void FileHistoryWindow::collectFileHistory()
{
QStringList cols = {
tr("Commit"),
tr("Date"),
tr("Author"),
tr("Message"),
};
int n = cols.size();
ui->tableWidget_log->setColumnCount(n);
ui->tableWidget_log->setRowCount(0);
for (int i = 0; i < n; i++) {
QString const &text = cols[i];
auto *item = new QTableWidgetItem(text);
ui->tableWidget_log->setHorizontalHeaderItem(i, item);
}
int count = m->commit_item_list.size();
ui->tableWidget_log->setRowCount(count);
for (int row = 0; row < count; row++) {
Git::CommitItem const &commit = m->commit_item_list[row];
int col = 0;
auto AddColumn = [&](QString const &text, QString const &tooltip){
auto *item = new QTableWidgetItem(text);
item->setToolTip(tooltip);
ui->tableWidget_log->setItem(row, col, item);
col++;
};
- QString commit_id = BasicMainWindow::abbrevCommitID(commit);
+ QString commit_id = MainWindow::abbrevCommitID(commit);
QString datetime = misc::makeDateTimeString(commit.commit_date);
AddColumn(commit_id, QString());
AddColumn(datetime, QString());
AddColumn(commit.author, QString());
AddColumn(commit.message, commit.message);
ui->tableWidget_log->setRowHeight(row, 24);
}
ui->tableWidget_log->resizeColumnsToContents();
ui->tableWidget_log->horizontalHeader()->setStretchLastSection(false);
ui->tableWidget_log->horizontalHeader()->setStretchLastSection(true);
ui->tableWidget_log->setFocus();
ui->tableWidget_log->setCurrentCell(0, 0);
}
class FindFileIdThread : public QThread {
private:
- BasicMainWindow *mainwindow;
+ MainWindow *mainwindow;
GitPtr g;
QString commit_id;
QString file;
public:
QString result;
- FindFileIdThread(BasicMainWindow *BasicMainWindow, GitPtr const &g, QString const &commit_id, QString const &file)
+ FindFileIdThread(MainWindow *mw, GitPtr const &g, QString const &commit_id, QString const &file)
{
- this->mainwindow = BasicMainWindow;
+ this->mainwindow = mw;
this->g = g;
this->commit_id = commit_id;
this->file = file;
}
protected:
void run() override
{
- result = mainwindow->findFileID(commit_id, file);
+ result = mainwindow->findFileID(mainwindow->frame(), commit_id, file);
}
};
void FileHistoryWindow::updateDiffView()
{
Q_ASSERT(m->g);
Q_ASSERT(m->g->isValidWorkingCopy());
ui->widget_diff_view->clearDiffView();
int row = ui->tableWidget_log->currentRow();
if (row >= 0 && row + 1 < (int)m->commit_item_list.size()) {
Git::CommitItem const &commit_left = m->commit_item_list[row + 1]; // older
Git::CommitItem const &commit_right = m->commit_item_list[row]; // newer
FindFileIdThread left_thread(mainwindow(), m->g->dup(), commit_left.commit_id, m->path);
FindFileIdThread right_thread(mainwindow(), m->g->dup(), commit_right.commit_id, m->path);
left_thread.start();
right_thread.start();
left_thread.wait();
right_thread.wait();
QString id_left = left_thread.result;
QString id_right = right_thread.result;
ui->widget_diff_view->updateDiffView(id_left, id_right, m->path);
} else if (row >= 0 && row < (int)m->commit_item_list.size()) {
Git::CommitItem const &commit = m->commit_item_list[row]; // newer
- QString id = mainwindow()->findFileID(commit.commit_id, m->path);
+ QString id = mainwindow()->findFileID(mainwindow()->frame(), commit.commit_id, m->path);
Git::Diff diff(id, m->path, QString());
ui->widget_diff_view->updateDiffView(diff, false);
}
}
void FileHistoryWindow::on_tableWidget_log_currentItemChanged(QTableWidgetItem * /*current*/, QTableWidgetItem * /*previous*/)
{
updateDiffView();
}
//void FileHistoryWindow::onMoveNextItem()
//{
// int row = ui->tableWidget_log->currentRow();
// int count = ui->tableWidget_log->rowCount();
// if (row + 1 < count) {
// ui->tableWidget_log->setCurrentCell(row + 1, 0, QItemSelectionModel::ClearAndSelect);
// }
//}
//void FileHistoryWindow::onMovePreviousItem()
//{
// int row = ui->tableWidget_log->currentRow();
// if (row > 0) {
// ui->tableWidget_log->setCurrentCell(row - 1, 0, QItemSelectionModel::ClearAndSelect);
// }
//}
void FileHistoryWindow::on_tableWidget_log_customContextMenuRequested(const QPoint &pos)
{
(void)pos;
Git::CommitItem const *commit = nullptr;
int row = ui->tableWidget_log->currentRow();
if (row >= 0 && row < (int)m->commit_item_list.size()) {
commit = &m->commit_item_list[row];
}
if (!commit) return;
QMenu menu;
QAction *a_property = mainwindow()->addMenuActionProperty(&menu);
QAction *a = menu.exec(QCursor::pos() + QPoint(8, -8));
if (a) {
if (a == a_property) {
- mainwindow()->execCommitPropertyDialog(this, commit);
+ mainwindow()->execCommitPropertyDialog(this, mainwindow()->frame(), commit);
return;
}
}
}
diff --git a/src/FileHistoryWindow.h b/src/FileHistoryWindow.h
index 433213c..a8e36d3 100644
--- a/src/FileHistoryWindow.h
+++ b/src/FileHistoryWindow.h
@@ -1,48 +1,48 @@
#ifndef FILEHISTORYWINDOW_H
#define FILEHISTORYWINDOW_H
#include <QDialog>
#include "Git.h"
#include "BasicMainWindow.h"
#include "FileDiffWidget.h"
namespace Ui {
class FileHistoryWindow;
}
class BasicMainWindow;
class QTableWidgetItem;
class FileHistoryWindow : public QDialog {
Q_OBJECT
private:
struct Private;
Private *m;
FileDiffWidget::DiffData *diffdata();
FileDiffWidget::DiffData const *diffdata() const;
- FileDiffWidget::DrawData *drawdata();
- FileDiffWidget::DrawData const *drawdata() const;
+// FileDiffWidget::DrawData *drawdata();
+// FileDiffWidget::DrawData const *drawdata() const;
int totalTextLines() const;
- int fileviewScrollPos() const;
+// int fileviewScrollPos() const;
public:
explicit FileHistoryWindow(BasicMainWindow *parent);
~FileHistoryWindow() override;
void prepare(const GitPtr &g, QString const &path);
private slots:
void on_tableWidget_log_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous);
// void onMoveNextItem();
// void onMovePreviousItem();
void on_tableWidget_log_customContextMenuRequested(const QPoint &pos);
private:
Ui::FileHistoryWindow *ui;
void collectFileHistory();
void updateDiffView();
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
};
#endif // FILEHISTORYWINDOW_H
diff --git a/src/FilePropertyDialog.cpp b/src/FilePropertyDialog.cpp
index cd9cf07..17b513d 100644
--- a/src/FilePropertyDialog.cpp
+++ b/src/FilePropertyDialog.cpp
@@ -1,30 +1,30 @@
#include "FilePropertyDialog.h"
#include "MainWindow.h"
#include "ui_FilePropertyDialog.h"
#include "main.h"
FilePropertyDialog::FilePropertyDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::FilePropertyDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
}
FilePropertyDialog::~FilePropertyDialog()
{
delete ui;
}
-void FilePropertyDialog::exec(BasicMainWindow *mw, QString const &path, QString const &id)
+void FilePropertyDialog::exec(MainWindow *mw, QString const &path, QString const &id)
{
mainwindow = mw;
ui->lineEdit_repo->setText(mainwindow->currentRepositoryName());
ui->lineEdit_path->setText(path);
ui->lineEdit_id->setText(id);
QDialog::exec();
}
diff --git a/src/FilePropertyDialog.h b/src/FilePropertyDialog.h
index 11a71b6..2e8974e 100644
--- a/src/FilePropertyDialog.h
+++ b/src/FilePropertyDialog.h
@@ -1,25 +1,25 @@
#ifndef FILEPROPERTYDIALOG_H
#define FILEPROPERTYDIALOG_H
#include <QDialog>
namespace Ui {
class FilePropertyDialog;
}
-class BasicMainWindow;
+class MainWindow;
class FilePropertyDialog : public QDialog {
Q_OBJECT
private:
- BasicMainWindow *mainwindow;
+ MainWindow *mainwindow;
public:
explicit FilePropertyDialog(QWidget *parent = nullptr);
~FilePropertyDialog() override;
- void exec(BasicMainWindow *mw, QString const &path, QString const &id);
+ void exec(MainWindow *mw, QString const &path, QString const &id);
private:
Ui::FilePropertyDialog *ui;
};
#endif // FILEPROPERTYDIALOG_H
diff --git a/src/FileViewWidget.cpp b/src/FileViewWidget.cpp
index bfc4d61..465c799 100644
--- a/src/FileViewWidget.cpp
+++ b/src/FileViewWidget.cpp
@@ -1,170 +1,170 @@
#include "FileViewWidget.h"
#include "common/misc.h"
#include <QMenu>
#include <QPainter>
#include <QStackedWidget>
#include <QVBoxLayout>
FileViewWidget::FileViewWidget(QWidget *parent)
: QWidget(parent)
{
setObjectName(QStringLiteral("FileViewWidget"));
ui_verticalLayout = new QVBoxLayout(this);
ui_verticalLayout->setSpacing(0);
ui_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
ui_verticalLayout->setContentsMargins(0, 0, 0, 0);
ui_stackedWidget = new QStackedWidget(this);
ui_stackedWidget->setObjectName(QStringLiteral("stackedWidget"));
ui_page_none = new QWidget();
ui_page_none->setObjectName(QStringLiteral("page_none"));
ui_stackedWidget->addWidget(ui_page_none);
ui_page_text = new X_TextEditorWidget();
ui_page_text->setObjectName(QStringLiteral("page_text"));
ui_page_text->setFocusPolicy(Qt::ClickFocus);
ui_stackedWidget->addWidget(ui_page_text);
ui_page_image = new X_ImageViewWidget();
ui_page_image->setObjectName(QStringLiteral("page_image"));
ui_page_image->setFocusPolicy(Qt::ClickFocus);
ui_stackedWidget->addWidget(ui_page_image);
ui_verticalLayout->addWidget(ui_stackedWidget);
setWindowTitle(QApplication::translate("FileViewWidget", "Form", Q_NULLPTR));
ui_stackedWidget->setCurrentIndex(1);
QMetaObject::connectSlotsByName(this);
ui_page_text->setRenderingMode(TextEditorWidget::DecoratedMode);
ui_page_text->setTheme(TextEditorTheme::Light());
ui_page_text->showHeader(false);
ui_page_text->showFooter(false);
ui_page_text->setAutoLayout(true);
ui_page_text->setReadOnly(true);
ui_page_text->setToggleSelectionAnchorEnabled(false);
ui_page_text->setFocusFrameVisible(true);
ui_stackedWidget->setCurrentWidget(ui_page_none);
}
void FileViewWidget::setTextCodec(QTextCodec *codec)
{
ui_page_text->setTextCodec(codec);
}
void FileViewWidget::bind(QMainWindow *mw, FileDiffWidget *fdw, QScrollBar *vsb, QScrollBar *hsb, TextEditorThemePtr const &theme)
{
ui_page_text->bindScrollBar(vsb, hsb);
ui_page_image->bind(mw, fdw, vsb, hsb);
ui_page_text->setTheme(theme);
}
void FileViewWidget::setViewType(FileViewType type)
{
view_type = type;
switch (view_type) {
case FileViewType::Text:
ui_stackedWidget->setCurrentWidget(ui_page_text);
return;
case FileViewType::Image:
ui_stackedWidget->setCurrentWidget(ui_page_image);
return;
default:
ui_stackedWidget->setCurrentWidget(ui_page_none);
return;
}
}
const TextEditorTheme *FileViewWidget::theme() const
{
return ui_page_text->theme();
}
//int FileViewWidget::latin1Width(QString const &s) const
//{
// return ui_page_text->latin1Width(s);
//}
int FileViewWidget::lineHeight() const
{
return ui_page_text->lineHeight();
}
void FileViewWidget::setDiffMode(TextEditorEnginePtr const &editor_engine, QScrollBar *vsb, QScrollBar *hsb)
{
ui_page_text->setTextEditorEngine(editor_engine);
return ui_page_text->bindScrollBar(vsb, hsb);
}
void FileViewWidget::refrectScrollBar()
{
switch (view_type) {
case FileViewType::Text:
ui_page_text->refrectScrollBar();
return;
case FileViewType::Image:
ui_page_image->refrectScrollBar();
return;
}
}
void FileViewWidget::move(int cur_row, int cur_col, int scr_row, int scr_col, bool auto_scroll)
{
return ui_page_text->move(cur_row, cur_col, scr_row, scr_col, auto_scroll);
}
void FileViewWidget::setImage(QString const &mimetype, QByteArray const &ba, QString const &object_id, QString const &path)
{
setViewType(FileViewType::Image);
this->source_id = object_id;
#ifdef APP_GUITAR
ui_page_image->setImage(mimetype, ba, object_id, path);
#else
ui_page_image->setImage(mimetype, ba);
#endif
}
void FileViewWidget::setText(const QList<Document::Line> *source, QMainWindow *mw, QString const &object_id, QString const &object_path)
{
setViewType(FileViewType::Text);
this->source_id = object_id;
#ifdef APP_GUITAR
- ui_page_text->setDocument(source, qobject_cast<BasicMainWindow *>(mw), object_id, object_path);
+ ui_page_text->setDocument(source, qobject_cast<MainWindow *>(mw), object_id, object_path);
scrollToTop();
texteditor()->moveCursorOut(); // 現在行を -1 にして、カーソルを非表示にする。
#else
ui_page_text->setDocument(source);
scrollToTop();
#endif
}
void FileViewWidget::setText(QByteArray const &ba, QMainWindow *mw, QString const &object_id, QString const &object_path)
{
std::vector<std::string> lines;
char const *begin = ba.data();
char const *end = begin + ba.size();
misc::splitLines(begin, end, &lines, true);
QList<Document::Line> source;
source.reserve(lines.size());
int num = 0;
for (std::string const &line : lines) {
Document::Line t(line);
t.line_number = ++num;
source.push_back(t);
}
setText(&source, mw, object_id, object_path);
}
void FileViewWidget::scrollToTop()
{
ui_page_text->scrollToTop();
}
void FileViewWidget::write(QKeyEvent *e)
{
ui_page_text->write(e);
}
TextEditorWidget *FileViewWidget::texteditor()
{
return ui_page_text;
}
diff --git a/src/FilesListWidget.cpp b/src/FilesListWidget.cpp
index ce5fcb8..581f46a 100644
--- a/src/FilesListWidget.cpp
+++ b/src/FilesListWidget.cpp
@@ -1,99 +1,115 @@
#include "FilesListWidget.h"
#include "BasicMainWindow.h"
#include <QDebug>
#include <QPainter>
#include <QStyledItemDelegate>
#include <map>
namespace {
class ItemDelegate : public QStyledItemDelegate {
private:
struct Badge {
QString text;
QColor color;
QIcon icon;
Badge() = default;
Badge(QString const &text, QColor const &color, QIcon const &icon)
: text(text)
, color(color)
, icon(icon)
{
}
};
std::map<QString, Badge> badge_map;
public:
ItemDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
badge_map["(chg) "] = Badge("Chg", QColor(240, 240, 140), QIcon(":/image/chg.svg"));
badge_map["(add) "] = Badge("Add", QColor(180, 240, 180), QIcon(":/image/add.svg"));
badge_map["(del) "] = Badge("Del", QColor(255, 200, 200), QIcon(":/image/del.svg"));
badge_map["(ren) "] = Badge("Ren", QColor(200, 210, 255), QIcon(":/image/ren.svg"));
badge_map["(cpy) "] = Badge("Cpy", QColor(200, 210, 255), QIcon(":/image/cpy.svg"));
}
+ /**
+ * @brief ファイルリストの1行分を描画する
+ * @param painter
+ * @param option
+ * @param index
+ */
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QStyleOptionViewItem o = option;
QStyledItemDelegate::initStyleOption(&o, index);
QString header = index.data(BasicMainWindow::HeaderRole).toString();
+// bool isSubmodule = !index.data(BasicMainWindow::SubmodulePathRole).toString().isEmpty();
int x = o.rect.x();
int y = o.rect.y();
int h = o.rect.height();
int w = 2 + h + painter->fontMetrics().size(0, " Aaa").width() + 2;
- // draw badge
+ // バッジの描画
Badge badge;
if (header == "(unmerged) ") {
badge = Badge("Unmerged", QColor(255, 80, 160), QIcon());
w = 4 + painter->fontMetrics().size(0, badge.text).width() + 4;
}
{
QColor color;
auto it = badge_map.find(header);
if (it != badge_map.end()) {
badge = it->second;
color = badge.color;
} else {
color = badge.color.isValid() ? badge.color : QColor(160, 160, 160);
}
{
QRect r(x, y, w, h);
QRect r_icon = badge.icon.isNull() ? QRect() : QRect(x + 2, y + 1, h - 2, h - 2).adjusted(2, 2, -2, -2);
QRect r_badge = r.adjusted(1, 1, -2, -2);
QRect r_text = r.adjusted(r_icon.width(), 0, 0, 0);
painter->setPen(Qt::NoPen);
painter->setBrush(QBrush(color.darker(130)));
painter->drawRoundedRect(r_badge.translated(1, 1), 3, 3);
painter->setBrush(QBrush(color));
painter->drawRoundedRect(r_badge, 3, 3);
+ // アイコン描画
if (!badge.icon.isNull()) {
painter->save();
painter->setOpacity(0.5);
badge.icon.paint(painter, r_icon);
painter->restore();
}
painter->setPen(Qt::black);
QTextOption to;
to.setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
if (badge.text.isEmpty()) {
badge.text = "?";
}
+ // バッジテキスト描画
painter->drawText(r_text, badge.text, to);
}
}
o.rect.adjust(w, 0, 0, 0);
- // draw text
+#if 0
+ // サブモジュール
+ if (isSubmodule) {
+ o.rect.adjust(h, 0, 0, 0);
+ }
+#endif
+
+ // アイテムテキスト描画
option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &o, painter, option.widget);
}
};
}
FilesListWidget::FilesListWidget(QWidget *parent)
: QListWidget(parent)
{
item_delegate = new ItemDelegate(this);
setItemDelegate(item_delegate);
}
diff --git a/src/Git.cpp b/src/Git.cpp
index a24d0bd..9bec4f6 100644
--- a/src/Git.cpp
+++ b/src/Git.cpp
@@ -1,1483 +1,1628 @@
#include "Git.h"
#include "GitObjectManager.h"
#include "MyProcess.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QProcess>
#include <QThread>
#include <QTimer>
#include <set>
#define DEBUGLOG 0
using callback_t = Git::callback_t;
struct Git::Private {
- QString git_command;
+ struct Info {
+ QString git_command;
+ QString working_repo_dir;
+ QString submodule_path;
+ callback_t fn_log_writer_callback = nullptr;
+ void *callback_cookie = nullptr;
+ };
+ Info info;
+ QString ssh_command;// = "C:/Program Files/Git/usr/bin/ssh.exe";
+ QString ssh_key_override;// = "C:/a/id_rsa";
std::vector<char> result;
QString error_message;
int process_exit_code = 0;
- QString working_repo_dir;
- callback_t fn_log_writer_callback = nullptr;
- void *callback_cookie = nullptr;
};
Git::Git()
: m(new Private)
{
}
-Git::Git(const Context &cx, QString const &repodir)
+Git::Git(const Context &cx, QString const &repodir, const QString &submodpath, const QString &sshkey)
: m(new Private)
{
- setGitCommand(cx.git_command);
- setWorkingRepositoryDir(repodir);
+ setGitCommand(cx.git_command, cx.ssh_command);
+ setWorkingRepositoryDir(repodir, submodpath, sshkey);
}
Git::~Git()
{
delete m;
}
void Git::setLogCallback(callback_t func, void *cookie)
{
- m->fn_log_writer_callback = func;
- m->callback_cookie = cookie;
+ m->info.fn_log_writer_callback = func;
+ m->info.callback_cookie = cookie;
+}
+
+void Git::setWorkingRepositoryDir(QString const &repo, const QString &submodpath, QString const &sshkey)
+{
+ m->info.working_repo_dir = repo;
+ m->info.submodule_path = submodpath;
+ m->ssh_key_override = sshkey;
+}
+
+QString Git::workingDir() const
+{
+ QString dir = m->info.working_repo_dir;
+ if (!m->info.submodule_path.isEmpty()) {
+ dir = dir / m->info.submodule_path;
+ }
+ return dir;
}
-void Git::setWorkingRepositoryDir(QString const &repo)
+QString const &Git::sshKey() const
{
- m->working_repo_dir = repo;
+ return m->ssh_key_override;
}
-QString const &Git::workingRepositoryDir() const
+void Git::setSshKey(QString const &sshkey) const
{
- return m->working_repo_dir;
+ m->ssh_key_override = sshkey;
}
bool Git::isValidID(QString const &id)
{
int zero = 0;
int n = id.size();
if (n >= 4 && n <= GIT_ID_LENGTH) {
ushort const *p = id.utf16();
for (int i = 0; i < n; i++) {
uchar c = p[i];
if (c == '0') {
zero++;
} else if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
// ok
} else {
return false;
}
}
if (zero == GIT_ID_LENGTH) { // 全部 0 の時は false を返す
return false;
}
return true; // ok
}
return false;
}
QString Git::status()
{
git("status");
return resultText();
}
QByteArray Git::toQByteArray() const
{
if (m->result.empty()) return QByteArray();
return QByteArray(&m->result[0], m->result.size());
}
QString Git::resultText() const
{
return QString::fromUtf8(toQByteArray());
}
-void Git::setGitCommand(QString const &path)
+void Git::setGitCommand(QString const &gitcmd, QString const &sshcmd)
{
- m->git_command = path;
+ m->info.git_command = gitcmd;
+ m->ssh_command = sshcmd;
}
QString Git::gitCommand() const
{
Q_ASSERT(m);
- return m->git_command;
+ return m->info.git_command;
}
void Git::clearResult()
{
m->result.clear();
m->process_exit_code = 0;
m->error_message = QString();
}
QString Git::errorMessage() const
{
return m->error_message;
}
int Git::getProcessExitCode() const
{
return m->process_exit_code;
}
bool Git::chdirexec(std::function<bool()> const &fn)
{
bool ok = false;
QString cwd = QDir::currentPath();
- QString dir = workingRepositoryDir();
- if (isValidWorkingCopy(dir) && QDir::setCurrent(dir)) {
+ QString dir = workingDir();
+ if (QDir::setCurrent(dir)) {
ok = fn();
QDir::setCurrent(cwd);
}
return ok;
}
bool Git::git(QString const &arg, bool chdir, bool errout, AbstractPtyProcess *pty)
{
QFileInfo info(gitCommand());
if (!info.isExecutable()) {
qDebug() << "Invalid git command: " << gitCommand();
return false;
}
#if DEBUGLOG
qDebug() << "exec: git " << arg;
QTime timer;
timer.start();
#endif
clearResult();
+ QString env;
+ if (m->ssh_command.isEmpty() || m->ssh_key_override.isEmpty()) {
+ // nop
+ } else {
+ if (m->ssh_command.indexOf('\"') >= 0) return false;
+ if (m->ssh_key_override.indexOf('\"') >= 0) return false;
+ if (!QFileInfo(m->ssh_command).isExecutable()) return false;
+ env = QString("GIT_SSH_COMMAND=\"%1\" -i \"%2\" ").arg(m->ssh_command).arg(m->ssh_key_override);
+ }
+
auto DoIt = [&](){
QString cmd = QString("\"%1\" --no-pager ").arg(gitCommand());
cmd += arg;
- if (m->fn_log_writer_callback) {
+ if (m->info.fn_log_writer_callback) {
QByteArray ba;
ba.append("> git ");
ba.append(arg);
ba.append('\n');
- m->fn_log_writer_callback(m->callback_cookie, ba.data(), (int)ba.size());
+ m->info.fn_log_writer_callback(m->info.callback_cookie, ba.data(), (int)ba.size());
}
if (pty) {
- pty->start(cmd, pty->userVariant());
+ pty->start(cmd, env, pty->userVariant());
m->process_exit_code = 0; // バックグラウンドで実行を継続するけど、とりあえず成功したことにしておく
} else {
Process proc;
proc.start(cmd, false);
m->process_exit_code = proc.wait();
if (errout) {
m->result = proc.errbytes;
} else {
m->result = proc.outbytes;
}
m->error_message = proc.errstring();
}
return m->process_exit_code == 0;
};
bool ok = false;
if (chdir) {
if (pty) {
- pty->setChangeDir(workingRepositoryDir());
+ pty->setChangeDir(workingDir());
ok = DoIt();
} else {
ok = chdirexec(DoIt);
}
} else {
ok = DoIt();
}
#if DEBUGLOG
qDebug() << timer.elapsed() << "ms";
#endif
return ok;
}
GitPtr Git::dup() const
{
Git *p = new Git();
- p->m->git_command = m->git_command;
- p->m->working_repo_dir = m->working_repo_dir;
- p->m->fn_log_writer_callback = m->fn_log_writer_callback;
- p->m->callback_cookie = m->callback_cookie;
+ p->m->info = m->info;
return GitPtr(p);
}
bool Git::isValidWorkingCopy(QString const &dir)
{
return QFileInfo(dir).isDir() && QDir(dir / ".git").exists();
}
bool Git::isValidWorkingCopy() const
{
- return isValidWorkingCopy(workingRepositoryDir());
+ return isValidWorkingCopy(workingDir());
}
QString Git::version()
{
git("--version", false);
return resultText().trimmed();
}
bool Git::init()
{
bool ok = false;
QDir cwd = QDir::current();
- QString dir = workingRepositoryDir();
+ QString dir = workingDir();
if (QDir::setCurrent(dir)) {
QString gitdir = dir / ".git";
if (!QFileInfo(gitdir).isDir()) {
git("init", false);
if (QFileInfo(gitdir).isDir()) {
ok = true;
}
}
QDir::setCurrent(cwd.path());
}
return ok;
}
QString Git::rev_parse(QString const &name)
{
QString cmd = "rev-parse %1";
cmd = cmd.arg(name);
if (git(cmd)) {
return resultText().trimmed();
}
return QString();
}
QList<Git::Tag> Git::tags()
{
#if 0
auto MidCmp = [](QString const &line, int i, char const *ptr){
ushort const *p = line.utf16();
for (int j = 0; ptr[j]; j++) {
if (p[i + j] != ptr[j]) return false;
}
return true;
};
QList<Git::Tag> list;
QStringList lines = refs();
for (QString const &line : lines) {
Tag tag;
if (line.isEmpty()) continue;
int i = line.indexOf(' ');
if (i == GIT_ID_LENGTH) {
tag.id = line.mid(0, i);
if (MidCmp(line, i, " refs/tags/")) {
tag.name = line.mid(i + 11).trimmed();
if (tag.name.isEmpty()) continue;
list.push_back(tag);
}
}
}
return list;
#else
return tags2();
#endif
}
QList<Git::Tag> Git::tags2()
{
QList<Tag> list;
git("show-ref");
QStringList lines = misc::splitLines(resultText());
for (QString const &line : lines) {
QStringList l = misc::splitWords(line);
if (l.size() >= 2) {
if (isValidID(l[0]) && l[1].startsWith("refs/tags/")) {
Tag t;
t.name = l[1].mid(10);
t.id = l[0];
list.push_back(t);
}
}
}
return list;
}
bool Git::tag(QString const &name, QString const &id)
{
QString cmd = "tag \"%1\" %2";
cmd = cmd.arg(name).arg(id);
return git(cmd);
}
void Git::delete_tag(QString const &name, bool remote)
{
QString cmd = "tag --delete \"%1\"";
cmd = cmd.arg(name);
git(cmd);
if (remote) {
QString cmd = "push --delete origin \"%1\"";
cmd = cmd.arg(name);
git(cmd);
}
}
QString Git::diff(QString const &old_id, QString const &new_id)
{
QString cmd = "diff --full-index -a %1 %2";
cmd = cmd.arg(old_id).arg(new_id);
git(cmd);
return resultText();
}
QString Git::diff_file(QString const &old_path, QString const &new_path)
{
QString cmd = "diff --full-index -a -- %1 %2";
cmd = cmd.arg(old_path).arg(new_path);
git(cmd);
return resultText();
}
QList<Git::DiffRaw> Git::diff_raw(QString const &old_id, QString const &new_id)
{
QList<DiffRaw> list;
QString cmd = "diff --raw --abbrev=%1 %2 %3";
cmd = cmd.arg(GIT_ID_LENGTH).arg(old_id).arg(new_id);
git(cmd);
QString text = resultText();
QStringList lines = text.split('\n', QString::SkipEmptyParts);
for (QString const &line : lines) { // raw format: e.g. ":100644 100644 08bc10d... 18f0501... M src/MainWindow.cpp"
DiffRaw item;
int colon = line.indexOf(':');
int tab = line.indexOf('\t');
if (colon >= 0 && colon < tab) {
QStringList header = line.mid(colon + 1, tab - colon - 1).split(' ', QString::SkipEmptyParts); // コロンとタブの間の文字列を空白で分割
if (header.size() >= 5) {
QStringList files = line.mid(tab + 1).split('\t', QString::SkipEmptyParts); // タブより後ろはファイルパス
if (!files.empty()) {
for (QString &file : files) {
file = Git::trimPath(file);
}
item.a.id = header[2];
item.b.id = header[3];
item.a.mode = header[0];
item.b.mode = header[1];
item.state = header[4];
item.files = files;
list.push_back(item);
}
}
}
}
return list;
}
QString Git::diff_to_file(QString const &old_id, QString const &path)
{
#if 1
QString cmd = "diff --full-index -a %1 -- \"%2\"";
cmd = cmd.arg(old_id).arg(path);
git(cmd);
return resultText();
#else
return diff_(old_id, new_id);
#endif
}
QString Git::getCurrentBranchName()
{
if (isValidWorkingCopy()) {
git("rev-parse --abbrev-ref HEAD");
QString s = resultText().trimmed();
if (!s.isEmpty() && !s.startsWith("fatal:") && s.indexOf(' ') < 0) {
return s;
}
}
return QString();
}
QStringList Git::getUntrackedFiles()
{
QStringList files;
Git::FileStatusList stats = status_s();
for (FileStatus const &s : stats) {
if (s.code() == FileStatusCode::Untracked) {
files.push_back(s.path1());
}
}
return files;
}
void Git::parseAheadBehind(QString const &s, Branch *b)
{
ushort const *begin = s.utf16();
ushort const *end = begin + s.size();
ushort const *ptr = begin;
auto NCompare = [](ushort const *a, char const *b, size_t n){
for (size_t i = 0; i < n; i++) {
if (*a < (unsigned char)*b) return false;
if (*a > (unsigned char)*b) return false;
if (!*a) break;
a++;
b++;
}
return true;
};
auto SkipSpace = [&](){
while (ptr < end && QChar(*ptr).isSpace()) {
ptr++;
}
};
auto GetNumber = [&](){
int n = 0;
while (ptr < end && QChar(*ptr).isDigit()) {
n = n * 10 + (*ptr - '0');
ptr++;
}
return n;
};
if (*ptr == '[') {
ptr++;
ushort const *e = nullptr;
int n;
for (n = 0; ptr + n < end; n++) {
if (ptr[n] == ']') {
e = ptr + n;
break;
}
}
if (e) {
end = e;
while (1) {
if (NCompare(ptr, "ahead ", 6)) {
ptr += 6;
SkipSpace();
b->ahead = GetNumber();
} else if (NCompare(ptr, "behind ", 7)) {
ptr += 7;
SkipSpace();
b->behind = GetNumber();
} else {
ushort c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == 0) break;
ptr++;
}
}
}
}
}
QList<Git::Branch> Git::branches()
{
QList<Branch> branches;
git(QString("branch -vv -a --abbrev=%1").arg(GIT_ID_LENGTH));
QString s = resultText();
#if DEBUGLOG
qDebug() << s;
#endif
QStringList lines = misc::splitLines(s);
for (QString const &line : lines) {
if (line.size() > 2) {
QString name;
QString text = line.mid(2);
int pos = 0;
if (text.startsWith('(')) {
int i, paren = 1;
for (i = 1; i < text.size(); i++) {
if (text[i] == '(') {
paren++;
} else if (text[i] == ')') {
if (paren > 0) {
paren--;
if (paren == 0) {
pos = i;
break;
}
}
}
}
// pos = text.indexOf(')');
if (pos > 1) {
name = text.mid(1, pos - 1);
pos++;
}
} else {
while (pos < text.size() && !QChar::isSpace(text.utf16()[pos])) {
pos++;
}
name = text.mid(0, pos);
}
if (!name.isEmpty()) {
while (pos < text.size() && QChar::isSpace(text.utf16()[pos])) {
pos++;
}
text = text.mid(pos);
Branch b;
if (name.startsWith("HEAD detached at ")) {
b.flags |= Branch::HeadDetachedAt;
name = name.mid(17);
}
if (name.startsWith("HEAD detached from ")) {
b.flags |= Branch::HeadDetachedFrom;
name = name.mid(19);
}
if (name.startsWith("remotes/")) {
name = name.mid(8);
int i = name.indexOf('/');
if (i > 0) {
b.remote = name.mid(0, i);
name = name.mid(i + 1);
}
}
b.name = name;
if (text.startsWith("-> ")) {
b.id = ">" + text.mid(3);
} else {
int i = text.indexOf(' ');
if (i == GIT_ID_LENGTH) {
b.id = text.mid(0, GIT_ID_LENGTH);
}
while (i < text.size() && QChar::isSpace(text.utf16()[i])) {
i++;
}
text = text.mid(i);
parseAheadBehind(text, &b);
}
if (line.startsWith('*')) {
b.flags |= Branch::Current;
}
branches.push_back(b);
}
}
}
for (int i = 0; i < branches.size(); i++) {
Branch *b = &branches[i];
if (b->id.startsWith('>')) {
QString name = b->id.mid(1);
for (int j = 0; j < branches.size(); j++) {
if (j != i) {
if (branches[j].name == name) {
branches[i].id = branches[j].id;
break;
}
}
}
}
}
return branches;
}
Git::CommitItemList Git::log_all(QString const &id, int maxcount)
{
CommitItemList items;
QString text;
QString cmd = "log --pretty=format:\"commit:%H#gpg:%G?#key:%GK#parent:%P#author:%an#mail:%ae#date:%ci##%s\" --all -%1 %2";
cmd = cmd.arg(maxcount).arg(id);
git(cmd);
if (getProcessExitCode() == 0) {
text = resultText().trimmed();
QStringList lines = misc::splitLines(text);
for (QString const &line : lines) {
int i = line.indexOf("##");
if (i > 0) {
Git::CommitItem item;
QString signed_key;
item.message = line.mid(i + 2);
QStringList atts = line.mid(0, i).split('#');
for (QString const &s : atts) {
int j = s.indexOf(':');
if (j > 0) {
QString key = s.mid(0, j);
QString val = s.mid(j + 1);
if (key == "commit") {
item.commit_id = val;
} else if (key == "gpg") {
item.signature = *val.utf16();
} else if (key == "key") {
signed_key = val;
} else if (key == "parent") {
item.parent_ids = val.split(' ', QString::SkipEmptyParts);
} else if (key == "author") {
item.author = val;
} else if (key == "mail") {
item.email = val;
} else if (key == "date") {
item.commit_date = QDateTime::fromString(val, Qt::ISODate).toLocalTime();
} else if (key == "debug") {
}
}
}
if (!signed_key.isEmpty()) {
ushort const *begin = signed_key.utf16();
int n = signed_key.size();
for (int i = 0; i + 1 < n; i += 2) {
ushort c = begin[i];
ushort d = begin[i + 1];
if (c < 0x80 && isxdigit(c) && isxdigit(d)) {
char tmp[3];
tmp[0] = c;
tmp[1] = d;
tmp[2] = 0;
int v = strtol(tmp, nullptr, 16);
item.fingerprint.push_back(v);
} else {
break;
}
}
}
items.push_back(item);
}
}
}
return items;
}
Git::CommitItemList Git::log(int maxcount)
{
return log_all(QString(), maxcount);
}
bool Git::queryCommit(QString const &id, CommitItem *out)
{
+ *out = {};
if (objectType(id) == "commit") {
out->commit_id = id;
QByteArray ba;
if (cat_file(id, &ba)) {
QStringList lines = misc::splitLines(ba, [](char const *p, size_t n){
return QString::fromUtf8(p, (int)n);
});
while (!lines.empty() && lines[lines.size() - 1].isEmpty()) {
lines.pop_back();
}
int i;
for (i = 0; i < lines.size(); i++) {
QString const &line = lines[i];
if (line.isEmpty()) {
i++;
for (; i < lines.size(); i++) {
QString const &line = lines[i];
if (!out->message.isEmpty()) {
out->message.append('\n');
}
out->message.append(line);
}
break;
}
if (line.startsWith("parent ")) {
out->parent_ids.push_back(line.mid(7));
} else if (line.startsWith("author ")) {
QStringList arr = misc::splitWords(line);
int n = arr.size();
if (n > 4) {
n -= 2;
out->commit_date = QDateTime::fromTime_t(atol(arr[n].toStdString().c_str()));
n--;
out->email = arr[n];
if (out->email.startsWith('<') && out->email.endsWith('>')) {
int n = out->email.size();
out->email = out->email.mid(1, n - 2);
}
for (int i = 1; i < n; i++) {
if (!out->author.isEmpty()) {
out->author += ' ';
}
out->author += arr[i];
}
}
}
}
}
return true;
}
return false;
}
Git::CloneData Git::preclone(QString const &url, QString const &path)
{
CloneData d;
d.url = url;
if (path.endsWith('/') || path.endsWith('\\')) {
auto GitBaseName = [](QString location){
int i = location.lastIndexOf('/');
int j = location.lastIndexOf('\\');
if (i < j) i = j;
i = i < 0 ? 0 : (i + 1);
j = location.size();
if (location.endsWith(".git")) {
j -= 4;
}
if (i < j) {
location = location.mid(i, j - i);
}
return location;
};
d.basedir = path;
d.subdir = GitBaseName(url);
} else {
QFileInfo info(path);
d.basedir = info.dir().path();
d.subdir = info.fileName();
}
return d;
}
bool Git::clone(CloneData const &data, AbstractPtyProcess *pty)
{
QString clone_to = data.basedir / data.subdir;
- m->working_repo_dir = misc::normalizePathSeparator(clone_to);
+ m->info.working_repo_dir = misc::normalizePathSeparator(clone_to);
bool ok = false;
QDir cwd = QDir::current();
auto DoIt = [&](){
QString cmd = "clone --progress \"%1\" \"%2\"";
cmd = cmd.arg(data.url).arg(data.subdir);
ok = git(cmd, false, true, pty);
};
if (pty) {
pty->setChangeDir(data.basedir);
DoIt();
} else {
if (QDir::setCurrent(data.basedir)) {
DoIt();
QDir::setCurrent(cwd.path());
}
}
return ok;
}
+QList<Git::SubmoduleItem> Git::submodules()
+{
+ QList<Git::SubmoduleItem> mods;
+
+ git("submodule");
+ QString text = resultText();
+ ushort c = text.utf16()[0];
+ if (c == ' ' || c == '+' || c == '-') {
+ text = text.mid(1);
+ }
+ QStringList words = misc::splitWords(text);
+ if (words.size() >= 2) {
+ SubmoduleItem sm;
+ sm.id = words[0];
+ sm.path = words[1];
+ if (isValidID(sm.id)) {
+ if (words.size() >= 3) {
+ sm.refs = words[2];
+ if (sm.refs.startsWith('(') && sm.refs.endsWith(')')) {
+ sm.refs = sm.refs.mid(1, sm.refs.size() - 2);
+ }
+ }
+
+ mods.push_back(sm);
+ }
+ }
+ return mods;
+}
+
+bool Git::submodule_add(const CloneData &data, bool force, AbstractPtyProcess *pty)
+{
+ bool ok = false;
+
+ QString cmd = "submodule add";
+ if (force) {
+ cmd += " -f";
+ }
+ cmd += " \"%1\" \"%2\"";
+ cmd = cmd.arg(data.url).arg(data.subdir);
+ ok = git(cmd, true, true, pty);
+
+ return ok;
+}
+
+bool Git::submodule_update(SubmoduleUpdateData const &data, AbstractPtyProcess *pty)
+{
+ bool ok = false;
+
+ QString cmd = "submodule update";
+ if (data.init) {
+ cmd += " --init";
+ }
+ if (data.recursive) {
+ cmd += " --recursive";
+ }
+ ok = git(cmd, true, true, pty);
+
+ return ok;
+
+}
+
QString Git::encodeQuotedText(QString const &str)
{
std::vector<ushort> vec;
ushort const *begin = str.utf16();
ushort const *end = begin + str.size();
ushort const *ptr = begin;
vec.push_back('\"');
while (ptr < end) {
ushort c = *ptr;
ptr++;
if (c == '\"') { // triple quotes
vec.push_back(c);
vec.push_back(c);
vec.push_back(c);
} else {
vec.push_back(c);
}
}
vec.push_back('\"');
return QString::fromUtf16(&vec[0], vec.size());
}
bool Git::commit_(QString const &msg, bool amend, bool sign, AbstractPtyProcess *pty)
{
QString cmd = "commit";
if (amend) {
cmd += " --amend";
}
if (sign) {
cmd += " -S";
}
QString text = msg.trimmed();
if (text.isEmpty()) {
text = "no message";
}
text = encodeQuotedText(text);
cmd += QString(" -m %1").arg(text);
return git(cmd, true, false, pty);
}
bool Git::commit(QString const &text, bool sign, AbstractPtyProcess *pty)
{
return commit_(text, false, sign, pty);
}
bool Git::commit_amend_m(QString const &text, bool sign, AbstractPtyProcess *pty)
{
return commit_(text, true, sign, pty);
}
bool Git::revert(QString const &id)
{
QString cmd = "revert %1";
cmd = cmd.arg(id);
return git(cmd);
}
void Git::push_u(QString const &remote, QString const &branch, AbstractPtyProcess *pty)
{
if (remote.indexOf('\"') >= 0 || branch.indexOf('\"') >= 0) {
return;
}
QString cmd = "push -u \"%1\" \"%2\"";
git(cmd.arg(remote).arg(branch), true, false, pty);
}
bool Git::push_(bool tags, AbstractPtyProcess *pty)
{
QString cmd = "push";
if (tags) {
cmd += " --tags";
}
return git(cmd, true, false, pty);
}
bool Git::push(bool tags, AbstractPtyProcess *pty)
{
return push_(tags, pty);
}
Git::FileStatusList Git::status_s_()
{
FileStatusList files;
if (git("status -s -u --porcelain")) {
QString text = resultText();
QStringList lines = misc::splitLines(text);
for (QString const &line : lines) {
if (line.size() > 3) {
FileStatus s(line);
if (s.code() != FileStatusCode::Unknown) {
files.push_back(s);
}
}
}
}
return files;
}
Git::FileStatusList Git::status_s()
{
return status_s_();
}
QString Git::objectType(QString const &id)
{
- git("cat-file -t " + id);
- return resultText().trimmed();
+ if (isValidID(id)) {
+ git("cat-file -t " + id);
+ return resultText().trimmed();
+ }
+ return {};
}
QByteArray Git::cat_file_(QString const &id)
{
- git("cat-file -p " + id);
- return toQByteArray();
+ if (isValidID(id)) {
+ git("cat-file -p " + id);
+ return toQByteArray();
+ }
+ return {};
}
bool Git::cat_file(QString const &id, QByteArray *out)
{
if (isValidID(id)) {
*out = cat_file_(id);
return true;
}
return false;
}
void Git::resetFile(QString const &path)
{
git("checkout -- " + path);
}
void Git::resetAllFiles()
{
git("reset --hard HEAD");
}
void Git::removeFile(QString const &path)
{
git("rm " + path);
}
void Git::stage(QString const &path)
{
git("add " + path);
}
void Git::stage(QStringList const &paths)
{
QString cmd = "add";
for (QString const &path : paths) {
cmd += ' ';
cmd += '\"';
cmd += path;
cmd += '\"';
}
git(cmd);
}
void Git::unstage(QString const &path)
{
QString cmd = "reset HEAD \"%1\"";
git(cmd.arg(path));
}
void Git::unstage(QStringList const &paths)
{
QString cmd = "reset HEAD";
for (QString const &path : paths) {
cmd += " \"";
cmd += path;
cmd += '\"';
}
git(cmd);
}
void Git::pull(AbstractPtyProcess *pty)
{
git("pull", true, false, pty);
}
void Git::fetch(AbstractPtyProcess *pty, bool prune)
{
QString cmd = "fetch --tags -f";
if (prune) {
cmd += " --prune";
}
git(cmd, true, false, pty);
}
void Git::fetch_tags_f(AbstractPtyProcess *pty)
{
QString cmd = "fetch --tags -f";
git(cmd, true, false, pty);
}
QStringList Git::make_branch_list_()
{
QStringList list;
QStringList l = misc::splitLines(resultText());
for (QString const &s : l) {
if (s.startsWith("* ")) list.push_back(s.mid(2));
if (s.startsWith(" ")) list.push_back(s.mid(2));
}
return list;
}
void Git::createBranch(QString const &name)
{
git("branch " + name);
}
void Git::checkoutBranch(QString const &name)
{
git("checkout " + name);
}
void Git::cherrypick(QString const &name)
{
git("cherry-pick " + name);
}
QString Git::getCherryPicking() const
{
- QString dir = workingRepositoryDir();
+ QString dir = workingDir();
QString path = dir / ".git/CHERRY_PICK_HEAD";
QFile file(path);
if (file.open(QFile::ReadOnly)) {
QString line = QString::fromLatin1(file.readLine()).trimmed();
if (isValidID(line)) {
return line;
}
}
return QString();
}
QString Git::getMessage(QString const &id)
{
git("show --no-patch --pretty=%s " + id);
return resultText().trimmed();
}
-void Git::mergeBranch(QString const &name, MergeFastForward ff)
+void Git::mergeBranch(QString const &name, MergeFastForward ff, bool squash)
{
QString cmd = "merge ";
switch (ff) {
case MergeFastForward::No:
cmd += "--no-ff ";
break;
case MergeFastForward::Only:
cmd += "--ff-only ";
break;
default:
cmd += "--ff ";
break;
}
+ if (squash) {
+ cmd += "--squash ";
+ }
git(cmd + name);
}
void Git::rebaseBranch(QString const &name)
{
git("rebase " + name);
}
QStringList Git::getRemotes()
{
QStringList ret;
git("remote");
QStringList lines = misc::splitLines(resultText());
for (QString const &line: lines) {
if (!line.isEmpty()) {
ret.push_back(line);
}
}
return ret;
}
Git::User Git::getUser(Source purpose)
{
User user;
bool global = purpose == Git::Source::Global;
bool local = purpose == Git::Source::Local;
QString arg1;
if (global) arg1 = "--global";
if (local) arg1 = "--local";
bool chdir = !global;
if (git(QString("config %1 user.name").arg(arg1), chdir)) {
user.name = resultText().trimmed();
}
if (git(QString("config %1 user.email").arg(arg1), chdir)) {
user.email = resultText().trimmed();
}
return user;
}
void Git::setUser(const User &user, bool global)
{
bool chdir = !global;
git(QString("config %1 user.name %2").arg(global ? "--global" : "").arg(encodeQuotedText(user.name)), chdir);
git(QString("config %1 user.email %2").arg(global ? "--global" : "").arg(encodeQuotedText(user.email)), chdir);
}
bool Git::reset_head1()
{
return git("reset HEAD~1");
}
bool Git::reset_hard()
{
return git("reset --hard");
}
bool Git::clean_df()
{
return git("clean -df");
}
bool Git::stash()
{
return git("stash");
}
bool Git::stash_apply()
{
return git("stash apply");
}
bool Git::stash_drop()
{
return git("stash drop");
}
bool Git::rm_cached(QString const &file)
{
QString cmd = "rm --cached \"%1\"";
return git(cmd.arg(file));
}
void Git::getRemoteURLs(QList<Remote> *out)
{
out->clear();
git("remote -v");
QStringList lines = misc::splitLines(resultText());
for (QString const &line : lines) {
int i = line.indexOf('\t');
int j = line.indexOf(" (");
if (i > 0 && i < j) {
Remote r;
r.name = line.mid(0, i);
r.url = line.mid(i + 1, j - i - 1);
r.purpose = line.mid(j + 1);
+ r.ssh_key = m->ssh_key_override;
if (r.purpose.startsWith('(') && r.purpose.endsWith(')')) {
r.purpose = r.purpose.mid(1, r.purpose.size() - 2);
}
out->push_back(r);
}
}
}
-void Git::setRemoteURL(QString const &name, QString const &url)
+void Git::setRemoteURL(Git::Remote const &remote)
{
QString cmd = "remote set-url %1 %2";
- cmd = cmd.arg(encodeQuotedText(name)).arg(encodeQuotedText(url));
+ cmd = cmd.arg(encodeQuotedText(remote.name)).arg(encodeQuotedText(remote.url));
git(cmd);
}
-void Git::addRemoteURL(QString const &name, QString const &url)
+void Git::addRemoteURL(Git::Remote const &remote)
{
QString cmd = "remote add \"%1\" \"%2\"";
- cmd = cmd.arg(encodeQuotedText(name)).arg(encodeQuotedText(url));
+ cmd = cmd.arg(encodeQuotedText(remote.name)).arg(encodeQuotedText(remote.url));
+ m->ssh_key_override = remote.ssh_key;
git(cmd);
}
void Git::removeRemote(QString const &name)
{
QString cmd = "remote remove %1";
cmd = cmd.arg(encodeQuotedText(name));
git(cmd);
}
bool Git::reflog(ReflogItemList *out, int maxcount)
{
out->clear();
QString cmd = "reflog --no-abbrev --raw -n %1";
cmd = cmd.arg(maxcount);
if (!git(cmd)) return false;
QByteArray ba = toQByteArray();
if (!ba.isEmpty()) {
ReflogItem item;
char const *begin = ba.data();
char const *end = begin + ba.size();
char const *left = begin;
char const *ptr = begin;
while (1) {
int c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == '\r' || c == '\n' || c == 0) {
int d = 0;
QString line = QString::fromUtf8(left, ptr - left);
if (left < ptr) {
d = *left & 0xff;
}
if (d == ':') {
// ex. ":100644 100644 bb603836fb597cca994309a1f0a52251d6b20314 d6b9798854debee375bb419f0f2ed9c8437f1932 M\tsrc/MainWindow.cpp"
int tab = line.indexOf('\t');
if (tab > 1) {
QString tmp = line.mid(1, tab - 1);
QString path = line.mid(tab + 1);
QStringList cols = misc::splitWords(tmp);
if (!path.isEmpty() && cols.size() == 5) {
ReflogItem::File file;
file.atts_a = cols[0];
file.atts_b = cols[1];
file.id_a = cols[2];
file.id_b = cols[3];
file.type = cols[4];
file.path = path;
item.files.push_back(file);
}
}
} else {
bool start = isxdigit(d);
if (start || c == 0) {
if (!item.id.isEmpty()) {
out->push_back(item);
}
}
if (start) {
// ex. "0a2a8b6b66f48bcbf985d8a2afcd14ff41676c16 HEAD@{188}: commit: message"
item = ReflogItem();
int i = line.indexOf(": ");
if (i > 0) {
int j = line.indexOf(": ", i + 2);
if (j > 2) {
item.head = line.mid(0, i);
item.command = line.mid(i + 2, j - i - 2);
item.message = line.mid(j + 2);
if (item.head.size() > GIT_ID_LENGTH) {
item.id = item.head.mid(0, GIT_ID_LENGTH);
item.head = item.head.mid(GIT_ID_LENGTH + 1);
}
}
}
}
}
if (c == 0) break;
ptr++;
left = ptr;
} else {
ptr++;
}
}
}
return true;
}
// Git::FileStatus
QString Git::trimPath(QString const &s)
{
ushort const *begin = s.utf16();
ushort const *end = begin + s.size();
ushort const *left = begin;
ushort const *right = end;
while (left < right && QChar(*left).isSpace()) left++;
while (left < right && QChar(right[-1]).isSpace()) right--;
if (left + 1 < right && *left == '\"' && right[-1] == '\"') { // if quoted ?
left++;
right--;
QByteArray ba;
ushort const *ptr = left;
while (ptr < right) {
ushort c = *ptr;
ptr++;
if (c == '\\') {
c = 0;
while (ptr < right && QChar(*ptr).isDigit()) { // decode \oct
c = c * 8 + (*ptr - '0');
ptr++;
}
}
ba.push_back(c);
}
return QString::fromUtf8(ba);
}
if (left == begin && right == end) return s;
return QString::fromUtf16(left, right - left);
}
Git::FileStatusCode Git::FileStatus::parseFileStatusCode(char x, char y)
{
if (x == ' ' && (y == 'M' || y == 'D')) return FileStatusCode::NotUpdated;
if (x == 'M' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::UpdatedInIndex;
if (x == 'A' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::AddedToIndex;
if (x == 'D' && (y == 'M' || y == ' ')) return FileStatusCode::DeletedFromIndex;
if (x == 'R' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::RenamedInIndex;
if (x == 'C' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::CopiedInIndex;
if (x == 'C' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::CopiedInIndex;
if (x == 'D' && y == 'D') return FileStatusCode::Unmerged_BothDeleted;
if (x == 'A' && y == 'U') return FileStatusCode::Unmerged_AddedByUs;
if (x == 'U' && y == 'D') return FileStatusCode::Unmerged_DeletedByThem;
if (x == 'U' && y == 'A') return FileStatusCode::Unmerged_AddedByThem;
if (x == 'D' && y == 'U') return FileStatusCode::Unmerged_DeletedByUs;
if (x == 'A' && y == 'A') return FileStatusCode::Unmerged_BothAdded;
if (x == 'U' && y == 'U') return FileStatusCode::Unmerged_BothModified;
if (x == '?' && y == '?') return FileStatusCode::Untracked;
if (x == '!' && y == '!') return FileStatusCode::Ignored;
return FileStatusCode::Unknown;
}
void Git::FileStatus::parse(QString const &text)
{
data = Data();
if (text.size() > 3) {
ushort const *p = text.utf16();
if (p[2] == ' ') {
data.code_x = p[0];
data.code_y = p[1];
int i = text.indexOf(" -> ", 3);
if (i > 3) {
data.rawpath1 = text.mid(3, i - 3);
data.rawpath2 = text.mid(i + 4);
data.path1 = trimPath(data.rawpath1);
data.path2 = trimPath(data.rawpath2);
} else {
data.rawpath1 = text.mid(3);
data.path1 = trimPath(data.rawpath1);
data.path2 = QString();
}
data.code = parseFileStatusCode(data.code_x, data.code_y);
}
}
}
QByteArray Git::blame(QString const &path)
{
QString cmd = "blame --porcelain --abbrev=40 \"%1\"";
cmd = cmd.arg(path);
if (git(cmd)) {
return toQByteArray();
}
return QByteArray();
}
QList<Git::RemoteInfo> Git::ls_remote()
{
QList<RemoteInfo> list;
QString cmd = "ls-remote";
if (git(cmd)) {
QStringList lines = misc::splitLines(resultText());
for (QString const &line : lines) {
QStringList words = misc::splitWords(line);
if (words.size() == 2 && isValidID(words[0])) {
RemoteInfo info;
info.commit_id = words[0];
info.name = words[1];
list.push_back(info);
}
}
}
return list;
}
QString Git::signingKey(Source purpose)
{
QString arg1;
if (purpose == Source::Global) arg1 = "--global";
if (purpose == Source::Local) arg1 = "--local";
QString cmd = "config %1 user.signingkey";
cmd = cmd.arg(arg1);
bool chdir = purpose != Source::Global;
if (git(cmd, chdir)) {
return resultText().trimmed();
}
return QString();
}
bool Git::setSigningKey(QString const &id, bool global)
{
for (auto i : id) {
if (!QChar(i).isLetterOrNumber()) return false;
}
QString cmd = "config %1 %2 user.signingkey %3";
cmd = cmd.arg(global ? "--global" : "--local").arg(id.isEmpty() ? "--unset" : "").arg(id);
return git(cmd, !global);
}
Git::SignPolicy Git::signPolicy(Source source)
{
QString arg1;
if (source == Source::Global) arg1 = "--global";
if (source == Source::Local) arg1 = "--local";
QString cmd = "config %1 commit.gpgsign";
cmd = cmd.arg(arg1);
bool chdir = source != Source::Global;
if (git(cmd, chdir)) {
QString t = resultText().trimmed();
if (t == "false") return SignPolicy::False;
if (t == "true") return SignPolicy::True;
}
return SignPolicy::Unset;
}
bool Git::setSignPolicy(Source source, SignPolicy policy)
{
QString arg1;
if (source == Source::Global) arg1 = "--global";
if (source == Source::Local) arg1 = "--local";
QString cmd = "config %1 %2 commit.gpgsign %3";
QString arg2;
QString arg3;
if (policy == SignPolicy::False) {
arg3 = "false";
} else if (policy == SignPolicy::True) {
arg3 = "true";
} else {
arg2 = "--unset";
}
cmd = cmd.arg(arg1).arg(arg2).arg(arg3);
bool chdir = source != Source::Global;
return git(cmd, chdir);
}
bool Git::configGpgProgram(QString const &path, bool global)
{
QString cmd = "config ";
if (global) {
cmd += "--global ";
}
if (path.isEmpty()) {
cmd += "--unset ";
}
cmd += "gpg.program ";
if (!path.isEmpty()) {
cmd += QString("\"%1\"").arg(path);
}
return git(cmd, false);
}
// Diff
void Git::Diff::makeForSingleFile(Git::Diff *diff, QString const &id_a, QString const &id_b, QString const &path, QString const &mode)
{
diff->diff = QString("diff --git a/%1 b/%2").arg(path).arg(path);
diff->index = QString("index %1..%2 %3").arg(id_a).arg(id_b).arg(0);
diff->blob.a_id = id_a;
diff->blob.b_id = id_b;
diff->path = path;
diff->mode = mode;
diff->type = Git::Diff::Type::Create;
}
//
void parseDiff(std::string const &s, Git::Diff const *info, Git::Diff *out)
{
std::vector<std::string> lines;
{
char const *begin = s.c_str();
char const *end = begin + s.size();
misc::splitLines(begin, end, &lines, false);
}
out->diff = QString("diff --git ") + ("a/" + info->path) + ' ' + ("b/" + info->path);
out->index = QString("index ") + info->blob.a_id + ".." + info->blob.b_id + ' ' + info->mode;
out->path = info->path;
out->blob = info->blob;
bool atat = false;
for (std::string const &line : lines) {
int c = line[0] & 0xff;
if (c == '@') {
if (strncmp(line.c_str(), "@@ ", 3) == 0) {
out->hunks.push_back(Git::Hunk());
out->hunks.back().at = line;
atat = true;
}
} else {
if (atat && c == '\\') { // e.g. \ No newline at end of file...
// ignore this line
} else {
if (atat) {
if (c == ' ' || c == '-' || c == '+') {
// nop
} else {
atat = false;
}
}
if (atat) {
if (!out->hunks.isEmpty()) {
out->hunks.back().lines.push_back(line);
}
}
}
}
}
}
+
+void parseGitSubModules(const QByteArray &ba, QList<Git::SubmoduleItem> *out)
+{
+ *out = {};
+ QStringList lines = misc::splitLines(QString::fromUtf8(ba));
+ Git::SubmoduleItem submod;
+ auto Push = [&](){
+ if (!submod.name.isEmpty()) {
+ out->push_back(submod);
+ }
+ submod = {};
+ };
+ for (int i = 0; i < lines.size(); i++) {
+ QString line = lines[i].trimmed();
+ if (line.startsWith('[')) {
+ Push();
+ if (line.startsWith("[submodule ") && line.endsWith(']')) {
+ int i = 11;
+ int j = line.size() - 1;
+ if (i + 1 < j && line[i] == '\"') {
+ if (line[j - 1] == '\"') {
+ i++;
+ j--;
+ }
+ }
+ submod.name = line.mid(i, j - i);
+ }
+ } else {
+ int i = line.indexOf('=');
+ if (i > 0) {
+ QString key = line.mid(0, i).trimmed();
+ QString val = line.mid(i + 1).trimmed();
+ if (key == "path") {
+ submod.path = val;
+ } else if (key == "url") {
+ submod.url = val;
+ }
+ }
+ }
+ }
+ Push();
+}
diff --git a/src/Git.h b/src/Git.h
index d5c9d30..b5c10c8 100644
--- a/src/Git.h
+++ b/src/Git.h
@@ -1,497 +1,533 @@
#ifndef GIT_H
#define GIT_H
#include "AbstractProcess.h"
#include <QDateTime>
#include <QObject>
#include <functional>
#include <QDebug>
#include <QMutex>
#include <memory>
#define SINGLE_THREAD 0
#define GIT_ID_LENGTH (40)
class Win32PtyProcess;
enum class LineSide {
Left,
Right,
};
struct TreeLine {
int index;
int depth;
int color_number = 0;
bool bend_early = false;
TreeLine(int index = -1, int depth = -1)
: index(index)
, depth(depth)
{
}
};
struct NamedCommitItem {
enum class Type {
None,
BranchLocal,
BranchRemote,
Tag,
};
Type type = Type::None;
QString remote;
QString name;
QString id;
};
using NamedCommitList = QList<NamedCommitItem>;
class Git;
using GitPtr = std::shared_ptr<Git>;
class Git : QObject {
public:
class Context {
public:
QString git_command;
+ QString ssh_command;// = "C:/Program Files/Git/usr/bin/ssh.exe";
};
struct Object {
enum class Type {
UNKNOWN = 0,
COMMIT = 1,
TREE = 2,
BLOB = 3,
TAG = 4,
UNDEFINED = 5,
OFS_DELTA = 6,
REF_DELTA = 7,
};
Type type = Type::UNKNOWN;
QByteArray content;
};
+ struct SubmoduleItem {
+ QString name;
+ QString id;
+ QString path;
+ QString refs;
+ QString url;
+ operator bool () const
+ {
+ return isValidID(id) && !path.isEmpty();
+ }
+ };
+
+ struct CommitItem {
+ QString commit_id;
+ QStringList parent_ids;
+ QString author;
+ QString email;
+ QString message;
+ QDateTime commit_date;
+ std::vector<TreeLine> parent_lines;
+ QByteArray fingerprint;
+ char signature = 0; // git log format:%G?
+ bool has_child = false;
+ int marker_depth = -1;
+ bool resolved = false;
+ bool strange_date = false;
+ };
+ using CommitItemList = std::vector<CommitItem>;
+
class Hunk {
public:
std::string at;
std::vector<std::string> lines;
};
class Diff {
public:
enum class Type {
Unknown,
Modify,
Copy,
Rename,
Create,
Delete,
ChType,
Unmerged,
};
Type type = Type::Unknown;
QString diff;
QString index;
QString path;
QString mode;
struct BLOB_AB_ {
QString a_id;
QString b_id;
} blob;
QList<Hunk> hunks;
+ struct SubmoduleDetail {
+ Git::SubmoduleItem item;
+ Git::CommitItem commit;
+ } a_submodule, b_submodule;
Diff() = default;
Diff(QString const &id, QString const &path, QString const &mode)
{
makeForSingleFile(this, QString(GIT_ID_LENGTH, '0'), id, path, mode);
}
+ bool isSubmodule() const
+ {
+ return mode == "160000";
+ }
private:
void makeForSingleFile(Git::Diff *diff, QString const &id_a, QString const &id_b, QString const &path, QString const &mode);
};
enum class SignatureGrade {
NoSignature,
Unknown,
Good,
Dubious,
Missing,
Bad,
};
static SignatureGrade evaluateSignature(char s)
{
switch (s) {
case 'G':
return SignatureGrade::Good;
case 'U':
case 'X':
case 'Y':
return SignatureGrade::Dubious;
case 'B':
case 'R':
return SignatureGrade::Bad;
case 'E':
return SignatureGrade::Missing;
case 'N':
case ' ':
case 0:
return SignatureGrade::NoSignature;
}
return SignatureGrade::Unknown;
}
- struct CommitItem {
- QString commit_id;
- QStringList parent_ids;
- QString author;
- QString email;
- QString message;
- QDateTime commit_date;
- std::vector<TreeLine> parent_lines;
- QByteArray fingerprint;
- char signature = 0; // git log format:%G?
- bool has_child = false;
- int marker_depth = -1;
- bool resolved = false;
- bool strange_date = false;
- };
- using CommitItemList = std::vector<CommitItem>;
-
static bool isUncommited(CommitItem const &item)
{
return item.commit_id.isEmpty();
}
struct Branch {
QString name;
QString id;
QString remote;
int ahead = 0;
int behind = 0;
enum {
None,
Current = 0x0001,
HeadDetachedAt = 0x0002,
HeadDetachedFrom = 0x0004,
};
int flags = 0;
operator bool () const
{
if (name.isEmpty()) return false;
if (id.isEmpty()) return false;
return true;
}
bool isCurrent() const
{
return flags & Current;
}
bool isHeadDetached() const
{
return flags & HeadDetachedAt;
}
};
struct Tag {
QString name;
QString id;
};
enum class FileStatusCode : unsigned int {
Unknown,
Ignored,
Untracked,
NotUpdated = 0x10000000,
Staged_ = 0x20000000,
UpdatedInIndex,
AddedToIndex,
DeletedFromIndex,
RenamedInIndex,
CopiedInIndex,
Unmerged_ = 0x40000000,
Unmerged_BothDeleted,
Unmerged_AddedByUs,
Unmerged_DeletedByThem,
Unmerged_AddedByThem,
Unmerged_DeletedByUs,
Unmerged_BothAdded,
Unmerged_BothModified,
Tracked_ = 0xf0000000
};
enum class MergeFastForward {
Default,
No,
Only,
};
class FileStatus {
public:
struct Data {
char code_x = 0;
char code_y = 0;
FileStatusCode code = FileStatusCode::Unknown;
QString rawpath1;
QString rawpath2;
QString path1;
QString path2;
} data;
static FileStatusCode parseFileStatusCode(char x, char y);
bool isStaged() const
{
return (int)data.code & (int)FileStatusCode::Staged_;
}
bool isUnmerged() const
{
return (int)data.code & (int)FileStatusCode::Unmerged_;
}
bool isTracked() const
{
return (int)data.code & (int)FileStatusCode::Tracked_;
}
void parse(QString const &text);
FileStatus() = default;
FileStatus(QString const &text)
{
parse(text);
}
FileStatusCode code() const
{
return data.code;
}
int code_x() const
{
return data.code_x;
}
int code_y() const
{
return data.code_y;
}
bool isDeleted() const
{
return code_x() == 'D' || code_y() == 'D';
}
QString path1() const
{
return data.path1;
}
QString path2() const
{
return data.path2;
}
QString rawpath1() const
{
return data.rawpath1;
}
QString rawpath2() const
{
return data.rawpath2;
}
};
using FileStatusList = std::vector<FileStatus>;
static QString trimPath(QString const &s);
private:
struct Private;
Private *m;
QStringList make_branch_list_();
QByteArray cat_file_(QString const &id);
FileStatusList status_s_();
bool commit_(QString const &msg, bool amend, bool sign, AbstractPtyProcess *pty);
bool push_(bool tags, AbstractPtyProcess *pty);
static void parseAheadBehind(QString const &s, Branch *b);
Git();
QString encodeQuotedText(QString const &str);
public:
- Git(Context const &cx, QString const &repodir);
+ Git(Context const &cx, QString const &repodir, QString const &submodpath, QString const &sshkey);
Git(Git &&r) = delete;
~Git() override;
using callback_t = bool (*)(void *, const char *, int);
void setLogCallback(callback_t func, void *cookie);
QByteArray toQByteArray() const;
- void setGitCommand(QString const &path);
+ void setGitCommand(QString const &gitcmd, const QString &sshcmd = {});
QString gitCommand() const;
void clearResult();
QString resultText() const;
bool chdirexec(std::function<bool ()> const &fn);
bool git(QString const &arg, bool chdir, bool errout = false, AbstractPtyProcess *pty = nullptr);
bool git(QString const &arg)
{
return git(arg, true);
}
- void setWorkingRepositoryDir(QString const &repo);
- QString const &workingRepositoryDir() const;
+ void setWorkingRepositoryDir(QString const &repo, const QString &submodpath, const QString &sshkey);
+ QString workingDir() const;
+ QString const &sshKey() const;
+ void setSshKey(const QString &sshkey) const;
QString getCurrentBranchName();
bool isValidWorkingCopy() const;
QString version();
bool init();
QStringList getUntrackedFiles();
CommitItemList log_all(QString const &id, int maxcount);
CommitItemList log(int maxcount);
bool queryCommit(QString const &id, CommitItem *out);
struct CloneData {
QString url;
QString basedir;
QString subdir;
};
static CloneData preclone(QString const &url, QString const &path);
bool clone(CloneData const &data, AbstractPtyProcess *pty);
FileStatusList status_s();
bool cat_file(QString const &id, QByteArray *out);
void resetFile(QString const &path);
void resetAllFiles();
void removeFile(QString const &path);
void stage(QString const &path);
void stage(QStringList const &paths);
void unstage(QString const &path);
void unstage(QStringList const &paths);
void pull(AbstractPtyProcess *pty = nullptr);
void fetch(AbstractPtyProcess *pty = nullptr, bool prune = false);
void fetch_tags_f(AbstractPtyProcess *pty);
QList<Branch> branches();
int getProcessExitCode() const;
QString diff(QString const &old_id, QString const &new_id);
QString diff_file(QString const &old_path, QString const &new_path);
struct DiffRaw {
struct AB {
QString id;
QString mode;
} a, b;
QString state;
QStringList files;
};
struct Remote {
QString name;
QString url;
QString purpose;
+ QString ssh_key;
};
QList<DiffRaw> diff_raw(QString const &old_id, QString const &new_id);
static bool isValidID(QString const &id);
QString status();
bool commit(QString const &text, bool sign, AbstractPtyProcess *pty);
bool commit_amend_m(QString const &text, bool sign, AbstractPtyProcess *pty);
bool revert(QString const &id);
bool push(bool tags, AbstractPtyProcess *pty = nullptr);
void getRemoteURLs(QList<Remote> *out);
void createBranch(QString const &name);
void checkoutBranch(QString const &name);
- void mergeBranch(QString const &name, MergeFastForward ff);
+ void mergeBranch(QString const &name, MergeFastForward ff, bool squash);
void rebaseBranch(QString const &name);
static bool isValidWorkingCopy(QString const &dir);
QString diff_to_file(QString const &old_id, QString const &path);
QString errorMessage() const;
GitPtr dup() const;
QString rev_parse(QString const &name);
QList<Tag> tags();
QList<Tag> tags2();
bool tag(QString const &name, QString const &id = QString());
void delete_tag(QString const &name, bool remote);
- void setRemoteURL(QString const &name, QString const &url);
- void addRemoteURL(QString const &name, QString const &url);
+ void setRemoteURL(const Remote &remote);
+ void addRemoteURL(const Remote &remote);
void removeRemote(QString const &name);
QStringList getRemotes();
struct User {
QString name;
QString email;
};
enum class Source {
Default,
Global,
Local,
};
User getUser(Source purpose);
void setUser(User const&user, bool global);
bool reset_head1();
bool reset_hard();
bool clean_df();
void push_u(QString const &remote, QString const &branch, AbstractPtyProcess *pty);
QString objectType(QString const &id);
bool rm_cached(QString const &file);
void cherrypick(QString const &name);
QString getCherryPicking() const;
QString getMessage(const QString &id);
struct ReflogItem {
QString id;
QString head;
QString command;
QString message;
struct File {
QString atts_a;
QString atts_b;
QString id_a;
QString id_b;
QString type;
QString path;
};
QList<File> files;
};
using ReflogItemList = QList<ReflogItem>;
bool reflog(ReflogItemList *out, int maxcount = 100);
QByteArray blame(QString const &path);
enum SignPolicy {
Unset,
False,
True,
};
QString signingKey(Source purpose);
bool setSigningKey(QString const &id, bool global);
SignPolicy signPolicy(Source source);
bool setSignPolicy(Source source, SignPolicy policy);
bool configGpgProgram(QString const &path, bool global);
struct RemoteInfo {
QString commit_id;
QString name;
};
QList<RemoteInfo> ls_remote();
bool stash();
bool stash_apply();
bool stash_drop();
+ struct SubmoduleUpdateData {
+ bool init = true;
+ bool recursive = true;
+ };
+
+
+ QList<SubmoduleItem> submodules();
+ bool submodule_add(const CloneData &data, bool force, AbstractPtyProcess *pty);
+ bool submodule_update(const SubmoduleUpdateData &data, AbstractPtyProcess *pty);
};
void parseDiff(std::string const &s, Git::Diff const *info, Git::Diff *out);
+void parseGitSubModules(QByteArray const &ba, QList<Git::SubmoduleItem> *out);
+
+
#endif // GIT_H
diff --git a/src/GitDiff.cpp b/src/GitDiff.cpp
index 3186f6a..0c3878f 100644
--- a/src/GitDiff.cpp
+++ b/src/GitDiff.cpp
@@ -1,458 +1,469 @@
#include "GitDiff.h"
#include "BasicMainWindow.h"
#include <QDebug>
#include <QThread>
-bool parse_tree_(GitObjectCache *objcache, QString const &commit_id, QString const &path_prefix, GitTreeItemList *out)
-{
- out->clear();
- if (!commit_id.isEmpty()) {
- Git::Object obj = objcache->catFile(commit_id);
- if (!obj.content.isEmpty()) { // 内容を取得
- QString s = QString::fromUtf8(obj.content);
- QStringList lines = misc::splitLines(s);
- for (QString const &line : lines) {
- int tab = line.indexOf('\t'); // タブより後ろにパスがある
- if (tab > 0) {
- QString stat = line.mid(0, tab); // タブの手前まで
- QStringList vals = misc::splitWords(stat); // 空白で分割
- if (vals.size() >= 3) {
- GitTreeItem data;
- data.mode = vals[0]; // ファイルモード
- data.id = vals[2]; // id(ハッシュ値)
- QString type = vals[1]; // 種類(tree/blob)
- QString path = line.mid(tab + 1); // パス
- path = Git::trimPath(path);
- data.name = path_prefix.isEmpty() ? path : misc::joinWithSlash(path_prefix, path);
- if (type == "tree") {
- data.type = GitTreeItem::TREE;
- out->push_back(data);
- } else if (type == "blob") {
- data.type = GitTreeItem::BLOB;
- out->push_back(data);
- }
- }
- }
- }
- return true;
- }
- }
- return false;
-}
-
// PathToIdMap
class GitDiff::LookupTable {
private:
public:
std::map<QString, QString> path_to_id_map;
std::map<QString, QString> id_to_path_map;
public:
using const_iterator = std::map<QString, QString>::const_iterator;
void store(QString const &path, QString const &id)
{
path_to_id_map[path] = id;
id_to_path_map[id] = path;
}
void store(GitTreeItemList const &files)
{
for (GitTreeItem const &cd : files) {
store(cd.name, cd.id);
}
}
const_iterator find_path(QString const &path) const
{
return path_to_id_map.find(path);
}
const_iterator end_path() const
{
return path_to_id_map.end();
}
};
// GitDiff
GitPtr GitDiff::git()
{
- return objcache->git()->dup();
+ return objcache->git();
+}
+
+GitPtr GitDiff::git(Git::SubmoduleItem const &submod)
+{
+ return objcache->git(submod);
}
QString GitDiff::makeKey(QString const &a_id, QString const &b_id)
{
return a_id + ".." + b_id;
}
QString GitDiff::makeKey(Git::Diff const &diff)
{
return makeKey(diff.blob.a_id, diff.blob.b_id);
}
QString GitDiff::prependPathPrefix(QString const &path)
{
return PATH_PREFIX + path;
}
QString GitDiff::diffObjects(GitPtr const &g, QString const &a_id, QString const &b_id)
{
QString path_prefix = PATH_PREFIX;
if (b_id.startsWith(path_prefix)) {
QString path = b_id.mid(path_prefix.size());
return g->diff_to_file(a_id, path);
} else {
return g->diff(a_id, b_id);
}
}
QString GitDiff::diffFiles(GitPtr const &g, QString const &a_path, QString const &b_path)
{
return g->diff_file(a_path, b_path);
}
void GitDiff::parseDiff(std::string const &s, Git::Diff const *info, Git::Diff *out)
{
std::vector<std::string> lines;
{
char const *begin = s.c_str();
char const *end = begin + s.size();
misc::splitLines(begin, end, &lines, false);
}
out->diff = QString("diff --git ") + ("a/" + info->path) + ' ' + ("b/" + info->path);
out->index = QString("index ") + info->blob.a_id + ".." + info->blob.b_id + ' ' + info->mode;
out->path = info->path;
out->blob = info->blob;
+ out->a_submodule.item = info->a_submodule.item;
+ out->a_submodule.commit = info->a_submodule.commit;
+ out->b_submodule.item = info->b_submodule.item;
+ out->b_submodule.commit = info->b_submodule.commit;
bool atat = false;
for (std::string const &line : lines) {
int c = line[0] & 0xff;
if (c == '@') {
if (strncmp(line.c_str(), "@@ ", 3) == 0) {
out->hunks.push_back(Git::Hunk());
out->hunks.back().at = line;
atat = true;
}
} else {
if (atat && c == '\\') { // e.g. \ No newline at end of file...
// ignore this line
} else {
if (atat) {
if (c == ' ' || c == '-' || c == '+') {
// nop
} else {
atat = false;
}
}
if (atat) {
if (!out->hunks.isEmpty()) {
out->hunks.back().lines.push_back(line);
}
}
}
}
}
}
void GitDiff::retrieveCompleteTree(QString const &dir, GitTreeItemList const *files, std::map<QString, GitTreeItem> *out)
{
for (GitTreeItem const &d : *files) {
QString path = misc::joinWithSlash(dir, d.name);
if (d.type == GitTreeItem::BLOB) {
(*out)[path] = d;
} else if (d.type == GitTreeItem::TREE) {
GitTreeItemList files2;
- parse_tree_(objcache, d.id, QString(), &files2);
+ parseGitTreeObject(objcache, d.id, QString(), &files2);
retrieveCompleteTree(path, &files2, out);
}
}
}
void GitDiff::retrieveCompleteTree(QString const &dir, GitTreeItemList const *files)
{
for (GitTreeItem const &d : *files) {
QString path = misc::joinWithSlash(dir, d.name);
if (d.type == GitTreeItem::BLOB) {
Git::Diff diff(d.id, path, d.mode);
diffs.push_back(diff);
} else if (d.type == GitTreeItem::TREE) {
GitTreeItemList files2;
- parse_tree_(objcache, d.id, QString(), &files2);
+ parseGitTreeObject(objcache, d.id, QString(), &files2);
retrieveCompleteTree(path, &files2);
}
}
}
-bool GitDiff::diff(QString const &id, QList<Git::Diff> *out)
+/**
+ * @brief コミットの差分を取得する
+ * @param id コミットID
+ * @param out
+ * @return
+ */
+bool GitDiff::diff(QString const &id, const QList<Git::SubmoduleItem> &submodules, QList<Git::Diff> *out)
{
out->clear();
diffs.clear();
try {
if (Git::isValidID(id)) { // 有効なID
{ // diff_raw
GitTreeItemList files;
GitCommit newer_commit;
- newer_commit.parseCommit(objcache, id);
- parse_tree_(objcache, newer_commit.tree_id, QString(), &files);
+ GitCommit::parseCommit(objcache, id, &newer_commit);
+ parseGitTreeObject(objcache, newer_commit.tree_id, QString(), &files);
if (newer_commit.parents.isEmpty()) { // 親がないなら最古のコミット
retrieveCompleteTree(QString(), &files); // ツリー全体を取得
} else {
std::map<QString, Git::Diff> diffmap;
std::set<QString> deleted_set;
std::set<QString> renamed_set;
QList<Git::DiffRaw> list;
for (QString const &parent : newer_commit.parents) {
QList<Git::DiffRaw> l = git()->diff_raw(parent, id);
for (Git::DiffRaw const &item : l) {
if (item.state.startsWith('D')) {
deleted_set.insert(item.a.id);
}
list.push_back(item);
}
}
for (Git::DiffRaw const &item : list) {
if (item.state.startsWith('A') || item.state.startsWith('C')) { // 追加されたファイル
auto it = deleted_set.find(item.b.id); // 同じオブジェクトIDが削除リストに載っているなら
if (it != deleted_set.end()) {
renamed_set.insert(item.b.id); // 名前変更とみなす
}
} else if (item.state.startsWith('R')) { // 名前変更されたファイル
renamed_set.insert(item.b.id);
}
}
for (Git::DiffRaw const &item : list) {
QString file;
if (!item.files.isEmpty()) {
file = item.files.back(); // 名前変更された場合はリストの最後が新しい名前
}
Git::Diff diff;
diff.diff = QString("diff --git a/%1 b/%2").arg(file).arg(file);
diff.index = QString("index %1..%2 %3").arg(item.a.id).arg(item.b.id).arg(item.b.mode);
diff.path = file;
diff.mode = item.b.mode;
if (Git::isValidID(item.a.id)) diff.blob.a_id = item.a.id;
if (Git::isValidID(item.b.id)) diff.blob.b_id = item.b.id;
#if 0
if (!diff.blob.a_id.isEmpty()) {
if (!diff.blob.b_id.isEmpty()) {
if (renamed_set.find(diff.blob.b_id) != renamed_set.end()) {
diff.type = Git::Diff::Type::Rename;
} else {
diff.type = Git::Diff::Type::Modify;
}
} else {
if (renamed_set.find(diff.blob.a_id) != renamed_set.end()) { // 名前変更されたオブジェクトなら
diff.type = Git::Diff::Type::Unknown; // マップに追加しない
} else {
diff.type = Git::Diff::Type::Delete; // 削除されたオブジェクト
}
}
} else if (!diff.blob.b_id.isEmpty()) {
if (renamed_set.find(diff.blob.b_id) != renamed_set.end()) {
diff.type = Git::Diff::Type::Rename;
} else {
diff.type = Git::Diff::Type::Create;
}
}
#else
diff.type = Git::Diff::Type::Unknown;
int state = item.state.utf16()[0];
switch (state) {
case 'A': diff.type = Git::Diff::Type::Create; break;
case 'C': diff.type = Git::Diff::Type::Copy; break;
case 'D': diff.type = Git::Diff::Type::Delete; break;
case 'M': diff.type = Git::Diff::Type::Modify; break;
case 'R': diff.type = Git::Diff::Type::Rename; break;
case 'T': diff.type = Git::Diff::Type::ChType; break;
case 'U': diff.type = Git::Diff::Type::Unmerged; break;
}
#endif
if (diff.type != Git::Diff::Type::Unknown) {
if (diffmap.find(diff.path) == diffmap.end()) {
diffmap[diff.path] = diff;
}
}
}
for (auto const &pair : diffmap) {
diffs.push_back(pair.second);
-
}
}
}
} else { // 無効なIDなら、HEADと作業コピーのdiff
GitPtr g = objcache->git();
QString head_id = objcache->revParse("HEAD");
Git::FileStatusList stats = g->status_s(); // git status
GitCommitTree head_tree(objcache);
head_tree.parseCommit(head_id); // HEADが親
QString zeros(GIT_ID_LENGTH, '0');
for (Git::FileStatus const &fs : stats) {
QString path = fs.path1();
Git::Diff item;
GitTreeItem treeitem;
if (head_tree.lookup(path, &treeitem)) {
item.blob.a_id = treeitem.id; // HEADにおけるこのファイルのID
if (fs.isDeleted()) { // 削除されてる
item.blob.b_id = zeros; // 削除された
} else {
item.blob.b_id = prependPathPrefix(path); // IDの代わりに実在するファイルパスを入れる
}
item.mode = treeitem.mode;
} else {
item.blob.a_id = zeros;
item.blob.b_id = prependPathPrefix(path); // 実在するファイルパス
}
item.diff = QString("diff --git a/%1 b/%2").arg(path).arg(path);
item.index = QString("index %1..%2 %3").arg(item.blob.a_id).arg(zeros).arg(item.mode);
item.path = path;
diffs.push_back(item);
}
+ }
+
+ for (int i = 0; i < diffs.size(); i++) {
+ Git::Diff *diff = &diffs[i];
+ if (diff->isSubmodule()) {
+ for (int j = 0; j < submodules.size(); j++) {
+ if (submodules[j].path == diff->path) {
+ GitPtr g = git(submodules[j]);
+ auto Do = [&](QString const &id, Git::Diff::SubmoduleDetail *out){
+ Git::SubmoduleItem const &mods = submodules[j];
+ if (Git::isValidID(id)) {
+ out->item = mods;
+ out->item.id = id;
+ g->queryCommit(out->item.id, &out->commit);
+ } else {
+ *out = {};
+ }
+ };
+ Do(diff->blob.a_id, &diff->a_submodule);
+ Do(diff->blob.b_id, &diff->b_submodule);
+ // なぜか逆に来ることがあるみたい?
+ if (diff->a_submodule.commit.commit_date > diff->b_submodule.commit.commit_date) {
+ std::swap(diff->a_submodule, diff->b_submodule);
+ }
+ break;
+ }
+ }
+ }
}
std::sort(diffs.begin(), diffs.end(), [](Git::Diff const &left, Git::Diff const &right){
return left.path.compare(right.path, Qt::CaseInsensitive) < 0;
});
*out = std::move(diffs);
return true;
} catch (Interrupted &) {
out->clear();
}
return false;
}
-bool GitDiff::diff_uncommited(QList<Git::Diff> *out)
+bool GitDiff::diff_uncommited(const QList<Git::SubmoduleItem> &submodules, QList<Git::Diff> *out)
{
- return diff(QString(), out);
+ return diff({}, submodules, out);
}
// GitCommitTree
GitCommitTree::GitCommitTree(GitObjectCache *objcache)
: objcache(objcache)
{
}
GitPtr GitCommitTree::git()
{
return objcache->git();
}
QString GitCommitTree::lookup_(QString const &file, GitTreeItem *out)
{
int i = file.lastIndexOf('/');
if (i >= 0) {
QString subdir = file.mid(0, i);
QString name = file.mid(i + 1);
QString tree_id;
{
auto it = tree_id_map.find(subdir);
if (it != tree_id_map.end()) {
tree_id = it->second;
} else {
tree_id = lookup_(subdir, out);
}
}
GitTreeItemList list;
- if (parse_tree_(objcache, tree_id, QString(), &list)) {
+ if (parseGitTreeObject(objcache, tree_id, QString(), &list)) {
QString return_id;
for (GitTreeItem const &d : list) {
if (d.name == name) {
return_id = d.id;
}
QString path = misc::joinWithSlash(subdir, d.name);
if (d.type == GitTreeItem::BLOB) {
if (out && d.name == name) {
*out = d;
}
blob_map[path] = d;
} else if (d.type == GitTreeItem::TREE) {
tree_id_map[path] = d.id;
}
}
return return_id;
}
} else {
QString return_id;
for (GitTreeItem const &d : root_item_list) {
if (d.name == file) {
return_id = d.id;
}
if (d.type == GitTreeItem::BLOB) {
if (out && d.name == file) {
*out = d;
}
blob_map[d.name] = d;
} else if (d.type == GitTreeItem::TREE) {
tree_id_map[d.name] = d.id;
}
}
return return_id;
}
return QString();
}
QString GitCommitTree::lookup(QString const &file)
{
auto it = blob_map.find(file);
if (it != blob_map.end()) {
return it->second.id;
}
return lookup_(file, nullptr);
}
bool GitCommitTree::lookup(QString const &file, GitTreeItem *out)
{
*out = GitTreeItem();
auto it = blob_map.find(file);
if (it != blob_map.end()) {
*out = it->second;
return true;
}
return !lookup_(file, out).isEmpty();
}
void GitCommitTree::parseTree(QString const &tree_id)
{
- parse_tree_(objcache, tree_id, QString(), &root_item_list);
+ parseGitTreeObject(objcache, tree_id, QString(), &root_item_list);
}
QString GitCommitTree::parseCommit(QString const &commit_id)
{
GitCommit commit;
- commit.parseCommit(objcache, commit_id);
+ GitCommit::parseCommit(objcache, commit_id, &commit);
parseTree(commit.tree_id);
return commit.tree_id;
}
//
+/**
+ * @brief 指定されたコミットに属するファイルのIDを求める
+ * @param objcache
+ * @param commit_id
+ * @param file
+ * @return
+ */
QString lookupFileID(GitObjectCache *objcache, QString const &commit_id, QString const &file)
-// 指定されたコミットに属するファイルのIDを求める
{
GitCommitTree commit_tree(objcache);
commit_tree.parseCommit(commit_id);
QString id = commit_tree.lookup(file);
return id;
}
diff --git a/src/GitDiff.h b/src/GitDiff.h
index 7f2a2c6..557515d 100644
--- a/src/GitDiff.h
+++ b/src/GitDiff.h
@@ -1,63 +1,64 @@
#ifndef GITDIFF_H
#define GITDIFF_H
#include <set>
#include "common/misc.h"
#include "Git.h"
#include "GitObjectManager.h"
class GitDiff {
friend class CommitListThread;
public:
private:
class LookupTable;
private:
GitObjectCache *objcache = nullptr;
QList<Git::Diff> diffs;
bool interrupted = false;
struct Interrupted {
};
void checkInterrupted()
{
if (interrupted) {
throw Interrupted();
}
}
using MapList = std::list<LookupTable>;
GitPtr git();
+ GitPtr git(const Git::SubmoduleItem &submod);
static void AddItem(Git::Diff *item, QList<Git::Diff> *diffs);
void retrieveCompleteTree(QString const &dir, GitTreeItemList const *files, std::map<QString, GitTreeItem> *out);
void retrieveCompleteTree(QString const &dir, GitTreeItemList const *files);
public:
GitDiff(GitObjectCache *objcache)
{
this->objcache = objcache;
}
- bool diff(QString const &id, QList<Git::Diff> *out);
- bool diff_uncommited(QList<Git::Diff> *out);
+ bool diff(QString const &id, const QList<Git::SubmoduleItem> &submodules, QList<Git::Diff> *out);
+ bool diff_uncommited(const QList<Git::SubmoduleItem> &submodules, QList<Git::Diff> *out);
void interrupt()
{
interrupted = true;
}
public:
static QString diffObjects(const GitPtr &g, QString const &a_id, QString const &b_id);
static QString diffFiles(const GitPtr &g, QString const &a_path, QString const &b_path);
static void parseDiff(std::string const &s, const Git::Diff *info, Git::Diff *out);
static QString makeKey(QString const &a_id, QString const &b_id);
static QString makeKey(const Git::Diff &diff);
static QString prependPathPrefix(QString const &path);
};
#endif // GITDIFF_H
diff --git a/src/GitHubAPI.cpp b/src/GitHubAPI.cpp
index 8561746..1771988 100644
--- a/src/GitHubAPI.cpp
+++ b/src/GitHubAPI.cpp
@@ -1,109 +1,109 @@
#include "GitHubAPI.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "MemoryReader.h"
#include "charvec.h"
#include "common/misc.h"
#include "urlencode.h"
#include "webclient.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <memory>
using WebClientPtr = GitHubAPI::WebClientPtr;
struct GitHubRequestThread::Private {
- BasicMainWindow *mainwindow = nullptr;
+ MainWindow *mainwindow = nullptr;
WebClientPtr web;
};
GitHubRequestThread::GitHubRequestThread()
: m(new Private)
{
}
GitHubRequestThread::~GitHubRequestThread()
{
delete m;
}
-void GitHubRequestThread::start(BasicMainWindow *mainwindow)
+void GitHubRequestThread::start(MainWindow *mainwindow)
{
m->mainwindow = mainwindow;
m->web = std::make_shared<WebClient>(m->mainwindow->webContext());
QThread::start();
}
void GitHubRequestThread::run()
{
ok = false;
if (web()->get(WebClient::Request(url)) == 200) {
WebClient::Response const &r = web()->response();
if (!r.content.empty()) {
text = to_stdstr(r.content);
ok = true;
if (callback) {
ok = callback(text);
}
}
} else {
std::string msg = web()->error().message();
if (!msg.empty()) {
m->mainwindow->emitWriteLog(QString::fromStdString("Failed to access the site: " + url + '\n').toUtf8());
QString s = QString::fromStdString(msg + '\n');
m->mainwindow->emitWriteLog(s.toUtf8());
}
}
}
GitHubAPI::WebClientPtr GitHubRequestThread::web()
{
return m->web;
}
QList<GitHubAPI::SearchResultItem> GitHubAPI::searchRepository(std::string const &q)
{
QList<GitHubAPI::SearchResultItem> items;
GitHubRequestThread th;
{
OverrideWaitCursor;
th.url = "https://api.github.com/search/repositories?q=" + q;
th.start(mainwindow_);
while (!th.wait(1)) {
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
}
if (th.ok) {
QByteArray ba(th.text.c_str(), th.text.size());
QJsonDocument doc = QJsonDocument::fromJson(ba);
QJsonArray a1 = doc.object().value("items").toArray();
for (QJsonValueRef const &v1 : a1) {
QJsonObject o1 = v1.toObject();
SearchResultItem item;
auto String = [&](QString const &key){
return o1.value(key).toString().toStdString();
};
item.full_name = String("full_name");
if (!item.full_name.empty()) {
item.description = String("description");
item.html_url = String("html_url");
item.ssh_url = String("ssh_url");
item.clone_url = String("clone_url");
item.score = o1.value("score").toDouble();
items.push_back(item);
}
}
}
std::sort(items.begin(), items.end(), [](SearchResultItem const &l, SearchResultItem const &r){
return l.score > r.score; // 降順
});
return items;
}
diff --git a/src/GitHubAPI.h b/src/GitHubAPI.h
index 42583d6..213fc38 100644
--- a/src/GitHubAPI.h
+++ b/src/GitHubAPI.h
@@ -1,63 +1,63 @@
#ifndef GITHUBAPI_H
#define GITHUBAPI_H
#include <QImage>
#include <QThread>
#include <string>
#include <functional>
#include <memory>
-class BasicMainWindow;
+class MainWindow;
class WebContext;
class WebClient;
class GitHubAPI {
public:
using WebClientPtr = std::shared_ptr<WebClient>;
struct User {
std::string login;
std::string avatar_url;
std::string name;
std::string email;
};
struct SearchResultItem {
std::string full_name;
std::string description;
std::string ssh_url;
std::string clone_url;
std::string html_url;
double score = 0;
};
- BasicMainWindow *mainwindow_;
+ MainWindow *mainwindow_;
- GitHubAPI(BasicMainWindow *mainwindow)
+ GitHubAPI(MainWindow *mainwindow)
: mainwindow_(mainwindow)
{
}
QList<GitHubAPI::SearchResultItem> searchRepository(std::string const &q);
};
class GitHubRequestThread : public QThread {
private:
struct Private;
Private *m;
protected:
void run() override;
public:
GitHubAPI::WebClientPtr web();
public:
GitHubRequestThread();
~GitHubRequestThread() override;
std::string url;
bool ok = false;
std::string text;
std::function<bool(std::string const &text)> callback;
- void start(BasicMainWindow *mainwindow);
+ void start(MainWindow *mainwindow);
};
#endif // GITHUBAPI_H
diff --git a/src/GitObjectManager.cpp b/src/GitObjectManager.cpp
index 45c6d09..8cf801a 100644
--- a/src/GitObjectManager.cpp
+++ b/src/GitObjectManager.cpp
@@ -1,393 +1,438 @@
#include "GitObjectManager.h"
#include "Git.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include <QBuffer>
#include <QDebug>
#include <QDirIterator>
#include <QFile>
#include <memory>
GitObjectManager::GitObjectManager()
{
subdir_git_objects = ".git/objects";
subdir_git_objects_pack = subdir_git_objects / "pack";
}
void GitObjectManager::setup(GitPtr const &g)
{
this->g = g;
clearIndexes();
}
QString GitObjectManager::workingDir()
{
- return g->workingRepositoryDir();
+ return g->workingDir();
}
void GitObjectManager::loadIndexes()
{
QMutexLocker lock(&mutex);
if (git_idx_list.empty()) {
QString path = workingDir() / subdir_git_objects_pack;
QDirIterator it(path, { "pack-*.idx" }, QDir::Files | QDir::Readable);
while (it.hasNext()) {
it.next();
GitPackIdxPtr idx = std::make_shared<GitPackIdxV2>();
idx->basename = it.fileInfo().baseName();
idx->parse(it.filePath());
git_idx_list.push_back(idx);
}
}
}
void GitObjectManager::clearIndexes()
{
git_idx_list.clear();
}
void GitObjectManager::applyDelta(QByteArray const *base_obj, QByteArray const *delta_obj, QByteArray *out)
{
if (delta_obj->size() > 0) {
uint8_t const *begin = (uint8_t const *)delta_obj->data();
uint8_t const *end = begin + delta_obj->size();
uint8_t const *ptr = begin;
auto ReadNumber = [&](){
uint64_t n = 0;
int shift = 0;
while (ptr < end) {
uint64_t c = *ptr;
ptr++;
n |= (c & 0x7f) << shift;
shift += 7;
if (!(c & 0x80)) break;
}
return n;
};
uint64_t a = ReadNumber(); // older file size
uint64_t b = ReadNumber(); // newer file size
(void)a;
(void)b;
// cf. https://github.com/github/git-msysgit/blob/master/patch-delta.c
while (ptr < end) {
uint8_t op = *ptr;
ptr++;
if (op & 0x80) { // copy operation
uint32_t offset = 0;
uint32_t length = 0;
if (op & 0x01) offset = *ptr++;
if (op & 0x02) offset |= (*ptr++ << 8);
if (op & 0x04) offset |= (*ptr++ << 16);
if (op & 0x08) offset |= ((unsigned) *ptr++ << 24);
if (op & 0x10) length = *ptr++;
if (op & 0x20) length |= (*ptr++ << 8);
if (op & 0x40) length |= (*ptr++ << 16);
if (length == 0) length = 0x10000;
if (offset + length > (uint32_t)base_obj->size()) {
qDebug() << Q_FUNC_INFO << "base-file or delta-file is corrupted";
out->clear();
return;
}
out->append(base_obj->data() + offset, length);
} else if (op > 0) { // insert operation
int length = op & 0x7f;
if (ptr + length <= end) {
out->append((char const *)ptr, length);
ptr += length;
}
} else {
qDebug() << Q_FUNC_INFO << "unexpected delta opcode 0";
}
}
}
}
bool GitObjectManager::loadPackedObject(GitPackIdxPtr const &idx, QIODevice *packfile, GitPackIdxItem const *item, GitPack::Object *out)
{
GitPack::Info info;
if (GitPack::seekPackedObject(packfile, item, &info)) {
GitPackIdxItem const *source_item = nullptr;
if (info.type == Git::Object::Type::OFS_DELTA) {
source_item = idx->item(item->offset - info.offset);
} else if (info.type == Git::Object::Type::REF_DELTA) {
source_item = idx->item(info.ref_id);
}
if (source_item) { // if deltified object
GitPack::Object source;
if (source_item && loadPackedObject(idx, packfile, source_item, &source)) {
GitPack::Object delta;
if (GitPack::load(packfile, item, &delta)) {
if (delta.checksum != item->checksum) {
qDebug() << "crc checksum incorrect";
return false;
}
QByteArray ba;
applyDelta(&source.content, &delta.content, &ba);
*out = GitPack::Object();
out->type = source.type;
out->content = std::move(ba);
out->expanded_size = out->content.size();
return true;
}
}
qDebug() << Q_FUNC_INFO << "failed";
return false;
}
}
return GitPack::load(packfile, item, out);
}
bool GitObjectManager::extractObjectFromPackFile(GitPackIdxPtr const &idx, GitPackIdxItem const *item, GitPack::Object *out)
{
*out = GitPack::Object();
QString packfilepath = workingDir() / subdir_git_objects_pack / (idx->basename + ".pack");
QFile packfile(packfilepath);
if (packfile.open(QFile::ReadOnly)) {
if (loadPackedObject(idx, &packfile, item, out)) {
if (out->type == Git::Object::Type::TREE) {
GitPack::decodeTree(&out->content);
}
return true;
}
}
return false;
}
bool GitObjectManager::extractObjectFromPackFile(QString const &id, QByteArray *out, Git::Object::Type *type)
{
loadIndexes();
for (GitPackIdxPtr const &idx : git_idx_list) {
GitPackIdxItem const *item = idx->item(id);
if (item) {
GitPack::Object obj;
if (extractObjectFromPackFile(idx, item, &obj)) {
*out = std::move(obj.content);
*type = obj.type;
return true;
}
qDebug() << Q_FUNC_INFO << "failed";
return false;
}
}
return false;
}
QString GitObjectManager::findObjectPath(QString const &id)
{
if (Git::isValidID(id)) {
int count = 0;
QString absolute_path;
QString xx = id.mid(0, 2); // leading two xdigits
QString name = id.mid(2); // remaining xdigits
QString dir = workingDir() / subdir_git_objects / xx; // e.g. /home/user/myproject/.git/objects/5a
QDirIterator it(dir, QDir::Files);
while (it.hasNext()) {
it.next();
if (it.fileName().startsWith(name)) {
QString id = xx + it.fileName(); // complete id
if (id.size() == GIT_ID_LENGTH && Git::isValidID(id)) {
absolute_path = dir / it.fileName();
count++;
}
}
}
if (count == 1) return absolute_path;
if (count > 1) qDebug() << Q_FUNC_INFO << "ambiguous id" << id;
}
return QString(); // not found
}
bool GitObjectManager::loadObject(QString const &id, QByteArray *out, Git::Object::Type *type)
{
QString path = findObjectPath(id);
if (!path.isEmpty()) {
QFile file(path);
if (file.open(QFile::ReadOnly)) {
if (GitPack::decompress(&file, 1000000000, out)) {
*type = GitPack::stripHeader(out);
if (*type == Git::Object::Type::TREE) {
GitPack::decodeTree(out);
}
return true;
}
}
}
return false;
}
bool GitObjectManager::catFile(QString const &id, QByteArray *out, Git::Object::Type *type)
{
*type = Git::Object::Type::UNKNOWN;
if (loadObject(id, out, type)) return true;
if (extractObjectFromPackFile(id, out, type)) return true;
return false;
}
//
size_t GitObjectCache::size() const
{
size_t size = 0;
for (ItemPtr const &item : items) {
size += item->ba.size();
}
return size;
}
void GitObjectCache::setup(GitPtr const &g)
{
items.clear();
revparsemap.clear();
if (g) {
object_manager.setup(g->dup());
}
}
QString GitObjectCache::revParse(QString const &name)
{
GitPtr g = git();
if (!g) return QString();
{
QMutexLocker lock(&object_manager.mutex);
auto it = revparsemap.find(name);
if (it != revparsemap.end()) {
return it->second;
}
}
QString id = g->rev_parse(name);
{
QMutexLocker lock(&object_manager.mutex);
revparsemap[name] = id;
return id;
}
}
Git::Object GitObjectCache::catFile(QString const &id)
{
{
QMutexLocker lock(&object_manager.mutex);
size_t n = items.size();
size_t i = n;
while (i > 0) {
i--;
if (items[i]->id == id) {
ItemPtr item = items[i];
if (i + 1 < n) {
items.erase(items.begin() + i);
items.push_back(item);
}
Git::Object obj;
obj.type = item->type;
obj.content = item->ba;
return obj;
}
}
while (size() > 100000000) { // 100MB
items.erase(items.begin());
}
}
QByteArray ba;
Git::Object::Type type = Git::Object::Type::UNKNOWN;
auto Store = [&](){
QMutexLocker lock(&object_manager.mutex);
Item *item = new Item();
item->id = id;
item->ba = std::move(ba);
item->type = type;
items.push_back(ItemPtr(item));
Git::Object obj;
obj.type = item->type;
obj.content = item->ba;
return obj;
};
if (object_manager.catFile(id, &ba, &type)) { // 独自実装のファイル取得
return Store();
}
if (true) {
if (git()->cat_file(id, &ba)) { // 外部コマンド起動の git cat-file -p を試してみる
// 上の独自実装のファイル取得が正しく動作していれば、ここには来ないはず
- qDebug() << __LINE__ << __FILE__ << Q_FUNC_INFO << id;
+ qDebug() << __FILE__ << __LINE__ << Q_FUNC_INFO << id;
return Store();
}
}
qDebug() << "failed to cat file: " << id;
return Git::Object();
}
QString GitObjectCache::getCommitIdFromTag(QString const &tag)
{
QString commit_id;
GitPtr g = git();
if (g && g->isValidWorkingCopy()) {
QString id = g->rev_parse(tag);
Git::Object obj = catFile(id);
switch (obj.type) {
case Git::Object::Type::COMMIT:
commit_id = id;
break;
case Git::Object::Type::TAG:
if (!obj.content.isEmpty()) {
misc::splitLines(obj.content, [&](char const *ptr, size_t len){
if (commit_id.isEmpty()) {
if (len >= 7 + GIT_ID_LENGTH && strncmp(ptr, "object ", 7) == 0) {
QString id = QString::fromUtf8(ptr + 7, len - 7).trimmed();
if (Git::isValidID(id)) {
commit_id = id;
}
}
}
return QString();
});
}
break;
}
}
return commit_id;
}
-bool GitCommit::parseCommit(GitObjectCache *objcache, QString const &id)
+bool GitCommit::parseCommit(GitObjectCache *objcache, QString const &id, GitCommit *out)
{
- parents.clear();
+ *out = {};
if (!id.isEmpty()) {
QStringList parents;
{
Git::Object obj = objcache->catFile(id);
if (!obj.content.isEmpty()) {
QStringList lines = misc::splitLines(QString::fromUtf8(obj.content));
for (QString const &line : lines) {
int i = line.indexOf(' ');
if (i < 1) break;
QString key = line.mid(0, i);
QString val = line.mid(i + 1).trimmed();
if (key == "tree") {
- tree_id = val;
+ out->tree_id = val;
} else if (key == "parent") {
parents.push_back(val);
}
}
}
}
- if (!tree_id.isEmpty()) { // サブディレクトリ
- this->parents.append(parents);
+ if (!out->tree_id.isEmpty()) { // サブディレクトリ
+ out->parents.append(parents);
return true;
}
}
return false;
}
+void parseGitTreeObject(QByteArray const &ba, const QString &path_prefix, GitTreeItemList *out)
+{
+ *out = {};
+ QString s = QString::fromUtf8(ba);
+ QStringList lines = misc::splitLines(s);
+ for (QString const &line : lines) {
+ int tab = line.indexOf('\t'); // タブより後ろにパスがある
+ if (tab > 0) {
+ QString stat = line.mid(0, tab); // タブの手前まで
+ QStringList vals = misc::splitWords(stat); // 空白で分割
+ if (vals.size() >= 3) {
+ GitTreeItem data;
+ data.mode = vals[0]; // ファイルモード
+ data.id = vals[2]; // id(ハッシュ値)
+ QString type = vals[1]; // 種類(tree/blob)
+ QString path = line.mid(tab + 1); // パス
+ path = Git::trimPath(path);
+ data.name = path_prefix.isEmpty() ? path : misc::joinWithSlash(path_prefix, path);
+ if (type == "tree") {
+ data.type = GitTreeItem::TREE;
+ } else if (type == "blob") {
+ data.type = GitTreeItem::BLOB;
+ } else if (type == "commit") {
+ data.type = GitTreeItem::COMMIT;
+ }
+ if (data.type != GitTreeItem::UNKNOWN) {
+ out->push_back(data);
+ }
+ }
+ }
+ }
+}
+
+bool parseGitTreeObject(GitObjectCache *objcache, const QString &commit_id, const QString &path_prefix, GitTreeItemList *out)
+{
+ out->clear();
+ if (!commit_id.isEmpty()) {
+ Git::Object obj = objcache->catFile(commit_id);
+ if (!obj.content.isEmpty()) { // 内容を取得
+ parseGitTreeObject(obj.content, path_prefix, out);
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/GitObjectManager.h b/src/GitObjectManager.h
index 99b23fc..26a9250 100644
--- a/src/GitObjectManager.h
+++ b/src/GitObjectManager.h
@@ -1,129 +1,145 @@
#ifndef GITOBJECTMANAGER_H
#define GITOBJECTMANAGER_H
#include <QMutex>
#include <QString>
#include "GitPack.h"
#include "GitPackIdxV2.h"
#include <map>
+#include "common/joinpath.h"
class GitPackIdxV2;
class Git;
using GitPtr = std::shared_ptr<Git>;
class GitObjectManager {
friend class GitObjectCache;
private:
GitPtr g;
QMutex mutex;
QString subdir_git_objects;
QString subdir_git_objects_pack;
std::vector<GitPackIdxPtr> git_idx_list;
QString workingDir();
static void applyDelta(QByteArray const *base, QByteArray const *delta, QByteArray *out);
static bool loadPackedObject(GitPackIdxPtr const &idx, QIODevice *packfile, GitPackIdxItem const *item, GitPack::Object *out);
bool extractObjectFromPackFile(GitPackIdxPtr const &idx, GitPackIdxItem const *item, GitPack::Object *out);
bool extractObjectFromPackFile(QString const &id, QByteArray *out, Git::Object::Type *type);
void loadIndexes();
QString findObjectPath(QString const &id);
bool loadObject(QString const &id, QByteArray *out, Git::Object::Type *type);
GitPtr git()
{
- return g;
+ return g->dup();
+ }
+ GitPtr git(Git::SubmoduleItem const &submod)
+ {
+ GitPtr g2 = g->dup();
+ g2->setWorkingRepositoryDir(g->workingDir(), submod.path, g->sshKey());
+ return g2;
}
public:
GitObjectManager();
void setup(const GitPtr &g);
bool catFile(QString const &id, QByteArray *out, Git::Object::Type *type);
void clearIndexes();
};
class GitObjectCache {
public:
struct Item {
QString id;
QByteArray ba;
Git::Object::Type type;
};
private:
GitObjectManager object_manager;
using ItemPtr = std::shared_ptr<Item>;
std::vector<ItemPtr> items;
std::map<QString, QString> revparsemap;
size_t size() const;
public:
GitPtr git()
{
return object_manager.git();
}
+ GitPtr git(Git::SubmoduleItem const &submod)
+ {
+ return object_manager.git(submod);
+ }
void setup(const GitPtr &g);
QString revParse(QString const &name);
Git::Object catFile(QString const &id);
QString getCommitIdFromTag(QString const &tag);
};
class GitCommit {
public:
QString tree_id;
QStringList parents;
- bool parseCommit(GitObjectCache *objcache, QString const &id);
+ static bool parseCommit(GitObjectCache *objcache, QString const &id, GitCommit *out);
};
struct GitTreeItem {
enum Type {
UNKNOWN,
TREE,
BLOB,
+ COMMIT,
};
Type type = UNKNOWN;
QString name;
QString id;
QString mode;
QString to_string_() const
{
QString t;
switch (type) {
case TREE: t = "TREE"; break;
case BLOB: t = "BLOB"; break;
}
return QString("GitTreeItem:{ %1 %2 %3 %4 }").arg(t).arg(id).arg(mode).arg(name);
}
};
using GitTreeItemList = QList<GitTreeItem>;
class GitCommitTree {
private:
GitObjectCache *objcache;
GitTreeItemList root_item_list;
std::map<QString, GitTreeItem> blob_map;
std::map<QString, QString> tree_id_map;
GitPtr git();
QString lookup_(QString const &file, GitTreeItem *out);
public:
GitCommitTree(GitObjectCache *objcache);
QString lookup(QString const &file);
bool lookup(QString const &file, GitTreeItem *out);
void parseTree(QString const &tree_id);
QString parseCommit(QString const &commit_id);
GitTreeItemList const *treelist() const
{
return &root_item_list;
}
};
QString lookupFileID(GitObjectCache *objcache, QString const &commit_id, QString const &file);
+void parseGitTreeObject(QByteArray const &ba, const QString &path_prefix, GitTreeItemList *out);
+bool parseGitTreeObject(GitObjectCache *objcache, QString const &commit_id, QString const &path_prefix, GitTreeItemList *out);
+
+
#endif // GITOBJECTMANAGER_H
diff --git a/src/GitPack.cpp b/src/GitPack.cpp
index cbe0fe5..d01aa3e 100644
--- a/src/GitPack.cpp
+++ b/src/GitPack.cpp
@@ -1,249 +1,248 @@
#include "GitPack.h"
#include "GitPackIdxV2.h"
#include <QDebug>
#include <QFile>
#include <zlib.h>
void GitPack::decodeTree(QByteArray *out)
{
if (out && out->size() > 0) {
QByteArray ba;
uint8_t const *begin = (uint8_t const *)out->data();
uint8_t const *end = begin + out->size();
uint8_t const *ptr = begin;
while (ptr < end) {
int mode = 0;
while (ptr < end) {
int c = *ptr & 0xff;
ptr++;
if (isdigit(c & 0xff)) {
mode = mode * 10 + (c - '0');
} else if (c == ' ') {
break;
}
}
uint8_t const *left = ptr;
while (ptr < end && *ptr) {
ptr++;
}
std::string name(left, ptr);
if (ptr + 20 < end) {
ptr++;
char tmp[100];
sprintf(tmp, "%06u %s ", mode, mode < 100000 ? "tree" : "blob");
char *p = tmp + 12;
for (int i = 0; i < 20; i++) {
sprintf(p, "%02x", ptr[i]);
p += 2;
}
ba.append(tmp, p - tmp);
ba.append('\t');
ba.append(name.c_str(), name.size());
ba.append('\n');
ptr += 20;
} else {
break;
}
}
*out = std::move(ba);
}
}
Git::Object::Type GitPack::stripHeader(QByteArray *out)
{
if (out) {
int n = out->size();
if (n > 0) {
char const *p = out->data();
if (n > 16) n = 16;
for (int i = 0; i < n; i++) {
if (p[i] == 0) {
Git::Object::Type type = Git::Object::Type::UNKNOWN;
if (strncmp(p, "blob ", 5) == 0) {
type = Git::Object::Type::BLOB;
} else if (strncmp(p, "tree ", 5) == 0) {
type = Git::Object::Type::TREE;
} else if (strncmp(p, "commit ", 7) == 0) {
type = Git::Object::Type::COMMIT;
} else if (strncmp(p, "tag ", 4) == 0) {
type = Git::Object::Type::TAG;
}
if (type != Git::Object::Type::UNKNOWN) {
*out = out->mid(i + 1);
}
return type;
}
}
}
}
return Git::Object::Type::UNKNOWN;
}
bool GitPack::decompress(QIODevice *in, size_t expanded_size, QByteArray *out, size_t *consumed, uint32_t *crc)
{
- qDebug() << Q_FUNC_INFO << expanded_size;
if (consumed) *consumed = 0;
try {
int err;
z_stream d_stream;
d_stream.zalloc = nullptr;
d_stream.zfree = nullptr;
d_stream.opaque = nullptr;
d_stream.avail_in = 0;
err = inflateInit(&d_stream);
if (err != Z_OK) {
throw QString("failed: inflateInit");
}
int flush = Z_NO_FLUSH;
while (1) {
uint8_t ibuf[65536];
uint8_t obuf[65536];
size_t ilen = d_stream.avail_in;
d_stream.next_in = ibuf;
d_stream.next_out = obuf;
d_stream.avail_out = sizeof(obuf);
err = ::inflate(&d_stream, flush);
if (err == Z_BUF_ERROR) {
if (flush == Z_FINISH) {
throw QString("failed: inflate");
}
d_stream.avail_in = in->read((char *)ibuf, sizeof(ibuf));
if (d_stream.avail_in == 0) {
flush = Z_FINISH;
}
continue;
}
ilen -= d_stream.avail_in;
if (consumed) *consumed += ilen;
if (crc) *crc = crc32(*crc, ibuf, ilen);
if (ilen > 0 && d_stream.avail_in > 0) {
memmove(ibuf, ibuf + ilen, d_stream.avail_in);
}
size_t n = sizeof(obuf) - d_stream.avail_out;
out->append((char const *)obuf, n);
if (err == Z_STREAM_END) {
break;
}
if (err != Z_OK) {
throw QString("failed: inflate");
}
if (expanded_size > 0 && (size_t)out->size() > expanded_size) {
throw QString("file too large");
}
}
err = inflateEnd(&d_stream);
if (err != Z_OK) {
throw QString("failed: inflateEnd");
}
return true;
} catch (QString const &e) {
qDebug() << e;
}
return false;
}
bool GitPack::seekPackedObject(QIODevice *file, GitPackIdxItem const *item, Info *out)
{
try {
Info info;
auto Read = [&](void *ptr, size_t len){
const auto l = file->read((char *)ptr, len);
if (l < 0 || ((size_t)(l)) != len) {
throw QString("failed to read");
}
info.checksum = crc32(info.checksum, (uint8_t const *)ptr, len);
};
file->seek(0);
uint32_t header[3];
Read(header, sizeof(int32_t) * 3);
if (memcmp(header, "PACK", 4) != 0) throw QString("invalid pack file");
uint32_t version = read_uint32_be(header + 1);
if (version < 2) throw QString("invalid pack file version");
/*int count = */read_uint32_be(header + 2);
file->seek(item->offset);
info.checksum = 0;
// cf. https://github.com/github/git-msysgit/blob/master/builtin/unpack-objects.c
{
size_t size = 0;
char c;
Read(&c, 1);
info.type = (Git::Object::Type)((c >> 4) & 7);
size = c & 0x0f;
int shift = 4;
while (c & 0x80) {
Read(&c, 1);
size |= (c & 0x7f) << shift;
shift += 7;
}
info.expanded_size = size;
}
if (info.type == Git::Object::Type::OFS_DELTA) {
uint64_t offset = 0;
char c;
Read(&c, 1);
offset = c & 0x7f;
while (c & 0x80) {
Read(&c, 1);
offset = ((offset + 1) << 7) | (c & 0x7f);
}
info.offset = offset;
} else if (info.type == Git::Object::Type::REF_DELTA) {
char bin[20];
Read(bin, 20);
char tmp[41];
for (int i = 0; i < 20; i++) {
sprintf(tmp + i * 2, "%02x", bin[i] & 0xff);
}
info.ref_id = QString::fromLatin1(tmp, GIT_ID_LENGTH);
}
*out = info;
return true;
} catch (QString const &e) {
qDebug() << e;
}
return false;
}
bool GitPack::load(QIODevice *file, GitPackIdxItem const *item, Object *out)
{
*out = Object();
try {
seekPackedObject(file, item, out);
if (decompress(file, out->expanded_size, &out->content, &out->packed_size, &out->checksum)) {
out->expanded_size = out->expanded_size;
return true;
}
} catch (QString const &e) {
qDebug() << e;
}
return false;
}
bool GitPack::load(QString const &packfile, GitPackIdxItem const *item, GitPack::Object *out)
{
QFile file(packfile);
if (file.open(QFile::ReadOnly)) {
if (load(&file, item, out)) {
return true;
}
}
return false;
}
diff --git a/src/ImageViewWidget.cpp b/src/ImageViewWidget.cpp
index a4c54b2..e11f540 100644
--- a/src/ImageViewWidget.cpp
+++ b/src/ImageViewWidget.cpp
@@ -1,373 +1,373 @@
#include "ImageViewWidget.h"
#include "FileDiffSliderWidget.h"
#include "FileDiffWidget.h"
#include "MainWindow.h"
#include "MemoryReader.h"
#include "Photoshop.h"
#include "charvec.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include <QBuffer>
#include <QDebug>
#include <QFileDialog>
#include <QMenu>
#include <QPainter>
#include <QSvgRenderer>
#include <QWheelEvent>
#include <cmath>
#include <functional>
#include <memory>
using SvgRendererPtr = std::shared_ptr<QSvgRenderer>;
struct ImageViewWidget::Private {
QMainWindow *mainwindow = nullptr;
FileDiffWidget *filediffwidget = nullptr;
- FileDiffWidget::DrawData *draw_data = nullptr;
+// FileDiffWidget::DrawData *draw_data = nullptr;
QScrollBar *v_scroll_bar = nullptr;
QScrollBar *h_scroll_bar = nullptr;
QString mime_type;
QPixmap pixmap;
SvgRendererPtr svg;
double image_scroll_x = 0;
double image_scroll_y = 0;
double image_scale = 1;
double scroll_origin_x = 0;
double scroll_origin_y = 0;
QPoint mouse_press_pos;
int wheel_delta = 0;
QPointF interest_pos;
int top_margin = 1;
int bottom_margin = 1;
bool draw_left_border = true;
#ifndef APP_GUITAR
QPixmap transparent_pixmap;
#endif
};
ImageViewWidget::ImageViewWidget(QWidget *parent)
: QWidget(parent)
, m(new Private)
{
#if defined(Q_OS_WIN32)
setFont(QFont("MS Gothic"));
#elif defined(Q_OS_LINUX)
setFont(QFont("Monospace"));
#elif defined(Q_OS_MAC)
setFont(QFont("Menlo"));
#endif
setContextMenuPolicy(Qt::DefaultContextMenu);
}
ImageViewWidget::~ImageViewWidget()
{
delete m;
}
void ImageViewWidget::bind(QMainWindow *mainwindow, FileDiffWidget *filediffwidget, QScrollBar *vsb, QScrollBar *hsb)
{
m->mainwindow = mainwindow;
m->filediffwidget = filediffwidget;
m->v_scroll_bar = vsb;
m->h_scroll_bar = hsb;
}
bool ImageViewWidget::hasFocus() const
{
QWidget *w = qApp->focusWidget();
return w && w != m->filediffwidget && w->isAncestorOf(this);
}
void ImageViewWidget::setLeftBorderVisible(bool f)
{
m->draw_left_border = f;
}
void ImageViewWidget::internalScrollImage(double x, double y)
{
m->image_scroll_x = x;
m->image_scroll_y = y;
QSizeF sz = imageScrollRange();
if (m->image_scroll_x < 0) m->image_scroll_x = 0;
if (m->image_scroll_y < 0) m->image_scroll_y = 0;
if (m->image_scroll_x > sz.width()) m->image_scroll_x = sz.width();
if (m->image_scroll_y > sz.height()) m->image_scroll_y = sz.height();
update();
}
void ImageViewWidget::scrollImage(double x, double y)
{
internalScrollImage(x, y);
if (m->h_scroll_bar) {
m->h_scroll_bar->blockSignals(true);
m->h_scroll_bar->setValue((int)m->image_scroll_x);
m->h_scroll_bar->blockSignals(false);
}
if (m->v_scroll_bar) {
m->v_scroll_bar->blockSignals(true);
m->v_scroll_bar->setValue((int)m->image_scroll_y);
m->v_scroll_bar->blockSignals(false);
}
}
void ImageViewWidget::refrectScrollBar()
{
double e = 0.75;
double x = m->h_scroll_bar->value();
double y = m->v_scroll_bar->value();
if (fabs(x - m->image_scroll_x) < e) x = m->image_scroll_x; // 差が小さいときは値を維持する
if (fabs(y - m->image_scroll_y) < e) y = m->image_scroll_y;
internalScrollImage(x, y);
}
void ImageViewWidget::clear()
{
m->mime_type = QString();
m->pixmap = QPixmap();
setMouseTracking(false);
update();
}
QString ImageViewWidget::formatText(Document::Line const &line)
{
QByteArray const &ba = line.text;
if (ba.isEmpty()) return QString();
std::vector<char> vec;
vec.reserve(ba.size() + 100);
char const *begin = ba.data();
char const *end = begin + ba.size();
char const *ptr = begin;
int x = 0;
while (ptr < end) {
if (*ptr == '\t') {
do {
vec.push_back(' ');
x++;
} while ((x % 4) != 0);
ptr++;
} else {
vec.push_back(*ptr);
ptr++;
x++;
}
}
return QString::fromUtf8(&vec[0], vec.size());
}
QSizeF ImageViewWidget::imageScrollRange() const
{
QSize sz = imageSize();
int w = int(sz.width() * m->image_scale);
int h = int(sz.height() * m->image_scale);
return QSize(w, h);
}
void ImageViewWidget::setScrollBarRange(QScrollBar *h, QScrollBar *v)
{
h->blockSignals(true);
v->blockSignals(true);
QSizeF sz = imageScrollRange();
h->setRange(0, (int)sz.width());
v->setRange(0, (int)sz.height());
h->setPageStep(width());
v->setPageStep(height());
h->blockSignals(false);
v->blockSignals(false);
}
void ImageViewWidget::updateScrollBarRange()
{
setScrollBarRange(m->h_scroll_bar, m->v_scroll_bar);
}
QMainWindow *ImageViewWidget::mainwindow()
{
return m->mainwindow;
}
QBrush ImageViewWidget::getTransparentBackgroundBrush()
{
#ifdef APP_GUITAR
- return qobject_cast<BasicMainWindow *>(mainwindow())->getTransparentPixmap();
+ return qobject_cast<MainWindow *>(mainwindow())->getTransparentPixmap();
#else
if (m->transparent_pixmap.isNull()) {
m->transparent_pixmap = QPixmap(":/image/transparent.png");
}
return m->transparent_pixmap;
#endif
}
bool ImageViewWidget::isValidImage() const
{
return !m->pixmap.isNull() || (m->svg && m->svg->isValid());
}
QSize ImageViewWidget::imageSize() const
{
if (!m->pixmap.isNull()) return m->pixmap.size();
if (m->svg && m->svg->isValid()) return m->svg->defaultSize();
return QSize();
}
void ImageViewWidget::paintEvent(QPaintEvent *)
{
QPainter pr(this);
QSize imagesize = imageSize();
if (imagesize.width() > 0 && imagesize.height() > 0) {
pr.save();
if (!m->draw_left_border) {
pr.setClipRect(1, 0, width() - 1, height());
}
double cx = width() / 2.0;
double cy = height() / 2.0;
double x = cx - m->image_scroll_x;
double y = cy - m->image_scroll_y;
QSizeF sz = imageScrollRange();
if (sz.width() > 0 && sz.height() > 0) {
QBrush br = getTransparentBackgroundBrush();
pr.setBrushOrigin((int)x, (int)y);
pr.fillRect((int)x, (int)y, (int)sz.width(), (int)sz.height(), br);
if (!m->pixmap.isNull()) {
pr.drawPixmap((int)x, (int)y, (int)sz.width(), (int)sz.height(), m->pixmap, 0, 0, imagesize.width(), imagesize.height());
} else if (m->svg && m->svg->isValid()) {
m->svg->render(&pr, QRectF(x, y, sz.width(), sz.height()));
}
}
misc::drawFrame(&pr, (int)x - 1, (int)y - 1, (int)sz.width() + 2, (int)sz.height() + 2, Qt::black);
pr.restore();
}
if (m->draw_left_border) {
pr.fillRect(0, 0, 1, height(), QColor(160, 160, 160));
}
if (hasFocus()) {
misc::drawFrame(&pr, 0, 0, width(), height(), QColor(0, 128, 255, 128));
misc::drawFrame(&pr, 1, 1, width() - 2, height() - 2, QColor(0, 128, 255, 64));
}
}
void ImageViewWidget::resizeEvent(QResizeEvent *)
{
updateScrollBarRange();
}
void ImageViewWidget::setImage(QString mimetype, QByteArray const &ba)
{
if (mimetype.isEmpty()) {
mimetype = "image/x-unknown";
}
setMouseTracking(true);
m->pixmap = QPixmap();
m->svg = SvgRendererPtr();
if (!ba.isEmpty()) {
if (misc::isSVG(mimetype)) {
m->svg = std::make_shared<QSvgRenderer>(ba);
} else if (misc::isPSD(mimetype)) {
if (!ba.isEmpty()) {
MemoryReader reader(ba.data(), ba.size());
if (reader.open(QIODevice::ReadOnly)) {
std::vector<char> jpeg;
photoshop::readThumbnail(&reader, &jpeg);
if (!jpeg.empty()) {
m->pixmap.loadFromData((uchar const *)&jpeg[0], jpeg.size());
}
}
}
} else {
m->pixmap.loadFromData(ba);
}
}
QSize sz = imageSize();
double sx = sz.width();
double sy = sz.height();
if (sx > 0 && sy > 0) {
sx = width() / sx;
sy = height() / sy;
m->image_scale = (sx < sy ? sx : sy) * 0.9;
}
updateScrollBarRange();
m->h_scroll_bar->blockSignals(true);
m->v_scroll_bar->blockSignals(true);
m->h_scroll_bar->setValue(m->h_scroll_bar->maximum() / 2);
m->v_scroll_bar->setValue(m->v_scroll_bar->maximum() / 2);
m->h_scroll_bar->blockSignals(false);
m->v_scroll_bar->blockSignals(false);
refrectScrollBar();
}
void ImageViewWidget::mousePressEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton) {
QPoint pos = mapFromGlobal(QCursor::pos());
m->mouse_press_pos = pos;
m->scroll_origin_x = m->image_scroll_x;
m->scroll_origin_y = m->image_scroll_y;
}
}
void ImageViewWidget::mouseMoveEvent(QMouseEvent *e)
{
if (isValidImage()) {
QPoint pos = mapFromGlobal(QCursor::pos());
if ((e->buttons() & Qt::LeftButton) && hasFocus()) {
int delta_x = pos.x() - m->mouse_press_pos.x();
int delta_y = pos.y() - m->mouse_press_pos.y();
scrollImage(m->scroll_origin_x - delta_x, m->scroll_origin_y - delta_y);
}
double cx = width() / 2.0;
double cy = height() / 2.0;
double x = (pos.x() + 0.5 - cx + m->image_scroll_x) / m->image_scale;
double y = (pos.y() + 0.5 - cy + m->image_scroll_y) / m->image_scale;
m->interest_pos = QPointF(x, y);
m->wheel_delta = 0;
}
}
void ImageViewWidget::setImageScale(double scale)
{
if (scale < 1 / 32.0) scale = 1 / 32.0;
if (scale > 32) scale = 32;
m->image_scale = scale;
}
void ImageViewWidget::wheelEvent(QWheelEvent *e)
{
if (isValidImage()) {
double scale = 1;
const double mul = 1.189207115; // sqrt(sqrt(2))
- m->wheel_delta += e->delta();
+ m->wheel_delta += e->angleDelta().y();
while (m->wheel_delta >= 120) {
m->wheel_delta -= 120;
scale *= mul;
}
while (m->wheel_delta <= -120) {
m->wheel_delta += 120;
scale /= mul;
}
setImageScale(m->image_scale * scale);
updateScrollBarRange();
double cx = width() / 2.0;
double cy = height() / 2.0;
QPoint pos = mapFromGlobal(QCursor::pos());
double dx = m->interest_pos.x() * m->image_scale + cx - (pos.x() + 0.5);
double dy = m->interest_pos.y() * m->image_scale + cy - (pos.y() + 0.5);
scrollImage(dx, dy);
update();
}
}
diff --git a/src/JumpDialog.h b/src/JumpDialog.h
index 8a9d418..046cb6c 100644
--- a/src/JumpDialog.h
+++ b/src/JumpDialog.h
@@ -1,35 +1,33 @@
#ifndef JUMPDIALOG_H
#define JUMPDIALOG_H
#include "MyTableWidgetDelegate.h"
#include "Git.h"
#include <QDialog>
namespace Ui {
class JumpDialog;
}
class QTableWidgetItem;
class JumpDialog : public QDialog {
Q_OBJECT
-public:
private:
+ Ui::JumpDialog *ui;
struct Private;
Private *m;
+ void updateTable();
+ void internalUpdateTable(const NamedCommitList &list2);
public:
explicit JumpDialog(QWidget *parent, NamedCommitList const &items);
~JumpDialog() override;
QString text() const;
static void sort(NamedCommitList *items);
private slots:
void on_toolButton_clicked();
void on_lineEdit_filter_textChanged(QString const &text);
void on_tableWidget_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous);
-private:
- Ui::JumpDialog *ui;
- void updateTable();
- void internalUpdateTable(const NamedCommitList &list2);
};
#endif // JUMPDIALOG_H
diff --git a/src/LogTableWidget.cpp b/src/LogTableWidget.cpp
index 9b6e7f4..8908922 100644
--- a/src/LogTableWidget.cpp
+++ b/src/LogTableWidget.cpp
@@ -1,373 +1,404 @@
#include "LogTableWidget.h"
#include "MainWindow.h"
#include "MyTableWidgetDelegate.h"
#include "common/misc.h"
#include <QApplication>
#include <QDebug>
#include <QEvent>
#include <QPainter>
+#include <QPainterPath>
#include <QProxyStyle>
#include <cmath>
#include <map>
+#include "ApplicationGlobal.h"
+#include "RepositoryWrapperFrame.h"
struct LogTableWidget::Private {
+ RepositoryWrapperFrame *frame = nullptr;
};
+/**
+ * @brief コミットログを描画するためのdelegate
+ */
class LogTableWidgetDelegate : public MyTableWidgetDelegate {
private:
- MainWindow *mainwindow() const
+ RepositoryWrapperFrame *frame() const
{
auto *w = dynamic_cast<LogTableWidget *>(QStyledItemDelegate::parent());
Q_ASSERT(w);
- return w->mainwindow();
- }
-
- static QColor labelColor(int kind)
- {
- switch (kind) {
- case BasicMainWindow::Label::Head: return QColor(255, 192, 224); // blue
- case BasicMainWindow::Label::LocalBranch: return QColor(192, 224, 255); // blue
- case BasicMainWindow::Label::RemoteBranch: return QColor(192, 240, 224); // green
- case BasicMainWindow::Label::Tag: return QColor(255, 224, 192); // orange
- }
- return QColor(224, 224, 224); // gray
+ return w->frame();
}
static QColor hiliteColor(QColor const &color)
{
int r = color.red();
int g = color.green();
int b = color.blue();
r = 255 - (255 - r) / 2;
g = 255 - (255 - g) / 2;
b = 255 - (255 - b) / 2;
return QColor(r, g, b);
}
static QColor shadowColor(QColor const &color)
{
return QColor(color.red() / 2, color.green() / 2, color.blue() / 2);
}
void drawSignatureIcon(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index) const
{
if (!opt.widget->isEnabled()) return;
- Git::CommitItem const *commit = mainwindow()->commitItem(index.row());
+ Git::CommitItem const *commit = frame()->commitItem(index.row());
if (commit) {
- QIcon icon = mainwindow()->verifiedIcon(commit->signature);
+ QIcon icon = frame()->verifiedIcon(commit->signature);
if (!icon.isNull()) {
QRect r = opt.rect.adjusted(6, 3, 0, -3);
int h = r.height();
int w = h;
int x = r.x() + r.width() - w;
int y = r.y();
icon.paint(painter, x, y, w, h);
}
}
}
void drawAvatar(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index) const
{
if (!opt.widget->isEnabled()) return;
int row = index.row();
- QIcon icon = mainwindow()->committerIcon(row);
+ QIcon icon = frame()->committerIcon(row);
if (!icon.isNull()) {
int h = opt.rect.height();
int w = h;
int x = opt.rect.x() + opt.rect.width() - w;
int y = opt.rect.y();
painter->save();
painter->setOpacity(0.5); // 半透明で描画
icon.paint(painter, x, y, w, h);
painter->restore();
}
}
- void drawLabels(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index) const
+ void drawLabels(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index, QString const &current_branch) const
{
int row = index.row();
- QList<BasicMainWindow::Label> const *labels = mainwindow()->label(row);
+ QList<BranchLabel> const *labels = frame()->label(row);
if (labels) {
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
- QFontMetrics fm = painter->fontMetrics();
+
+ bool show = global->mainwindow->isLabelsVisible(); // ラベル透過モード
+ painter->setOpacity(show ? 1.0 : 0.0625);
+
const int space = 8;
int x = opt.rect.x() + opt.rect.width() - 3;
int x1 = x;
int y0 = opt.rect.y();
int y1 = y0 + opt.rect.height() - 1;
int i = labels->size();
while (i > 0) {
i--;
- BasicMainWindow::Label const &label = labels->at(i);
+
+ // ラベル
+ BranchLabel const &label = labels->at(i);
QString text = misc::abbrevBranchName(label.text + label.info);
- int w = fm.size(0, text).width() + space * 2; // 幅
+
+ // 現在のブランチ名と一致するなら太字
+ bool bold = false;
+ if (text.startsWith(current_branch)) {
+ auto c = text.utf16()[current_branch.size()];
+ if (c == 0 || c == ',') {
+ bold = true;
+ }
+ }
+
+ // フォントの設定
+ QFont font = painter->font();
+ font.setBold(bold);
+ painter->setFont(font);
+
+ // ラベルの矩形
+ int w = painter->fontMetrics().size(0, text).width() + space * 2; // 幅
int x0 = x1 - w;
QRect r(x0, y0, x1 - x0, y1 - y0);
- painter->setPen(Qt::NoPen);
- auto DrawRect = [&](int dx, int dy, QColor color){
+
+ // ラベル枠の描画
+ auto DrawLabelFrame = [&](int dx, int dy, QColor color){
painter->setBrush(color);
painter->drawRoundedRect(r.adjusted(lround(dx + 3), lround(dy + 3), lround(dx - 3), lround(dy - 3)), 3, 3);
};
- QColor color = labelColor(label.kind);
- QColor hilite = hiliteColor(color);
- QColor shadow = shadowColor(color);
- DrawRect(-1, -1, hilite);
- DrawRect(1, 1, shadow);
- DrawRect(0, 0, color);
+
+ QColor color = BranchLabel::color(label.kind); // ラベル表面の色
+ QColor hilite = hiliteColor(color); // ハイライトの色
+ QColor shadow = shadowColor(color); // 陰の色
+
+ painter->setPen(Qt::NoPen);
+ DrawLabelFrame(-1, -1, hilite);
+ DrawLabelFrame(1, 1, shadow);
+ DrawLabelFrame(0, 0, color);
+
+ // ラベルテキストの描画
painter->setPen(Qt::black);
painter->setBrush(Qt::NoBrush);
QApplication::style()->drawItemText(painter, r.adjusted(space, 0, 0, 0), opt.displayAlignment, opt.palette, true, text);
x1 = x0;
}
painter->restore();
}
}
public:
explicit LogTableWidgetDelegate(QObject *parent = Q_NULLPTR)
: MyTableWidgetDelegate(parent)
{
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, QModelIndex const &index) const override
{
MyTableWidgetDelegate::paint(painter, option, index);
enum {
Graph,
CommitId,
Date,
Author,
Message,
};
// signatureの描画
if (index.column() == CommitId) {
drawSignatureIcon(painter, option, index);
}
// コミット日時
if (index.column() == Date) {
- Git::CommitItem const *commit = mainwindow()->commitItem(index.row());
+ Git::CommitItem const *commit = frame()->commitItem(index.row());
if (commit && commit->strange_date) {
QColor color(255, 0, 0, 128);
QRect r = option.rect.adjusted(1, 1, -1, -2);
misc::drawFrame(painter, r.x(), r.y(), r.width(), r.height(), color, color);
}
}
// avatarの描画
if (index.column() == Author) {
drawAvatar(painter, option, index);
}
// ラベルの描画
if (index.column() == Message) {
- drawLabels(painter, option, index);
+ QString current_branch = frame()->currentBranchName();
+ drawLabels(painter, option, index, current_branch);
}
}
};
LogTableWidget::LogTableWidget(QWidget *parent)
: QTableWidget(parent)
, m(new Private)
{
setItemDelegate(new LogTableWidgetDelegate(this));
}
LogTableWidget::~LogTableWidget()
{
delete m;
}
-MainWindow *LogTableWidget::mainwindow()
+void LogTableWidget::bind(RepositoryWrapperFrame *frame)
+{
+ m->frame = frame;
+}
+
+RepositoryWrapperFrame *LogTableWidget::frame()
{
- auto *mw = qobject_cast<MainWindow *>(window());
- Q_ASSERT(mw);
- return mw;
+// auto *mw = qobject_cast<RepositoryWrapperFrame *>(window());
+// Q_ASSERT(mw);
+// return mw;
+ Q_ASSERT(m->frame);
+ return m->frame;
}
void drawBranch(QPainterPath *path, double x0, double y0, double x1, double y1, double r, bool bend_early)
{
- const double k = 0.55228475;
+ const double k = 0.55228475; // 三次ベジェ曲線で円を近似するための定数
if (x0 == x1) {
path->moveTo(x0, y0);
path->lineTo(x1, y1);
} else {
double ym = bend_early ? (y0 + r) : (y1 - r);
double h = fabs(y1 - y0);
double w = fabs(x1 - x0);
if (r > h / 2) r = h / 2;
if (r > w / 2) r = w / 2;
double s = r;
if (x0 > x1) r = -r;
if (y0 > y1) s = -s;
if (0) {
path->moveTo(x0, y0);
path->lineTo(x0, ym - s);
path->cubicTo(x0, ym - s + s * k, x0 + r - r * k, ym, x0 + r, ym);
path->lineTo(x1 - r, ym);
path->cubicTo(x1 - r + r * k, ym, x1, ym + s - s * k, x1, ym + s);
path->lineTo(x1, y1);
} else {
if (bend_early) {
path->moveTo(x0, y0);
path->cubicTo(x0, ym, x1, ym, x1, ym + ym - y0);
path->lineTo(x1, y1);
} else {
path->moveTo(x0, y0);
path->lineTo(x0, ym + ym - y1);
path->cubicTo(x0, ym, x1, ym, x1, y1);
}
}
}
}
void LogTableWidget::paintEvent(QPaintEvent *e)
{
if (rowCount() < 1) return;
QTableWidget::paintEvent(e);
QPainter pr(viewport());
pr.setRenderHint(QPainter::Antialiasing);
pr.setBrush(QBrush(QColor(255, 255, 255)));
- Git::CommitItemList const *list = &mainwindow()->getLogs();
+ Git::CommitItemList const *list = &frame()->getLogs();
int indent_span = 16;
int line_width = 2;
int thick_line_width = 4;
auto ItemRect = [&](int row){
QRect r;
QTableWidgetItem *p = item(row, 0);
if (p) {
r = visualItemRect(p);
}
return r;
};
auto IsAncestor = [&](Git::CommitItem const &item){
- return mainwindow()->isAncestorCommit(item.commit_id);
+ return frame()->isAncestorCommit(item.commit_id);
};
auto ItemPoint = [&](int depth, QRect const &rect){
int h = rect.height();
double n = h / 2.0;
double x = floor(rect.x() + n + depth * indent_span);
double y = floor(rect.y() + n);
return QPointF(x, y);
};
auto SetPen = [&](QPainter *pr, int level, bool thick){
- QColor c = mainwindow()->color(level + 1);
+ QColor c = frame()->color(level + 1);
Qt::PenStyle s = Qt::SolidLine;
pr->setPen(QPen(c, thick ? thick_line_width : line_width, s));
};
auto DrawLine = [&](size_t index, int itemrow){
QRect rc1;
if (index < list->size()) {
Git::CommitItem const &item1 = list->at(index);
rc1 = ItemRect(itemrow);
QPointF pt1 = ItemPoint(item1.marker_depth, rc1);
double halfheight = rc1.height() / 2.0;
for (TreeLine const &line : item1.parent_lines) {
if (line.depth >= 0) {
QPainterPath *path = nullptr;
Git::CommitItem const &item2 = list->at(line.index);
QRect rc2 = ItemRect(line.index);
if (index + 1 == (size_t)line.index || line.depth == item1.marker_depth || line.depth == item2.marker_depth) {
QPointF pt2 = ItemPoint(line.depth, rc2);
if (pt2.y() > 0) {
path = new QPainterPath();
drawBranch(path, pt1.x(), pt1.y(), pt2.x(), pt2.y(), halfheight, line.bend_early);
}
} else {
QPointF pt3 = ItemPoint(item2.marker_depth, rc2);
if (pt3.y() > 0) {
path = new QPainterPath();
QRect rc3 = ItemRect(itemrow + 1);
QPointF pt2 = ItemPoint(line.depth, rc3);
drawBranch(path, pt1.x(), pt1.y(), pt2.x(), pt2.y(), halfheight, true);
drawBranch(path, pt2.x(), pt2.y(), pt3.x(), pt3.y(), halfheight, false);
}
}
if (path) {
SetPen(&pr, line.color_number, IsAncestor(item1));
pr.drawPath(*path);
delete path;
}
}
}
}
return rc1.y();
};
auto DrawMark = [&](size_t index, int row){
double x, y;
y = 0;
if (index < list->size()) {
Git::CommitItem const &item = list->at(index);
QRect rc = ItemRect(row);
QPointF pt = ItemPoint(item.marker_depth, rc);
double r = 4;
x = pt.x() - r;
y = pt.y() - r;
if (item.resolved) {
// ◯
SetPen(&pr, item.marker_depth, IsAncestor(item));
pr.drawEllipse((int)x, (int)y, int(r * 2), int(r * 2));
} else {
// ▽
SetPen(&pr, item.marker_depth, false);
QPainterPath path;
path.moveTo(pt.x(), pt.y() + r);
path.lineTo(pt.x() - r, pt.y() - r);
path.lineTo(pt.x() + r, pt.y() - r);
path.lineTo(pt.x(), pt.y() + r);
pr.drawPath(path);
}
}
return y;
};
// draw lines
pr.setOpacity(0.5);
pr.setBrush(Qt::NoBrush);
for (size_t i = 0; i < list->size(); i++) {
double y = DrawLine(i, i);
if (y >= height()) break;
}
// draw marks
pr.setOpacity(1);
- pr.setBrush(mainwindow()->color(0));
+ pr.setBrush(frame()->color(0));
for (size_t i = 0; i < list->size(); i++) {
double y = DrawMark(i, i);
if (y >= height()) break;
}
}
void LogTableWidget::resizeEvent(QResizeEvent *e)
{
- mainwindow()->updateAncestorCommitMap();
+ frame()->updateAncestorCommitMap();
QTableWidget::resizeEvent(e);
}
void LogTableWidget::verticalScrollbarValueChanged(int value)
{
(void)value;
- mainwindow()->updateAncestorCommitMap();
+ frame()->updateAncestorCommitMap();
}
diff --git a/src/LogTableWidget.h b/src/LogTableWidget.h
index 45b1435..e44cf21 100644
--- a/src/LogTableWidget.h
+++ b/src/LogTableWidget.h
@@ -1,26 +1,30 @@
#ifndef LOGTABLEWIDGET_H
#define LOGTABLEWIDGET_H
#include <QTableWidget>
-class MainWindow;
+class RepositoryWrapperFrame;
class LogTableWidgetDelegate;
+/**
+ * @brief コミットログテーブルウィジェット
+ */
class LogTableWidget : public QTableWidget {
Q_OBJECT
friend class LogTableWidgetDelegate;
private:
struct Private;
Private *m;
- MainWindow *mainwindow();
+ RepositoryWrapperFrame *frame();
public:
explicit LogTableWidget(QWidget *parent = nullptr);
~LogTableWidget() override;
+ void bind(RepositoryWrapperFrame *frame);
protected:
void paintEvent(QPaintEvent *) override;
void resizeEvent(QResizeEvent *e) override;
protected slots:
void verticalScrollbarValueChanged(int value) override;
};
#endif // LOGTABLEWIDGET_H
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 66bceb6..a90ed0e 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -1,2991 +1,3473 @@
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "AboutDialog.h"
#include "ApplicationGlobal.h"
#include "AreYouSureYouWantToContinueConnectingDialog.h"
-#include "AvatarLoader.h"
#include "BlameWindow.h"
+#include "CheckoutDialog.h"
+#include "CherryPickDialog.h"
#include "CloneFromGitHubDialog.h"
#include "CommitPropertyDialog.h"
-#include "DeleteBranchDialog.h"
#include "EditGitIgnoreDialog.h"
#include "EditTagsDialog.h"
-#include "FileDiffWidget.h"
#include "FindCommitDialog.h"
#include "GitDiff.h"
#include "JumpDialog.h"
#include "LineEditDialog.h"
+#include "MergeDialog.h"
#include "MySettings.h"
#include "ObjectBrowserDialog.h"
#include "ReflogWindow.h"
-#include "RemoteWatcher.h"
#include "SetGpgSigningDialog.h"
#include "SettingsDialog.h"
#include "StatusLabel.h"
+#include "SubmoduleUpdateDialog.h"
+#include "SubmodulesDialog.h"
#include "TextEditDialog.h"
-#include "common/joinpath.h"
+#include "UserEvent.h"
+#include "SubmoduleMainWindow.h"
+#include "FilePropertyDialog.h"
#include "common/misc.h"
-#include "CherryPickDialog.h"
-#include "MergeDialog.h"
-#include "platform.h"
-#include "webclient.h"
#include <QClipboard>
-#include <QDirIterator>
+#include <QDir>
#include <QElapsedTimer>
#include <QFileDialog>
#include <QFileIconProvider>
#include <QMessageBox>
#include <QMimeData>
#include <QPainter>
+#include <QShortcut>
#include <QStandardPaths>
#include <QTimer>
+#include "coloredit/ColorDialog.h"
-enum class CustomEvent {
- Start = QEvent::User,
-};
-class StartEvent : public QEvent {
-public:
- StartEvent()
- : QEvent((QEvent::Type)CustomEvent::Start)
+
+
+struct EventItem {
+ QObject *receiver = nullptr;
+ QEvent *event = nullptr;
+ QDateTime at;
+ EventItem(QObject *receiver, QEvent *event, QDateTime const &at)
+ : receiver(receiver)
+ , event(event)
+ , at(at)
{
}
};
-FileDiffWidget::DrawData::DrawData()
-{
- bgcolor_text = QColor(255, 255, 255);
- bgcolor_gray = QColor(224, 224, 224);
- bgcolor_add = QColor(192, 240, 192);
- bgcolor_del = QColor(255, 224, 224);
- bgcolor_add_dark = QColor(64, 192, 64);
- bgcolor_del_dark = QColor(240, 64, 64);
-}
+struct MainWindow::Private2 {
+ std::vector<EventItem> event_item_list;
-struct MainWindow::Private {
bool is_online_mode = true;
QTimer interval_10ms_timer;
QImage graph_color;
QPixmap digits;
StatusLabel *status_bar_label;
QObject *last_focused_file_list = nullptr;
QListWidgetItem *last_selected_file_item = nullptr;
bool searching = false;
QString search_text;
- RemoteWatcher remote_watcher;
-
int repos_panel_width = 0;
std::set<QString> ancestors;
QWidget *focused_widget = nullptr;
QList<int> splitter_h_sizes;
};
MainWindow::MainWindow(QWidget *parent)
: BasicMainWindow(parent)
, ui(new Ui::MainWindow)
- , m(new Private)
+ , m1(new Private1)
+ , m2(new Private2)
{
ui->setupUi(this);
+ ui->frame_repository_wrapper->bind(this
+ , ui->tableWidget_log
+ , ui->listWidget_files
+ , ui->listWidget_unstaged
+ , ui->listWidget_staged
+ , ui->widget_diff_view
+ );
+
+ loadApplicationSettings();
+ m1->starting_dir = QDir::current().absolutePath();
+
+ { // load graphic resources
+ QFileIconProvider icons;
+ m1->folder_icon = icons.icon(QFileIconProvider::Folder);
+ m1->repository_icon = QIcon(":/image/repository.png");
+ m1->signature_good_icon = QIcon(":/image/signature-good.png");
+ m1->signature_bad_icon = QIcon(":/image/signature-bad.png");
+ m1->signature_dubious_icon = QIcon(":/image/signature-dubious.png");
+ m1->transparent_pixmap = QPixmap(":/image/transparent.png");
+ }
+
#ifdef Q_OS_MACX
ui->action_about->setText("About Guitar...");
ui->action_edit_settings->setText("Settings...");
#endif
ui->splitter_v->setSizes({100, 400});
ui->splitter_h->setSizes({200, 100, 200});
- m->status_bar_label = new StatusLabel(this);
- ui->statusBar->addWidget(m->status_bar_label);
+ m2->status_bar_label = new StatusLabel(this);
+ ui->statusBar->addWidget(m2->status_bar_label);
- ui->widget_diff_view->bind(this);
+ frame()->filediffwidget()->bind(this);
qApp->installEventFilter(this);
+ setShowLabels(appsettings()->show_labels, false);
+
ui->widget_log->setupForLogWidget(ui->verticalScrollBar_log, ui->horizontalScrollBar_log, themeForTextEditor());
onLogVisibilityChanged();
initNetworking();
showFileList(FilesListType::SingleList);
- m->digits.load(":/image/digits.png");
- m->graph_color = global->theme->graphColorMap();
+ m2->digits.load(":/image/digits.png");
+ m2->graph_color = global->theme->graphColorMap();
- prepareLogTableWidget();
+ frame()->prepareLogTableWidget();
#ifdef Q_OS_WIN
{
QFont font;
font = ui->label_repo_name->font();
font.setFamily("Meiryo");
ui->label_repo_name->setFont(font);
font = ui->label_branch_name->font();
font.setFamily("Meiryo");
ui->label_branch_name->setFont(font);
}
#endif
- connect(this, &BasicMainWindow::signalWriteLog, this, &BasicMainWindow::writeLog_);
+ connect(this, &MainWindow::signalWriteLog, this, &MainWindow::writeLog_);
connect(ui->dockWidget_log, &QDockWidget::visibilityChanged, this, &MainWindow::onLogVisibilityChanged);
connect(ui->widget_log, &TextEditorWidget::idle, this, &MainWindow::onLogIdle);
connect(ui->treeWidget_repos, &RepositoriesTreeWidget::dropped, this, &MainWindow::onRepositoriesTreeDropped);
connect((AbstractPtyProcess *)getPtyProcess(), &AbstractPtyProcess::completed, this, &MainWindow::onPtyProcessCompleted);
- connect(this, &BasicMainWindow::remoteInfoChanged, [&](){
+ connect(this, &MainWindow::remoteInfoChanged, [&](){
ui->lineEdit_remote->setText(currentRemoteName());
});
connect(this, &MainWindow::signalSetRemoteChanged, [&](bool f){
setRemoteChanged(f);
updateButton();
});
+ connect(new QShortcut(QKeySequence("Ctrl+T"), this), &QShortcut::activated, this, &MainWindow::test);
+
//
QString path = getBookmarksFilePath();
*getReposPtr() = RepositoryBookmark::load(path);
updateRepositoriesList();
- webContext()->set_keep_alive_enabled(true);
- getAvatarLoader()->start(this);
- connect(getAvatarLoader(), &AvatarLoader::updated, this, &MainWindow::onAvatarUpdated);
-
- *ptrUpdateFilesListCounter() = 0;
+ {
+ // アイコン取得機能
+ webContext()->set_keep_alive_enabled(true);
+ getAvatarLoader()->start(this);
+ connect(getAvatarLoader(), &AvatarLoader::updated, this, &MainWindow::onAvatarUpdated);
+ }
- connect(ui->widget_diff_view, &FileDiffWidget::textcodecChanged, [&](){ updateDiffView(); });
+ connect(frame()->filediffwidget(), &FileDiffWidget::textcodecChanged, [&](){ updateDiffView(frame()); });
if (!global->start_with_shift_key && appsettings()->remember_and_restore_window_position) {
Qt::WindowStates state = windowState();
MySettings settings;
settings.beginGroup("MainWindow");
bool maximized = settings.value("Maximized").toBool();
restoreGeometry(settings.value("Geometry").toByteArray());
settings.endGroup();
if (maximized) {
state |= Qt::WindowMaximized;
setWindowState(state);
}
}
+ ui->action_sidebar->setChecked(true);
+
startTimers();
}
MainWindow::~MainWindow()
{
+ cancelPendingUserEvents();
+
stopPtyProcess();
getAvatarLoader()->stop();
- m->remote_watcher.quit();
- m->remote_watcher.wait();
+ deleteTempFiles();
- delete m;
+ delete m2;
+ delete m1;
delete ui;
}
+RepositoryWrapperFrame *MainWindow::frame()
+{
+ return ui->frame_repository_wrapper;
+}
-void MainWindow::notifyRemoteChanged(bool f)
+RepositoryWrapperFrame const *MainWindow::frame() const
{
- postUserFunctionEvent([&](QVariant const &v){
- setRemoteChanged(v.toBool());
- updateButton();
- }, QVariant(f));
+ return ui->frame_repository_wrapper;
}
-void MainWindow::postStartEvent()
+/**
+ * @brief イベントをポストする
+ * @param receiver 宛先
+ * @param event イベント
+ * @param ms_later 遅延時間(0なら即座)
+ */
+void MainWindow::postEvent(QObject *receiver, QEvent *event, int ms_later)
{
- QTimer::singleShot(100, [&](){
- QApplication::postEvent(this, new StartEvent);
- });
+ if (ms_later <= 0) {
+ QApplication::postEvent(this, event);
+ } else {
+ auto at = QDateTime::currentDateTime().addMSecs(ms_later);
+ m2->event_item_list.emplace_back(receiver, event, at);
+ std::stable_sort(m2->event_item_list.begin(), m2->event_item_list.end(), [](EventItem const &l, EventItem const &r){
+ return l.at > r.at; // 降順
+ });
+ }
+}
+
+/**
+ * @brief ユーザー関数イベントをポストする
+ * @param fn 関数
+ * @param v QVariant
+ * @param p ポインタ
+ * @param ms_later 遅延時間(0なら即座)
+ */
+void MainWindow::postUserFunctionEvent(const std::function<void (const QVariant &, void *ptr)> &fn, const QVariant &v, void *p, int ms_later)
+{
+ postEvent(this, new UserFunctionEvent(fn, v, p), ms_later);
+}
+
+/**
+ * @brief 未送信のイベントをすべて削除する
+ */
+void MainWindow::cancelPendingUserEvents()
+{
+ for (auto &item : m2->event_item_list) {
+ delete item.event;
+ }
+ m2->event_item_list.clear();
+}
+
+/**
+ * @brief 開始イベントをポストする
+ */
+void MainWindow::postStartEvent(int ms_later)
+{
+ postEvent(this, new StartEvent, ms_later);
+}
+
+/**
+ * @brief インターバルタイマを開始する
+ */
+void MainWindow::startTimers()
+{
+ // タイマ開始
+ connect(&m2->interval_10ms_timer, &QTimer::timeout, this, &MainWindow::onInterval10ms);
+ m2->interval_10ms_timer.setInterval(10);
+ m2->interval_10ms_timer.start();
+}
+
+/**
+ * @brief 10ms間隔のインターバルタイマ
+ */
+void MainWindow::onInterval10ms()
+{
+ {
+ // ユーザーイベントの処理
+
+ std::vector<EventItem> items; // 処理するイベント
+
+ QDateTime now = QDateTime::currentDateTime(); // 現在時刻
+
+ size_t i = m2->event_item_list.size(); // 後ろから走査
+ while (i > 0) {
+ i--;
+ if (m2->event_item_list[i].at <= now) { // 予約時間を過ぎていたら
+ items.push_back(m2->event_item_list[i]); // 処理リストに追加
+ m2->event_item_list.erase(m2->event_item_list.begin() + i); // 処理待ちリストから削除
+ }
+ }
+
+ // イベントをポストする
+ for (auto it = items.rbegin(); it != items.rend(); it++) {
+ QApplication::postEvent(it->receiver, it->event);
+ }
+ }
+
+ {
+ // PTYプロセスの監視
+
+ bool running = getPtyProcess()->isRunning();
+ if (ui->toolButton_stop_process->isEnabled() != running) {
+ ui->toolButton_stop_process->setEnabled(running); // ボタンの状態を設定
+ ui->action_stop_process->setEnabled(running);
+ setNetworkingCommandsEnabled(!running);
+ }
+ if (!running) {
+ setInteractionMode(InteractionMode::None);
+ }
+
+ // PTYプロセスの出力をログに書き込む
+ while (1) {
+ char tmp[1024];
+ int len = getPtyProcess()->readOutput(tmp, sizeof(tmp));
+ if (len < 1) break;
+ writeLog(tmp, len);
+ }
+ }
}
bool MainWindow::shown()
{
- m->repos_panel_width = ui->stackedWidget_leftpanel->width();
+ m2->repos_panel_width = ui->stackedWidget_leftpanel->width();
ui->stackedWidget_leftpanel->setCurrentWidget(ui->page_repos);
ui->action_repositories_panel->setChecked(true);
{
MySettings settings;
{
settings.beginGroup("Remote");
bool f = settings.value("Online", true).toBool();
settings.endGroup();
setRemoteOnline(f, false);
}
{
settings.beginGroup("MainWindow");
int n = settings.value("FirstColumnWidth", 50).toInt();
if (n < 10) n = 50;
- ui->tableWidget_log->setColumnWidth(0, n);
+ frame()->logtablewidget()->setColumnWidth(0, n);
settings.endGroup();
}
}
updateUI();
- postStartEvent(); // 開始イベント
+ postStartEvent(100); // 開始イベント(100ms後)
return true;
}
bool MainWindow::isUninitialized()
{
return !misc::isExecutable(appsettings()->git_command) || !misc::isExecutable(appsettings()->file_command);
}
void MainWindow::onStartEvent()
{
if (isUninitialized()) { // gitコマンドの有効性チェック
if (!execWelcomeWizardDialog()) { // ようこそダイアログを表示
close(); // キャンセルされたらプログラム終了
}
}
if (isUninitialized()) { // 正しく初期設定されたか
- postStartEvent(); // 初期設定されなかったら、もういちどようこそダイアログを出す。
+ postStartEvent(100); // 初期設定されなかったら、もういちどようこそダイアログを出す(100ms後)
} else {
// 外部コマンド登録
setGitCommand(appsettings()->git_command, false);
setFileCommand(appsettings()->file_command, false);
setGpgCommand(appsettings()->gpg_command, false);
+ setSshCommand(appsettings()->ssh_command, false);
// メインウィンドウのタイトルを設定
updateWindowTitle(git());
// プログラムバーション表示
writeLog(AboutDialog::appVersion() + '\n');
// gitコマンドバージョン表示
logGitVersion();
}
}
-void MainWindow::startTimers()
+void MainWindow::setCurrentLogRow(RepositoryWrapperFrame *frame, int row)
{
- // interval 10ms
-
- connect(&m->interval_10ms_timer, &QTimer::timeout, [&](){
- const int ms = 10;
- auto *p1 = ptrUpdateCommitTableCounter();
- if (*p1 > 0) {
- if (*p1 > ms) {
- *p1 -= ms;
- } else {
- *p1 = 0;
- ui->tableWidget_log->viewport()->update();
- }
- }
- auto *p2 = ptrUpdateFilesListCounter();
- if (*p2 > 0) {
- if (*p2 > ms) {
- *p2 -= ms;
- } else {
- *p2 = 0;
- updateCurrentFilesList();
- }
- }
- });
- m->interval_10ms_timer.setInterval(10);
- m->interval_10ms_timer.start();
-
- startTimer(10);
-}
-
-void MainWindow::setCurrentLogRow(int row)
-{
- if (row >= 0 && row < ui->tableWidget_log->rowCount()) {
- ui->tableWidget_log->setCurrentCell(row, 2);
- ui->tableWidget_log->setFocus();
- updateStatusBarText();
+ if (row >= 0 && row < frame->logtablewidget()->rowCount()) {
+ updateStatusBarText(frame);
+ frame->logtablewidget()->setFocus();
+ frame->logtablewidget()->setCurrentCell(row, 2);
}
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
QEvent::Type et = event->type();
if (et == QEvent::KeyPress) {
if (QApplication::activeModalWidget()) {
// thru
} else {
auto *e = dynamic_cast<QKeyEvent *>(event);
Q_ASSERT(e);
int k = e->key();
if (k == Qt::Key_Escape) {
if (centralWidget()->isAncestorOf(qApp->focusWidget())) {
ui->treeWidget_repos->setFocus();
return true;
}
}
if (e->modifiers() & Qt::ControlModifier) {
if (k == Qt::Key_Up || k == Qt::Key_Down) {
- int rows = ui->tableWidget_log->rowCount();
- int row = ui->tableWidget_log->currentRow();
+ int rows = frame()->logtablewidget()->rowCount();
+ int row = frame()->logtablewidget()->currentRow();
if (k == Qt::Key_Up) {
if (row > 0) {
row--;
}
} else if (k == Qt::Key_Down) {
if (row + 1 < rows) {
row++;
}
}
- ui->tableWidget_log->setCurrentCell(row, 0);
+ frame()->logtablewidget()->setCurrentCell(row, 0);
return true;
}
}
if (watched == ui->treeWidget_repos) {
if (k == Qt::Key_Enter || k == Qt::Key_Return) {
openSelectedRepository();
return true;
}
if (!(e->modifiers() & Qt::ControlModifier)) {
if (k >= 0 && k < 128 && QChar((uchar)k).isLetterOrNumber()) {
appendCharToRepoFilter(k);
return true;
}
if (k == Qt::Key_Backspace) {
backspaceRepoFilter();
return true;
}
if (k == Qt::Key_Escape) {
clearRepoFilter();
return true;
}
}
- } else if (watched == ui->tableWidget_log) {
+ } else if (watched == frame()->logtablewidget()) {
if (k == Qt::Key_Home) {
- setCurrentLogRow(0);
+ setCurrentLogRow(frame(), 0);
return true;
}
if (k == Qt::Key_Escape) {
ui->treeWidget_repos->setFocus();
return true;
}
- } else if (watched == ui->listWidget_files || watched == ui->listWidget_unstaged || watched == ui->listWidget_staged) {
+ } else if (watched == frame()->fileslistwidget() || watched == frame()->unstagedFileslistwidget() || watched == frame()->stagedFileslistwidget()) {
if (k == Qt::Key_Escape) {
- ui->tableWidget_log->setFocus();
+ frame()->logtablewidget()->setFocus();
return true;
}
}
}
} else if (et == QEvent::FocusIn) {
auto SelectItem = [](QListWidget *w){
int row = w->currentRow();
if (row < 0) {
row = 0;
w->setCurrentRow(row);
}
w->setItemSelected(w->item(row), true);
w->viewport()->update();
};
// ファイルリストがフォーカスを得たとき、diffビューを更新する。(コンテキストメニュー対応)
- if (watched == ui->listWidget_unstaged) {
- m->last_focused_file_list = watched;
- updateStatusBarText();
- updateUnstagedFileCurrentItem();
- SelectItem(ui->listWidget_unstaged);
+ if (watched == frame()->unstagedFileslistwidget()) {
+ m2->last_focused_file_list = watched;
+ updateStatusBarText(frame());
+ updateUnstagedFileCurrentItem(frame());
+ SelectItem(frame()->unstagedFileslistwidget());
return true;
}
- if (watched == ui->listWidget_staged) {
- m->last_focused_file_list = watched;
- updateStatusBarText();
- updateStagedFileCurrentItem();
- SelectItem(ui->listWidget_staged);
+ if (watched == frame()->stagedFileslistwidget()) {
+ m2->last_focused_file_list = watched;
+ updateStatusBarText(frame());
+ updateStagedFileCurrentItem(frame());
+ SelectItem(frame()->stagedFileslistwidget());
return true;
}
- if (watched == ui->listWidget_files) {
- m->last_focused_file_list = watched;
- SelectItem(ui->listWidget_files);
+ if (watched == frame()->fileslistwidget()) {
+ m2->last_focused_file_list = watched;
+ SelectItem(frame()->fileslistwidget());
return true;
}
}
return false;
}
bool MainWindow::event(QEvent *event)
{
QEvent::Type et = event->type();
if (et == QEvent::KeyPress) {
auto *e = dynamic_cast<QKeyEvent *>(event);
Q_ASSERT(e);
int k = e->key();
if (k == Qt::Key_Escape) {
emit onEscapeKeyPressed();
} else if (k == Qt::Key_Delete) {
if (qApp->focusWidget() == ui->treeWidget_repos) {
removeSelectedRepositoryFromBookmark(true);
return true;
}
}
+ } else if (et == (QEvent::Type)UserEvent::UserFunction) {
+ if (auto *e = (UserFunctionEvent *)event) {
+ e->func(e->var, e->ptr);
+ return true;
+ }
}
return BasicMainWindow::event(event);
}
void MainWindow::customEvent(QEvent *e)
{
- if (e->type() == (QEvent::Type)CustomEvent::Start) {
+ if (e->type() == (QEvent::Type)UserEvent::Start) {
onStartEvent();
return;
}
}
void MainWindow::closeEvent(QCloseEvent *event)
{
MySettings settings;
if (appsettings()->remember_and_restore_window_position) {
setWindowOpacity(0);
Qt::WindowStates state = windowState();
bool maximized = (state & Qt::WindowMaximized) != 0;
if (maximized) {
state &= ~Qt::WindowMaximized;
setWindowState(state);
}
{
settings.beginGroup("MainWindow");
settings.setValue("Maximized", maximized);
settings.setValue("Geometry", saveGeometry());
settings.endGroup();
}
}
{
settings.beginGroup("MainWindow");
- settings.setValue("FirstColumnWidth", ui->tableWidget_log->columnWidth(0));
+ settings.setValue("FirstColumnWidth", frame()->logtablewidget()->columnWidth(0));
settings.endGroup();
}
QMainWindow::closeEvent(event);
}
void MainWindow::setStatusBarText(QString const &text)
{
- m->status_bar_label->setText(text);
+ m2->status_bar_label->setText(text);
}
void MainWindow::clearStatusBarText()
{
setStatusBarText(QString());
}
void MainWindow::onLogVisibilityChanged()
{
ui->action_window_log->setChecked(ui->dockWidget_log->isVisible());
}
void MainWindow::internalWriteLog(char const *ptr, int len)
{
ui->widget_log->logicalMoveToBottom();
ui->widget_log->write(ptr, len, false);
ui->widget_log->setChanged(false);
setInteractionCanceled(false);
}
void MainWindow::buildRepoTree(QString const &group, QTreeWidgetItem *item, QList<RepositoryItem> *repos)
{
QString name = item->text(0);
if (isGroupItem(item)) {
int n = item->childCount();
for (int i = 0; i < n; i++) {
QTreeWidgetItem *child = item->child(i);
QString sub = group / name;
buildRepoTree(sub, child, repos);
}
} else {
RepositoryItem const *repo = repositoryItem(item);
if (repo) {
RepositoryItem newrepo = *repo;
newrepo.name = name;
newrepo.group = group;
item->setData(0, IndexRole, repos->size());
repos->push_back(newrepo);
}
}
}
void MainWindow::refrectRepositories()
{
QList<RepositoryItem> newrepos;
int n = ui->treeWidget_repos->topLevelItemCount();
for (int i = 0; i < n; i++) {
QTreeWidgetItem *item = ui->treeWidget_repos->topLevelItem(i);
buildRepoTree(QString(), item, &newrepos);
}
*getReposPtr() = std::move(newrepos);
saveRepositoryBookmarks();
}
void MainWindow::onRepositoriesTreeDropped()
{
refrectRepositories();
QTreeWidgetItem *item = ui->treeWidget_repos->currentItem();
if (item) item->setExpanded(true);
}
const QPixmap &MainWindow::digitsPixmap() const
{
- return m->digits;
+ return m2->digits;
}
int MainWindow::digitWidth() const
{
return 5;
}
int MainWindow::digitHeight() const
{
return 7;
}
void MainWindow::drawDigit(QPainter *pr, int x, int y, int n) const
{
int w = digitWidth();
int h = digitHeight();
- pr->drawPixmap(x, y, w, h, m->digits, n * w, 0, w, h);
+ pr->drawPixmap(x, y, w, h, m2->digits, n * w, 0, w, h);
}
-QString BasicMainWindow::defaultWorkingDir() const
+QString MainWindow::defaultWorkingDir() const
{
return appsettings()->default_working_dir;
}
+/**
+ * @brief サブモジュール情報を取得する
+ * @param path
+ * @param commit コミット情報を取得(nullptr可)
+ * @return
+ */
+Git::SubmoduleItem const *MainWindow::querySubmoduleByPath(const QString &path, Git::CommitItem *commit)
+{
+ if (commit) *commit = {};
+ for (auto const &submod : m1->submodules) {
+ if (submod.path == path) {
+ if (commit) {
+ GitPtr g = git(submod);
+ g->queryCommit(submod.id, commit);
+ }
+ return &submod;
+ }
+ }
+ return nullptr;
+}
+
QColor MainWindow::color(unsigned int i)
{
- unsigned int n = m->graph_color.width();
+ unsigned int n = m2->graph_color.width();
if (n > 0) {
n--;
if (i > n) i = n;
- QRgb const *p = (QRgb const *)m->graph_color.scanLine(0);
+ QRgb const *p = (QRgb const *)m2->graph_color.scanLine(0);
return QColor(qRed(p[i]), qGreen(p[i]), qBlue(p[i]));
}
return Qt::black;
}
-QString MainWindow::currentWorkingCopyDir() const
-{
- QString workdir = BasicMainWindow::currentWorkingCopyDir();
- if (workdir.isEmpty()) {
- RepositoryItem const *repo = selectedRepositoryItem();
- if (repo) {
- workdir = repo->local_dir;
- return workdir;
- }
- }
- return workdir;
-}
+//QString MainWindow::currentWorkingCopyDir() const
+//{
+// QString workdir = BasicMainWindow::currentWorkingCopyDir();
+// if (workdir.isEmpty()) {
+// RepositoryItem const *repo = selectedRepositoryItem();
+// if (repo) {
+// workdir = repo->local_dir;
+// return workdir;
+// }
+// }
+// return workdir;
+//}
-RepositoryItem const *BasicMainWindow::findRegisteredRepository(QString *workdir) const
+RepositoryItem const *MainWindow::findRegisteredRepository(QString *workdir) const
{
*workdir = QDir(*workdir).absolutePath();
workdir->replace('\\', '/');
if (Git::isValidWorkingCopy(*workdir)) {
for (RepositoryItem const &item : getRepos()) {
Qt::CaseSensitivity cs = Qt::CaseSensitive;
#ifdef Q_OS_WIN
cs = Qt::CaseInsensitive;
#endif
if (workdir->compare(item.local_dir, cs) == 0) {
return &item;
}
}
}
return nullptr;
}
int MainWindow::repositoryIndex_(QTreeWidgetItem const *item) const
{
if (item) {
int i = item->data(0, IndexRole).toInt();
if (i >= 0 && i < getRepos().size()) {
return i;
}
}
return -1;
}
RepositoryItem const *MainWindow::repositoryItem(QTreeWidgetItem const *item) const
{
int row = repositoryIndex_(item);
- auto const &repos = getRepos();
+ QList<RepositoryItem> const &repos = getRepos();
return (row >= 0 && row < repos.size()) ? &repos[row] : nullptr;
}
RepositoryItem const *MainWindow::selectedRepositoryItem() const
{
return repositoryItem(ui->treeWidget_repos->currentItem());
}
static QTreeWidgetItem *newQTreeWidgetItem()
{
auto *item = new QTreeWidgetItem;
item->setSizeHint(0, QSize(20, 20));
return item;
}
QTreeWidgetItem *MainWindow::newQTreeWidgetFolderItem(QString const &name)
{
QTreeWidgetItem *item = newQTreeWidgetItem();
item->setText(0, name);
item->setData(0, IndexRole, GroupItem);
item->setIcon(0, getFolderIcon());
item->setFlags(item->flags() | Qt::ItemIsEditable);
return item;
}
void MainWindow::updateRepositoriesList()
{
QString path = getBookmarksFilePath();
auto *repos = getReposPtr();
*repos = RepositoryBookmark::load(path);
QString filter = getRepositoryFilterText();
ui->treeWidget_repos->clear();
std::map<QString, QTreeWidgetItem *> parentmap;
for (int i = 0; i < repos->size(); i++) {
RepositoryItem const &repo = repos->at(i);
if (!filter.isEmpty() && repo.name.indexOf(filter, 0, Qt::CaseInsensitive) < 0) {
continue;
}
QTreeWidgetItem *parent = nullptr;
{
QString group = repo.group;
if (group.startsWith('/')) {
group = group.mid(1);
}
auto it = parentmap.find(group);
if (it != parentmap.end()) {
parent = it->second;
}
if (!parent) {
QStringList list = group.split('/', QString::SkipEmptyParts);
if (list.isEmpty()) {
list.push_back(tr("Default"));
}
for (QString const &name : list) {
if (name.isEmpty()) continue;
if (!parent) {
auto it = parentmap.find(name);
if (it != parentmap.end()) {
parent = it->second;
} else {
parent = newQTreeWidgetFolderItem(name);
ui->treeWidget_repos->addTopLevelItem(parent);
}
} else {
QTreeWidgetItem *child = newQTreeWidgetFolderItem(name);
parent->addChild(child);
parent = child;
}
parent->setExpanded(true);
}
Q_ASSERT(parent);
parentmap[group] = parent;
}
parent->setData(0, FilePathRole, "");
}
QTreeWidgetItem *child = newQTreeWidgetItem();
child->setText(0, repo.name);
child->setData(0, IndexRole, i);
child->setIcon(0, getRepositoryIcon());
child->setFlags(child->flags() & ~Qt::ItemIsDropEnabled);
parent->addChild(child);
parent->setExpanded(true);
}
}
void MainWindow::showFileList(FilesListType files_list_type)
{
switch (files_list_type) {
case FilesListType::SingleList:
ui->stackedWidget_filelist->setCurrentWidget(ui->page_files);
break;
case FilesListType::SideBySide:
ui->stackedWidget_filelist->setCurrentWidget(ui->page_uncommited);
break;
}
}
-void MainWindow::clearFileList()
+/**
+ * @brief ファイルリストを消去
+ * @param frame
+ */
+void MainWindow::clearFileList(RepositoryWrapperFrame *frame)
{
showFileList(FilesListType::SingleList);
- ui->listWidget_unstaged->clear();
- ui->listWidget_staged->clear();
- ui->listWidget_files->clear();
+ frame->unstagedFileslistwidget()->clear();
+ frame->stagedFileslistwidget()->clear();
+ frame->fileslistwidget()->clear();
}
-void MainWindow::clearDiffView()
+void MainWindow::clearDiffView(RepositoryWrapperFrame *frame)
{
- ui->widget_diff_view->clearDiffView();
+ frame->filediffwidget()->clearDiffView();
}
void MainWindow::clearRepositoryInfo()
{
internalClearRepositoryInfo();
ui->label_repo_name->setText(QString());
ui->label_branch_name->setText(QString());
}
void MainWindow::setRepositoryInfo(QString const &reponame, QString const &brname)
{
ui->label_repo_name->setText(reponame);
ui->label_branch_name->setText(brname);
}
-void MainWindow::updateFilesList(QString id, bool wait)
+/**
+ * @brief 指定のコミットにおけるサブモジュールリストを取得
+ * @param g
+ * @param id
+ * @param out
+ */
+void MainWindow::updateSubmodules(GitPtr g, QString const &id, QList<Git::SubmoduleItem> *out)
+{
+ *out = {};
+ QList<Git::SubmoduleItem> submodules;
+ if (id.isEmpty()) {
+ submodules = g->submodules();
+ } else {
+ GitTreeItemList list;
+ GitObjectCache objcache;
+ objcache.setup(g);
+ // サブモジュールリストを取得する
+ {
+ GitCommit tree;
+ GitCommit::parseCommit(&objcache, id, &tree);
+ parseGitTreeObject(&objcache, tree.tree_id, {}, &list);
+ for (GitTreeItem const &item : list) {
+ if (item.type == GitTreeItem::Type::BLOB && item.name == ".gitmodules") {
+ Git::Object obj = objcache.catFile(item.id);
+ if (!obj.content.isEmpty()) {
+ parseGitSubModules(obj.content, &submodules);
+ }
+ }
+ }
+ }
+ // サブモジュールに対応するIDを求める
+ for (int i = 0; i < submodules.size(); i++) {
+ QStringList vars = submodules[i].path.split('/');
+ for (int j = 0; j < vars.size(); j++) {
+ for (int k = 0; k < list.size(); k++) {
+ if (list[k].name == vars[j]) {
+ if (list[k].type == GitTreeItem::Type::BLOB) {
+ if (j + 1 == vars.size()) {
+ submodules[i].id = list[k].id;
+ goto done;
+ }
+ } else if (list[k].type == GitTreeItem::Type::TREE) {
+ Git::Object obj = objcache.catFile(list[k].id);
+ parseGitTreeObject(obj.content, {}, &list);
+ break;
+ }
+ }
+ }
+ }
+done:;
+ }
+ }
+ *out = submodules;
+}
+
+const Git::CommitItemList &MainWindow::getLogs(RepositoryWrapperFrame const *frame) const
+{
+ // return m1->logs;
+ return frame->logs;
+}
+
+const Git::CommitItem *MainWindow::getLog(RepositoryWrapperFrame const *frame, int index) const
+{
+ Git::CommitItemList const &logs = frame->logs;
+ return (index >= 0 && index < (int)logs.size()) ? &logs[index] : nullptr;
+}
+
+Git::CommitItemList *MainWindow::getLogsPtr(RepositoryWrapperFrame *frame)
+{
+ return &frame->logs;
+}
+
+void MainWindow::setLogs(RepositoryWrapperFrame *frame, const Git::CommitItemList &logs)
+{
+ frame->logs = logs;
+}
+
+void MainWindow::clearLogs(RepositoryWrapperFrame *frame)
+{
+ frame->logs.clear();
+}
+
+/**
+ * @brief リストウィジェット用ファイルアイテムを作成する
+ * @param data
+ * @return
+ */
+QListWidgetItem *MainWindow::NewListWidgetFileItem(MainWindow::ObjectData const &data)
+{
+ const bool issubmodule = data.submod; // サブモジュール
+
+ QString header = data.header; // ヘッダ(バッジ識別子)
+ if (header.isEmpty()) {
+ header = "(??\?) "; // damn trigraph
+ }
+
+
+ QString text = data.path; // テキスト
+ if (issubmodule) {
+ text += QString(" <%0> [%1] %2")
+ .arg(data.submod.id.mid(0, 7))
+ .arg(misc::makeDateTimeString(data.submod_commit.commit_date))
+ .arg(data.submod_commit.message)
+ ;
+ }
+
+ QListWidgetItem *item = new QListWidgetItem(text);
+ item->setSizeHint(QSize(item->sizeHint().width(), 18));
+ item->setData(FilePathRole, data.path);
+ item->setData(DiffIndexRole, data.idiff);
+ item->setData(ObjectIdRole, data.id);
+// item->setData(HunkIndexRole, -1);
+ item->setData(HeaderRole, header);
+ item->setData(SubmodulePathRole, data.submod.path);
+ if (issubmodule) {
+ item->setToolTip(text); // ツールチップ
+ }
+ return item;
+}
+
+/**
+ * @brief 差分リスト情報をもとにリストウィジェットへアイテムを追加する
+ * @param diff_list
+ * @param fn_add_item
+ */
+void MainWindow::addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (ObjectData const &data)> &fn_add_item)
+{
+ for (int idiff = 0; idiff < diff_list->size(); idiff++) {
+ Git::Diff const &diff = diff_list->at(idiff);
+ QString header;
+
+ switch (diff.type) {
+ case Git::Diff::Type::Modify: header = "(chg) "; break;
+ case Git::Diff::Type::Copy: header = "(cpy) "; break;
+ case Git::Diff::Type::Rename: header = "(ren) "; break;
+ case Git::Diff::Type::Create: header = "(add) "; break;
+ case Git::Diff::Type::Delete: header = "(del) "; break;
+ case Git::Diff::Type::ChType: header = "(chg) "; break;
+ case Git::Diff::Type::Unmerged: header = "(unmerged) "; break;
+ }
+
+ ObjectData data;
+ data.id = diff.blob.b_id;
+ data.path = diff.path;
+ data.submod = diff.b_submodule.item;
+ data.submod_commit = diff.b_submodule.commit;
+ data.header = header;
+ data.idiff = idiff;
+ fn_add_item(data);
+ }
+}
+
+/**
+ * @brief コミットログを更新(100ms遅延)
+ */
+void MainWindow::updateCommitLogTableLater(RepositoryWrapperFrame *frame, int ms_later)
+{
+ postUserFunctionEvent([&](QVariant const &, void *ptr){
+ qDebug() << (void *)ptr;
+ if (ptr) {
+ RepositoryWrapperFrame *frame = reinterpret_cast<RepositoryWrapperFrame *>(ptr);
+ frame->logtablewidget()->viewport()->update();
+ }
+ }, {}, reinterpret_cast<void *>(frame), ms_later);
+}
+
+QString MainWindow::getObjectID(QListWidgetItem *item)
+{
+ if (!item) return {};
+ return item->data(ObjectIdRole).toString();
+}
+
+QString MainWindow::getFilePath(QListWidgetItem *item)
+{
+ if (!item) return {};
+ return item->data(FilePathRole).toString();
+}
+
+QString MainWindow::getSubmodulePath(QListWidgetItem *item)
+{
+ if (!item) return {};
+ return item->data(SubmodulePathRole).toString();
+}
+
+/**
+ * @brief ファイルリストを更新
+ * @param id コミットID
+ * @param wait
+ */
+void MainWindow::updateFilesList(RepositoryWrapperFrame *frame, QString id, bool wait)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (!wait) return;
- clearFileList();
+ clearFileList(frame);
Git::FileStatusList stats = g->status_s();
setUncommitedChanges(!stats.empty());
FilesListType files_list_type = FilesListType::SingleList;
bool staged = false;
- auto AddItem = [&](QString const &filename, QString header, int idiff){
- if (header.isEmpty()) {
- header = "(??\?) "; // damn trigraph
- }
- QListWidgetItem *item = new QListWidgetItem(filename);
- item->setSizeHint(QSize(item->sizeHint().width(), 18));
- item->setData(FilePathRole, filename);
- item->setData(DiffIndexRole, idiff);
- item->setData(HunkIndexRole, -1);
- item->setData(HeaderRole, header);
+ auto AddItem = [&](ObjectData const &data){
+ QListWidgetItem *item = NewListWidgetFileItem(data);
switch (files_list_type) {
case FilesListType::SingleList:
- ui->listWidget_files->addItem(item);
+ frame->fileslistwidget()->addItem(item);
break;
case FilesListType::SideBySide:
if (staged) {
- ui->listWidget_staged->addItem(item);
+ frame->stagedFileslistwidget()->addItem(item);
} else {
- ui->listWidget_unstaged->addItem(item);
+ frame->unstagedFileslistwidget()->addItem(item);
}
break;
}
};
if (id.isEmpty()) {
bool uncommited = isThereUncommitedChanges();
if (uncommited) {
files_list_type = FilesListType::SideBySide;
}
- if (!makeDiff(uncommited ? QString() : id, diffResult())) {
- return;
- }
+ bool ok = false;
+ auto diffs = makeDiffs(frame, uncommited ? QString() : id, &ok);
+ setDiffResult(diffs);
+ if (!ok) return;
std::map<QString, int> diffmap;
for (int idiff = 0; idiff < diffResult()->size(); idiff++) {
Git::Diff const &diff = diffResult()->at(idiff);
QString filename = diff.path;
if (!filename.isEmpty()) {
diffmap[filename] = idiff;
}
}
showFileList(files_list_type);
for (Git::FileStatus const &s : stats) {
staged = (s.isStaged() && s.code_y() == ' ');
int idiff = -1;
QString header;
auto it = diffmap.find(s.path1());
+ Git::Diff const *diff = nullptr;
if (it != diffmap.end()) {
idiff = it->second;
+ diff = &diffResult()->at(idiff);
}
QString path = s.path1();
if (s.code() == Git::FileStatusCode::Unknown) {
qDebug() << "something wrong...";
} else if (s.code() == Git::FileStatusCode::Untracked) {
// nop
} else if (s.isUnmerged()) {
header += "(unmerged) ";
} else if (s.code() == Git::FileStatusCode::AddedToIndex) {
header = "(add) ";
} else if (s.code_x() == 'D' || s.code_y() == 'D' || s.code() == Git::FileStatusCode::DeletedFromIndex) {
header = "(del) ";
} else if (s.code_x() == 'R' || s.code() == Git::FileStatusCode::RenamedInIndex) {
header = "(ren) ";
path = s.path2(); // renamed newer path
} else if (s.code_x() == 'M' || s.code_y() == 'M') {
header = "(chg) ";
}
- AddItem(path, header, idiff);
+ ObjectData data;
+ data.path = path;
+ data.header = header;
+ data.idiff = idiff;
+ if (diff) {
+ data.submod = diff->b_submodule.item; // TODO:
+ if (data.submod) {
+ GitPtr g = git(data.submod);
+ g->queryCommit(data.submod.id, &data.submod_commit);
+ }
+ }
+ AddItem(data);
}
} else {
- if (!makeDiff(id, diffResult())) {
- return;
- }
+ bool ok = false;
+ auto diffs = makeDiffs(frame, id, &ok);
+ setDiffResult(diffs);
+ if (!ok) return;
+
showFileList(files_list_type);
addDiffItems(diffResult(), AddItem);
}
for (Git::Diff const &diff : *diffResult()) {
QString key = GitDiff::makeKey(diff);
- (*getDiffCacheMap())[key] = diff;
+ (*getDiffCacheMap(frame))[key] = diff;
}
}
-void MainWindow::updateFilesList(Git::CommitItem const &commit, bool wait)
+/**
+ * @brief ファイルリストを更新
+ * @param id
+ * @param diff_list
+ * @param listwidget
+ */
+void MainWindow::updateFilesList2(RepositoryWrapperFrame *frame, QString const &id, QList<Git::Diff> *diff_list, QListWidget *listwidget)
+{
+ GitPtr g = git();
+ if (!isValidWorkingCopy(g)) return;
+
+ listwidget->clear();
+
+ auto AddItem = [&](ObjectData const &data){
+ QListWidgetItem *item = NewListWidgetFileItem(data);
+ listwidget->addItem(item);
+ };
+
+ GitDiff dm(getObjCache(frame));
+ if (!dm.diff(id, submodules(), diff_list)) return;
+
+ addDiffItems(diff_list, AddItem);
+}
+
+void MainWindow::updateFilesList(RepositoryWrapperFrame *frame, Git::CommitItem const &commit, bool wait)
{
QString id;
if (Git::isUncommited(commit)) {
// empty id for uncommited changes
} else {
id = commit.commit_id;
}
- updateFilesList(id, wait);
+ updateFilesList(frame, id, wait);
}
-void MainWindow::updateCurrentFilesList()
+void MainWindow::updateCurrentFilesList(RepositoryWrapperFrame *frame)
{
- auto logs = getLogs();
- QTableWidgetItem *item = ui->tableWidget_log->item(selectedLogIndex(), 0);
+ auto logs = getLogs(frame);
+ QTableWidgetItem *item = frame->logtablewidget()->item(selectedLogIndex(frame), 0);
if (!item) return;
- int row = item->data(IndexRole).toInt();
+ int index = item->data(IndexRole).toInt();
int count = (int)logs.size();
- if (row < count) {
- updateFilesList(logs[row], true);
+ if (index < count) {
+ updateFilesList(frame, logs[index], true);
}
}
-void MainWindow::prepareLogTableWidget()
-{
- QStringList cols = {
- tr("Graph"),
- tr("Commit"),
- tr("Date"),
- tr("Author"),
- tr("Message"),
- };
- int n = cols.size();
- ui->tableWidget_log->setColumnCount(n);
- ui->tableWidget_log->setRowCount(0);
- for (int i = 0; i < n; i++) {
- QString const &text = cols[i];
- auto *item = new QTableWidgetItem(text);
- ui->tableWidget_log->setHorizontalHeaderItem(i, item);
- }
-
- updateCommitGraph(); // コミットグラフを更新
-}
+//void MainWindow::prepareLogTableWidget(RepositoryWrapperFrame *frame)
+//{
+// frame->prepareLogTableWidget();
+//}
void MainWindow::detectGitServerType(GitPtr const &g)
{
setServerType(ServerType::Standard);
*ptrGitHub() = GitHubRepositoryInfo();
QString push_url;
QList<Git::Remote> remotes;
g->getRemoteURLs(&remotes);
for (Git::Remote const &r : remotes) {
if (r.purpose == "push") {
push_url = r.url;
}
}
auto Check = [&](QString const &s){
int i = push_url.indexOf(s);
if (i > 0) return i + s.size();
return 0;
};
// check GitHub
int pos = Check("@github.com:");
if (pos == 0) {
pos = Check("://github.com/");
}
if (pos > 0) {
int end = push_url.size();
{
QString s = ".git";
if (push_url.endsWith(s)) {
end -= s.size();
}
}
QString s = push_url.mid(pos, end - pos);
int i = s.indexOf('/');
if (i > 0) {
auto *p = ptrGitHub();
QString user = s.mid(0, i);
QString repo = s.mid(i + 1);
p->owner_account_name = user;
p->repository_name = repo;
}
setServerType(ServerType::GitHub);
}
}
-void MainWindow::clearLog()
+void MainWindow::clearLog(RepositoryWrapperFrame *frame)
{
- clearLogs();
- clearLabelMap();
+ clearLogs(frame);
+ clearLabelMap(frame);
setUncommitedChanges(false);
- ui->tableWidget_log->clearContents();
- ui->tableWidget_log->scrollToTop();
+ frame->clearLogContents();
}
void MainWindow::openRepository_(GitPtr g, bool keep_selection)
{
- getObjCache()->setup(g);
+ openRepository_(frame(), g, keep_selection);
+}
+
+void MainWindow::openRepository_(RepositoryWrapperFrame *frame, GitPtr g, bool keep_selection)
+{
+ getObjCache(frame)->setup(g);
int scroll_pos = -1;
int select_row = -1;
if (keep_selection) {
- scroll_pos = ui->tableWidget_log->verticalScrollBar()->value();
- select_row = ui->tableWidget_log->currentRow();
+ scroll_pos = frame->logtablewidget()->verticalScrollBar()->value();
+ select_row = frame->logtablewidget()->currentRow();
}
- if (isValidWorkingCopy(g)) {
+ if (isValidWorkingCopy(g), 1) { ///
bool do_fetch = isOnlineMode() && (getForceFetch() || appsettings()->automatically_fetch_when_opening_the_repository);
setForceFetch(false);
if (do_fetch) {
if (!fetch(g, false)) {
return;
}
}
- clearLog();
+ clearLog(frame);
clearRepositoryInfo();
detectGitServerType(g);
- updateFilesList(QString(), true);
+ updateFilesList(frame, QString(), true);
bool canceled = false;
- ui->tableWidget_log->setEnabled(false);
+ frame->logtablewidget()->setEnabled(false);
// ログを取得
- setLogs(retrieveCommitLog(g));
+ setLogs(frame, retrieveCommitLog(g));
// ブランチを取得
- queryBranches(g);
+ queryBranches(frame, g);
// タグを取得
- ptrTagMap()->clear();
+ ptrTagMap(frame)->clear();
QList<Git::Tag> tags = g->tags();
for (Git::Tag const &tag : tags) {
Git::Tag t = tag;
- t.id = getObjCache()->getCommitIdFromTag(t.id);
- (*ptrTagMap())[t.id].push_back(t);
+ t.id = getObjCache(frame)->getCommitIdFromTag(t.id);
+ (*ptrTagMap(frame))[t.id].push_back(t);
}
- ui->tableWidget_log->setEnabled(true);
- updateCommitTableLater();
+ frame->logtablewidget()->setEnabled(true);
+ updateCommitLogTableLater(frame, 100); // ミコットログを更新(100ms後)
if (canceled) return;
QString branch_name;
if (currentBranch().flags & Git::Branch::HeadDetachedAt) {
branch_name += QString("(HEAD detached at %1)").arg(currentBranchName());
}
if (currentBranch().flags & Git::Branch::HeadDetachedFrom) {
branch_name += QString("(HEAD detached from %1)").arg(currentBranchName());
}
if (branch_name.isEmpty()) {
branch_name = currentBranchName();
}
QString repo_name = currentRepositoryName();
setRepositoryInfo(repo_name, branch_name);
} else {
- clearLog();
+ clearLog(frame);
clearRepositoryInfo();
}
if (!g) return;
updateRemoteInfo();
updateWindowTitle(g);
- setHeadId(getObjCache()->revParse("HEAD"));
+ setHeadId(getObjCache(frame)->revParse("HEAD"));
if (isThereUncommitedChanges()) {
Git::CommitItem item;
item.parent_ids.push_back(currentBranch().id);
item.message = tr("Uncommited changes");
- auto p = getLogsPtr();
+ auto p = getLogsPtr(frame);
p->insert(p->begin(), item);
}
- prepareLogTableWidget();
+ frame->prepareLogTableWidget();
- auto const &logs = getLogs();
+ auto const &logs = getLogs(frame);
const int count = logs.size();
- ui->tableWidget_log->setRowCount(count);
+ frame->logtablewidget()->setRowCount(count);
int selrow = 0;
for (int row = 0; row < count; row++) {
Git::CommitItem const *commit = &logs[row];
{
auto *item = new QTableWidgetItem;
item->setData(IndexRole, row);
- ui->tableWidget_log->setItem(row, 0, item);
+ frame->logtablewidget()->setItem(row, 0, item);
}
int col = 1; // カラム0はコミットグラフなので、その次から
auto AddColumn = [&](QString const &text, bool bold, QString const &tooltip){
auto *item = new QTableWidgetItem(text);
if (!tooltip.isEmpty()) {
QString tt = tooltip;
tt.replace('\n', ' ');
tt = tt.toHtmlEscaped();
tt = "<p style='white-space: pre'>" + tt + "</p>";
item->setToolTip(tt);
}
if (bold) {
QFont font = item->font();
font.setBold(true);
item->setFont(font);
}
- ui->tableWidget_log->setItem(row, col, item);
+ frame->logtablewidget()->setItem(row, col, item);
col++;
};
QString commit_id;
QString datetime;
QString author;
QString message;
QString message_ex;
bool isHEAD = (commit->commit_id == getHeadId());
bool bold = false;
{
if (Git::isUncommited(*commit)) { // 未コミットの時
bold = true; // 太字
selrow = row;
} else {
if (isHEAD && !isThereUncommitedChanges()) { // HEADで、未コミットがないとき
bold = true; // 太字
selrow = row;
}
commit_id = abbrevCommitID(*commit);
}
datetime = misc::makeDateTimeString(commit->commit_date);
author = commit->author;
message = commit->message;
- message_ex = makeCommitInfoText(row, &(*getLabelMap())[row]);
+ message_ex = makeCommitInfoText(frame, row, &(*getLabelMap(frame))[row]);
}
AddColumn(commit_id, false, QString());
AddColumn(datetime, false, QString());
AddColumn(author, false, QString());
AddColumn(message, bold, message + message_ex);
- ui->tableWidget_log->setRowHeight(row, 24);
+ frame->logtablewidget()->setRowHeight(row, 24);
}
- int t = ui->tableWidget_log->columnWidth(0);
- ui->tableWidget_log->resizeColumnsToContents();
- ui->tableWidget_log->setColumnWidth(0, t);
- ui->tableWidget_log->horizontalHeader()->setStretchLastSection(false);
- ui->tableWidget_log->horizontalHeader()->setStretchLastSection(true);
+ int t = frame->logtablewidget()->columnWidth(0);
+ frame->logtablewidget()->resizeColumnsToContents();
+ frame->logtablewidget()->setColumnWidth(0, t);
+ frame->logtablewidget()->horizontalHeader()->setStretchLastSection(false);
+ frame->logtablewidget()->horizontalHeader()->setStretchLastSection(true);
- m->last_focused_file_list = nullptr;
+ m2->last_focused_file_list = nullptr;
- ui->tableWidget_log->setFocus();
+ frame->logtablewidget()->setFocus();
if (select_row < 0) {
- setCurrentLogRow(selrow);
+ setCurrentLogRow(frame, selrow);
} else {
- setCurrentLogRow(select_row);
- ui->tableWidget_log->verticalScrollBar()->setValue(scroll_pos >= 0 ? scroll_pos : 0);
+ setCurrentLogRow(frame, select_row);
+ frame->logtablewidget()->verticalScrollBar()->setValue(scroll_pos >= 0 ? scroll_pos : 0);
}
- m->remote_watcher.setCurrent(currentRemoteName(), currentBranchName());
-
updateUI();
}
void MainWindow::removeSelectedRepositoryFromBookmark(bool ask)
{
int i = indexOfRepository(ui->treeWidget_repos->currentItem());
removeRepositoryFromBookmark(i, ask);
}
void MainWindow::setNetworkingCommandsEnabled(bool enabled)
{
ui->action_clone->setEnabled(enabled);
ui->toolButton_clone->setEnabled(enabled);
if (!Git::isValidWorkingCopy(currentWorkingCopyDir())) {
enabled = false;
}
bool opened = !currentRepository().name.isEmpty();
ui->action_fetch->setEnabled(enabled || opened);
ui->toolButton_fetch->setEnabled(enabled || opened);
if (isOnlineMode()) {
ui->action_fetch->setText(tr("Fetch"));
ui->toolButton_fetch->setText(tr("Fetch"));
} else {
ui->action_fetch->setText(tr("Update"));
ui->toolButton_fetch->setText(tr("Update"));
}
ui->action_fetch_prune->setEnabled(enabled);
ui->action_pull->setEnabled(enabled);
ui->action_push->setEnabled(enabled);
ui->action_push_u->setEnabled(enabled);
ui->action_push_all_tags->setEnabled(enabled);
ui->toolButton_pull->setEnabled(enabled);
ui->toolButton_push->setEnabled(enabled);
}
void MainWindow::updateUI()
{
setNetworkingCommandsEnabled(isOnlineMode());
ui->toolButton_fetch->setDot(getRemoteChanged());
Git::Branch b = currentBranch();
ui->toolButton_push->setNumber(b.ahead > 0 ? b.ahead : -1);
ui->toolButton_pull->setNumber(b.behind > 0 ? b.behind : -1);
{
bool f = isRepositoryOpened();
ui->toolButton_status->setEnabled(f);
ui->toolButton_terminal->setEnabled(f);
ui->toolButton_explorer->setEnabled(f);
ui->action_repository_status->setEnabled(f);
ui->action_terminal->setEnabled(f);
ui->action_explorer->setEnabled(f);
}
}
-void MainWindow::updateStatusBarText()
+void MainWindow::updateStatusBarText(RepositoryWrapperFrame *frame)
{
QString text;
QWidget *w = qApp->focusWidget();
if (w == ui->treeWidget_repos) {
RepositoryItem const *repo = selectedRepositoryItem();
if (repo) {
text = QString("%1 : %2")
.arg(repo->name)
.arg(misc::normalizePathSeparator(repo->local_dir))
;
}
- } else if (w == ui->tableWidget_log) {
- QTableWidgetItem *item = ui->tableWidget_log->item(selectedLogIndex(), 0);
+ } else if (w == frame->logtablewidget()) {
+ QTableWidgetItem *item = frame->logtablewidget()->item(selectedLogIndex(frame), 0);
if (item) {
- auto const &logs = getLogs();
+ auto const &logs = getLogs(frame);
int row = item->data(IndexRole).toInt();
if (row < (int)logs.size()) {
Git::CommitItem const &commit = logs[row];
if (Git::isUncommited(commit)) {
text = tr("Uncommited changes");
} else {
QString id = commit.commit_id;
text = QString("%1 : %2%3")
.arg(id.mid(0, 7))
.arg(commit.message)
- .arg(makeCommitInfoText(row, nullptr))
+ .arg(makeCommitInfoText(frame, row, nullptr))
;
}
}
}
}
setStatusBarText(text);
}
-void MainWindow::mergeBranch(QString const &commit, Git::MergeFastForward ff)
+void MainWindow::mergeBranch(QString const &commit, Git::MergeFastForward ff, bool squash)
{
if (commit.isEmpty()) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
- g->mergeBranch(commit, ff);
+ g->mergeBranch(commit, ff, squash);
openRepository(true);
}
-void MainWindow::mergeBranch(Git::CommitItem const *commit, Git::MergeFastForward ff)
+void MainWindow::mergeBranch(Git::CommitItem const *commit, Git::MergeFastForward ff, bool squash)
{
if (!commit) return;
- mergeBranch(commit->commit_id, ff);
+ mergeBranch(commit->commit_id, ff, squash);
}
void MainWindow::rebaseBranch(Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QString text = tr("Are you sure you want to rebase the commit ?");
text += "\n\n";
text += "> git rebase " + commit->commit_id;
int r = QMessageBox::information(this, tr("Rebase"), text, QMessageBox::Ok, QMessageBox::Cancel);
if (r == QMessageBox::Ok) {
g->rebaseBranch(commit->commit_id);
openRepository(true);
}
}
void MainWindow::cherrypick(Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
int n = commit->parent_ids.size();
if (n == 1) {
g->cherrypick(commit->commit_id);
} else if (n > 1) {
Git::CommitItem head;
Git::CommitItem pick;
g->queryCommit(g->rev_parse("HEAD"), &head);
g->queryCommit(commit->commit_id, &pick);
QList<Git::CommitItem> parents;
for (int i = 0; i < n; i++) {
QString id = commit->commit_id + QString("^%1").arg(i + 1);
id = g->rev_parse(id);
Git::CommitItem item;
g->queryCommit(id, &item);
parents.push_back(item);
}
CherryPickDialog dlg(this, head, pick, parents);
if (dlg.exec() == QDialog::Accepted) {
QString cmd = "-m %1 ";
cmd = cmd.arg(dlg.number());
if (dlg.allowEmpty()) {
cmd += "--allow-empty ";
}
cmd += commit->commit_id;
g->cherrypick(cmd);
} else {
return;
}
}
openRepository(true);
}
-void MainWindow::merge(Git::CommitItem const *commit)
+void MainWindow::merge(RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
{
if (isThereUncommitedChanges()) return;
if (!commit) {
- int row = selectedLogIndex();
- commit = commitItem(row);
+ int row = selectedLogIndex(frame);
+ commit = commitItem(frame, row);
if (!commit) return;
}
if (!Git::isValidID(commit->commit_id)) return;
static const char MergeFastForward[] = "MergeFastForward";
QString fastforward;
{
MySettings s;
s.beginGroup("Behavior");
fastforward = s.value(MergeFastForward).toString();
s.endGroup();
}
std::vector<QString> labels;
{
- int row = selectedLogIndex();
- QList<Label> const *v = label(row);
- for (Label const &label : *v) {
- if (label.kind == Label::LocalBranch || label.kind == Label::Tag) {
+ int row = selectedLogIndex(frame);
+ QList<BranchLabel> const *v = label(frame, row);
+ for (BranchLabel const &label : *v) {
+ if (label.kind == BranchLabel::LocalBranch || label.kind == BranchLabel::Tag) {
labels.push_back(label.text);
}
}
std::sort(labels.begin(), labels.end());
labels.erase(std::unique(labels.begin(), labels.end()), labels.end());
}
labels.push_back(commit->commit_id);
QString branch_name = currentBranchName();
MergeDialog dlg(fastforward, labels, branch_name, this);
if (dlg.exec() == QDialog::Accepted) {
fastforward = dlg.getFastForwardPolicy();
+ bool squash = dlg.isSquashEnabled();
{
MySettings s;
s.beginGroup("Behavior");
s.setValue(MergeFastForward, fastforward);
s.endGroup();
}
QString from = dlg.mergeFrom();
- mergeBranch(from, MergeDialog::ff(fastforward));
+ mergeBranch(from, MergeDialog::ff(fastforward), squash);
}
}
void MainWindow::showStatus()
{
auto g = git();
if (!g->isValidWorkingCopy()) {
msgNoRepositorySelected();
return;
}
QString s = g->status();
TextEditDialog dlg(this);
dlg.setWindowTitle(tr("Status"));
dlg.setText(s, true);
dlg.exec();
}
void MainWindow::on_action_commit_triggered()
{
- commit();
+ commit(frame());
}
void MainWindow::on_action_fetch_triggered()
{
if (isOnlineMode()) {
reopenRepository(true, [&](GitPtr g){
fetch(g, false);
});
} else {
updateRepository();
}
}
void MainWindow::on_action_fetch_prune_triggered()
{
if (!isOnlineMode()) return;
reopenRepository(true, [&](GitPtr g){
fetch(g, true);
});
}
void MainWindow::on_action_push_triggered()
{
push();
}
void MainWindow::on_action_pull_triggered()
{
if (!isOnlineMode()) return;
reopenRepository(true, [&](GitPtr g){
setPtyCondition(PtyCondition::Pull);
setPtyProcessOk(true);
g->pull(getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
});
}
void MainWindow::on_toolButton_push_clicked()
{
ui->action_push->trigger();
}
void MainWindow::on_toolButton_pull_clicked()
{
ui->action_pull->trigger();
}
void MainWindow::on_toolButton_status_clicked()
{
showStatus();
}
void MainWindow::on_action_repository_status_triggered()
{
showStatus();
}
void MainWindow::on_treeWidget_repos_currentItemChanged(QTreeWidgetItem * /*current*/, QTreeWidgetItem * /*previous*/)
{
- updateStatusBarText();
+ updateStatusBarText(frame());
}
void MainWindow::on_treeWidget_repos_itemDoubleClicked(QTreeWidgetItem * /*item*/, int /*column*/)
{
openSelectedRepository();
}
-void BasicMainWindow::execCommitPropertyDialog(QWidget *parent, Git::CommitItem const *commit)
+void MainWindow::execCommitPropertyDialog(QWidget *parent, RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
{
- CommitPropertyDialog dlg(parent, this, commit);
+ CommitPropertyDialog dlg(parent, this, frame, commit);
dlg.exec();
}
int MainWindow::indexOfRepository(QTreeWidgetItem const *treeitem) const
{
if (!treeitem) return -1;
return treeitem->data(0, IndexRole).toInt();
}
void MainWindow::on_treeWidget_repos_customContextMenuRequested(const QPoint &pos)
{
QTreeWidgetItem *treeitem = ui->treeWidget_repos->currentItem();
if (!treeitem) return;
RepositoryItem const *repo = repositoryItem(treeitem);
int index = indexOfRepository(treeitem);
if (isGroupItem(treeitem)) { // group item
QMenu menu;
QAction *a_add_new_group = menu.addAction(tr("&Add new group"));
QAction *a_delete_group = menu.addAction(tr("&Delete group"));
QAction *a_rename_group = menu.addAction(tr("&Rename group"));
QPoint pt = ui->treeWidget_repos->mapToGlobal(pos);
QAction *a = menu.exec(pt + QPoint(8, -8));
if (a) {
if (a == a_add_new_group) {
QTreeWidgetItem *child = newQTreeWidgetFolderItem(tr("New group"));
treeitem->addChild(child);
child->setFlags(child->flags() | Qt::ItemIsEditable);
ui->treeWidget_repos->setCurrentItem(child);
return;
}
if (a == a_delete_group) {
QTreeWidgetItem *parent = treeitem->parent();
if (parent) {
int i = parent->indexOfChild(treeitem);
delete parent->takeChild(i);
} else {
int i = ui->treeWidget_repos->indexOfTopLevelItem(treeitem);
delete ui->treeWidget_repos->takeTopLevelItem(i);
}
refrectRepositories();
return;
}
if (a == a_rename_group) {
ui->treeWidget_repos->editItem(treeitem);
return;
}
}
} else if (repo) { // repository item
QString open_terminal = tr("Open &terminal");
QString open_commandprompt = tr("Open command promp&t");
QMenu menu;
QAction *a_open = menu.addAction(tr("&Open"));
menu.addSeparator();
#ifdef Q_OS_WIN
QAction *a_open_terminal = menu.addAction(open_commandprompt);
(void)open_terminal;
#else
QAction *a_open_terminal = menu.addAction(open_terminal);
(void)open_commandprompt;
#endif
a_open_terminal->setIcon(QIcon(":/image/terminal.svg"));
QAction *a_open_folder = menu.addAction(tr("Open &folder"));
a_open_folder->setIcon(QIcon(":/image/explorer.svg"));
menu.addSeparator();
QAction *a_remove = menu.addAction(tr("&Remove"));
menu.addSeparator();
QAction *a_properties = addMenuActionProperty(&menu);
QPoint pt = ui->treeWidget_repos->mapToGlobal(pos);
QAction *a = menu.exec(pt + QPoint(8, -8));
if (a) {
if (a == a_open) {
openSelectedRepository();
return;
}
if (a == a_open_folder) {
openExplorer(repo);
return;
}
if (a == a_open_terminal) {
openTerminal(repo);
return;
}
if (a == a_remove) {
removeRepositoryFromBookmark(index, true);
return;
}
if (a == a_properties) {
- execRepositoryPropertyDialog(repo->local_dir);
+ execRepositoryPropertyDialog(*repo);
return;
}
}
}
}
void MainWindow::on_tableWidget_log_customContextMenuRequested(const QPoint &pos)
{
- int row = selectedLogIndex();
- Git::CommitItem const *commit = commitItem(row);
+ int row = selectedLogIndex(frame());
+ Git::CommitItem const *commit = commitItem(frame(), row);
if (commit) {
bool is_valid_commit_id = Git::isValidID(commit->commit_id);
QMenu menu;
QAction *a_copy_id_7letters = is_valid_commit_id ? menu.addAction(tr("Copy commit id (7 letters)")) : nullptr;
QAction *a_copy_id_complete = is_valid_commit_id ? menu.addAction(tr("Copy commit id (completely)")) : nullptr;
std::set<QAction *> copy_label_actions;
{
- QList<BasicMainWindow::Label> v = sortedLabels(row);
+ QList<BranchLabel> v = sortedLabels(frame(), row);
if (!v.isEmpty()) {
auto *copy_lebel_menu = menu.addMenu("Copy label");
- for (BasicMainWindow::Label const &l : v) {
+ for (BranchLabel const &l : v) {
QAction *a = copy_lebel_menu->addAction(l.text);
copy_label_actions.insert(copy_label_actions.end(), a);
}
}
}
menu.addSeparator();
QAction *a_checkout = menu.addAction(tr("Checkout/Branch..."));
menu.addSeparator();
QAction *a_edit_message = nullptr;
auto canEditMessage = [&](){
if (commit->has_child) return false; // 子がないこと
if (Git::isUncommited(*commit)) return false; // 未コミットがないこと
bool is_head = false;
bool has_remote_branch = false;
- QList<Label> const *labels = label(row);
- for (const Label &label : *labels) {
- if (label.kind == Label::Head) {
+ QList<BranchLabel> const *labels = label(frame(), row);
+ for (const BranchLabel &label : *labels) {
+ if (label.kind == BranchLabel::Head) {
is_head = true;
- } else if (label.kind == Label::RemoteBranch) {
+ } else if (label.kind == BranchLabel::RemoteBranch) {
has_remote_branch = true;
}
}
return is_head && !has_remote_branch; // HEAD && リモートブランチ無し
};
if (canEditMessage()) {
a_edit_message = menu.addAction(tr("Edit message..."));
}
QAction *a_merge = is_valid_commit_id ? menu.addAction(tr("Merge")) : nullptr;
QAction *a_rebase = is_valid_commit_id ? menu.addAction(tr("Rebase")) : nullptr;
QAction *a_cherrypick = is_valid_commit_id ? menu.addAction(tr("Cherry-pick")) : nullptr;
QAction *a_edit_tags = is_valid_commit_id ? menu.addAction(tr("Edit tags...")) : nullptr;
QAction *a_revert = is_valid_commit_id ? menu.addAction(tr("Revert")) : nullptr;
menu.addSeparator();
QAction *a_delbranch = is_valid_commit_id ? menu.addAction(tr("Delete branch...")) : nullptr;
- QAction *a_delrembranch = remoteBranches(commit->commit_id, nullptr).isEmpty() ? nullptr : menu.addAction(tr("Delete remote branch..."));
+ QAction *a_delrembranch = remoteBranches(frame(), commit->commit_id, nullptr).isEmpty() ? nullptr : menu.addAction(tr("Delete remote branch..."));
menu.addSeparator();
QAction *a_explore = is_valid_commit_id ? menu.addAction(tr("Explore")) : nullptr;
QAction *a_properties = addMenuActionProperty(&menu);
- QAction *a = menu.exec(ui->tableWidget_log->viewport()->mapToGlobal(pos) + QPoint(8, -8));
+ QAction *a = menu.exec(frame()->logtablewidget()->viewport()->mapToGlobal(pos) + QPoint(8, -8));
if (a) {
if (a == a_copy_id_7letters) {
qApp->clipboard()->setText(commit->commit_id.mid(0, 7));
return;
}
if (a == a_copy_id_complete) {
qApp->clipboard()->setText(commit->commit_id);
return;
}
if (a == a_properties) {
- execCommitPropertyDialog(this, commit);
+ execCommitPropertyDialog(this, frame(), commit);
return;
}
if (a == a_edit_message) {
- commitAmend();
+ commitAmend(frame());
return;
}
if (a == a_checkout) {
- checkout(this, commit);
+ checkout(frame(), this, commit);
return;
}
if (a == a_delbranch) {
- deleteBranch(commit);
+ deleteBranch(frame(), commit);
return;
}
if (a == a_delrembranch) {
- deleteRemoteBranch(commit);
+ deleteRemoteBranch(frame(), commit);
return;
}
if (a == a_merge) {
- merge(commit);
+ merge(frame(), commit);
return;
}
if (a == a_rebase) {
rebaseBranch(commit);
return;
}
if (a == a_cherrypick) {
cherrypick(commit);
return;
}
if (a == a_edit_tags) {
ui->action_edit_tags->trigger();
return;
}
if (a == a_revert) {
- revertCommit();
+ revertCommit(frame());
return;
}
if (a == a_explore) {
- execCommitExploreWindow(this, commit);
+ execCommitExploreWindow(frame(), this, commit);
return;
}
if (copy_label_actions.find(a) != copy_label_actions.end()) {
QString text = a->text();
QApplication::clipboard()->setText(text);
return;
}
}
}
}
void MainWindow::on_listWidget_files_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QMenu menu;
QAction *a_delete = menu.addAction(tr("Delete"));
QAction *a_untrack = menu.addAction(tr("Untrack"));
QAction *a_history = menu.addAction(tr("History"));
QAction *a_blame = menu.addAction(tr("Blame"));
QAction *a_properties = addMenuActionProperty(&menu);
- QPoint pt = ui->listWidget_files->mapToGlobal(pos) + QPoint(8, -8);
+ QPoint pt = frame()->fileslistwidget()->mapToGlobal(pos) + QPoint(8, -8);
QAction *a = menu.exec(pt);
if (a) {
- QListWidgetItem *item = ui->listWidget_files->currentItem();
+ QListWidgetItem *item = frame()->fileslistwidget()->currentItem();
if (a == a_delete) {
if (askAreYouSureYouWantToRun("Delete", tr("Delete selected files."))) {
for_each_selected_files([&](QString const &path){
g->removeFile(path);
g->chdirexec([&](){
QFile(path).remove();
return true;
});
});
openRepository(false);
}
} else if (a == a_untrack) {
if (askAreYouSureYouWantToRun("Untrack", tr("rm --cached files"))) {
for_each_selected_files([&](QString const &path){
g->rm_cached(path);
});
openRepository(false);
}
} else if (a == a_history) {
execFileHistory(item);
} else if (a == a_blame) {
blame(item);
} else if (a == a_properties) {
- execFilePropertyDialog(item);
+ showObjectProperty(item);
}
}
}
void MainWindow::on_listWidget_unstaged_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
- QList<QListWidgetItem *> items = ui->listWidget_unstaged->selectedItems();
+ QList<QListWidgetItem *> items = frame()->unstagedFileslistwidget()->selectedItems();
if (!items.isEmpty()) {
QMenu menu;
QAction *a_stage = menu.addAction(tr("Stage"));
QAction *a_reset_file = menu.addAction(tr("Reset"));
QAction *a_ignore = menu.addAction(tr("Ignore"));
QAction *a_delete = menu.addAction(tr("Delete"));
QAction *a_untrack = menu.addAction(tr("Untrack"));
QAction *a_history = menu.addAction(tr("History"));
QAction *a_blame = menu.addAction(tr("Blame"));
QAction *a_properties = addMenuActionProperty(&menu);
- QPoint pt = ui->listWidget_unstaged->mapToGlobal(pos) + QPoint(8, -8);
+ QPoint pt = frame()->unstagedFileslistwidget()->mapToGlobal(pos) + QPoint(8, -8);
QAction *a = menu.exec(pt);
if (a) {
- QListWidgetItem *item = ui->listWidget_unstaged->currentItem();
+ QListWidgetItem *item = frame()->unstagedFileslistwidget()->currentItem();
if (a == a_stage) {
for_each_selected_files([&](QString const &path){
g->stage(path);
});
- updateCurrentFilesList();
+ updateCurrentFilesList(frame());
return;
}
if (a == a_reset_file) {
QStringList paths;
for_each_selected_files([&](QString const &path){
paths.push_back(path);
});
resetFile(paths);
return;
}
if (a == a_ignore) {
QString gitignore_path = currentWorkingCopyDir() / ".gitignore";
if (items.size() == 1) {
QString file = getFilePath(items[0]);
EditGitIgnoreDialog dlg(this, gitignore_path, file);
if (dlg.exec() == QDialog::Accepted) {
QString appending = dlg.text();
if (!appending.isEmpty()) {
QString text;
QString path = gitignore_path;
path.replace('/', QDir::separator());
{
QFile file(path);
if (file.open(QFile::ReadOnly)) {
text += QString::fromUtf8(file.readAll());
}
}
size_t n = text.size();
if (n > 0 && text[(int)n - 1] != '\n') {
text += '\n'; // 最後に改行を追加
}
text += appending + '\n';
{
QFile file(path);
if (file.open(QFile::WriteOnly)) {
file.write(text.toUtf8());
}
}
- updateCurrentFilesList();
+ updateCurrentFilesList(frame());
return;
}
} else {
return;
}
}
QString append;
for_each_selected_files([&](QString const &path){
if (path == ".gitignore") {
// skip
} else {
append += path + '\n';
}
});
if (TextEditDialog::editFile(this, gitignore_path, ".gitignore", append)) {
- updateCurrentFilesList();
+ updateCurrentFilesList(frame());
}
return;
}
if (a == a_delete) {
if (askAreYouSureYouWantToRun("Delete", "Delete selected files.")) {
for_each_selected_files([&](QString const &path){
g->removeFile(path);
g->chdirexec([&](){
QFile(path).remove();
return true;
});
});
openRepository(false);
}
return;
}
if (a == a_untrack) {
if (askAreYouSureYouWantToRun("Untrack", "rm --cached")) {
for_each_selected_files([&](QString const &path){
g->rm_cached(path);
});
openRepository(false);
}
return;
}
if (a == a_history) {
execFileHistory(item);
return;
}
if (a == a_blame) {
blame(item);
return;
}
if (a == a_properties) {
- execFilePropertyDialog(item);
+ showObjectProperty(item);
return;
}
}
}
}
void MainWindow::on_listWidget_staged_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
- QListWidgetItem *item = ui->listWidget_staged->currentItem();
+ QListWidgetItem *item = frame()->stagedFileslistwidget()->currentItem();
if (item) {
QString path = getFilePath(item);
QString fullpath = currentWorkingCopyDir() / path;
if (QFileInfo(fullpath).isFile()) {
QMenu menu;
QAction *a_unstage = menu.addAction(tr("Unstage"));
QAction *a_history = menu.addAction(tr("History"));
QAction *a_blame = menu.addAction(tr("Blame"));
QAction *a_properties = addMenuActionProperty(&menu);
- QPoint pt = ui->listWidget_staged->mapToGlobal(pos) + QPoint(8, -8);
+ QPoint pt = frame()->stagedFileslistwidget()->mapToGlobal(pos) + QPoint(8, -8);
QAction *a = menu.exec(pt);
if (a) {
- QListWidgetItem *item = ui->listWidget_unstaged->currentItem();
+ QListWidgetItem *item = frame()->unstagedFileslistwidget()->currentItem();
if (a == a_unstage) {
g->unstage(path);
openRepository(false);
} else if (a == a_history) {
execFileHistory(item);
} else if (a == a_blame) {
blame(item);
} else if (a == a_properties) {
- execFilePropertyDialog(item);
+ showObjectProperty(item);
}
}
}
}
}
QStringList MainWindow::selectedFiles_(QListWidget *listwidget) const
{
QStringList list;
QList<QListWidgetItem *> items = listwidget->selectedItems();
for (QListWidgetItem *item : items) {
QString path = getFilePath(item);
list.push_back(path);
}
return list;
}
QStringList MainWindow::selectedFiles() const
{
- if (m->last_focused_file_list == ui->listWidget_files) return selectedFiles_(ui->listWidget_files);
- if (m->last_focused_file_list == ui->listWidget_staged) return selectedFiles_(ui->listWidget_staged);
- if (m->last_focused_file_list == ui->listWidget_unstaged) return selectedFiles_(ui->listWidget_unstaged);
+ if (m2->last_focused_file_list == ui->listWidget_files) return selectedFiles_(ui->listWidget_files);
+ if (m2->last_focused_file_list == ui->listWidget_staged) return selectedFiles_(ui->listWidget_staged);
+ if (m2->last_focused_file_list == ui->listWidget_unstaged) return selectedFiles_(ui->listWidget_unstaged);
return QStringList();
}
void MainWindow::for_each_selected_files(std::function<void(QString const&)> const &fn)
{
for (QString const &path : selectedFiles()) {
fn(path);
}
}
-void BasicMainWindow::execFileHistory(QListWidgetItem *item)
+void MainWindow::execFileHistory(QListWidgetItem *item)
{
if (item) {
QString path = getFilePath(item);
if (!path.isEmpty()) {
execFileHistory(path);
}
}
}
-void MainWindow::doLogCurrentItemChanged()
+/**
+ * @brief オブジェクトプロパティ
+ * @param item
+ */
+void MainWindow::showObjectProperty(QListWidgetItem *item)
+{
+ if (item) {
+ QString submodpath = getSubmodulePath(item);
+ if (!submodpath.isEmpty()) {
+ // サブモジュールウィンドウを表示する
+ Git::SubmoduleItem submod;
+ submod.path = submodpath;
+ submod.id = getObjectID(item);
+ if (submod) {
+ OverrideWaitCursor;
+ GitPtr g = git(submod);
+ SubmoduleMainWindow *w = new SubmoduleMainWindow(this, g);
+ w->show();
+ w->reset();
+ }
+ } else {
+ // ファイルプロパティダイアログを表示する
+ QString path = getFilePath(item);
+ QString id = getObjectID(item);
+ FilePropertyDialog dlg(this);
+ dlg.exec(this, path, id);
+ }
+ }
+}
+
+void MainWindow::checkout(RepositoryWrapperFrame *frame, QWidget *parent, const Git::CommitItem *commit, std::function<void ()> accepted_callback)
{
- clearFileList();
+ if (!commit) return;
- int row = selectedLogIndex();
- QTableWidgetItem *item = ui->tableWidget_log->item(row, 0);
+ GitPtr g = git();
+ if (!isValidWorkingCopy(g)) return;
+
+ QStringList tags;
+ QStringList all_local_branches;
+ QStringList local_branches;
+ QStringList remote_branches;
+ {
+ NamedCommitList named_commits = namedCommitItems(frame, Branches | Tags | Remotes);
+ for (NamedCommitItem const &item : named_commits) {
+ QString name = item.name;
+ if (item.id == commit->commit_id) {
+ if (item.type == NamedCommitItem::Type::Tag) {
+ tags.push_back(name);
+ } else if (item.type == NamedCommitItem::Type::BranchLocal || item.type == NamedCommitItem::Type::BranchRemote) {
+ int i = name.lastIndexOf('/');
+ if (i < 0 && name == "HEAD") continue;
+ if (i > 0 && name.mid(i + 1) == "HEAD") continue;
+ if (item.type == NamedCommitItem::Type::BranchLocal) {
+ local_branches.push_back(name);
+ } else if (item.type == NamedCommitItem::Type::BranchRemote) {
+ remote_branches.push_back(name);
+ }
+ }
+ }
+ if (item.type == NamedCommitItem::Type::BranchLocal) {
+ all_local_branches.push_back(name);
+ }
+ }
+ }
+
+ CheckoutDialog dlg(parent, tags, all_local_branches, local_branches, remote_branches);
+ if (dlg.exec() == QDialog::Accepted) {
+ if (accepted_callback) {
+ accepted_callback();
+ }
+ CheckoutDialog::Operation op = dlg.operation();
+ QString name = dlg.branchName();
+ QString id = commit->commit_id;
+ if (id.isEmpty() && !commit->parent_ids.isEmpty()) {
+ id = commit->parent_ids.front();
+ }
+ bool ok = false;
+ setLogEnabled(g, true);
+ if (op == CheckoutDialog::Operation::HeadDetached) {
+ if (!id.isEmpty()) {
+ ok = g->git(QString("checkout \"%1\"").arg(id), true);
+ }
+ } else if (op == CheckoutDialog::Operation::CreateLocalBranch) {
+ if (!name.isEmpty() && !id.isEmpty()) {
+ ok = g->git(QString("checkout -b \"%1\" \"%2\"").arg(name).arg(id), true);
+ }
+ } else if (op == CheckoutDialog::Operation::ExistingLocalBranch) {
+ if (!name.isEmpty()) {
+ ok = g->git(QString("checkout \"%1\"").arg(name), true);
+ }
+ }
+ if (ok) {
+ openRepository(true);
+ }
+ }
+}
+
+void MainWindow::checkout(RepositoryWrapperFrame *frame)
+{
+ checkout(frame, this, selectedCommitItem(frame));
+}
+
+/**
+ * @brief コミットログの選択が変化した
+ */
+void MainWindow::doLogCurrentItemChanged(RepositoryWrapperFrame *frame)
+{
+ clearFileList(frame);
+
+ int row = selectedLogIndex(frame);
+ QTableWidgetItem *item = frame->logtablewidget()->item(row, 0);
if (item) {
- auto const &logs = getLogs();
- int row = item->data(IndexRole).toInt();
- if (row < (int)logs.size()) {
- updateStatusBarText();
- *ptrUpdateFilesListCounter() = 200;
+ auto const &logs = getLogs(frame);
+ int index = item->data(IndexRole).toInt();
+ if (index < (int)logs.size()) {
+ // ステータスバー更新
+ updateStatusBarText(frame);
+ // 少し待ってファイルリストを更新する
+ postUserFunctionEvent([&](QVariant const &, void *p){
+ RepositoryWrapperFrame *frame = reinterpret_cast<RepositoryWrapperFrame *>(p);
+ updateCurrentFilesList(frame);
+ }, {}, reinterpret_cast<void *>(frame), 300); // 300ms後(キーボードのオートリピート想定)
}
} else {
row = -1;
}
- updateAncestorCommitMap();
- ui->tableWidget_log->viewport()->update();
+ updateAncestorCommitMap(frame);
+ frame->logtablewidget()->viewport()->update();
}
-void MainWindow::findNext()
+void MainWindow::findNext(RepositoryWrapperFrame *frame)
{
- if (m->search_text.isEmpty()) {
+ if (m2->search_text.isEmpty()) {
return;
}
- auto const &logs = getLogs();
+ auto const &logs = getLogs(frame);
for (int pass = 0; pass < 2; pass++) {
int row = 0;
if (pass == 0) {
- row = selectedLogIndex();
+ row = selectedLogIndex(frame);
if (row < 0) {
row = 0;
- } else if (m->searching) {
+ } else if (m2->searching) {
row++;
}
}
while (row < (int)logs.size()) {
Git::CommitItem const commit = logs[row];
if (!Git::isUncommited(commit)) {
- if (commit.message.indexOf(m->search_text, 0, Qt::CaseInsensitive) >= 0) {
- bool b = ui->tableWidget_log->blockSignals(true);
- setCurrentLogRow(row);
- ui->tableWidget_log->blockSignals(b);
- m->searching = true;
+ if (commit.message.indexOf(m2->search_text, 0, Qt::CaseInsensitive) >= 0) {
+ bool b = frame->logtablewidget()->blockSignals(true);
+ setCurrentLogRow(frame, row);
+ frame->logtablewidget()->blockSignals(b);
+ m2->searching = true;
return;
}
}
row++;
}
}
}
void MainWindow::findText(QString const &text)
{
- m->search_text = text;
+ m2->search_text = text;
}
bool MainWindow::isAncestorCommit(QString const &id)
{
- auto it = m->ancestors.find(id);
- return it != m->ancestors.end();
+ auto it = m2->ancestors.find(id);
+ return it != m2->ancestors.end();
}
-void MainWindow::updateAncestorCommitMap()
+void MainWindow::updateAncestorCommitMap(RepositoryWrapperFrame *frame)
{
- m->ancestors.clear();
+ m2->ancestors.clear();
- auto const &logs = getLogs();
+ auto const &logs = getLogs(frame);
const size_t LogCount = logs.size();
- const size_t index = selectedLogIndex();
+ const size_t index = selectedLogIndex(frame);
if (index < LogCount) {
// ok
} else {
return;
}
- auto *logsp = getLogsPtr();
+ auto *logsp = getLogsPtr(frame);
auto LogItem = [&](size_t i)->Git::CommitItem &{ return logsp->at(i); };
std::map<QString, size_t> commit_to_index_map;
size_t end = LogCount;
if (index < end) {
for (size_t i = index; i < end; i++) {
Git::CommitItem const &commit = LogItem(i);
commit_to_index_map[commit.commit_id] = i;
- auto *item = ui->tableWidget_log->item(i, 0);
- QRect r = ui->tableWidget_log->visualItemRect(item);
- if (r.y() >= ui->tableWidget_log->height()) {
+ auto *item = frame->logtablewidget()->item(i, 0);
+ QRect r = frame->logtablewidget()->visualItemRect(item);
+ if (r.y() >= frame->logtablewidget()->height()) {
end = i + 1;
break;
}
}
}
Git::CommitItem *item = &LogItem(index);
if (item) {
- m->ancestors.insert(m->ancestors.end(), item->commit_id);
+ m2->ancestors.insert(m2->ancestors.end(), item->commit_id);
}
for (size_t i = index; i < end; i++) {
Git::CommitItem *item = &LogItem(i);
if (isAncestorCommit(item->commit_id)) {
for (QString const &parent : item->parent_ids) {
- m->ancestors.insert(m->ancestors.end(), parent);
+ m2->ancestors.insert(m2->ancestors.end(), parent);
}
}
}
}
void MainWindow::on_action_open_existing_working_copy_triggered()
{
QString dir = defaultWorkingDir();
dir = QFileDialog::getExistingDirectory(this, tr("Add existing working copy"), dir);
addWorkingCopyDir(dir, false);
}
void MainWindow::on_action_view_refresh_triggered()
{
openRepository(true);
}
void MainWindow::on_tableWidget_log_currentItemChanged(QTableWidgetItem * /*current*/, QTableWidgetItem * /*previous*/)
{
- doLogCurrentItemChanged();
- m->searching = false;
+ doLogCurrentItemChanged(frame());
+ m2->searching = false;
}
void MainWindow::on_toolButton_stage_clicked()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->stage(selectedFiles());
- updateCurrentFilesList();
+ updateCurrentFilesList(frame());
}
void MainWindow::on_toolButton_unstage_clicked()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->unstage(selectedFiles());
- updateCurrentFilesList();
+ updateCurrentFilesList(frame());
}
void MainWindow::on_toolButton_select_all_clicked()
{
- if (ui->listWidget_unstaged->count() > 0) {
- ui->listWidget_unstaged->setFocus();
- ui->listWidget_unstaged->selectAll();
- } else if (ui->listWidget_staged->count() > 0) {
- ui->listWidget_staged->setFocus();
- ui->listWidget_staged->selectAll();
+ if (frame()->unstagedFileslistwidget()->count() > 0) {
+ frame()->unstagedFileslistwidget()->setFocus();
+ frame()->unstagedFileslistwidget()->selectAll();
+ } else if (frame()->stagedFileslistwidget()->count() > 0) {
+ frame()->stagedFileslistwidget()->setFocus();
+ frame()->stagedFileslistwidget()->selectAll();
}
}
void MainWindow::on_toolButton_commit_clicked()
{
ui->action_commit->trigger();
}
void MainWindow::on_action_edit_global_gitconfig_triggered()
{
QString dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
QString path = dir / ".gitconfig";
editFile(path, ".gitconfig");
}
void MainWindow::on_action_edit_git_config_triggered()
{
QString dir = currentWorkingCopyDir();
QString path = dir / ".git/config";
editFile(path, ".git/config");
}
void MainWindow::on_action_edit_gitignore_triggered()
{
QString dir = currentWorkingCopyDir();
QString path = dir / ".gitignore";
if (editFile(path, ".gitignore")) {
- updateCurrentFilesList();
+ updateCurrentFilesList(frame());
}
}
-int MainWindow::selectedLogIndex() const
+int MainWindow::selectedLogIndex(RepositoryWrapperFrame *frame) const
{
- auto const &logs = getLogs();
- int i = ui->tableWidget_log->currentRow();
+ auto const &logs = getLogs(frame);
+ int i = frame->logtablewidget()->currentRow();
if (i >= 0 && i < (int)logs.size()) {
return i;
}
return -1;
}
-void MainWindow::updateDiffView(QListWidgetItem *item)
+/**
+ * @brief ファイル差分表示を更新する
+ * @param item
+ */
+void MainWindow::updateDiffView(RepositoryWrapperFrame *frame, QListWidgetItem *item)
{
- clearDiffView();
+ clearDiffView(frame);
- m->last_selected_file_item = item;
+ m2->last_selected_file_item = item;
if (!item) return;
int idiff = indexOfDiff(item);
if (idiff >= 0 && idiff < diffResult()->size()) {
Git::Diff const &diff = diffResult()->at(idiff);
QString key = GitDiff::makeKey(diff);
- auto it = getDiffCacheMap()->find(key);
- if (it != getDiffCacheMap()->end()) {
- auto const &logs = getLogs();
- int row = ui->tableWidget_log->currentRow();
+ auto it = getDiffCacheMap(frame)->find(key);
+ if (it != getDiffCacheMap(frame)->end()) {
+ auto const &logs = getLogs(frame);
+ int row = frame->logtablewidget()->currentRow();
bool uncommited = (row >= 0 && row < (int)logs.size() && Git::isUncommited(logs[row]));
- ui->widget_diff_view->updateDiffView(it->second, uncommited);
+ frame->filediffwidget()->updateDiffView(it->second, uncommited);
}
}
}
-void MainWindow::updateDiffView()
+void MainWindow::updateDiffView(RepositoryWrapperFrame *frame)
{
- updateDiffView(m->last_selected_file_item);
+ updateDiffView(frame, m2->last_selected_file_item);
}
-void MainWindow::updateUnstagedFileCurrentItem()
+void MainWindow::updateUnstagedFileCurrentItem(RepositoryWrapperFrame *frame)
{
- updateDiffView(ui->listWidget_unstaged->currentItem());
+ updateDiffView(frame, frame->unstagedFileslistwidget()->currentItem());
}
-void MainWindow::updateStagedFileCurrentItem()
+void MainWindow::updateStagedFileCurrentItem(RepositoryWrapperFrame *frame)
{
- updateDiffView(ui->listWidget_staged->currentItem());
+ updateDiffView(frame, frame->stagedFileslistwidget()->currentItem());
}
void MainWindow::on_listWidget_unstaged_currentRowChanged(int /*currentRow*/)
{
- updateUnstagedFileCurrentItem();
+ updateUnstagedFileCurrentItem(frame());
}
void MainWindow::on_listWidget_staged_currentRowChanged(int /*currentRow*/)
{
- updateStagedFileCurrentItem();
+ updateStagedFileCurrentItem(frame());
}
void MainWindow::on_listWidget_files_currentRowChanged(int /*currentRow*/)
{
- updateDiffView(ui->listWidget_files->currentItem());
+ updateDiffView(frame(), frame()->fileslistwidget()->currentItem());
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (QApplication::modalWindow()) return;
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
event->accept();
}
}
-void MainWindow::timerEvent(QTimerEvent *)
-{
- bool running = getPtyProcess()->isRunning();
- if (ui->toolButton_stop_process->isEnabled() != running) {
- ui->toolButton_stop_process->setEnabled(running);
- ui->action_stop_process->setEnabled(running);
- setNetworkingCommandsEnabled(!running);
- }
- if (!running) {
- setInteractionMode(InteractionMode::None);
- }
-
- while (1) {
- char tmp[1024];
- int len = getPtyProcess()->readOutput(tmp, sizeof(tmp));
- if (len < 1) break;
- writeLog(tmp, len);
- }
-}
-
void MainWindow::keyPressEvent(QKeyEvent *event)
{
int c = event->key();
if (c == Qt::Key_T && (event->modifiers() & Qt::ControlModifier)) {
test();
return;
}
if (QApplication::focusWidget() == ui->widget_log) {
auto write_char = [&](char c){
if (getPtyProcess()->isRunning()) {
getPtyProcess()->writeInput(&c, 1);
}
};
auto write_text = [&](QString const &str){
std::string s = str.toStdString();
for (char i : s) {
write_char(i);
}
};
if (c == Qt::Key_Return || c == Qt::Key_Enter) {
write_char('\n');
} else {
QString text = event->text();
write_text(text);
}
}
}
void MainWindow::on_action_edit_settings_triggered()
{
SettingsDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
ApplicationSettings const &newsettings = dlg.settings();
setAppSettings(newsettings);
setGitCommand(appsettings()->git_command, false);
setFileCommand(appsettings()->file_command, false);
setGpgCommand(appsettings()->gpg_command, false);
- setRemoteMonitoringEnabled(true);
}
}
void MainWindow::onCloneCompleted(bool success, QVariant const &userdata)
{
if (success) {
RepositoryItem r = userdata.value<RepositoryItem>();
saveRepositoryBookmark(r);
setCurrentRepository(r, false);
openRepository(true);
}
}
void MainWindow::onPtyProcessCompleted(bool /*ok*/, QVariant const &userdata)
{
switch (getPtyCondition()) {
case PtyCondition::Clone:
onCloneCompleted(getPtyProcessOk(), userdata);
break;
}
setPtyCondition(PtyCondition::None);
}
void MainWindow::on_action_clone_triggered()
{
clone();
}
void MainWindow::on_action_about_triggered()
{
AboutDialog dlg(this);
dlg.exec();
}
void MainWindow::on_toolButton_clone_clicked()
{
ui->action_clone->trigger();
}
void MainWindow::on_toolButton_fetch_clicked()
{
ui->action_fetch->trigger();
}
void MainWindow::clearRepoFilter()
{
ui->lineEdit_filter->clear();
}
void MainWindow::appendCharToRepoFilter(ushort c)
{
if (QChar(c).isLetter()) {
c = QChar(c).toLower().unicode();
}
ui->lineEdit_filter->setText(getRepositoryFilterText() + c);
}
void MainWindow::backspaceRepoFilter()
{
QString s = getRepositoryFilterText();
int n = s.size();
if (n > 0) {
s = s.mid(0, n - 1);
}
ui->lineEdit_filter->setText(s);
}
void MainWindow::on_lineEdit_filter_textChanged(QString const &text)
{
setRepositoryFilterText(text);
updateRepositoriesList();
}
void MainWindow::on_toolButton_erase_filter_clicked()
{
clearRepoFilter();
ui->lineEdit_filter->setFocus();
}
-void MainWindow::deleteTags(QStringList const &tagnames)
+void MainWindow::deleteTags(RepositoryWrapperFrame *frame, QStringList const &tagnames)
{
- int row = ui->tableWidget_log->currentRow();
+ int row = frame->logtablewidget()->currentRow();
internalDeleteTags(tagnames);
- ui->tableWidget_log->selectRow(row);
+ frame->logtablewidget()->selectRow(row);
}
-void MainWindow::revertCommit()
+void MainWindow::revertCommit(RepositoryWrapperFrame *frame)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
- Git::CommitItem const *commit = selectedCommitItem();
+ Git::CommitItem const *commit = selectedCommitItem(frame);
if (commit) {
g->revert(commit->commit_id);
openRepository(false);
}
}
-bool MainWindow::addTag(QString const &name)
+bool MainWindow::addTag(RepositoryWrapperFrame *frame, QString const &name)
{
- int row = ui->tableWidget_log->currentRow();
+ int row = frame->logtablewidget()->currentRow();
- bool ok = internalAddTag(name);
+ bool ok = internalAddTag(frame, name);
- ui->tableWidget_log->selectRow(row);
+ frame->selectLogTableRow(row);
return ok;
}
void MainWindow::on_action_push_all_tags_triggered()
{
reopenRepository(false, [&](GitPtr g){
g->push(true);
});
}
void MainWindow::on_tableWidget_log_itemDoubleClicked(QTableWidgetItem *)
{
- Git::CommitItem const *commit = selectedCommitItem();
+ Git::CommitItem const *commit = selectedCommitItem(frame());
if (commit) {
- execCommitPropertyDialog(this, commit);
+ execCommitPropertyDialog(this, frame(), commit);
}
}
void MainWindow::on_listWidget_unstaged_itemDoubleClicked(QListWidgetItem * item)
{
- execFilePropertyDialog(item);
+ showObjectProperty(item);
}
void MainWindow::on_listWidget_staged_itemDoubleClicked(QListWidgetItem *item)
{
- execFilePropertyDialog(item);
+ showObjectProperty(item);
}
void MainWindow::on_listWidget_files_itemDoubleClicked(QListWidgetItem *item)
{
- execFilePropertyDialog(item);
+ showObjectProperty(item);
}
QListWidgetItem *MainWindow::currentFileItem() const
{
QListWidget *listwidget = nullptr;
if (ui->stackedWidget_filelist->currentWidget() == ui->page_uncommited) {
QWidget *w = qApp->focusWidget();
if (w == ui->listWidget_unstaged) {
listwidget = ui->listWidget_unstaged;
} else if (w == ui->listWidget_staged) {
listwidget = ui->listWidget_staged;
}
} else {
listwidget = ui->listWidget_files;
}
if (listwidget) {
return listwidget->currentItem();
}
return nullptr;
}
void MainWindow::on_action_set_config_user_triggered()
{
Git::User global_user;
Git::User repo_user;
GitPtr g = git();
if (isValidWorkingCopy(g)) {
repo_user = g->getUser(Git::Source::Local);
}
global_user = g->getUser(Git::Source::Global);
execSetUserDialog(global_user, repo_user, currentRepositoryName());
}
void MainWindow::showLogWindow(bool show)
{
ui->dockWidget_log->setVisible(show);
}
void MainWindow::on_action_window_log_triggered(bool checked)
{
showLogWindow(checked);
}
void MainWindow::on_action_repo_jump_triggered()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
- NamedCommitList items = namedCommitItems(Branches | Tags | Remotes);
+ NamedCommitList items = namedCommitItems(frame(), Branches | Tags | Remotes);
{
NamedCommitItem head;
head.name = "HEAD";
head.id = getHeadId();
items.push_front(head);
}
JumpDialog dlg(this, items);
if (dlg.exec() == QDialog::Accepted) {
QString text = dlg.text();
if (text.isEmpty()) return;
QString id = g->rev_parse(text);
if (id.isEmpty() && Git::isValidID(text)) {
QStringList list = findGitObject(text);
if (list.isEmpty()) {
QMessageBox::warning(this, tr("Jump"), QString("%1\n\n").arg(text) + tr("No such commit"));
return;
}
ObjectBrowserDialog dlg2(this, list);
if (dlg2.exec() == QDialog::Accepted) {
id = dlg2.text();
if (id.isEmpty()) return;
}
}
if (g->objectType(id) == "tag") {
- id = getObjCache()->getCommitIdFromTag(id);
+ id = getObjCache(frame())->getCommitIdFromTag(id);
}
- int row = rowFromCommitId(id);
+ int row = rowFromCommitId(frame(), id);
if (row < 0) {
QMessageBox::warning(this, tr("Jump"), QString("%1\n(%2)\n\n").arg(text).arg(id) + tr("No such commit"));
} else {
- setCurrentLogRow(row);
+ setCurrentLogRow(frame(), row);
}
}
}
void MainWindow::on_action_repo_checkout_triggered()
{
- checkout();
+ checkout(frame());
}
void MainWindow::on_action_delete_branch_triggered()
{
- deleteBranch();
+ deleteBranch(frame());
}
void MainWindow::on_toolButton_terminal_clicked()
{
openTerminal(nullptr);
}
void MainWindow::on_toolButton_explorer_clicked()
{
openExplorer(nullptr);
}
void MainWindow::on_action_reset_HEAD_1_triggered()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->reset_head1();
openRepository(false);
}
void MainWindow::on_action_create_a_repository_triggered()
{
createRepository(QString());
}
bool MainWindow::isOnlineMode() const
{
- return m->is_online_mode;
+ return m2->is_online_mode;
}
void MainWindow::setRemoteOnline(bool f, bool save)
{
- m->is_online_mode = f;
+ m2->is_online_mode = f;
{
QRadioButton *rb = nullptr;
rb = f ? ui->radioButton_remote_online : ui->radioButton_remote_offline;
rb->blockSignals(true);
rb->click();
rb->blockSignals(false);
ui->action_online->setCheckable(true);
ui->action_offline->setCheckable(true);
ui->action_online->setChecked(f);
ui->action_offline->setChecked(!f);
setNetworkingCommandsEnabled(f);
}
if (save) {
MySettings s;
s.beginGroup("Remote");
s.setValue("Online", f);
s.endGroup();
}
}
void MainWindow::on_radioButton_remote_online_clicked()
{
setRemoteOnline(true, true);
}
void MainWindow::on_radioButton_remote_offline_clicked()
{
setRemoteOnline(false, true);
}
void MainWindow::on_verticalScrollBar_log_valueChanged(int)
{
ui->widget_log->refrectScrollBar();
}
void MainWindow::on_horizontalScrollBar_log_valueChanged(int)
{
ui->widget_log->refrectScrollBar();
}
void MainWindow::on_toolButton_stop_process_clicked()
{
abortPtyProcess();
}
void MainWindow::on_action_stop_process_triggered()
{
abortPtyProcess();
}
void MainWindow::on_action_exit_triggered()
{
close();
}
void MainWindow::on_action_reflog_triggered()
{
GitPtr g = git();
Git::ReflogItemList reflog;
g->reflog(&reflog);
ReflogWindow dlg(this, this, reflog);
dlg.exec();
}
void MainWindow::blame(QListWidgetItem *item)
{
QList<BlameItem> list;
QString path = getFilePath(item);
{
GitPtr g = git();
QByteArray ba = g->blame(path);
if (!ba.isEmpty()) {
char const *begin = ba.data();
char const *end = begin + ba.size();
list = BlameWindow::parseBlame(begin, end);
}
}
if (!list.isEmpty()) {
qApp->setOverrideCursor(Qt::WaitCursor);
BlameWindow win(this, path, list);
qApp->restoreOverrideCursor();
win.exec();
}
}
void MainWindow::blame()
{
blame(currentFileItem());
}
void MainWindow::on_action_repository_property_triggered()
{
- execRepositoryPropertyDialog(currentWorkingCopyDir());
+ execRepositoryPropertyDialog(currentRepository());
}
void MainWindow::on_action_set_gpg_signing_triggered()
{
GitPtr g = git();
QString global_key_id = g->signingKey(Git::Source::Global);
QString repository_key_id;
if (g->isValidWorkingCopy()) {
repository_key_id = g->signingKey(Git::Source::Local);
}
SetGpgSigningDialog dlg(this, currentRepositoryName(), global_key_id, repository_key_id);
if (dlg.exec() == QDialog::Accepted) {
g->setSigningKey(dlg.id(), dlg.isGlobalChecked());
}
}
void MainWindow::execAreYouSureYouWantToContinueConnectingDialog()
{
using TheDlg = AreYouSureYouWantToContinueConnectingDialog;
setInteractionMode(InteractionMode::Busy);
QApplication::restoreOverrideCursor();
TheDlg dlg(this);
if (dlg.exec() == QDialog::Accepted) {
TheDlg::Result r = dlg.result();
if (r == TheDlg::Result::Yes) {
getPtyProcess()->writeInput("yes\n", 4);
} else {
setPtyProcessOk(false); // abort
getPtyProcess()->writeInput("no\n", 3);
QThread::msleep(300);
stopPtyProcess();
}
} else {
ui->widget_log->setFocus();
setInteractionCanceled(true);
}
setInteractionMode(InteractionMode::Busy);
}
void MainWindow::onLogIdle()
{
if (interactionCanceled()) return;
if (interactionMode() != InteractionMode::None) return;
static char const are_you_sure_you_want_to_continue_connecting[] = "Are you sure you want to continue connecting (yes/no)?";
static char const enter_passphrase[] = "Enter passphrase: ";
static char const enter_passphrase_for_key[] = "Enter passphrase for key '";
static char const fatal_authentication_failed_for[] = "fatal: Authentication failed for '";
std::vector<char> vec;
ui->widget_log->retrieveLastText(&vec, 100);
if (!vec.empty()) {
std::string line;
int n = (int)vec.size();
int i = n;
while (i > 0) {
i--;
if (i + 1 < n && vec[i] == '\n') {
i++;
line.assign(&vec[i], n - i);
break;
}
}
if (!line.empty()) {
auto ExecLineEditDialog = [&](QWidget *parent, QString const &title, QString const &prompt, QString const &val, bool password){
LineEditDialog dlg(parent, title, prompt, val, password);
if (dlg.exec() == QDialog::Accepted) {
std::string ret = dlg.text().toStdString();
std::string str = ret + '\n';
getPtyProcess()->writeInput(str.c_str(), str.size());
return ret;
}
abortPtyProcess();
return std::string();
};
auto Match = [&](char const *str){
int n = strlen(str);
if (strncmp(line.c_str(), str, n) == 0) {
char const *p = line.c_str() + n;
while (1) {
if (!*p) return true;
if (!isspace((unsigned char)*p)) break;
p++;
}
}
return false;
};
auto StartsWith = [&](char const *str){
char const *p = line.c_str();
while (*str) {
if (*p != *str) return false;
str++;
p++;
}
return true;
};
if (Match(are_you_sure_you_want_to_continue_connecting)) {
execAreYouSureYouWantToContinueConnectingDialog();
return;
}
if (line == enter_passphrase) {
ExecLineEditDialog(this, "Passphrase", QString::fromStdString(line), QString(), true);
return;
}
if (StartsWith(enter_passphrase_for_key)) {
std::string keyfile;
{
int i = strlen(enter_passphrase_for_key);
char const *p = line.c_str() + i;
char const *q = strrchr(p, ':');
if (q && p + 2 < q && q[-1] == '\'') {
keyfile.assign(p, q - 1);
}
}
if (!keyfile.empty()) {
if (keyfile == sshPassphraseUser() && !sshPassphrasePass().empty()) {
std::string text = sshPassphrasePass() + '\n';
getPtyProcess()->writeInput(text.c_str(), text.size());
} else {
std::string secret = ExecLineEditDialog(this, "Passphrase for key", QString::fromStdString(line), QString(), true);
sshSetPassphrase(keyfile, secret);
}
return;
}
}
char const *begin = line.c_str();
char const *end = line.c_str() + line.size();
auto Input = [&](QString const &title, bool password, std::string *value){
Q_ASSERT(value);
std::string header = QString("%1 for '").arg(title).toStdString();
if (strncmp(begin, header.c_str(), header.size()) == 0) {
QString msg;
if (memcmp(end - 2, "':", 2) == 0) {
msg = QString::fromUtf8(begin, end - begin - 1);
} else if (memcmp(end - 3, "': ", 3) == 0) {
msg = QString::fromUtf8(begin, end - begin - 2);
}
if (!msg.isEmpty()) {
std::string s = ExecLineEditDialog(this, title, msg, value ? QString::fromStdString(*value) : QString(), password);
*value = s;
return true;
}
}
return false;
};
std::string uid = httpAuthenticationUser();
std::string pwd = httpAuthenticationPass();
bool ok = false;
if (Input("Username", false, &uid)) ok = true;
if (Input("Password", true, &pwd)) ok = true;
if (ok) {
httpSetAuthentication(uid, pwd);
return;
}
if (StartsWith(fatal_authentication_failed_for)) {
QMessageBox::critical(this, tr("Authentication Failed"), QString::fromStdString(line));
abortPtyProcess();
return;
}
}
}
}
void MainWindow::on_action_edit_tags_triggered()
{
- Git::CommitItem const *commit = selectedCommitItem();
+ Git::CommitItem const *commit = selectedCommitItem(frame());
if (commit && Git::isValidID(commit->commit_id)) {
EditTagsDialog dlg(this, commit);
dlg.exec();
}
}
void MainWindow::on_action_push_u_triggered()
{
pushSetUpstream(false);
}
void MainWindow::on_action_delete_remote_branch_triggered()
{
- deleteRemoteBranch(selectedCommitItem());
+ deleteRemoteBranch(frame(), selectedCommitItem(frame()));
}
void MainWindow::on_action_terminal_triggered()
{
auto const *repo = &currentRepository();
openTerminal(repo);
}
void MainWindow::on_action_explorer_triggered()
{
auto const *repo = &currentRepository();
openExplorer(repo);
}
void MainWindow::on_action_reset_hard_triggered()
{
doGitCommand([&](GitPtr g){
g->reset_hard();
});
}
void MainWindow::on_action_clean_df_triggered()
{
doGitCommand([&](GitPtr g){
g->clean_df();
});
}
void MainWindow::postOpenRepositoryFromGitHub(QString const &username, QString const &reponame)
{
QVariantList list;
list.push_back(username);
list.push_back(reponame);
- postUserFunctionEvent([&](QVariant const &v){
+ postUserFunctionEvent([&](QVariant const &v, void *){
QVariantList l = v.toList();
QString uname = l[0].toString();
QString rname = l[1].toString();
CloneFromGitHubDialog dlg(this, uname, rname);
if (dlg.exec() == QDialog::Accepted) {
clone(dlg.url());
}
}, QVariant(list));
}
void MainWindow::on_action_stash_triggered()
{
doGitCommand([&](GitPtr g){
g->stash();
});
}
void MainWindow::on_action_stash_apply_triggered()
{
doGitCommand([&](GitPtr g){
g->stash_apply();
});
}
void MainWindow::on_action_stash_drop_triggered()
{
doGitCommand([&](GitPtr g){
g->stash_drop();
});
}
void MainWindow::on_action_online_triggered()
{
ui->radioButton_remote_online->click();
}
void MainWindow::on_action_offline_triggered()
{
ui->radioButton_remote_offline->click();
}
void MainWindow::on_action_repositories_panel_triggered()
{
bool checked = ui->action_repositories_panel->isChecked();
ui->stackedWidget_leftpanel->setCurrentWidget(checked ? ui->page_repos : ui->page_collapsed);
if (checked) {
- ui->stackedWidget_leftpanel->setFixedWidth(m->repos_panel_width);
+ ui->stackedWidget_leftpanel->setFixedWidth(m2->repos_panel_width);
ui->stackedWidget_leftpanel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
ui->stackedWidget_leftpanel->setMinimumWidth(QWIDGETSIZE_MAX);
ui->stackedWidget_leftpanel->setMaximumWidth(QWIDGETSIZE_MAX);
} else {
- m->repos_panel_width = ui->stackedWidget_leftpanel->width();
+ m2->repos_panel_width = ui->stackedWidget_leftpanel->width();
ui->stackedWidget_leftpanel->setFixedWidth(24);
}
}
void MainWindow::on_action_find_triggered()
{
- m->searching = false;
+ m2->searching = false;
- if (getLogs().empty()) {
+ if (getLogs(frame()).empty()) {
return;
}
- FindCommitDialog dlg(this, m->search_text);
+ FindCommitDialog dlg(this, m2->search_text);
if (dlg.exec() == QDialog::Accepted) {
- m->search_text = dlg.text();
- ui->tableWidget_log->setFocus();
- findNext();
+ m2->search_text = dlg.text();
+ frame()->setFocusToLogTable();
+ findNext(frame());
}
}
void MainWindow::on_action_find_next_triggered()
{
- if (m->search_text.isEmpty()) {
+ if (m2->search_text.isEmpty()) {
on_action_find_triggered();
} else {
- findNext();
+ findNext(frame());
}
}
void MainWindow::on_action_repo_jump_to_head_triggered()
{
QString name = "HEAD";
GitPtr g = git();
QString id = g->rev_parse(name);
- int row = rowFromCommitId(id);
+ int row = rowFromCommitId(frame(), id);
if (row < 0) {
qDebug() << "No such commit";
} else {
- setCurrentLogRow(row);
+ setCurrentLogRow(frame(), row);
}
}
void MainWindow::on_action_repo_merge_triggered()
{
- merge();
+ merge(frame());
}
void MainWindow::on_action_expand_commit_log_triggered()
{
ui->splitter_h->setSizes({10000, 1, 1});
}
void MainWindow::on_action_expand_file_list_triggered()
{
ui->splitter_h->setSizes({1, 10000, 1});
}
void MainWindow::on_action_expand_diff_view_triggered()
{
ui->splitter_h->setSizes({1, 1, 10000});
}
void MainWindow::on_action_sidebar_triggered()
{
bool f = ui->stackedWidget_leftpanel->isVisible();
f = !f;
ui->stackedWidget_leftpanel->setVisible(f);
+ ui->action_sidebar->setChecked(f);
}
-
-
+#if 0
void MainWindow::on_action_wide_triggered()
{
QWidget *w = focusWidget();
if (w == m->focused_widget) {
ui->splitter_h->setSizes(m->splitter_h_sizes);
m->focused_widget = nullptr;
} else {
m->focused_widget = w;
m->splitter_h_sizes = ui->splitter_h->sizes();
- if (w == ui->tableWidget_log) {
+ if (w == frame->logtablewidget()) {
ui->splitter_h->setSizes({10000, 1, 1});
} else if (ui->stackedWidget_filelist->isAncestorOf(w)) {
ui->splitter_h->setSizes({1, 10000, 1});
} else if (ui->frame_diff_view->isAncestorOf(w)) {
ui->splitter_h->setSizes({1, 1, 10000});
}
}
}
+#endif
-void MainWindow::test()
+void MainWindow::setShowLabels(bool show, bool save)
{
- QElapsedTimer t;
- t.start();
- {
- QPixmap pm(1, 1);
- QPainter pr(&pm);
- pr.setFont(QFont("MS Gothic", 30));
- char tmp[2];
- for (int i = 0x20; i < 0x80; i++) {
- tmp[0] = i;
- tmp[1] = 0;
- QString s = tmp;
- int w = pr.fontMetrics().size(0, s).width();
- qDebug() << w;
+ ApplicationSettings *as = appsettings();
+ as->show_labels = show;
- }
+ bool b = ui->action_show_labels->blockSignals(true);
+ ui->action_show_labels->setChecked(show);
+ ui->action_show_labels->blockSignals(b);
+
+ if (save) {
+ saveApplicationSettings();
+ }
+}
+
+bool MainWindow::isLabelsVisible() const
+{
+ return appsettings()->show_labels;
+}
+
+void MainWindow::on_action_show_labels_triggered()
+{
+ bool f = ui->action_show_labels->isChecked();
+ setShowLabels(f, true);
+ frame()->updateLogTableView();
+}
+
+void MainWindow::on_action_submodules_triggered()
+{
+ GitPtr g = git();
+ QList<Git::SubmoduleItem> mods = g->submodules();
+ std::vector<SubmodulesDialog::Submodule> mods2;
+ mods2.resize(mods.size());
+
+ for (size_t i = 0; i < mods.size(); i++) {
+ const Git::SubmoduleItem mod = mods[i];
+ mods2[i].submodule = mod;
+
+ GitPtr g2 = git(g->workingDir(), mod.path, g->sshKey());
+ g2->queryCommit(mod.id, &mods2[i].head);
}
- qDebug() << QString("%1ms").arg(t.elapsed());
+
+ SubmodulesDialog dlg(this, mods2);
+ dlg.exec();
}
+void MainWindow::on_action_submodule_add_triggered()
+{
+ QString dir = currentRepository().local_dir;
+ submodule_add({}, dir);
+}
+void MainWindow::on_action_submodule_update_triggered()
+{
+ SubmoduleUpdateDialog dlg(this);
+ if (dlg.exec() == QDialog::Accepted) {
+ GitPtr g = git();
+ Git::SubmoduleUpdateData data;
+ data.init = dlg.isInit();
+ data.recursive = dlg.isRecursive();
+ g->submodule_update(data, getPtyProcess());
+ }
+}
+/**
+ * @brief アイコンの読み込みが完了した
+ */
+void MainWindow::onAvatarUpdated(RepositoryWrapperFrameP frame)
+{
+ updateCommitLogTableLater(frame.pointer, 100); // コミットログを更新(100ms後)
+}
+void MainWindow::test()
+{
+}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 7dfef9e..e382e02 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -1,233 +1,576 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "BasicMainWindow.h"
+#include "Git.h"
+
+#include "GitHubAPI.h"
+#include "RepositoryData.h"
+#include "MyProcess.h"
+#include "main.h"
+#include "webclient.h"
+#include "AvatarLoader.h"
+#include "GitObjectManager.h"
+#include "Theme.h"
+#include "BranchLabel.h"
+
+class RepositoryWrapperFrame;
+class LogTableWidget;
+class QListWidgetItem;
+class QListWidget;
+class QTreeWidgetItem;
+class QTableWidgetItem;
namespace Ui {
class MainWindow;
}
-class QTableWidgetItem;
class HunkItem {
public:
int hunk_number = -1;
size_t pos, len;
std::vector<std::string> lines;
};
class MainWindow : public BasicMainWindow {
Q_OBJECT
+ friend class RepositoryWrapperFrame;
+ friend class SubmoduleMainWindow;
friend class ImageViewWidget;
friend class FileDiffSliderWidget;
friend class FileHistoryWindow;
friend class FileDiffWidget;
friend class AboutDialog;
private:
- struct Private;
- Private *m;
+ struct Private1 {
+
+ QIcon repository_icon;
+ QIcon folder_icon;
+ QIcon signature_good_icon;
+ QIcon signature_dubious_icon;
+ QIcon signature_bad_icon;
+ QPixmap transparent_pixmap;
+
+ QString starting_dir;
+ Git::Context gcx;
+ RepositoryItem current_repo;
+
+ QList<RepositoryItem> repos;
+// Git::CommitItemList logs;
+ QList<Git::Diff> diff_result;
+ QList<Git::SubmoduleItem> submodules;
+
+ QStringList added;
+ QStringList remotes;
+ QString current_remote_name;
+ Git::Branch current_branch;
+ unsigned int temp_file_counter = 0;
+
+ std::string ssh_passphrase_user;
+ std::string ssh_passphrase_pass;
+
+ std::string http_uid;
+ std::string http_pwd;
+
+ std::map<QString, GitHubAPI::User> committer_map; // key is email
+
+ PtyProcess pty_process;
+ bool pty_process_ok = false;
+ PtyCondition pty_condition = PtyCondition::None;
+
+ WebContext webcx;
+
+ AvatarLoader avatar_loader;
+// int update_files_list_counter = 0;
+// int update_commit_table_counter = 0;
+
+ bool interaction_canceled = false;
+ InteractionMode interaction_mode = InteractionMode::None;
+
+ QString repository_filter_text;
+ bool uncommited_changes = false;
+
+// std::map<QString, QList<Git::Branch>> branch_map;
+// std::map<QString, QList<Git::Tag>> tag_map;
+// std::map<int, QList<BranchLabel>> label_map;
+// std::map<QString, Git::Diff> diff_cache;
+// GitObjectCache objcache;
+
+ bool remote_changed = false;
+
+ ServerType server_type = ServerType::Standard;
+ GitHubRepositoryInfo github;
+
+ QString head_id;
+ bool force_fetch = false;
+
+ RepositoryItem temp_repo_for_clone_complete;
+ QVariant pty_process_completion_data;
+ };
+ Private1 *m1;
+
+ struct Private2;
+ Private2 *m2;
+
+ struct ObjectData {
+ QString id;
+ QString path;
+ Git::SubmoduleItem submod;
+ Git::CommitItem submod_commit;
+ QString header;
+ int idiff;
+ };
+
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
+ RepositoryWrapperFrame *frame();
+ RepositoryWrapperFrame const *frame() const;
+
QPixmap const &digitsPixmap() const;
- QString currentWorkingCopyDir() const override;
+// QString currentWorkingCopyDir() const override;
QColor color(unsigned int i);
- bool isOnlineMode() const override;
+ bool isOnlineMode() const;
private:
Ui::MainWindow *ui;
- void updateFilesList(QString id, bool wait) override;
- void updateFilesList(Git::CommitItem const &commit, bool wait);
- void updateRepositoriesList() override;
+ void postEvent(QObject *receiver, QEvent *event, int ms_later);
+ void postUserFunctionEvent(const std::function<void (const QVariant &, void *)> &fn, QVariant const &v = QVariant(), void *p = nullptr, int ms_later = 0);
+
+ void updateFilesList(RepositoryWrapperFrame *frame, QString id, bool wait);
+ void updateFilesList(RepositoryWrapperFrame *frame, Git::CommitItem const &commit, bool wait);
+ void updateRepositoriesList();
- void openRepository_(GitPtr g, bool keep_selection = false) override;
+ void openRepository_(GitPtr g, bool keep_selection = false);
+ void openRepository_(RepositoryWrapperFrame *frame, GitPtr g, bool keep_selection = false);
- void prepareLogTableWidget();
+// void prepareLogTableWidget(RepositoryWrapperFrame *frame);
QStringList selectedFiles_(QListWidget *listwidget) const;
QStringList selectedFiles() const;
void for_each_selected_files(std::function<void (QString const &)> const &fn);
void showFileList(FilesListType files_list_type);
- void clearLog();
- void clearFileList() override;
- void clearDiffView();
+ void clearLog(RepositoryWrapperFrame *frame);
+ void clearFileList(RepositoryWrapperFrame *frame);
+ void clearDiffView(RepositoryWrapperFrame *frame);
void clearRepositoryInfo();
int repositoryIndex_(const QTreeWidgetItem *item) const;
RepositoryItem const *repositoryItem(const QTreeWidgetItem *item) const;
QTreeWidgetItem *newQTreeWidgetFolderItem(QString const &name);
void buildRepoTree(QString const &group, QTreeWidgetItem *item, QList<RepositoryItem> *repos);
void refrectRepositories();
- void updateDiffView(QListWidgetItem *item);
- void updateDiffView();
- void updateUnstagedFileCurrentItem();
- void updateStagedFileCurrentItem();
- void updateStatusBarText() override;
- void setRepositoryInfo(QString const &reponame, QString const &brname) override;
+ void updateDiffView(RepositoryWrapperFrame *frame, QListWidgetItem *item);
+ void updateDiffView(RepositoryWrapperFrame *frame);
+ void updateUnstagedFileCurrentItem(RepositoryWrapperFrame *frame);
+ void updateStagedFileCurrentItem(RepositoryWrapperFrame *frame);
+ void updateStatusBarText(RepositoryWrapperFrame *frame);
+ void setRepositoryInfo(QString const &reponame, QString const &brname);
int indexOfRepository(const QTreeWidgetItem *treeitem) const;
void clearRepoFilter();
void appendCharToRepoFilter(ushort c);
void backspaceRepoFilter();
- void revertCommit();
- void mergeBranch(const QString &commit, Git::MergeFastForward ff);
- void mergeBranch(Git::CommitItem const *commit, Git::MergeFastForward ff);
+ void revertCommit(RepositoryWrapperFrame *frame);
+ void mergeBranch(const QString &commit, Git::MergeFastForward ff, bool squash);
+ void mergeBranch(Git::CommitItem const *commit, Git::MergeFastForward ff, bool squash);
void rebaseBranch(Git::CommitItem const *commit);
void cherrypick(Git::CommitItem const *commit);
- void merge(const Git::CommitItem *commit = nullptr);
+ void merge(RepositoryWrapperFrame *frame, const Git::CommitItem *commit = nullptr);
void detectGitServerType(const GitPtr &g);
void setRemoteOnline(bool f, bool save);
void startTimers();
void onCloneCompleted(bool success, const QVariant &userdata);
void setNetworkingCommandsEnabled(bool enabled);
void blame(QListWidgetItem *item);
void blame();
QListWidgetItem *currentFileItem() const;
void execAreYouSureYouWantToContinueConnectingDialog();
- void deleteRemoteBranch(Git::CommitItem const *commit);
- QStringList remoteBranches(QString const &id, QStringList *all);
+ void deleteRemoteBranch(RepositoryWrapperFrame *frame, Git::CommitItem const *commit);
+ QStringList remoteBranches(RepositoryWrapperFrame *frame, QString const &id, QStringList *all);
bool isUninitialized();
- void doLogCurrentItemChanged();
- void findNext();
+ void doLogCurrentItemChanged(RepositoryWrapperFrame *frame);
+ void findNext(RepositoryWrapperFrame *frame);
void findText(const QString &text);
void showStatus();
void onStartEvent();
void showLogWindow(bool show);
+ bool isValidRemoteURL(const QString &url, const QString &sshkey);
+ QStringList whichCommand_(const QString &cmdfile1, const QString &cmdfile2 = {});
+ QString selectCommand_(const QString &cmdname, const QStringList &cmdfiles, const QStringList &list, QString path, const std::function<void (const QString &)> &callback);
+ QString selectCommand_(const QString &cmdname, const QString &cmdfile, const QStringList &list, const QString &path, const std::function<void (const QString &)> &callback);
+ const RepositoryItem *findRegisteredRepository(QString *workdir) const;
+ static bool git_callback(void *cookie, const char *ptr, int len);
+ bool execSetGlobalUserDialog();
+ void revertAllFiles();
+ void addWorkingCopyDir(QString dir, QString name, bool open);
+ bool execWelcomeWizardDialog();
+ void execRepositoryPropertyDialog(const RepositoryItem &repo, bool open_repository_menu = false);
+ void execSetUserDialog(const Git::User &global_user, const Git::User &repo_user, const QString &reponame);
+ void setGitCommand(QString path, bool save);
+ void setFileCommand(QString path, bool save);
+ void setGpgCommand(QString path, bool save);
+ void setSshCommand(QString path, bool save);
+ bool checkGitCommand();
+ bool checkFileCommand();
+ bool saveBlobAs(RepositoryWrapperFrame *frame, const QString &id, const QString &dstpath);
+ bool saveByteArrayAs(const QByteArray &ba, const QString &dstpath);
+ static QString makeRepositoryName(const QString &loc);
+ bool saveFileAs(const QString &srcpath, const QString &dstpath);
+ QString saveAsTemp(RepositoryWrapperFrame *frame, const QString &id);
+ QString executableOrEmpty(const QString &path);
+ bool checkExecutable(const QString &path);
+ void internalSaveCommandPath(const QString &path, bool save, const QString &name);
+ void logGitVersion();
+ void internalClearRepositoryInfo();
+ void checkUser();
+ void openRepository(bool validate, bool waitcursor = true, bool keep_selection = false);
+ void updateRepository();
+ void reopenRepository(bool log, const std::function<void (const GitPtr &)> &callback);
+ void setCurrentRepository(const RepositoryItem &repo, bool clear_authentication);
+ void openSelectedRepository();
+ QList<Git::Diff> makeDiffs(RepositoryWrapperFrame *frame, QString id, bool *ok);
+ void queryBranches(RepositoryWrapperFrame *frame, const GitPtr &g);
+ void updateRemoteInfo();
+ void queryRemotes(const GitPtr &g);
+ void clone(QString url = {}, QString dir = {});
+ void submodule_add(QString url = {}, QString local_dir = {});
+ const Git::CommitItem *selectedCommitItem(RepositoryWrapperFrame *frame) const;
+ void commit(RepositoryWrapperFrame *frame, bool amend = false);
+ void commitAmend(RepositoryWrapperFrame *framae);
+ void pushSetUpstream(const QString &remote, const QString &branch);
+ bool pushSetUpstream(bool testonly);
+ void push();
+ void deleteBranch(RepositoryWrapperFrame *frame, const Git::CommitItem *commit);
+ void deleteBranch(RepositoryWrapperFrame *frame);
+ void resetFile(const QStringList &paths);
+ void clearAuthentication();
+ void clearSshAuthentication();
+ void internalDeleteTags(const QStringList &tagnames);
+ bool internalAddTag(RepositoryWrapperFrame *frame, const QString &name);
+ void createRepository(const QString &dir);
+ void setLogEnabled(const GitPtr &g, bool f);
+ void doGitCommand(const std::function<void (GitPtr)> &callback);
+ void setWindowTitle_(const Git::User &user);
+ void setUnknownRepositoryInfo();
+ void setCurrentRemoteName(const QString &name);
+ void deleteTags(RepositoryWrapperFrame *frame, const Git::CommitItem &commit);
+ bool isAvatarEnabled() const;
+ bool isGitHub() const;
+ QStringList remotes() const;
+ QList<Git::Branch> findBranch(RepositoryWrapperFrame *frame, const QString &id);
+ QString tempfileHeader() const;
+ void deleteTempFiles();
+ QString getCommitIdFromTag(RepositoryWrapperFrame *frame, const QString &tag);
+ QString newTempFilePath();
+ int limitLogCount() const;
+ Git::Object cat_file_(RepositoryWrapperFrame *frame, const GitPtr &g, const QString &id);
+ bool isThereUncommitedChanges() const;
+ static void addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (const ObjectData &)> &add_item);
+ Git::CommitItemList retrieveCommitLog(const GitPtr &g);
+ std::map<QString, QList<Git::Branch> > &branchMapRef(RepositoryWrapperFrame *frame);
+ void updateCommitLogTableLater(RepositoryWrapperFrame *frame, int ms_later);
+ void updateWindowTitle(const GitPtr &g);
+ QString makeCommitInfoText(RepositoryWrapperFrame *frame, int row, QList<BranchLabel> *label_list);
+ void removeRepositoryFromBookmark(int index, bool ask);
+ void openTerminal(const RepositoryItem *repo);
+ void openExplorer(const RepositoryItem *repo);
+ bool askAreYouSureYouWantToRun(const QString &title, const QString &command);
+ bool editFile(const QString &path, const QString &title);
+ void setAppSettings(const ApplicationSettings &appsettings);
+ QIcon getRepositoryIcon() const;
+ QIcon getFolderIcon() const;
+ QIcon getSignatureGoodIcon() const;
+ QIcon getSignatureDubiousIcon() const;
+ QIcon getSignatureBadIcon() const;
+ QPixmap getTransparentPixmap() const;
+ QStringList findGitObject(const QString &id) const;
+ void writeLog(const char *ptr, int len);
+ void writeLog(const QString &str);
+ QList<BranchLabel> sortedLabels(RepositoryWrapperFrame *frame, int row) const;
+ void saveApplicationSettings();
+ void loadApplicationSettings();
+ void setDiffResult(const QList<Git::Diff> &diffs);
+ const QList<Git::SubmoduleItem> &submodules() const;
+ void setSubmodules(const QList<Git::SubmoduleItem> &submodules);
+ bool runOnRepositoryDir(const std::function<void (QString)> &callback, const RepositoryItem *repo);
+ NamedCommitList namedCommitItems(RepositoryWrapperFrame *frame, int flags);
+ static QString getObjectID(QListWidgetItem *item);
+ static QString getFilePath(QListWidgetItem *item);
+ static QString getSubmodulePath(QListWidgetItem *item);
+ static bool isGroupItem(QTreeWidgetItem *item);
+ static int indexOfLog(QListWidgetItem *item);
+ static int indexOfDiff(QListWidgetItem *item);
+// static int getHunkIndex(QListWidgetItem *item);
+ static void updateSubmodules(GitPtr g, const QString &id, QList<Git::SubmoduleItem> *out);
+ void saveRepositoryBookmark(RepositoryItem item);
+ void changeRepositoryBookmarkName(RepositoryItem item, QString new_name);
+ int rowFromCommitId(RepositoryWrapperFrame *frame, const QString &id);
+ QList<Git::Tag> findTag(RepositoryWrapperFrame *frame, const QString &id);
+ void sshSetPassphrase(const std::string &user, const std::string &pass);
+ std::string sshPassphraseUser() const;
+ std::string sshPassphrasePass() const;
+ void httpSetAuthentication(const std::string &user, const std::string &pass);
+ std::string httpAuthenticationUser() const;
+ std::string httpAuthenticationPass() const;
+// const Git::CommitItemList &getLogs() const;
+ const Git::CommitItem *getLog(RepositoryWrapperFrame const *frame, int index) const;
+ void updateCommitGraph(RepositoryWrapperFrame *frame);
+ void initNetworking();
+ bool saveRepositoryBookmarks() const;
+ QString getBookmarksFilePath() const;
+ void stopPtyProcess();
+ void abortPtyProcess();
+ Git::CommitItemList *getLogsPtr(RepositoryWrapperFrame *frame);
+ void setLogs(RepositoryWrapperFrame *frame, const Git::CommitItemList &logs);
+ void clearLogs(RepositoryWrapperFrame *frame);
+ PtyProcess *getPtyProcess();
+ bool getPtyProcessOk() const;
+ BasicMainWindow::PtyCondition getPtyCondition();
+ void setPtyUserData(const QVariant &userdata);
+ void setPtyProcessOk(bool pty_process_ok);
+ bool fetch(const GitPtr &g, bool prune);
+ bool fetch_tags_f(const GitPtr &g);
+ void setPtyCondition(const PtyCondition &ptyCondition);
+ const QList<RepositoryItem> &getRepos() const;
+ QList<RepositoryItem> *getReposPtr();
+ AvatarLoader *getAvatarLoader();
+ const AvatarLoader *getAvatarLoader() const;
+// int *ptrUpdateFilesListCounter();
+// int *ptrUpdateCommitTableCounter();
+ bool interactionCanceled() const;
+ void setInteractionCanceled(bool canceled);
+ BasicMainWindow::InteractionMode interactionMode() const;
+ void setInteractionMode(const InteractionMode &im);
+ QString getRepositoryFilterText() const;
+ void setRepositoryFilterText(const QString &text);
+ void setUncommitedChanges(bool uncommited_changes);
+ QList<Git::Diff> *diffResult();
+ std::map<QString, Git::Diff> *getDiffCacheMap(RepositoryWrapperFrame *frame);
+ bool getRemoteChanged() const;
+ void setRemoteChanged(bool remote_changed);
+ void setServerType(const ServerType &server_type);
+ GitHubRepositoryInfo *ptrGitHub();
+ std::map<int, QList<BranchLabel> > *getLabelMap(RepositoryWrapperFrame *frame);
+ const std::map<int, QList<BranchLabel> > *getLabelMap(const RepositoryWrapperFrame *frame) const;
+ void clearLabelMap(RepositoryWrapperFrame *frame);
+ GitObjectCache *getObjCache(RepositoryWrapperFrame *frame);
+ bool getForceFetch() const;
+ void setForceFetch(bool force_fetch);
+ std::map<QString, QList<Git::Tag> > *ptrTagMap(RepositoryWrapperFrame *frame);
+ QString getHeadId() const;
+ void setHeadId(const QString &head_id);
+ void setPtyProcessCompletionData(const QVariant &value);
+ const QVariant &getTempRepoForCloneCompleteV() const;
+ void msgNoRepositorySelected();
+ bool isRepositoryOpened() const;
+ static std::pair<QString, QString> makeFileItemText(const ObjectData &data);
+ QString gitCommand() const;
+ QPixmap getTransparentPixmap();
+ static QListWidgetItem *NewListWidgetFileItem(const MainWindow::ObjectData &data);
+ void cancelPendingUserEvents();
protected:
- void customEvent(QEvent *);
+ void customEvent(QEvent *) override;
void dragEnterEvent(QDragEnterEvent *event) override;
- void timerEvent(QTimerEvent *) override;
+// void timerEvent(QTimerEvent *) override;
void keyPressEvent(QKeyEvent *event) override;
bool event(QEvent *event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
public:
void drawDigit(QPainter *pr, int x, int y, int n) const;
int digitWidth() const;
int digitHeight() const;
void setStatusBarText(QString const &text);
void clearStatusBarText();
- void setCurrentLogRow(int row) override;
+ void setCurrentLogRow(RepositoryWrapperFrame *frame, int row);
bool shown();
- void deleteTags(QStringList const &tagnames) override;
- bool addTag(QString const &name);
- void updateCurrentFilesList();
- void notifyRemoteChanged(bool f);
+ void deleteTags(RepositoryWrapperFrame *frame, QStringList const &tagnames);
+ bool addTag(RepositoryWrapperFrame *frame, QString const &name);
+ void updateCurrentFilesList(RepositoryWrapperFrame *frame);
void postOpenRepositoryFromGitHub(const QString &username, const QString &reponame);
- int selectedLogIndex() const override;
- void updateAncestorCommitMap();
+ int selectedLogIndex(RepositoryWrapperFrame *frame) const;
+ void updateAncestorCommitMap(RepositoryWrapperFrame *frame);
bool isAncestorCommit(const QString &id);
- void test();
- void postStartEvent();
+ void postStartEvent(int ms_later);
+ void setShowLabels(bool show, bool save);
+ bool isLabelsVisible() const;
+ void updateFilesList2(RepositoryWrapperFrame *frame, const QString &id, QList<Git::Diff> *diff_list, QListWidget *listwidget);
+ void execCommitViewWindow(const Git::CommitItem *commit);
+ void execCommitPropertyDialog(QWidget *parent, RepositoryWrapperFrame *frame, const Git::CommitItem *commit);
+ void execCommitExploreWindow(RepositoryWrapperFrame *frame, QWidget *parent, const Git::CommitItem *commit);
+ void execFileHistory(const QString &path);
+ void execFileHistory(QListWidgetItem *item);
+ void showObjectProperty(QListWidgetItem *item);
+ bool testRemoteRepositoryValidity(const QString &url, const QString &sshkey);
+ QString selectGitCommand(bool save);
+ QString selectFileCommand(bool save);
+ QString selectGpgCommand(bool save);
+ QString selectSshCommand(bool save);
+ const Git::Branch &currentBranch() const;
+ void setCurrentBranch(const Git::Branch &b);
+ const RepositoryItem &currentRepository() const;
+ QString currentRepositoryName() const;
+ QString currentRemoteName() const;
+ QString currentBranchName() const;
+ GitPtr git(const QString &dir, const QString &submodpath, const QString &sshkey) const;
+ GitPtr git();
+ GitPtr git(Git::SubmoduleItem const &submodpath);
+ void autoOpenRepository(QString dir);
+ bool queryCommit(const QString &id, Git::CommitItem *out);
+ void checkout(RepositoryWrapperFrame *frame, QWidget *parent, const Git::CommitItem *commit, std::function<void ()> accepted_callback = {});
+ void checkout(RepositoryWrapperFrame *frame);
+ void jumpToCommit(RepositoryWrapperFrame *frame, QString id);
+ Git::Object cat_file(RepositoryWrapperFrame *frame, const QString &id);
+ void addWorkingCopyDir(const QString &dir, bool open);
+ bool saveAs(RepositoryWrapperFrame *frame, const QString &id, const QString &dstpath);
+ QString determinFileType_(const QString &path, bool mime, const std::function<void (const QString &, QByteArray *)> &callback) const;
+ QString determinFileType(const QString &path, bool mime);
+ QString determinFileType(QByteArray in, bool mime);
+ QList<Git::Tag> queryTagList(RepositoryWrapperFrame *frame);
+ TextEditorThemePtr themeForTextEditor();
+ bool isValidWorkingCopy(const GitPtr &g) const;
+ void emitWriteLog(const QByteArray &ba);
+ QString findFileID(RepositoryWrapperFrame *frame, const QString &commit_id, const QString &file);
+ const Git::CommitItem *commitItem(RepositoryWrapperFrame *frame, int row) const;
+ QIcon committerIcon(RepositoryWrapperFrame *frame, int row) const;
+ void changeSshKey(const QString &localdir, const QString &sshkey);
+ static QString abbrevCommitID(const Git::CommitItem &commit);
+ const Git::CommitItemList &getLogs(RepositoryWrapperFrame const *frame) const;
+ const QList<BranchLabel> *label(const RepositoryWrapperFrame *frame, int row) const;
+ ApplicationSettings *appsettings();
+ const ApplicationSettings *appsettings() const;
+ QString defaultWorkingDir() const;
+ WebContext *webContext();
+ QIcon verifiedIcon(char s) const;
+ QAction *addMenuActionProperty(QMenu *menu);
+ QString currentWorkingCopyDir() const;
+ Git::SubmoduleItem const *querySubmoduleByPath(const QString &path, Git::CommitItem *commit);
+public slots:
+ void writeLog_(QByteArray ba);
private slots:
void updateUI();
void onLogVisibilityChanged();
void onPtyProcessCompleted(bool ok, const QVariant &userdata);
void onRepositoriesTreeDropped();
void on_action_about_triggered();
void on_action_clean_df_triggered();
void on_action_clone_triggered();
void on_action_commit_triggered();
void on_action_create_a_repository_triggered();
void on_action_delete_branch_triggered();
void on_action_delete_remote_branch_triggered();
void on_action_edit_git_config_triggered();
void on_action_edit_gitignore_triggered();
void on_action_edit_global_gitconfig_triggered();
void on_action_edit_settings_triggered();
void on_action_edit_tags_triggered();
void on_action_exit_triggered();
void on_action_explorer_triggered();
void on_action_fetch_triggered();
void on_action_fetch_prune_triggered();
void on_action_find_next_triggered();
void on_action_find_triggered();
void on_action_offline_triggered();
void on_action_online_triggered();
void on_action_open_existing_working_copy_triggered();
void on_action_pull_triggered();
void on_action_push_all_tags_triggered();
void on_action_push_triggered();
void on_action_push_u_triggered();
void on_action_reflog_triggered();
void on_action_repo_checkout_triggered();
void on_action_repo_jump_to_head_triggered();
void on_action_repo_jump_triggered();
void on_action_repositories_panel_triggered();
void on_action_repository_property_triggered();
void on_action_repository_status_triggered();
void on_action_reset_HEAD_1_triggered();
void on_action_reset_hard_triggered();
void on_action_set_config_user_triggered();
void on_action_set_gpg_signing_triggered();
void on_action_stash_apply_triggered();
void on_action_stash_drop_triggered();
void on_action_stash_triggered();
void on_action_stop_process_triggered();
void on_action_terminal_triggered();
void on_action_view_refresh_triggered();
void on_action_window_log_triggered(bool checked);
void on_horizontalScrollBar_log_valueChanged(int);
void on_lineEdit_filter_textChanged(QString const &text);
void on_listWidget_files_currentRowChanged(int currentRow);
void on_listWidget_files_customContextMenuRequested(const QPoint &pos);
void on_listWidget_files_itemDoubleClicked(QListWidgetItem *item);
void on_listWidget_staged_currentRowChanged(int currentRow);
void on_listWidget_staged_customContextMenuRequested(const QPoint &pos);
void on_listWidget_staged_itemDoubleClicked(QListWidgetItem *item);
void on_listWidget_unstaged_currentRowChanged(int currentRow);
void on_listWidget_unstaged_customContextMenuRequested(const QPoint &pos);
void on_listWidget_unstaged_itemDoubleClicked(QListWidgetItem *item);
void on_radioButton_remote_offline_clicked();
void on_radioButton_remote_online_clicked();
void on_tableWidget_log_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous);
void on_tableWidget_log_customContextMenuRequested(const QPoint &pos);
void on_tableWidget_log_itemDoubleClicked(QTableWidgetItem *);
void on_toolButton_clone_clicked();
void on_toolButton_commit_clicked();
void on_toolButton_erase_filter_clicked();
void on_toolButton_explorer_clicked();
void on_toolButton_fetch_clicked();
void on_toolButton_pull_clicked();
void on_toolButton_push_clicked();
void on_toolButton_select_all_clicked();
void on_toolButton_stage_clicked();
void on_toolButton_status_clicked();
void on_toolButton_stop_process_clicked();
void on_toolButton_terminal_clicked();
void on_toolButton_unstage_clicked();
void on_treeWidget_repos_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
void on_treeWidget_repos_customContextMenuRequested(const QPoint &pos);
void on_treeWidget_repos_itemDoubleClicked(QTreeWidgetItem *item, int column);
void on_verticalScrollBar_log_valueChanged(int);
void on_action_repo_merge_triggered();
void on_action_expand_commit_log_triggered();
void on_action_expand_file_list_triggered();
void on_action_expand_diff_view_triggered();
- void on_action_wide_triggered();
+// void on_action_wide_triggered();
void on_action_sidebar_triggered();
+ void on_action_show_labels_triggered();
+
+ void on_action_submodule_add_triggered();
+
+ void on_action_submodules_triggered();
+
+ void on_action_submodule_update_triggered();
+
+ void onAvatarUpdated(RepositoryWrapperFrameP frame);
+ void test();
+ void onInterval10ms();
protected:
void closeEvent(QCloseEvent *event) override;
- void internalWriteLog(const char *ptr, int len) override;
- RepositoryItem const *selectedRepositoryItem() const override;
- void removeSelectedRepositoryFromBookmark(bool ask) override;
+ void internalWriteLog(const char *ptr, int len);
+ RepositoryItem const *selectedRepositoryItem() const;
+ void removeSelectedRepositoryFromBookmark(bool ask);
protected slots:
void onLogIdle();
signals:
+ void signalWriteLog(QByteArray ba);
+ void remoteInfoChanged();
void signalSetRemoteChanged(bool f);
void onEscapeKeyPressed();
void updateButton();
};
#endif // MAINWINDOW_H
diff --git a/src/MainWindow.ui b/src/MainWindow.ui
index 9647615..c02a6c2 100644
--- a/src/MainWindow.ui
+++ b/src/MainWindow.ui
@@ -1,1753 +1,1828 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>848</width>
- <height>596</height>
+ <width>816</width>
+ <height>581</height>
</rect>
</property>
<property name="windowTitle">
<string>Guitar</string>
</property>
<property name="animated">
<bool>false</bool>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout_8">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>56</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>56</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>1</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="MyToolButton" name="toolButton_clone">
<property name="minimumSize">
<size>
<width>64</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>48</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Clone</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/clone.svg</normaloff>:/image/clone.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="MyToolButton" name="toolButton_fetch">
<property name="minimumSize">
<size>
<width>64</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>48</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Fetch</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/fetch.svg</normaloff>:/image/fetch.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="MyToolButton" name="toolButton_pull">
<property name="minimumSize">
<size>
<width>64</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>48</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Pull</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/pull.svg</normaloff>:/image/pull.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="MyToolButton" name="toolButton_push">
<property name="minimumSize">
<size>
<width>64</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>48</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Push</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/push.svg</normaloff>:/image/push.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="MyToolButton" name="toolButton_status">
<property name="minimumSize">
<size>
<width>64</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>48</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Status</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/status.svg</normaloff>:/image/status.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="MyToolButton" name="toolButton_terminal">
<property name="minimumSize">
<size>
<width>64</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>48</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Terminal</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/terminal.svg</normaloff>:/image/terminal.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="MyToolButton" name="toolButton_explorer">
<property name="minimumSize">
<size>
<width>64</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>48</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Explorer</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/explorer.svg</normaloff>:/image/explorer.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>503</width>
<height>48</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="RepositoryInfoFrame" name="frame_3">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>48</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_repo_name">
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Repository</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_branch_name">
<property name="text">
<string>Branch Name</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>4</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_10">
<property name="spacing">
<number>1</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="QRadioButton" name="radioButton_remote_online">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Online</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_remote_offline">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Offline</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="ReadOnlyLineEdit" name="lineEdit_remote">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter_v">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QStackedWidget" name="stackedWidget_leftpanel">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_repos">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="RepositoriesTreeWidget" name="treeWidget_repos">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="editTriggers">
<set>QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_5">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="lineEdit_filter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="ClearButton" name="toolButton_erase_filter">
<property name="minimumSize">
<size>
<width>22</width>
<height>17</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>15</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_collapsed">
<layout class="QVBoxLayout" name="verticalLayout_9">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="toolButton">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true"/>
</item>
</layout>
</widget>
</widget>
- <widget class="QSplitter" name="splitter_h">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
+ <widget class="RepositoryWrapperFrame" name="frame_repository_wrapper">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="childrenCollapsible">
- <bool>false</bool>
- </property>
- <widget class="LogTableWidget" name="tableWidget_log">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- <property name="editTriggers">
- <set>QAbstractItemView::NoEditTriggers</set>
- </property>
- <property name="tabKeyNavigation">
- <bool>false</bool>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::SingleSelection</enum>
- </property>
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectRows</enum>
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <property name="leftMargin">
+ <number>0</number>
</property>
- <property name="horizontalScrollMode">
- <enum>QAbstractItemView::ScrollPerPixel</enum>
+ <property name="topMargin">
+ <number>0</number>
</property>
- <property name="wordWrap">
- <bool>false</bool>
+ <property name="rightMargin">
+ <number>0</number>
</property>
- <attribute name="horizontalHeaderHighlightSections">
- <bool>false</bool>
- </attribute>
- <attribute name="horizontalHeaderStretchLastSection">
- <bool>true</bool>
- </attribute>
- <attribute name="verticalHeaderVisible">
- <bool>false</bool>
- </attribute>
- </widget>
- <widget class="QStackedWidget" name="stackedWidget_filelist">
- <property name="currentIndex">
+ <property name="bottomMargin">
<number>0</number>
</property>
- <widget class="QWidget" name="page_uncommited">
- <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,1">
- <property name="spacing">
- <number>2</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
+ <item>
+ <widget class="QSplitter" name="splitter_h">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
</property>
- <property name="bottomMargin">
- <number>0</number>
+ <property name="childrenCollapsible">
+ <bool>false</bool>
</property>
- <item>
- <widget class="FilesListWidget" name="listWidget_unstaged">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QFrame" name="frame_2">
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <widget class="LogTableWidget" name="tableWidget_log">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="tabKeyNavigation">
+ <bool>false</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderHighlightSections">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="QStackedWidget" name="stackedWidget_filelist">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="page_uncommited">
+ <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,1">
+ <property name="spacing">
+ <number>2</number>
+ </property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
- <layout class="QGridLayout" name="gridLayout" columnstretch="1,1">
- <property name="topMargin">
- <number>0</number>
+ <widget class="FilesListWidget" name="listWidget_unstaged">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="bottomMargin">
- <number>0</number>
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
</property>
- <property name="spacing">
- <number>3</number>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
</property>
- <item row="2" column="0" colspan="2">
- <widget class="Line" name="line">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="MyToolButton" name="toolButton_unstage">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>64</width>
- <height>0</height>
- </size>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- <property name="text">
- <string>Unstage</string>
- </property>
- <property name="icon">
- <iconset resource="resources/resources.qrc">
- <normaloff>:/image/unstage.svg</normaloff>:/image/unstage.svg</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>24</width>
- <height>24</height>
- </size>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextUnderIcon</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="0" colspan="2">
- <widget class="MyToolButton" name="toolButton_select_all">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- <property name="text">
- <string>Select all</string>
- </property>
- <property name="icon">
- <iconset resource="resources/resources.qrc">
- <normaloff>:/image/selall.svg</normaloff>:/image/selall.svg</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>24</width>
- <height>18</height>
- </size>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextUnderIcon</enum>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="MyToolButton" name="toolButton_stage">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>64</width>
- <height>0</height>
- </size>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- <property name="text">
- <string>Stage</string>
- </property>
- <property name="icon">
- <iconset resource="resources/resources.qrc">
- <normaloff>:/image/stage.svg</normaloff>:/image/stage.svg</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>24</width>
- <height>24</height>
- </size>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextUnderIcon</enum>
- </property>
- </widget>
- </item>
- <item row="3" column="0" colspan="2">
- <widget class="MyToolButton" name="toolButton_commit">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- <property name="text">
- <string>Commit</string>
- </property>
- <property name="icon">
- <iconset resource="resources/resources.qrc">
- <normaloff>:/image/commit.svg</normaloff>:/image/commit.svg</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>24</width>
- <height>24</height>
- </size>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextUnderIcon</enum>
- </property>
- </widget>
- </item>
- </layout>
+ </widget>
</item>
<item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
+ <widget class="QFrame" name="frame_2">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>0</height>
- </size>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
</property>
- </spacer>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout" columnstretch="1,1">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="2" column="0" colspan="2">
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QToolButton" name="toolButton_unstage">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>64</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Unstage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources/resources.qrc">
+ <normaloff>:/image/unstage.svg</normaloff>:/image/unstage.svg</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QToolButton" name="toolButton_select_all">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Select all</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources/resources.qrc">
+ <normaloff>:/image/selall.svg</normaloff>:/image/selall.svg</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>18</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QToolButton" name="toolButton_stage">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>64</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Stage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources/resources.qrc">
+ <normaloff>:/image/stage.svg</normaloff>:/image/stage.svg</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QToolButton" name="toolButton_commit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Commit</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources/resources.qrc">
+ <normaloff>:/image/commit.svg</normaloff>:/image/commit.svg</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="FilesListWidget" name="listWidget_staged">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ </widget>
</item>
</layout>
</widget>
- </item>
- <item>
- <widget class="FilesListWidget" name="listWidget_staged">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <widget class="QWidget" name="page_files">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="FilesListWidget" name="listWidget_files">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <widget class="QFrame" name="frame_diff_view">
+ <property name="frameShape">
+ <enum>QFrame::Panel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="spacing">
+ <number>0</number>
</property>
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
+ <property name="leftMargin">
+ <number>0</number>
</property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="page_files">
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="FilesListWidget" name="listWidget_files">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
+ <property name="rightMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- <widget class="QFrame" name="frame_diff_view">
- <property name="frameShape">
- <enum>QFrame::Panel</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="FileDiffWidget" name="widget_diff_view" native="true">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>100</height>
- </size>
- </property>
- <property name="focusPolicy">
- <enum>Qt::ClickFocus</enum>
- </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="FileDiffWidget" name="widget_diff_view" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
- </item>
- </layout>
- </widget>
+ </widget>
+ </item>
+ </layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>848</width>
- <height>21</height>
+ <width>816</width>
+ <height>25</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="action_open_existing_working_copy"/>
<addaction name="action_create_a_repository"/>
<addaction name="separator"/>
<addaction name="action_stop_process"/>
<addaction name="separator"/>
<addaction name="action_exit"/>
</widget>
<widget class="QMenu" name="menu_View">
<property name="title">
<string>&amp;View</string>
</property>
<addaction name="action_view_refresh"/>
+ <addaction name="action_show_labels"/>
</widget>
<widget class="QMenu" name="menu_Edit">
<property name="title">
<string>&amp;Edit</string>
</property>
<addaction name="action_edit_gitignore"/>
<addaction name="action_edit_git_config"/>
<addaction name="action_edit_global_gitconfig"/>
<addaction name="action_set_config_user"/>
<addaction name="action_set_gpg_signing"/>
<addaction name="separator"/>
<addaction name="action_find"/>
<addaction name="action_find_next"/>
<addaction name="separator"/>
<addaction name="action_edit_settings"/>
</widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
<string>&amp;Help</string>
</property>
<addaction name="action_about"/>
</widget>
<widget class="QMenu" name="menu_Window">
<property name="title">
<string>&amp;Window</string>
</property>
<addaction name="action_window_log"/>
+ <addaction name="separator"/>
+ <addaction name="action_sidebar"/>
+ <addaction name="action_expand_commit_log"/>
+ <addaction name="action_expand_file_list"/>
+ <addaction name="action_expand_diff_view"/>
</widget>
<widget class="QMenu" name="menu_Repository">
<property name="title">
<string>&amp;Repository</string>
</property>
<widget class="QMenu" name="menuStash">
<property name="title">
<string>Stash</string>
</property>
<addaction name="action_stash"/>
<addaction name="action_stash_apply"/>
<addaction name="separator"/>
<addaction name="action_stash_drop"/>
</widget>
<addaction name="action_repository_status"/>
<addaction name="action_repo_merge"/>
<addaction name="action_commit"/>
<addaction name="separator"/>
<addaction name="action_repo_jump"/>
<addaction name="action_repo_jump_to_head"/>
<addaction name="action_repo_checkout"/>
<addaction name="menuStash"/>
<addaction name="action_delete_branch"/>
<addaction name="action_delete_remote_branch"/>
<addaction name="action_edit_tags"/>
<addaction name="action_reflog"/>
<addaction name="separator"/>
<addaction name="action_terminal"/>
<addaction name="action_explorer"/>
<addaction name="separator"/>
<addaction name="action_repository_property"/>
</widget>
<widget class="QMenu" name="menuRe_mote">
<property name="title">
<string>Re&amp;mote</string>
</property>
<addaction name="action_fetch"/>
<addaction name="action_pull"/>
<addaction name="action_push"/>
<addaction name="action_push_u"/>
<addaction name="action_push_all_tags"/>
<addaction name="separator"/>
<addaction name="action_clone"/>
<addaction name="separator"/>
<addaction name="action_online"/>
<addaction name="action_offline"/>
</widget>
<widget class="QMenu" name="menuDestructive_3">
<property name="title">
<string>&amp;Destructive</string>
</property>
<addaction name="action_reset_HEAD_1"/>
<addaction name="separator"/>
<addaction name="action_reset_hard"/>
<addaction name="separator"/>
<addaction name="action_fetch_prune"/>
<addaction name="separator"/>
<addaction name="action_clean_df"/>
</widget>
<widget class="QMenu" name="menuExperimental">
<property name="title">
<string>Experimental</string>
</property>
- <addaction name="action_expand_commit_log"/>
- <addaction name="action_expand_file_list"/>
- <addaction name="action_expand_diff_view"/>
- <addaction name="action_sidebar"/>
- <addaction name="action_wide"/>
+ <addaction name="action_submodules"/>
+ <addaction name="action_submodule_add"/>
+ <addaction name="action_submodule_update"/>
</widget>
<addaction name="menu_File"/>
<addaction name="menu_View"/>
<addaction name="menu_Edit"/>
<addaction name="menu_Repository"/>
<addaction name="menuRe_mote"/>
<addaction name="menuDestructive_3"/>
<addaction name="menu_Window"/>
<addaction name="menu_Help"/>
<addaction name="menuExperimental"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<widget class="QDockWidget" name="dockWidget_log">
<property name="floating">
<bool>false</bool>
</property>
<property name="features">
<set>QDockWidget::DockWidgetFeatureMask</set>
</property>
<property name="allowedAreas">
<set>Qt::BottomDockWidgetArea|Qt::TopDockWidgetArea</set>
</property>
<property name="windowTitle">
<string>Log</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame_8">
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
<number>1</number>
</property>
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>1</number>
</property>
<property name="rightMargin">
<number>1</number>
</property>
<property name="bottomMargin">
<number>1</number>
</property>
<item>
<widget class="QToolButton" name="toolButton_stop_process">
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/redsquare.svg</normaloff>:/image/redsquare.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>12</width>
<height>12</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>45</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="QScrollBar" name="verticalScrollBar_log">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="TextEditorWidget" name="widget_log" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>80</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QScrollBar" name="horizontalScrollBar_log">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<action name="action_open_existing_working_copy">
<property name="text">
<string>&amp;Open existing working copy...</string>
</property>
<property name="toolTip">
<string>Add existing working copy</string>
</property>
</action>
<action name="action_view_refresh">
<property name="text">
<string>Refresh</string>
</property>
<property name="toolTip">
<string>Refresh</string>
</property>
<property name="shortcut">
<string>F5</string>
</property>
</action>
<action name="action_commit">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/commit.svg</normaloff>:/image/commit.svg</iconset>
</property>
<property name="text">
<string>&amp;Commit</string>
</property>
</action>
<action name="action_push">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/push.svg</normaloff>:/image/push.svg</iconset>
</property>
<property name="text">
<string>&amp;Push</string>
</property>
<property name="autoRepeat">
<bool>false</bool>
</property>
</action>
<action name="action_test">
<property name="text">
<string>test</string>
</property>
<property name="shortcut">
<string>Ctrl+T</string>
</property>
</action>
<action name="action_pull">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/pull.svg</normaloff>:/image/pull.svg</iconset>
</property>
<property name="text">
<string>Pu&amp;ll</string>
</property>
</action>
<action name="action_fetch">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/fetch.svg</normaloff>:/image/fetch.svg</iconset>
</property>
<property name="text">
<string>&amp;Fetch</string>
</property>
</action>
<action name="action_edit_global_gitconfig">
<property name="text">
<string>Edit global .gitconfig</string>
</property>
<property name="toolTip">
<string>Edit global .gitconfig</string>
</property>
</action>
<action name="action_edit_git_config">
<property name="text">
<string>Edit .git/config</string>
</property>
</action>
<action name="action_edit_gitignore">
<property name="text">
<string>Edit .gitignore</string>
</property>
</action>
<action name="action_edit_settings">
<property name="text">
<string>&amp;Settings...</string>
</property>
<property name="toolTip">
<string>Settings</string>
</property>
</action>
<action name="action_clone">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/clone.svg</normaloff>:/image/clone.svg</iconset>
</property>
<property name="text">
<string>Clone</string>
</property>
<property name="toolTip">
<string>Clone</string>
</property>
</action>
<action name="action_about">
<property name="text">
<string>&amp;About</string>
</property>
</action>
<action name="action_edit_tags">
<property name="text">
<string>Edit tags...</string>
</property>
<property name="toolTip">
<string>Edit tags</string>
</property>
</action>
<action name="action_push_all_tags">
<property name="text">
<string>Push all tags</string>
</property>
</action>
<action name="action_set_config_user">
<property name="text">
<string>Set config user</string>
</property>
</action>
<action name="action_window_log">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Log</string>
</property>
<property name="shortcut">
<string>F4</string>
</property>
</action>
<action name="action_repo_jump">
<property name="text">
<string>&amp;Jump...</string>
</property>
<property name="shortcut">
<string>Ctrl+J</string>
</property>
</action>
<action name="action_repo_checkout">
<property name="text">
<string>Check&amp;out...</string>
</property>
</action>
<action name="action_delete_branch">
<property name="text">
<string>Delete branch...</string>
</property>
</action>
<action name="action_push_u">
<property name="text">
<string>Push upstream</string>
</property>
</action>
<action name="action_reset_HEAD_1">
<property name="text">
<string>Reset HEAD~1</string>
</property>
<property name="toolTip">
<string>Reset HEAD~1</string>
</property>
</action>
<action name="action_create_a_repository">
<property name="text">
<string>Create a repository</string>
</property>
</action>
<action name="action_stop_process">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/redsquare.svg</normaloff>:/image/redsquare.svg</iconset>
</property>
<property name="text">
<string>Stop process</string>
</property>
</action>
<action name="action_exit">
<property name="text">
<string>E&amp;xit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="action_reflog">
<property name="text">
<string>Reflog...</string>
</property>
</action>
<action name="action_repository_property">
<property name="text">
<string>Property...</string>
</property>
</action>
<action name="action_set_gpg_signing">
<property name="text">
<string>Set GPG signing</string>
</property>
<property name="toolTip">
<string>Set GPG signing</string>
</property>
</action>
<action name="action_delete_remote_branch">
<property name="text">
<string>Delete remote branch...</string>
</property>
</action>
<action name="action_fetch_prune">
<property name="text">
<string>Fetch --prune</string>
</property>
</action>
<action name="action_terminal">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/terminal.svg</normaloff>:/image/terminal.svg</iconset>
</property>
<property name="text">
<string>Terminal</string>
</property>
</action>
<action name="action_explorer">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/explorer.svg</normaloff>:/image/explorer.svg</iconset>
</property>
<property name="text">
<string>Explorer</string>
</property>
</action>
<action name="action_clean_df">
<property name="text">
<string>Clean -df</string>
</property>
</action>
<action name="action_reset_hard">
<property name="text">
<string>Reset --hard</string>
</property>
</action>
<action name="action_stash">
<property name="text">
<string>Stash</string>
</property>
</action>
<action name="action_stash_apply">
<property name="text">
<string>Apply</string>
</property>
</action>
<action name="action_stash_drop">
<property name="text">
<string>Drop</string>
</property>
</action>
<action name="action_online">
<property name="text">
<string>Online</string>
</property>
</action>
<action name="action_offline">
<property name="text">
<string>Offline</string>
</property>
</action>
<action name="action_repositories_panel">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Repositories panel</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<action name="action_find">
<property name="text">
<string>&amp;Find...</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
<action name="action_find_next">
<property name="text">
<string>Find next</string>
</property>
<property name="shortcut">
<string>F3</string>
</property>
</action>
<action name="action_repository_status">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/status.svg</normaloff>:/image/status.svg</iconset>
</property>
<property name="text">
<string>Status</string>
</property>
</action>
<action name="action_repo_jump_to_head">
<property name="text">
<string>Jump to &amp;HEAD</string>
</property>
<property name="shortcut">
<string>Ctrl+H</string>
</property>
</action>
<action name="action_repo_merge">
<property name="text">
<string>Merge...</string>
</property>
</action>
<action name="action_expand_commit_log">
<property name="text">
<string>Expand commit log</string>
</property>
<property name="shortcut">
<string>Ctrl+2</string>
</property>
</action>
<action name="action_expand_file_list">
<property name="text">
<string>Expand file list</string>
</property>
<property name="shortcut">
<string>Ctrl+3</string>
</property>
</action>
<action name="action_expand_diff_view">
<property name="text">
<string>Expand diff view</string>
</property>
<property name="shortcut">
<string>Ctrl+4</string>
</property>
</action>
<action name="action_sidebar">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
<property name="text">
<string>Sidebar</string>
</property>
<property name="shortcut">
- <string>F1</string>
+ <string>Ctrl+1</string>
</property>
</action>
<action name="action_wide">
<property name="text">
<string>Wide</string>
</property>
<property name="shortcut">
<string>F2</string>
</property>
</action>
+ <action name="action_show_labels">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Show labels</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+L</string>
+ </property>
+ </action>
+ <action name="action_submodule_add">
+ <property name="text">
+ <string>Submodule add...</string>
+ </property>
+ </action>
+ <action name="action_submodules">
+ <property name="text">
+ <string>Submodules</string>
+ </property>
+ </action>
+ <action name="action_submodule_update">
+ <property name="text">
+ <string>Submodule update...</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>FileDiffWidget</class>
<extends>QWidget</extends>
<header>FileDiffWidget.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>LogTableWidget</class>
+ <extends>QTableWidget</extends>
+ <header>LogTableWidget.h</header>
+ </customwidget>
+ <customwidget>
+ <class>MyToolButton</class>
+ <extends>QToolButton</extends>
+ <header>MyToolButton.h</header>
+ </customwidget>
+ <customwidget>
+ <class>FilesListWidget</class>
+ <extends>QListWidget</extends>
+ <header>FilesListWidget.h</header>
+ </customwidget>
+ <customwidget>
+ <class>RepositoryWrapperFrame</class>
+ <extends>QFrame</extends>
+ <header>RepositoryWrapperFrame.h</header>
+ <container>1</container>
+ </customwidget>
<customwidget>
<class>ReadOnlyLineEdit</class>
<extends>QLineEdit</extends>
<header>ReadOnlyLineEdit.h</header>
</customwidget>
<customwidget>
<class>TextEditorWidget</class>
<extends>QWidget</extends>
<header>TextEditorWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ClearButton</class>
<extends>QToolButton</extends>
<header>ClearButton.h</header>
</customwidget>
- <customwidget>
- <class>LogTableWidget</class>
- <extends>QTableWidget</extends>
- <header>LogTableWidget.h</header>
- </customwidget>
<customwidget>
<class>RepositoryInfoFrame</class>
<extends>QFrame</extends>
<header>RepositoryInfoFrame.h</header>
<container>1</container>
</customwidget>
- <customwidget>
- <class>MyToolButton</class>
- <extends>QToolButton</extends>
- <header>MyToolButton.h</header>
- </customwidget>
<customwidget>
<class>RepositoriesTreeWidget</class>
<extends>QTreeWidget</extends>
<header>RepositoriesTreeWidget.h</header>
</customwidget>
- <customwidget>
- <class>FilesListWidget</class>
- <extends>QListWidget</extends>
- <header>FilesListWidget.h</header>
- </customwidget>
</customwidgets>
<tabstops>
<tabstop>tableWidget_log</tabstop>
<tabstop>listWidget_unstaged</tabstop>
<tabstop>toolButton_select_all</tabstop>
<tabstop>toolButton_stage</tabstop>
<tabstop>toolButton_unstage</tabstop>
<tabstop>toolButton_commit</tabstop>
<tabstop>listWidget_staged</tabstop>
<tabstop>listWidget_files</tabstop>
<tabstop>widget_diff_view</tabstop>
<tabstop>toolButton_stop_process</tabstop>
<tabstop>widget_log</tabstop>
</tabstops>
<resources>
<include location="resources/resources.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/src/MergeDialog.cpp b/src/MergeDialog.cpp
index 64da9ad..0c215cc 100644
--- a/src/MergeDialog.cpp
+++ b/src/MergeDialog.cpp
@@ -1,73 +1,77 @@
#include "MergeDialog.h"
#include "ui_MergeDialog.h"
MergeDialog::MergeDialog(QString const &fastforward, const std::vector<QString> &labels, const QString curr_branch_name, QWidget *parent)
: QDialog(parent)
, ui(new Ui::MergeDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
setFastForwardPolicy(fastforward);
int select = 0;
for (size_t i = 0; i < labels.size(); i++) {
QString const &label = labels[i];
ui->listWidget_from->addItem(label);
if (label == curr_branch_name) {
select = i;
}
}
ui->listWidget_from->setCurrentRow(select);
}
MergeDialog::~MergeDialog()
{
delete ui;
}
QString MergeDialog::getFastForwardPolicy() const
{
if (ui->radioButton_ff_only->isChecked()) return "only";
if (ui->radioButton_ff_no->isChecked()) return "no";
return "default";
}
void MergeDialog::setFastForwardPolicy(QString const &ff)
{
if (ff.compare("only", Qt::CaseInsensitive) == 0) {
ui->radioButton_ff_only->click();
return;
}
if (ff.compare("no", Qt::CaseInsensitive) == 0) {
ui->radioButton_ff_no->click();
return;
}
ui->radioButton_ff_default->click();
}
Git::MergeFastForward MergeDialog::ff(QString const &ff)
{
if (ff.compare("only", Qt::CaseInsensitive) == 0) {
return Git::MergeFastForward::Only;
}
if (ff.compare("no", Qt::CaseInsensitive) == 0) {
return Git::MergeFastForward::No;
}
return Git::MergeFastForward::Default;
}
QString MergeDialog::mergeFrom() const
{
QListWidgetItem *p = ui->listWidget_from->currentItem();
return p ? p->text() : QString();
}
+bool MergeDialog::isSquashEnabled() const
+{
+ return ui->checkBox_squash->isChecked();
+}
void MergeDialog::on_listWidget_from_itemDoubleClicked(QListWidgetItem *item)
{
(void)item;
done(QDialog::Accepted);
}
diff --git a/src/MergeDialog.h b/src/MergeDialog.h
index 6408c4b..f62e8f2 100644
--- a/src/MergeDialog.h
+++ b/src/MergeDialog.h
@@ -1,30 +1,31 @@
#ifndef MERGEDIALOG_H
#define MERGEDIALOG_H
#include "Git.h"
#include <QDialog>
class QListWidgetItem;
namespace Ui {
class MergeDialog;
}
class MergeDialog : public QDialog {
Q_OBJECT
public:
explicit MergeDialog(const QString &fastforward, std::vector<QString> const &labels, QString const curr_branch_name, QWidget *parent = nullptr);
~MergeDialog();
QString getFastForwardPolicy() const;
void setFastForwardPolicy(const QString &ff);
QString mergeFrom() const;
static Git::MergeFastForward ff(const QString &ff);
+ bool isSquashEnabled() const;
private slots:
void on_listWidget_from_itemDoubleClicked(QListWidgetItem *item);
private:
Ui::MergeDialog *ui;
};
#endif // MERGEDIALOG_H
diff --git a/src/MergeDialog.ui b/src/MergeDialog.ui
index 0641f40..3af609b 100644
--- a/src/MergeDialog.ui
+++ b/src/MergeDialog.ui
@@ -1,135 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MergeDialog</class>
<widget class="QDialog" name="MergeDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>430</width>
- <height>274</height>
+ <width>437</width>
+ <height>322</height>
</rect>
</property>
<property name="windowTitle">
<string>Merge</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Fast Forwarding</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="radioButton_ff_default">
<property name="text">
<string>Default (--ff)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_ff_no">
<property name="text">
<string>No fast forward (--no-ff)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_ff_only">
<property name="text">
<string>Fast forward only (--ff-only)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_squash">
+ <property name="text">
+ <string>Squash</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>From</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListWidget" name="listWidget_from"/>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>radioButton_ff_default</tabstop>
<tabstop>radioButton_ff_no</tabstop>
<tabstop>radioButton_ff_only</tabstop>
<tabstop>listWidget_from</tabstop>
<tabstop>pushButton</tabstop>
<tabstop>pushButton_2</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>MergeDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>174</x>
<y>229</y>
</hint>
<hint type="destinationlabel">
<x>153</x>
<y>228</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_2</sender>
<signal>clicked()</signal>
<receiver>MergeDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>287</x>
<y>229</y>
</hint>
<hint type="destinationlabel">
<x>327</x>
<y>233</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/MyImageViewWidget.cpp b/src/MyImageViewWidget.cpp
index 8090ef3..cf28be2 100644
--- a/src/MyImageViewWidget.cpp
+++ b/src/MyImageViewWidget.cpp
@@ -1,52 +1,52 @@
#include "MyImageViewWidget.h"
#include <QMenu>
#include <QFileDialog>
#include "common/joinpath.h"
#include "common/misc.h"
MyImageViewWidget::MyImageViewWidget(QWidget *parent)
: ImageViewWidget(parent)
{
}
void MyImageViewWidget::setImage(QString const &mimetype, QByteArray const &ba, QString const &object_id, QString const &path)
{
this->object_id_ = object_id;
this->path_ = path;
ImageViewWidget::setImage(mimetype, ba);
}
void MyImageViewWidget::contextMenuEvent(QContextMenuEvent *e)
{
QString id = object_id_;
if (id.startsWith(PATH_PREFIX)) {
// pass
} else if (Git::isValidID(id)) {
// pass
} else {
return; // invalid id
}
QMenu menu;
QAction *a_save_as = id.isEmpty() ? nullptr : menu.addAction(tr("Save as..."));
if (!menu.actions().isEmpty()) {
update();
QAction *a = menu.exec(misc::contextMenuPos(this, e));
if (a) {
if (a == a_save_as) {
auto *mw = qobject_cast<MainWindow *>(mainwindow());
QString path = mw->currentWorkingCopyDir() / path_;
QString dstpath = QFileDialog::getSaveFileName(window(), tr("Save as"), path);
if (!dstpath.isEmpty()) {
- mw->saveAs(id, dstpath);
+ mw->saveAs(mw->frame(), id, dstpath);
}
update();
return;
}
}
}
}
diff --git a/src/MyTextEditorWidget.cpp b/src/MyTextEditorWidget.cpp
index 67c9780..7a9fe0e 100644
--- a/src/MyTextEditorWidget.cpp
+++ b/src/MyTextEditorWidget.cpp
@@ -1,57 +1,57 @@
#include "MyTextEditorWidget.h"
#include "MainWindow.h"
#include "common/misc.h"
#include <QMenu>
#include <QFileDialog>
#include "common/joinpath.h"
MyTextEditorWidget::MyTextEditorWidget(QWidget *parent)
: TextEditorWidget(parent)
{
}
-void MyTextEditorWidget::setDocument(const QList<Document::Line> *source, BasicMainWindow *mw, QString const &object_id, QString const &object_path)
+void MyTextEditorWidget::setDocument(const QList<Document::Line> *source, MainWindow *mw, QString const &object_id, QString const &object_path)
{
this->mainwindow = mw;
this->object_id = object_id;
this->object_path = object_path;
TextEditorWidget::setDocument(source);
}
void MyTextEditorWidget::contextMenuEvent(QContextMenuEvent *event)
{
QString id = object_id;
if (id.startsWith(PATH_PREFIX)) {
// pass
} else if (Git::isValidID(id)) {
// pass
} else {
return; // invalid id
}
QMenu menu;
QAction *a_save_as = id.isEmpty() ? nullptr : menu.addAction(tr("Save as..."));
QAction *a_copy = menu.addAction(tr("Copy"));
if (!menu.actions().isEmpty()) {
update();
QAction *a = menu.exec(misc::contextMenuPos(this, event));
if (a) {
if (a == a_save_as) {
QString path = mainwindow->currentWorkingCopyDir() / object_path;
QString dstpath = QFileDialog::getSaveFileName(window(), tr("Save as"), path);
if (!dstpath.isEmpty()) {
- mainwindow->saveAs(id, dstpath);
+ mainwindow->saveAs(mainwindow->frame(), id, dstpath);
}
update();
return;
}
if (a == a_copy) {
editCopy();
return;
}
}
}
}
diff --git a/src/MyTextEditorWidget.h b/src/MyTextEditorWidget.h
index 616e828..2b78050 100644
--- a/src/MyTextEditorWidget.h
+++ b/src/MyTextEditorWidget.h
@@ -1,20 +1,20 @@
#ifndef MYTEXTEDITORWIDGET_H
#define MYTEXTEDITORWIDGET_H
#include "texteditor/TextEditorWidget.h"
-class BasicMainWindow;
+class MainWindow;
class MyTextEditorWidget : public TextEditorWidget {
private:
- BasicMainWindow *mainwindow;
+ MainWindow *mainwindow;
QString object_id;
QString object_path;
public:
MyTextEditorWidget(QWidget *parent = nullptr);
- void setDocument(const QList<Document::Line> *source, BasicMainWindow *mw, QString const &object_id, QString const &object_path);
+ void setDocument(const QList<Document::Line> *source, MainWindow *mw, QString const &object_id, QString const &object_path);
protected:
void contextMenuEvent(QContextMenuEvent *event) override;
};
#endif // MYTEXTEDITORWIDGET_H
diff --git a/src/MyToolButton.cpp b/src/MyToolButton.cpp
index 299b80b..dde7a4d 100644
--- a/src/MyToolButton.cpp
+++ b/src/MyToolButton.cpp
@@ -1,66 +1,67 @@
#include "MyToolButton.h"
#include "MainWindow.h"
+#include "SubmoduleMainWindow.h"
#include <QPainter>
MyToolButton::MyToolButton(QWidget *parent)
: QToolButton(parent)
{
}
void MyToolButton::setIndicatorMode(Indicator i)
{
indicator = i;
update();
}
void MyToolButton::setDot(bool f)
{
indicator = Dot;
number = f;
update();
}
void MyToolButton::setNumber(int n)
{
indicator = Number;
number = n;
update();
}
void MyToolButton::paintEvent(QPaintEvent *event)
{
QToolButton::paintEvent(event);
- auto *mw = qobject_cast<MainWindow *>(window());
+ MainWindow *mw = qobject_cast<MainWindow *>(window());
Q_ASSERT(mw);
if (indicator == Dot && number > 0) {
QPainter pr(this);
pr.setRenderHint(QPainter::Antialiasing);
int z = 10;
QRect r(width() - z, 0, z, z);
pr.setPen(Qt::NoPen);
pr.setBrush(QColor(255, 0, 0));
pr.drawEllipse(r);
} else if (indicator == Number && number >= 0) {
int w = mw->digitWidth();
int h = mw->digitHeight();
QPixmap pm;
{
char tmp[100];
int n = sprintf(tmp, "%u", number);
pm = QPixmap((w + 1) * n + 3, h + 4);
pm.fill(Qt::red);
QPainter pr(&pm);
for (int i = 0; i < n; i++) {
mw->drawDigit(&pr, 2 + i * (w + 1), 2, tmp[i] - '0');
}
}
QPainter pr(this);
pr.drawPixmap(width() - pm.width(), 0, pm);
}
}
diff --git a/src/ObjectBrowserDialog.cpp b/src/ObjectBrowserDialog.cpp
index d656dd2..f0ab347 100644
--- a/src/ObjectBrowserDialog.cpp
+++ b/src/ObjectBrowserDialog.cpp
@@ -1,124 +1,124 @@
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "ui_ObjectBrowserDialog.h"
#include "ObjectBrowserDialog.h"
#include <QMessageBox>
enum {
IdRole = Qt::UserRole,
TypeRole,
};
-ObjectBrowserDialog::ObjectBrowserDialog(BasicMainWindow *parent, const QStringList &list)
+ObjectBrowserDialog::ObjectBrowserDialog(MainWindow *parent, const QStringList &list)
: QDialog(parent)
, ui(new Ui::ObjectBrowserDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
GitPtr g = git();
QStringList cols = {
tr("ID"),
tr("Type"),
};
auto NewQTableWidgetItem = [](){
auto *item = new QTableWidgetItem();
return item;
};
ui->tableWidget->setColumnCount(cols.size());
for (int i = 0; i < cols.size(); i++) {
QTableWidgetItem *item = NewQTableWidgetItem();
item->setText(cols[i]);
ui->tableWidget->setHorizontalHeaderItem(i, item);
}
ui->tableWidget->setRowCount(list.size());
for (int row = 0; row < list.size(); row++) {
QString const &text = list[row];
QString type = g->objectType(text);
auto *item0 = NewQTableWidgetItem();
item0->setData(IdRole, text);
item0->setData(TypeRole, type);
item0->setText(text);
ui->tableWidget->setItem(row, 0, item0);
auto *item1 = NewQTableWidgetItem();
item1->setText(type);
ui->tableWidget->setItem(row, 1, item1);
}
ui->tableWidget->resizeColumnsToContents();
ui->tableWidget->setCurrentItem(ui->tableWidget->item(0, 0));
}
ObjectBrowserDialog::~ObjectBrowserDialog()
{
delete ui;
}
-BasicMainWindow *ObjectBrowserDialog::mainwindow()
+MainWindow *ObjectBrowserDialog::mainwindow()
{
- return qobject_cast<BasicMainWindow *>(parent());
+ return qobject_cast<MainWindow *>(parent());
}
GitPtr ObjectBrowserDialog::git()
{
return mainwindow()->git();
}
QString ObjectBrowserDialog::text() const
{
int row = ui->tableWidget->currentRow();
auto *item = ui->tableWidget->item(row, 0);
if (item) {
return item->data(IdRole).toString();
}
return QString();
}
void ObjectBrowserDialog::on_tableWidget_itemDoubleClicked(QTableWidgetItem *item)
{
(void)item;
done(QDialog::Accepted);
}
void ObjectBrowserDialog::on_tableWidget_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous)
{
(void)current;
(void)previous;
// if (current) {
// QString id = current->data(IdRole).toString();
// QString ty = current->data(TypeRole).toString();
// if (Git::isValidID(id) && ty == "commit") {
// }
// }
}
void ObjectBrowserDialog::on_pushButton_inspect_clicked()
{
int row = ui->tableWidget->currentRow();
auto *item = ui->tableWidget->item(row, 0);
if (item) {
GitPtr g = git();
QString id = item->data(IdRole).toString();
QString ty = item->data(TypeRole).toString();
if (Git::isValidID(id) && ty == "commit") {
Git::CommitItem commit;
if (g->queryCommit(id, &commit)) {
- mainwindow()->execCommitPropertyDialog(this, &commit);
+ mainwindow()->execCommitPropertyDialog(this, mainwindow()->frame(), &commit);
}
} else {
QMessageBox::information(this, tr("Object Inspection"), id + "\n\n" + ty);
}
}
}
diff --git a/src/ObjectBrowserDialog.h b/src/ObjectBrowserDialog.h
index 6188a15..01fd730 100644
--- a/src/ObjectBrowserDialog.h
+++ b/src/ObjectBrowserDialog.h
@@ -1,31 +1,31 @@
#ifndef OBJECTBROWSERDIALOG_H
#define OBJECTBROWSERDIALOG_H
#include <QDialog>
#include "Git.h"
-class BasicMainWindow;
+class MainWindow;
class QListWidgetItem;
class QTableWidgetItem;
namespace Ui {
class ObjectBrowserDialog;
}
class ObjectBrowserDialog : public QDialog {
Q_OBJECT
private:
Ui::ObjectBrowserDialog *ui;
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
GitPtr git();
public:
- explicit ObjectBrowserDialog(BasicMainWindow *parent, QStringList const &list);
+ explicit ObjectBrowserDialog(MainWindow *parent, QStringList const &list);
~ObjectBrowserDialog() override;
QString text() const;
private slots:
void on_pushButton_inspect_clicked();
void on_tableWidget_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous);
void on_tableWidget_itemDoubleClicked(QTableWidgetItem *item);
};
#endif // OBJECTBROWSERDIALOG_H
diff --git a/src/ReflogWindow.cpp b/src/ReflogWindow.cpp
index 6a59c92..9905296 100644
--- a/src/ReflogWindow.cpp
+++ b/src/ReflogWindow.cpp
@@ -1,126 +1,126 @@
#include "CommitExploreWindow.h"
#include "ReflogWindow.h"
#include "ui_ReflogWindow.h"
#include "MainWindow.h"
#include "Git.h"
#include <QMenu>
ReflogWindow::ReflogWindow(QWidget *parent, MainWindow *mainwin, Git::ReflogItemList const &reflog)
: QDialog(parent)
, ui(new Ui::ReflogWindow)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
flags |= Qt::WindowMaximizeButtonHint;
setWindowFlags(flags);
mainwindow_ = mainwin;
reflog_ = reflog;
updateTable(reflog_);
}
ReflogWindow::~ReflogWindow()
{
delete ui;
}
void ReflogWindow::updateTable(Git::ReflogItemList const &reflog)
{
QTableWidgetItem *item;
ui->tableWidget->clear();
QStringList cols = {
tr("Commit"),
tr("Head"),
tr("Command"),
tr("Message"),
};
auto newQTableWidgetItem = [](QString const &text){
auto *item = new QTableWidgetItem(text);
return item;
};
ui->tableWidget->setColumnCount(cols.size());
ui->tableWidget->setRowCount(reflog.size());
for (int col = 0; col < cols.size(); col++) {
item = newQTableWidgetItem(cols[col]);
ui->tableWidget->setHorizontalHeaderItem(col, item);
}
int row = 0;
for (Git::ReflogItem const &t : reflog) {
QString text = t.id.mid(0, 7);
item = newQTableWidgetItem(text);
ui->tableWidget->setItem(row, 0, item);
item = newQTableWidgetItem(t.head);
ui->tableWidget->setItem(row, 1, item);
QString cmd = t.command;
int i = cmd.indexOf(' ');
if (i < 0) i = cmd.size();
if (i > 10) i = 10;
cmd = cmd.mid(0, i);
item = newQTableWidgetItem(cmd);
ui->tableWidget->setItem(row, 2, item);
item = newQTableWidgetItem(t.message);
ui->tableWidget->setItem(row, 3, item);
ui->tableWidget->setRowHeight(row, 24);
row++;
}
ui->tableWidget->resizeColumnsToContents();
ui->tableWidget->horizontalHeader()->setStretchLastSection(true);
}
bool ReflogWindow::currentCommit(Git::CommitItem *out)
{
bool ok = false;
*out = Git::CommitItem();
int row = ui->tableWidget->currentRow();
if (row >= 0 && row < reflog_.size()) {
Git::ReflogItem const &logitem = reflog_[row];
if (mainwindow()->queryCommit(logitem.id, out)) {
ok = true;
}
}
return ok;
}
void ReflogWindow::on_tableWidget_customContextMenuRequested(const QPoint &pos)
{
Git::CommitItem commit;
if (!currentCommit(&commit)) return;
QMenu menu;
QAction *a_checkout = menu.addAction(tr("Checkout"));
QAction *a_explorer = menu.addAction(tr("Explorer"));
QAction *a_property = mainwindow()->addMenuActionProperty(&menu);
QAction *a = menu.exec(ui->tableWidget->viewport()->mapToGlobal(pos) + QPoint(8, -8));
if (a) {
if (a == a_checkout) {
- mainwindow()->checkout(this, &commit);
+ mainwindow()->checkout(mainwindow()->frame(), this, &commit);
return;
}
if (a == a_explorer) {
- mainwindow()->execCommitExploreWindow(this, &commit);
+ mainwindow()->execCommitExploreWindow(mainwindow()->frame(), this, &commit);
return;
}
if (a == a_property) {
- mainwindow()->execCommitPropertyDialog(this, &commit);
+ mainwindow()->execCommitPropertyDialog(this, mainwindow()->frame(), &commit);
return;
}
}
}
void ReflogWindow::on_tableWidget_itemDoubleClicked(QTableWidgetItem *item)
{
(void)item;
Git::CommitItem commit;
if (!currentCommit(&commit)) return;
- mainwindow()->execCommitPropertyDialog(this, &commit);
+ mainwindow()->execCommitPropertyDialog(this, mainwindow()->frame(), &commit);
}
diff --git a/src/RemoteAdvancedOptionWidget.cpp b/src/RemoteAdvancedOptionWidget.cpp
new file mode 100644
index 0000000..a5b099c
--- /dev/null
+++ b/src/RemoteAdvancedOptionWidget.cpp
@@ -0,0 +1,54 @@
+#include "RemoteAdvancedOptionWidget.h"
+#include "ui_RemoteAdvancedOptionWidget.h"
+
+#include <QFileDialog>
+#include <QStandardPaths>
+
+RemoteAdvancedOptionWidget::RemoteAdvancedOptionWidget(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::RemoteAdvancedOptionWidget)
+{
+ ui->setupUi(this);
+}
+
+RemoteAdvancedOptionWidget::~RemoteAdvancedOptionWidget()
+{
+ delete ui;
+}
+
+void RemoteAdvancedOptionWidget::setSshKeyOverrigingEnabled(bool enabled)
+{
+ ui->pushButton_browse_ssh_key->setEnabled(enabled);
+ ui->pushButton_clear_ssh_key->setEnabled(enabled);
+ ui->lineEdit_ssh_key->setEnabled(enabled);
+ ui->lineEdit_ssh_key->setText(enabled ? QString() : tr("SSH command is not registered."));
+}
+
+void RemoteAdvancedOptionWidget::on_pushButton_browse_ssh_key_clicked()
+{
+ QString path = QStandardPaths::locate(QStandardPaths::HomeLocation, ".ssh", QStandardPaths::LocateDirectory);
+ path = QFileDialog::getOpenFileName(this, tr("SSH key override"), path);
+ if (!path.isEmpty()) {
+ ui->lineEdit_ssh_key->setText(path);
+ }
+}
+
+void RemoteAdvancedOptionWidget::on_pushButton_clear_ssh_key_clicked()
+{
+ ui->lineEdit_ssh_key->clear();
+}
+
+QString RemoteAdvancedOptionWidget::sshKey() const
+{
+ return ui->lineEdit_ssh_key->isEnabled() ? ui->lineEdit_ssh_key->text() : QString();
+}
+
+void RemoteAdvancedOptionWidget::setSshKey(const QString &s)
+{
+ ui->lineEdit_ssh_key->setText(s);
+}
+
+void RemoteAdvancedOptionWidget::clearSshKey()
+{
+ ui->lineEdit_ssh_key->clear();
+}
diff --git a/src/RemoteAdvancedOptionWidget.h b/src/RemoteAdvancedOptionWidget.h
new file mode 100644
index 0000000..5305ce1
--- /dev/null
+++ b/src/RemoteAdvancedOptionWidget.h
@@ -0,0 +1,28 @@
+#ifndef REMOTEADVANCEDOPTIONWIDGET_H
+#define REMOTEADVANCEDOPTIONWIDGET_H
+
+#include <QWidget>
+
+namespace Ui {
+class RemoteAdvancedOptionWidget;
+}
+
+class RemoteAdvancedOptionWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit RemoteAdvancedOptionWidget(QWidget *parent = nullptr);
+ ~RemoteAdvancedOptionWidget();
+ void setSshKeyOverrigingEnabled(bool enabled);
+ QString sshKey() const;
+ void setSshKey(QString const &s);
+ void clearSshKey();
+private slots:
+ void on_pushButton_browse_ssh_key_clicked();
+ void on_pushButton_clear_ssh_key_clicked();
+private:
+ Ui::RemoteAdvancedOptionWidget *ui;
+};
+
+#endif // REMOTEADVANCEDOPTIONWIDGET_H
diff --git a/src/RemoteAdvancedOptionWidget.ui b/src/RemoteAdvancedOptionWidget.ui
new file mode 100644
index 0000000..633f4e3
--- /dev/null
+++ b/src/RemoteAdvancedOptionWidget.ui
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>RemoteAdvancedOptionWidget</class>
+ <widget class="QWidget" name="RemoteAdvancedOptionWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>740</width>
+ <height>78</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Advanced Option</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="pushButton_browse_ssh_key">
+ <property name="text">
+ <string>SSH key override...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton_clear_ssh_key">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ReadOnlyLineEdit" name="lineEdit_ssh_key"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ReadOnlyLineEdit</class>
+ <extends>QLineEdit</extends>
+ <header>ReadOnlyLineEdit.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/RemoteWatcher.cpp b/src/RemoteWatcher.cpp
deleted file mode 100644
index d3a9b5d..0000000
--- a/src/RemoteWatcher.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-#include "RemoteWatcher.h"
-#include "common/joinpath.h"
-#include <QFileInfo>
-
-void RemoteWatcher::start(MainWindow *mw)
-{
- mainwindow_ = mw;
- QThread::start();
- moveToThread(this);
-}
-
-void RemoteWatcher::setCurrent(QString const &remote, QString const &branch)
-{
- remote_ = remote;
- branch_ = branch;
-}
-
-void RemoteWatcher::checkRemoteUpdate()
-{
- QString dir = mainwindow()->currentWorkingCopyDir();
-
- QString refs;
- QString local_id;
-
- if (QFileInfo(dir).isDir()) {
- refs = "refs/remotes" / remote_ / branch_;
- QString head = dir / ".git" / refs;
- QFile file1(head);
- if (file1.open(QFile::ReadOnly)) {
- local_id = file1.readLine().trimmed();
- }
- refs = "refs/heads" / branch_;
- }
- if (local_id.isEmpty()) return;
-
- QString remote_id;
- GitPtr g = mainwindow()->git();
- auto list = g->ls_remote();
- for (auto const &item : list) {
- if (item.name == refs) {
- remote_id = item.commit_id;
- break;
- }
- }
-
- bool changed = remote_id != local_id;
- mainwindow()->notifyRemoteChanged(changed);
-}
-
diff --git a/src/RemoteWatcher.h b/src/RemoteWatcher.h
deleted file mode 100644
index 10631ae..0000000
--- a/src/RemoteWatcher.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef REMOTEWATCHER_H
-#define REMOTEWATCHER_H
-
-#include <QThread>
-
-#include "MainWindow.h"
-
-class RemoteWatcher : public QThread {
- Q_OBJECT
-private:
- MainWindow *mainwindow_ = nullptr;
- QString remote_;
- QString branch_;
- MainWindow *mainwindow()
- {
- return mainwindow_;
- }
-public:
- RemoteWatcher() = default;
- void start(MainWindow *mw);
- void setCurrent(QString const &remote, QString const &branch);
-public slots:
- void checkRemoteUpdate();
-};
-
-#endif // REMOTEWATCHER_H
diff --git a/src/RepositoryData.cpp b/src/RepositoryData.cpp
index c80b3c4..c2f7ab4 100644
--- a/src/RepositoryData.cpp
+++ b/src/RepositoryData.cpp
@@ -1,126 +1,135 @@
#include "RepositoryData.h"
#include <QXmlStreamWriter>
#include <QXmlStreamReader>
#include <QFile>
#include <vector>
class TagState {
private:
std::vector<ushort> arr;
public:
void push(QString const &tag)
{
if (arr.empty()) {
arr.reserve(100);
}
size_t s = arr.size();
size_t t = tag.size();
arr.resize(s + t + 1);
arr[s] = '/';
std::copy_n(tag.utf16(), t, &arr[s + 1]);
}
void push(QStringRef const &tag)
{
push(tag.toString());
}
void pop()
{
size_t s = arr.size();
while (s > 0) {
s--;
if (arr[s] == '/') break;
}
arr.resize(s);
}
bool is(char const *path) const
{
size_t s = arr.size();
for (size_t i = 0; i < s; i++) {
if (arr[i] != path[i]) return false;
}
return path[s] == 0;
}
bool operator == (char const *path) const
{
return is(path);
}
QString str() const
{
return arr.empty() ? QString() : QString::fromUtf16(&arr[0], arr.size());
}
};
bool RepositoryBookmark::save(QString const &path, QList<RepositoryItem> const *items)
{
QFile file(path);
if (file.open(QFile::WriteOnly)) {
QXmlStreamWriter writer(&file);
writer.setAutoFormatting(true);
writer.writeStartDocument();
writer.writeStartElement("repositories");
for (RepositoryItem const &item : *items) {
if (item.name.isEmpty() && item.local_dir.isEmpty()) {
continue;
}
writer.writeStartElement("repository");
writer.writeAttribute("name", item.name);
writer.writeAttribute("group", item.group);
if (!item.local_dir.isEmpty()) {
QString local = item.local_dir;
local.replace('\\', '/');
writer.writeStartElement("local");
writer.writeCharacters(local);
writer.writeEndElement(); // local
}
+ if (!item.ssh_key.isEmpty()) {
+ writer.writeStartElement("sshkey");
+ writer.writeCharacters(item.ssh_key);
+ writer.writeEndElement(); // sshkey
+ }
writer.writeEndElement(); // repository
}
writer.writeEndElement(); // repositories
writer.writeEndDocument();
return true;
}
return false;
}
QList<RepositoryItem> RepositoryBookmark::load(QString const &path)
{
QList<RepositoryItem> items;
QFile file(path);
if (file.open(QFile::ReadOnly)) {
RepositoryItem item;
QString text;
TagState state;
QXmlStreamReader reader(&file);
reader.setNamespaceProcessing(false);
while (!reader.atEnd()) {
QXmlStreamReader::TokenType tt = reader.readNext();
switch (tt) {
case QXmlStreamReader::StartElement:
state.push(reader.name());
{
QXmlStreamAttributes atts = reader.attributes();
if (state == "/repositories/repository") {
item = RepositoryItem();
item.name = atts.value("name").toString();
item.group = atts.value("group").toString();
} else if (state == "/repositories/repository/local") {
text = QString();
+ } else if (state == "/repositories/repository/sshkey") {
+ text = QString();
}
}
break;
case QXmlStreamReader::EndElement:
if (state == "/repositories/repository/local") {
item.local_dir = text;
item.local_dir.replace('\\', '/');
+ } else if (state == "/repositories/repository/sshkey") {
+ item.ssh_key = text;
} else if (state == "/repositories/repository") {
items.push_back(item);
}
state.pop();
break;
case QXmlStreamReader::Characters:
text.append(reader.text());
break;
}
}
}
return items;
}
diff --git a/src/RepositoryData.h b/src/RepositoryData.h
index e6736c0..05e7c91 100644
--- a/src/RepositoryData.h
+++ b/src/RepositoryData.h
@@ -1,26 +1,27 @@
#ifndef REPOSITORYDATA_H
#define REPOSITORYDATA_H
#include <QList>
#include <QMetaType>
enum class ServerType {
Standard,
GitHub,
};
struct RepositoryItem {
QString name;
QString group;
QString local_dir;
+ QString ssh_key;
};
Q_DECLARE_METATYPE(RepositoryItem);
class RepositoryBookmark {
public:
RepositoryBookmark() = default;
static bool save(QString const &path, QList<RepositoryItem> const *items);
static QList<RepositoryItem> load(QString const &path);
};
#endif // REPOSITORYDATA_H
diff --git a/src/RepositoryPropertyDialog.cpp b/src/RepositoryPropertyDialog.cpp
index 6db74ed..f73bd87 100644
--- a/src/RepositoryPropertyDialog.cpp
+++ b/src/RepositoryPropertyDialog.cpp
@@ -1,144 +1,222 @@
#include "RepositoryPropertyDialog.h"
#include "ui_RepositoryPropertyDialog.h"
#include "MainWindow.h"
+#include "BasicRepositoryDialog.h"
#include "common/misc.h"
#include <QClipboard>
#include <QMenu>
#include <QMessageBox>
-RepositoryPropertyDialog::RepositoryPropertyDialog(BasicMainWindow *parent, GitPtr const &g, RepositoryItem const &item, bool open_repository_menu)
+RepositoryPropertyDialog::RepositoryPropertyDialog(MainWindow *parent, Git::Context const *gcx, GitPtr const &g, RepositoryItem const &item, bool open_repository_menu)
: BasicRepositoryDialog(parent, g)
, ui(new Ui::RepositoryPropertyDialog)
+ , gcx(gcx)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
repository = item;
ui->groupBox_remote->setVisible(open_repository_menu);
- ui->label_name->setText(repository.name);
- ui->lineEdit_local_dir->setText(misc::normalizePathSeparator(repository.local_dir));
+ ui->label_editable_name->setText(repository.name);
+ ui->label_editable_name->setVisible(true);
+ ui->lineEdit_name->setText(repository.name);
+ ui->lineEdit_name->setVisible(false);
+ ui->lineEdit_local_dir->setText(misc::normalizePathSeparator(repository.local_dir));
+
+ ui->pushButton_close->setFocus();
updateRemotesTable();
}
RepositoryPropertyDialog::~RepositoryPropertyDialog()
{
delete ui;
}
void RepositoryPropertyDialog::updateRemotesTable()
{
BasicRepositoryDialog::updateRemotesTable(ui->tableWidget);
}
void RepositoryPropertyDialog::toggleRemoteMenuActivity()
{
ui->groupBox_remote->setVisible(!ui->groupBox_remote->isVisible());
}
-
+void MainWindow::changeSshKey(QString const &localdir, QString const &sshkey)
+{
+ bool changed = false;
+ QList<RepositoryItem> *repos = getReposPtr();
+ for (int i = 0; i < repos->size(); i++) {
+ RepositoryItem *item = &(*repos)[i];
+ if (item->local_dir == localdir) {
+ item->ssh_key = sshkey;
+ changed = true;
+ }
+ }
+ if (changed) {
+ saveRepositoryBookmarks();
+ }
+}
bool RepositoryPropertyDialog::execEditRemoteDialog(Git::Remote *remote, EditRemoteDialog::Operation op)
{
auto const *list = remotes();
if (op == EditRemoteDialog::RemoteSet) {
int row = ui->tableWidget->currentRow();
if (row >= 0 && row < list->size()) {
*remote = list->at(row);
}
} else {
*remote = Git::Remote();
}
if (remote->name.isEmpty() && list->isEmpty()) {
op = EditRemoteDialog::RemoteAdd;
remote->name = "origin";
}
- EditRemoteDialog dlg(mainwindow(), op);
+ EditRemoteDialog dlg(mainwindow(), op, gcx);
dlg.setName(remote->name);
dlg.setUrl(remote->url);
+ dlg.setSshKey(remote->ssh_key);
if (dlg.exec() == QDialog::Accepted) {
remote->name = dlg.name();
remote->url = dlg.url();
+ remote->ssh_key = dlg.sshKey();
GitPtr g = git();
if (op == EditRemoteDialog::RemoteAdd) {
bool ok = true;
for (Git::Remote const &r : *list) {
if (r.name == remote->name) {
qDebug() << "remote add: Already exists" << remote->name;
ok = false;
break;
}
}
if (ok) {
- g->addRemoteURL(remote->name, remote->url);
+ g->addRemoteURL(*remote);
}
} else if (op == EditRemoteDialog::RemoteSet) {
- g->setRemoteURL(remote->name, remote->url);
+ g->setRemoteURL(*remote);
}
+
+ // wip
+ QString localdir = ui->lineEdit_local_dir->text();
+ mainwindow()->changeSshKey(localdir, remote->ssh_key);
+ setSshKey_(remote->ssh_key);
+ getRemotes_();
+
updateRemotesTable();
return true;
}
return false;
}
Git::Remote RepositoryPropertyDialog::selectedRemote() const
{
Git::Remote remote;
auto const *list = remotes();
int row = ui->tableWidget->currentRow();
if (row >= 0 && row < list->size()) {
remote = list->at(row);
}
return remote;
}
bool RepositoryPropertyDialog::isRemoteChanged() const
{
return remote_changed;
}
+bool RepositoryPropertyDialog::isNameChanged() const
+{
+ return name_changed;
+}
+
+QString RepositoryPropertyDialog::getName()
+{
+ return repository.name;
+}
+
void RepositoryPropertyDialog::on_pushButton_remote_add_clicked()
{
Git::Remote r;
if (execEditRemoteDialog(&r, EditRemoteDialog::RemoteAdd)) {
remote_changed = true;
}
}
void RepositoryPropertyDialog::on_pushButton_remote_edit_clicked()
{
int row = ui->tableWidget->currentRow();
if (row < 0) {
ui->tableWidget->setCurrentCell(0, 0);
}
Git::Remote remote = selectedRemote();
if (execEditRemoteDialog(&remote, EditRemoteDialog::RemoteSet)) {
remote_changed = true;
}
}
void RepositoryPropertyDialog::on_pushButton_remote_remove_clicked()
{
Git::Remote remote = selectedRemote();
if (!remote.name.isEmpty()) {
int r = QMessageBox::warning(this, tr("Confirm Remove"), tr("Are you sure you want to remove the remote '%1' from the repository '%2' ?").arg(remote.name).arg(repository.name), QMessageBox::Ok, QMessageBox::Cancel);
if (r == QMessageBox::Ok) {
GitPtr g = git();
g->removeRemote(remote.name);
updateRemotesTable();
remote_changed = true;
}
}
}
void RepositoryPropertyDialog::on_pushButton_remote_menu_clicked()
{
toggleRemoteMenuActivity();
}
+
+
+void RepositoryPropertyDialog::setNameEditMode(bool f)
+{
+ ui->lineEdit_name->setVisible(f);
+ ui->label_editable_name->setVisible(!f);
+ ui->pushButton_edit_name->setText(f ? tr("Save") : tr("Edit Name"));
+ if (f) {
+ ui->lineEdit_name->setText(repository.name);
+ ui->lineEdit_name->setFocus();
+ } else {
+ if (!ui->lineEdit_name->text().isEmpty()) {
+ repository.name = ui->lineEdit_name->text();
+ ui->label_editable_name->setText(repository.name);
+ name_changed = true;
+ }
+ ui->pushButton_close->setFocus();
+ }
+}
+
+bool RepositoryPropertyDialog::isNameEditMode() const
+{
+ return ui->lineEdit_name->isVisible();
+}
+
+void RepositoryPropertyDialog::on_pushButton_edit_name_clicked()
+{
+ setNameEditMode(!isNameEditMode());
+}
+
+void RepositoryPropertyDialog::reject()
+{
+ if (isNameEditMode()) {
+ setNameEditMode(false);
+ } else {
+ BasicRepositoryDialog::reject();
+ }
+}
diff --git a/src/RepositoryPropertyDialog.h b/src/RepositoryPropertyDialog.h
index af68926..4ce3b12 100644
--- a/src/RepositoryPropertyDialog.h
+++ b/src/RepositoryPropertyDialog.h
@@ -1,39 +1,49 @@
#ifndef REPOSITORYPROPERTYDIALOG_H
#define REPOSITORYPROPERTYDIALOG_H
#include "RepositoryData.h"
#include "BasicRepositoryDialog.h"
#include "EditRemoteDialog.h"
#include <QDialog>
#include "Git.h"
-class BasicMainWindow;
+class MainWindow;
namespace Ui {
class RepositoryPropertyDialog;
}
class RepositoryPropertyDialog : public BasicRepositoryDialog {
Q_OBJECT
private:
Ui::RepositoryPropertyDialog *ui;
RepositoryItem repository;
bool remote_changed = false;
-
+ bool name_changed = false;
+ Git::Context const *gcx;
void updateRemotesTable();
bool execEditRemoteDialog(Git::Remote *remote, EditRemoteDialog::Operation op);
Git::Remote selectedRemote() const;
void toggleRemoteMenuActivity();
+ bool isNameEditMode() const;
+ void setNameEditMode(bool f);
public:
- explicit RepositoryPropertyDialog(BasicMainWindow *parent, const GitPtr &g, RepositoryItem const &item, bool open_repository_menu = false);
+ explicit RepositoryPropertyDialog(MainWindow *parent, const Git::Context *gcx, const GitPtr &g, RepositoryItem const &item, bool open_repository_menu = false);
~RepositoryPropertyDialog() override;
bool isRemoteChanged() const;
+ bool isNameChanged() const;
+ QString getName();
private slots:
void on_pushButton_remote_add_clicked();
void on_pushButton_remote_edit_clicked();
void on_pushButton_remote_remove_clicked();
void on_pushButton_remote_menu_clicked();
+ void on_pushButton_edit_name_clicked();
+
+ // QDialog interface
+public slots:
+ void reject();
};
#endif // REPOSITORYPROPERTYDIALOG_H
diff --git a/src/RepositoryPropertyDialog.ui b/src/RepositoryPropertyDialog.ui
index e4a6610..93958ac 100644
--- a/src/RepositoryPropertyDialog.ui
+++ b/src/RepositoryPropertyDialog.ui
@@ -1,240 +1,295 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RepositoryPropertyDialog</class>
<widget class="QDialog" name="RepositoryPropertyDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>468</width>
- <height>355</height>
+ <width>700</width>
+ <height>502</height>
</rect>
</property>
<property name="windowTitle">
<string>Repository Property</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="DialogHeaderFrame" name="frame">
<property name="styleSheet">
<string notr="true">QFrame {
background: #ffffff;
}</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>16</number>
</property>
<property name="bottomMargin">
<number>16</number>
</property>
<item>
- <widget class="QLabel" name="label_name">
- <property name="font">
- <font>
- <pointsize>15</pointsize>
- <weight>75</weight>
- <bold>true</bold>
- </font>
- </property>
- <property name="styleSheet">
- <string notr="true">QLabel {
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="label_name">
+ <property name="font">
+ <font>
+ <pointsize>15</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QLabel {
background: transparent;
}
</string>
- </property>
- <property name="text">
- <string>TextLabel</string>
- </property>
- </widget>
+ </property>
+ <property name="text">
+ <string>Name: </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_editable_name">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>15</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEdit_name">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton_edit_name">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Edit Name</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Local dir :</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="ReadOnlyLineEdit" name="lineEdit_local_dir">
<property name="frame">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Remote URLs</string>
</property>
</widget>
</item>
<item>
<widget class="RemoteRepositoriesTableWidget" name="tableWidget"/>
</item>
<item>
<widget class="QGroupBox" name="groupBox_remote">
<property name="title">
<string>Remote</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="pushButton_remote_add">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_remote_edit">
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>118</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_remote_remove">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="pushButton_remote_menu">
<property name="text">
<string>&amp;Remote menu</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_close">
<property name="text">
<string>Close</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ReadOnlyLineEdit</class>
<extends>QLineEdit</extends>
<header>ReadOnlyLineEdit.h</header>
</customwidget>
<customwidget>
<class>RemoteRepositoriesTableWidget</class>
<extends>QTableWidget</extends>
<header>RemoteRepositoriesTableWidget.h</header>
</customwidget>
<customwidget>
<class>DialogHeaderFrame</class>
<extends>QFrame</extends>
<header>DialogHeaderFrame.h</header>
<container>1</container>
</customwidget>
</customwidgets>
+ <tabstops>
+ <tabstop>lineEdit_name</tabstop>
+ <tabstop>pushButton_edit_name</tabstop>
+ <tabstop>lineEdit_local_dir</tabstop>
+ <tabstop>tableWidget</tabstop>
+ <tabstop>pushButton_remote_add</tabstop>
+ <tabstop>pushButton_remote_edit</tabstop>
+ <tabstop>pushButton_remote_remove</tabstop>
+ <tabstop>pushButton_remote_menu</tabstop>
+ <tabstop>pushButton_close</tabstop>
+ </tabstops>
<resources/>
<connections>
<connection>
<sender>pushButton_close</sender>
<signal>clicked()</signal>
<receiver>RepositoryPropertyDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>456</x>
<y>343</y>
</hint>
<hint type="destinationlabel">
<x>226</x>
<y>354</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/RepositoryWrapperFrame.cpp b/src/RepositoryWrapperFrame.cpp
new file mode 100644
index 0000000..d99686a
--- /dev/null
+++ b/src/RepositoryWrapperFrame.cpp
@@ -0,0 +1,144 @@
+#include "LogTableWidget.h"
+#include "MainWindow.h"
+#include "RepositoryWrapperFrame.h"
+
+RepositoryWrapperFrame::RepositoryWrapperFrame(QWidget *parent)
+ : QFrame(parent)
+{
+
+}
+
+void RepositoryWrapperFrame::bind(MainWindow *mw, LogTableWidget *logtablewidget, FilesListWidget *fileslistwidget, FilesListWidget *unstagedfileslistwidget, FilesListWidget *stagesfileslistwidget, FileDiffWidget *filediffwidget)
+{
+ mw_ = mw;
+ logtablewidget_ = logtablewidget;
+ fileslistwidget_ = fileslistwidget;
+ unstagedfileslistwidget_ = unstagedfileslistwidget;
+ stagesfileslistwidget_ = stagesfileslistwidget;
+ filediffwidget_ = filediffwidget;
+ logtablewidget->bind(this);
+}
+
+MainWindow *RepositoryWrapperFrame::mainwindow()
+{
+ Q_ASSERT(mw_);
+ return mw_;
+}
+
+MainWindow const *RepositoryWrapperFrame::mainwindow() const
+{
+ Q_ASSERT(mw_);
+ return mw_;
+}
+
+LogTableWidget *RepositoryWrapperFrame::logtablewidget()
+{
+ return logtablewidget_;
+}
+
+FilesListWidget *RepositoryWrapperFrame::fileslistwidget()
+{
+ return fileslistwidget_;
+}
+
+FilesListWidget *RepositoryWrapperFrame::unstagedFileslistwidget()
+{
+ return unstagedfileslistwidget_;
+}
+
+FilesListWidget *RepositoryWrapperFrame::stagedFileslistwidget()
+{
+ return stagesfileslistwidget_;
+}
+
+FileDiffWidget *RepositoryWrapperFrame::filediffwidget()
+{
+ return filediffwidget_;
+}
+
+const Git::CommitItem *RepositoryWrapperFrame::commitItem(int row)
+{
+ return mainwindow()->commitItem(mainwindow()->frame(), row);
+}
+
+QIcon RepositoryWrapperFrame::verifiedIcon(char s) const
+{
+ return mainwindow()->verifiedIcon(s);
+}
+
+QIcon RepositoryWrapperFrame::committerIcon(int row) const
+{
+ return mainwindow()->committerIcon(const_cast<RepositoryWrapperFrame *>(this), row);
+}
+
+QList<BranchLabel> const *RepositoryWrapperFrame::label(int row) const
+{
+ return mainwindow()->label(this, row);
+}
+
+QString RepositoryWrapperFrame::currentBranchName() const
+{
+ return mainwindow()->currentBranchName();
+}
+
+const Git::CommitItemList &RepositoryWrapperFrame::getLogs() const
+{
+ return mainwindow()->getLogs(this);
+}
+
+bool RepositoryWrapperFrame::isAncestorCommit(const QString &id)
+{
+ return mainwindow()->isAncestorCommit(id);
+}
+
+QColor RepositoryWrapperFrame::color(unsigned int i)
+{
+ return mainwindow()->color(i);
+}
+
+void RepositoryWrapperFrame::updateAncestorCommitMap()
+{
+ mainwindow()->updateAncestorCommitMap(this);
+}
+
+void RepositoryWrapperFrame::updateLogTableView()
+{
+ logtablewidget_->viewport()->update();
+}
+
+void RepositoryWrapperFrame::setFocusToLogTable()
+{
+ logtablewidget_->setFocus();
+}
+
+void RepositoryWrapperFrame::selectLogTableRow(int row)
+{
+ logtablewidget_->selectRow(row);
+}
+
+void RepositoryWrapperFrame::prepareLogTableWidget()
+{
+ QStringList cols = {
+ tr("Graph"),
+ tr("Commit"),
+ tr("Date"),
+ tr("Author"),
+ tr("Message"),
+ };
+ int n = cols.size();
+ logtablewidget_->setColumnCount(n);
+ logtablewidget_->setRowCount(0);
+ for (int i = 0; i < n; i++) {
+ QString const &text = cols[i];
+ auto *item = new QTableWidgetItem(text);
+ logtablewidget_->setHorizontalHeaderItem(i, item);
+ }
+
+ mainwindow()->updateCommitGraph(this); // コミットグラフを更新
+}
+
+void RepositoryWrapperFrame::clearLogContents()
+{
+ logtablewidget_->clearContents();
+ logtablewidget_->scrollToTop();
+}
diff --git a/src/RepositoryWrapperFrame.h b/src/RepositoryWrapperFrame.h
new file mode 100644
index 0000000..eecf445
--- /dev/null
+++ b/src/RepositoryWrapperFrame.h
@@ -0,0 +1,76 @@
+#ifndef REPOSITORYWRAPPERFRAME_H
+#define REPOSITORYWRAPPERFRAME_H
+
+#include "BranchLabel.h"
+#include "Git.h"
+#include "GitObjectManager.h"
+
+#include <QFrame>
+
+class MainWindow;
+class LogTableWidget;
+class FilesListWidget;
+class FileDiffWidget;
+
+class RepositoryWrapperFrame : public QFrame {
+ Q_OBJECT
+ friend class MainWindow;
+private:
+ Git::CommitItemList logs;
+
+ MainWindow *mw_ = nullptr;
+ LogTableWidget *logtablewidget_ = nullptr;
+ FilesListWidget *fileslistwidget_ = nullptr;
+ FilesListWidget *unstagedfileslistwidget_ = nullptr;
+ FilesListWidget *stagesfileslistwidget_ = nullptr;
+ FileDiffWidget *filediffwidget_ = nullptr;
+
+ std::map<QString, QList<Git::Branch>> branch_map;
+ std::map<QString, QList<Git::Tag>> tag_map;
+ std::map<int, QList<BranchLabel>> label_map;
+ std::map<QString, Git::Diff> diff_cache;
+
+ GitObjectCache objcache;
+
+ MainWindow *mainwindow();
+ MainWindow const *mainwindow() const;
+public:
+ explicit RepositoryWrapperFrame(QWidget *parent = nullptr);
+ Git::CommitItem const *commitItem(int row);
+ QIcon verifiedIcon(char s) const;
+ QIcon committerIcon(int row) const;
+ const QList<BranchLabel> *label(int row) const;
+ QString currentBranchName() const;
+ const Git::CommitItemList &getLogs() const;
+ bool isAncestorCommit(const QString &id);
+ QColor color(unsigned int i);
+ void updateAncestorCommitMap();
+ void bind(MainWindow *mw
+ , LogTableWidget *logtablewidget
+ , FilesListWidget *fileslistwidget
+ , FilesListWidget *unstagedfileslistwidget
+ , FilesListWidget *stagesfileslistwidget
+ , FileDiffWidget *filediffwidget
+ );
+
+ void prepareLogTableWidget();
+ void clearLogContents();
+ LogTableWidget *logtablewidget();
+ FilesListWidget *fileslistwidget();
+ FilesListWidget *unstagedFileslistwidget();
+ FileDiffWidget *filediffwidget();
+ FilesListWidget *stagedFileslistwidget();
+ void updateLogTableView();
+ void setFocusToLogTable();
+ void selectLogTableRow(int row);
+};
+
+struct RepositoryWrapperFrameP {
+ RepositoryWrapperFrame *pointer;
+ RepositoryWrapperFrameP(RepositoryWrapperFrame *pointer = nullptr)
+ : pointer(pointer)
+ {
+ }
+};
+
+#endif // REPOSITORYWRAPPERFRAME_H
diff --git a/src/SearchFromGitHubDialog.cpp b/src/SearchFromGitHubDialog.cpp
index c1810bc..b065f46 100644
--- a/src/SearchFromGitHubDialog.cpp
+++ b/src/SearchFromGitHubDialog.cpp
@@ -1,167 +1,167 @@
#include "SearchFromGitHubDialog.h"
#include "ui_SearchFromGitHubDialog.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "common/misc.h"
#include "urlencode.h"
#include <QDebug>
#include <QDesktopServices>
#include <QThread>
#include <QUrl>
#include <functional>
using SearchResultItem = GitHubAPI::SearchResultItem;
static QString toQString(std::string const &s)
{
return QString::fromUtf8(s.c_str(), s.size());
}
-SearchFromGitHubDialog::SearchFromGitHubDialog(QWidget *parent, BasicMainWindow *mw)
+SearchFromGitHubDialog::SearchFromGitHubDialog(QWidget *parent, MainWindow *mw)
: QDialog(parent)
, ui(new Ui::SearchFromGitHubDialog)
, mainwindow(mw)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
ui->tableWidget->setItemDelegate(&item_delegate);
connect(ui->label_hyperlink, &HyperLinkLabel::clicked, this, &SearchFromGitHubDialog::onHyperlinkClicked);
ui->pushButton_ok->setEnabled(false);
}
SearchFromGitHubDialog::~SearchFromGitHubDialog()
{
delete ui;
}
QString SearchFromGitHubDialog::url() const
{
return url_;
}
void SearchFromGitHubDialog::on_pushButton_search_clicked()
{
std::string q = ui->lineEdit_keywords->text().trimmed().toStdString();
q = url_encode(q);
if (q.empty()) return;
GitHubAPI github(mainwindow);
items = github.searchRepository(q);
QStringList cols = {
tr("Name"),
tr("Owner"),
tr("Score"),
tr("Description"),
};
ui->tableWidget->setColumnCount(cols.size());
ui->tableWidget->setRowCount(items.size());
for (int col = 0; col < cols.size(); col++) {
auto *p = new QTableWidgetItem();
p->setText(cols[col]);
ui->tableWidget->setHorizontalHeaderItem(col, p);
}
for (int row = 0; row < items.size(); row++) {
SearchResultItem const &item = items[row];
QTableWidgetItem *p;
QString name = QString::fromStdString(item.full_name);
QString owner;
int i = name.indexOf('/');
if (i > 0) {
owner = name.mid(0, i);
name = name.mid(i + 1);
}
int col = 0;
auto AddItem = [&](std::function<void(QTableWidgetItem *)> callback){
p = new QTableWidgetItem();
callback(p);
ui->tableWidget->setItem(row, col, p);
col++;
};
AddItem([&](QTableWidgetItem *p){
p->setData(Qt::UserRole, (int)row);
p->setText(name);
});
AddItem([&](QTableWidgetItem *p){
p->setText(owner);
});
AddItem([&](QTableWidgetItem *p){
char tmp[100];
sprintf(tmp, "%.2f", item.score);
p->setText(tmp);
p->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
});
AddItem([&](QTableWidgetItem *p){
p->setText(toQString(item.description));
});
ui->tableWidget->setRowHeight(row, 24);
}
ui->tableWidget->resizeColumnsToContents();
ui->tableWidget->horizontalHeader()->setStretchLastSection(true);
}
void SearchFromGitHubDialog::updateUI()
{
auto Check = [&](){
if (ui->radioButton_ssh->isChecked()) {
url_ = ui->lineEdit_ssh->text();
if (!url_.isEmpty()) {
return true;
}
}
if (ui->radioButton_http->isChecked()) {
url_ = ui->lineEdit_http->text();
if (!url_.isEmpty()) {
return true;
}
}
url_ = QString();
return false;
};
ui->pushButton_ok->setEnabled(Check());
}
void SearchFromGitHubDialog::on_tableWidget_currentItemChanged(QTableWidgetItem * /*current*/, QTableWidgetItem * /*previous*/)
{
int row = ui->tableWidget->currentRow();
QTableWidgetItem *p = ui->tableWidget->item(row, 0);
if (p) {
int i = p->data(Qt::UserRole).toInt();
if (i < items.size()) {
SearchResultItem const &item = items[i];
ui->lineEdit_ssh->setText(toQString(item.ssh_url));
ui->lineEdit_http->setText(toQString(item.clone_url));
ui->label_hyperlink->setText(toQString(item.html_url));
}
}
updateUI();
}
void SearchFromGitHubDialog::on_radioButton_ssh_clicked()
{
updateUI();
}
void SearchFromGitHubDialog::on_radioButton_http_clicked()
{
updateUI();
}
void SearchFromGitHubDialog::onHyperlinkClicked()
{
QString url = ui->label_hyperlink->text();
QDesktopServices::openUrl(QUrl(url));
}
diff --git a/src/SearchFromGitHubDialog.h b/src/SearchFromGitHubDialog.h
index 4418a1a..521b11d 100644
--- a/src/SearchFromGitHubDialog.h
+++ b/src/SearchFromGitHubDialog.h
@@ -1,40 +1,40 @@
#ifndef SEARCHFROMGITHUBDIALOG_H
#define SEARCHFROMGITHUBDIALOG_H
#include "MyTableWidgetDelegate.h"
#include "GitHubAPI.h"
#include <QDialog>
namespace Ui {
class SearchFromGitHubDialog;
}
class QTableWidgetItem;
-class BasicMainWindow;
+class MainWindow;
class SearchFromGitHubDialog : public QDialog {
Q_OBJECT
private:
Ui::SearchFromGitHubDialog *ui;
QList<GitHubAPI::SearchResultItem> items;
QString url_;
MyTableWidgetDelegate item_delegate;
- BasicMainWindow *mainwindow;
+ MainWindow *mainwindow;
void updateUI();
public:
- explicit SearchFromGitHubDialog(QWidget *parent, BasicMainWindow *mw);
+ explicit SearchFromGitHubDialog(QWidget *parent, MainWindow *mw);
~SearchFromGitHubDialog() override;
QString url() const;
private slots:
void on_pushButton_search_clicked();
void on_tableWidget_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous);
void on_radioButton_ssh_clicked();
void on_radioButton_http_clicked();
void onHyperlinkClicked();
};
#endif // SEARCHFROMGITHUBDIALOG_H
diff --git a/src/SetRemoteUrlDialog.cpp b/src/SetRemoteUrlDialog.cpp
index 955fd10..7d58118 100644
--- a/src/SetRemoteUrlDialog.cpp
+++ b/src/SetRemoteUrlDialog.cpp
@@ -1,62 +1,65 @@
#include "SetRemoteUrlDialog.h"
#include "ui_SetRemoteUrlDialog.h"
#include "MainWindow.h"
#include "MyTableWidgetDelegate.h"
#include "common/misc.h"
#include <QMessageBox>
SetRemoteUrlDialog::SetRemoteUrlDialog(MainWindow *mainwindow, QStringList const &remotes, GitPtr const &g)
: BasicRepositoryDialog(mainwindow, g)
, ui(new Ui::SetRemoteUrlDialog)
{
ui->setupUi(this);
this->remotes = remotes;
}
SetRemoteUrlDialog::~SetRemoteUrlDialog()
{
delete ui;
}
int SetRemoteUrlDialog::exec()
{
GitPtr g = git();
if (g->isValidWorkingCopy()) {
QString remote = remotes.isEmpty() ? "origin" : remotes[0];
ui->lineEdit_name->setText(remote);
}
updateRemotesTable();
return QDialog::exec();
}
void SetRemoteUrlDialog::updateRemotesTable()
{
QString url = BasicRepositoryDialog::updateRemotesTable(ui->tableWidget);
ui->lineEdit_url->setText(url);
}
void SetRemoteUrlDialog::accept()
{
GitPtr g = git();
if (g->isValidWorkingCopy()) {
QString rem = ui->lineEdit_name->text();
QString url = ui->lineEdit_url->text();
+ Git::Remote r;
+ r.name = rem;
+ r.url = url;
if (remotes.contains(rem)) {
- g->setRemoteURL(rem, url);
+ g->setRemoteURL(r);
} else {
- g->addRemoteURL(rem, url);
+ g->addRemoteURL(r);
}
updateRemotesTable();
}
QDialog::done(Accepted);
}
void SetRemoteUrlDialog::on_pushButton_test_clicked()
{
QString url = ui->lineEdit_url->text();
- mainwindow()->testRemoteRepositoryValidity(url);
+ mainwindow()->testRemoteRepositoryValidity(url, {});
}
diff --git a/src/SetUserDialog.cpp b/src/SetUserDialog.cpp
index 582c8ed..2ace25b 100644
--- a/src/SetUserDialog.cpp
+++ b/src/SetUserDialog.cpp
@@ -1,130 +1,130 @@
#include "SetUserDialog.h"
#include "ui_SetUserDialog.h"
#include "AvatarLoader.h"
-#include "BasicMainWindow.h"
+#include "MainWindow.h"
#include "common/misc.h"
struct SetUserDialog::Private {
Git::User global_user;
Git::User repo_user;
AvatarLoader avatar_loader;
};
-SetUserDialog::SetUserDialog(BasicMainWindow *parent, Git::User const &global_user, Git::User const &repo_user, QString const &repo)
+SetUserDialog::SetUserDialog(MainWindow *parent, Git::User const &global_user, Git::User const &repo_user, QString const &repo)
: QDialog(parent)
, ui(new Ui::SetUserDialog)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
misc::setFixedSize(this);
m->global_user = global_user;
m->repo_user = repo_user;
if (repo.isEmpty()) {
ui->radioButton_repository->setEnabled(false);
} else {
QString text = tr("Repository");
text += " : ";
text += repo;
ui->radioButton_repository->setText(text);
}
ui->radioButton_global->click();
ui->lineEdit_name->setFocus();
m->avatar_loader.start(mainwindow());
- connect(&m->avatar_loader, &AvatarLoader::updated, [&](){
+ connect(&m->avatar_loader, &AvatarLoader::updated, [&](RepositoryWrapperFrameP frame){
QString email = ui->lineEdit_mail->text();
- QIcon icon = m->avatar_loader.fetch(email.toStdString(), false);
+ QIcon icon = m->avatar_loader.fetch(frame.pointer, email.toStdString(), false);
setAvatar(icon);
});
}
SetUserDialog::~SetUserDialog()
{
m->avatar_loader.stop();
delete m;
delete ui;
}
-BasicMainWindow *SetUserDialog::mainwindow()
+MainWindow *SetUserDialog::mainwindow()
{
- return qobject_cast<BasicMainWindow *>(parent());
+ return qobject_cast<MainWindow *>(parent());
}
bool SetUserDialog::isGlobalChecked() const
{
return ui->radioButton_global->isChecked();
}
bool SetUserDialog::isRepositoryChecked() const
{
return ui->radioButton_repository->isChecked();
}
Git::User SetUserDialog::user() const
{
Git::User user;
user.name = ui->lineEdit_name->text();
user.email = ui->lineEdit_mail->text();
return user;
}
void SetUserDialog::setAvatar(QIcon const &icon)
{
QPixmap pm = icon.pixmap(QSize(64, 64));
ui->label_avatar->setPixmap(pm);
}
void SetUserDialog::on_radioButton_global_toggled(bool checked)
{
if (checked) {
ui->lineEdit_name->setText(m->global_user.name);
ui->lineEdit_mail->setText(m->global_user.email);
}
}
void SetUserDialog::on_radioButton_repository_toggled(bool checked)
{
if (checked) {
ui->lineEdit_name->setText(m->repo_user.name);
ui->lineEdit_mail->setText(m->repo_user.email);
}
}
void SetUserDialog::on_lineEdit_name_textChanged(QString const &text)
{
if (isGlobalChecked()) {
m->global_user.name = text;
}
if (isRepositoryChecked()) {
m->repo_user.name = text;
}
}
void SetUserDialog::on_lineEdit_mail_textChanged(QString const &text)
{
if (isGlobalChecked()) {
m->global_user.email = text;
}
if (isRepositoryChecked()) {
m->repo_user.email = text;
}
}
void SetUserDialog::on_pushButton_get_icon_clicked()
{
ui->label_avatar->setPixmap(QPixmap());
QString email = ui->lineEdit_mail->text();
if (email.indexOf('@') > 0) {
- QIcon icon = m->avatar_loader.fetch(email.toStdString(), true);
+ QIcon icon = m->avatar_loader.fetch(nullptr, email.toStdString(), true);
if (!icon.isNull()) {
setAvatar(icon);
}
}
}
diff --git a/src/SetUserDialog.h b/src/SetUserDialog.h
index 2d68674..b56949d 100644
--- a/src/SetUserDialog.h
+++ b/src/SetUserDialog.h
@@ -1,39 +1,39 @@
#ifndef SETUSERDIALOG_H
#define SETUSERDIALOG_H
#include "Git.h"
#include <QDialog>
-class BasicMainWindow;
+class MainWindow;
namespace Ui {
class SetUserDialog;
}
class SetUserDialog : public QDialog {
Q_OBJECT
private:
Ui::SetUserDialog *ui;
struct Private;
Private *m;
void setAvatar(const QIcon &icon);
- BasicMainWindow *mainwindow();
+ MainWindow *mainwindow();
public:
- explicit SetUserDialog(BasicMainWindow *parent, Git::User const &global_user, Git::User const &repo_user, QString const &repo);
+ explicit SetUserDialog(MainWindow *parent, Git::User const &global_user, Git::User const &repo_user, QString const &repo);
~SetUserDialog() override;
bool isGlobalChecked() const;
bool isRepositoryChecked() const;
Git::User user() const;
private slots:
void on_radioButton_global_toggled(bool checked);
void on_radioButton_repository_toggled(bool checked);
void on_lineEdit_name_textChanged(QString const &text);
void on_lineEdit_mail_textChanged(QString const &text);
void on_pushButton_get_icon_clicked();
};
#endif // SETUSERDIALOG_H
diff --git a/src/SettingExampleForm.h b/src/SettingExampleForm.h
index c390382..b94dc4f 100644
--- a/src/SettingExampleForm.h
+++ b/src/SettingExampleForm.h
@@ -1,23 +1,21 @@
#ifndef SETTINGEXAMPLEFORM_H
#define SETTINGEXAMPLEFORM_H
#include "AbstractSettingForm.h"
-#include <QWidget>
-
namespace Ui {
class SettingExampleForm;
}
class SettingExampleForm : public AbstractSettingForm {
Q_OBJECT
public:
explicit SettingExampleForm(QWidget *parent = nullptr);
~SettingExampleForm() override;
void exchange(bool save) override;
private:
Ui::SettingExampleForm *ui;
};
#endif // SETTINGEXAMPLEFORM_H
diff --git a/src/SettingGeneralForm.cpp b/src/SettingGeneralForm.cpp
index 434e735..1ee32ba 100644
--- a/src/SettingGeneralForm.cpp
+++ b/src/SettingGeneralForm.cpp
@@ -1,120 +1,120 @@
+#include "SettingGeneralForm.h"
+#include "ui_SettingGeneralForm.h"
#include "MySettings.h"
#include "SelectItemDialog.h"
#include "ApplicationGlobal.h"
-#include "SettingGeneralForm.h"
-#include "ui_SettingGeneralForm.h"
#include "Languages.h"
#include "common/misc.h"
#include <QFileDialog>
SettingGeneralForm::SettingGeneralForm(QWidget *parent) :
AbstractSettingForm(parent),
ui(new Ui::SettingGeneralForm)
{
ui->setupUi(this);
langs = languages();
themes.push_back(Languages::Item("standard", tr("Standard")));
themes.push_back(Languages::Item("dark", tr("Dark")));
updateLanguage();
updateTheme();
}
SettingGeneralForm::~SettingGeneralForm()
{
delete ui;
}
QList<Languages::Item> SettingGeneralForm::languages()
{
return Languages().items;
}
void SettingGeneralForm::exchange(bool save)
{
if (save) {
settings()->remember_and_restore_window_position = ui->checkBox_save_window_pos->isChecked();
settings()->enable_high_dpi_scaling = ui->checkBox_enable_high_dpi_scaling->isChecked();
} else {
ui->checkBox_save_window_pos->setChecked(settings()->remember_and_restore_window_position);
ui->checkBox_enable_high_dpi_scaling->setChecked(settings()->enable_high_dpi_scaling);
}
}
void SettingGeneralForm::on_pushButton_browse_default_working_dir_clicked()
{
}
void SettingGeneralForm::updateLanguage()
{
QString id = global->language_id;
if (id.isEmpty()) {
id = "en";
}
for (Languages::Item const &item : langs) {
if (item.id == id) {
ui->lineEdit_language->setText(item.description);
return;
}
}
}
void SettingGeneralForm::updateTheme()
{
QString id = global->theme_id;
if (id.isEmpty()) {
id = "standard";
}
for (Languages::Item const &item : themes) {
if (item.id == id) {
ui->lineEdit_theme->setText(item.description);
return;
}
}
}
void SettingGeneralForm::execSelectLanguageDialog(QWidget *parent, QList<Languages::Item> const &langs, std::function<void()> const &done)
{
SelectItemDialog dlg(parent);
dlg.setWindowTitle(tr("Select Language"));
for (Languages::Item const &item : langs) {
dlg.addItem(item.id, item.description);
}
dlg.select(global->language_id.isEmpty() ? "en" : global->language_id);
if (dlg.exec() == QDialog::Accepted) {
Languages::Item item = dlg.item();
global->language_id = item.id;
MySettings s;
s.beginGroup("UI");
s.setValue("Language", global->language_id);
s.endGroup();
done();
}
}
void SettingGeneralForm::on_pushButton_change_language_clicked()
{
execSelectLanguageDialog(this, langs, [&](){updateLanguage();});
}
void SettingGeneralForm::on_pushButton_change_theme_clicked()
{
SelectItemDialog dlg(this);
dlg.setWindowTitle(tr("Select Theme"));
for (Languages::Item const &item : themes) {
dlg.addItem(item.id, item.description);
}
dlg.select(global->theme_id.isEmpty() ? "standard" : global->theme_id);
if (dlg.exec() == QDialog::Accepted) {
Languages::Item item = dlg.item();
global->theme_id = item.id;
MySettings s;
s.beginGroup("UI");
s.setValue("Theme", global->theme_id);
s.endGroup();
updateTheme();
}
}
diff --git a/src/SettingProgramsForm.cpp b/src/SettingProgramsForm.cpp
index acf9291..fecb6da 100644
--- a/src/SettingProgramsForm.cpp
+++ b/src/SettingProgramsForm.cpp
@@ -1,63 +1,90 @@
#include "SettingProgramsForm.h"
#include "ui_SettingProgramsForm.h"
#include <QFileDialog>
#include "SettingsDialog.h"
#include "common/misc.h"
SettingProgramsForm::SettingProgramsForm(QWidget *parent) :
AbstractSettingForm(parent),
ui(new Ui::SettingProgramsForm)
{
ui->setupUi(this);
+
+#ifdef Q_OS_WIN
+ ui->groupBox_terminal->setVisible(false);
+#endif
}
SettingProgramsForm::~SettingProgramsForm()
{
delete ui;
}
void SettingProgramsForm::exchange(bool save)
{
if (save) {
settings()->git_command = ui->lineEdit_git_command->text();
settings()->file_command = ui->lineEdit_file_command->text();
settings()->gpg_command = ui->lineEdit_gpg_command->text();
+ settings()->ssh_command = ui->lineEdit_ssh_command->text();
+ settings()->terminal_command = ui->lineEdit_terminal_command->text();
} else {
ui->lineEdit_git_command->setText(settings()->git_command);
ui->lineEdit_file_command->setText(settings()->file_command);
ui->lineEdit_gpg_command->setText(settings()->gpg_command);
+ ui->lineEdit_ssh_command->setText(settings()->ssh_command);
+ ui->lineEdit_terminal_command->setText(settings()->terminal_command);
}
}
void SettingProgramsForm::on_pushButton_select_git_command_clicked()
{
QString path = mainwindow()->selectGitCommand(false);
if (!path.isEmpty()) {
settings()->git_command = path;
ui->lineEdit_git_command->setText(path);
}
}
void SettingProgramsForm::on_pushButton_select_file_command_clicked()
{
QString path = mainwindow()->selectFileCommand(false);
if (!path.isEmpty()) {
settings()->file_command = path;
ui->lineEdit_file_command->setText(path);
}
}
void SettingProgramsForm::on_pushButton_select_gpg_command_clicked()
{
QString path = mainwindow()->selectGpgCommand(false);
if (!path.isEmpty()) {
settings()->gpg_command = path;
ui->lineEdit_gpg_command->setText(path);
}
}
+void SettingProgramsForm::on_pushButton_select_ssh_command_clicked()
+{
+ QString path = mainwindow()->selectSshCommand(false);
+ if (!path.isEmpty()) {
+ settings()->ssh_command = path;
+ ui->lineEdit_ssh_command->setText(path);
+ }
+}
+void SettingProgramsForm::on_pushButton_select_term_command_clicked()
+{
+ QString path = QFileDialog::getOpenFileName(window(), tr("Terminal Command"), "/usr/bin");
+ if (!path.isEmpty()) {
+ settings()->terminal_command = path;
+ ui->lineEdit_terminal_command->setText(path);
+ }
+}
-
-
+void SettingProgramsForm::on_pushButton_reset_term_command_clicked()
+{
+ QString path = ApplicationSettings::defaultSettings().terminal_command;
+ ui->lineEdit_terminal_command->setText(path);
+}
diff --git a/src/SettingProgramsForm.h b/src/SettingProgramsForm.h
index bd1bd38..87e985e 100644
--- a/src/SettingProgramsForm.h
+++ b/src/SettingProgramsForm.h
@@ -1,25 +1,27 @@
#ifndef SETTINGPROGRAMSFORM_H
#define SETTINGPROGRAMSFORM_H
#include "AbstractSettingForm.h"
namespace Ui {
class SettingProgramsForm;
}
class SettingProgramsForm : public AbstractSettingForm {
Q_OBJECT
private:
Ui::SettingProgramsForm *ui;
public:
explicit SettingProgramsForm(QWidget *parent = nullptr);
~SettingProgramsForm() override;
void exchange(bool save) override;
private slots:
void on_pushButton_select_git_command_clicked();
void on_pushButton_select_file_command_clicked();
void on_pushButton_select_gpg_command_clicked();
-
+ void on_pushButton_select_ssh_command_clicked();
+ void on_pushButton_select_term_command_clicked();
+ void on_pushButton_reset_term_command_clicked();
};
#endif // SETTINGPROGRAMSFORM_H
diff --git a/src/SettingProgramsForm.ui b/src/SettingProgramsForm.ui
index dd59083..3f37bac 100644
--- a/src/SettingProgramsForm.ui
+++ b/src/SettingProgramsForm.ui
@@ -1,99 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingProgramsForm</class>
<widget class="QWidget" name="SettingProgramsForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>309</width>
- <height>260</height>
+ <width>950</width>
+ <height>736</height>
</rect>
</property>
<property name="windowTitle">
<string>Programs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Git command</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLineEdit" name="lineEdit_git_command"/>
</item>
<item>
<widget class="QPushButton" name="pushButton_select_git_command">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>File command</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="lineEdit_file_command"/>
</item>
<item>
<widget class="QPushButton" name="pushButton_select_file_command">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>GPG command (option)</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLineEdit" name="lineEdit_gpg_command"/>
</item>
<item>
<widget class="QPushButton" name="pushButton_select_gpg_command">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_5">
+ <property name="title">
+ <string>SSH command (option)</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <widget class="QLineEdit" name="lineEdit_ssh_command"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton_select_ssh_command">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_terminal">
+ <property name="title">
+ <string>Terminal command (option)</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <item>
+ <widget class="QLineEdit" name="lineEdit_terminal_command"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton_reset_term_command">
+ <property name="text">
+ <string>Reset</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton_select_term_command">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>74</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<tabstops>
<tabstop>lineEdit_git_command</tabstop>
<tabstop>pushButton_select_git_command</tabstop>
<tabstop>lineEdit_file_command</tabstop>
<tabstop>pushButton_select_file_command</tabstop>
<tabstop>lineEdit_gpg_command</tabstop>
<tabstop>pushButton_select_gpg_command</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
diff --git a/src/SettingVisualForm.cpp b/src/SettingVisualForm.cpp
new file mode 100644
index 0000000..23090b2
--- /dev/null
+++ b/src/SettingVisualForm.cpp
@@ -0,0 +1,41 @@
+#include "SettingVisualForm.h"
+#include "ui_SettingVisualForm.h"
+#include "MySettings.h"
+#include "ApplicationGlobal.h"
+
+SettingVisualForm::SettingVisualForm(QWidget *parent)
+ : AbstractSettingForm(parent)
+ , ui(new Ui::SettingVisualForm)
+{
+ ui->setupUi(this);
+}
+
+SettingVisualForm::~SettingVisualForm()
+{
+ delete ui;
+}
+
+void SettingVisualForm::exchange(bool save)
+{
+ if (save) {
+ settings()->branch_label_color.head = ui->toolButton_head->color();
+ settings()->branch_label_color.local = ui->toolButton_local_branch->color();
+ settings()->branch_label_color.remote = ui->toolButton_remote_branch->color();
+ settings()->branch_label_color.tag = ui->toolButton_tag->color();
+ } else {
+ ui->toolButton_head->setColor(settings()->branch_label_color.head);
+ ui->toolButton_local_branch->setColor(settings()->branch_label_color.local);
+ ui->toolButton_remote_branch->setColor(settings()->branch_label_color.remote);
+ ui->toolButton_tag->setColor(settings()->branch_label_color.tag);
+ }
+}
+
+
+void SettingVisualForm::on_pushButton_reset_clicked()
+{
+ auto def = ApplicationSettings::defaultSettings();
+ ui->toolButton_head->setColor(def.branch_label_color.head);
+ ui->toolButton_local_branch->setColor(def.branch_label_color.local);
+ ui->toolButton_remote_branch->setColor(def.branch_label_color.remote);
+ ui->toolButton_tag->setColor(def.branch_label_color.tag);
+}
diff --git a/src/SettingVisualForm.h b/src/SettingVisualForm.h
new file mode 100644
index 0000000..38f5ff3
--- /dev/null
+++ b/src/SettingVisualForm.h
@@ -0,0 +1,23 @@
+#ifndef SETTINGVISUALFORM_H
+#define SETTINGVISUALFORM_H
+
+#include "AbstractSettingForm.h"
+
+namespace Ui {
+class SettingVisualForm;
+}
+
+class SettingVisualForm : public AbstractSettingForm {
+ Q_OBJECT
+public:
+ explicit SettingVisualForm(QWidget *parent = nullptr);
+ ~SettingVisualForm();
+ void exchange(bool save) override;
+private slots:
+ void on_pushButton_reset_clicked();
+
+private:
+ Ui::SettingVisualForm *ui;
+};
+
+#endif // SETTINGVISUALFORM_H
diff --git a/src/SettingVisualForm.ui b/src/SettingVisualForm.ui
new file mode 100644
index 0000000..ec61ef4
--- /dev/null
+++ b/src/SettingVisualForm.ui
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SettingVisualForm</class>
+ <widget class="QWidget" name="SettingVisualForm">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Visual</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Label Color</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="ColorButton" name="toolButton_head">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Head</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ColorButton" name="toolButton_local_branch">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Local Branch</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ColorButton" name="toolButton_remote_branch">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Remote Branch</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ColorButton" name="toolButton_tag">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Tag</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>147</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="pushButton_reset">
+ <property name="text">
+ <string>Reset</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ColorButton</class>
+ <extends>QToolButton</extends>
+ <header>ColorButton.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp
index a60e60d..1487935 100644
--- a/src/SettingsDialog.cpp
+++ b/src/SettingsDialog.cpp
@@ -1,184 +1,229 @@
#include "SettingsDialog.h"
#include "ui_SettingsDialog.h"
#include "MySettings.h"
#include "common/misc.h"
#include <QFileDialog>
static int page_number = 0;
SettingsDialog::SettingsDialog(MainWindow *parent) :
QDialog(parent),
ui(new Ui::SettingsDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
mainwindow_ = parent;
loadSettings();
QTreeWidgetItem *item;
auto AddPage = [&](QWidget *page){
page->layout()->setMargin(0);
QString name = page->windowTitle();
item = new QTreeWidgetItem();
item->setText(0, name);
item->setData(0, Qt::UserRole, QVariant::fromValue((uintptr_t)(QWidget *)page));
ui->treeWidget->addTopLevelItem(item);
};
AddPage(ui->page_general);
AddPage(ui->page_programs);
AddPage(ui->page_behavior);
AddPage(ui->page_network);
+ AddPage(ui->page_visual);
// AddPage(ui->page_example);
ui->treeWidget->setCurrentItem(ui->treeWidget->topLevelItem(page_number));
}
SettingsDialog::~SettingsDialog()
{
delete ui;
}
namespace {
template <typename T> class GetValue {
private:
public:
MySettings &settings;
QString name;
GetValue(MySettings &s, QString const &name)
: settings(s)
, name(name)
{
}
- void operator >> (T &value)
- {
- value = settings.value(name, value).template value<T>();
- }
+// void operator >> (T &value)
+// {
+// value = settings.value(name, value).template value<T>();
+// }
};
+template <typename T> void operator >> (GetValue<T> const &l, T &r)
+{
+ r = l.settings.value(l.name, r).template value<T>();
+}
+
+template <> void operator >> (GetValue<QColor> const &l, QColor &r)
+{
+ QString s = l.settings.value(l.name, QString()).template value<QString>(); // 文字列で取得
+ if (s.startsWith('#')) {
+ r = s;
+ }
+}
+
template <typename T> class SetValue {
private:
public:
MySettings &settings;
QString name;
SetValue(MySettings &s, QString const &name)
: settings(s)
, name(name)
{
}
- void operator << (T const &value)
- {
- settings.setValue(name, value);
- }
+// void operator << (T const &value)
+// {
+// settings.setValue(name, value);
+// }
};
+template <typename T> void operator << (SetValue<T> &&l, T const &r)
+{
+ l.settings.setValue(l.name, r);
+}
+
+template <> void operator << (SetValue<QColor> &&l, QColor const &r)
+{
+ QString s = QString::asprintf("#%02x%02x%02x", r.red(), r.green(), r.blue());
+ l.settings.setValue(l.name, s);
+}
+
} // namespace
void SettingsDialog::loadSettings(ApplicationSettings *as)
{
MySettings s;
*as = ApplicationSettings::defaultSettings();
s.beginGroup("Global");
GetValue<bool>(s, "SaveWindowPosition") >> as->remember_and_restore_window_position;
GetValue<QString>(s, "DefaultWorkingDirectory") >> as->default_working_dir;
GetValue<QString>(s, "GitCommand") >> as->git_command;
GetValue<QString>(s, "FileCommand") >> as->file_command;
GetValue<QString>(s, "GpgCommand") >> as->gpg_command;
+ GetValue<QString>(s, "SshCommand") >> as->ssh_command;
+ GetValue<QString>(s, "TerminalCommand") >> as->terminal_command;
s.endGroup();
s.beginGroup("UI");
GetValue<bool>(s, "EnableHighDpiScaling") >> as->enable_high_dpi_scaling;
+ GetValue<bool>(s, "ShowLabels") >> as->show_labels;
s.endGroup();
s.beginGroup("Network");
GetValue<QString>(s, "ProxyType") >> as->proxy_type;
GetValue<QString>(s, "ProxyServer") >> as->proxy_server;
GetValue<bool>(s, "GetCommitterIcon") >> as->get_committer_icon;
s.endGroup();
as->proxy_server = misc::makeProxyServerURL(as->proxy_server);
s.beginGroup("Behavior");
GetValue<bool>(s, "AutomaticFetch") >> as->automatically_fetch_when_opening_the_repository;
GetValue<unsigned int>(s, "MaxCommitItemAcquisitions") >> as->maximum_number_of_commit_item_acquisitions;
s.endGroup();
+
+ s.beginGroup("Visual");
+ GetValue<QColor>(s, "LabelColorHead") >> as->branch_label_color.head;
+ GetValue<QColor>(s, "LabelColorLocalBranch") >> as->branch_label_color.local;
+ GetValue<QColor>(s, "LabelColorRemoteBranch") >> as->branch_label_color.remote;
+ GetValue<QColor>(s, "LabelColorTag") >> as->branch_label_color.tag;
+ s.endGroup();
}
void SettingsDialog::saveSettings(ApplicationSettings const *as)
{
MySettings s;
s.beginGroup("Global");
SetValue<bool>(s, "SaveWindowPosition") << as->remember_and_restore_window_position;
SetValue<QString>(s, "DefaultWorkingDirectory") << as->default_working_dir;
SetValue<QString>(s, "GitCommand") << as->git_command;
SetValue<QString>(s, "FileCommand") << as->file_command;
SetValue<QString>(s, "GpgCommand") << as->gpg_command;
+ SetValue<QString>(s, "SshCommand") << as->ssh_command;
+ SetValue<QString>(s, "TerminalCommand") << as->terminal_command;
s.endGroup();
s.beginGroup("UI");
SetValue<bool>(s, "EnableHighDpiScaling") << as->enable_high_dpi_scaling;
+ SetValue<bool>(s, "ShowLabels") << as->show_labels;
s.endGroup();
s.beginGroup("Network");
SetValue<QString>(s, "ProxyType") << as->proxy_type;
SetValue<QString>(s, "ProxyServer") << misc::makeProxyServerURL(as->proxy_server);
SetValue<bool>(s, "GetCommitterIcon") << as->get_committer_icon;
s.endGroup();
s.beginGroup("Behavior");
SetValue<bool>(s, "AutomaticFetch") << as->automatically_fetch_when_opening_the_repository;
SetValue<unsigned int>(s, "MaxCommitItemAcquisitions") << as->maximum_number_of_commit_item_acquisitions;
s.endGroup();
+ s.beginGroup("Visual");
+ SetValue<QColor>(s, "LabelColorHead") << as->branch_label_color.head;
+ SetValue<QColor>(s, "LabelColorLocalBranch") << as->branch_label_color.local;
+ SetValue<QColor>(s, "LabelColorRemoteBranch") << as->branch_label_color.remote;
+ SetValue<QColor>(s, "LabelColorTag") << as->branch_label_color.tag;
+ s.endGroup();
+
}
void SettingsDialog::saveSettings()
{
saveSettings(&set);
}
void SettingsDialog::exchange(bool save)
{
QList<AbstractSettingForm *> forms = ui->stackedWidget->findChildren<AbstractSettingForm *>();
for (AbstractSettingForm *form : forms) {
form->exchange(save);
}
}
void SettingsDialog::loadSettings()
{
loadSettings(&set);
exchange(false);
}
void SettingsDialog::done(int r)
{
page_number = ui->treeWidget->currentIndex().row();
QDialog::done(r);
}
void SettingsDialog::accept()
{
exchange(true);
saveSettings();
done(QDialog::Accepted);
}
void SettingsDialog::on_treeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
{
(void)previous;
if (current) {
uintptr_t p = current->data(0, Qt::UserRole).value<uintptr_t>();
QWidget *w = reinterpret_cast<QWidget *>(p);
Q_ASSERT(w);
ui->stackedWidget->setCurrentWidget(w);
}
}
diff --git a/src/SettingsDialog.ui b/src/SettingsDialog.ui
index e04ad3b..dd52d97 100644
--- a/src/SettingsDialog.ui
+++ b/src/SettingsDialog.ui
@@ -1,223 +1,230 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDialog</class>
<widget class="QDialog" name="SettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>523</width>
<height>340</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTreeWidget" name="treeWidget">
<property name="minimumSize">
<size>
<width>160</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>160</width>
<height>16777215</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
- <number>2</number>
+ <number>4</number>
</property>
<widget class="SettingGeneralForm" name="page_general"/>
<widget class="SettingBehaviorForm" name="page_behavior">
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
<widget class="SettingProgramsForm" name="page_programs">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
<widget class="SettingNetworkForm" name="page_network">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
+ <widget class="SettingVisualForm" name="page_visual"/>
<widget class="SettingExampleForm" name="page_example">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_3">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SettingExampleForm</class>
<extends>QWidget</extends>
<header>SettingExampleForm.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>SettingNetworkForm</class>
<extends>QWidget</extends>
<header>SettingNetworkForm.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>SettingBehaviorForm</class>
<extends>QWidget</extends>
<header>SettingBehaviorForm.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>SettingGeneralForm</class>
<extends>QWidget</extends>
<header>SettingGeneralForm.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>SettingProgramsForm</class>
<extends>QWidget</extends>
<header>SettingProgramsForm.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>SettingVisualForm</class>
+ <extends>QWidget</extends>
+ <header>SettingVisualForm.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>SettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>361</x>
<y>284</y>
</hint>
<hint type="destinationlabel">
<x>444</x>
<y>334</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_3</sender>
<signal>clicked()</signal>
<receiver>SettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>463</x>
<y>290</y>
</hint>
<hint type="destinationlabel">
<x>467</x>
<y>349</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/CloneDialog.cpp b/src/SubmoduleAddDialog.cpp
similarity index 59%
copy from src/CloneDialog.cpp
copy to src/SubmoduleAddDialog.cpp
index 9dacf42..f5c0aad 100644
--- a/src/CloneDialog.cpp
+++ b/src/SubmoduleAddDialog.cpp
@@ -1,143 +1,138 @@
-#include "CloneDialog.h"
-#include "ui_CloneDialog.h"
-#include "common/misc.h"
-#include "common/joinpath.h"
-#include "BasicMainWindow.h"
+#include "SubmoduleAddDialog.h"
+#include "ui_SubmoduleAddDialog.h"
+#include "ApplicationGlobal.h"
+#include "MainWindow.h"
#include "SearchFromGitHubDialog.h"
-
+#include "common/joinpath.h"
+#include "common/misc.h"
#include <QFileDialog>
#include <QMessageBox>
+#include <QStandardPaths>
#include <QThread>
enum SearchRepository {
None,
GitHub,
};
-struct CloneDialog::Private {
+struct SubmoduleAddDialog::Private {
QString url;
QString repo_name;
QString default_working_dir;
bool ok = false;
QString errmsg;
- CloneDialog::Action action = CloneDialog::Action::Clone;
};
-CloneDialog::CloneDialog(BasicMainWindow *parent, QString const &url, QString const &defworkdir)
+SubmoduleAddDialog::SubmoduleAddDialog(MainWindow *parent, QString const &url, QString const &defworkdir, Git::Context const *gcx)
: QDialog(parent)
- , ui(new Ui::CloneDialog)
+ , ui(new Ui::SubmoduleAddDialog)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
m->default_working_dir = defworkdir;
ui->lineEdit_working_dir->setText(m->default_working_dir);
ui->lineEdit_repo_location->setText(url);
- ui->comboBox->addItem(tr("Search"));
- ui->comboBox->addItem(tr("GitHub"));
-
+ ui->advanced_option->setSshKeyOverrigingEnabled(!gcx->ssh_command.isEmpty());
-#ifdef Q_OS_MACX
- ui->comboBox->setMinimumWidth(100);
-#endif
+ ui->lineEdit_repo_location->setFocus();
}
-CloneDialog::~CloneDialog()
+SubmoduleAddDialog::~SubmoduleAddDialog()
{
delete m;
delete ui;
}
-CloneDialog::Action CloneDialog::action() const
+MainWindow *SubmoduleAddDialog::mainwindow()
{
- return m->action;
+ return qobject_cast<MainWindow *>(parent());
}
-BasicMainWindow *CloneDialog::mainwindow()
+QString SubmoduleAddDialog::url()
{
- return qobject_cast<BasicMainWindow *>(parent());
+ return ui->lineEdit_repo_location->text();
}
-QString CloneDialog::url()
+QString SubmoduleAddDialog::dir()
{
- return ui->lineEdit_repo_location->text();
+ return ui->lineEdit_working_dir->text();
}
-QString CloneDialog::dir()
+bool SubmoduleAddDialog::isForce() const
{
- return ui->lineEdit_working_dir->text();
+ return ui->checkBox_force->isChecked();
}
-void CloneDialog::on_lineEdit_repo_location_textChanged(QString const &text)
+void SubmoduleAddDialog::on_lineEdit_repo_location_textChanged(QString const &text)
{
QString path;
int i = text.lastIndexOf('/');
int j = text.lastIndexOf('\\');
if (i < j) i = j;
j = text.size();
if (text.endsWith(".git")) {
j -= 4;
}
if (i >= 0 && i < j) {
path = text.mid(i, j - i);
}
m->repo_name = path;
path = m->default_working_dir / m->repo_name;
path = misc::normalizePathSeparator(path);
ui->lineEdit_working_dir->setText(path);
}
-void CloneDialog::on_comboBox_currentIndexChanged(int index)
-{
- if (index == GitHub) {
- SearchFromGitHubDialog dlg(this, mainwindow());
- if (dlg.exec() == QDialog::Accepted) {
- ui->lineEdit_repo_location->setText(dlg.url());
- }
- }
- ui->comboBox->setCurrentIndex(0);
-}
-void CloneDialog::on_pushButton_test_clicked()
+
+void SubmoduleAddDialog::on_pushButton_test_clicked()
{
- mainwindow()->testRemoteRepositoryValidity(url());
+ mainwindow()->testRemoteRepositoryValidity(url(), overridedSshKey());
}
-void CloneDialog::on_pushButton_browse_clicked()
+void SubmoduleAddDialog::on_pushButton_browse_clicked()
{
QString path = ui->lineEdit_working_dir->text();
path = QFileDialog::getExistingDirectory(this, tr("Checkout into"), path);
if (!path.isEmpty()) {
m->default_working_dir = path;
path = m->default_working_dir / m->repo_name;
path = misc::normalizePathSeparator(path);
ui->lineEdit_working_dir->setText(path);
}
}
-void CloneDialog::on_pushButton_open_existing_clicked()
+void SubmoduleAddDialog::on_pushButton_open_existing_clicked()
{
QString dir = mainwindow()->defaultWorkingDir();
dir = QFileDialog::getExistingDirectory(this, tr("Open existing directory"), dir);
if (QFileInfo(dir).isDir()) {
QString url;
- GitPtr g = mainwindow()->git(dir);
+ GitPtr g = mainwindow()->git(dir, {}, {});
QList<Git::Remote> vec;
if (g->isValidWorkingCopy()) {
g->getRemoteURLs(&vec);
}
for (Git::Remote const &r : vec) {
if (r.purpose == "push" || url.isEmpty()) {
url = r.url;
}
}
ui->lineEdit_repo_location->setText(url);
ui->lineEdit_working_dir->setText(dir);
- m->action = CloneDialog::Action::AddExisting;
done(Accepted);
}
}
+
+QString SubmoduleAddDialog::overridedSshKey() const
+{
+ return ui->advanced_option->sshKey();
+}
+
+
+
+
diff --git a/src/SubmoduleAddDialog.h b/src/SubmoduleAddDialog.h
new file mode 100644
index 0000000..13bc579
--- /dev/null
+++ b/src/SubmoduleAddDialog.h
@@ -0,0 +1,39 @@
+#ifndef SUBMODULEADDDIALOG_H
+#define SUBMODULEADDDIALOG_H
+
+#include <QDialog>
+#include <QThread>
+#include "Git.h"
+
+namespace Ui {
+class SubmoduleAddDialog;
+}
+
+class MainWindow;
+
+class SubmoduleAddDialog : public QDialog {
+ Q_OBJECT
+private:
+ Ui::SubmoduleAddDialog *ui;
+ struct Private;
+ Private *m;
+
+ using GitPtr = std::shared_ptr<Git>;
+private:
+ MainWindow *mainwindow();
+public:
+ explicit SubmoduleAddDialog(MainWindow *parent, QString const &url, QString const &defworkdir, const Git::Context *gcx);
+ ~SubmoduleAddDialog() override;
+
+ QString url();
+ QString dir();
+ QString overridedSshKey() const;
+ bool isForce() const;
+private slots:
+ void on_lineEdit_repo_location_textChanged(QString const &text);
+ void on_pushButton_test_clicked();
+ void on_pushButton_browse_clicked();
+ void on_pushButton_open_existing_clicked();
+};
+
+#endif // SUBMODULEADDDIALOG_H
diff --git a/src/CloneDialog.ui b/src/SubmoduleAddDialog.ui
similarity index 72%
copy from src/CloneDialog.ui
copy to src/SubmoduleAddDialog.ui
index d82f778..5ddbff0 100644
--- a/src/CloneDialog.ui
+++ b/src/SubmoduleAddDialog.ui
@@ -1,175 +1,189 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>CloneDialog</class>
- <widget class="QDialog" name="CloneDialog">
+ <class>SubmoduleAddDialog</class>
+ <widget class="QDialog" name="SubmoduleAddDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>545</width>
- <height>140</height>
+ <width>612</width>
+ <height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>Clone</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Remote</string>
</property>
</widget>
</item>
- <item row="0" column="1">
- <widget class="RepositoryLineEdit" name="lineEdit_repo_location">
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>0</height>
- </size>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="pushButton_browse">
+ <property name="text">
+ <string>Browse</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="pushButton_test">
<property name="text">
<string>&amp;Test</string>
</property>
</widget>
</item>
+ <item row="1" column="1">
+ <widget class="DirectoryLineEdit" name="lineEdit_working_dir"/>
+ </item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Local</string>
</property>
</widget>
</item>
- <item row="1" column="1">
- <widget class="DirectoryLineEdit" name="lineEdit_working_dir"/>
- </item>
- <item row="1" column="2">
- <widget class="QPushButton" name="pushButton_browse">
- <property name="text">
- <string>Browse</string>
+ <item row="0" column="1">
+ <widget class="RepositoryLineEdit" name="lineEdit_repo_location">
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>0</height>
+ </size>
</property>
</widget>
</item>
</layout>
</item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="checkBox_force">
+ <property name="text">
+ <string>--force</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="RemoteAdvancedOptionWidget" name="advanced_option" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>110</height>
+ </size>
+ </property>
+ </widget>
+ </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QComboBox" name="comboBox"/>
- </item>
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="pushButton_open_existing">
- <property name="text">
- <string>Open existing local directory...</string>
- </property>
- </widget>
- </item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
- <string>Clone</string>
+ <string>OK</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RepositoryLineEdit</class>
<extends>QLineEdit</extends>
<header>RepositoryLineEdit.h</header>
</customwidget>
<customwidget>
<class>DirectoryLineEdit</class>
<extends>QLineEdit</extends>
<header>DirectoryLineEdit.h</header>
</customwidget>
+ <customwidget>
+ <class>RemoteAdvancedOptionWidget</class>
+ <extends>QWidget</extends>
+ <header>RemoteAdvancedOptionWidget.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<tabstops>
<tabstop>lineEdit_repo_location</tabstop>
<tabstop>pushButton_test</tabstop>
<tabstop>lineEdit_working_dir</tabstop>
+ <tabstop>pushButton_browse</tabstop>
+ <tabstop>checkBox_force</tabstop>
<tabstop>pushButton</tabstop>
<tabstop>pushButton_2</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
- <receiver>CloneDialog</receiver>
+ <receiver>SubmoduleAddDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
- <x>152</x>
- <y>269</y>
+ <x>508</x>
+ <y>385</y>
</hint>
<hint type="destinationlabel">
<x>104</x>
<y>320</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_2</sender>
<signal>clicked()</signal>
- <receiver>CloneDialog</receiver>
+ <receiver>SubmoduleAddDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
- <x>244</x>
- <y>270</y>
+ <x>597</x>
+ <y>385</y>
</hint>
<hint type="destinationlabel">
<x>238</x>
<y>317</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/SubmoduleMainWindow.cpp b/src/SubmoduleMainWindow.cpp
new file mode 100644
index 0000000..af785a5
--- /dev/null
+++ b/src/SubmoduleMainWindow.cpp
@@ -0,0 +1,52 @@
+#include "SubmoduleMainWindow.h"
+#include "ui_SubmoduleMainWindow.h"
+
+SubmoduleMainWindow::SubmoduleMainWindow(MainWindow *parent, GitPtr g)
+ : QMainWindow(parent)
+ , ui(new Ui::SubmoduleMainWindow)
+{
+ ui->setupUi(this);
+ mw_ = parent;
+ g_ = g;
+ ui->frame_repository_wrapper2->bind(mw_
+ , ui->tableWidget_log2
+ , ui->listWidget_files
+ , ui->listWidget_unstaged
+ , ui->listWidget_staged
+ , ui->widget_diff_view
+ );
+ ui->frame_repository_wrapper2->prepareLogTableWidget();
+
+ QString text = g->workingDir();
+ text = "Submodule " + text;
+ setWindowTitle(text);
+
+ ui->stackedWidget_filelist->setCurrentWidget(ui->page_files);
+}
+
+SubmoduleMainWindow::~SubmoduleMainWindow()
+{
+ delete ui;
+}
+
+MainWindow *SubmoduleMainWindow::mainwindow()
+{
+ return mw_;
+}
+
+GitPtr SubmoduleMainWindow::git()
+{
+ return g_->dup();
+}
+
+RepositoryWrapperFrame *SubmoduleMainWindow::frame()
+{
+ return ui->frame_repository_wrapper2;
+}
+
+void SubmoduleMainWindow::reset()
+{
+ GitPtr g = git();
+ mainwindow()->openRepository_(ui->frame_repository_wrapper2, g);
+
+}
diff --git a/src/SubmoduleMainWindow.h b/src/SubmoduleMainWindow.h
new file mode 100644
index 0000000..215fc50
--- /dev/null
+++ b/src/SubmoduleMainWindow.h
@@ -0,0 +1,31 @@
+#ifndef SUBMODULEMAINWINDOW_H
+#define SUBMODULEMAINWINDOW_H
+
+#include <QMainWindow>
+#include "Git.h"
+
+class MainWindow;
+class RepositoryWrapperFrame;
+
+namespace Ui {
+class SubmoduleMainWindow;
+}
+
+class SubmoduleMainWindow : public QMainWindow {
+ Q_OBJECT
+private:
+ Ui::SubmoduleMainWindow *ui;
+ MainWindow *mw_;
+ GitPtr g_;
+ MainWindow *mainwindow();
+ GitPtr git();
+public:
+ explicit SubmoduleMainWindow(MainWindow *parent, GitPtr g);
+ ~SubmoduleMainWindow();
+
+ RepositoryWrapperFrame *frame();
+
+ void reset();
+};
+
+#endif // SUBMODULEMAINWINDOW_H
diff --git a/src/SubmoduleMainWindow.ui b/src/SubmoduleMainWindow.ui
new file mode 100644
index 0000000..6eefdd1
--- /dev/null
+++ b/src/SubmoduleMainWindow.ui
@@ -0,0 +1,455 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SubmoduleMainWindow</class>
+ <widget class="QMainWindow" name="SubmoduleMainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>816</width>
+ <height>581</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Guitar</string>
+ </property>
+ <property name="animated">
+ <bool>false</bool>
+ </property>
+ <widget class="QWidget" name="centralWidget">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="RepositoryWrapperFrame" name="frame_repository_wrapper2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter_h">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="childrenCollapsible">
+ <bool>false</bool>
+ </property>
+ <widget class="LogTableWidget" name="tableWidget_log2">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="tabKeyNavigation">
+ <bool>false</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderHighlightSections">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="QStackedWidget" name="stackedWidget_filelist">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="page_uncommited">
+ <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,1">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="FilesListWidget" name="listWidget_unstaged">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_2">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout" columnstretch="1,1">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="2" column="0" colspan="2">
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QToolButton" name="toolButton_unstage">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>64</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Unstage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources/resources.qrc">
+ <normaloff>:/image/unstage.svg</normaloff>:/image/unstage.svg</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QToolButton" name="toolButton_select_all">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Select all</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources/resources.qrc">
+ <normaloff>:/image/selall.svg</normaloff>:/image/selall.svg</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>18</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QToolButton" name="toolButton_stage">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>64</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Stage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources/resources.qrc">
+ <normaloff>:/image/stage.svg</normaloff>:/image/stage.svg</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QToolButton" name="toolButton_commit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string>Commit</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources/resources.qrc">
+ <normaloff>:/image/commit.svg</normaloff>:/image/commit.svg</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="FilesListWidget" name="listWidget_staged">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_files">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="FilesListWidget" name="listWidget_files">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <widget class="QFrame" name="frame_diff_view">
+ <property name="frameShape">
+ <enum>QFrame::Panel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="FileDiffWidget" name="widget_diff_view" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QStatusBar" name="statusBar"/>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>816</width>
+ <height>25</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <customwidgets>
+ <customwidget>
+ <class>FileDiffWidget</class>
+ <extends>QWidget</extends>
+ <header>FileDiffWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>LogTableWidget</class>
+ <extends>QTableWidget</extends>
+ <header>LogTableWidget.h</header>
+ </customwidget>
+ <customwidget>
+ <class>FilesListWidget</class>
+ <extends>QListWidget</extends>
+ <header>FilesListWidget.h</header>
+ </customwidget>
+ <customwidget>
+ <class>RepositoryWrapperFrame</class>
+ <extends>QFrame</extends>
+ <header>RepositoryWrapperFrame.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>tableWidget_log2</tabstop>
+ <tabstop>listWidget_unstaged</tabstop>
+ <tabstop>toolButton_select_all</tabstop>
+ <tabstop>toolButton_stage</tabstop>
+ <tabstop>toolButton_unstage</tabstop>
+ <tabstop>toolButton_commit</tabstop>
+ <tabstop>listWidget_staged</tabstop>
+ <tabstop>listWidget_files</tabstop>
+ <tabstop>widget_diff_view</tabstop>
+ </tabstops>
+ <resources>
+ <include location="resources/resources.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/SubmoduleUpdateDialog.cpp b/src/SubmoduleUpdateDialog.cpp
new file mode 100644
index 0000000..e63b81b
--- /dev/null
+++ b/src/SubmoduleUpdateDialog.cpp
@@ -0,0 +1,24 @@
+#include "SubmoduleUpdateDialog.h"
+#include "ui_SubmoduleUpdateDialog.h"
+
+SubmoduleUpdateDialog::SubmoduleUpdateDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::SubmoduleUpdateDialog)
+{
+ ui->setupUi(this);
+}
+
+SubmoduleUpdateDialog::~SubmoduleUpdateDialog()
+{
+ delete ui;
+}
+
+bool SubmoduleUpdateDialog::isInit() const
+{
+ return ui->checkBox_init->isChecked();
+}
+
+bool SubmoduleUpdateDialog::isRecursive() const
+{
+ return ui->checkBox_recursive->isChecked();
+}
diff --git a/src/SubmoduleUpdateDialog.h b/src/SubmoduleUpdateDialog.h
new file mode 100644
index 0000000..7599375
--- /dev/null
+++ b/src/SubmoduleUpdateDialog.h
@@ -0,0 +1,26 @@
+#ifndef SUBMODULEUPDATEDIALOG_H
+#define SUBMODULEUPDATEDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+class SubmoduleUpdateDialog;
+}
+
+class SubmoduleUpdateDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit SubmoduleUpdateDialog(QWidget *parent = nullptr);
+ ~SubmoduleUpdateDialog();
+
+ bool isInit() const;
+ bool isRecursive() const;
+
+
+private:
+ Ui::SubmoduleUpdateDialog *ui;
+};
+
+#endif // SUBMODULEUPDATEDIALOG_H
diff --git a/src/CheckoutDialog.ui b/src/SubmoduleUpdateDialog.ui
similarity index 61%
copy from src/CheckoutDialog.ui
copy to src/SubmoduleUpdateDialog.ui
index 1409b7f..9b3abb5 100644
--- a/src/CheckoutDialog.ui
+++ b/src/SubmoduleUpdateDialog.ui
@@ -1,120 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>CheckoutDialog</class>
- <widget class="QDialog" name="CheckoutDialog">
+ <class>SubmoduleUpdateDialog</class>
+ <widget class="QDialog" name="SubmoduleUpdateDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>366</width>
- <height>135</height>
+ <width>400</width>
+ <height>91</height>
</rect>
</property>
<property name="windowTitle">
- <string>Checkout</string>
+ <string>Submodule Update</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QRadioButton" name="radioButton_head_detached">
+ <widget class="QCheckBox" name="checkBox_init">
<property name="text">
- <string>RadioButton</string>
+ <string>--init</string>
</property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="radioButton_existing_local_branch">
- <property name="text">
- <string>RadioButton</string>
+ <property name="checked">
+ <bool>true</bool>
</property>
</widget>
</item>
<item>
- <widget class="QRadioButton" name="radioButton_create_local_branch">
+ <widget class="QCheckBox" name="checkBox_recursive">
<property name="text">
- <string>RadioButton</string>
+ <string>--recursive</string>
</property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="comboBox_branch_name">
- <property name="editable">
+ <property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
- <tabstops>
- <tabstop>radioButton_head_detached</tabstop>
- <tabstop>radioButton_existing_local_branch</tabstop>
- <tabstop>radioButton_create_local_branch</tabstop>
- <tabstop>pushButton</tabstop>
- <tabstop>pushButton_2</tabstop>
- </tabstops>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
- <receiver>CheckoutDialog</receiver>
+ <receiver>SubmoduleUpdateDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
- <x>162</x>
- <y>165</y>
+ <x>230</x>
+ <y>72</y>
</hint>
<hint type="destinationlabel">
- <x>134</x>
- <y>160</y>
+ <x>217</x>
+ <y>73</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_2</sender>
<signal>clicked()</signal>
- <receiver>CheckoutDialog</receiver>
+ <receiver>SubmoduleUpdateDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
- <x>271</x>
- <y>171</y>
+ <x>377</x>
+ <y>71</y>
</hint>
<hint type="destinationlabel">
- <x>243</x>
- <y>177</y>
+ <x>392</x>
+ <y>70</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/src/SubmodulesDialog.cpp b/src/SubmodulesDialog.cpp
new file mode 100644
index 0000000..7de9ae9
--- /dev/null
+++ b/src/SubmodulesDialog.cpp
@@ -0,0 +1,46 @@
+#include "SubmodulesDialog.h"
+#include "ui_SubmodulesDialog.h"
+#include "MainWindow.h"
+#include "common/misc.h"
+
+SubmodulesDialog::SubmodulesDialog(QWidget *parent, std::vector<Submodule> mods)
+ : QDialog(parent)
+ , ui(new Ui::SubmodulesDialog)
+{
+ ui->setupUi(this);
+
+ int rows = (int)mods.size();
+ ui->tableWidget->setColumnCount(5);
+ ui->tableWidget->setRowCount(rows);
+ for (int row = 0; row < rows; row++) {
+ QTableWidgetItem *item;
+
+ item = new QTableWidgetItem();
+ item->setText(mods[row].submodule.path);
+ ui->tableWidget->setItem(row, 0, item);
+
+ item = new QTableWidgetItem();
+ item->setText(MainWindow::abbrevCommitID(mods[row].head));
+ ui->tableWidget->setItem(row, 1, item);
+
+ item = new QTableWidgetItem();
+ item->setText(misc::makeDateTimeString(mods[row].head.commit_date));
+ ui->tableWidget->setItem(row, 2, item);
+
+ item = new QTableWidgetItem();
+ item->setText(mods[row].head.author);
+ ui->tableWidget->setItem(row, 3, item);
+
+ item = new QTableWidgetItem();
+ item->setText(mods[row].head.message);
+ ui->tableWidget->setItem(row, 4, item);
+ }
+
+ ui->tableWidget->resizeColumnsToContents();
+ ui->tableWidget->horizontalHeader()->setStretchLastSection(true);
+}
+
+SubmodulesDialog::~SubmodulesDialog()
+{
+ delete ui;
+}
diff --git a/src/SubmodulesDialog.h b/src/SubmodulesDialog.h
new file mode 100644
index 0000000..a0941a5
--- /dev/null
+++ b/src/SubmodulesDialog.h
@@ -0,0 +1,27 @@
+#ifndef SUBMODULESDIALOG_H
+#define SUBMODULESDIALOG_H
+
+#include <QDialog>
+#include "Git.h"
+#include <vector>
+
+namespace Ui {
+class SubmodulesDialog;
+}
+
+class SubmodulesDialog : public QDialog {
+ Q_OBJECT
+public:
+ struct Submodule {
+ Git::SubmoduleItem submodule;
+ Git::CommitItem head;
+ };
+public:
+ explicit SubmodulesDialog(QWidget *parent, std::vector<Submodule> mods);
+ ~SubmodulesDialog();
+
+private:
+ Ui::SubmodulesDialog *ui;
+};
+
+#endif // SUBMODULESDIALOG_H
diff --git a/src/SubmodulesDialog.ui b/src/SubmodulesDialog.ui
new file mode 100644
index 0000000..c35cb2e
--- /dev/null
+++ b/src/SubmodulesDialog.ui
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SubmodulesDialog</class>
+ <widget class="QDialog" name="SubmodulesDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTableWidget" name="tableWidget">
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>pushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SubmodulesDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>322</x>
+ <y>285</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>298</x>
+ <y>280</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/Terminal.cpp b/src/Terminal.cpp
index 84c2e57..aa9e7e2 100644
--- a/src/Terminal.cpp
+++ b/src/Terminal.cpp
@@ -1,59 +1,47 @@
#include "Terminal.h"
-
+#include "ApplicationGlobal.h"
#include <QFileInfo>
#ifdef Q_OS_WIN
#include <windows.h>
void Terminal::open(QString const &dir)
{
if (dir.indexOf('\"') < 0 && QFileInfo(dir).isDir()) {
QString arg;
if (dir.at(0).isLetter() && dir.at(1) == ':') {
arg = QString("%1 & cd %2").arg(dir.mid(0, 2)).arg(dir);
} else {
arg = QString("cd %1").arg(dir);
}
QString cmd = "cmd.exe /k \"%1\"";
cmd = cmd.arg(arg);
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
CreateProcess(nullptr, (wchar_t *)cmd.utf16(), nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
}
#else
void Terminal::open(QString const &dir)
{
if (dir.indexOf('\"') < 0 && QFileInfo(dir).isDir()) {
- auto GetTerm = [&](std::vector<char const *> const &vec){
- #ifndef __HAIKU__
- for (char const *name : vec) {
- char const *p = getenv(name);
- if (p && *p) return p;
- }
- return "x-terminal-emulator";
- #else
- return "Terminal";
- #endif
- };
- QString term = GetTerm({"COLORTERM", "TERM"});
-
+ QString term = global->appsettings.terminal_command;
QString cmd = "/bin/sh -c \"cd \\\"%1\\\" && %2\" &";
cmd = cmd.arg(dir).arg(term);
int r = system(cmd.toStdString().c_str());
(void)r;
}
}
#endif
diff --git a/src/UserEvent.cpp b/src/UserEvent.cpp
new file mode 100644
index 0000000..b0611a0
--- /dev/null
+++ b/src/UserEvent.cpp
@@ -0,0 +1,3 @@
+#include "UserEvent.h"
+
+
diff --git a/src/UserEvent.h b/src/UserEvent.h
new file mode 100644
index 0000000..713172f
--- /dev/null
+++ b/src/UserEvent.h
@@ -0,0 +1,37 @@
+#ifndef USEREVENT_H
+#define USEREVENT_H
+
+#include <QEvent>
+#include <QVariant>
+#include <functional>
+
+enum class UserEvent {
+ Start = QEvent::User,
+ UserFunction,
+};
+
+class StartEvent : public QEvent {
+public:
+ StartEvent()
+ : QEvent((QEvent::Type)UserEvent::Start)
+ {
+ }
+};
+
+class UserFunctionEvent : public QEvent {
+public:
+ std::function<void(QVariant const &, void *ptr)> func;
+ QVariant var;
+ void *ptr = nullptr;
+
+ explicit UserFunctionEvent(std::function<void(QVariant const &, void *ptr)> const &func, QVariant const &var, void *ptr = nullptr)
+ : QEvent((QEvent::Type)UserEvent::UserFunction)
+ , func(func)
+ , var(var)
+ , ptr(ptr)
+ {
+ }
+};
+
+
+#endif // USEREVENT_H
diff --git a/src/WelcomeWizardDialog.cpp b/src/WelcomeWizardDialog.cpp
index c7f0f24..6d61a53 100644
--- a/src/WelcomeWizardDialog.cpp
+++ b/src/WelcomeWizardDialog.cpp
@@ -1,198 +1,216 @@
#include "WelcomeWizardDialog.h"
#include "ui_WelcomeWizardDialog.h"
#include <QFileDialog>
#include "MainWindow.h"
#include "common/misc.h"
#include "Git.h"
-WelcomeWizardDialog::WelcomeWizardDialog(BasicMainWindow *parent)
+WelcomeWizardDialog::WelcomeWizardDialog(MainWindow *parent)
: QDialog(parent)
, ui(new Ui::WelcomeWizardDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
mainwindow_ = parent;
pages_.push_back(ui->page_helper_tools);
pages_.push_back(ui->page_global_user_information);
pages_.push_back(ui->page_default_working_folder);
pages_.push_back(ui->page_finish);
ui->stackedWidget->setCurrentWidget(pages_[0]);
on_stackedWidget_currentChanged(0);
avatar_loader_.start(mainwindow_);
- connect(&avatar_loader_, &AvatarLoader::updated, [&](){
+ connect(&avatar_loader_, &AvatarLoader::updated, [&](RepositoryWrapperFrameP frame){
QString email = ui->lineEdit_user_email->text();
- QIcon icon = avatar_loader_.fetch(email.toStdString(), false);
+ QIcon icon = avatar_loader_.fetch(frame.pointer, email.toStdString(), false);
setAvatar(icon);
});
ui->stackedWidget->setCurrentWidget(ui->page_helper_tools);
}
WelcomeWizardDialog::~WelcomeWizardDialog()
{
avatar_loader_.stop();
delete ui;
}
void WelcomeWizardDialog::set_user_name(QString const &v)
{
ui->lineEdit_user_name->setText(v);
}
void WelcomeWizardDialog::set_user_email(QString const &v)
{
ui->lineEdit_user_email->setText(v);
}
void WelcomeWizardDialog::set_default_working_folder(QString const &v)
{
ui->lineEdit_default_working_folder->setText(v);
}
void WelcomeWizardDialog::set_git_command_path(QString const &v)
{
ui->lineEdit_git->setText(v);
}
void WelcomeWizardDialog::set_file_command_path(QString const &v)
{
ui->lineEdit_file->setText(v);
}
QString WelcomeWizardDialog::user_name() const
{
return ui->lineEdit_user_name->text();
}
QString WelcomeWizardDialog::user_email() const
{
return ui->lineEdit_user_email->text();
}
QString WelcomeWizardDialog::default_working_folder() const
{
return ui->lineEdit_default_working_folder->text();
}
QString WelcomeWizardDialog::git_command_path() const
{
return ui->lineEdit_git->text();
}
QString WelcomeWizardDialog::file_command_path() const
{
return ui->lineEdit_file->text();
}
void WelcomeWizardDialog::on_pushButton_prev_clicked()
{
int i = pages_.indexOf(ui->stackedWidget->currentWidget());
if (i == 0) {
done(QDialog::Rejected);
return;
}
if (i > 0) {
i--;
QWidget *w = pages_[i];
ui->stackedWidget->setCurrentWidget(w);
}
}
void WelcomeWizardDialog::on_pushButton_next_clicked()
{
if (ui->stackedWidget->currentWidget() == ui->page_finish) {
done(QDialog::Accepted);
}
int i = pages_.indexOf(ui->stackedWidget->currentWidget());
if (i + 1 < pages_.size()) {
i++;
QWidget *w = pages_[i];
ui->stackedWidget->setCurrentWidget(w);
}
}
void WelcomeWizardDialog::on_stackedWidget_currentChanged(int /*arg1*/)
{
QString prev_text;
QString next_text;
QWidget *w = ui->stackedWidget->currentWidget();
if (w == ui->page_helper_tools) {
prev_text = tr("Cancel");
ui->lineEdit_git->setFocus();
} else if (w == ui->page_global_user_information) {
if (user_name().isEmpty() && user_email().isEmpty()) {
Git::Context gcx;
gcx.git_command = git_command_path();
- Git g(gcx, QString());
+ Git g(gcx, {}, {}, {});
Git::User user = g.getUser(Git::Source::Global);
set_user_name(user.name);
set_user_email(user.email);
}
if (user_name().isEmpty()) {
ui->lineEdit_user_name->setFocus();
} else if (user_email().isEmpty()) {
ui->lineEdit_user_email->setFocus();
} else {
ui->pushButton_next->setFocus();
}
} else if (w == ui->page_default_working_folder) {
ui->lineEdit_default_working_folder->setFocus();
} else if (w == ui->page_finish) {
ui->lineEdit_preview_user->setText(ui->lineEdit_user_name->text());
ui->lineEdit_preview_email->setText(ui->lineEdit_user_email->text());
ui->lineEdit_preview_folder->setText(ui->lineEdit_default_working_folder->text());
ui->lineEdit_preview_git->setText(ui->lineEdit_git->text());
ui->lineEdit_preview_file->setText(ui->lineEdit_file->text());
next_text = tr("Finish");
ui->pushButton_next->setFocus();
}
ui->pushButton_prev->setText(prev_text.isEmpty() ? tr("<< Prev") : prev_text);
ui->pushButton_next->setText(next_text.isEmpty() ? tr("Next >>") : next_text);
ui->pushButton_next->setDefault(true);
}
void WelcomeWizardDialog::on_pushButton_browse_default_workiing_folder_clicked()
{
QString s = ui->lineEdit_default_working_folder->text();
s = QFileDialog::getExistingDirectory(this, tr("Default Working Folder"), s);
s = misc::normalizePathSeparator(s);
ui->lineEdit_default_working_folder->setText(s);
}
void WelcomeWizardDialog::on_pushButton_browse_git_clicked()
{
QString s = mainwindow_->selectGitCommand(false);
ui->lineEdit_git->setText(s);
}
void WelcomeWizardDialog::on_pushButton_browse_file_clicked()
{
QString s = mainwindow_->selectFileCommand(false);
ui->lineEdit_file->setText(s);
}
void WelcomeWizardDialog::setAvatar(QIcon const &icon)
{
QPixmap pm = icon.pixmap(QSize(64, 64));
ui->label_avatar->setPixmap(pm);
}
void WelcomeWizardDialog::on_pushButton_get_icon_clicked()
{
ui->label_avatar->setPixmap(QPixmap());
QString email = ui->lineEdit_user_email->text();
if (email.indexOf('@') > 0) {
- QIcon icon = avatar_loader_.fetch(email.toStdString(), true);
+ QIcon icon = avatar_loader_.fetch(nullptr, email.toStdString(), true);
if (!icon.isNull()) {
setAvatar(icon);
}
}
}
+
+void WelcomeWizardDialog::on_lineEdit_git_textChanged(const QString &arg1)
+{
+ QString ss;
+ if (!misc::isExecutable(arg1)) {
+ ss = "* { background-color: #ffc0c0; }";
+ }
+ ui->lineEdit_git->setStyleSheet(ss);
+}
+
+void WelcomeWizardDialog::on_lineEdit_file_textChanged(const QString &arg1)
+{
+ QString ss;
+ if (!misc::isExecutable(arg1)) {
+ ss = "* { background-color: #ffc0c0; }";
+ }
+ ui->lineEdit_file->setStyleSheet(ss);
+}
diff --git a/src/WelcomeWizardDialog.h b/src/WelcomeWizardDialog.h
index a6d8774..5036ef0 100644
--- a/src/WelcomeWizardDialog.h
+++ b/src/WelcomeWizardDialog.h
@@ -1,48 +1,52 @@
#ifndef WELCOMEWIZARDDIALOG_H
#define WELCOMEWIZARDDIALOG_H
#include "AvatarLoader.h"
#include <QDialog>
-class BasicMainWindow;
+class MainWindow;
namespace Ui {
class WelcomeWizardDialog;
}
class WelcomeWizardDialog : public QDialog {
Q_OBJECT
private:
- BasicMainWindow *mainwindow_;
+ MainWindow *mainwindow_;
AvatarLoader avatar_loader_;
QList<QWidget *> pages_;
public:
- explicit WelcomeWizardDialog(BasicMainWindow *parent = nullptr);
+ explicit WelcomeWizardDialog(MainWindow *parent = nullptr);
~WelcomeWizardDialog() override;
void set_user_name(QString const &v);
void set_user_email(QString const &v);
void set_default_working_folder(QString const &v);
void set_git_command_path(QString const &v);
void set_file_command_path(QString const &v);
QString user_name() const;
QString user_email() const;
QString default_working_folder() const;
QString git_command_path() const;
QString file_command_path() const;
private slots:
void on_stackedWidget_currentChanged(int arg1);
void on_pushButton_browse_default_workiing_folder_clicked();
void on_pushButton_browse_git_clicked();
void on_pushButton_browse_file_clicked();
void on_pushButton_get_icon_clicked();
void on_pushButton_prev_clicked();
void on_pushButton_next_clicked();
+ void on_lineEdit_git_textChanged(const QString &arg1);
+
+ void on_lineEdit_file_textChanged(const QString &arg1);
+
private:
Ui::WelcomeWizardDialog *ui;
void setAvatar(const QIcon &icon);
};
#endif // WELCOMEWIZARDDIALOG_H
diff --git a/src/WelcomeWizardDialog.ui b/src/WelcomeWizardDialog.ui
index 727bffa..a017d1d 100644
--- a/src/WelcomeWizardDialog.ui
+++ b/src/WelcomeWizardDialog.ui
@@ -1,450 +1,450 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WelcomeWizardDialog</class>
<widget class="QDialog" name="WelcomeWizardDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>295</height>
</rect>
</property>
<property name="windowTitle">
<string>Welcome to the Guitar Wizard</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
- <number>1</number>
+ <number>0</number>
</property>
<widget class="QWidget" name="page_helper_tools">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Helper Tools</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>git</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_git"/>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="pushButton_browse_git">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>file</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_file"/>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pushButton_browse_file">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>161</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_global_user_information">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Global User Information</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>git config --global user.name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_user_name"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>git config --global user.email</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_user_email"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="pushButton_get_icon">
<property name="text">
<string>Get icon from Gravatar</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_avatar">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>167</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_default_working_folder">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Default Working Folder</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>folder</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_default_working_folder"/>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="pushButton_browse_default_workiing_folder">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>190</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_finish">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="label_8">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Ready to play the Guitar !</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>user</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="ReadOnlyLineEdit" name="lineEdit_preview_user">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>email</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="ReadOnlyLineEdit" name="lineEdit_preview_email">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>folder</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="ReadOnlyLineEdit" name="lineEdit_preview_folder">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>git</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="ReadOnlyLineEdit" name="lineEdit_preview_git">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>file</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="ReadOnlyLineEdit" name="lineEdit_preview_file">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="Line" name="line_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>71</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_prev">
<property name="text">
<string>&lt;&lt; Prev</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_next">
<property name="text">
<string>Next &gt;&gt;</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ReadOnlyLineEdit</class>
<extends>QLineEdit</extends>
<header>ReadOnlyLineEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/src/coloredit/ColorDialog.cpp b/src/coloredit/ColorDialog.cpp
new file mode 100644
index 0000000..1e236ff
--- /dev/null
+++ b/src/coloredit/ColorDialog.cpp
@@ -0,0 +1,65 @@
+#include "ColorDialog.h"
+#include "ui_ColorDialog.h"
+
+ColorDialog::ColorDialog(QWidget *parent, const QColor &color)
+ : QDialog(parent)
+ , ui(new Ui::ColorDialog)
+{
+ ui->setupUi(this);
+
+ ui->widget_editor->bind(ui->widget_square);
+
+ connect(ui->widget_editor, &ColorEditWidget::colorChanged, [&](QColor const &color){
+ ui->widget_preview->setColor(color);
+ });
+
+ setColor(color);
+}
+
+ColorDialog::~ColorDialog()
+{
+ delete ui;
+}
+
+QColor ColorDialog::color() const
+{
+ return ui->widget_editor->color();
+}
+
+void ColorDialog::setColor(QColor const &color)
+{
+ ui->widget_editor->setColor(color);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/coloredit/ColorDialog.h b/src/coloredit/ColorDialog.h
new file mode 100644
index 0000000..309345f
--- /dev/null
+++ b/src/coloredit/ColorDialog.h
@@ -0,0 +1,22 @@
+#ifndef COLORDIALOG_H
+#define COLORDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+class ColorDialog;
+}
+
+class ColorDialog : public QDialog {
+ Q_OBJECT
+private:
+public:
+ explicit ColorDialog(QWidget *parent, QColor const &color);
+ ~ColorDialog();
+ QColor color() const;
+ void setColor(const QColor &color);
+private:
+ Ui::ColorDialog *ui;
+};
+
+#endif // COLORDIALOG_H
diff --git a/src/coloredit/ColorDialog.ui b/src/coloredit/ColorDialog.ui
new file mode 100644
index 0000000..e585e56
--- /dev/null
+++ b/src/coloredit/ColorDialog.ui
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColorDialog</class>
+ <widget class="QDialog" name="ColorDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>514</width>
+ <height>184</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Color</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="ColorSquareWidget" name="widget_square" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>128</width>
+ <height>128</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>128</width>
+ <height>128</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ColorEditWidget" name="widget_editor" native="true"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="ColorPreviewWidget" name="widget_preview" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>128</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>OK</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton_2">
+ <property name="text">
+ <string>Cancel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ColorEditWidget</class>
+ <extends>QWidget</extends>
+ <header>ColorEditWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ColorPreviewWidget</class>
+ <extends>QWidget</extends>
+ <header>ColorPreviewWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ColorSquareWidget</class>
+ <extends>QWidget</extends>
+ <header>ColorSquareWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>pushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ColorDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>342</x>
+ <y>159</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>322</x>
+ <y>165</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>pushButton_2</sender>
+ <signal>clicked()</signal>
+ <receiver>ColorDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>482</x>
+ <y>166</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>508</x>
+ <y>167</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/coloredit/ColorEditWidget.cpp b/src/coloredit/ColorEditWidget.cpp
new file mode 100644
index 0000000..273baa5
--- /dev/null
+++ b/src/coloredit/ColorEditWidget.cpp
@@ -0,0 +1,173 @@
+#include "ColorEditWidget.h"
+#include "ColorSquareWidget.h"
+#include "ui_ColorEditWidget.h"
+
+ColorEditWidget::ColorEditWidget(QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::ColorEditWidget)
+{
+ ui->setupUi(this);
+
+ ui->horizontalSlider_rgb_r->setVisualType(ColorSlider::RGB_R);
+ ui->horizontalSlider_rgb_g->setVisualType(ColorSlider::RGB_G);
+ ui->horizontalSlider_rgb_b->setVisualType(ColorSlider::RGB_B);
+ ui->horizontalSlider_hsv_h->setVisualType(ColorSlider::HSV_H);
+ ui->horizontalSlider_hsv_s->setVisualType(ColorSlider::HSV_S);
+ ui->horizontalSlider_hsv_v->setVisualType(ColorSlider::HSV_V);
+
+ ui->tabWidget->setCurrentWidget(ui->tab_color_hsv);
+
+}
+
+ColorEditWidget::~ColorEditWidget()
+{
+ delete ui;
+}
+
+void ColorEditWidget::bind(ColorSquareWidget *w)
+{
+ pickupper_ = w;
+ connect(pickupper_, &ColorSquareWidget::changeColor, this, &ColorEditWidget::setColor);
+}
+
+ColorSquareWidget *ColorEditWidget::pickupper()
+{
+ return pickupper_;
+}
+
+QColor ColorEditWidget::color() const
+{
+ return color_;
+}
+
+void ColorEditWidget::setColor(QColor color)
+{
+ color_ = color;
+
+ auto Set = [&](int v, ColorSlider *slider, QSpinBox *spin){
+ bool f1 = slider->blockSignals(true);
+ slider->setColor(color_);
+ slider->setValue(v);
+ slider->blockSignals(f1);
+ bool f2 = spin->blockSignals(true);
+ spin->setValue(v);
+ spin->blockSignals(f2);
+ };
+ Set(color.red(), ui->horizontalSlider_rgb_r, ui->spinBox_rgb_r);
+ Set(color.green(), ui->horizontalSlider_rgb_g, ui->spinBox_rgb_g);
+ Set(color.blue(), ui->horizontalSlider_rgb_b, ui->spinBox_rgb_b);
+ Set(color.hue(), ui->horizontalSlider_hsv_h, ui->spinBox_hsv_h);
+ Set(color.saturation(), ui->horizontalSlider_hsv_s, ui->spinBox_hsv_s);
+ Set(color.value(), ui->horizontalSlider_hsv_v, ui->spinBox_hsv_v);
+
+ ColorSquareWidget *w = pickupper();
+ if (w) {
+ bool f = w->blockSignals(true);
+ w->setHue(color.hue());
+ w->blockSignals(f);
+ }
+
+ emit colorChanged(color_);
+}
+
+void ColorEditWidget::setColorRed(int value)
+{
+ QColor c = color();
+ c = QColor(value, c.green(), c.blue());
+ setColor(c);
+}
+
+void ColorEditWidget::setColorGreen(int value)
+{
+ QColor c = color();
+ c = QColor(c.red(), value, c.blue());
+ setColor(c);
+}
+
+void ColorEditWidget::setColorBlue(int value)
+{
+ QColor c = color();
+ c = QColor(c.red(), c.green(), value);
+ setColor(c);
+}
+
+void ColorEditWidget::setColorHue(int value)
+{
+ QColor c = color();
+ c = QColor::fromHsv(value, c.saturation(), c.value());
+ setColor(c);
+}
+
+void ColorEditWidget::setColorSaturation(int value)
+{
+ QColor c = color();
+ c = QColor::fromHsv(c.hue(), value, c.value());
+ setColor(c);
+}
+
+void ColorEditWidget::setColorValue(int value)
+{
+ QColor c = color();
+ c = QColor::fromHsv(c.hue(), c.saturation(), value);
+ setColor(c);
+}
+
+void ColorEditWidget::on_horizontalSlider_hsv_h_valueChanged(int value)
+{
+ setColorHue(value);
+}
+
+void ColorEditWidget::on_horizontalSlider_hsv_s_valueChanged(int value)
+{
+ setColorSaturation(value);
+}
+
+void ColorEditWidget::on_horizontalSlider_hsv_v_valueChanged(int value)
+{
+ setColorValue(value);
+}
+
+void ColorEditWidget::on_horizontalSlider_rgb_r_valueChanged(int value)
+{
+ setColorRed(value);
+}
+
+void ColorEditWidget::on_horizontalSlider_rgb_g_valueChanged(int value)
+{
+ setColorGreen(value);
+}
+
+void ColorEditWidget::on_horizontalSlider_rgb_b_valueChanged(int value)
+{
+ setColorBlue(value);
+}
+
+void ColorEditWidget::on_spinBox_hsv_h_valueChanged(int arg1)
+{
+ setColorHue(arg1);
+}
+
+void ColorEditWidget::on_spinBox_hsv_s_valueChanged(int arg1)
+{
+ setColorSaturation(arg1);
+}
+
+void ColorEditWidget::on_spinBox_hsv_v_valueChanged(int arg1)
+{
+ setColorValue(arg1);
+}
+
+void ColorEditWidget::on_spinBox_rgb_r_valueChanged(int arg1)
+{
+ setColorRed(arg1);
+}
+
+void ColorEditWidget::on_spinBox_rgb_g_valueChanged(int arg1)
+{
+ setColorGreen(arg1);
+}
+
+void ColorEditWidget::on_spinBox_rgb_b_valueChanged(int arg1)
+{
+ setColorBlue(arg1);
+}
diff --git a/src/coloredit/ColorEditWidget.h b/src/coloredit/ColorEditWidget.h
new file mode 100644
index 0000000..f77a369
--- /dev/null
+++ b/src/coloredit/ColorEditWidget.h
@@ -0,0 +1,50 @@
+#ifndef COLOREDITWIDGET_H
+#define COLOREDITWIDGET_H
+
+#include <QWidget>
+
+class ColorSquareWidget;
+
+namespace Ui {
+class ColorEditWidget;
+}
+
+class ColorEditWidget : public QWidget {
+ Q_OBJECT
+private:
+ Ui::ColorEditWidget *ui;
+ QColor color_;
+ ColorSquareWidget *pickupper_ = nullptr;
+ void setColorRed(int value);
+ void setColorGreen(int value);
+ void setColorBlue(int value);
+ void setColorHue(int value);
+ void setColorSaturation(int value);
+ void setColorValue(int value);
+public:
+ explicit ColorEditWidget(QWidget *parent = nullptr);
+ ~ColorEditWidget();
+ void bind(ColorSquareWidget *w);
+
+ QColor color() const;
+ void setColor(QColor color);
+private:
+ ColorSquareWidget *pickupper();
+private slots:
+ void on_horizontalSlider_hsv_h_valueChanged(int value);
+ void on_horizontalSlider_hsv_s_valueChanged(int value);
+ void on_horizontalSlider_hsv_v_valueChanged(int value);
+ void on_horizontalSlider_rgb_r_valueChanged(int value);
+ void on_horizontalSlider_rgb_g_valueChanged(int value);
+ void on_horizontalSlider_rgb_b_valueChanged(int value);
+ void on_spinBox_hsv_h_valueChanged(int arg1);
+ void on_spinBox_hsv_s_valueChanged(int arg1);
+ void on_spinBox_hsv_v_valueChanged(int arg1);
+ void on_spinBox_rgb_r_valueChanged(int arg1);
+ void on_spinBox_rgb_g_valueChanged(int arg1);
+ void on_spinBox_rgb_b_valueChanged(int arg1);
+signals:
+ void colorChanged(QColor const &color);
+};
+
+#endif // COLOREDITWIDGET_H
diff --git a/src/coloredit/ColorEditWidget.ui b/src/coloredit/ColorEditWidget.ui
new file mode 100644
index 0000000..250e486
--- /dev/null
+++ b/src/coloredit/ColorEditWidget.ui
@@ -0,0 +1,438 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColorEditWidget</class>
+ <widget class="QWidget" name="ColorEditWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>357</width>
+ <height>122</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab_color_hsv">
+ <attribute name="title">
+ <string>HSV</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="ColorSlider" name="horizontalSlider_hsv_h">
+ <property name="maximum">
+ <number>359</number>
+ </property>
+ <property name="pageStep">
+ <number>10</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ColorSlider" name="horizontalSlider_hsv_s">
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>10</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ColorSlider" name="horizontalSlider_hsv_v">
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>10</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="minimumSize">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>H</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_hsv_h">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>359</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="minimumSize">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>S</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_hsv_s">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_6">
+ <property name="minimumSize">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>V</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_hsv_v">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_color_rgb">
+ <attribute name="title">
+ <string>RGB</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="ColorSlider" name="horizontalSlider_rgb_r">
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>8</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ColorSlider" name="horizontalSlider_rgb_g">
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>8</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ColorSlider" name="horizontalSlider_rgb_b">
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>8</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="minimumSize">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>R</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_rgb_r">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="minimumSize">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>G</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_rgb_g">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="minimumSize">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>B</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_rgb_b">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ColorSlider</class>
+ <extends>QSlider</extends>
+ <header>ColorSlider.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/coloredit/ColorPreviewWidget.cpp b/src/coloredit/ColorPreviewWidget.cpp
new file mode 100644
index 0000000..79e1f25
--- /dev/null
+++ b/src/coloredit/ColorPreviewWidget.cpp
@@ -0,0 +1,27 @@
+#include "ColorPreviewWidget.h"
+
+#include <QPainter>
+
+ColorPreviewWidget::ColorPreviewWidget(QWidget *parent)
+ : QWidget(parent)
+{
+ color_ = Qt::red;
+}
+
+void ColorPreviewWidget::setColor(const QColor &color)
+{
+ color_ = color;
+ update();
+}
+
+void ColorPreviewWidget::paintEvent(QPaintEvent *)
+{
+ int w = width();
+ int h = height();
+ QPainter pr(this);
+ pr.fillRect(0, 0, w - 1, 1, Qt::black);
+ pr.fillRect(0, 1, 1, h - 1, Qt::black);
+ pr.fillRect(w - 1, 0, 1, h - 1, Qt::black);
+ pr.fillRect(1, h - 1, w - 1, 1, Qt::black);
+ pr.fillRect(1, 1, w - 2, h - 2, color_);
+}
diff --git a/src/coloredit/ColorPreviewWidget.h b/src/coloredit/ColorPreviewWidget.h
new file mode 100644
index 0000000..ac9c87f
--- /dev/null
+++ b/src/coloredit/ColorPreviewWidget.h
@@ -0,0 +1,16 @@
+#ifndef COLORPREVIEWWIDGET_H
+#define COLORPREVIEWWIDGET_H
+
+#include <QWidget>
+
+class ColorPreviewWidget : public QWidget {
+private:
+ QColor color_;
+protected:
+ void paintEvent(QPaintEvent *);
+public:
+ ColorPreviewWidget(QWidget *parent);
+ void setColor(QColor const &color);
+};
+
+#endif // COLORPREVIEWWIDGET_H
diff --git a/src/coloredit/ColorSlider.cpp b/src/coloredit/ColorSlider.cpp
new file mode 100644
index 0000000..fbf0e8b
--- /dev/null
+++ b/src/coloredit/ColorSlider.cpp
@@ -0,0 +1,65 @@
+#include "ColorSlider.h"
+#include <functional>
+
+ColorSlider::ColorSlider(QWidget *parent)
+ : RingSlider(parent)
+{
+ color_ = Qt::white;
+ setVisualType(HSV_H);
+}
+
+ColorSlider::VisualType ColorSlider::visualType() const
+{
+ return color_type_;
+}
+
+void ColorSlider::setVisualType(VisualType type)
+{
+ color_type_ = type;
+
+ int max = (color_type_ == HSV_H) ? 359 : 255;
+ setMaximum(max);
+
+ update();
+}
+
+void ColorSlider::setColor(QColor const &color)
+{
+ color_ = color;
+ update();
+}
+
+QImage ColorSlider::generateSliderImage()
+{
+ int max = maximum();
+
+ std::function<QColor(int t)> color;
+
+ auto rgb_r = [&](int r){ return QColor(r, color_.green(), color_.blue()); };
+ auto rgb_g = [&](int g){ return QColor(color_.red(), g, color_.blue()); };
+ auto rgb_b = [&](int b){ return QColor(color_.red(), color_.green(), b); };
+ auto hsv_h = [&](int h){ return QColor::fromHsv(h, 255, 255); };
+ auto hsv_s = [&](int s){ return QColor::fromHsv(color_.hue(), s, color_.value()); };
+ auto hsv_v = [&](int v){ return QColor::fromHsv(color_.hue(), color_.saturation(), v); };
+ auto unknown = [&](int i){ return QColor(i, i, i); };
+
+ switch (color_type_) {
+ case RGB_R: color = rgb_r; break;
+ case RGB_G: color = rgb_g; break;
+ case RGB_B: color = rgb_b; break;
+ case HSV_H: color = hsv_h; break;
+ case HSV_S: color = hsv_s; break;
+ case HSV_V: color = hsv_v; break;
+ default: color = unknown; break;
+ }
+
+ int w = sliderImageSize().width();
+ QImage img(w, 1, QImage::Format_ARGB32);
+ for (int i = 0; i < w; i++) {
+ int t = i * (max + 1) / w;
+ reinterpret_cast<QRgb *>(img.scanLine(0))[i] = color(t).rgb();
+ }
+
+ return img;
+}
+
diff --git a/src/coloredit/ColorSlider.h b/src/coloredit/ColorSlider.h
new file mode 100644
index 0000000..d884737
--- /dev/null
+++ b/src/coloredit/ColorSlider.h
@@ -0,0 +1,29 @@
+#ifndef COLORSLIDER_H
+#define COLORSLIDER_H
+
+#include "RingSlider.h"
+
+class ColorSlider : public RingSlider {
+ Q_OBJECT
+public:
+ enum VisualType {
+ RGB_R,
+ RGB_G,
+ RGB_B,
+ HSV_H,
+ HSV_S,
+ HSV_V,
+ };
+private:
+ QColor color_;
+ VisualType color_type_ = HSV_H;
+protected:
+ QImage generateSliderImage() override;
+public:
+ explicit ColorSlider(QWidget *parent = nullptr);
+ VisualType visualType() const;
+ void setVisualType(VisualType type);
+ void setColor(const QColor &color);
+};
+
+#endif // COLORSLIDER_H
diff --git a/src/coloredit/ColorSquareWidget.cpp b/src/coloredit/ColorSquareWidget.cpp
new file mode 100644
index 0000000..2e571b9
--- /dev/null
+++ b/src/coloredit/ColorSquareWidget.cpp
@@ -0,0 +1,159 @@
+#include "ColorSquareWidget.h"
+#include "MainWindow.h"
+#include <QDebug>
+#include <QFile>
+#include <QPainter>
+#include <stdint.h>
+#include <QMouseEvent>
+//#include <omp.h>
+
+struct ColorSquareWidget::Private {
+ int hue = 0;
+ int sat = 255;
+ int val = 255;
+ QImage image;
+ QRect rect;
+};
+
+ColorSquareWidget::ColorSquareWidget(QWidget *parent)
+ : QWidget(parent)
+ , m(new Private)
+{
+ QString path = ":/cl/example.cl";
+ std::string source;
+ QFile file(path);
+ if (file.open(QFile::ReadOnly)) {
+ QByteArray ba = file.readAll();
+ if (!ba.isEmpty()) {
+ source = std::string(ba.begin(), ba.end());
+ }
+ }
+
+#if USE_OPENCL
+ prog.build(getCL(), source, "saturation_brightness");
+#endif
+}
+
+ColorSquareWidget::~ColorSquareWidget()
+{
+ delete m;
+}
+
+static void drawFrame(QPainter *pr, int x, int y, int w, int h)
+{
+ if (w < 3 || h < 3) {
+ pr->fillRect(x, y, w, h, Qt::black);
+ } else {
+ pr->fillRect(x, y, w - 1, 1, Qt::black);
+ pr->fillRect(x, y + 1, 1, h - 1, Qt::black);
+ pr->fillRect(x + w - 1, y, 1, h - 1, Qt::black);
+ pr->fillRect(x + 1, y + h - 1, w - 1, 1, Qt::black);
+ }
+}
+
+#if USE_OPENCL
+MiraCL *SaturationBrightnessWidget::getCL()
+{
+ return theApp->getCL();
+}
+#endif
+
+QImage ColorSquareWidget::createImage(int w, int h)
+{
+ QImage image(w, h, QImage::Format_RGB32);
+#if USE_OPENCL
+ QColor color = QColor::fromHsv(hue, 255, 255);
+ cl_uint r = color.red();
+ cl_uint g = color.green();
+ cl_uint b = color.blue();
+ MiraCL::Buffer buff(getCL());
+ buff.alloc(w * h * sizeof(uint32_t));
+ prog.arg(0, &w);
+ prog.arg(1, &h);
+ prog.arg(2, &r);
+ prog.arg(3, &g);
+ prog.arg(4, &b);
+ prog.arg(5, &buff);
+ prog.run(w, h);
+ buff.read(0, w * h * sizeof(uint32_t), image.bits());
+ getCL()->flush();
+ getCL()->finish();
+#else
+ for (int y = 0; y < h; y++) {
+ int bri = 255 - 255 * y / h;
+ QRgb *dst = (QRgb *)image.scanLine(y);
+ for (int x = 0; x < w; x++) {
+ int sat = 256 * x / w;
+ QColor color = QColor::fromHsv(m->hue, sat, bri);
+ dst[x] = qRgb(color.red(), color.green(), color.blue());
+ }
+ }
+#endif
+ return image;
+}
+
+MainWindow *ColorSquareWidget::mainwindow()
+{
+ return qobject_cast<MainWindow *>(window());
+}
+
+void ColorSquareWidget::updatePixmap(bool force)
+{
+ int w = width();
+ int h = height();
+ if (w > 1 && h > 1) {
+ if ((m->image.width() != w || m->image.height() != h) || force) {
+ m->image = createImage(w, h);
+ m->rect = QRect(0, 0, w, h);
+ }
+ } else {
+ m->image = QImage();
+ }
+}
+
+void ColorSquareWidget::paintEvent(QPaintEvent *)
+{
+ updatePixmap(false);
+ if (!m->image.isNull()) {
+ QPainter pr(this);
+ pr.drawImage(0, 0, m->image);
+ drawFrame(&pr, 0, 0, width(), height());
+ }
+}
+
+void ColorSquareWidget::press(QPoint const &pos)
+{
+ int x = pos.x() - m->rect.x();
+ int y = pos.y() - m->rect.y();
+ x = std::max(0, std::min(x, m->image.width() - 1));
+ y = std::max(0, std::min(y, m->image.height() - 1));
+ m->sat = x * 255 / m->image.width();
+ m->val = 255 - y * 255 / m->image.height();
+ emit changeColor(QColor::fromHsv(m->hue, m->sat, m->val));
+}
+
+void ColorSquareWidget::mousePressEvent(QMouseEvent *event)
+{
+ press(event->pos());
+}
+
+void ColorSquareWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ press(event->pos());
+}
+
+void ColorSquareWidget::setHue(int h)
+{
+ if (h < 0) {
+ h = 360 - (-h % 360);
+ } else {
+ h %= 360;
+ }
+ m->hue = h;
+ updatePixmap(true);
+ emit changeColor(QColor::fromHsv(m->hue, m->sat, m->val));
+ update();
+}
+
+
+
diff --git a/src/coloredit/ColorSquareWidget.h b/src/coloredit/ColorSquareWidget.h
new file mode 100644
index 0000000..14ebedd
--- /dev/null
+++ b/src/coloredit/ColorSquareWidget.h
@@ -0,0 +1,35 @@
+#ifndef COLORSQUAREWIDGET_H
+#define COLORSQUAREWIDGET_H
+
+#include <QWidget>
+#include <QPixmap>
+
+class MainWindow;
+
+class ColorSquareWidget : public QWidget {
+ Q_OBJECT
+private:
+#if USE_OPENCL
+ MiraCL *getCL();
+ MiraCL::Program prog;
+#endif
+ struct Private;
+ Private *m;
+ MainWindow *mainwindow();
+ void updatePixmap(bool force);
+ QImage createImage(int w, int h);
+ void press(const QPoint &pos);
+protected:
+ void paintEvent(QPaintEvent *);
+ void mousePressEvent(QMouseEvent *event);
+ void mouseMoveEvent(QMouseEvent *event);
+public:
+ explicit ColorSquareWidget(QWidget *parent = 0);
+ ~ColorSquareWidget();
+ void setHue(int h);
+signals:
+ void changeColor(const QColor &color);
+
+};
+
+#endif // COLORSQUAREWIDGET_H
diff --git a/src/coloredit/RingSlider.cpp b/src/coloredit/RingSlider.cpp
new file mode 100644
index 0000000..035462e
--- /dev/null
+++ b/src/coloredit/RingSlider.cpp
@@ -0,0 +1,190 @@
+#include "RingSlider.h"
+//#include "misc.h"
+#include <QKeyEvent>
+#include <QPainter>
+#include <QPainterPath>
+#include <math.h>
+
+void RingSlider::updateGeometry()
+{
+ handle_size_ = height();
+
+ int x = handle_size_ / 2;
+ int w = width() - handle_size_;
+ slider_rect_ = QRect(x, 3, w, height() - 6);
+
+ int val = value();
+ int max = maximum();
+ int handle_x = val * slider_rect_.width() / (max + 1) + slider_rect_.x() - handle_size_ / 2;
+ handle_rect_ = QRect(handle_x, 0, handle_size_, handle_size_);
+
+ slider_image_cache_ = QImage();
+}
+
+QSize RingSlider::sliderImageSize() const
+{
+ return slider_rect_.size();
+}
+
+void RingSlider::offset(int delta)
+{
+ setValue(value() + delta);
+}
+
+void RingSlider::resizeEvent(QResizeEvent *e)
+{
+ QWidget::resizeEvent(e);
+ updateGeometry();
+}
+
+void RingSlider::keyPressEvent(QKeyEvent *e)
+{
+ int k = e->key();
+
+ switch (k) {
+ case Qt::Key_Home:
+ setValue(minimum());
+ return;
+ case Qt::Key_End:
+ setValue(maximum());
+ return;
+ case Qt::Key_Left:
+ offset(-singleStep());
+ return;
+ case Qt::Key_Right:
+ offset(singleStep());
+ return;
+ case Qt::Key_PageDown:
+ offset(-pageStep());
+ return;
+ case Qt::Key_PageUp:
+ offset(pageStep());
+ return;
+ }
+}
+
+void RingSlider::paintEvent(QPaintEvent *)
+{
+ updateGeometry();
+
+ slider_image_cache_ = generateSliderImage();
+
+ QPainter pr(this);
+ pr.setRenderHint(QPainter::Antialiasing);
+
+ { // left rounded cap
+ int h = slider_rect_.height();
+ QRect r(slider_rect_.x() - h / 2 - 1, slider_rect_.y(), h, h);
+ QRectF fr(r);
+
+ QPainterPath path;
+ path.addEllipse(fr);
+ pr.save();
+ pr.setClipPath(path);
+ pr.fillRect(fr, Qt::black);
+ fr.adjust(1, 1, -1, -1);
+
+ QPainterPath path2;
+ path2.addEllipse(fr);
+ pr.setClipPath(path2);
+ pr.drawImage(fr, slider_image_cache_, QRect(0, 0, 1, slider_image_cache_.height()));
+ pr.restore();
+ }
+ { // right rounded cap
+ int h = slider_rect_.height();
+ QRect r(slider_rect_.x() + slider_rect_.width() - h / 2, slider_rect_.y(), h, h);
+ QRectF fr(r);
+
+ QPainterPath path;
+ path.addEllipse(fr);
+ pr.save();
+ pr.setClipPath(path);
+ pr.fillRect(fr, Qt::black);
+ fr.adjust(1, 1, -1, -1);
+
+ QPainterPath path2;
+ path2.addEllipse(fr);
+ pr.setClipPath(path2);
+ pr.drawImage(fr, slider_image_cache_, QRect(slider_image_cache_.width() - 1, 0, 1, slider_image_cache_.height()));
+ pr.restore();
+ }
+
+ { // top and bottom border
+ int x = slider_rect_.x();
+ int y = slider_rect_.y();
+ int w = slider_rect_.width();
+ int h = slider_rect_.height();
+ pr.fillRect(x, y, w, 1, Qt::black);
+ pr.fillRect(x, y + h - 1, w, 1, Qt::black);
+ }
+
+ pr.drawImage(slider_rect_.adjusted(0, 1, 0, -1), slider_image_cache_, slider_image_cache_.rect());
+
+ // slider handle
+ {
+ QPainterPath path;
+ path.addRect(rect());
+ QPainterPath path2;
+ path2.addEllipse(handle_rect_.adjusted(4, 4, -4, -4));
+ path = path.subtracted(path2);
+ pr.setClipPath(path);
+
+ pr.setPen(Qt::NoPen);
+ pr.setBrush(Qt::black);
+ pr.drawEllipse(handle_rect_);
+ pr.setBrush(Qt::white);
+ pr.drawEllipse(handle_rect_.adjusted(1, 1, -1, -1));
+ pr.setPen(Qt::NoPen);
+ pr.setBrush(Qt::black);
+ pr.drawEllipse(handle_rect_.adjusted(3, 3, -3, -3));
+ }
+}
+
+void RingSlider::mousePressEvent(QMouseEvent *e)
+{
+ int x = e->pos().x();
+ if (x < handle_rect_.x()) {
+ offset(-pageStep());
+ return;
+ }
+ if (x >= handle_rect_.x() + handle_rect_.width()) {
+ offset(pageStep());
+ return;
+ }
+
+ mouse_press_value_ = value();
+ mouse_press_pos_ = e->pos();
+}
+
+void RingSlider::mouseMoveEvent(QMouseEvent *e)
+{
+ if (e->buttons() & Qt::LeftButton) {
+ double slider_w = slider_rect_.width();
+ double range = maximum() - minimum();
+ double x = (mouse_press_value_ - minimum()) * slider_w / range + e->pos().x() - mouse_press_pos_.x();
+ int v = int(x * range / slider_w + minimum());
+ setValue(v);
+ return;
+ }
+}
+
+void RingSlider::mouseDoubleClickEvent(QMouseEvent *e)
+{
+ int w = slider_rect_.width();
+ if (w > 1) {
+ double x = (e->pos().x() - slider_rect_.x()) * (maximum() - minimum()) / (w - 1);
+ int v = floor(x + 0.5);
+ v = std::clamp(v, minimum(), maximum());
+ setValue(v);
+ }
+}
+
+void RingSlider::wheelEvent(QWheelEvent *e)
+{
+ int delta = e->delta();
+ bool sign = (delta < 0);
+ if (sign) delta = -delta;
+ delta = (delta + 119) / 120;
+ if (sign) delta = -delta;
+ offset(delta);
+}
diff --git a/src/coloredit/RingSlider.h b/src/coloredit/RingSlider.h
new file mode 100644
index 0000000..aaa3408
--- /dev/null
+++ b/src/coloredit/RingSlider.h
@@ -0,0 +1,32 @@
+#ifndef RINGSLIDER_H
+#define RINGSLIDER_H
+
+#include <QSlider>
+
+class RingSlider : public QSlider {
+protected:
+ int handle_size_ = 16;
+ QRect slider_rect_;
+ QRect handle_rect_;
+ int mouse_press_value_;
+ QPoint mouse_press_pos_;
+ QImage slider_image_cache_;
+ void updateGeometry();
+ QSize sliderImageSize() const;
+ void offset(int delta);
+ void resizeEvent(QResizeEvent *e) override;
+ void keyPressEvent(QKeyEvent *e) override;
+ void paintEvent(QPaintEvent *) override;
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseMoveEvent(QMouseEvent *e) override;
+ void mouseDoubleClickEvent(QMouseEvent *e) override;
+ void wheelEvent(QWheelEvent *e) override;
+ virtual QImage generateSliderImage() = 0;
+public:
+ explicit RingSlider(QWidget *parent = nullptr)
+ : QSlider(parent)
+ {}
+};
+
+
+#endif // RINGSLIDER_H
diff --git a/src/darktheme/DarkStyle.cpp b/src/darktheme/DarkStyle.cpp
index 8fefd88..72a4c00 100644
--- a/src/darktheme/DarkStyle.cpp
+++ b/src/darktheme/DarkStyle.cpp
@@ -1,2571 +1,2574 @@
#include "DarkStyle.h"
#include "NinePatch.h"
#include "TraditionalWindowsStyleTreeControl.h"
#include <QApplication>
#include <QComboBox>
#include <QDebug>
#include <QDialogButtonBox>
#include <QDockWidget>
#include <QInputDialog>
#include <QListView>
#include <QMessageBox>
+#include <QPainterPath>
#include <QPixmapCache>
#include <QStyleOptionComplex>
#include <QStyleOptionFrameV3>
#include <QTableWidget>
#include <QTextLayout>
#include <QToolTip>
#include <cmath>
#include <cstdint>
+#include <QPainterPath>
+
#define MBI_NORMAL 1
#define MBI_HOT 2
#define MBI_PUSHED 3
#define MBI_DISABLED 4
namespace {
const int windowsItemFrame = 2; // menu item frame width
const int windowsItemHMargin = 3; // menu item hor text margin
const int windowsItemVMargin = 4; // menu item ver text margin
const int windowsArrowHMargin = 6; // arrow horizontal margin
const int windowsRightBorder = 15; // right border on windows
void drawFrame(QPainter *pr, int x, int y, int w, int h, QColor color_topleft, QColor color_bottomright)
{
if (w < 3 || h < 3) {
if (w > 0 && h > 0) {
pr->fillRect(x, y, w, h, color_topleft);
}
} else {
if (!color_topleft.isValid()) color_topleft = Qt::black;
if (!color_bottomright.isValid()) color_bottomright = color_topleft;
pr->fillRect(x, y, w - 1, 1, color_topleft);
pr->fillRect(x, y + 1, 1, h -1, color_topleft);
pr->fillRect(x + w - 1, y, 1, h -1, color_bottomright);
pr->fillRect(x + 1, y + h - 1, w - 1, 1, color_bottomright);
}
}
void drawFrame(QPainter *pr, QRect const &r, QColor const &color_topleft, QColor const &color_bottomright)
{
return drawFrame(pr, r.x(), r.y(), r.width(), r.height(), color_topleft, color_bottomright);
}
struct TextureCacheItem {
QString key;
QPixmap pm;
};
QString pixmapkey(QString const &name, QString const &role, QSize const &size, QColor const &color)
{
QString key = "%1:%2:%3:%4:%5";
key = key.arg(name).arg(role).arg(size.width()).arg(size.height()).arg(QString().sprintf("%02X%02X%02X", color.red(), color.green(), color.blue()));
return key;
}
void drawRaisedFrame(QPainter *p, const QRect &rect, const QPalette &palette)
{
p->save();
int x = rect.x();
int y = rect.y();
int w = rect.width();
int h = rect.height();
p->setClipRect(x, y, w, h);
p->fillRect(x, y, w, h, palette.color(QPalette::Window));
p->fillRect(x, y, w - 1, 1, palette.color(QPalette::Light));
p->fillRect(x, y, 1, h - 1, palette.color(QPalette::Light));
// p->fillRect(x + 1, y + h - 2, w - 2, 1, palette.color(QPalette::Dark));
// p->fillRect(x + w - 2, y + 1, 1, h - 2, palette.color(QPalette::Dark));
p->fillRect(x, y + h - 1, w, 1, palette.color(QPalette::Shadow));
p->fillRect(x + w - 1, y, 1, h, palette.color(QPalette::Shadow));
p->restore();
}
QImage loadImage(QString const &path, QString const &role = QString())
{
QImage image(path);
image.setText("name", path);
image.setText("role", role);
return image;
}
QRgb colorize(QRgb color, int light, int alpha)
{
int r, g, b;
r = g = b = 0;
if (alpha != 0) {
r = qRed(color);
g = qGreen(color);
b = qBlue(color);
if (light < 128) {
r = r * light / 127;
g = g * light / 127;
b = b * light / 127;
} else {
int u = 255 - light;
r = 255 - (255 - r) * u / 127;
g = 255 - (255 - g) * u / 127;
b = 255 - (255 - b) * u / 127;
}
}
return qRgba(r, g, b, alpha);
}
} // namespace
// DarkStyle
static const int TEXTURE_CACHE_SIZE = 100;
struct DarkStyle::Private {
QColor base_color;
bool images_loaded = false;
ScrollBarTextures hsb;
ScrollBarTextures vsb;
int scroll_bar_extent = -1;
QImage button_normal;
QImage button_press;
QImage progress_horz;
QImage progress_vert;
TraditionalWindowsStyleTreeControl legacy_windows;
};
DarkStyle::DarkStyle(QColor const &base_color)
: m(new Private)
{
setBaseColor(base_color);
}
DarkStyle::~DarkStyle()
{
delete m;
}
QColor DarkStyle::getBaseColor()
{
return m->base_color;
}
void DarkStyle::setBaseColor(QColor const &color)
{
m->base_color = color;
m->images_loaded = false;
}
QColor DarkStyle::color(int level, int alpha) const
{
QColor c = m->base_color.lighter(level * 100 / 255);
c.setAlpha(alpha);
return c;
}
void DarkStyle::setScrollBarExtent(int n)
{
m->scroll_bar_extent = n;
}
QImage DarkStyle::colorizeImage(QImage image)
{
int w = image.width();
int h = image.height();
if (w > 0 && h > 0) {
QColor c = color(128);
QRgb rgb = c.rgb();
Q_ASSERT(image.format() == QImage::Format_ARGB32);
for (int y = 0; y < h; y++) {
QRgb *p = reinterpret_cast<QRgb *>(image.scanLine(y));
for (int x = 0; x < w; x++) {
p[x] = colorize(rgb, qGray(p[x]), qAlpha(p[x]));
}
}
}
return image;
}
QImage DarkStyle::loadColorizedImage(QString const &path, QString const &role)
{
QImage image = loadImage(path);
image = colorizeImage(image);
image.setText("name", path);
image.setText("role", role);
return image;
}
namespace {
class Lighten {
private:
int lut[256];
public:
Lighten()
{
#if 0
const double x = 0.75;
for (int i = 0; i < 256; i++) {
lut[i] = (int)(pow(i / 255.0, x) * 255);
}
#else
for (int i = 0; i < 256; i++) {
lut[i] = 255 - (255 - i) * 192 / 256;
}
#endif
}
int operator [] (int i)
{
return lut[i];
}
};
}
DarkStyle::ButtonImages DarkStyle::generateButtonImages(QString const &path)
{
QImage source = loadImage(path);
ButtonImages buttons;
int w = source.width();
int h = source.height();
if (w > 4 && h > 4) {
QColor c = color(128);
QRgb rgb = c.rgb();
Lighten lighten;
buttons.im_normal = source;
buttons.im_hover = source;
w -= 4;
h -= 4;
for (int y = 0; y < h; y++) {
QRgb *src1 = reinterpret_cast<QRgb *>(source.scanLine(y + 1));
QRgb *src2 = reinterpret_cast<QRgb *>(source.scanLine(y + 2));
QRgb *src3 = reinterpret_cast<QRgb *>(source.scanLine(y + 3));
QRgb *dst0 = reinterpret_cast<QRgb *>(buttons.im_normal.scanLine(y + 2)) + 2;
QRgb *dst1 = reinterpret_cast<QRgb *>(buttons.im_hover.scanLine(y + 2)) + 2;
for (int x = 0; x < w; x++) {
int v = (int)qAlpha(src3[x + 3]) - (int)qAlpha(src1[x + 1]);
v = (v + 256) / 2;
int alpha = qAlpha(src2[x + 2]);
dst0[x] = colorize(rgb, v, alpha);
v = lighten[v];
dst1[x] = colorize(rgb, v, alpha);
}
}
buttons.im_normal.setText("name", source.text("name"));
buttons.im_hover.setText("name", source.text("name"));
buttons.im_normal.setText("role", "normal");
buttons.im_hover.setText("role", "hover");
}
return buttons;
}
QImage DarkStyle::generateHoverImage(QImage const &source)
{
QImage newimage;
int w = source.width();
int h = source.height();
if (w > 2 && h > 2) {
QColor c = color(128);
QRgb rgb = c.rgb();
Lighten lighten;
newimage = source;
Q_ASSERT(newimage.format() == QImage::Format_ARGB32);
w -= 2;
h -= 2;
for (int y = 0; y < h; y++) {
QRgb *ptr = reinterpret_cast<QRgb *>(newimage.scanLine(y + 1)) + 1;
for (int x = 0; x < w; x++) {
int v = qGray(ptr[x]);
v = lighten[v];
ptr[x] = colorize(rgb, v, qAlpha(ptr[x]));
}
}
newimage.setText("role", "hover");
}
return newimage;
}
void DarkStyle::loadImages()
{
if (m->images_loaded) return;
if (!m->base_color.isValid()) {
setBaseColor(Qt::white);
}
m->button_normal = loadColorizedImage(QLatin1String(":/darktheme/button/button_normal.png"), QLatin1String("normal"));
m->button_press = loadColorizedImage(QLatin1String(":/darktheme/button/button_press.png"), QLatin1String("press"));
m->hsb.sub_line = generateButtonImages(QLatin1String(":/darktheme/hsb/hsb_sub_line.png"));
m->hsb.add_line = generateButtonImages(QLatin1String(":/darktheme/hsb/hsb_add_line.png"));
m->hsb.page_bg = loadColorizedImage(QLatin1String(":/darktheme/hsb/hsb_page_bg.png"));
m->hsb.slider.im_normal = loadColorizedImage(QLatin1String(":/darktheme/hsb/hsb_slider.png"));
m->hsb.slider.im_hover = generateHoverImage(m->hsb.slider.im_normal);
m->vsb.sub_line = generateButtonImages(QLatin1String(":/darktheme/vsb/vsb_sub_line.png"));
m->vsb.add_line = generateButtonImages(QLatin1String(":/darktheme/vsb/vsb_add_line.png"));
m->vsb.page_bg = loadColorizedImage(QLatin1String(":/darktheme/vsb/vsb_page_bg.png"));
m->vsb.slider.im_normal = loadColorizedImage(QLatin1String(":/darktheme/vsb/vsb_slider.png"));
m->vsb.slider.im_hover = generateHoverImage(m->vsb.slider.im_normal);
m->progress_horz = loadImage(QLatin1String(":/darktheme/progress/horz.png"));
m->progress_vert = loadImage(QLatin1String(":/darktheme/progress/vert.png"));
m->images_loaded = true;
}
QPixmap DarkStyle::pixmapFromImage(const QImage &image, QSize size) const
{
QString key = pixmapkey(image.text("name"), image.text("role"), size, m->base_color);
QPixmap *pm = QPixmapCache::find(key);
if (pm) return *pm;
TextureCacheItem t;
t.key = key;
t.pm = QPixmap::fromImage(image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
QPixmapCache::insert(key, t.pm);
return t.pm;
}
QColor DarkStyle::selectionColor() const
{
// return QColor(128, 192, 255);
return QColor(80, 160, 255);
}
QColor DarkStyle::colorForItemView(QStyleOption const *opt) const
{
#ifdef Q_OS_WIN
return opt->palette.color(QPalette::Base);
#else
return opt->palette.color(QPalette::Dark);
#endif
}
void DarkStyle::drawNinePatchImage(QPainter *p, const QImage &image, const QRect &r, int w, int h) const
{
QImage im = createImageFromNinePatchImage(image, w, h);
im.setText("name", image.text("name"));
im.setText("role", image.text("role"));
QPixmap pm = pixmapFromImage(im, r.size());
p->drawPixmap(r.x(), r.y(), pm);
}
void DarkStyle::polish(QPalette &palette)
{
if (!m->base_color.isValid()) {
setBaseColor(Qt::white);
}
loadImages();
palette = QPalette(color(64));
palette.setColor(QPalette::Disabled, QPalette::Text, color(160));
palette.setColor(QPalette::Normal, QPalette::Highlight, selectionColor().lighter(75));
palette.setColor(QPalette::Disabled, QPalette::ButtonText, color(128));
#ifndef Q_OS_WIN
palette.setColor(QPalette::ToolTipText, Qt::black); // ツールチップの文字色
#endif
}
void DarkStyle::drawGutter(QPainter *p, const QRect &r) const
{
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
QColor dark = color(32);
QColor lite = color(128);
if (w < h) {
x += (w - 1) / 2;
p->fillRect(x, y, 1, h, dark);
p->fillRect(x + 1, y, 1, h, lite);
} else if (w > h) {
y += (h - 1) / 2;
p->fillRect(x, y, w, 1, dark);
p->fillRect(x, y + 1, w, 1, lite);
}
}
void DarkStyle::drawSelectedItemFrame(QPainter *p, QRect rect, bool focus) const
{
QColor color = focus ? selectionColor() : QColor(128, 128, 128);
int x, y, w, h;
x = rect.x();
y = rect.y();
w = rect.width();
h = rect.height();
QString key = QString::asprintf("selection_frame:%02x%02x%02x:%dx%d", color.red(), color.green(), color.blue(), w, h);
QPixmap pixmap;
if (!QPixmapCache::find(key, &pixmap)) {
pixmap = QPixmap(w, h);
pixmap.fill(Qt::transparent);
QPainter pr(&pixmap);
pr.setRenderHint(QPainter::Antialiasing);
QColor pencolor = color;
pencolor.setAlpha(128);
pr.setPen(pencolor);
pr.setBrush(Qt::NoBrush);
QPainterPath path;
path.addRoundedRect(1.5, 1.5, w - 1.5, h - 1.5, 3, 3);
pr.drawPath(path);
pr.setClipPath(path);
int a = color.alpha();
QColor color0 = color;
QColor color1 = color;
color0.setAlpha(128 * a / 255);
color1.setAlpha(64 * a / 255);
QLinearGradient gr(QPointF(0, 0), QPointF(0, h));
gr.setColorAt(0, color0);
gr.setColorAt(1, color1);
QBrush br(gr);
pr.fillRect(0, 0, w, h, br);
pr.end();
QPixmapCache::insert(key, pixmap);
}
p->drawPixmap(x, y, w, h, pixmap);
}
void DarkStyle::drawFocusFrame(QPainter *p, QRect const &rect, int margin) const
{
p->save();
p->setRenderHint(QPainter::Antialiasing);
QColor color = selectionColor();
p->setPen(color);
p->setBrush(Qt::NoBrush);
double m = margin + 0.5;
p->drawRoundedRect(((QRectF)rect).adjusted(m, m, -m, -m), 3, 3);
p->restore();
}
void DarkStyle::drawButton(QPainter *p, const QStyleOption *option, bool mac_margin) const
{
QRect rect = option->rect;
int w = rect.width();
int h = rect.height();
#ifdef Q_OS_MAC
if (mac_margin) {
int margin = pixelMetric(PM_ButtonMargin, option, nullptr);
if (margin > 0) {
int n = std::min(w, h);
if (n > margin * 2) {
n = (n - margin * 2) / 2;
if (n > margin) n = margin;
rect = rect.adjusted(n, n, -n, -n);
w = rect.width();
h = rect.height();
}
}
}
#else
(void)mac_margin;
#endif
bool pressed = (option->state & (State_Sunken | State_On));
bool hover = (option->state & State_MouseOver);
if (pressed) {
drawNinePatchImage(p, m->button_press, rect, w, h);
} else {
drawNinePatchImage(p, m->button_normal, rect, w, h);
}
{
QPainterPath path;
path.addRoundedRect(rect, 6, 6); // 角丸四角形のパス
p->save();
p->setRenderHint(QPainter::Antialiasing);
p->setClipPath(path);
int x = rect.x();
int y = rect.y();
int w = rect.width();
int h = rect.height();
QColor color0, color1;
#ifdef Q_OS_MAC
if (pressed) {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(48);
color1.setAlpha(144);
} else {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(32);
color1.setAlpha(128);
}
#else
if (pressed) {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(32);
color1.setAlpha(128);
} else if (hover) {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(16);
color1.setAlpha(96);
} else {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(32);
color1.setAlpha(128);
}
#endif
QLinearGradient gr(QPointF(x, y), QPointF(x, y + h));
gr.setColorAt(0, color0);
gr.setColorAt(1, color1);
QBrush br(gr);
p->fillRect(x, y, w, h, br);
if (option->state & State_HasFocus) {
#if 1
drawFocusFrame(p, rect, 3);
#else
p->fillRect(x, y, w, h, QColor(80, 160, 255, 32));
#endif
}
p->restore();
}
}
void DarkStyle::drawToolButton(QPainter *p, const QStyleOption *option) const
{
p->save();
bool hover = (option->state & State_MouseOver);
bool pressed = (option->state & State_Sunken);
#ifdef Q_OS_MAC
hover = false;
#endif
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
QColor color0, color1;
#ifdef Q_OS_MAC
if (pressed) {
color0 = color(72);
color1 = color(40);
} else {
color0 = color(80);
color1 = color(48);
}
#else
if (pressed) {
color0 = color(80);
color1 = color(48);
} else if (hover) {
color0 = color(96);
color1 = color(64);
} else {
color0 = color(80);
color1 = color(48);
}
#endif
QLinearGradient gr(QPointF(x, y), QPointF(x, y + h));
gr.setColorAt(0, color0);
gr.setColorAt(1, color1);
QBrush br(gr);
p->fillRect(x, y, w, h, br);
if (option->state & State_Raised) {
drawFrame(p, option->rect, color(96), color(32));
} else if (option->state & State_Sunken) {
drawFrame(p, option->rect, color(48), color(48));
}
p->restore();
}
void DarkStyle::drawMenuBarBG(QPainter *p, const QStyleOption *option, QWidget const *widget) const
{
int x = option->rect.x();
int y = widget->y();
int w = option->rect.width();
int h = widget->height();
QLinearGradient gradient;
gradient.setStart(x, y);
gradient.setFinalStop(x, y + h / 2.0);
gradient.setColorAt(0, option->palette.color(QPalette::Light));
gradient.setColorAt(1, option->palette.color(QPalette::Window));
p->fillRect(x, y, w, h, gradient);
p->fillRect(x, y + h - 1, w, 1, option->palette.color(QPalette::Dark));
}
int DarkStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
{
if (metric == PM_ScrollBarExtent) {
if (m->scroll_bar_extent > 0) {
return m->scroll_bar_extent;
}
}
if (metric == PM_SliderLength) {
return std::min(widget->width(), widget->height());
}
return QProxyStyle::pixelMetric(metric, option, widget);
}
QRect DarkStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *option, SubControl sc, const QWidget *widget) const
{
if (cc == CC_Slider && sc == SC_SliderGroove) {
return widget->rect();
}
if (cc == CC_Slider && sc == SC_SliderHandle) {
if (auto const *o = qstyleoption_cast<QStyleOptionSlider const *>(option)) {
QRect rect;
int extent = std::min(widget->width(), widget->height());
int span = pixelMetric(PM_SliderLength, o, widget);
bool horizontal = o->orientation == Qt::Horizontal;
int sliderPos = sliderPositionFromValue(o->minimum, o->maximum,
o->sliderPosition,
(horizontal ? o->rect.width()
: o->rect.height()) - span,
o->upsideDown);
if (horizontal) {
rect.setRect(o->rect.x() + sliderPos, o->rect.y(), span, extent);
} else {
rect.setRect(o->rect.x(), o->rect.y() + sliderPos, extent, span);
}
return rect;
}
}
if (cc == CC_GroupBox) {
QRect ret;
if (auto const *o = qstyleoption_cast<QStyleOptionGroupBox const *>(option)) {
switch (sc) {
case SC_GroupBoxFrame:
case SC_GroupBoxContents:
{
int topMargin = 0;
int topHeight = 0;
int verticalAlignment = proxy()->styleHint(SH_GroupBox_TextLabelVerticalAlignment, o, widget);
#ifdef Q_OS_MACX
verticalAlignment |= Qt::AlignVCenter;
#endif
if (o->text.size() || (o->subControls & QStyle::SC_GroupBoxCheckBox)) {
topHeight = o->fontMetrics.height();
if (verticalAlignment & Qt::AlignVCenter) {
topMargin = topHeight / 2;
} else if (verticalAlignment & Qt::AlignTop) {
topMargin = topHeight;
}
}
QRect frameRect = o->rect;
frameRect.setTop(topMargin);
if (sc == SC_GroupBoxFrame) {
ret = frameRect;
break;
}
int frameWidth = 0;
if ((o->features & QStyleOptionFrame::Flat) == 0) {
frameWidth = proxy()->pixelMetric(PM_DefaultFrameWidth, o, widget);
}
ret = frameRect.adjusted(frameWidth, frameWidth + topHeight - topMargin, -frameWidth, -frameWidth);
}
break;
case SC_GroupBoxCheckBox:
case SC_GroupBoxLabel:
{
QFontMetrics fontMetrics = o->fontMetrics;
int h = fontMetrics.height();
int tw = fontMetrics.size(Qt::TextShowMnemonic, o->text + QLatin1Char(' ')).width();
int marg = (o->features & QStyleOptionFrame::Flat) ? 0 : 8;
ret = o->rect.adjusted(marg, 0, -marg, 0);
ret.setHeight(h);
int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, option, widget);
int indicatorSpace = proxy()->pixelMetric(PM_CheckBoxLabelSpacing, option, widget) - 1;
bool hasCheckBox = o->subControls & QStyle::SC_GroupBoxCheckBox;
int checkBoxSize = hasCheckBox ? (indicatorWidth + indicatorSpace) : 0;
// Adjusted rect for label + indicatorWidth + indicatorSpace
QRect totalRect = alignedRect(o->direction, o->textAlignment, QSize(tw + checkBoxSize, h), ret);
// Adjust totalRect if checkbox is set
if (hasCheckBox) {
bool ltr = o->direction == Qt::LeftToRight;
int left = 0;
// Adjust for check box
if (sc == SC_GroupBoxCheckBox) {
int indicatorHeight = proxy()->pixelMetric(PM_IndicatorHeight, option, widget);
left = ltr ? totalRect.left() : (totalRect.right() - indicatorWidth);
int top = totalRect.top() + qMax(0, fontMetrics.height() - indicatorHeight) / 2;
totalRect.setRect(left, top, indicatorWidth, indicatorHeight);
// Adjust for label
} else {
left = ltr ? (totalRect.left() + checkBoxSize - 2) : totalRect.left();
totalRect.setRect(left, totalRect.top(), totalRect.width() - checkBoxSize, totalRect.height());
}
}
ret = totalRect;
}
break;
}
return ret;
}
}
return QProxyStyle::subControlRect(cc, option, sc, widget);
}
int DarkStyle::styleHint(QStyle::StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const
{
if (stylehint == SH_Table_GridLineColor) {
return static_cast<int>(color(32).rgb());
}
return QProxyStyle::styleHint(stylehint, opt, widget, returnData);
}
#if 0
static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth)
{
qreal height = 0;
qreal widthUsed = 0;
textLayout.beginLayout();
while (true) {
QTextLine line = textLayout.createLine();
if (!line.isValid())
break;
line.setLineWidth(lineWidth);
line.setPosition(QPointF(0, height));
height += line.height();
widthUsed = qMax(widthUsed, line.naturalTextWidth());
}
textLayout.endLayout();
return QSizeF(widthUsed, height);
}
void DarkStyle::drawItemViewText(QPainter *p, const QStyleOptionViewItem *option, const QRect &rect) const
{
const QWidget *widget = option->widget;
const int textMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1;
QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding
const bool wrapText = option->features & QStyleOptionViewItem::WrapText;
QTextOption textOption;
textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap);
textOption.setTextDirection(option->direction);
textOption.setAlignment(QStyle::visualAlignment(option->direction, option->displayAlignment));
QTextLayout textLayout(option->text, option->font);
textLayout.setTextOption(textOption);
viewItemTextLayout(textLayout, textRect.width());
const QRectF boundingRect = textLayout.boundingRect();
const QRect layoutRect = QStyle::alignedRect(option->direction, option->displayAlignment, boundingRect.size().toSize(), textRect);
const QPointF position = layoutRect.topLeft();
const int lineCount = textLayout.lineCount();
qreal height = 0;
for (int i = 0; i < lineCount; ++i) {
const QTextLine line = textLayout.lineAt(i);
height += line.height();
// above visible rect
if (height + layoutRect.top() <= textRect.top()) continue;
const int start = line.textStart();
const int length = line.textLength();
const bool drawElided = line.naturalTextWidth() > textRect.width();
bool elideLastVisibleLine = false;
if (!drawElided && i + 1 < lineCount) {
const QTextLine nextLine = textLayout.lineAt(i + 1);
const int nextHeight = height + nextLine.height() / 2;
// elide when less than the next half line is visible
if (nextHeight + layoutRect.top() > textRect.height() + textRect.top()) {
elideLastVisibleLine = true;
}
}
if (drawElided || elideLastVisibleLine) {
QString text = textLayout.text().mid(start, length);
if (elideLastVisibleLine) {
text += QChar(0x2026);
}
// const QStackTextEngine engine(text, option->font);
const QString elidedText = text;//engine.elidedText(option->textElideMode, textRect.width());
const QPointF pos(position.x() + line.x(), position.y() + line.y() + line.ascent());
p->save();
p->setFont(option->font);
p->drawText(pos, elidedText);
p->restore();
} else {
line.draw(p, position);
}
// below visible text, can stop
if (height + layoutRect.top() >= textRect.bottom()) {
break;
}
}
}
#else
void DarkStyle::drawItemViewText(QPainter *p, const QStyleOptionViewItem *option, const QRect &rect, bool abbreviation) const
{
bool enabled = (option->state & State_Enabled);
p->save();
p->setFont(option->font);
QString text = option->text;
if (abbreviation) {
int n = text.size();
if (n > 1) {
QFontMetrics fm = p->fontMetrics();
int w = rect.width();
while (1) {
if (fm.size(0, text).width() <= w) break;
n--;
text = text.mid(0, n);
text += "...";
}
}
}
drawItemText(p, rect, option->displayAlignment, option->palette, enabled, text, QPalette::NoRole);
p->restore();
}
#endif
void DarkStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *p, const QWidget *widget) const
{
// qDebug() << pe;
#ifndef Q_OS_MAC
if (pe == PE_FrameFocusRect) {
// if (auto const *w = qobject_cast<QTableView const *>(widget)) {
// if (w->selectionBehavior() == QAbstractItemView::SelectRows) {
// if (auto const *v = w->viewport()) {
// QRect r(0, option->rect.y(), v->width(), option->rect.height());
// return;
// }
// }
// }
drawFocusFrame(p, option->rect, 0);
return;
}
#endif
if (pe == PE_IndicatorArrowDown) {
switch (pe) {
case PE_IndicatorArrowUp:
case PE_IndicatorArrowDown:
case PE_IndicatorArrowRight:
case PE_IndicatorArrowLeft:
{
if (option->rect.width() <= 1 || option->rect.height() <= 1) {
break;
}
QRect r = option->rect.adjusted(-10, -10, 0, 10);
int size = qMin(r.height(), r.width());
QPixmap pixmap;
if (1) {
int border = size / 5;
int sqsize = 2 * (size / 2);
QImage image(sqsize, sqsize, QImage::Format_ARGB32_Premultiplied);
image.fill(0);
QPainter imagePainter(&image);
QPolygon a;
switch (pe) {
case PE_IndicatorArrowUp:
a.setPoints(3, border, sqsize / 2, sqsize / 2, border, sqsize - border, sqsize / 2);
break;
case PE_IndicatorArrowDown:
a.setPoints(3, border, sqsize / 2, sqsize / 2, sqsize - border, sqsize - border, sqsize / 2);
break;
case PE_IndicatorArrowRight:
a.setPoints(3, sqsize - border, sqsize / 2, sqsize / 2, border, sqsize / 2, sqsize - border);
break;
case PE_IndicatorArrowLeft:
a.setPoints(3, border, sqsize / 2, sqsize / 2, border, sqsize / 2, sqsize - border);
break;
default:
break;
}
int bsx = 0;
int bsy = 0;
if (option->state & State_Sunken) {
bsx = pixelMetric(PM_ButtonShiftHorizontal, option, widget);
bsy = pixelMetric(PM_ButtonShiftVertical, option, widget);
}
QRect bounds = a.boundingRect();
int sx = sqsize / 2 - bounds.center().x() - 1;
int sy = sqsize / 2 - bounds.center().y() - 1;
imagePainter.translate(sx + bsx, sy + bsy);
imagePainter.setPen(option->palette.buttonText().color());
imagePainter.setBrush(option->palette.buttonText());
imagePainter.setRenderHint(QPainter::Qt4CompatiblePainting);
if (!(option->state & State_Enabled)) {
imagePainter.translate(1, 1);
imagePainter.setBrush(option->palette.light().color());
imagePainter.setPen(option->palette.light().color());
imagePainter.drawPolygon(a);
imagePainter.translate(-1, -1);
imagePainter.setBrush(option->palette.mid().color());
imagePainter.setPen(option->palette.mid().color());
}
imagePainter.drawPolygon(a);
imagePainter.end();
pixmap = QPixmap::fromImage(image);
}
int xOffset = r.x() + (r.width() - size)/2;
int yOffset = r.y() + (r.height() - size)/2;
p->drawPixmap(xOffset, yOffset, pixmap);
}
}
return;
}
if (pe == PE_PanelMenu) {
#ifdef Q_OS_MACX
if (qobject_cast<const QComboBox *>(widget->parent())) {
p->fillRect(option->rect, option->palette.color(QPalette::Light));
return;
}
#endif
QRect r = option->rect;
drawFrame(p, r, Qt::black, Qt::black);
r = r.adjusted(1, 1, -1, -1);
drawFrame(p, r, color(128), color(64));
r = r.adjusted(1, 1, -1, -1);
p->fillRect(r, color(80));
return;
}
if (pe == PE_FrameMenu) {
return;
}
if (pe == PE_PanelButtonBevel) {
drawButton(p, option);
return;
}
if (pe == PE_PanelStatusBar) {
p->fillRect(option->rect, option->palette.color(QPalette::Window));
return;
}
if (pe == PE_FrameTabWidget) {
if (auto const *o = qstyleoption_cast<QStyleOptionTabWidgetFrame const *>(option)) {
int x = o->rect.x();
int y = o->rect.y();
int w = o->rect.width();
int h = o->rect.height();
#ifdef Q_OS_WIN
switch (o->shape) {
case QTabBar::RoundedNorth:
break;
case QTabBar::RoundedSouth:
h -= 1;
break;
case QTabBar::RoundedWest:
break;
case QTabBar::RoundedEast:
w -= 2;
break;
}
#endif
drawRaisedFrame(p, QRect(x, y, w, h), o->palette);
return;
}
}
if (pe == PE_PanelLineEdit) {
if (auto const *panel = qstyleoption_cast<QStyleOptionFrame const *>(option)) {
p->fillRect(option->rect, colorForItemView(option));
if (panel->lineWidth > 0) {
drawFrame(p, option->rect, option->palette.color(QPalette::Shadow), option->palette.color(QPalette::Light));
}
}
return;
}
if (pe == PE_FrameGroupBox) {
p->save();
p->setRenderHint(QPainter::Antialiasing);
QRectF r = option->rect;
r = r.adjusted(1.5, 1.5, -0.5, -0.5);
p->setPen(option->palette.color(QPalette::Light));
p->drawRoundedRect(r, 5, 5);
r = r.adjusted(-1, -1, -1, -1);
p->setPen(option->palette.color(QPalette::Dark));
p->drawRoundedRect(r, 5, 5);
p->restore();
return;
}
if (pe == PE_PanelItemViewRow) {
// p->drawEllipse(option->rect);
// return;
#ifdef Q_OS_WIN
// thru
#else
return;
#endif
}
if (pe == PE_PanelItemViewItem) {
// p->fillRect(option->rect, colorForItemView(option)); // 選択枠を透過描画させるので背景は描かない
auto DrawSelectionFrame = [&](QRect const &r){
bool focus = widget && widget->hasFocus();
drawSelectedItemFrame(p, r, focus);
};
if (auto const *tableview = qobject_cast<QTableView const *>(widget)) {
#if 0
Qt::PenStyle grid_pen_style = Qt::NoPen;
if (tableview->showGrid()) {
grid_pen_style = tableview->gridStyle();
}
if (grid_pen_style != Qt::NoPen) {
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
p->fillRect(x + w - 1, y, 1, h, option->palette.color(QPalette::Dark));
p->fillRect(x, y + h - 1, w, 1, option->palette.color(QPalette::Dark));
}
#endif
QAbstractItemView::SelectionBehavior selection_behavior = tableview->selectionBehavior();
if (option->state & State_Selected) {
p->save();
p->setClipRect(option->rect);
QRect r = widget->rect();
if (selection_behavior == QAbstractItemView::SelectionBehavior::SelectRows) {
r = QRect(r.x(), option->rect.y(), r.width(), option->rect.height());
} else if (selection_behavior == QAbstractItemView::SelectionBehavior::SelectRows) {
r = QRect(option->rect.x(), r.y(), option->rect.y(), r.height());
}
DrawSelectionFrame(r);
p->restore();
}
} else {
if (option->state & State_Selected) {
DrawSelectionFrame(option->rect);
}
}
return;
}
if (pe == QStyle::PE_IndicatorBranch) {
QColor bg = option->palette.color(QPalette::Base);
p->fillRect(option->rect, bg);
if (m->legacy_windows.drawPrimitive(pe, option, p, widget)) {
return;
}
}
if (pe == QStyle::PE_Widget) { // bg for messagebox
const QDialogButtonBox *buttonBox = nullptr;
if (qobject_cast<const QMessageBox *> (widget))
buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox"));
#ifndef QT_NO_INPUTDIALOG
else if (qobject_cast<const QInputDialog *> (widget))
buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox"));
#endif // QT_NO_INPUTDIALOG
if (buttonBox) {
int y = buttonBox->geometry().top();
QRect r(option->rect.x(), y, option->rect.width(), 1);
p->fillRect(r, option->palette.color(QPalette::Light));
r.translate(0, -1);
p->fillRect(r, option->palette.color(QPalette::Dark));
}
return;
}
#ifndef Q_OS_WIN
if (pe == QStyle::PE_PanelTipLabel) {
// ツールチップの背景パネル
p->fillRect(option->rect, QColor(255, 255, 192));
drawFrame(p, option->rect, Qt::black, Qt::black);
return;
}
#endif
// qDebug() << pe;
QProxyStyle::drawPrimitive(pe, option, p, widget);
}
void DarkStyle::drawControl(ControlElement ce, const QStyleOption *option, QPainter *p, const QWidget *widget) const
{
// qDebug() << ce;
bool disabled = !(option->state & State_Enabled);
#ifdef Q_OS_MAC
if (ce == CE_ToolBar) {
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
p->fillRect(x, y + h - 1, w, 1, color(32));
return;
}
#endif
#ifdef Q_OS_LINUX
if (ce == CE_ToolBar) {
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
QColor color = option->palette.color(QPalette::Window);
p->fillRect(x, y + h - 1, w, 1, color);
return;
}
if (ce == CE_PushButtonLabel) {
if (auto const *o = qstyleoption_cast<QStyleOptionButton const *>(option)) {
QRect ir = o->rect;
uint tf = Qt::AlignVCenter | Qt::TextShowMnemonic;
QPoint buttonShift;
if (option->state & State_Sunken) {
buttonShift = QPoint(pixelMetric(PM_ButtonShiftHorizontal, option, widget), proxy()->pixelMetric(PM_ButtonShiftVertical, option, widget));
}
if (proxy()->styleHint(SH_UnderlineShortcut, o, widget)) {
tf |= Qt::TextShowMnemonic;
} else {
tf |= Qt::TextHideMnemonic;
}
if (!o->icon.isNull()) {
//Center both icon and text
QPoint point;
QIcon::Mode mode = o->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
if (mode == QIcon::Normal && o->state & State_HasFocus) {
mode = QIcon::Active;
}
QIcon::State state = QIcon::Off;
if (o->state & State_On) {
state = QIcon::On;
}
QPixmap pixmap = o->icon.pixmap(o->iconSize, mode, state);
int w = pixmap.width();
int h = pixmap.height();
if (!o->text.isEmpty()) {
w += o->fontMetrics.boundingRect(option->rect, tf, o->text).width() + 4;
}
point = QPoint(ir.x() + ir.width() / 2 - w / 2, ir.y() + ir.height() / 2 - h / 2);
if (o->direction == Qt::RightToLeft) {
point.rx() += pixmap.width();
}
p->drawPixmap(visualPos(o->direction, o->rect, point + buttonShift), pixmap);
if (o->direction == Qt::RightToLeft) {
ir.translate(-point.x() - 2, 0);
} else {
ir.translate(point.x() + pixmap.width() + 2, 0);
}
// left-align text if there is
if (!o->text.isEmpty()) {
tf |= Qt::AlignLeft;
}
} else {
tf |= Qt::AlignHCenter;
}
ir.translate(buttonShift);
if (o->features & QStyleOptionButton::HasMenu) {
ir = ir.adjusted(0, 0, -pixelMetric(PM_MenuButtonIndicator, o, widget), 0);
}
drawItemText(p, ir, tf, option->palette, (o->state & State_Enabled), o->text, QPalette::ButtonText);
return;
}
}
if (ce == CE_RadioButton || ce == CE_CheckBox) {
if (auto const *o = qstyleoption_cast<QStyleOptionButton const *>(option)) {
bool isRadio = (ce == CE_RadioButton);
QStyleOptionButton subopt = *o;
subopt.rect = subElementRect(isRadio ? SE_RadioButtonIndicator : SE_CheckBoxIndicator, o, widget);
proxy()->drawPrimitive(isRadio ? PE_IndicatorRadioButton : PE_IndicatorCheckBox, &subopt, p, widget);
subopt.rect = subElementRect(isRadio ? SE_RadioButtonContents : SE_CheckBoxContents, o, widget);
drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, p, widget);
if (o->state & State_HasFocus) {
QStyleOptionFocusRect fropt;
fropt.QStyleOption::operator=(*o);
fropt.rect = subElementRect(isRadio ? SE_RadioButtonFocusRect : SE_CheckBoxFocusRect, o, widget);
drawPrimitive(PE_FrameFocusRect, &fropt, p, widget);
}
return;
}
}
if (ce == CE_RadioButtonLabel || ce == CE_CheckBoxLabel) {
if (auto const *o = qstyleoption_cast<QStyleOptionButton const *>(option)) {
uint alignment = visualAlignment(o->direction, Qt::AlignLeft | Qt::AlignVCenter);
if (!styleHint(SH_UnderlineShortcut, o, widget)) {
alignment |= Qt::TextHideMnemonic;
}
QPixmap pix;
QRect textRect = o->rect;
if (!o->icon.isNull()) {
pix = o->icon.pixmap(o->iconSize, o->state & State_Enabled ? QIcon::Normal : QIcon::Disabled);
drawItemPixmap(p, o->rect, alignment, pix);
if (o->direction == Qt::RightToLeft) {
textRect.setRight(textRect.right() - o->iconSize.width() - 4);
} else {
textRect.setLeft(textRect.left() + o->iconSize.width() + 4);
}
}
if (!o->text.isEmpty()){
drawItemText(p, textRect, alignment | Qt::TextShowMnemonic, o->palette, o->state & State_Enabled, o->text, QPalette::ButtonText);
}
return;
}
}
if (ce == CE_ComboBoxLabel) {
if (auto const *o = qstyleoption_cast<QStyleOptionComboBox const *>(option)) {
QRect editRect = subControlRect(CC_ComboBox, o, SC_ComboBoxEditField, widget);
uint alignment = Qt::AlignLeft | Qt::AlignVCenter;
if (!styleHint(SH_UnderlineShortcut, o, widget)) {
alignment |= Qt::TextHideMnemonic;
}
QPixmap pix;
QRect iconRect(editRect);
QIcon icon = o->currentIcon;
QString text = o->currentText;
if (!icon.isNull()) {
pix = icon.pixmap(o->iconSize, o->state & State_Enabled ? QIcon::Normal : QIcon::Disabled);
drawItemPixmap(p, iconRect, alignment, pix);
if (o->direction == Qt::RightToLeft) {
editRect.setRight(editRect.right() - o->iconSize.width() - 4);
} else {
editRect.setLeft(editRect.left() + o->iconSize.width() + 4);
}
}
if (!text.isEmpty()){
drawItemText(p, editRect, alignment, o->palette, o->state & State_Enabled, text, QPalette::ButtonText);
}
return;
}
}
#endif
if (ce == CE_ShapedFrame) {
- if (auto const *o = qstyleoption_cast<QStyleOptionFrameV3 const *>(option)) {
+ if (auto const *o = qstyleoption_cast<QStyleOptionFrame const *>(option)) {
int lw = o->lineWidth;
if (lw > 0) {
QRect r = o->rect;
if (o->frameShape == QFrame::Panel || o->frameShape == QFrame::HLine || o->frameShape == QFrame::VLine) {
if (o->state & (State_Sunken | State_Raised)) {
if (r.width() < r.height()) {
if (r.width() > 2) {
r.adjust(0, 0, -1, 0);
}
} else {
if (r.height() > 2) {
r.adjust(0, 0, 0, -1);
}
}
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
QColor color_topleft = o->palette.color(QPalette::Dark);
QColor color_bottomright = o->palette.color(QPalette::Light);
if (o->state & State_Raised) {
std::swap(color_topleft, color_bottomright);
}
int t = lw;
if (o->frameShape == QFrame::HLine) {
y = h / 2 - t;
h = t * 2;
} else if (o->frameShape == QFrame::VLine) {
x = w / 2 - t;
w = t * 2;
}
p->fillRect(x, y, w - t, t, color_topleft);
p->fillRect(x, y + t, t, h - t, color_topleft);
p->fillRect(x + w - t, y, t, h - t, color_bottomright);
p->fillRect(x + t, y + h - t, w - t, t, color_bottomright);
} else {
if (r.width() < r.height()) {
if (lw < r.width()) {
r = QRect(r.x() + (r.width() - lw) / 2, r.y(), lw, r.height());
}
} else {
if (lw < r.height()) {
r = QRect(r.x(), r.y() + (r.height() - lw) / 2, r.width(), lw);
}
}
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
QColor color = o->palette.color(QPalette::Shadow);
p->fillRect(x, y, w, h, color);
}
} else if (o->frameShape == QFrame::Box) {
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
if (w > 1) w--;
if (h > 1) h--;
QColor color_topleft = o->palette.color(QPalette::Dark);
QColor color_bottomright = o->palette.color(QPalette::Light);
if (o->state & State_Raised) {
std::swap(color_topleft, color_bottomright);
}
drawFrame(p, x + 1, y + 1, w, h, color_bottomright, color_bottomright);
drawFrame(p, x, y, w, h, color_topleft, color_topleft);
} else if (o->frameShape == QFrame::StyledPanel) {
QColor color = o->palette.color(QPalette::Midlight);
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
drawFrame(p, x, y, w, h, color, color);
}
}
}
return;
}
if (ce == CE_PushButtonBevel) {
if (auto const *o = qstyleoption_cast<QStyleOptionButton const *>(option)) {
if (o->features & QStyleOptionButton::Flat) {
// nop
} else {
drawButton(p, option);
}
if (o->features & QStyleOptionButton::HasMenu) {
QStyleOptionButton newBtn = *o;
QRect rect = option->rect;
#ifdef Q_OS_MAC
{
int margin = pixelMetric(PM_ButtonMargin, option, nullptr);
if (margin > 0) {
int n = rect.height();
if (n > margin * 2) {
n = (n - margin * 2) / 2;
if (n > margin) n = margin;
rect = rect.adjusted(n, n, -n, -n);
}
}
}
#endif
int mbi = pixelMetric(PM_IndicatorWidth, o, widget);
rect = QRect(rect.x() + rect.width() - mbi, rect.y(), mbi - 4, rect.height());
newBtn.rect = rect.adjusted(1, 1, -1, -1);
drawPrimitive(PE_IndicatorSpinDown, &newBtn, p, widget);
}
}
return;
}
if (ce == CE_MenuBarEmptyArea) {
drawMenuBarBG(p, option, widget);
return;
}
if (ce == CE_MenuBarItem) {
drawMenuBarBG(p, option, widget);
if (option->state & State_Selected) {
drawSelectedItemFrame(p, option->rect, true);
}
if (auto const *o = qstyleoption_cast<QStyleOptionMenuItem const *>(option)) {
QPalette::ColorRole textRole = disabled ? QPalette::Text : QPalette::ButtonText;
QPixmap pix = o->icon.pixmap(pixelMetric(PM_SmallIconSize, option, widget), QIcon::Normal);
uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
if (!styleHint(SH_UnderlineShortcut, o, widget)) {
alignment |= Qt::TextHideMnemonic;
}
if (!pix.isNull()) {
drawItemPixmap(p, o->rect, alignment, pix);
} else {
drawItemText(p, o->rect, alignment, o->palette, o->state & State_Enabled, o->text, textRole);
}
}
return;
}
if (ce == CE_MenuEmptyArea) {
return;
}
if (ce == CE_MenuItem) {
if (auto const *o = qstyleoption_cast<QStyleOptionMenuItem const *>(option)) {
#ifdef Q_OS_MAC
int checkcol = 15;
#else
// windows always has a check column, regardless whether we have an icon or not
int checkcol = 25;// / QWindowsXPStylePrivate::devicePixelRatio(widget);
#endif
const int gutterWidth = 3;// / QWindowsXPStylePrivate::devicePixelRatio(widget);
QRect rect = option->rect;
bool ignoreCheckMark = false;
if (qobject_cast<const QComboBox*>(widget)) {
// bg
p->fillRect(option->rect, option->palette.color(QPalette::Light));
#ifndef Q_OS_MACX
ignoreCheckMark = true; //ignore the checkmarks provided by the QComboMenuDelegate
#endif
}
//draw vertical menu line
if (option->direction == Qt::LeftToRight) {
checkcol += rect.x();
}
int x, y, w, h;
o->rect.getRect(&x, &y, &w, &h);
int tab = o->tabWidth;
bool dis = !(o->state & State_Enabled);
bool checked = (o->checkType != QStyleOptionMenuItem::NotCheckable) && o->checked;
bool act = o->state & State_Selected;
if (o->menuItemType == QStyleOptionMenuItem::Separator) {
QRect r = option->rect.adjusted(2, 0, -2, 0);
drawGutter(p, r);
return;
}
#ifdef Q_OS_MAC
// qDebug() << pixelMetric(PM_SmallIconSize, option, widget);
QRect vCheckRect = visualRect(option->direction, o->rect, QRect(o->rect.x(), o->rect.y(), 20 - (gutterWidth + o->rect.x()), o->rect.height()));
#else
QRect vCheckRect = visualRect(option->direction, o->rect, QRect(o->rect.x(), o->rect.y(), checkcol - (gutterWidth + o->rect.x()), o->rect.height()));
#endif
if (act) {
drawSelectedItemFrame(p, option->rect, true);
}
int xm = windowsItemFrame;
#ifdef Q_OS_WIN
xm += windowsItemHMargin + (gutterWidth - o->rect.x()) - 1;
#endif
if (!ignoreCheckMark) {
xm += checkcol;
}
int xpos = o->rect.x() + xm;
if (checked && !ignoreCheckMark) {
const qreal boxMargin = 3.5;
const qreal boxWidth = checkcol - 2 * boxMargin;
const int checkColHOffset = windowsItemHMargin + windowsItemFrame - 1;
#ifdef Q_OS_MAC
QRectF checkRectF(0, option->rect.y(), xpos, option->rect.height());
QRect checkRect = checkRectF.toRect();
checkRect.setWidth(checkRect.height());
checkRect.adjust(windowsItemFrame + (xpos - windowsItemFrame - checkRect.width()) / 2, 0, 0, 0);
#else
QRectF checkRectF(option->rect.left() + boxMargin + checkColHOffset, option->rect.center().y() - boxWidth / 2 + 1, boxWidth, boxWidth);
QRect checkRect = checkRectF.toRect();
checkRect.setWidth(checkRect.height()); // avoid .toRect() round error results in non-perfect square
#endif
checkRect = visualRect(o->direction, o->rect, checkRect);
QStyleOptionButton box;
box.QStyleOption::operator=(*option);
box.rect = checkRect;
if (checked) {
box.state |= State_On;
}
drawPrimitive(PE_IndicatorCheckBox, &box, p, widget);
}
if (!ignoreCheckMark) {
if (!o->icon.isNull()) {
QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal;
if (act && !dis) {
mode = QIcon::Active;
}
QPixmap pixmap;
if (checked) {
pixmap = o->icon.pixmap(pixelMetric(PM_SmallIconSize, option, widget), mode, QIcon::On);
} else {
pixmap = o->icon.pixmap(pixelMetric(PM_SmallIconSize, option, widget), mode);
}
double dpr = pixmap.devicePixelRatio();
if (dpr > 0) {
const int pixw = int(pixmap.width() / dpr);
const int pixh = int(pixmap.height() / dpr);
#ifdef Q_OS_MAC
QRect pmr(xpos, vCheckRect.y() + (vCheckRect.height() - pixh) / 2, pixw, pixh);
p->setPen(o->palette.text().color());
p->drawPixmap(pmr.topLeft(), pixmap);
xpos += pmr.width() + 4;
#else
QRect pmr(0, 0, pixw, pixh);
pmr.moveCenter(vCheckRect.center());
p->setPen(o->palette.text().color());
p->drawPixmap(pmr.topLeft(), pixmap);
#endif
}
}
} else {
if (o->icon.isNull()) {
checkcol = 0;
} else {
checkcol = o->maxIconWidth;
}
}
p->setPen(o->palette.buttonText().color());
const QColor textColor = o->palette.text().color();
if (dis) {
p->setPen(textColor);
}
QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin);
QRect vTextRect = visualRect(option->direction, o->rect, textRect);
QString s = o->text;
if (!s.isEmpty()) { // draw text
p->save();
int t = s.indexOf(QLatin1Char('\t'));
int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
if (!styleHint(SH_UnderlineShortcut, o, widget)) {
text_flags |= Qt::TextHideMnemonic;
}
text_flags |= Qt::AlignLeft;
if (t >= 0) {
QRect vShortcutRect = visualRect(option->direction, o->rect, QRect(textRect.topRight(), QPoint(o->rect.right(), textRect.bottom())));
p->drawText(vShortcutRect, text_flags, s.mid(t + 1));
s = s.left(t);
}
QFont font = o->font;
if (o->menuItemType == QStyleOptionMenuItem::DefaultItem) {
font.setBold(true);
}
p->setFont(font);
p->setPen(textColor);
p->drawText(vTextRect, text_flags, s.left(t));
p->restore();
}
if (o->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow
int dim = (h - 2 * windowsItemFrame) / 2;
PrimitiveElement arrow;
arrow = (option->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight;
xpos = x + w - windowsArrowHMargin - windowsItemFrame - dim;
QRect vSubMenuRect = visualRect(option->direction, o->rect, QRect(xpos, y + h / 2 - dim / 2, dim, dim));
QStyleOptionMenuItem newMI = *o;
newMI.rect = vSubMenuRect;
newMI.state = dis ? State_None : State_Enabled;
drawPrimitive(arrow, &newMI, p, widget);
}
}
return;
}
if (ce == CE_TabBarTabShape) {
#ifdef Q_OS_MAC
if (auto const *o = qstyleoption_cast<QStyleOptionTab const *>(option)) {
drawButton(p, option, false);
bool selected = o->state & State_Selected;
if (selected) {
drawSelectedItemFrame(p, o->rect.adjusted(0, 0, -2, -2), widget);
}
return;
}
#else
if (auto const *o = qstyleoption_cast<QStyleOptionTab const *>(option)) {
bool rtlHorTabs = (o->direction == Qt::RightToLeft && (o->shape == QTabBar::RoundedNorth || o->shape == QTabBar::RoundedSouth));
bool selected = o->state & State_Selected;
bool lastTab = ((!rtlHorTabs && o->position == QStyleOptionTab::End) || (rtlHorTabs && o->position == QStyleOptionTab::Beginning));
bool firstTab = ((!rtlHorTabs && o->position == QStyleOptionTab::Beginning) || (rtlHorTabs && o->position == QStyleOptionTab::End));
bool onlyOne = o->position == QStyleOptionTab::OnlyOneTab;
bool previousSelected = ((!rtlHorTabs && o->selectedPosition == QStyleOptionTab::PreviousIsSelected) || (rtlHorTabs && o->selectedPosition == QStyleOptionTab::NextIsSelected));
bool nextSelected = ((!rtlHorTabs && o->selectedPosition == QStyleOptionTab::NextIsSelected) || (rtlHorTabs && o->selectedPosition == QStyleOptionTab::PreviousIsSelected));
int tabBarAlignment = proxy()->styleHint(SH_TabBar_Alignment, o, widget);
bool leftAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignLeft) || (rtlHorTabs && tabBarAlignment == Qt::AlignRight);
bool rightAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignRight) || (rtlHorTabs && tabBarAlignment == Qt::AlignLeft);
QColor light = o->palette.light().color();
// QColor dark = o->palette.dark().color();
QColor shadow = o->palette.shadow().color();
int borderThinkness = proxy()->pixelMetric(PM_TabBarBaseOverlap, o, widget);
if (selected) {
borderThinkness /= 2;
}
QRect r2(option->rect);
int x1 = r2.left();
int x2 = r2.right();
int y1 = r2.top();
int y2 = r2.bottom();
switch (o->shape) {
default:
QProxyStyle::drawControl(ce, o, p, widget);
break;
case QTabBar::RoundedNorth:
{
#ifdef Q_OS_WIN
if (selected) {
y2 += 2;
} else {
y2 += 1;
y1 += 2;
x1 += onlyOne || firstTab ? borderThinkness : 0;
x2 -= onlyOne || lastTab ? borderThinkness : 0;
}
#elif 1
if (selected) {
y2 += 2;
} else {
y2 += 1;
y1 += 2;
x1 += onlyOne || firstTab ? borderThinkness : 0;
x2 -= onlyOne || lastTab ? borderThinkness : 0;
}
#else
if (!selected) {
y1 += 2;
x1 += onlyOne || firstTab ? borderThinkness : 0;
x2 -= onlyOne || lastTab ? borderThinkness : 0;
}
#endif
p->fillRect(QRect(x1 + 1, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 2), o->palette.background());
// Left
if (firstTab || selected || onlyOne || !previousSelected) {
p->setPen(light);
p->drawLine(x1, y1 + 2, x1, y2 - 1 - ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness));
p->drawPoint(x1 + 1, y1 + 1);
}
// Top
{
int beg = x1 + (previousSelected ? 0 : 2);
int end = x2 - (nextSelected ? 0 : 2);
p->setPen(light);
p->drawLine(beg, y1, end, y1);
}
// Right
if (lastTab || selected || onlyOne || !nextSelected) {
p->setPen(shadow);
p->drawLine(x2, y1 + 2, x2, y2 - 1 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness));
p->drawPoint(x2 - 1, y1 + 1);
}
break;
}
case QTabBar::RoundedSouth:
{
#ifdef Q_OS_WIN
if (selected) {
y1 -= 2;
} else {
x1 -= 1;
y1 -= 1;
y2 -= 2;
x1 += firstTab ? borderThinkness : 0;
x2 -= lastTab ? borderThinkness : 0;
}
#elif 1
if (selected) {
y1 -= 2;
} else {
y1 -= 1;
y2 -= 2;
x1 += firstTab ? borderThinkness : 0;
x2 -= lastTab ? borderThinkness : 0;
}
#else
if (!selected) {
y2 -= 2;
x1 += firstTab ? borderThinkness : 0;
x2 -= lastTab ? borderThinkness : 0;
}
#endif
p->fillRect(QRect(x1 + 1, y1 + 2, (x2 - x1) - 1, (y2 - y1) - 1), o->palette.background());
// Left
if (firstTab || selected || onlyOne || !previousSelected) {
p->setPen(light);
p->drawLine(x1, y2 - 2, x1, y1 + ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness));
p->drawPoint(x1 + 1, y2 - 1);
}
// Bottom
{
int beg = x1 + (previousSelected ? 0 : 2);
int end = x2 - (nextSelected ? 0 : 2);
p->setPen(shadow);
p->drawLine(beg, y2, end, y2);
}
// Right
if (lastTab || selected || onlyOne || !nextSelected) {
p->setPen(shadow);
p->drawLine(x2, y2 - 2, x2, y1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness));
p->drawPoint(x2 - 1, y2 - 1);
}
break; }
case QTabBar::RoundedWest:
{
#ifdef Q_OS_WIN
if (selected) {
x2 += 1;
} else {
x1 += 2;
y1 += firstTab ? borderThinkness : 0;
y2 -= lastTab ? borderThinkness : 0;
}
#elif 1
if (selected) {
x2 += 1;
} else {
x1 += 2;
y1 += firstTab ? borderThinkness : 0;
y2 -= lastTab ? borderThinkness : 0;
}
#else
if (!selected) {
x1 += 2;
y1 += firstTab ? borderThinkness : 0;
y2 -= lastTab ? borderThinkness : 0;
}
#endif
p->fillRect(QRect(x1 + 1, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 1), o->palette.background());
// Top
if (firstTab || selected || onlyOne || !previousSelected) {
p->setPen(light);
p->drawLine(x1 + 2, y1, x2 - ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness), y1);
p->drawPoint(x1 + 1, y1 + 1);
}
// Left
{
int beg = y1 + (previousSelected ? 0 : 2);
int end = y2 - (nextSelected ? 0 : 2);
p->setPen(light);
p->drawLine(x1, beg, x1, end);
}
// Bottom
if (lastTab || selected || onlyOne || !nextSelected) {
p->setPen(shadow);
p->drawLine(x1 + 2, y2, x2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2);
p->drawPoint(x1 + 1, y2 - 1);
}
break;
}
case QTabBar::RoundedEast:
{
#ifdef Q_OS_WIN
if (selected) {
x1 -= 2;
} else {
y1 -= 2;
x1 -= 1;
x2 -= 2;
y1 += firstTab ? borderThinkness : 0;
y2 -= lastTab ? borderThinkness : 0;
}
#elif 1
if (selected) {
x1 -= 2;
} else {
x1 -= 1;
x2 -= 2;
y1 += firstTab ? borderThinkness : 0;
y2 -= lastTab ? borderThinkness : 0;
}
#else
if (!selected) {
x2 -= 2;
y1 += firstTab ? borderThinkness : 0;
y2 -= lastTab ? borderThinkness : 0;
}
#endif
p->fillRect(QRect(x1 + 2, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 1), o->palette.background());
// Top
if (firstTab || selected || onlyOne || !previousSelected) {
p->setPen(light);
p->drawLine(x2 - 2, y1, x1 - 1 + ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness), y1);
p->drawPoint(x2 - 1, y1 + 1);
}
// Right
{
int beg = y1 + (previousSelected ? 0 : 2);
int end = y2 - (nextSelected ? 0 : 2);
p->setPen(shadow);
p->drawLine(x2, beg, x2, end);
}
// Bottom
if (lastTab || selected || onlyOne || !nextSelected) {
p->setPen(shadow);
p->drawLine(x2 - 2, y2, x1 - 1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2);
p->drawPoint(x2 - 1, y2 - 1);
}
break;
}
}
}
#endif
return;
}
if (ce == CE_ProgressBarGroove || ce == CE_ProgressBarContents) {
if (auto const *o = qstyleoption_cast<QStyleOptionProgressBar const *>(option)) {
QColor color(0, 128, 255);
bool horz = (o->orientation == Qt::Horizontal);
QString key;
QImage const *image;
if (horz) {
key = "horz";
image = &m->progress_horz;
} else {
key = "vert";
image = &m->progress_vert;
}
p->save();
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
if (ce == CE_ProgressBarContents) {
int len = o->progress - o->minimum;
int div = o->maximum - o->minimum;
bool inv = o->invertedAppearance;
if (horz) {
int v = div > 0 ? (w * len / div) : 0;
if (inv) {
x = x + w - v;
}
w = v;
} else {
int v = div > 0 ? (h * len / div) : 0;
if (!inv) {
y = y + h - v;
}
h = v;
}
QRect r(x, y, w, h);
p->setClipRect(r);
p->fillRect(option->rect, color);
}
QPixmap pm;
QSize size(w, h);
key = pixmapkey("progress_bar", key, size, m->base_color);
if (!QPixmapCache::find(key, &pm)) {
QImage im = image->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
pm = QPixmap::fromImage(im);
QPixmapCache::insert(key, pm);
}
p->setOpacity(0.5);
p->drawPixmap(option->rect, pm, pm.rect());
p->restore();
}
return;
}
if (ce == CE_HeaderSection || ce == CE_HeaderEmptyArea) {
bool horz = true;
if (auto const *o = qstyleoption_cast<QStyleOptionHeader const *>(option)) {
horz = (o->orientation == Qt::Horizontal);
}
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
QLinearGradient gradient;
gradient.setStart(x, y);
gradient.setFinalStop(x, y + h / 4.0);
gradient.setColorAt(0, option->palette.color(QPalette::Light));
gradient.setColorAt(1, option->palette.color(QPalette::Window));
p->fillRect(x, y, w, h, gradient);
p->fillRect(x, y + h - 1, w, 1, option->palette.color(QPalette::Dark));
if (horz) {
p->fillRect(x + w - 1, y, 1, h, option->palette.color(QPalette::Dark));
}
if (ce == CE_HeaderSection) {
if (horz) {
p->fillRect(x, y, 1, h, option->palette.color(QPalette::Light));
}
if (option->state & QStyle::State_MouseOver) {
p->save();
p->fillRect(x, y, w, h, QColor(255, 255, 255, 32));
p->restore();
}
}
if (option->state & QStyle::State_Sunken) {
drawFocusFrame(p, option->rect.adjusted(0, 0, -1, -1), 0);
}
return;
}
if (ce == CE_HeaderLabel) {
if (auto const *o = qstyleoption_cast<QStyleOptionHeader const *>(option)) {
QRect rect = o->rect;
if (!o->icon.isNull()) {
int iconExtent = pixelMetric(PM_SmallIconSize, option, widget);
QPixmap pixmap = o->icon.pixmap(QSize(iconExtent, iconExtent), (o->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled);
int pixw = int(pixmap.width() / pixmap.devicePixelRatio());
QRect aligned = alignedRect(o->direction, QFlag(o->iconAlignment), pixmap.size() / pixmap.devicePixelRatio(), rect);
QRect inter = aligned.intersected(rect);
p->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), inter.y() - aligned.y(), int(aligned.width() * pixmap.devicePixelRatio()), int(pixmap.height() * pixmap.devicePixelRatio()));
const int margin = pixelMetric(QStyle::PM_HeaderMargin, option, widget);
if (o->direction == Qt::LeftToRight) {
rect.setLeft(rect.left() + pixw + margin);
} else {
rect.setRight(rect.right() - pixw - margin);
}
}
if (o->state & QStyle::State_On) {
QFont fnt = p->font();
fnt.setBold(true);
p->setFont(fnt);
}
int leftmargin = 0;
auto align = o->textAlignment;
if (align & Qt::AlignLeft) {
leftmargin += 4;
}
drawItemText(p, rect.adjusted(leftmargin, 0, 0, 0), align, o->palette, (o->state & State_Enabled), o->text, QPalette::ButtonText);
}
return;
}
#ifdef Q_OS_MAC
if (ce == CE_DockWidgetTitle) {
if (auto const *o = qstyleoption_cast<QStyleOptionDockWidget const *>(option)) {
auto const *dockWidget = qobject_cast<QDockWidget const *>(widget);
QRect rect = option->rect;
if (dockWidget && dockWidget->isFloating()) {
QProxyStyle::drawControl(ce, option, p, widget);
return;
}
auto const *v2 = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(o);
bool verticalTitleBar = v2 && v2->verticalTitleBar;
if (verticalTitleBar) {
rect.setSize(rect.size().transposed());
p->translate(rect.left() - 1, rect.top() + rect.width());
p->rotate(-90);
p->translate(-rect.left() + 1, -rect.top());
}
p->setBrush(option->palette.background().color().darker(110));
p->setPen(option->palette.background().color().darker(130));
p->drawRect(rect.adjusted(0, 1, -1, -3));
int buttonMargin = 4;
int mw = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, o, widget);
int fw = proxy()->pixelMetric(PM_DockWidgetFrameWidth, o, widget);
auto const *dw = qobject_cast<const QDockWidget *>(widget);
bool isFloating = dw && dw->isFloating();
QRect r = option->rect.adjusted(0, 2, -1, -3);
QRect titleRect = r;
if (o->closable) {
QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, o, widget).actualSize(QSize(10, 10));
titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
}
if (o->floatable) {
QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarMaxButton, o, widget).actualSize(QSize(10, 10));
titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
}
if (isFloating) {
titleRect.adjust(0, -fw, 0, 0);
if (widget && widget->windowIcon().cacheKey() != QApplication::windowIcon().cacheKey()) {
titleRect.adjust(titleRect.height() + mw, 0, 0, 0);
}
} else {
titleRect.adjust(mw, 0, 0, 0);
if (!o->floatable && !o->closable) {
titleRect.adjust(0, 0, -mw, 0);
}
}
if (!verticalTitleBar) {
titleRect = visualRect(o->direction, r, titleRect);
}
if (!o->title.isEmpty()) {
QString titleText = p->fontMetrics().elidedText(o->title, Qt::ElideRight, verticalTitleBar ? titleRect.height() : titleRect.width());
const int indent = 4;
int align = Qt::AlignRight | Qt::AlignVCenter;
drawItemText(p, rect.adjusted(indent + 1, 1, -indent - 1, -1), align, o->palette, o->state & State_Enabled, titleText, QPalette::WindowText);
}
}
return;
}
#endif // Q_OS_MAC
#ifdef Q_OS_LINUX
if (ce == CE_Splitter) {
p->fillRect(option->rect, option->palette.color(QPalette::Window));
return;
}
#endif
if (ce == CE_ItemViewItem) {
#if 0
if (auto const *o = qstyleoption_cast<QStyleOptionViewItem const *>(option)) {
p->save();
p->setFont(o->font);
drawPrimitive(PE_PanelItemViewItem, option, p, widget);
drawItemText(p, o->rect.adjusted(2, 0, 0, 0), o->displayAlignment, option->palette, true, o->text);
p->restore();
return;
}
#else
if (auto const *o = qstyleoption_cast<QStyleOptionViewItem const *>(option)) {
p->save();
p->setClipRect(o->rect);
QRect checkRect = subElementRect(SE_ItemViewItemCheckIndicator, o, widget);
QRect iconRect = subElementRect(SE_ItemViewItemDecoration, o, widget);
QRect textRect = subElementRect(SE_ItemViewItemText, o, widget);
textRect.adjust(2, 0, 0, 0);
// draw the background
drawPrimitive(PE_PanelItemViewItem, o, p, widget);
// draw the check mark
if (o->features & QStyleOptionViewItem::HasCheckIndicator) {
QStyleOptionViewItem o2(*o);
o2.rect = checkRect;
o2.state = o2.state & ~QStyle::State_HasFocus;
switch (o->checkState) {
case Qt::Unchecked:
o2.state |= QStyle::State_Off;
break;
case Qt::PartiallyChecked:
o2.state |= QStyle::State_NoChange;
break;
case Qt::Checked:
o2.state |= QStyle::State_On;
break;
}
drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &o2, p, widget);
}
// draw the icon
QIcon::Mode mode = QIcon::Normal;
if (!(o->state & QStyle::State_Enabled)) {
mode = QIcon::Disabled;
} else if (o->state & QStyle::State_Selected) {
mode = QIcon::Selected;
}
QIcon::State state = o->state & QStyle::State_Open ? QIcon::On : QIcon::Off;
o->icon.paint(p, iconRect, o->decorationAlignment, mode, state);
// draw the text
if (!o->text.isEmpty()) {
QPalette::ColorGroup cg = o->state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
if (cg == QPalette::Normal && !(o->state & QStyle::State_Active)) {
cg = QPalette::Inactive;
}
if (o->state & QStyle::State_Selected) {
p->setPen(o->palette.color(cg, QPalette::HighlightedText));
} else {
p->setPen(o->palette.color(cg, QPalette::Text));
}
if (o->state & QStyle::State_Editing) {
p->setPen(o->palette.color(cg, QPalette::Text));
p->drawRect(textRect.adjusted(0, 0, -1, -1));
}
drawItemViewText(p, o, textRect, true);
}
#if 0
// draw the focus rect
if (o->state & QStyle::State_HasFocus) {
QStyleOptionFocusRect o3;
o3.QStyleOption::operator=(*o);
o3.rect = subElementRect(SE_ItemViewItemFocusRect, o, widget);
o3.state |= QStyle::State_KeyboardFocusChange;
o3.state |= QStyle::State_Item;
QPalette::ColorGroup cg = (o->state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
o3.backgroundColor = o->palette.color(cg, (o->state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window);
drawPrimitive(PE_FrameFocusRect, &o3, p, widget);
}
#endif
p->restore();
}
return;
#endif
}
if (ce == CE_Header) {
drawControl(CE_HeaderSection, option, p, widget);
drawControl(CE_HeaderLabel, option, p, widget);
return;
}
if (ce == CE_TabBarTab) {
drawControl(CE_TabBarTabShape, option, p, widget);
drawControl(CE_TabBarTabLabel, option, p, widget);
return;
}
if (ce == CE_PushButton) {
drawControl(CE_PushButtonBevel, option, p, widget);
drawControl(CE_PushButtonLabel, option, p, widget);
return;
}
if (ce == CE_ProgressBar) {
drawControl(CE_ProgressBarGroove, option, p, widget);
drawControl(CE_ProgressBarContents, option, p, widget);
return;
}
// qDebug() << ce;
QProxyStyle::drawControl(ce, option, p, widget);
}
void DarkStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, QPainter *p, const QWidget *widget) const
{
// qDebug() << cc;
if (cc == QStyle::CC_ComboBox) {
if (auto const *o = qstyleoption_cast<QStyleOptionComboBox const *>(option)) {
SubControls sub = option->subControls;
if (o->editable) {
#define EP_EDITBORDER_NOSCROLL 6
#define EP_EDITBORDER_HVSCROLL 9
if (sub & SC_ComboBoxEditField) {
p->fillRect(option->rect, option->palette.color(QPalette::Dark));
drawFrame(p, option->rect, option->palette.color(QPalette::Shadow), option->palette.color(QPalette::Light));
}
if (sub & SC_ComboBoxArrow) {
QRect subRect = subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget);
QStyleOptionComplex opt = *option;
opt.rect = subRect;
drawToolButton(p, &opt);
opt.rect = QRect(subRect.right() - 4, subRect.y() + subRect.height() / 2 - 1, 2, 2);
drawPrimitive(PE_IndicatorArrowDown, &opt, p, widget);
}
} else {
if (sub & SC_ComboBoxFrame) {
QStyleOptionButton btn;
btn.QStyleOption::operator=(*option);
btn.rect = option->rect.adjusted(-1, -1, 1, 1);
if (sub & SC_ComboBoxArrow) {
btn.features = QStyleOptionButton::HasMenu;
}
drawControl(QStyle::CE_PushButton, &btn, p, widget);
}
}
}
return;
}
if (cc == QStyle::CC_ToolButton) {
QStyle::State flags = option->state;
if (auto const *o = qstyleoption_cast<QStyleOptionToolButton const *>(option)) {
QRect button, menuarea;
button = subControlRect(cc, o, SC_ToolButton, widget);
menuarea = subControlRect(cc, o, SC_ToolButtonMenu, widget);
State bflags = o->state & ~State_Sunken;
State mflags = bflags;
bool autoRaise = flags & State_AutoRaise;
if (autoRaise) {
if (!(bflags & State_MouseOver) || !(bflags & State_Enabled)) {
bflags &= ~State_Raised;
}
}
if (o->state & State_Sunken) {
if (o->activeSubControls & SC_ToolButton) {
bflags |= State_Sunken;
mflags |= State_MouseOver | State_Sunken;
} else if (o->activeSubControls & SC_ToolButtonMenu) {
mflags |= State_Sunken;
bflags |= State_MouseOver;
}
}
QStyleOption tool(0);
tool.palette = o->palette;
if (o->subControls & SC_ToolButton) {
if (flags & (State_Sunken | State_On | State_Raised) || !autoRaise) {
tool.rect = option->rect;
tool.state = bflags;
drawToolButton(p, &tool);
}
}
if (o->state & State_HasFocus) {
QStyleOptionFocusRect fr;
fr.QStyleOption::operator=(*o);
fr.rect.adjust(3, 3, -3, -3);
if (o->features & QStyleOptionToolButton::MenuButtonPopup) {
fr.rect.adjust(0, 0, -pixelMetric(QStyle::PM_MenuButtonIndicator, o, widget), 0);
}
drawPrimitive(PE_FrameFocusRect, &fr, p, widget);
}
QStyleOptionToolButton label = *o;
label.state = bflags;
int fw = 2;
if (!autoRaise)
label.state &= ~State_Sunken;
label.rect = button.adjusted(fw, fw, -fw, -fw);
drawControl(CE_ToolButtonLabel, &label, p, widget);
if (o->subControls & SC_ToolButtonMenu) {
tool.rect = menuarea;
tool.state = mflags;
if (autoRaise) {
drawPrimitive(PE_IndicatorButtonDropDown, &tool, p, widget);
} else {
tool.state = mflags;
menuarea.adjust(-2, 0, 0, 0);
// Draw menu button
if ((bflags & State_Sunken) != (mflags & State_Sunken)){
p->save();
p->setClipRect(menuarea);
tool.rect = option->rect;
drawToolButton(p, &tool);
p->restore();
}
// Draw arrow
p->save();
p->setPen(option->palette.dark().color());
p->drawLine(menuarea.left(), menuarea.top() + 3, menuarea.left(), menuarea.bottom() - 3);
p->setPen(option->palette.light().color());
p->drawLine(menuarea.left() - 1, menuarea.top() + 3, menuarea.left() - 1, menuarea.bottom() - 3);
tool.rect = menuarea.adjusted(2, 3, -2, -1);
drawPrimitive(PE_IndicatorArrowDown, &tool, p, widget);
p->restore();
}
} else if (o->features & QStyleOptionToolButton::HasMenu) {
int mbi = pixelMetric(PM_MenuButtonIndicator, o, widget);
QRect ir = o->rect;
QStyleOptionToolButton newBtn = *o;
newBtn.rect = QRect(ir.right() + 4 - mbi, ir.height() - mbi + 4, mbi - 5, mbi - 5);
drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget);
}
}
return;
}
if (cc == QStyle::CC_ScrollBar) {
bool ishorz = (option->state & State_Horizontal);
auto *tx = const_cast<ScrollBarTextures *>(ishorz ? &m->hsb : &m->vsb);
int extent = (ishorz ? tx->slider.im_normal.height() : tx->slider.im_normal.width()) - 2;
auto DrawNinePatchImage2 = [&](QImage const &image, QRect const &r){
int w, h;
if (ishorz) {
h = extent;
int d = r.height();
w = (d == 0) ? 0 : (h * r.width() / d);
} else {
w = extent;
int d = r.width();
h = (d == 0) ? 0 : (w * r.height() / d);
}
drawNinePatchImage(p, image, r, w, h);
};
p->fillRect(option->rect, color(64));
p->setRenderHint(QPainter::Antialiasing);
{
#ifdef Q_OS_MAC
QRect r = option->rect;
#else
QRect r1 = subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget);
QRect r2 = subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget);
QRect r = r1.united(r2);
#endif
DrawNinePatchImage2(tx->page_bg, r);
}
#ifndef Q_OS_MAC // macOSのスクロールバーには、ラインステップボタンが無い
{
QRect r = subControlRect(CC_ScrollBar, option, SC_ScrollBarSubLine, widget);
DrawNinePatchImage2(tx->page_bg, r);
}
{
QRect r = subControlRect(CC_ScrollBar, option, SC_ScrollBarAddLine, widget);
DrawNinePatchImage2(tx->page_bg, r);
}
auto DrawAddSubButton = [&](ButtonImages const &ims, SubControl subctl){
QRect r = subControlRect(CC_ScrollBar, option, subctl, widget);
QPixmap pm;;
if (!(option->activeSubControls & subctl)) {
pm = pixmapFromImage(ims.im_normal, r.size());
p->drawPixmap(r.topLeft(), pm);
} else {
pm = pixmapFromImage(ims.im_hover, r.size());
p->drawPixmap(r.topLeft(), pm);
}
};
DrawAddSubButton(tx->sub_line, SC_ScrollBarSubLine);
DrawAddSubButton(tx->add_line, SC_ScrollBarAddLine);
#endif
{
QRect r = subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
int w, h;
if (ishorz) {
h = extent;
int d = r.height();
w = (d == 0) ? 0 : (h * r.width() / d);
} else {
w = extent;
int d = r.width();
h = (d == 0) ? 0 : (w * r.height() / d);
}
#ifdef Q_OS_MAC // macだとズレて見えるので調整する
if (ishorz) {
r = r.translated(1, 0);
} else {
r = r.translated(0, 1);
}
#endif
if (!(option->activeSubControls & SC_ScrollBarSlider)) {
DrawNinePatchImage2(tx->slider.im_normal, r);
} else {
DrawNinePatchImage2(tx->slider.im_hover, r);
}
}
return;
}
if (cc == CC_GroupBox) {
if (auto const *o = qstyleoption_cast<QStyleOptionGroupBox const *>(option)) {
QRect textRect = subControlRect(CC_GroupBox, option, SC_GroupBoxLabel, widget);
QRect checkBoxRect = subControlRect(CC_GroupBox, option, SC_GroupBoxCheckBox, widget);
if (o->subControls & QStyle::SC_GroupBoxFrame) {
QStyleOptionFrame frame;
frame.QStyleOption::operator=(*o);
frame.features = o->features;
frame.lineWidth = o->lineWidth;
frame.midLineWidth = o->midLineWidth;
frame.rect = subControlRect(CC_GroupBox, option, SC_GroupBoxFrame, widget);
p->save();
QRegion region(o->rect);
if (!o->text.isEmpty()) {
bool ltr = o->direction == Qt::LeftToRight;
QRect finalRect;
if (o->subControls & QStyle::SC_GroupBoxCheckBox) {
finalRect = checkBoxRect.united(textRect);
finalRect.adjust(ltr ? -4 : 0, 0, ltr ? 0 : 4, 0);
} else {
finalRect = textRect;
}
region -= finalRect;
}
p->setClipRegion(region);
drawPrimitive(PE_FrameGroupBox, &frame, p, widget);
p->restore();
}
// Draw title
if ((o->subControls & QStyle::SC_GroupBoxLabel) && !o->text.isEmpty()) {
QColor textColor = o->textColor;
if (textColor.isValid()) {
p->setPen(textColor);
}
int alignment = int(o->textAlignment);
if (!styleHint(QStyle::SH_UnderlineShortcut, option, widget)) {
alignment |= Qt::TextHideMnemonic;
}
alignment |= Qt::TextShowMnemonic;
alignment |= Qt::AlignHCenter;
drawItemText(p, textRect, alignment, o->palette, o->state & State_Enabled, o->text, QPalette::WindowText);
if (o->state & State_HasFocus) {
QStyleOptionFocusRect fropt;
fropt.QStyleOption::operator=(*o);
fropt.rect = textRect;
drawPrimitive(PE_FrameFocusRect, &fropt, p, widget);
}
}
// Draw checkbox
if (o->subControls & SC_GroupBoxCheckBox) {
QStyleOptionButton box;
box.QStyleOption::operator=(*o);
box.rect = checkBoxRect;
drawPrimitive(PE_IndicatorCheckBox, &box, p, widget);
}
}
return;
}
if (cc == CC_Slider) {
if (auto const *o = qstyleoption_cast<QStyleOptionSlider const *>(option)) {
QRect groove = subControlRect(CC_Slider, option, SC_SliderGroove, widget);
QRect handle = subControlRect(CC_Slider, option, SC_SliderHandle, widget);
bool horizontal = o->orientation == Qt::Horizontal;
bool ticksAbove = o->tickPosition & QSlider::TicksAbove;
bool ticksBelow = o->tickPosition & QSlider::TicksBelow;
QPixmap cache;
QBrush oldBrush = p->brush();
QPen oldPen = p->pen();
QColor shadowAlpha(Qt::black);
shadowAlpha.setAlpha(10);
if ((option->subControls & SC_SliderGroove) && groove.isValid()) {
QRect rect(0, 0, groove.width(), groove.height());
QString key = pixmapkey("slider_groove", horizontal ? "horz" : "vert", rect.size(), m->base_color);
QRectF grooveRect;
double r = std::min(groove.width(), groove.height()) / 2.0;
{
double n = r * 3 / 4;
grooveRect = QRectF(rect).adjusted(n, n, -n, -n);
r -= n;
}
// draw background groove
QPixmap cache;
if (!QPixmapCache::find(key, cache)) {
cache = QPixmap(rect.size());
cache.fill(Qt::transparent);
QPainter groovePainter(&cache);
groovePainter.setRenderHint(QPainter::Antialiasing, true);
groovePainter.translate(0.5, 0.5);
QLinearGradient gradient;
if (horizontal) {
gradient.setStart(grooveRect.center().x(), grooveRect.top());
gradient.setFinalStop(grooveRect.center().x(), grooveRect.bottom());
} else {
gradient.setStart(grooveRect.left(), grooveRect.center().y());
gradient.setFinalStop(grooveRect.right(), grooveRect.center().y());
}
groovePainter.setPen(Qt::NoPen);
gradient.setColorAt(0, color(32));
gradient.setColorAt(1, color(128));
groovePainter.setBrush(gradient);
groovePainter.drawRoundedRect(grooveRect, r, r);
groovePainter.end();
QPixmapCache::insert(key, cache);
}
p->drawPixmap(groove.topLeft(), cache);
}
if (option->subControls & SC_SliderTickmarks) {
int tickSize = pixelMetric(PM_SliderTickmarkOffset, option, widget);
int available = pixelMetric(PM_SliderSpaceAvailable, o, widget);
int interval = o->tickInterval;
if (interval <= 0) {
interval = o->singleStep;
if (QStyle::sliderPositionFromValue(o->minimum, o->maximum, interval, available) - QStyle::sliderPositionFromValue(o->minimum, o->maximum, 0, available) < 3) {
interval = o->pageStep;
}
}
if (interval <= 0) {
interval = 1;
}
int v = o->minimum;
int len = pixelMetric(PM_SliderLength, o, widget);
while (v <= o->maximum + 1) {
if (v == o->maximum + 1 && interval == 1) break;
const int v2 = qMin(v, o->maximum);
int pos = sliderPositionFromValue(o->minimum, o->maximum,
v2, (horizontal
? o->rect.width()
: o->rect.height()) - len,
o->upsideDown) + len / 2;
int extra = 2 - ((v2 == o->minimum || v2 == o->maximum) ? 1 : 0);
if (horizontal) {
if (ticksAbove) {
p->setPen(color(128));
p->drawLine(pos, o->rect.top() + extra, pos, o->rect.top() + tickSize);
}
if (ticksBelow) {
p->setPen(color(0));
p->drawLine(pos, o->rect.bottom() - extra, pos, o->rect.bottom() - tickSize);
}
} else {
if (ticksAbove) {
p->setPen(color(128));
p->drawLine(o->rect.left() + extra, pos, o->rect.left() + tickSize, pos);
}
if (ticksBelow) {
p->setPen(color(0));
p->drawLine(o->rect.right() - extra, pos, o->rect.right() - tickSize, pos);
}
}
// in the case where maximum is max int
int nextInterval = v + interval;
if (nextInterval < v) break;
v = nextInterval;
}
}
// draw handle
if ((option->subControls & SC_SliderHandle) ) {
QRect pixmapRect(0, 0, handle.width(), handle.height());
QRect r = pixmapRect.adjusted(0, 0, -1, -1);
QPainterPath path;
path.addEllipse(r);
QString handlePixmapName = pixmapkey(QLatin1String("slider_handle"), "", handle.size(), m->base_color);
if (!QPixmapCache::find(handlePixmapName, cache)) {
cache = QPixmap(handle.size());
cache.fill(Qt::transparent);
QPainter handlePainter(&cache);
handlePainter.setRenderHint(QPainter::Antialiasing, true);
handlePainter.translate(0.5, 0.5);
QLinearGradient gradient;
gradient.setStart(pixmapRect.topLeft());
gradient.setFinalStop(pixmapRect.bottomRight());
gradient.setColorAt(0, color(192));
gradient.setColorAt(1, QColor(0, 0, 0));
handlePainter.save();
handlePainter.setClipPath(path);
handlePainter.fillRect(r, gradient);
QColor highlight_color = colorize(color(160).rgb(), 192, 255);
handlePainter.setPen(QPen(highlight_color, 2));
handlePainter.setBrush(Qt::NoBrush);
handlePainter.drawEllipse(r.adjusted(0, 0, 2, 2));
handlePainter.setPen(QPen(QColor(0, 0, 0), 2));
handlePainter.setBrush(Qt::NoBrush);
handlePainter.drawEllipse(r.adjusted(-2, -2, 0, 0));
handlePainter.restore();
handlePainter.end();
QPixmapCache::insert(handlePixmapName, cache);
}
QPoint topleft = handle.topLeft();
p->drawPixmap(topleft, cache);
if ((option->state & State_HasFocus) && (option->state & State_KeyboardFocusChange)) {
p->save();
p->setClipPath(path.translated(topleft));
p->fillRect(handle, QColor(255, 255, 255, 32));
p->restore();
}
}
p->setBrush(oldBrush);
p->setPen(oldPen);
}
return;
}
// qDebug() << cc;
QProxyStyle::drawComplexControl(cc, option, p, widget);
}
diff --git a/src/main.cpp b/src/main.cpp
index 71c2e03..c0693cc 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,184 +1,197 @@
#include "main.h"
#include "ApplicationGlobal.h"
#include "MainWindow.h"
#include "MySettings.h"
#include "SettingGeneralForm.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include "darktheme/DarkStyle.h"
#include "platform.h"
#include "webclient.h"
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QProxyStyle>
#include <QStandardPaths>
#include <QTranslator>
#include <signal.h>
#include <string>
#ifdef Q_OS_WIN
#include "win32/win32.h"
#include "SettingGeneralForm.h"
#endif
#ifndef APP_GUITAR
#error APP_GUITAR is not defined.
#endif
ApplicationGlobal *global = nullptr;
ApplicationSettings ApplicationSettings::defaultSettings()
{
ApplicationSettings s;
s.proxy_server = "http://squid:3128/";
+
+ s.branch_label_color.head = QColor(255, 192, 224); // pink
+ s.branch_label_color.local = QColor(192, 224, 255); // blue
+ s.branch_label_color.remote = QColor(192, 240, 224); // green
+ s.branch_label_color.tag = QColor(255, 224, 192); // orange
+
+#ifndef __HAIKU__
+ s.terminal_command = "x-terminal-emulator";
+#else
+ s.terminal_command = "Terminal";
+#endif
+
return s;
}
static bool isHighDpiScalingEnabled()
{
MySettings s;
s.beginGroup("UI");
QVariant v = s.value("EnableHighDpiScaling");
return v.isNull() || v.toBool();
}
void setEnvironmentVariable(QString const &name, QString const &value);
void onSigTerm(int)
{
qDebug() << "SIGTERM caught";
if (global->mainwindow) {
global->mainwindow->close();
}
}
int main(int argc, char *argv[])
{
#ifdef Q_OS_WIN
setEnvironmentVariable("UNICODEMAP_JP", "cp932");
#else
setenv("UNICODEMAP_JP", "cp932", 1);
#endif
ApplicationGlobal g;
global = &g;
signal(SIGTERM, onSigTerm);
global->organization_name = ORGANIZATION_NAME;
global->application_name = APPLICATION_NAME;
global->generic_config_dir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
global->app_config_dir = global->generic_config_dir / global->organization_name / global->application_name;
global->config_file_path = joinpath(global->app_config_dir, global->application_name + ".ini");
if (!QFileInfo(global->app_config_dir).isDir()) {
QDir().mkpath(global->app_config_dir);
}
if (isHighDpiScalingEnabled()){
#if (QT_VERSION < QT_VERSION_CHECK(5, 6, 0))
qDebug() << "High DPI scaling is not supported";
#else
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
}
QApplication a(argc, argv);
a.setAttribute(Qt::AA_UseHighDpiPixmaps);
QApplication::setOrganizationName(global->organization_name);
QApplication::setApplicationName(global->application_name);
qRegisterMetaType<RepositoryItem>("RepositoryItem");
+ qRegisterMetaType<RepositoryWrapperFrameP>("RepositoryWrapperFrameP");
{
MySettings s;
s.beginGroup("UI");
global->language_id = s.value("Language").toString();
global->theme_id = s.value("Theme").toString();
if (global->theme_id.compare("dark", Qt::CaseInsensitive) == 0) {
global->theme = createDarkTheme();
} else {
global->theme = createStandardTheme();
}
s.endGroup();
}
QApplication::setStyle(global->theme->newStyle());
if (QApplication::queryKeyboardModifiers() & Qt::ShiftModifier) {
global->start_with_shift_key = true;
}
WebClient::initialize();
bool f_open_here = false;
QStringList args;
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg[0] == '-') {
if (arg == "--open-here") {
f_open_here = true;
}
} else {
args.push_back(QString::fromStdString(arg));
}
}
if (global->app_config_dir.isEmpty()) {
QMessageBox::warning(nullptr, qApp->applicationName(), "Preparation of data storage folder failed.");
return 1;
}
// 設定ファイルがないときは、言語の選択をする。
if (!QFileInfo::exists(global->config_file_path)) {
auto langs = SettingGeneralForm::languages();
SettingGeneralForm::execSelectLanguageDialog(nullptr, langs, [](){});
}
QTranslator translator;
{
if (global->language_id.isEmpty() || global->language_id == "en") {
// thru
} else {
QString path = ":/translations/Guitar_" + global->language_id;
bool f = translator.load(path, QApplication::applicationDirPath());
if (!f) {
qDebug() << QString("Failed to load the translation file: %1").arg(path);
}
QApplication::installTranslator(&translator);
}
}
MainWindow w;
global->mainwindow = &w;
global->panel_bg_color = w.palette().color(QPalette::Background);
w.setWindowIcon(QIcon(":/image/guitar.png"));
w.show();
w.shown();
if (f_open_here) {
QString dir = QDir::current().absolutePath();
w.autoOpenRepository(dir);
} else if (args.size() == 1) {
QString dir = args[0] / QString();
if (dir.startsWith("./") || dir.startsWith(".\\")) {
dir = QDir::current().absolutePath() / dir.mid(2);
}
QFileInfo fi(dir);
if (fi.isDir()) {
dir = fi.absolutePath();
w.autoOpenRepository(dir);
}
}
int r = QApplication::exec();
global->mainwindow = nullptr;
return r;
}
diff --git a/src/main.h b/src/main.h
index d8a1317..0d21403 100644
--- a/src/main.h
+++ b/src/main.h
@@ -1,25 +1,38 @@
#ifndef MAIN_H
#define MAIN_H
+#include <QColor>
#include <QString>
#define ORGANIZATION_NAME "soramimi.jp"
#define APPLICATION_NAME "Guitar"
class ApplicationSettings {
public:
QString git_command;
QString file_command;
QString gpg_command;
+ QString ssh_command;
+ QString terminal_command;
QString default_working_dir;
QString proxy_type;
QString proxy_server;
bool get_committer_icon = true;
bool remember_and_restore_window_position = false;
bool enable_high_dpi_scaling = true;
bool automatically_fetch_when_opening_the_repository = true;
unsigned int maximum_number_of_commit_item_acquisitions = 10000;
+ bool show_labels = true;
+
+ struct {
+ QColor head;
+ QColor local;
+ QColor remote;
+ QColor tag;
+ } branch_label_color;
+
+
static ApplicationSettings defaultSettings();
};
#endif // MAIN_H
diff --git a/src/platform.h b/src/platform.h
index 6070579..01ccc34 100644
--- a/src/platform.h
+++ b/src/platform.h
@@ -1,28 +1,30 @@
#ifndef PLATFORM_H
#define PLATFORM_H
#include <QApplication>
#ifdef Q_OS_WIN
#include "win32/win32.h"
#else
#include <unistd.h>
#endif
#ifdef Q_OS_MAC
extern "C" char **environ;
#endif
#ifdef Q_OS_WIN
#define GIT_COMMAND "git.exe"
#define FILE_COMMAND "file.exe"
#define GPG_COMMAND "gpg.exe"
#define GPG2_COMMAND "gpg2.exe"
+#define SSH_COMMAND "ssh.exe"
#else
#define GIT_COMMAND "git"
#define FILE_COMMAND "file"
#define GPG_COMMAND "gpg"
#define GPG2_COMMAND "gpg2"
+#define SSH_COMMAND "ssh"
#endif
#endif // PLATFORM_H
diff --git a/src/resources/translations/Guitar_ja.qm b/src/resources/translations/Guitar_ja.qm
index 409739b..b9faeaa 100644
Binary files a/src/resources/translations/Guitar_ja.qm and b/src/resources/translations/Guitar_ja.qm differ
diff --git a/src/resources/translations/Guitar_ja.ts b/src/resources/translations/Guitar_ja.ts
index 20ad774..249482d 100644
--- a/src/resources/translations/Guitar_ja.ts
+++ b/src/resources/translations/Guitar_ja.ts
@@ -1,2986 +1,3041 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ja_JP">
<context>
<name>AboutDialog</name>
<message>
<location filename="../../AboutDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../AboutDialog.ui" line="34"/>
<location filename="../../AboutDialog.ui" line="46"/>
<location filename="../../AboutDialog.ui" line="58"/>
<location filename="../../AboutDialog.ui" line="70"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../AboutDialog.ui" line="86"/>
<source>&lt;a href=&quot;https://github.com/soramimi/Guitar&quot;&gt;https://github.com/soramimi/Guitar&lt;/a&gt;</source>
<translation></translation>
</message>
<message>
<location filename="../../AboutDialog.cpp" line="25"/>
<source>About %1</source>
<translation>%1 について</translation>
</message>
</context>
<context>
<name>AreYouSureYouWantToContinueConnectingDialog</name>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="14"/>
<source>Unknown Host</source>
<translation>不明なホスト</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="20"/>
<source>Are you sure you want to continue connecting (yes/no)?</source>
<translation>接続を続行しますか? (yes/no)?</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="52"/>
<source>Continue</source>
<translation>続行</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="59"/>
<source>Close</source>
<translation>閉じる</translation>
</message>
</context>
<context>
<name>BasicMainWindow</name>
<message>
- <location filename="../../BasicMainWindow.cpp" line="261"/>
+ <location filename="../../BasicMainWindow.cpp" line="243"/>
<source>git command not specified</source>
<translation>gitコマンドが指定されていません</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="374"/>
+ <location filename="../../BasicMainWindow.cpp" line="358"/>
<source>The URL is a valid repository</source>
<translation>このURLは有効なリポジトリです</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="375"/>
+ <location filename="../../BasicMainWindow.cpp" line="359"/>
<source>Failed to access the URL</source>
<translation>このURLへのアクセスに失敗しました</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="380"/>
+ <location filename="../../BasicMainWindow.cpp" line="364"/>
<source>Remote Repository</source>
<translation>リモートリポジトリ</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="405"/>
+ <location filename="../../BasicMainWindow.cpp" line="389"/>
<source>&amp;Property</source>
<translation>プロパティ(&amp;P)</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1273"/>
+ <location filename="../../BasicMainWindow.cpp" line="1272"/>
<source>Select %1 command</source>
<translation>%1 コマンドの選択</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1452"/>
+ <location filename="../../BasicMainWindow.cpp" line="1447"/>
<source>Revert all files</source>
<translation>すべてのファイルの変更を破棄</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1610"/>
+ <location filename="../../BasicMainWindow.cpp" line="1605"/>
<source>The folder is not a valid git repository.</source>
<translation>フォルダは有効なGitリポジトリではありません。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1614"/>
+ <location filename="../../BasicMainWindow.cpp" line="1609"/>
<source>Do you want to initialize it as a git repository ?</source>
<translation>Gitリポジトリとして初期化しますか?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1615"/>
+ <location filename="../../BasicMainWindow.cpp" line="1610"/>
<source>Initialize Repository</source>
<translation>リポジトリの初期化</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1745"/>
+ <location filename="../../BasicMainWindow.cpp" line="1740"/>
<source>No repository selected</source>
<translation>リポジトリが選択されていません</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1915"/>
<source>Repository Property</source>
- <translation>リポジトリのプロパティ</translation>
+ <translation type="vanished">リポジトリのプロパティ</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1915"/>
<location filename="../../BasicMainWindow.cpp" line="2017"/>
<source>Not a valid git repository</source>
<translation>有効なリポジトリではありません</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<location filename="../../BasicMainWindow.cpp" line="2017"/>
<source>Open Repository</source>
<translation>リポジトリを開く</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<source>No such folder</source>
<translation>そのようなフォルダはありません</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<source>Remove from bookmark ?</source>
<translation>ブックマークから削除しますか?</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2248"/>
<source>, %1 ahead</source>
<translation></translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2251"/>
<source>, %1 behind</source>
<translation></translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>Confirm Remove</source>
<translation>削除の確認</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>Are you sure you want to remove the repository from bookmarks ?</source>
<translation>リポジトリをブックマークから削除してよろしいですか?</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>(Files will NOT be deleted)</source>
<translation>(ファルは削除されません)</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2302"/>
<source>A file with same name already exists</source>
<translation>同じ名前のファイルが既に存在しています</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2303"/>
<location filename="../../BasicMainWindow.cpp" line="2308"/>
<location filename="../../BasicMainWindow.cpp" line="2325"/>
<location filename="../../BasicMainWindow.cpp" line="2330"/>
<source>Clone</source>
<translation>クローン</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2307"/>
<source>A folder with same name already exists</source>
<translation>同じ名前のフォルダが既に存在しています</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2324"/>
<source>Invalid folder</source>
<translation>無効なフォルダ</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2329"/>
<source>No such folder. Create it now ?</source>
<translation>このフォルダはありません。作成しますか?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2398"/>
+ <location filename="../../BasicMainWindow.cpp" line="2402"/>
<source>Commit</source>
<translation>コミット</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2398"/>
+ <location filename="../../BasicMainWindow.cpp" line="2402"/>
<source>Commit message can not be omitted.</source>
<translation>コミットメッセージを空にすることはできません。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2415"/>
+ <location filename="../../BasicMainWindow.cpp" line="2419"/>
<source>Failed to commit</source>
<translation>コミット失敗</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2449"/>
- <location filename="../../BasicMainWindow.cpp" line="2533"/>
+ <location filename="../../BasicMainWindow.cpp" line="2453"/>
+ <location filename="../../BasicMainWindow.cpp" line="2538"/>
<source>Connection refused.</source>
<translation>接続が拒否されました。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2502"/>
+ <location filename="../../BasicMainWindow.cpp" line="2506"/>
<source>No remote repository is registered.</source>
<translation>リモートリポジトリが登録されていません。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2524"/>
+ <location filename="../../BasicMainWindow.cpp" line="2529"/>
<source>The current branch %1 has no upstream branch.</source>
<translation>現在のブランチ「%1」には上流ブランチがありません。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2527"/>
+ <location filename="../../BasicMainWindow.cpp" line="2532"/>
<source>You try push --set-upstream</source>
<translation>--set-upstream を試してください</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2616"/>
+ <location filename="../../BasicMainWindow.cpp" line="2621"/>
<source>Failed to delete the branch &apos;%1&apos;</source>
<translation>ブランチ「%1」の削除に失敗しました</translation>
</message>
<message>
<source>Failed to delete the branch &apos;%1&apos;
</source>
<translation type="vanished">ブランチの削除に失敗しました : &apos;%1&apos;</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2680"/>
+ <location filename="../../BasicMainWindow.cpp" line="2685"/>
<source>Are you sure you want to run the following command ?</source>
<translation>次のコマンドを実行してよろしいですか?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2701"/>
+ <location filename="../../BasicMainWindow.cpp" line="2706"/>
<source>Reset a file</source>
<translation>ファイルをリセットします</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2715"/>
+ <location filename="../../BasicMainWindow.cpp" line="2720"/>
<source>Unnamed</source>
<translation>無名</translation>
</message>
</context>
<context>
<name>BasicRepositoryDialog</name>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="66"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="77"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="67"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="78"/>
<source>Purpose</source>
<translation>用途</translation>
</message>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="68"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="79"/>
<source>URL</source>
<translation></translation>
</message>
</context>
<context>
<name>BigDiffWindow</name>
<message>
<location filename="../../BigDiffWindow.ui" line="14"/>
<source>Diff</source>
<translation>差分</translation>
</message>
</context>
<context>
<name>BlameWindow</name>
<message>
<location filename="../../BlameWindow.ui" line="14"/>
<source>Blame</source>
<translation></translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="45"/>
<source>Information</source>
<translation>情報</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="60"/>
<source>Commit</source>
<translation>コミット</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="70"/>
<source>Date</source>
<translation>日付</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="90"/>
<source>Message</source>
<translation>メッセージ</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="80"/>
<source>Author</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="118"/>
<source>Close</source>
<translation>閉じる</translation>
</message>
</context>
<context>
<name>CheckoutDialog</name>
<message>
<location filename="../../CheckoutDialog.ui" line="14"/>
<source>Checkout</source>
<translation>チェックアウト</translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="20"/>
<location filename="../../CheckoutDialog.ui" line="27"/>
<location filename="../../CheckoutDialog.ui" line="34"/>
<source>RadioButton</source>
<translation></translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="63"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="70"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>CherryPickDialog</name>
<message>
<location filename="../../CherryPickDialog.ui" line="14"/>
<source>Cherry-pick</source>
<translation>チェリーピック</translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="20"/>
<source>HEAD</source>
<translation></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="49"/>
<source>Pick</source>
<translation></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="78"/>
<source>Mainline</source>
<translation></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="109"/>
<source>Allow empty</source>
<translation></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="131"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="138"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="19"/>
<source>Commit</source>
<translation>コミット</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="20"/>
<source>Date</source>
<translation>日付</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="21"/>
<source>Author</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="22"/>
<source>Message</source>
<translation>メッセージ</translation>
</message>
</context>
<context>
<name>CloneDialog</name>
<message>
<location filename="../../CloneDialog.ui" line="14"/>
- <location filename="../../CloneDialog.ui" line="103"/>
+ <location filename="../../CloneDialog.ui" line="113"/>
<source>Clone</source>
<translation>クローン</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="22"/>
<source>Remote</source>
<translation>リモート</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="39"/>
<source>&amp;Test</source>
<translation>テスト(&amp;T)</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="46"/>
<source>Local</source>
<translation>ローカル</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="56"/>
<source>Browse</source>
<translation>参照</translation>
</message>
<message>
- <location filename="../../CloneDialog.ui" line="83"/>
+ <location filename="../../CloneDialog.ui" line="93"/>
<source>Open existing local directory...</source>
<translation>既存のフォルダを開く...</translation>
</message>
<message>
- <location filename="../../CloneDialog.ui" line="113"/>
+ <location filename="../../CloneDialog.ui" line="123"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="40"/>
+ <location filename="../../CloneDialog.cpp" line="41"/>
<source>Search</source>
<translation>検索</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="41"/>
+ <location filename="../../CloneDialog.cpp" line="42"/>
<source>GitHub</source>
<translation></translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="113"/>
+ <location filename="../../CloneDialog.cpp" line="115"/>
<source>Checkout into</source>
<translation>ここにチェックアウト</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="125"/>
+ <location filename="../../CloneDialog.cpp" line="127"/>
<source>Open existing directory</source>
<translation>既存のフォンるだを開く</translation>
</message>
</context>
<context>
<name>CloneFromGitHubDialog</name>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="14"/>
<source>Clone from GitHub</source>
<translation>GitHubからクローン</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="45"/>
<source>ssh</source>
<translation></translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="55"/>
<source>http</source>
<translation></translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="95"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="102"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>CommitDialog</name>
<message>
<location filename="../../CommitDialog.ui" line="14"/>
<source>Commit</source>
<translation>コミット</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="27"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="49"/>
<source>Author</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="68"/>
<location filename="../../CommitDialog.ui" line="94"/>
<location filename="../../CommitDialog.ui" line="139"/>
<location filename="../../CommitDialog.ui" line="159"/>
<location filename="../../CommitDialog.ui" line="179"/>
<source>---</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="75"/>
<location filename="../../CommitDialog.ui" line="166"/>
<source>Mail</source>
<translation>メール</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="111"/>
<source>GPG Signing</source>
<translation>GPG署名</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="126"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="146"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="189"/>
<source>Configure...</source>
<translation>設定...</translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="198"/>
+ <location filename="../../CommitDialog.ui" line="203"/>
<source>Message</source>
<translation>メッセージ</translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="223"/>
+ <location filename="../../CommitDialog.ui" line="210"/>
+ <source>Amend</source>
+ <translation>修正する</translation>
+ </message>
+ <message>
+ <location filename="../../CommitDialog.ui" line="237"/>
<source>OK</source>
<translation></translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="233"/>
+ <location filename="../../CommitDialog.ui" line="247"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>CommitExploreWindow</name>
<message>
<location filename="../../CommitExploreWindow.ui" line="14"/>
<source>Commit Explorer</source>
<translation>コミットエクスプローラー</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="65"/>
<source>Commit ID</source>
<translation>コミットID</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="82"/>
<source>Date</source>
<translation>日付</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="89"/>
<source>Author</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.cpp" line="78"/>
<source>Commit</source>
<translation>コミット</translation>
</message>
</context>
<context>
<name>CommitPropertyDialog</name>
<message>
<location filename="../../CommitPropertyDialog.ui" line="14"/>
<source>Commit Property</source>
<oldsource>Commit Properties</oldsource>
<translation>コミットのプロパティ</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="46"/>
<source>Date</source>
<translation>日付</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="56"/>
<source>Author</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="66"/>
<location filename="../../CommitPropertyDialog.ui" line="196"/>
<source>Mail</source>
<translation>メール</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="154"/>
<source>GPG Sign</source>
<translation>GPG署名</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="176"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="186"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="233"/>
<source>Commit ID</source>
<translation>コミットID</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="243"/>
<source>Parent IDs</source>
<translation>親ID</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="265"/>
<source>Files...</source>
<translation>ファイル...</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="285"/>
<source>Explorer</source>
<translation>エクスプローラ</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="305"/>
<source>Checkout</source>
<translation>チェックアウト</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="312"/>
<source>Jump</source>
<translation>移動</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="332"/>
<source>Close</source>
<translation>閉じる</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.cpp" line="60"/>
<source>&lt;Unknown&gt;</source>
<translation>&lt;不明&gt;</translation>
</message>
</context>
<context>
<name>CommitViewWindow</name>
<message>
<location filename="../../CommitViewWindow.ui" line="14"/>
<source>Commit View</source>
<translation>コミットビュー</translation>
</message>
<message>
<location filename="../../CommitViewWindow.cpp" line="58"/>
<source>History</source>
<translation>履歴</translation>
</message>
</context>
<context>
<name>ConfigCredentialHelperDialog</name>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="26"/>
<source>wincred</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="39"/>
<source>winstore</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="52"/>
<source>None</source>
<translation>なし</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="65"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="78"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="91"/>
<source>Other</source>
<translation>その他</translation>
</message>
</context>
<context>
<name>ConfigSigningDialog</name>
<message>
<location filename="../../ConfigSigningDialog.ui" line="14"/>
<source>Signing Policy</source>
<translation>署名ポリシー</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="20"/>
<source>Config commit.gpgsign</source>
<translation>commit.gpgsign の設定</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="32"/>
<source>global</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="42"/>
<source>local</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="70"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="77"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>CreateRepositoryDialog</name>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="14"/>
<location filename="../../CreateRepositoryDialog.cpp" line="42"/>
<location filename="../../CreateRepositoryDialog.cpp" line="46"/>
<location filename="../../CreateRepositoryDialog.cpp" line="50"/>
<location filename="../../CreateRepositoryDialog.cpp" line="54"/>
<source>Create Repository</source>
<translation>リポジトリの作成</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="22"/>
<source>Path</source>
<translation>パス</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="32"/>
<source>Browse</source>
<translation>参照</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="54"/>
<source>Bookmark</source>
<translation>ブックマーク</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="63"/>
<location filename="../../CreateRepositoryDialog.ui" line="88"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="76"/>
<source>Remote</source>
<translation>リモート</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="98"/>
<source>URL</source>
<translation></translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="108"/>
<source>Test</source>
<translation>テスト</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="146"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="153"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="19"/>
<source>A valid git repository already exists there.</source>
<translation>有効なリポジトリが既に存在しています。</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="42"/>
<location filename="../../CreateRepositoryDialog.cpp" line="50"/>
<source>The specified path is not a directory.</source>
<translation>指定されたパスはフォルダではありません。</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="54"/>
<source>Remote name is invalid.</source>
<translation>リモート名が無効です。</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="62"/>
<source>Destination Path</source>
<translation>作成先のパス</translation>
</message>
</context>
<context>
<name>DeleteBranchDialog</name>
<message>
<location filename="../../DeleteBranchDialog.ui" line="14"/>
<source>Delete Branch</source>
<translation>ブランチの削除</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="25"/>
<source>&amp;All branches</source>
<translation>全てのブランチ(&amp;A)</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="45"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="52"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.cpp" line="23"/>
<source>Delete Remote Branch</source>
<translation>リモートブランチの削除</translation>
</message>
</context>
<context>
<name>DeleteTagsDialog</name>
<message>
<location filename="../../DeleteTagsDialog.ui" line="14"/>
<source>Delete tags</source>
<translation>タグを削除</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="32"/>
<source>Check all</source>
<translation>全てチェック</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="52"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="62"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>DoYouWantToInitDialog</name>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="14"/>
<source>Init Repository</source>
<translation>リポジトリの初期化</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="20"/>
<source>The folder:</source>
<translation>フォルダ:</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="32"/>
<source>is not a valid git working copy.</source>
<translation>は、有効なGitリポジトリではありません。</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="39"/>
<source>Do you want to init it as a git repository ?</source>
<translation>これをGitリポジトリとして初期化しますか?</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="81"/>
<source>Yes, Please init it.</source>
<translation>はい、初期化します。</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="88"/>
<source>No, Stop it.</source>
<translation>いいえ、初期化しません。</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="140"/>
<source>Please choose option</source>
<translation>選択してください</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.cpp" line="20"/>
<source>Next...</source>
<translation>次へ...</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.cpp" line="26"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>EditGitIgnoreDialog</name>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="14"/>
<source>Edit Git Ignore</source>
<translation>Gitで無視するファイルの編集</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="20"/>
<location filename="../../EditGitIgnoreDialog.ui" line="27"/>
<location filename="../../EditGitIgnoreDialog.ui" line="34"/>
<location filename="../../EditGitIgnoreDialog.ui" line="41"/>
<source>RadioButton</source>
<translation></translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="63"/>
<source>Edit the file</source>
<translation>ファイルを編集する</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="83"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="90"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>EditRemoteDialog</name>
<message>
<location filename="../../EditRemoteDialog.ui" line="14"/>
<source>Edit Remote</source>
<translation>リモートの編集</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="20"/>
<source>Remote</source>
<translation>リモート</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="26"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="33"/>
<source>URL</source>
<translation></translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="53"/>
<source>&amp;Test</source>
<translation>テスト(&amp;T)</translation>
</message>
<message>
- <location filename="../../EditRemoteDialog.ui" line="81"/>
+ <location filename="../../EditRemoteDialog.ui" line="104"/>
<source>OK</source>
<translation></translation>
</message>
<message>
- <location filename="../../EditRemoteDialog.ui" line="88"/>
+ <location filename="../../EditRemoteDialog.ui" line="111"/>
<source>Close</source>
<translation>閉じる</translation>
</message>
</context>
<context>
<name>EditTagsDialog</name>
<message>
<location filename="../../EditTagsDialog.ui" line="14"/>
<source>Edit Tags</source>
<translation>タグの編集</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="41"/>
<source>Add...</source>
<translation>追加...</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="48"/>
<source>Delete</source>
<translation>削除</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="68"/>
<source>Close</source>
<translation>閉じる</translation>
</message>
</context>
<context>
<name>ExperimentDialog</name>
<message>
<location filename="../../ExperimentDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
</context>
<context>
<name>FileDiffWidget</name>
<message>
<location filename="../../FileDiffWidget.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
</context>
<context>
<name>FileHistoryWindow</name>
<message>
<location filename="../../FileHistoryWindow.ui" line="14"/>
<source>File History</source>
<translation>ファイルの履歴</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.ui" line="20"/>
<location filename="../../FileHistoryWindow.ui" line="27"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="111"/>
<source>Commit</source>
<translation>コミット</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="112"/>
<source>Date</source>
<translation>日付</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="113"/>
<source>Author</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="114"/>
<source>Message</source>
<translation>メッセージ</translation>
</message>
<message>
<source>Description</source>
<translation type="vanished">概要</translation>
</message>
</context>
<context>
<name>FilePropertyDialog</name>
<message>
<location filename="../../FilePropertyDialog.ui" line="14"/>
<source>File Property</source>
<oldsource>File Properties</oldsource>
<translation>ファイルのプロパティ</translation>
</message>
<message>
<location filename="../../FilePropertyDialog.ui" line="78"/>
<source>&amp;Close</source>
<translation>閉じる(&amp;C)</translation>
</message>
</context>
+<context>
+ <name>FileViewWidget</name>
+ <message>
+ <location filename="../../FileViewWidget.cpp" line="30"/>
+ <source>Form</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>FindCommitDialog</name>
<message>
<location filename="../../FindCommitDialog.ui" line="14"/>
<source>Find Commit</source>
<translation>コミットの検索</translation>
</message>
<message>
<location filename="../../FindCommitDialog.ui" line="54"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../FindCommitDialog.ui" line="61"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>InputNewTagDialog</name>
<message>
<location filename="../../InputNewTagDialog.ui" line="14"/>
<source>Edit tag</source>
<translation>タグの編集</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="31"/>
<source>Tag</source>
<translation>タグ</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="58"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="65"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>JumpDialog</name>
<message>
<location filename="../../JumpDialog.ui" line="14"/>
<source>Jump</source>
<translation>移動</translation>
</message>
<message>
<source>Branches and Tags</source>
<translation type="vanished">ブランチとタグ</translation>
</message>
<message>
<source>&amp;Filter</source>
<translation type="vanished">フィルタ(&amp;F)</translation>
</message>
<message>
<source>Find</source>
<translation type="vanished">検索</translation>
</message>
<message>
<source>&amp;Checkout</source>
<translation type="vanished">チェックアウト(&amp;C)</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="20"/>
<source>Branch, Tag or Commit-ID :</source>
<translation>ブランチ、タグ、コミットID</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="71"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="81"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../JumpDialog.cpp" line="39"/>
<source>Name</source>
<translation>名前</translation>
</message>
</context>
<context>
<name>Languages</name>
<message>
<location filename="../../Languages.cpp" line="6"/>
<source>English</source>
<translation></translation>
</message>
<message>
<location filename="../../Languages.cpp" line="7"/>
<source>Japanese</source>
<translation>日本語</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="8"/>
<source>Russian</source>
<translation>ロシア語</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="9"/>
<source>Chinese (Simplified)</source>
<translation>中国語(簡体字)</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="10"/>
<source>Chinese (Traditional/Taiwan)</source>
<translation>中国語(繁体字/台湾)</translation>
</message>
</context>
<context>
<name>LineEditDialog</name>
<message>
<location filename="../../LineEditDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="20"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="45"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="52"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../MainWindow.ui" line="14"/>
<source>Guitar</source>
<translation></translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="96"/>
- <location filename="../../MainWindow.ui" line="1421"/>
- <location filename="../../MainWindow.ui" line="1424"/>
+ <location filename="../../MainWindow.ui" line="1416"/>
+ <location filename="../../MainWindow.ui" line="1419"/>
<source>Clone</source>
<translation>クローン</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="131"/>
- <location filename="../../MainWindow.cpp" line="1140"/>
- <location filename="../../MainWindow.cpp" line="1141"/>
+ <location filename="../../MainWindow.cpp" line="1135"/>
+ <location filename="../../MainWindow.cpp" line="1136"/>
<source>Fetch</source>
<translation>フェッチ</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="166"/>
<source>Pull</source>
<translation>プル</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="201"/>
<source>Push</source>
<translation>プッシュ</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="303"/>
- <location filename="../../MainWindow.ui" line="1548"/>
+ <location filename="../../MainWindow.ui" line="1543"/>
<source>Terminal</source>
<translation>端末</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="338"/>
- <location filename="../../MainWindow.ui" line="1557"/>
+ <location filename="../../MainWindow.ui" line="1552"/>
<source>Explorer</source>
<translation>エクスプローラ</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="424"/>
<source>Repository</source>
<translation>リポジトリ</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="431"/>
<source>Branch Name</source>
<translation>ブランチ名</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="483"/>
- <location filename="../../MainWindow.ui" line="1592"/>
+ <location filename="../../MainWindow.ui" line="1587"/>
<source>Offline</source>
<translation>オフライン</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="473"/>
- <location filename="../../MainWindow.ui" line="1587"/>
+ <location filename="../../MainWindow.ui" line="1582"/>
<source>Online</source>
<translation>オンライン</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="799"/>
- <location filename="../../MainWindow.cpp" line="1853"/>
+ <location filename="../../MainWindow.cpp" line="1848"/>
<source>Unstage</source>
<translation>除外</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="828"/>
<source>Select all</source>
<translation>全て選択</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="863"/>
- <location filename="../../MainWindow.cpp" line="1723"/>
+ <location filename="../../MainWindow.cpp" line="1718"/>
<source>Stage</source>
<translation>追加</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="892"/>
- <location filename="../../MainWindow.cpp" line="879"/>
+ <location filename="../../MainWindow.cpp" line="874"/>
<source>Commit</source>
<translation>コミット</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="1023"/>
<source>&amp;File</source>
<translation>ファイル(&amp;F)</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="1034"/>
<source>&amp;View</source>
<translation>表示(&amp;V)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1040"/>
+ <location filename="../../MainWindow.ui" line="1041"/>
<source>&amp;Edit</source>
<translation>編集(&amp;E)</translation>
</message>
<message>
<source>Destructive</source>
<translation type="vanished">注意を要するコマンド</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1412"/>
+ <location filename="../../MainWindow.ui" line="1407"/>
<source>Settings</source>
<translation>設定</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1434"/>
- <location filename="../../MainWindow.cpp" line="1595"/>
+ <location filename="../../MainWindow.ui" line="1429"/>
+ <location filename="../../MainWindow.cpp" line="1590"/>
<source>Edit tags...</source>
<translation>タグの編集...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1437"/>
+ <location filename="../../MainWindow.ui" line="1432"/>
<source>Edit tags</source>
<translation>タグの編集</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1562"/>
+ <location filename="../../MainWindow.ui" line="1557"/>
<source>Clean -df</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1567"/>
+ <location filename="../../MainWindow.ui" line="1562"/>
<source>Reset --hard</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1494"/>
+ <location filename="../../MainWindow.ui" line="1489"/>
<source>Create a repository</source>
<translation>リポジトリの作成</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1481"/>
+ <location filename="../../MainWindow.ui" line="1476"/>
<source>Push upstream</source>
<translation>プッシュ upstream</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1503"/>
+ <location filename="../../MainWindow.ui" line="1498"/>
<source>Stop process</source>
<translation>処理の停止</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1508"/>
+ <location filename="../../MainWindow.ui" line="1503"/>
<source>E&amp;xit</source>
<translation>終了(&amp;X)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1511"/>
+ <location filename="../../MainWindow.ui" line="1506"/>
<source>Ctrl+Q</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1516"/>
+ <location filename="../../MainWindow.ui" line="1511"/>
<source>Reflog...</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1521"/>
+ <location filename="../../MainWindow.ui" line="1516"/>
<source>Property...</source>
<translation>プロパティ...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1526"/>
- <location filename="../../MainWindow.ui" line="1529"/>
+ <location filename="../../MainWindow.ui" line="1521"/>
+ <location filename="../../MainWindow.ui" line="1524"/>
<source>Set GPG signing</source>
<translation>GPG署名の指定</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1539"/>
+ <location filename="../../MainWindow.ui" line="1534"/>
<source>Fetch --prune</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1055"/>
+ <location filename="../../MainWindow.ui" line="1056"/>
<source>&amp;Help</source>
<translation>ヘルプ(&amp;H)</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="252"/>
- <location filename="../../MainWindow.ui" line="1628"/>
- <location filename="../../MainWindow.cpp" line="1353"/>
+ <location filename="../../MainWindow.ui" line="1623"/>
+ <location filename="../../MainWindow.cpp" line="1348"/>
<source>Status</source>
<translation>ステータス</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="656"/>
<source>...</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1061"/>
+ <location filename="../../MainWindow.ui" line="1062"/>
<source>&amp;Window</source>
<translation>ウィンドウ(&amp;W)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1067"/>
+ <location filename="../../MainWindow.ui" line="1073"/>
<source>&amp;Repository</source>
<translation>リポジトリ(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1071"/>
- <location filename="../../MainWindow.ui" line="1572"/>
+ <location filename="../../MainWindow.ui" line="1077"/>
+ <location filename="../../MainWindow.ui" line="1567"/>
<source>Stash</source>
<translation>素タッシュ</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1098"/>
+ <location filename="../../MainWindow.ui" line="1104"/>
<source>Re&amp;mote</source>
<translation>リモート(&amp;M)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1113"/>
+ <location filename="../../MainWindow.ui" line="1119"/>
<source>&amp;Destructive</source>
<translation>要注意(&amp;D)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1125"/>
- <source>Experimental</source>
- <translation></translation>
- </message>
- <message>
- <location filename="../../MainWindow.ui" line="1155"/>
+ <location filename="../../MainWindow.ui" line="1150"/>
<source>Log</source>
<translation>ログ</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1325"/>
+ <location filename="../../MainWindow.ui" line="1320"/>
<source>&amp;Open existing working copy...</source>
<translation>既存の作業コピーを開く(&amp;O)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1328"/>
- <location filename="../../MainWindow.cpp" line="2027"/>
+ <location filename="../../MainWindow.ui" line="1323"/>
+ <location filename="../../MainWindow.cpp" line="2022"/>
<source>Add existing working copy</source>
<translation>既存の作業コピーを追加</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1333"/>
- <location filename="../../MainWindow.ui" line="1336"/>
+ <location filename="../../MainWindow.ui" line="1328"/>
+ <location filename="../../MainWindow.ui" line="1331"/>
<source>Refresh</source>
<translation>更新</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1339"/>
+ <location filename="../../MainWindow.ui" line="1334"/>
<source>F5</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1348"/>
+ <location filename="../../MainWindow.ui" line="1343"/>
<source>&amp;Commit</source>
<translation>コミット(&amp;C)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1357"/>
+ <location filename="../../MainWindow.ui" line="1352"/>
<source>&amp;Push</source>
<translation>プッシュ(&amp;P)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1365"/>
+ <location filename="../../MainWindow.ui" line="1360"/>
<source>test</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1368"/>
+ <location filename="../../MainWindow.ui" line="1363"/>
<source>Ctrl+T</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1377"/>
+ <location filename="../../MainWindow.ui" line="1372"/>
<source>Pu&amp;ll</source>
<translation>プル(&amp;L)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1386"/>
+ <location filename="../../MainWindow.ui" line="1381"/>
<source>&amp;Fetch</source>
<translation>フェッチ(&amp;F)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1391"/>
- <location filename="../../MainWindow.ui" line="1394"/>
+ <location filename="../../MainWindow.ui" line="1386"/>
+ <location filename="../../MainWindow.ui" line="1389"/>
<source>Edit global .gitconfig</source>
<translation>グローバル .gitignore を編集</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1399"/>
+ <location filename="../../MainWindow.ui" line="1394"/>
<source>Edit .git/config</source>
<translation>.git/config を編集</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1404"/>
+ <location filename="../../MainWindow.ui" line="1399"/>
<source>Edit .gitignore</source>
<translation>.gitignore を編集</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1409"/>
+ <location filename="../../MainWindow.ui" line="1404"/>
<source>&amp;Settings...</source>
<translation>設定(&amp;S)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1458"/>
+ <location filename="../../MainWindow.ui" line="1453"/>
<source>F4</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1463"/>
+ <location filename="../../MainWindow.ui" line="1458"/>
<source>&amp;Jump...</source>
<translation>移動(&amp;J)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1466"/>
+ <location filename="../../MainWindow.ui" line="1461"/>
<source>Ctrl+J</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1471"/>
+ <location filename="../../MainWindow.ui" line="1466"/>
<source>Check&amp;out...</source>
<translation>チェックアウト(&amp;O)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1476"/>
- <location filename="../../MainWindow.cpp" line="1600"/>
+ <location filename="../../MainWindow.ui" line="1471"/>
+ <location filename="../../MainWindow.cpp" line="1595"/>
<source>Delete branch...</source>
<translation>ブランチの削除...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1577"/>
+ <location filename="../../MainWindow.ui" line="1572"/>
<source>Apply</source>
<translation>適用</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1582"/>
+ <location filename="../../MainWindow.ui" line="1577"/>
<source>Drop</source>
<translation>破棄</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1600"/>
+ <location filename="../../MainWindow.ui" line="1595"/>
<source>Repositories panel</source>
<translation>リポジトリパネル</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1603"/>
+ <location filename="../../MainWindow.ui" line="1598"/>
<source>Ctrl+R</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1608"/>
+ <location filename="../../MainWindow.ui" line="1603"/>
<source>&amp;Find...</source>
<translation>検索(&amp;F)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1611"/>
+ <location filename="../../MainWindow.ui" line="1606"/>
<source>Ctrl+F</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1616"/>
+ <location filename="../../MainWindow.ui" line="1611"/>
<source>Find next</source>
<translation>次を検索</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1619"/>
+ <location filename="../../MainWindow.ui" line="1614"/>
<source>F3</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1633"/>
+ <location filename="../../MainWindow.ui" line="1628"/>
<source>Jump to &amp;HEAD</source>
<translation>HEADへ移動(&amp;H)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1636"/>
+ <location filename="../../MainWindow.ui" line="1631"/>
<source>Ctrl+H</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1641"/>
+ <location filename="../../MainWindow.ui" line="1636"/>
<source>Merge...</source>
<translation>マージ...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1646"/>
+ <location filename="../../MainWindow.ui" line="1641"/>
<source>Expand commit log</source>
<translation>コミットログを展開</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1649"/>
+ <location filename="../../MainWindow.ui" line="1644"/>
<source>Ctrl+2</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1654"/>
+ <location filename="../../MainWindow.ui" line="1649"/>
<source>Expand file list</source>
<translation>ファイルリストを展開</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1657"/>
+ <location filename="../../MainWindow.ui" line="1652"/>
<source>Ctrl+3</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1662"/>
+ <location filename="../../MainWindow.ui" line="1657"/>
<source>Expand diff view</source>
<translation>差分ビューを展開</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1665"/>
+ <location filename="../../MainWindow.ui" line="1660"/>
<source>Ctrl+4</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1670"/>
+ <location filename="../../MainWindow.ui" line="1668"/>
<source>Sidebar</source>
<translation>サイドバー</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1673"/>
- <source>F1</source>
- <translation></translation>
+ <location filename="../../MainWindow.ui" line="1671"/>
+ <source>Ctrl+1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../MainWindow.ui" line="1687"/>
+ <source>Show labels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../MainWindow.ui" line="1690"/>
+ <source>Ctrl+L</source>
+ <translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1678"/>
+ <location filename="../../MainWindow.ui" line="1676"/>
<source>Wide</source>
<translation>広げる</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1681"/>
+ <location filename="../../MainWindow.ui" line="1679"/>
<source>F2</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1486"/>
- <location filename="../../MainWindow.ui" line="1489"/>
+ <location filename="../../MainWindow.ui" line="1481"/>
+ <location filename="../../MainWindow.ui" line="1484"/>
<source>Reset HEAD~1</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1534"/>
- <location filename="../../MainWindow.cpp" line="1601"/>
+ <location filename="../../MainWindow.ui" line="1529"/>
+ <location filename="../../MainWindow.cpp" line="1596"/>
<source>Delete remote branch...</source>
<translation>リモートブランチの削除...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1429"/>
+ <location filename="../../MainWindow.ui" line="1424"/>
<source>&amp;About</source>
<translation>Guitarについて(&amp;A)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1442"/>
+ <location filename="../../MainWindow.ui" line="1437"/>
<source>Push all tags</source>
<translation>全てのタグをプッシュ</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1447"/>
+ <location filename="../../MainWindow.ui" line="1442"/>
<source>Set config user</source>
<translation>ユーザー情報を設定</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1455"/>
+ <location filename="../../MainWindow.ui" line="1450"/>
<source>&amp;Log</source>
<translation>ログ(&amp;L)</translation>
</message>
<message>
<source>Unnamed</source>
<translation type="vanished">無名</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="679"/>
+ <location filename="../../MainWindow.cpp" line="674"/>
<source>Default</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="878"/>
+ <location filename="../../MainWindow.cpp" line="873"/>
<source>Graph</source>
<translation>樹形図</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="880"/>
+ <location filename="../../MainWindow.cpp" line="875"/>
<source>Date</source>
<translation>日付</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="881"/>
+ <location filename="../../MainWindow.cpp" line="876"/>
<source>Author</source>
<translation>名前</translation>
</message>
<message>
<source>Description</source>
<translation type="vanished">概要</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1029"/>
- <location filename="../../MainWindow.cpp" line="1199"/>
+ <location filename="../../MainWindow.cpp" line="1024"/>
+ <location filename="../../MainWindow.cpp" line="1194"/>
<source>Uncommited changes</source>
<translation>コミットされていない変更があります</translation>
</message>
<message>
<source>Are you sure you want to remove the repository from bookmarks ?</source>
<translation type="vanished">リポジトリをブックマークから削除してよろしいですか?</translation>
</message>
<message>
<source>(Files will NOT be deleted)</source>
<translation type="vanished">(ファルは削除されません)</translation>
</message>
<message>
<source>Open Repository</source>
<translation type="vanished">リポジトリを開く</translation>
</message>
<message>
<source>No such folder</source>
<translation type="vanished">そのようなフォルダはありません</translation>
</message>
<message>
<source>Not a valid git repository</source>
<translation type="vanished">有効なリポジトリではありません</translation>
</message>
<message>
<source>Commit message can not be omitted.</source>
<translation type="vanished">コミットメッセージを空にすることはできません。</translation>
</message>
<message>
<source>Repository Property</source>
<translation type="vanished">リポジトリのプロパティ</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1457"/>
+ <location filename="../../MainWindow.cpp" line="1452"/>
<source>&amp;Add new group</source>
<translation>新しいグループを追加(&amp;A)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1458"/>
+ <location filename="../../MainWindow.cpp" line="1453"/>
<source>&amp;Delete group</source>
<translation>グループを削除(&amp;D)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1459"/>
+ <location filename="../../MainWindow.cpp" line="1454"/>
<source>&amp;Rename group</source>
<translation>グループ名の変更(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1464"/>
+ <location filename="../../MainWindow.cpp" line="1459"/>
<source>New group</source>
<translation>新しいグループ</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1488"/>
+ <location filename="../../MainWindow.cpp" line="1483"/>
<source>Open &amp;terminal</source>
<translation>端末を開く(&amp;T)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1489"/>
+ <location filename="../../MainWindow.cpp" line="1484"/>
<source>Open command promp&amp;t</source>
<translation>コマンドプロンプトを開く(&amp;T)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1491"/>
+ <location filename="../../MainWindow.cpp" line="1486"/>
<source>&amp;Open</source>
<translation>開く(&amp;O)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1502"/>
+ <location filename="../../MainWindow.cpp" line="1497"/>
<source>Open &amp;folder</source>
<translation>フォルダを開く(&amp;F)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1507"/>
+ <location filename="../../MainWindow.cpp" line="1502"/>
<source>&amp;Remove</source>
<translation>削除(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1550"/>
+ <location filename="../../MainWindow.cpp" line="1545"/>
<source>Copy commit id (7 letters)</source>
<translation>コミットIDをコピー(7文字)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1551"/>
+ <location filename="../../MainWindow.cpp" line="1546"/>
<source>Copy commit id (completely)</source>
<translation>コミットIDをコピー(すべて)</translation>
</message>
<message>
<source>Edit comment...</source>
<translation type="vanished">コメントを編集...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1242"/>
- <location filename="../../MainWindow.cpp" line="1593"/>
+ <location filename="../../MainWindow.cpp" line="1237"/>
+ <location filename="../../MainWindow.cpp" line="1588"/>
<source>Rebase</source>
<translation>リベース</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1605"/>
+ <location filename="../../MainWindow.cpp" line="1600"/>
<source>Explore</source>
<translation>探索</translation>
</message>
<message>
<source>Reset HEAD</source>
<translation type="vanished">HEADをリセット</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="882"/>
+ <location filename="../../MainWindow.cpp" line="877"/>
<source>Message</source>
<translation>メッセージ</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1679"/>
- <location filename="../../MainWindow.cpp" line="1728"/>
- <location filename="../../MainWindow.cpp" line="1854"/>
+ <location filename="../../MainWindow.cpp" line="1674"/>
+ <location filename="../../MainWindow.cpp" line="1723"/>
+ <location filename="../../MainWindow.cpp" line="1849"/>
<source>History</source>
<translation>履歴</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2436"/>
- <location filename="../../MainWindow.cpp" line="2450"/>
+ <location filename="../../MainWindow.cpp" line="2431"/>
+ <location filename="../../MainWindow.cpp" line="2445"/>
<source>No such commit</source>
<translation>そのようなコミットはありません</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1143"/>
- <location filename="../../MainWindow.cpp" line="1144"/>
+ <location filename="../../MainWindow.cpp" line="1138"/>
+ <location filename="../../MainWindow.cpp" line="1139"/>
<source>Update</source>
<translation>更新</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2762"/>
+ <location filename="../../MainWindow.cpp" line="2757"/>
<source>Authentication Failed</source>
<translation>認証が失敗しました</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1596"/>
+ <location filename="../../MainWindow.cpp" line="1591"/>
<source>Revert</source>
<translation>変更を破棄する</translation>
</message>
<message>
<source>Failed to commit</source>
<translation type="vanished">コミット失敗</translation>
</message>
<message>
<source>No remote repository is registered.</source>
<translation type="vanished">リモートリポジトリが登録されていません。</translation>
</message>
<message>
<source>The current branch %1 has no upstream branch.</source>
<translation type="vanished">現在のブランチ「%1」には上流ブランチがありません。</translation>
</message>
<message>
<source>You try push --set-upstream</source>
<translation type="vanished">--set-upstream を試してください</translation>
</message>
<message>
<source>Connection refused.</source>
<translation type="vanished">接続が拒否されました。</translation>
</message>
<message>
<source>&amp;Property</source>
<translation type="vanished">プロパティ(&amp;P)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1567"/>
+ <location filename="../../MainWindow.cpp" line="1562"/>
<source>Checkout/Branch...</source>
<translation>チェックアウト/ブランチ...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1589"/>
+ <location filename="../../MainWindow.cpp" line="1584"/>
<source>Edit message...</source>
<translation>メッセージの編集...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1592"/>
+ <location filename="../../MainWindow.cpp" line="1587"/>
<source>Merge</source>
<translation>マージ</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1594"/>
+ <location filename="../../MainWindow.cpp" line="1589"/>
<source>Cherry-pick</source>
<translation>チェリーピック</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1678"/>
- <location filename="../../MainWindow.cpp" line="1727"/>
+ <location filename="../../MainWindow.cpp" line="1673"/>
+ <location filename="../../MainWindow.cpp" line="1722"/>
<source>Untrack</source>
<translation>追跡しない</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1680"/>
- <location filename="../../MainWindow.cpp" line="1729"/>
- <location filename="../../MainWindow.cpp" line="1855"/>
+ <location filename="../../MainWindow.cpp" line="1675"/>
+ <location filename="../../MainWindow.cpp" line="1724"/>
+ <location filename="../../MainWindow.cpp" line="1850"/>
<source>Blame</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1688"/>
+ <location filename="../../MainWindow.cpp" line="1683"/>
<source>Delete selected files.</source>
<translation>選択されたファイルを削除します。</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1699"/>
+ <location filename="../../MainWindow.cpp" line="1694"/>
<source>rm --cached files</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1724"/>
+ <location filename="../../MainWindow.cpp" line="1719"/>
<source>Reset</source>
<translation>リセット</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1725"/>
+ <location filename="../../MainWindow.cpp" line="1720"/>
<source>Ignore</source>
<translation>無視する</translation>
</message>
<message>
<source>Reset a file</source>
<translation type="vanished">ファイルをリセットします</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1239"/>
+ <location filename="../../MainWindow.cpp" line="1234"/>
<source>Are you sure you want to rebase the commit ?</source>
<translation>コミットをリベースしてよろしいですか?</translation>
</message>
<message>
<source>No repository selected</source>
<translation type="vanished">リポジトリが選択されていません</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1677"/>
- <location filename="../../MainWindow.cpp" line="1726"/>
+ <location filename="../../MainWindow.cpp" line="1672"/>
+ <location filename="../../MainWindow.cpp" line="1721"/>
<source>Delete</source>
<translation>削除</translation>
</message>
<message>
<source>Are you sure you want to run the following command ?</source>
<translation type="vanished">次のコマンドを実行してよろしいですか?</translation>
</message>
<message>
<source>Revert all files</source>
<translation type="vanished">すべてのファイルの変更を破棄</translation>
</message>
<message>
<source>Select %1 command</source>
<translation type="vanished">%1 コマンドの選択</translation>
</message>
<message>
<source>A file with same name already exists</source>
<translation type="vanished">同じ名前のファイルが既に存在しています</translation>
</message>
<message>
<source>A folder with same name already exists</source>
<translation type="vanished">同じ名前のフォルダが既に存在しています</translation>
</message>
<message>
<source>Invalid folder</source>
<translation type="vanished">無効なフォルダ</translation>
</message>
<message>
<source>No such folder. Create it now ?</source>
<translation type="vanished">このフォルダはありません。作成しますか?</translation>
</message>
<message>
<source>The URL is a valid repository</source>
<translation type="vanished">このURLは有効なリポジトリです</translation>
</message>
<message>
<source>Failed to access the URL</source>
<translation type="vanished">このURLへのアクセスに失敗しました</translation>
</message>
<message>
<source>Remote Repository</source>
<translation type="vanished">リモートリポジトリ</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2436"/>
- <location filename="../../MainWindow.cpp" line="2450"/>
+ <location filename="../../MainWindow.cpp" line="2431"/>
+ <location filename="../../MainWindow.cpp" line="2445"/>
<source>Jump</source>
<translation>移動</translation>
</message>
<message>
<source>That commmit has not foud or not read yet</source>
<translation type="vanished">そのコミットは、見つからないか、まだ読み込まれていません</translation>
</message>
<message>
<source>Failed to delete the branch &apos;%1&apos;
</source>
<translation type="vanished">ブランチの削除に失敗しました : &apos;%1&apos;</translation>
</message>
</context>
<context>
<name>MergeBranchDialog</name>
<message>
<source>Merge</source>
<translation type="vanished">マージ</translation>
</message>
<message>
<source>Current branch :</source>
<translation type="vanished">現在のブランチ :</translation>
</message>
<message>
<source>Cancel</source>
<translation type="vanished">キャンセル</translation>
</message>
</context>
<context>
<name>MergeDialog</name>
<message>
<location filename="../../MergeDialog.ui" line="14"/>
<source>Merge</source>
<translation>マージ</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="20"/>
<source>Fast Forwarding</source>
<translation>ファストフォワーディング</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="26"/>
<source>Default (--ff)</source>
<translation>規定 (--ff)</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="33"/>
<source>No fast forward (--no-ff)</source>
<translation>ファストフォワードしない (--no-ff)</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="40"/>
<source>Fast forward only (--ff-only)</source>
<translation>ファストフォワードのみ (--ff-only)</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="50"/>
<source>From</source>
<translation>元</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="77"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="84"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>MyImageViewWidget</name>
<message>
<location filename="../../MyImageViewWidget.cpp" line="34"/>
<source>Save as...</source>
<translation>名前を付けて保存...</translation>
</message>
<message>
<location filename="../../MyImageViewWidget.cpp" line="42"/>
<source>Save as</source>
<translation>名前を付けて保存</translation>
</message>
</context>
<context>
<name>MyTextEditorWidget</name>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="35"/>
<source>Save as...</source>
<translation>名前を付けて保存...</translation>
</message>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="36"/>
<source>Copy</source>
<translation>コピー</translation>
</message>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="43"/>
<source>Save as</source>
<translation>名前を付けて保存</translation>
</message>
</context>
<context>
<name>ObjectBrowserDialog</name>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="14"/>
<source>Object Browser</source>
<translation>オブジェクトブラウザ</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="38"/>
<source>Inspect</source>
<translation>調査</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="58"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="65"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="23"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="24"/>
<source>Type</source>
<translation>種類</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="119"/>
<source>Object Inspection</source>
<translation>オブジェクトの調査</translation>
</message>
</context>
<context>
<name>PushDialog</name>
<message>
<location filename="../../PushDialog.ui" line="14"/>
<source>Push</source>
<translation>プッシュ</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="20"/>
<source>push --set-upstream</source>
<translation></translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="26"/>
<source>Remote</source>
<translation>リモート</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="33"/>
<source>Branch</source>
<translation>ブランチ</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="64"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="71"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>RebaseOntoDialog</name>
<message>
<source>Branch</source>
<translation type="vanished">ブランチ</translation>
</message>
<message>
<source>Cancel</source>
<translation type="vanished">キャンセル</translation>
</message>
</context>
<context>
<name>ReflogWindow</name>
<message>
<location filename="../../ReflogWindow.ui" line="14"/>
<source>Reflog</source>
<translation></translation>
</message>
<message>
<location filename="../../ReflogWindow.ui" line="57"/>
<source>Close</source>
<translation>閉じる</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="35"/>
<source>Commit</source>
<translation>コミット</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="36"/>
<source>Head</source>
<translation>ヘッド</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="37"/>
<source>Command</source>
<translation>コマンド</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="38"/>
<source>Message</source>
<translation>メッセージ</translation>
</message>
<message>
<source>Comment</source>
<translation type="vanished">コメント</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="98"/>
<source>Checkout</source>
<translation>チェックアウト</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="99"/>
<source>Explorer</source>
<translation>エクスプローラ</translation>
</message>
</context>
+<context>
+ <name>RemoteAdvancedOptionWidget</name>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="32"/>
+ <source>Advanced Option</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="38"/>
+ <source>SSH key override...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="45"/>
+ <source>Clear</source>
+ <translation type="unfinished">消去</translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.cpp" line="24"/>
+ <source>SSH command is not registered.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.cpp" line="30"/>
+ <source>SSH key override</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>RemoteRepositoriesTableWidget</name>
<message>
<location filename="../../RemoteRepositoriesTableWidget.cpp" line="27"/>
<source>Copy URL</source>
<translation>URLをコピー</translation>
</message>
</context>
<context>
<name>RepositoryPropertyDialog</name>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="14"/>
<source>Repository Property</source>
<oldsource>Repository Properties</oldsource>
<translation>リポジトリのプロパティ</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="65"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="95"/>
<source>Local dir :</source>
<translation>ローカルフォルダ :</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="114"/>
<source>Remote URLs</source>
<translation>リモートURL</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="124"/>
<source>Remote</source>
<translation>リモート</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="130"/>
<source>Add</source>
<translation>追加</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="137"/>
<source>Edit</source>
<translation>編集</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="157"/>
<source>Remove</source>
<translation>削除</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="169"/>
<source>&amp;Remote menu</source>
<translation>リモートメニュー(&amp;R)</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="189"/>
<source>Close</source>
<translation>閉じる</translation>
</message>
<message>
- <location filename="../../RepositoryPropertyDialog.cpp" line="131"/>
+ <location filename="../../RepositoryPropertyDialog.cpp" line="155"/>
<source>Confirm Remove</source>
<translation>削除の確認</translation>
</message>
<message>
- <location filename="../../RepositoryPropertyDialog.cpp" line="131"/>
+ <location filename="../../RepositoryPropertyDialog.cpp" line="155"/>
<source>Are you sure you want to remove the remote &apos;%1&apos; from the repository &apos;%2&apos; ?</source>
<translation>リポジトリ「%2」からリモート「%1」を削除してよろしいですか?</translation>
</message>
</context>
<context>
<name>SearchFromGitHubDialog</name>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="14"/>
<source>Search From GitHub</source>
<translation>GitHubから検索</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="25"/>
<source>Search</source>
<translation>検索</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="78"/>
<source>ssh</source>
<translation></translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="88"/>
<source>http</source>
<translation></translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="115"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="122"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="56"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="57"/>
<source>Owner</source>
<translation>オーナー</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="58"/>
<source>Score</source>
<translation>スコア</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="59"/>
<source>Description</source>
<translation>概要</translation>
</message>
</context>
<context>
<name>SelectCommandDialog</name>
<message>
<location filename="../../SelectCommandDialog.ui" line="14"/>
<source>Select git command</source>
<translation>git コマンドの選択</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="20"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="32"/>
<source>&amp;Browse...</source>
<translation>参照(&amp;B)...</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="52"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="59"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="29"/>
<source>Please select the &apos;%1&apos; command you want to use.</source>
<translation>使用したい &apos;%1&apos; コマンドを選択してください。</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="58"/>
<location filename="../../SelectCommandDialog.cpp" line="65"/>
<source>%1 command (%2);;</source>
<translation>%1 コマンド (%2);;</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="60"/>
<source>Executable files (*.exe)</source>
<translation>実行可能ファイル (*.exe)</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="71"/>
<source>%1 command</source>
<translation>%1 コマンド</translation>
</message>
</context>
<context>
<name>SelectGpgKeyDialog</name>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="14"/>
<source>Select GPG Key</source>
<translation>GPGキーの選択</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="57"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="64"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="31"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="32"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="33"/>
<source>Mail</source>
<translation>メール</translation>
</message>
</context>
<context>
<name>SelectItemDialog</name>
<message>
<location filename="../../SelectItemDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectItemDialog.ui" line="25"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectItemDialog.ui" line="32"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>SetGlobalUserDialog</name>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="14"/>
<source>Global User Setting</source>
<translation>グローバルユーザー設定</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="31"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="41"/>
<source>Mail</source>
<translation>メール</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="68"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="78"/>
<source>Skip</source>
<translation>スキップ</translation>
</message>
</context>
<context>
<name>SetGpgSigningDialog</name>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="14"/>
<source>Set GPG Signing</source>
<translation>GPG署名の指定</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="26"/>
<source>Global</source>
<translation>グローバル</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="39"/>
<location filename="../../SetGpgSigningDialog.cpp" line="29"/>
<source>Repository</source>
<translation>リポジトリ</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="61"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="71"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="81"/>
<source>Mail</source>
<translation>メール</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="93"/>
<source>Select</source>
<translation>選択</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="113"/>
<source>Clear</source>
<translation>消去</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="127"/>
<source>Configure...</source>
<translation>設定...</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="147"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="154"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>SetRemoteUrlDialog</name>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="14"/>
<source>Set Remote URL</source>
<translation>リモートURLの設定</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="20"/>
<source>Current URLs</source>
<translation>現在のURL</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="30"/>
<source>New URL</source>
<translation>新しいURL</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="36"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="91"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="98"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="43"/>
<source>URL</source>
<translation></translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="63"/>
<source>&amp;Test</source>
<translation>テスト(&amp;T)</translation>
</message>
</context>
<context>
<name>SetUserDialog</name>
<message>
<location filename="../../SetUserDialog.ui" line="14"/>
<source>Set User</source>
<translation>ユーザーの設定</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="20"/>
<source>Global</source>
<translation>グローバル</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="27"/>
<location filename="../../SetUserDialog.cpp" line="31"/>
<source>Repository</source>
<translation>リポジトリ</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="42"/>
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="52"/>
<source>Mail</source>
<translation>メール</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="66"/>
<source>Get icon from Gravatar</source>
<translation>Gravatarからアイコンを取得</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="122"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="132"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>SettingBehaviorForm</name>
<message>
<location filename="../../SettingBehaviorForm.ui" line="14"/>
<source>Behavior</source>
<translation>動作</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="20"/>
<location filename="../../SettingBehaviorForm.cpp" line="43"/>
<source>Default working folder</source>
<translation>既定の作業フォルダ</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="29"/>
<source>Browse...</source>
<translation>参照...</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="39"/>
<source>Automatically fetch when opening the repository</source>
<translation>リポジトリを開くとき自動的にフェッチする</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="46"/>
<source>Get committer&apos;s icon from gravatar.com</source>
<translation>gravatar.comからアイコンを取得する</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="61"/>
<source>Maximum number of commit item acquisitions</source>
<translation>取得するコミット情報の最大個数</translation>
</message>
<message>
<source>Watch remote changes periodically</source>
<oldsource>Periodically watch remote updates</oldsource>
<translation type="vanished">定期的リモートの変更を監視する</translation>
</message>
<message>
<source>interval</source>
<oldsource>interval in min:</oldsource>
<translation type="vanished">間隔</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="94"/>
<source>GPG signing policy</source>
<oldsource>GPG Signing Policy</oldsource>
<translation>GPG署名ポリシー</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="101"/>
<source>Configure...</source>
<translation>設定...</translation>
</message>
<message>
<source>Disable</source>
<translation type="vanished">無効</translation>
</message>
<message>
<source>1 min</source>
<translation type="vanished">1分</translation>
</message>
<message>
<source>%1 mins</source>
<translation type="vanished">%1分</translation>
</message>
</context>
<context>
<name>SettingExampleForm</name>
<message>
<location filename="../../SettingExampleForm.ui" line="14"/>
<source>Example</source>
<translation></translation>
</message>
<message>
<location filename="../../SettingExampleForm.ui" line="20"/>
<source>Underconstruction</source>
<translation></translation>
</message>
</context>
<context>
<name>SettingGeneralForm</name>
<message>
<location filename="../../SettingGeneralForm.ui" line="14"/>
<source>General</source>
<translation>一般</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="20"/>
<source>Language</source>
<translation>言語</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="31"/>
<source>Change Language...</source>
<translation>言語の変更...</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="54"/>
<source>Change Theme...</source>
<translation>テーマの変更...</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="66"/>
<source>Remember and restore window position</source>
<translation>ウィンドウの位置を記憶し復元する</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="73"/>
<source>Enable high DPI scaling</source>
<translation>高精細画面のスケーリングを行う</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="43"/>
<source>Theme</source>
<translation>テーマ</translation>
</message>
<message>
<source>Japanese</source>
<translation type="vanished">日本語</translation>
</message>
<message>
<source>Russian</source>
<translation type="vanished">ロシア語</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="19"/>
<source>Standard</source>
<translation>標準</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="20"/>
<source>Dark</source>
<translation>ダーク</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="82"/>
<source>Select Language</source>
<translation>言語の選択</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="106"/>
<source>Select Theme</source>
<translation>テーマの選択</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="93"/>
<source>(Changes are applied at next run)</source>
<translation>(設定は次の実行時に有効になります)</translation>
</message>
</context>
<context>
<name>SettingNetworkForm</name>
<message>
<location filename="../../SettingNetworkForm.ui" line="14"/>
<source>Network</source>
<translation>ネットワーク</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="20"/>
<source>Proxy server</source>
<oldsource>Proxy Server</oldsource>
<translation>プロキシサーバー</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="26"/>
<source>No proxy</source>
<oldsource>No Proxy</oldsource>
<translation>プロキシなし</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="33"/>
<source>Auto detect</source>
<oldsource>Auto Detect</oldsource>
<translation>自動検出</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="40"/>
<source>Manual</source>
<translation>手動</translation>
</message>
</context>
<context>
<name>SettingProgramsForm</name>
<message>
<location filename="../../SettingProgramsForm.ui" line="14"/>
<source>Programs</source>
<translation>プログラム</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="20"/>
<source>Git command</source>
<translation>Git コマンド</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="29"/>
<location filename="../../SettingProgramsForm.ui" line="48"/>
<location filename="../../SettingProgramsForm.ui" line="67"/>
+ <location filename="../../SettingProgramsForm.ui" line="86"/>
<source>Browse...</source>
<translation>参照...</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="39"/>
<source>File command</source>
<translation>File コマンド</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="58"/>
<source>GPG command (option)</source>
<translation>GPGコマンド(オプション)</translation>
</message>
+ <message>
+ <location filename="../../SettingProgramsForm.ui" line="77"/>
+ <source>SSH command (option)</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../../SettingsDialog.ui" line="14"/>
<source>Settings</source>
<translation>設定</translation>
</message>
<message>
<location filename="../../SettingsDialog.ui" line="141"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SettingsDialog.ui" line="148"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>TextEditDialog</name>
<message>
<location filename="../../TextEditDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../TextEditDialog.ui" line="48"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../TextEditDialog.ui" line="58"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../TextEditDialog.cpp" line="29"/>
<source>&amp;Close</source>
<translation>閉じる(&amp;C)</translation>
</message>
<message>
<location filename="../../TextEditDialog.cpp" line="29"/>
<source>Cacnel</source>
<translation>キャンセル</translation>
</message>
</context>
<context>
<name>WelcomeWizardDialog</name>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="14"/>
<source>Welcome to the Guitar Wizard</source>
<translation>Guitarウィザードへようこそ</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="45"/>
<source>Helper Tools</source>
<translation>ヘルパープログラム</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="61"/>
<location filename="../../WelcomeWizardDialog.ui" line="338"/>
<source>git</source>
<translation></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="71"/>
<location filename="../../WelcomeWizardDialog.ui" line="88"/>
<location filename="../../WelcomeWizardDialog.ui" line="248"/>
<source>Browse</source>
<translation>参照</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="78"/>
<location filename="../../WelcomeWizardDialog.ui" line="352"/>
<source>file</source>
<translation></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="120"/>
<source>Global User Information</source>
<translation>グローバルユーザー情報</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="142"/>
<source>git config --global user.name</source>
<translation></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="152"/>
<source>git config --global user.email</source>
<translation></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="168"/>
<source>Get icon from Gravatar</source>
<translation>Gravatarからアイコンを取得</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="222"/>
<location filename="../../WelcomeWizardDialog.cpp" line="163"/>
<source>Default Working Folder</source>
<translation>規定の作業フォルダ</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="238"/>
<location filename="../../WelcomeWizardDialog.ui" line="324"/>
<source>folder</source>
<translation>フォルダ</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="280"/>
<source>Ready to play the Guitar !</source>
<translation>Guitarの準備ができました!</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="296"/>
<source>user</source>
<translation>ユーザー</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="310"/>
<source>email</source>
<translation>メール</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="426"/>
<location filename="../../WelcomeWizardDialog.cpp" line="155"/>
<source>&lt;&lt; Prev</source>
<translation>&lt;&lt; 戻る</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="433"/>
<location filename="../../WelcomeWizardDialog.cpp" line="156"/>
<source>Next &gt;&gt;</source>
<translation>次へ &gt;&gt;</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.cpp" line="126"/>
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.cpp" line="152"/>
<source>Finish</source>
<translation>完了</translation>
</message>
</context>
</TS>
diff --git a/src/resources/translations/Guitar_ru.qm b/src/resources/translations/Guitar_ru.qm
index 9b31be9..e31c3b4 100644
Binary files a/src/resources/translations/Guitar_ru.qm and b/src/resources/translations/Guitar_ru.qm differ
diff --git a/src/resources/translations/Guitar_ru.ts b/src/resources/translations/Guitar_ru.ts
index 0f5fbcd..8de89c5 100644
--- a/src/resources/translations/Guitar_ru.ts
+++ b/src/resources/translations/Guitar_ru.ts
@@ -1,2982 +1,3037 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
<name>AboutDialog</name>
<message>
<location filename="../../AboutDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../AboutDialog.ui" line="34"/>
<location filename="../../AboutDialog.ui" line="46"/>
<location filename="../../AboutDialog.ui" line="58"/>
<location filename="../../AboutDialog.ui" line="70"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../AboutDialog.ui" line="86"/>
<source>&lt;a href=&quot;https://github.com/soramimi/Guitar&quot;&gt;https://github.com/soramimi/Guitar&lt;/a&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../AboutDialog.cpp" line="25"/>
<source>About %1</source>
<translation>О %1</translation>
</message>
</context>
<context>
<name>AreYouSureYouWantToContinueConnectingDialog</name>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="14"/>
<source>Unknown Host</source>
<translation>Узел не найден</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="20"/>
<source>Are you sure you want to continue connecting (yes/no)?</source>
<translation>Повторить попытку соединения? (Да/нет)?</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="52"/>
<source>Continue</source>
<translation>Да, продолжить</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="59"/>
<source>Close</source>
<translation>Нет, закрыть</translation>
</message>
</context>
<context>
<name>BasicMainWindow</name>
<message>
- <location filename="../../BasicMainWindow.cpp" line="261"/>
+ <location filename="../../BasicMainWindow.cpp" line="243"/>
<source>git command not specified</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="374"/>
+ <location filename="../../BasicMainWindow.cpp" line="358"/>
<source>The URL is a valid repository</source>
<translation>URL ссылается на корректный репозиторий</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="375"/>
+ <location filename="../../BasicMainWindow.cpp" line="359"/>
<source>Failed to access the URL</source>
<translation>Нет доступа по URL</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="380"/>
+ <location filename="../../BasicMainWindow.cpp" line="364"/>
<source>Remote Repository</source>
<translation>Репозиторий-истоник</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="405"/>
+ <location filename="../../BasicMainWindow.cpp" line="389"/>
<source>&amp;Property</source>
<translation>&amp;Свойства</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1273"/>
+ <location filename="../../BasicMainWindow.cpp" line="1272"/>
<source>Select %1 command</source>
<translation>Выбор %1 команды</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1452"/>
+ <location filename="../../BasicMainWindow.cpp" line="1447"/>
<source>Revert all files</source>
<translation>Отменить (Revert) изменения во всех файлах</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1610"/>
+ <location filename="../../BasicMainWindow.cpp" line="1605"/>
<source>The folder is not a valid git repository.</source>
<translation>Папка не является репозиторием Git.</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1614"/>
+ <location filename="../../BasicMainWindow.cpp" line="1609"/>
<source>Do you want to initialize it as a git repository ?</source>
<translation>Инициализировать репозиторий Git?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1615"/>
+ <location filename="../../BasicMainWindow.cpp" line="1610"/>
<source>Initialize Repository</source>
<translation>Инициализировать репозиторий</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1745"/>
+ <location filename="../../BasicMainWindow.cpp" line="1740"/>
<source>No repository selected</source>
<translation>Репозиторий не выбран</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1915"/>
<source>Repository Property</source>
- <translation>Свойства репозитория</translation>
+ <translation type="vanished">Свойства репозитория</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1915"/>
<location filename="../../BasicMainWindow.cpp" line="2017"/>
<source>Not a valid git repository</source>
<translation>Каталог не является репозиторием Git</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<location filename="../../BasicMainWindow.cpp" line="2017"/>
<source>Open Repository</source>
<translation>Открыть репозиторий</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<source>No such folder</source>
<translation>Каталог не существует</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<source>Remove from bookmark ?</source>
<translation>Удалить из закладок?</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2248"/>
<source>, %1 ahead</source>
<translation>, %1 впереди</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2251"/>
<source>, %1 behind</source>
<translation>, %1 позади</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>Confirm Remove</source>
<translation>Подтвердите удаление</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>Are you sure you want to remove the repository from bookmarks ?</source>
<translation>Вы точно хотите удалить репозиторий из закладок?</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>(Files will NOT be deleted)</source>
<translation>(Файлы НЕ будут удалены)</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2302"/>
<source>A file with same name already exists</source>
<translation>Существует файл с тем же именем</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2303"/>
<location filename="../../BasicMainWindow.cpp" line="2308"/>
<location filename="../../BasicMainWindow.cpp" line="2325"/>
<location filename="../../BasicMainWindow.cpp" line="2330"/>
<source>Clone</source>
<translation>Клонировать (Clone)</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2307"/>
<source>A folder with same name already exists</source>
<translation>Существует папка с тем же именем</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2324"/>
<source>Invalid folder</source>
<translation>Ошибочный каталог</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2329"/>
<source>No such folder. Create it now ?</source>
<translation>Каталог не существует. Создать?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2398"/>
+ <location filename="../../BasicMainWindow.cpp" line="2402"/>
<source>Commit</source>
<translation>Фиксировать (Commit)</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2398"/>
+ <location filename="../../BasicMainWindow.cpp" line="2402"/>
<source>Commit message can not be omitted.</source>
<translation>Комментарий к фиксации является обязательным.</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2415"/>
+ <location filename="../../BasicMainWindow.cpp" line="2419"/>
<source>Failed to commit</source>
<translation>Не удалось зафиксировать</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2449"/>
- <location filename="../../BasicMainWindow.cpp" line="2533"/>
+ <location filename="../../BasicMainWindow.cpp" line="2453"/>
+ <location filename="../../BasicMainWindow.cpp" line="2538"/>
<source>Connection refused.</source>
<translation>Отказано в соединении.</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2502"/>
+ <location filename="../../BasicMainWindow.cpp" line="2506"/>
<source>No remote repository is registered.</source>
<translation>Репозиторий источника не зарегистрирован.</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2524"/>
+ <location filename="../../BasicMainWindow.cpp" line="2529"/>
<source>The current branch %1 has no upstream branch.</source>
<translation>Текущая ветвь %1 не содержится в источнике.</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2527"/>
+ <location filename="../../BasicMainWindow.cpp" line="2532"/>
<source>You try push --set-upstream</source>
<translation>Попытайтесь отправить (Push) с опцией --set-upstream</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2616"/>
+ <location filename="../../BasicMainWindow.cpp" line="2621"/>
<source>Failed to delete the branch &apos;%1&apos;</source>
<translation>Не удалось удалить ветвь &apos;%1&apos;</translation>
</message>
<message>
<source>Failed to delete the branch &apos;%1&apos;
</source>
<translation type="vanished">ブランチの削除に失敗しました : &apos;%1&apos;</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2680"/>
+ <location filename="../../BasicMainWindow.cpp" line="2685"/>
<source>Are you sure you want to run the following command ?</source>
<translation>Вы точно хотите выполнить следующую команду?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2701"/>
+ <location filename="../../BasicMainWindow.cpp" line="2706"/>
<source>Reset a file</source>
<translation>Отбросить изменения (Reset) файла</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2715"/>
+ <location filename="../../BasicMainWindow.cpp" line="2720"/>
<source>Unnamed</source>
<translation>Безымянный</translation>
</message>
</context>
<context>
<name>BasicRepositoryDialog</name>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="66"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="77"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="67"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="78"/>
<source>Purpose</source>
<translation>Назначение</translation>
</message>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="68"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="79"/>
<source>URL</source>
<translation></translation>
</message>
</context>
<context>
<name>BigDiffWindow</name>
<message>
<location filename="../../BigDiffWindow.ui" line="14"/>
<source>Diff</source>
<translation></translation>
</message>
</context>
<context>
<name>BlameWindow</name>
<message>
<location filename="../../BlameWindow.ui" line="14"/>
<source>Blame</source>
<translation></translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="45"/>
<source>Information</source>
<translation>Информация</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="60"/>
<source>Commit</source>
<translation>Фиксация (Commit)</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="70"/>
<source>Date</source>
<translation>Дата</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="90"/>
<source>Message</source>
<translation>Комментарий</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="80"/>
<source>Author</source>
<translation>Автор</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="118"/>
<source>Close</source>
<translation>Закрыть</translation>
</message>
</context>
<context>
<name>CheckoutDialog</name>
<message>
<location filename="../../CheckoutDialog.ui" line="14"/>
<source>Checkout</source>
<translation>Проверка (Checkout)</translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="20"/>
<location filename="../../CheckoutDialog.ui" line="27"/>
<location filename="../../CheckoutDialog.ui" line="34"/>
<source>RadioButton</source>
<translation></translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="63"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="70"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>CherryPickDialog</name>
<message>
<location filename="../../CherryPickDialog.ui" line="14"/>
<source>Cherry-pick</source>
<translation type="unfinished">Выборочное применение (Cherry-pick)</translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="20"/>
<source>HEAD</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="49"/>
<source>Pick</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="78"/>
<source>Mainline</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="109"/>
<source>Allow empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="131"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="138"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="19"/>
<source>Commit</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="20"/>
<source>Date</source>
<translation type="unfinished">Дата</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="21"/>
<source>Author</source>
<translation type="unfinished">Автор</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="22"/>
<source>Message</source>
<translation type="unfinished">Комментарий</translation>
</message>
</context>
<context>
<name>CloneDialog</name>
<message>
<location filename="../../CloneDialog.ui" line="14"/>
- <location filename="../../CloneDialog.ui" line="103"/>
+ <location filename="../../CloneDialog.ui" line="113"/>
<source>Clone</source>
<translation>Клонировать (Clone)</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="22"/>
<source>Remote</source>
<translation>Источник</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="39"/>
<source>&amp;Test</source>
<translation>&amp;Тестировать</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="46"/>
<source>Local</source>
<translation>Локальный</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="56"/>
<source>Browse</source>
<translation>Обзор</translation>
</message>
<message>
- <location filename="../../CloneDialog.ui" line="83"/>
+ <location filename="../../CloneDialog.ui" line="93"/>
<source>Open existing local directory...</source>
<translation>Открыть локальную копию...</translation>
</message>
<message>
- <location filename="../../CloneDialog.ui" line="113"/>
+ <location filename="../../CloneDialog.ui" line="123"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="40"/>
+ <location filename="../../CloneDialog.cpp" line="41"/>
<source>Search</source>
<translation>Поиск</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="41"/>
+ <location filename="../../CloneDialog.cpp" line="42"/>
<source>GitHub</source>
<translation></translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="113"/>
+ <location filename="../../CloneDialog.cpp" line="115"/>
<source>Checkout into</source>
<translation>Проверить в</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="125"/>
+ <location filename="../../CloneDialog.cpp" line="127"/>
<source>Open existing directory</source>
<translation>Открыть существующий каталог</translation>
</message>
</context>
<context>
<name>CloneFromGitHubDialog</name>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="14"/>
<source>Clone from GitHub</source>
<translation>Клонировать из GitHub</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="45"/>
<source>ssh</source>
<translation></translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="55"/>
<source>http</source>
<translation></translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="95"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="102"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>CommitDialog</name>
<message>
<location filename="../../CommitDialog.ui" line="14"/>
<source>Commit</source>
<translation>Фиксация (Commit)</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="27"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="49"/>
<source>Author</source>
<translation>Автор</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="68"/>
<location filename="../../CommitDialog.ui" line="94"/>
<location filename="../../CommitDialog.ui" line="139"/>
<location filename="../../CommitDialog.ui" line="159"/>
<location filename="../../CommitDialog.ui" line="179"/>
<source>---</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="75"/>
<location filename="../../CommitDialog.ui" line="166"/>
<source>Mail</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="111"/>
<source>GPG Signing</source>
<translation>Подпись GPG</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="126"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="146"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="189"/>
<source>Configure...</source>
<translation>Настроить...</translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="198"/>
+ <location filename="../../CommitDialog.ui" line="203"/>
<source>Message</source>
<translation>Комментарий</translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="223"/>
+ <location filename="../../CommitDialog.ui" line="210"/>
+ <source>Amend</source>
+ <translation>изменить</translation>
+ </message>
+ <message>
+ <location filename="../../CommitDialog.ui" line="237"/>
<source>OK</source>
<translation></translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="233"/>
+ <location filename="../../CommitDialog.ui" line="247"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>CommitExploreWindow</name>
<message>
<location filename="../../CommitExploreWindow.ui" line="14"/>
<source>Commit Explorer</source>
<translation>Обзор фиксации</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="65"/>
<source>Commit ID</source>
<translation>ID фиксации</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="82"/>
<source>Date</source>
<translation>Дата</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="89"/>
<source>Author</source>
<translation>Автор</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.cpp" line="78"/>
<source>Commit</source>
<translation>Фиксация (Commit)</translation>
</message>
</context>
<context>
<name>CommitPropertyDialog</name>
<message>
<location filename="../../CommitPropertyDialog.ui" line="14"/>
<source>Commit Property</source>
<oldsource>Commit Properties</oldsource>
<translation>Свойства фиксации</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="46"/>
<source>Date</source>
<translation>Дата</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="56"/>
<source>Author</source>
<translation>Автор</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="66"/>
<location filename="../../CommitPropertyDialog.ui" line="196"/>
<source>Mail</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="154"/>
<source>GPG Sign</source>
<translation>Подпись GPG</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="176"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="186"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="233"/>
<source>Commit ID</source>
<translation>ID фиксации</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="243"/>
<source>Parent IDs</source>
<translation>ID родительских фиксаций</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="265"/>
<source>Files...</source>
<translation>Файлы...</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="285"/>
<source>Explorer</source>
<translation>Обзор</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="305"/>
<source>Checkout</source>
<translation>Проверка (Checkout)</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="312"/>
<source>Jump</source>
<translation>Перейти</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="332"/>
<source>Close</source>
<translation>Закрыть</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.cpp" line="60"/>
<source>&lt;Unknown&gt;</source>
<translation>&lt;Неизвестно&gt;</translation>
</message>
</context>
<context>
<name>CommitViewWindow</name>
<message>
<location filename="../../CommitViewWindow.ui" line="14"/>
<source>Commit View</source>
<translation>Просмотр фиксации (Commit)</translation>
</message>
<message>
<location filename="../../CommitViewWindow.cpp" line="58"/>
<source>History</source>
<translation>История</translation>
</message>
</context>
<context>
<name>ConfigCredentialHelperDialog</name>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="26"/>
<source>wincred</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="39"/>
<source>winstore</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="52"/>
<source>None</source>
<translation>Нет</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="65"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="78"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="91"/>
<source>Other</source>
<translation>Другие</translation>
</message>
</context>
<context>
<name>ConfigSigningDialog</name>
<message>
<location filename="../../ConfigSigningDialog.ui" line="14"/>
<source>Signing Policy</source>
<translation>Политика подписания</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="20"/>
<source>Config commit.gpgsign</source>
<translation>Настройка commit.gpgsign</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="32"/>
<source>global</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="42"/>
<source>local</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="70"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="77"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>CreateRepositoryDialog</name>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="14"/>
<location filename="../../CreateRepositoryDialog.cpp" line="42"/>
<location filename="../../CreateRepositoryDialog.cpp" line="46"/>
<location filename="../../CreateRepositoryDialog.cpp" line="50"/>
<location filename="../../CreateRepositoryDialog.cpp" line="54"/>
<source>Create Repository</source>
<translation>Создать репозиторий</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="22"/>
<source>Path</source>
<translation>Путь</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="32"/>
<source>Browse</source>
<translation>Обзор</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="54"/>
<source>Bookmark</source>
<translation>Закладка</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="63"/>
<location filename="../../CreateRepositoryDialog.ui" line="88"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="76"/>
<source>Remote</source>
<translation>Источник</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="98"/>
<source>URL</source>
<translation></translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="108"/>
<source>Test</source>
<translation>Тестировать</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="146"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="153"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="19"/>
<source>A valid git repository already exists there.</source>
<translation>Репозиторий Git уже существует.</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="42"/>
<location filename="../../CreateRepositoryDialog.cpp" line="50"/>
<source>The specified path is not a directory.</source>
<translation>Путь не является каталогом.</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="54"/>
<source>Remote name is invalid.</source>
<translation>Имя источника некорректно.</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="62"/>
<source>Destination Path</source>
<translation>Путь назначения</translation>
</message>
</context>
<context>
<name>DeleteBranchDialog</name>
<message>
<location filename="../../DeleteBranchDialog.ui" line="14"/>
<source>Delete Branch</source>
<translation>Удалить ветвь</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="25"/>
<source>&amp;All branches</source>
<translation>Все ветви (&amp;A)</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="45"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="52"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.cpp" line="23"/>
<source>Delete Remote Branch</source>
<translation>Удалить ветвь в источнике</translation>
</message>
</context>
<context>
<name>DeleteTagsDialog</name>
<message>
<location filename="../../DeleteTagsDialog.ui" line="14"/>
<source>Delete tags</source>
<translation>Удалить тэги</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="32"/>
<source>Check all</source>
<translation>Проверить все</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="52"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="62"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>DoYouWantToInitDialog</name>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="14"/>
<source>Init Repository</source>
<translation>Создать репозиторий</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="20"/>
<source>The folder:</source>
<translation>Каталог:</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="32"/>
<source>is not a valid git working copy.</source>
<translation>не является репозиторием.</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="39"/>
<source>Do you want to init it as a git repository ?</source>
<translation>Инициализировать репозиторий Git?</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="81"/>
<source>Yes, Please init it.</source>
<translation>Да, инициализировать.</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="88"/>
<source>No, Stop it.</source>
<translation>Нет, отменить.</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="140"/>
<source>Please choose option</source>
<translation>Продолжить</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.cpp" line="20"/>
<source>Next...</source>
<translation>Далее...</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.cpp" line="26"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>EditGitIgnoreDialog</name>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="14"/>
<source>Edit Git Ignore</source>
<translation>Редактировать список игнорирования</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="20"/>
<location filename="../../EditGitIgnoreDialog.ui" line="27"/>
<location filename="../../EditGitIgnoreDialog.ui" line="34"/>
<location filename="../../EditGitIgnoreDialog.ui" line="41"/>
<source>RadioButton</source>
<translation></translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="63"/>
<source>Edit the file</source>
<translation>Редактировать</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="83"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="90"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>EditRemoteDialog</name>
<message>
<location filename="../../EditRemoteDialog.ui" line="14"/>
<source>Edit Remote</source>
<translation>Свойства источника</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="20"/>
<source>Remote</source>
<translation>Источник</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="26"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="33"/>
<source>URL</source>
<translation></translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="53"/>
<source>&amp;Test</source>
<translation>&amp;Проверить</translation>
</message>
<message>
- <location filename="../../EditRemoteDialog.ui" line="81"/>
+ <location filename="../../EditRemoteDialog.ui" line="104"/>
<source>OK</source>
<translation></translation>
</message>
<message>
- <location filename="../../EditRemoteDialog.ui" line="88"/>
+ <location filename="../../EditRemoteDialog.ui" line="111"/>
<source>Close</source>
<translation>Закрыть</translation>
</message>
</context>
<context>
<name>EditTagsDialog</name>
<message>
<location filename="../../EditTagsDialog.ui" line="14"/>
<source>Edit Tags</source>
<translation>Редактор тэгов</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="41"/>
<source>Add...</source>
<translation>Добавить...</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="48"/>
<source>Delete</source>
<translation>Удалить</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="68"/>
<source>Close</source>
<translation>Закрыть</translation>
</message>
</context>
<context>
<name>ExperimentDialog</name>
<message>
<location filename="../../ExperimentDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
</context>
<context>
<name>FileDiffWidget</name>
<message>
<location filename="../../FileDiffWidget.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
</context>
<context>
<name>FileHistoryWindow</name>
<message>
<location filename="../../FileHistoryWindow.ui" line="14"/>
<source>File History</source>
<translation>История файла</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.ui" line="20"/>
<location filename="../../FileHistoryWindow.ui" line="27"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="111"/>
<source>Commit</source>
<translation>Фиксация (Commit)</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="112"/>
<source>Date</source>
<translation>Дата</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="113"/>
<source>Author</source>
<translation>Автор</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="114"/>
<source>Message</source>
<translation>Комментарий</translation>
</message>
<message>
<source>Description</source>
<translation type="vanished">概要</translation>
</message>
</context>
<context>
<name>FilePropertyDialog</name>
<message>
<location filename="../../FilePropertyDialog.ui" line="14"/>
<source>File Property</source>
<oldsource>File Properties</oldsource>
<translation>Свойства файла</translation>
</message>
<message>
<location filename="../../FilePropertyDialog.ui" line="78"/>
<source>&amp;Close</source>
<translation>&amp;Закрыть</translation>
</message>
</context>
+<context>
+ <name>FileViewWidget</name>
+ <message>
+ <location filename="../../FileViewWidget.cpp" line="30"/>
+ <source>Form</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>FindCommitDialog</name>
<message>
<location filename="../../FindCommitDialog.ui" line="14"/>
<source>Find Commit</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../FindCommitDialog.ui" line="54"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../FindCommitDialog.ui" line="61"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>InputNewTagDialog</name>
<message>
<location filename="../../InputNewTagDialog.ui" line="14"/>
<source>Edit tag</source>
<translation>Редактор тэга</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="31"/>
<source>Tag</source>
<translation>Тэг</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="58"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="65"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>JumpDialog</name>
<message>
<location filename="../../JumpDialog.ui" line="14"/>
<source>Jump</source>
<translation>Перейти</translation>
</message>
<message>
<source>Branches and Tags</source>
<translation type="vanished">ブランチとタグ</translation>
</message>
<message>
<source>&amp;Filter</source>
<translation type="vanished">フィルタ(&amp;F)</translation>
</message>
<message>
<source>Find</source>
<translation type="vanished">検索</translation>
</message>
<message>
<source>&amp;Checkout</source>
<translation type="vanished">チェックアウト(&amp;C)</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="20"/>
<source>Branch, Tag or Commit-ID :</source>
<translation>Ветвь, Тэг или ID фиксации :</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="71"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="81"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../JumpDialog.cpp" line="39"/>
<source>Name</source>
<translation>Имя</translation>
</message>
</context>
<context>
<name>Languages</name>
<message>
<location filename="../../Languages.cpp" line="6"/>
<source>English</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../Languages.cpp" line="7"/>
<source>Japanese</source>
<translation type="unfinished">Японский</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="8"/>
<source>Russian</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../Languages.cpp" line="9"/>
<source>Chinese (Simplified)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../Languages.cpp" line="10"/>
<source>Chinese (Traditional/Taiwan)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>LineEditDialog</name>
<message>
<location filename="../../LineEditDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="20"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="45"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="52"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../MainWindow.ui" line="14"/>
<source>Guitar</source>
<translation></translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="96"/>
- <location filename="../../MainWindow.ui" line="1421"/>
- <location filename="../../MainWindow.ui" line="1424"/>
+ <location filename="../../MainWindow.ui" line="1416"/>
+ <location filename="../../MainWindow.ui" line="1419"/>
<source>Clone</source>
<translation>Клонировать</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="131"/>
- <location filename="../../MainWindow.cpp" line="1140"/>
- <location filename="../../MainWindow.cpp" line="1141"/>
+ <location filename="../../MainWindow.cpp" line="1135"/>
+ <location filename="../../MainWindow.cpp" line="1136"/>
<source>Fetch</source>
<translation>Загрузить</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="166"/>
<source>Pull</source>
<translation>Обновить</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="201"/>
<source>Push</source>
<translation>Выгрузить</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="303"/>
- <location filename="../../MainWindow.ui" line="1548"/>
+ <location filename="../../MainWindow.ui" line="1543"/>
<source>Terminal</source>
<translation>Терминал</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="338"/>
- <location filename="../../MainWindow.ui" line="1557"/>
+ <location filename="../../MainWindow.ui" line="1552"/>
<source>Explorer</source>
<translation>Обзор</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="424"/>
<source>Repository</source>
<translation>Репозиторий</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="431"/>
<source>Branch Name</source>
<translation>Имя ветви</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="483"/>
- <location filename="../../MainWindow.ui" line="1592"/>
+ <location filename="../../MainWindow.ui" line="1587"/>
<source>Offline</source>
<translation>Офлайн</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="473"/>
- <location filename="../../MainWindow.ui" line="1587"/>
+ <location filename="../../MainWindow.ui" line="1582"/>
<source>Online</source>
<translation>Онлайн</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="799"/>
- <location filename="../../MainWindow.cpp" line="1853"/>
+ <location filename="../../MainWindow.cpp" line="1848"/>
<source>Unstage</source>
<translation>Не отслеживать</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="828"/>
<source>Select all</source>
<translation>Выбрать всё</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="863"/>
- <location filename="../../MainWindow.cpp" line="1723"/>
+ <location filename="../../MainWindow.cpp" line="1718"/>
<source>Stage</source>
<translation>Отслеживать</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="892"/>
- <location filename="../../MainWindow.cpp" line="879"/>
+ <location filename="../../MainWindow.cpp" line="874"/>
<source>Commit</source>
<translation>Фиксация</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="1023"/>
<source>&amp;File</source>
<translation>&amp;Файл</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="1034"/>
<source>&amp;View</source>
<translation>Вид(&amp;V)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1040"/>
+ <location filename="../../MainWindow.ui" line="1041"/>
<source>&amp;Edit</source>
<translation>Редактировать(&amp;E)</translation>
</message>
<message>
<source>Destructive</source>
<translation type="vanished">注意を要するコマンド</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1412"/>
+ <location filename="../../MainWindow.ui" line="1407"/>
<source>Settings</source>
<translation>Настройки</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1434"/>
- <location filename="../../MainWindow.cpp" line="1595"/>
+ <location filename="../../MainWindow.ui" line="1429"/>
+ <location filename="../../MainWindow.cpp" line="1590"/>
<source>Edit tags...</source>
<translation>Правка тэгов...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1437"/>
+ <location filename="../../MainWindow.ui" line="1432"/>
<source>Edit tags</source>
<translation>Правка тэгов</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1562"/>
+ <location filename="../../MainWindow.ui" line="1557"/>
<source>Clean -df</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1567"/>
+ <location filename="../../MainWindow.ui" line="1562"/>
<source>Reset --hard</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1494"/>
+ <location filename="../../MainWindow.ui" line="1489"/>
<source>Create a repository</source>
<translation>Создание репозитория</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1481"/>
+ <location filename="../../MainWindow.ui" line="1476"/>
<source>Push upstream</source>
<translation>Выгрузить в источник</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1503"/>
+ <location filename="../../MainWindow.ui" line="1498"/>
<source>Stop process</source>
<translation>Остановить</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1508"/>
+ <location filename="../../MainWindow.ui" line="1503"/>
<source>E&amp;xit</source>
<translation>В&amp;ыход</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1511"/>
+ <location filename="../../MainWindow.ui" line="1506"/>
<source>Ctrl+Q</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1516"/>
+ <location filename="../../MainWindow.ui" line="1511"/>
<source>Reflog...</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1521"/>
+ <location filename="../../MainWindow.ui" line="1516"/>
<source>Property...</source>
<translation>Свойства...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1526"/>
- <location filename="../../MainWindow.ui" line="1529"/>
+ <location filename="../../MainWindow.ui" line="1521"/>
+ <location filename="../../MainWindow.ui" line="1524"/>
<source>Set GPG signing</source>
<translation>Настройка подписей GPG</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1539"/>
+ <location filename="../../MainWindow.ui" line="1534"/>
<source>Fetch --prune</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1055"/>
+ <location filename="../../MainWindow.ui" line="1056"/>
<source>&amp;Help</source>
<translation>Помощь (&amp;H)</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="252"/>
- <location filename="../../MainWindow.ui" line="1628"/>
- <location filename="../../MainWindow.cpp" line="1353"/>
+ <location filename="../../MainWindow.ui" line="1623"/>
+ <location filename="../../MainWindow.cpp" line="1348"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="656"/>
<source>...</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1061"/>
+ <location filename="../../MainWindow.ui" line="1062"/>
<source>&amp;Window</source>
<translation>Окно(&amp;W)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1067"/>
+ <location filename="../../MainWindow.ui" line="1073"/>
<source>&amp;Repository</source>
<translation>Репозиторий(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1071"/>
- <location filename="../../MainWindow.ui" line="1572"/>
+ <location filename="../../MainWindow.ui" line="1077"/>
+ <location filename="../../MainWindow.ui" line="1567"/>
<source>Stash</source>
<translation>Спрятать</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1098"/>
+ <location filename="../../MainWindow.ui" line="1104"/>
<source>Re&amp;mote</source>
<translation>Источник (&amp;M)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1113"/>
+ <location filename="../../MainWindow.ui" line="1119"/>
<source>&amp;Destructive</source>
<translation>Разрушительные (&amp;D)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1125"/>
- <source>Experimental</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="../../MainWindow.ui" line="1155"/>
+ <location filename="../../MainWindow.ui" line="1150"/>
<source>Log</source>
<translation>Журнал</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1325"/>
+ <location filename="../../MainWindow.ui" line="1320"/>
<source>&amp;Open existing working copy...</source>
<translation>Открыть существующий репозиторий (&amp;O)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1328"/>
- <location filename="../../MainWindow.cpp" line="2027"/>
+ <location filename="../../MainWindow.ui" line="1323"/>
+ <location filename="../../MainWindow.cpp" line="2022"/>
<source>Add existing working copy</source>
<translation>Добавить существующий репозиторий</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1333"/>
- <location filename="../../MainWindow.ui" line="1336"/>
+ <location filename="../../MainWindow.ui" line="1328"/>
+ <location filename="../../MainWindow.ui" line="1331"/>
<source>Refresh</source>
<translation>Обновить</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1339"/>
+ <location filename="../../MainWindow.ui" line="1334"/>
<source>F5</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1348"/>
+ <location filename="../../MainWindow.ui" line="1343"/>
<source>&amp;Commit</source>
<translation>Фиксация (&amp;Commit)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1357"/>
+ <location filename="../../MainWindow.ui" line="1352"/>
<source>&amp;Push</source>
<translation>Выгрузить (&amp;Push)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1365"/>
+ <location filename="../../MainWindow.ui" line="1360"/>
<source>test</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1368"/>
+ <location filename="../../MainWindow.ui" line="1363"/>
<source>Ctrl+T</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1377"/>
+ <location filename="../../MainWindow.ui" line="1372"/>
<source>Pu&amp;ll</source>
<translation>Обновить (Pu&amp;ll)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1386"/>
+ <location filename="../../MainWindow.ui" line="1381"/>
<source>&amp;Fetch</source>
<translation>Загрузить (&amp;Fetch)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1391"/>
- <location filename="../../MainWindow.ui" line="1394"/>
+ <location filename="../../MainWindow.ui" line="1386"/>
+ <location filename="../../MainWindow.ui" line="1389"/>
<source>Edit global .gitconfig</source>
<translation>Прака глобального .gitconfig</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1399"/>
+ <location filename="../../MainWindow.ui" line="1394"/>
<source>Edit .git/config</source>
<translation>Правка .git/config</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1404"/>
+ <location filename="../../MainWindow.ui" line="1399"/>
<source>Edit .gitignore</source>
<translation>Правка .gitignore</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1409"/>
+ <location filename="../../MainWindow.ui" line="1404"/>
<source>&amp;Settings...</source>
<translation>Настройки (&amp;S)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1458"/>
+ <location filename="../../MainWindow.ui" line="1453"/>
<source>F4</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1463"/>
+ <location filename="../../MainWindow.ui" line="1458"/>
<source>&amp;Jump...</source>
<translation>Переход (&amp;Jump)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1466"/>
+ <location filename="../../MainWindow.ui" line="1461"/>
<source>Ctrl+J</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1471"/>
+ <location filename="../../MainWindow.ui" line="1466"/>
<source>Check&amp;out...</source>
<translation>Проверка (C&amp;heckout)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1476"/>
- <location filename="../../MainWindow.cpp" line="1600"/>
+ <location filename="../../MainWindow.ui" line="1471"/>
+ <location filename="../../MainWindow.cpp" line="1595"/>
<source>Delete branch...</source>
<translation>Удалить ветвь...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1577"/>
+ <location filename="../../MainWindow.ui" line="1572"/>
<source>Apply</source>
<translation>Применить</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1582"/>
+ <location filename="../../MainWindow.ui" line="1577"/>
<source>Drop</source>
<translation>Отменить</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1600"/>
+ <location filename="../../MainWindow.ui" line="1595"/>
<source>Repositories panel</source>
<translation>Панель репозиториев</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1603"/>
+ <location filename="../../MainWindow.ui" line="1598"/>
<source>Ctrl+R</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1608"/>
+ <location filename="../../MainWindow.ui" line="1603"/>
<source>&amp;Find...</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1611"/>
+ <location filename="../../MainWindow.ui" line="1606"/>
<source>Ctrl+F</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1616"/>
+ <location filename="../../MainWindow.ui" line="1611"/>
<source>Find next</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1619"/>
+ <location filename="../../MainWindow.ui" line="1614"/>
<source>F3</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1633"/>
+ <location filename="../../MainWindow.ui" line="1628"/>
<source>Jump to &amp;HEAD</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1636"/>
+ <location filename="../../MainWindow.ui" line="1631"/>
<source>Ctrl+H</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1641"/>
+ <location filename="../../MainWindow.ui" line="1636"/>
<source>Merge...</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1646"/>
+ <location filename="../../MainWindow.ui" line="1641"/>
<source>Expand commit log</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1649"/>
+ <location filename="../../MainWindow.ui" line="1644"/>
<source>Ctrl+2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1654"/>
+ <location filename="../../MainWindow.ui" line="1649"/>
<source>Expand file list</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1657"/>
+ <location filename="../../MainWindow.ui" line="1652"/>
<source>Ctrl+3</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1662"/>
+ <location filename="../../MainWindow.ui" line="1657"/>
<source>Expand diff view</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1665"/>
+ <location filename="../../MainWindow.ui" line="1660"/>
<source>Ctrl+4</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1670"/>
+ <location filename="../../MainWindow.ui" line="1668"/>
<source>Sidebar</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1673"/>
- <source>F1</source>
+ <location filename="../../MainWindow.ui" line="1671"/>
+ <source>Ctrl+1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1678"/>
+ <location filename="../../MainWindow.ui" line="1687"/>
+ <source>Show labels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../MainWindow.ui" line="1690"/>
+ <source>Ctrl+L</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../MainWindow.ui" line="1676"/>
<source>Wide</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1681"/>
+ <location filename="../../MainWindow.ui" line="1679"/>
<source>F2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1486"/>
- <location filename="../../MainWindow.ui" line="1489"/>
+ <location filename="../../MainWindow.ui" line="1481"/>
+ <location filename="../../MainWindow.ui" line="1484"/>
<source>Reset HEAD~1</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1534"/>
- <location filename="../../MainWindow.cpp" line="1601"/>
+ <location filename="../../MainWindow.ui" line="1529"/>
+ <location filename="../../MainWindow.cpp" line="1596"/>
<source>Delete remote branch...</source>
<translation>Удалить ветвь источника...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1429"/>
+ <location filename="../../MainWindow.ui" line="1424"/>
<source>&amp;About</source>
<translation>&amp;О программе</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1442"/>
+ <location filename="../../MainWindow.ui" line="1437"/>
<source>Push all tags</source>
<translation>Выгрузить все тэги</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1447"/>
+ <location filename="../../MainWindow.ui" line="1442"/>
<source>Set config user</source>
<translation>Выбрать пользователя</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1455"/>
+ <location filename="../../MainWindow.ui" line="1450"/>
<source>&amp;Log</source>
<translation>Журнал (&amp;Log)</translation>
</message>
<message>
<source>Unnamed</source>
<translation type="vanished">無名</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="679"/>
+ <location filename="../../MainWindow.cpp" line="674"/>
<source>Default</source>
<translation>По-умолчанию</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="878"/>
+ <location filename="../../MainWindow.cpp" line="873"/>
<source>Graph</source>
<translation>Граф</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="880"/>
+ <location filename="../../MainWindow.cpp" line="875"/>
<source>Date</source>
<translation>Дата</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="881"/>
+ <location filename="../../MainWindow.cpp" line="876"/>
<source>Author</source>
<translation>Автор</translation>
</message>
<message>
<source>Description</source>
<translation type="vanished">概要</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1029"/>
- <location filename="../../MainWindow.cpp" line="1199"/>
+ <location filename="../../MainWindow.cpp" line="1024"/>
+ <location filename="../../MainWindow.cpp" line="1194"/>
<source>Uncommited changes</source>
<translation>Незафиксированные изменения</translation>
</message>
<message>
<source>Are you sure you want to remove the repository from bookmarks ?</source>
<translation type="vanished">リポジトリをブックマークから削除してよろしいですか?</translation>
</message>
<message>
<source>(Files will NOT be deleted)</source>
<translation type="vanished">(ファルは削除されません)</translation>
</message>
<message>
<source>Open Repository</source>
<translation type="vanished">リポジトリを開く</translation>
</message>
<message>
<source>No such folder</source>
<translation type="vanished">そのようなフォルダはありません</translation>
</message>
<message>
<source>Not a valid git repository</source>
<translation type="vanished">有効なリポジトリではありません</translation>
</message>
<message>
<source>Commit message can not be omitted.</source>
<translation type="vanished">コミットメッセージを空にすることはできません。</translation>
</message>
<message>
<source>Repository Property</source>
<translation type="vanished">リポジトリのプロパティ</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1457"/>
+ <location filename="../../MainWindow.cpp" line="1452"/>
<source>&amp;Add new group</source>
<translation>Новая &amp;группа</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1458"/>
+ <location filename="../../MainWindow.cpp" line="1453"/>
<source>&amp;Delete group</source>
<translation>Удалить &amp;группу</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1459"/>
+ <location filename="../../MainWindow.cpp" line="1454"/>
<source>&amp;Rename group</source>
<translation>Переименовать группу(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1464"/>
+ <location filename="../../MainWindow.cpp" line="1459"/>
<source>New group</source>
<translation>Новая группа</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1488"/>
+ <location filename="../../MainWindow.cpp" line="1483"/>
<source>Open &amp;terminal</source>
<translation>Открыть терминал (&amp;T)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1489"/>
+ <location filename="../../MainWindow.cpp" line="1484"/>
<source>Open command promp&amp;t</source>
<translation>Открыть терминал (&amp;T)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1491"/>
+ <location filename="../../MainWindow.cpp" line="1486"/>
<source>&amp;Open</source>
<translation>Открыть (&amp;Open)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1502"/>
+ <location filename="../../MainWindow.cpp" line="1497"/>
<source>Open &amp;folder</source>
<translation>Открыть каталог (&amp;F)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1507"/>
+ <location filename="../../MainWindow.cpp" line="1502"/>
<source>&amp;Remove</source>
<translation>Удалить (&amp;Remove)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1550"/>
+ <location filename="../../MainWindow.cpp" line="1545"/>
<source>Copy commit id (7 letters)</source>
<translation>Скопируйте ID фиксации(7 символов)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1551"/>
+ <location filename="../../MainWindow.cpp" line="1546"/>
<source>Copy commit id (completely)</source>
<translation>Скопируйте ID фиксации(полностью)</translation>
</message>
<message>
<source>Edit comment...</source>
<translation type="vanished">コメントを編集...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1242"/>
- <location filename="../../MainWindow.cpp" line="1593"/>
+ <location filename="../../MainWindow.cpp" line="1237"/>
+ <location filename="../../MainWindow.cpp" line="1588"/>
<source>Rebase</source>
<translation>Перебазирование</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1605"/>
+ <location filename="../../MainWindow.cpp" line="1600"/>
<source>Explore</source>
<translation>Обзор</translation>
</message>
<message>
<source>Reset HEAD</source>
<translation type="vanished">Откат HEAD</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="882"/>
+ <location filename="../../MainWindow.cpp" line="877"/>
<source>Message</source>
<translation>Комментарий</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1679"/>
- <location filename="../../MainWindow.cpp" line="1728"/>
- <location filename="../../MainWindow.cpp" line="1854"/>
+ <location filename="../../MainWindow.cpp" line="1674"/>
+ <location filename="../../MainWindow.cpp" line="1723"/>
+ <location filename="../../MainWindow.cpp" line="1849"/>
<source>History</source>
<translation>История</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2436"/>
- <location filename="../../MainWindow.cpp" line="2450"/>
+ <location filename="../../MainWindow.cpp" line="2431"/>
+ <location filename="../../MainWindow.cpp" line="2445"/>
<source>No such commit</source>
<translation>Фиксация не существует</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1143"/>
- <location filename="../../MainWindow.cpp" line="1144"/>
+ <location filename="../../MainWindow.cpp" line="1138"/>
+ <location filename="../../MainWindow.cpp" line="1139"/>
<source>Update</source>
<translation>Обновить</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2762"/>
+ <location filename="../../MainWindow.cpp" line="2757"/>
<source>Authentication Failed</source>
<translation>Авторизация не удалась</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1596"/>
+ <location filename="../../MainWindow.cpp" line="1591"/>
<source>Revert</source>
<translation>Отмена (Revert)</translation>
</message>
<message>
<source>Failed to commit</source>
<translation type="vanished">コミット失敗</translation>
</message>
<message>
<source>No remote repository is registered.</source>
<translation type="vanished">リモートリポジトリが登録されていません。</translation>
</message>
<message>
<source>The current branch %1 has no upstream branch.</source>
<translation type="vanished">現在のブランチ「%1」には上流ブランチがありません。</translation>
</message>
<message>
<source>You try push --set-upstream</source>
<translation type="vanished">--set-upstream を試してください</translation>
</message>
<message>
<source>Connection refused.</source>
<translation type="vanished">接続が拒否されました。</translation>
</message>
<message>
<source>&amp;Property</source>
<translation type="vanished">プロパティ(&amp;P)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1567"/>
+ <location filename="../../MainWindow.cpp" line="1562"/>
<source>Checkout/Branch...</source>
<translation>Проверка/ветвь...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1589"/>
+ <location filename="../../MainWindow.cpp" line="1584"/>
<source>Edit message...</source>
<translation>Правка комментария...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1592"/>
+ <location filename="../../MainWindow.cpp" line="1587"/>
<source>Merge</source>
<translation>Слияние</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1594"/>
+ <location filename="../../MainWindow.cpp" line="1589"/>
<source>Cherry-pick</source>
<translation>Выборочное применение (Cherry-pick)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1678"/>
- <location filename="../../MainWindow.cpp" line="1727"/>
+ <location filename="../../MainWindow.cpp" line="1673"/>
+ <location filename="../../MainWindow.cpp" line="1722"/>
<source>Untrack</source>
<translation>Не отслеживать</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1680"/>
- <location filename="../../MainWindow.cpp" line="1729"/>
- <location filename="../../MainWindow.cpp" line="1855"/>
+ <location filename="../../MainWindow.cpp" line="1675"/>
+ <location filename="../../MainWindow.cpp" line="1724"/>
+ <location filename="../../MainWindow.cpp" line="1850"/>
<source>Blame</source>
<translation>Обращение</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1688"/>
+ <location filename="../../MainWindow.cpp" line="1683"/>
<source>Delete selected files.</source>
<translation>Удалить выбранные файлы.</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1699"/>
+ <location filename="../../MainWindow.cpp" line="1694"/>
<source>rm --cached files</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1724"/>
+ <location filename="../../MainWindow.cpp" line="1719"/>
<source>Reset</source>
<translation>Откат (Reset)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1725"/>
+ <location filename="../../MainWindow.cpp" line="1720"/>
<source>Ignore</source>
<translation>Игнорировать</translation>
</message>
<message>
<source>Reset a file</source>
<translation type="vanished">ファイルをリセットします</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1239"/>
+ <location filename="../../MainWindow.cpp" line="1234"/>
<source>Are you sure you want to rebase the commit ?</source>
<translation>Вы уверены, что хотите переместить фиксацию?</translation>
</message>
<message>
<source>No repository selected</source>
<translation type="vanished">リポジトリが選択されていません</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1677"/>
- <location filename="../../MainWindow.cpp" line="1726"/>
+ <location filename="../../MainWindow.cpp" line="1672"/>
+ <location filename="../../MainWindow.cpp" line="1721"/>
<source>Delete</source>
<translation>Удалить</translation>
</message>
<message>
<source>Are you sure you want to run the following command ?</source>
<translation type="vanished">次のコマンドを実行してよろしいですか?</translation>
</message>
<message>
<source>Revert all files</source>
<translation type="vanished">すべてのファイルの変更を破棄</translation>
</message>
<message>
<source>Select %1 command</source>
<translation type="vanished">%1 コマンドの選択</translation>
</message>
<message>
<source>A file with same name already exists</source>
<translation type="vanished">同じ名前のファイルが既に存在しています</translation>
</message>
<message>
<source>A folder with same name already exists</source>
<translation type="vanished">同じ名前のフォルダが既に存在しています</translation>
</message>
<message>
<source>Invalid folder</source>
<translation type="vanished">無効なフォルダ</translation>
</message>
<message>
<source>No such folder. Create it now ?</source>
<translation type="vanished">このフォルダはありません。作成しますか?</translation>
</message>
<message>
<source>The URL is a valid repository</source>
<translation type="vanished">このURLは有効なリポジトリです</translation>
</message>
<message>
<source>Failed to access the URL</source>
<translation type="vanished">このURLへのアクセスに失敗しました</translation>
</message>
<message>
<source>Remote Repository</source>
<translation type="vanished">リモートリポジトリ</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2436"/>
- <location filename="../../MainWindow.cpp" line="2450"/>
+ <location filename="../../MainWindow.cpp" line="2431"/>
+ <location filename="../../MainWindow.cpp" line="2445"/>
<source>Jump</source>
<translation>Перейти</translation>
</message>
<message>
<source>That commmit has not foud or not read yet</source>
<translation type="vanished">そのコミットは、見つからないか、まだ読み込まれていません</translation>
</message>
<message>
<source>Failed to delete the branch &apos;%1&apos;
</source>
<translation type="vanished">ブランチの削除に失敗しました : &apos;%1&apos;</translation>
</message>
</context>
<context>
<name>MergeBranchDialog</name>
<message>
<source>Merge</source>
<translation type="vanished">Слияние</translation>
</message>
<message>
<source>Current branch :</source>
<translation type="vanished">Текущая ветвь :</translation>
</message>
<message>
<source>Cancel</source>
<translation type="vanished">Отмена</translation>
</message>
</context>
<context>
<name>MergeDialog</name>
<message>
<location filename="../../MergeDialog.ui" line="14"/>
<source>Merge</source>
<translation type="unfinished">Слияние</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="20"/>
<source>Fast Forwarding</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="26"/>
<source>Default (--ff)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="33"/>
<source>No fast forward (--no-ff)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="40"/>
<source>Fast forward only (--ff-only)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="50"/>
<source>From</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="77"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="84"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MyImageViewWidget</name>
<message>
<location filename="../../MyImageViewWidget.cpp" line="34"/>
<source>Save as...</source>
<translation>Сохранить как...</translation>
</message>
<message>
<location filename="../../MyImageViewWidget.cpp" line="42"/>
<source>Save as</source>
<translation>Сохранить как</translation>
</message>
</context>
<context>
<name>MyTextEditorWidget</name>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="35"/>
<source>Save as...</source>
<translation>Сохранить как...</translation>
</message>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="36"/>
<source>Copy</source>
<translation>Копировать</translation>
</message>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="43"/>
<source>Save as</source>
<translation>Сохранить как</translation>
</message>
</context>
<context>
<name>ObjectBrowserDialog</name>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="14"/>
<source>Object Browser</source>
<translation>Обозреватель объектов</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="38"/>
<source>Inspect</source>
<translation>Проверка</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="58"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="65"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="23"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="24"/>
<source>Type</source>
<translation>Тип</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="119"/>
<source>Object Inspection</source>
<translation>Проверка объектов</translation>
</message>
</context>
<context>
<name>PushDialog</name>
<message>
<location filename="../../PushDialog.ui" line="14"/>
<source>Push</source>
<translation>Выгрузить</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="20"/>
<source>push --set-upstream</source>
<translation>Выгрузить в источник</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="26"/>
<source>Remote</source>
<translation>Источник</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="33"/>
<source>Branch</source>
<translation>Ветвь</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="64"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="71"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>RebaseOntoDialog</name>
<message>
<source>Branch</source>
<translation type="vanished">ブランチ</translation>
</message>
<message>
<source>Cancel</source>
<translation type="vanished">キャンセル</translation>
</message>
</context>
<context>
<name>ReflogWindow</name>
<message>
<location filename="../../ReflogWindow.ui" line="14"/>
<source>Reflog</source>
<translation></translation>
</message>
<message>
<location filename="../../ReflogWindow.ui" line="57"/>
<source>Close</source>
<translation>Закрыть</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="35"/>
<source>Commit</source>
<translation>Фиксация (Commit)</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="36"/>
<source>Head</source>
<translation>Текущая</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="37"/>
<source>Command</source>
<translation>Команда</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="38"/>
<source>Message</source>
<translation>Комментарий</translation>
</message>
<message>
<source>Comment</source>
<translation type="vanished">コメント</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="98"/>
<source>Checkout</source>
<translation>Проверка (Checkout)</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="99"/>
<source>Explorer</source>
<translation>Обзор</translation>
</message>
</context>
+<context>
+ <name>RemoteAdvancedOptionWidget</name>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="32"/>
+ <source>Advanced Option</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="38"/>
+ <source>SSH key override...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="45"/>
+ <source>Clear</source>
+ <translation type="unfinished">Очистить</translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.cpp" line="24"/>
+ <source>SSH command is not registered.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.cpp" line="30"/>
+ <source>SSH key override</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>RemoteRepositoriesTableWidget</name>
<message>
<location filename="../../RemoteRepositoriesTableWidget.cpp" line="27"/>
<source>Copy URL</source>
<translation>Копировать URL</translation>
</message>
</context>
<context>
<name>RepositoryPropertyDialog</name>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="14"/>
<source>Repository Property</source>
<oldsource>Repository Properties</oldsource>
<translation>Свойства репозитория</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="65"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="95"/>
<source>Local dir :</source>
<translation>Локальный каталог:</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="114"/>
<source>Remote URLs</source>
<translation>URL источников</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="124"/>
<source>Remote</source>
<translation>Источник</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="130"/>
<source>Add</source>
<translation>Добавить</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="137"/>
<source>Edit</source>
<translation>Редактировать</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="157"/>
<source>Remove</source>
<translation>Удалить</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="169"/>
<source>&amp;Remote menu</source>
<translation>Меню источника (&amp;R)</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="189"/>
<source>Close</source>
<translation>Закрыть</translation>
</message>
<message>
- <location filename="../../RepositoryPropertyDialog.cpp" line="131"/>
+ <location filename="../../RepositoryPropertyDialog.cpp" line="155"/>
<source>Confirm Remove</source>
<translation>Подтвердите удаление</translation>
</message>
<message>
- <location filename="../../RepositoryPropertyDialog.cpp" line="131"/>
+ <location filename="../../RepositoryPropertyDialog.cpp" line="155"/>
<source>Are you sure you want to remove the remote &apos;%1&apos; from the repository &apos;%2&apos; ?</source>
<translation>Вы точно хотите удалить источник &apos;%1&apos; из репозитория &apos;%2&apos; ?</translation>
</message>
</context>
<context>
<name>SearchFromGitHubDialog</name>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="14"/>
<source>Search From GitHub</source>
<translation>Поиск на GitHub</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="25"/>
<source>Search</source>
<translation>Поиск</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="78"/>
<source>ssh</source>
<translation></translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="88"/>
<source>http</source>
<translation></translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="115"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="122"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="56"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="57"/>
<source>Owner</source>
<translation>Владелец</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="58"/>
<source>Score</source>
<translation>Популярность</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="59"/>
<source>Description</source>
<translation>Описание</translation>
</message>
</context>
<context>
<name>SelectCommandDialog</name>
<message>
<location filename="../../SelectCommandDialog.ui" line="14"/>
<source>Select git command</source>
<translation>Выбрать команду git</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="20"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="32"/>
<source>&amp;Browse...</source>
<translation>&amp;Обзор...</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="52"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="59"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="29"/>
<source>Please select the &apos;%1&apos; command you want to use.</source>
<translation>Выберите &apos;%1&apos; команду, которую вы хотите выполнить.</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="58"/>
<location filename="../../SelectCommandDialog.cpp" line="65"/>
<source>%1 command (%2);;</source>
<translation>%1 команда (%2);;</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="60"/>
<source>Executable files (*.exe)</source>
<translation>Исполняемые файлы (*.exe)</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="71"/>
<source>%1 command</source>
<translation>%1 команда</translation>
</message>
</context>
<context>
<name>SelectGpgKeyDialog</name>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="14"/>
<source>Select GPG Key</source>
<translation>Выбор ключа GPG</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="57"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="64"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="31"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="32"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="33"/>
<source>Mail</source>
<translation></translation>
</message>
</context>
<context>
<name>SelectItemDialog</name>
<message>
<location filename="../../SelectItemDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectItemDialog.ui" line="25"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectItemDialog.ui" line="32"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>SetGlobalUserDialog</name>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="14"/>
<source>Global User Setting</source>
<translation>Глобальная настройка пользователя</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="31"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="41"/>
<source>Mail</source>
<translation></translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="68"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="78"/>
<source>Skip</source>
<translation>Пропустить</translation>
</message>
</context>
<context>
<name>SetGpgSigningDialog</name>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="14"/>
<source>Set GPG Signing</source>
<translation>Настройка подписей GPG</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="26"/>
<source>Global</source>
<translation>Глобально</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="39"/>
<location filename="../../SetGpgSigningDialog.cpp" line="29"/>
<source>Repository</source>
<translation>Репозиторий</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="61"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="71"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="81"/>
<source>Mail</source>
<translation></translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="93"/>
<source>Select</source>
<translation>Выбрать</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="113"/>
<source>Clear</source>
<translation>Очистить</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="127"/>
<source>Configure...</source>
<translation>Настроить...</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="147"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="154"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>SetRemoteUrlDialog</name>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="14"/>
<source>Set Remote URL</source>
<translation>Установка URL источника</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="20"/>
<source>Current URLs</source>
<translation>Текущий URL</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="30"/>
<source>New URL</source>
<translation>Новый URL</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="36"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="91"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="98"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="43"/>
<source>URL</source>
<translation></translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="63"/>
<source>&amp;Test</source>
<translation>&amp;Тестировать</translation>
</message>
</context>
<context>
<name>SetUserDialog</name>
<message>
<location filename="../../SetUserDialog.ui" line="14"/>
<source>Set User</source>
<translation>Выбрать пользователя</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="20"/>
<source>Global</source>
<translation>Глобально</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="27"/>
<location filename="../../SetUserDialog.cpp" line="31"/>
<source>Repository</source>
<translation>Репозиторий</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="42"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="52"/>
<source>Mail</source>
<translation></translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="66"/>
<source>Get icon from Gravatar</source>
<translation>Получить аватар с Gravatar</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="122"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="132"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>SettingBehaviorForm</name>
<message>
<location filename="../../SettingBehaviorForm.ui" line="14"/>
<source>Behavior</source>
<translation>Поведение</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="20"/>
<location filename="../../SettingBehaviorForm.cpp" line="43"/>
<source>Default working folder</source>
<translation>Рабочий каталог по-умолчанию</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="29"/>
<source>Browse...</source>
<translation>Обзор...</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="39"/>
<source>Automatically fetch when opening the repository</source>
<translation>Автоматически загружать при открытии репозитория</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="46"/>
<source>Get committer&apos;s icon from gravatar.com</source>
<translation>Получить аватар коммитера с gravatar.com</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="61"/>
<source>Maximum number of commit item acquisitions</source>
<translation>Максимальное число загружаемых коммитов</translation>
</message>
<message>
<source>Watch remote changes periodically</source>
<oldsource>Periodically watch remote updates</oldsource>
<translation type="vanished">Отслеживать изменения в источнике</translation>
</message>
<message>
<source>interval</source>
<oldsource>interval in min:</oldsource>
<translation type="vanished">интервал</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="94"/>
<source>GPG signing policy</source>
<oldsource>GPG Signing Policy</oldsource>
<translation>Политика подписания</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="101"/>
<source>Configure...</source>
<translation>Настроить...</translation>
</message>
<message>
<source>Disable</source>
<translation type="vanished">Выключить</translation>
</message>
<message>
<source>1 min</source>
<translation type="vanished">1 минута</translation>
</message>
<message>
<source>%1 mins</source>
<translation type="vanished">%1 минут</translation>
</message>
</context>
<context>
<name>SettingExampleForm</name>
<message>
<location filename="../../SettingExampleForm.ui" line="14"/>
<source>Example</source>
<translation></translation>
</message>
<message>
<location filename="../../SettingExampleForm.ui" line="20"/>
<source>Underconstruction</source>
<translation></translation>
</message>
</context>
<context>
<name>SettingGeneralForm</name>
<message>
<location filename="../../SettingGeneralForm.ui" line="14"/>
<source>General</source>
<translation>Общие</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="20"/>
<source>Language</source>
<translation>Язык</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="31"/>
<source>Change Language...</source>
<translation>Изменить язык...</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="54"/>
<source>Change Theme...</source>
<translation>Изменить тему...</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="66"/>
<source>Remember and restore window position</source>
<translation>Помнить позицию и размер окна</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="73"/>
<source>Enable high DPI scaling</source>
<translation>Включить highDPI масштабирование</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="43"/>
<source>Theme</source>
<translation>Тема</translation>
</message>
<message>
<source>Japanese</source>
<translation type="vanished">Японский</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="19"/>
<source>Standard</source>
<translation>Стандартная</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="20"/>
<source>Dark</source>
<translation>Тёмная</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="82"/>
<source>Select Language</source>
<translation>Выбор языка</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="106"/>
<source>Select Theme</source>
<translation>Выбор темы</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="93"/>
<source>(Changes are applied at next run)</source>
<translation>(Изменения применятся при следующем запуске)</translation>
</message>
</context>
<context>
<name>SettingNetworkForm</name>
<message>
<location filename="../../SettingNetworkForm.ui" line="14"/>
<source>Network</source>
<translation>Сеть</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="20"/>
<source>Proxy server</source>
<oldsource>Proxy Server</oldsource>
<translation>Прокси-сервер</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="26"/>
<source>No proxy</source>
<oldsource>No Proxy</oldsource>
<translation>Без прокси</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="33"/>
<source>Auto detect</source>
<oldsource>Auto Detect</oldsource>
<translation>Авто-определение</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="40"/>
<source>Manual</source>
<translation>Вручную</translation>
</message>
</context>
<context>
<name>SettingProgramsForm</name>
<message>
<location filename="../../SettingProgramsForm.ui" line="14"/>
<source>Programs</source>
<translation>Программы</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="20"/>
<source>Git command</source>
<translation>путь к git</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="29"/>
<location filename="../../SettingProgramsForm.ui" line="48"/>
<location filename="../../SettingProgramsForm.ui" line="67"/>
+ <location filename="../../SettingProgramsForm.ui" line="86"/>
<source>Browse...</source>
<translation>Обзор...</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="39"/>
<source>File command</source>
<translation>путь к file</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="58"/>
<source>GPG command (option)</source>
<translation>путь к gpg (опционально)</translation>
</message>
+ <message>
+ <location filename="../../SettingProgramsForm.ui" line="77"/>
+ <source>SSH command (option)</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../../SettingsDialog.ui" line="14"/>
<source>Settings</source>
<translation>Настройки</translation>
</message>
<message>
<location filename="../../SettingsDialog.ui" line="141"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../SettingsDialog.ui" line="148"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
</context>
<context>
<name>TextEditDialog</name>
<message>
<location filename="../../TextEditDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../TextEditDialog.ui" line="48"/>
<source>OK</source>
<translation></translation>
</message>
<message>
<location filename="../../TextEditDialog.ui" line="58"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../TextEditDialog.cpp" line="29"/>
<source>&amp;Close</source>
<translation type="unfinished">&amp;Закрыть</translation>
</message>
<message>
<location filename="../../TextEditDialog.cpp" line="29"/>
<source>Cacnel</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>WelcomeWizardDialog</name>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="14"/>
<source>Welcome to the Guitar Wizard</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="45"/>
<source>Helper Tools</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="61"/>
<location filename="../../WelcomeWizardDialog.ui" line="338"/>
<source>git</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="71"/>
<location filename="../../WelcomeWizardDialog.ui" line="88"/>
<location filename="../../WelcomeWizardDialog.ui" line="248"/>
<source>Browse</source>
<translation type="unfinished">Обзор</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="78"/>
<location filename="../../WelcomeWizardDialog.ui" line="352"/>
<source>file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="120"/>
<source>Global User Information</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="142"/>
<source>git config --global user.name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="152"/>
<source>git config --global user.email</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="168"/>
<source>Get icon from Gravatar</source>
<translation type="unfinished">Получить аватар с Gravatar</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="222"/>
<location filename="../../WelcomeWizardDialog.cpp" line="163"/>
<source>Default Working Folder</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="238"/>
<location filename="../../WelcomeWizardDialog.ui" line="324"/>
<source>folder</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="280"/>
<source>Ready to play the Guitar !</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="296"/>
<source>user</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="310"/>
<source>email</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="426"/>
<location filename="../../WelcomeWizardDialog.cpp" line="155"/>
<source>&lt;&lt; Prev</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="433"/>
<location filename="../../WelcomeWizardDialog.cpp" line="156"/>
<source>Next &gt;&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.cpp" line="126"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.cpp" line="152"/>
<source>Finish</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>
diff --git a/src/resources/translations/Guitar_zh-CN.qm b/src/resources/translations/Guitar_zh-CN.qm
index 4b5d2d8..e167ee6 100644
Binary files a/src/resources/translations/Guitar_zh-CN.qm and b/src/resources/translations/Guitar_zh-CN.qm differ
diff --git a/src/resources/translations/Guitar_zh-CN.ts b/src/resources/translations/Guitar_zh-CN.ts
index 79866af..d6d12d7 100644
--- a/src/resources/translations/Guitar_zh-CN.ts
+++ b/src/resources/translations/Guitar_zh-CN.ts
@@ -1,2994 +1,3049 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>AboutDialog</name>
<message>
<location filename="../../AboutDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../AboutDialog.ui" line="34"/>
<location filename="../../AboutDialog.ui" line="46"/>
<location filename="../../AboutDialog.ui" line="58"/>
<location filename="../../AboutDialog.ui" line="70"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../AboutDialog.ui" line="86"/>
<source>&lt;a href=&quot;https://github.com/soramimi/Guitar&quot;&gt;https://github.com/soramimi/Guitar&lt;/a&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../AboutDialog.cpp" line="25"/>
<source>About %1</source>
<translation>%1 关于</translation>
</message>
</context>
<context>
<name>AreYouSureYouWantToContinueConnectingDialog</name>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="14"/>
<source>Unknown Host</source>
<translation>未知主机</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="20"/>
<source>Are you sure you want to continue connecting (yes/no)?</source>
<translation>你想继续连接吗?(是/否)?</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="52"/>
<source>Continue</source>
<translation>继续</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="59"/>
<source>Close</source>
<translation>关闭</translation>
</message>
</context>
<context>
<name>BasicMainWindow</name>
<message>
- <location filename="../../BasicMainWindow.cpp" line="261"/>
+ <location filename="../../BasicMainWindow.cpp" line="243"/>
<source>git command not specified</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="374"/>
+ <location filename="../../BasicMainWindow.cpp" line="358"/>
<source>The URL is a valid repository</source>
<translation>此URL是有效的仓库</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="375"/>
+ <location filename="../../BasicMainWindow.cpp" line="359"/>
<source>Failed to access the URL</source>
<translation>无法访问此URL</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="380"/>
+ <location filename="../../BasicMainWindow.cpp" line="364"/>
<source>Remote Repository</source>
<translation>远程仓库</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="405"/>
+ <location filename="../../BasicMainWindow.cpp" line="389"/>
<source>&amp;Property</source>
<translation>属性(&amp;P)</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1273"/>
+ <location filename="../../BasicMainWindow.cpp" line="1272"/>
<source>Select %1 command</source>
<translation>选择 %1 命令</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1452"/>
+ <location filename="../../BasicMainWindow.cpp" line="1447"/>
<source>Revert all files</source>
<translation>放弃所有文件更改</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1610"/>
+ <location filename="../../BasicMainWindow.cpp" line="1605"/>
<source>The folder is not a valid git repository.</source>
<translation>文件夹不是有效的Git仓库。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1614"/>
+ <location filename="../../BasicMainWindow.cpp" line="1609"/>
<source>Do you want to initialize it as a git repository ?</source>
<translation>是否要将其初始化为Git仓库?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1615"/>
+ <location filename="../../BasicMainWindow.cpp" line="1610"/>
<source>Initialize Repository</source>
<translation>初始化仓库</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1745"/>
+ <location filename="../../BasicMainWindow.cpp" line="1740"/>
<source>No repository selected</source>
<translation>未选择仓库</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1915"/>
<source>Repository Property</source>
- <translation>仓库属性</translation>
+ <translation type="vanished">仓库属性</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1915"/>
<location filename="../../BasicMainWindow.cpp" line="2017"/>
<source>Not a valid git repository</source>
<translation>不是有效的仓库。</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<location filename="../../BasicMainWindow.cpp" line="2017"/>
<source>Open Repository</source>
<translation>打开仓库く</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<source>No such folder</source>
<translation>没有这样的文件夹</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<source>Remove from bookmark ?</source>
<translation>是否确实要从书签中删除?</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2248"/>
<source>, %1 ahead</source>
<translation></translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2251"/>
<source>, %1 behind</source>
<translation></translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>Confirm Remove</source>
<translation>确认删除</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>Are you sure you want to remove the repository from bookmarks ?</source>
<translation>是否确实要从书签中删除仓库?</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>(Files will NOT be deleted)</source>
<translation>(文件未被删除)</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2302"/>
<source>A file with same name already exists</source>
<translation>同名的文件已存在</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2303"/>
<location filename="../../BasicMainWindow.cpp" line="2308"/>
<location filename="../../BasicMainWindow.cpp" line="2325"/>
<location filename="../../BasicMainWindow.cpp" line="2330"/>
<source>Clone</source>
<translation>克隆</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2307"/>
<source>A folder with same name already exists</source>
<translation>同名的文件夹已存在</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2324"/>
<source>Invalid folder</source>
<translation>无效文件夹</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2329"/>
<source>No such folder. Create it now ?</source>
<translation>此文件夹不存在。是否要创建?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2398"/>
+ <location filename="../../BasicMainWindow.cpp" line="2402"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2398"/>
+ <location filename="../../BasicMainWindow.cpp" line="2402"/>
<source>Commit message can not be omitted.</source>
<translation>提交消息不能为空。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2415"/>
+ <location filename="../../BasicMainWindow.cpp" line="2419"/>
<source>Failed to commit</source>
<translation>提交失败</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2449"/>
- <location filename="../../BasicMainWindow.cpp" line="2533"/>
+ <location filename="../../BasicMainWindow.cpp" line="2453"/>
+ <location filename="../../BasicMainWindow.cpp" line="2538"/>
<source>Connection refused.</source>
<translation>连接被拒绝。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2502"/>
+ <location filename="../../BasicMainWindow.cpp" line="2506"/>
<source>No remote repository is registered.</source>
<translation>远程仓库未注册。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2524"/>
+ <location filename="../../BasicMainWindow.cpp" line="2529"/>
<source>The current branch %1 has no upstream branch.</source>
<translation>目前的分支「%1」没有上游分支。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2527"/>
+ <location filename="../../BasicMainWindow.cpp" line="2532"/>
<source>You try push --set-upstream</source>
<translation>请尝试--set-upstream </translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2616"/>
+ <location filename="../../BasicMainWindow.cpp" line="2621"/>
<source>Failed to delete the branch &apos;%1&apos;</source>
<translation>无法删除分支「%1」</translation>
</message>
<message>
<source>Failed to delete the branch &apos;%1&apos;
</source>
<translation type="vanished">无法删除分支: &apos;%1&apos;</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2680"/>
+ <location filename="../../BasicMainWindow.cpp" line="2685"/>
<source>Are you sure you want to run the following command ?</source>
<translation>您确定要运行以下命令吗?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2701"/>
+ <location filename="../../BasicMainWindow.cpp" line="2706"/>
<source>Reset a file</source>
<translation>重置文件</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2715"/>
+ <location filename="../../BasicMainWindow.cpp" line="2720"/>
<source>Unnamed</source>
<translation>未命名</translation>
</message>
</context>
<context>
<name>BasicRepositoryDialog</name>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="66"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="77"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="67"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="78"/>
<source>Purpose</source>
<translation>用途</translation>
</message>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="68"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="79"/>
<source>URL</source>
<translation></translation>
</message>
</context>
<context>
<name>BigDiffWindow</name>
<message>
<location filename="../../BigDiffWindow.ui" line="14"/>
<source>Diff</source>
<translation>差异</translation>
</message>
</context>
<context>
<name>BlameWindow</name>
<message>
<location filename="../../BlameWindow.ui" line="14"/>
<source>Blame</source>
<translation>作者及修订版本信息</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="45"/>
<source>Information</source>
<translation>信息</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="60"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="70"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="90"/>
<source>Message</source>
<translation>消息</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="80"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="118"/>
<source>Close</source>
<translation>关闭</translation>
</message>
</context>
<context>
<name>CheckoutDialog</name>
<message>
<location filename="../../CheckoutDialog.ui" line="14"/>
<source>Checkout</source>
<translation>检出</translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="20"/>
<location filename="../../CheckoutDialog.ui" line="27"/>
<location filename="../../CheckoutDialog.ui" line="34"/>
<source>RadioButton</source>
<translation></translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="63"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="70"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>CherryPickDialog</name>
<message>
<location filename="../../CherryPickDialog.ui" line="14"/>
<source>Cherry-pick</source>
<translation type="unfinished">择优拣选</translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="20"/>
<source>HEAD</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="49"/>
<source>Pick</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="78"/>
<source>Mainline</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="109"/>
<source>Allow empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="131"/>
<source>OK</source>
<translation type="unfinished">确定</translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="138"/>
<source>Cancel</source>
<translation type="unfinished">取消</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="19"/>
<source>Commit</source>
<translation type="unfinished">提交</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="20"/>
<source>Date</source>
<translation type="unfinished">日期</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="21"/>
<source>Author</source>
<translation type="unfinished">作者</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="22"/>
<source>Message</source>
<translation type="unfinished">消息</translation>
</message>
</context>
<context>
<name>CloneDialog</name>
<message>
<location filename="../../CloneDialog.ui" line="14"/>
- <location filename="../../CloneDialog.ui" line="103"/>
+ <location filename="../../CloneDialog.ui" line="113"/>
<source>Clone</source>
<translation>克隆</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="22"/>
<source>Remote</source>
<translation>远程</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="39"/>
<source>&amp;Test</source>
<translation>测试(&amp;T)</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="46"/>
<source>Local</source>
<translation>本地</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="56"/>
<source>Browse</source>
<translation>浏览</translation>
</message>
<message>
- <location filename="../../CloneDialog.ui" line="83"/>
+ <location filename="../../CloneDialog.ui" line="93"/>
<source>Open existing local directory...</source>
<translation>打开现有文件夹く...</translation>
</message>
<message>
- <location filename="../../CloneDialog.ui" line="113"/>
+ <location filename="../../CloneDialog.ui" line="123"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="40"/>
+ <location filename="../../CloneDialog.cpp" line="41"/>
<source>Search</source>
<translation>搜索</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="41"/>
+ <location filename="../../CloneDialog.cpp" line="42"/>
<source>GitHub</source>
<translation></translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="113"/>
+ <location filename="../../CloneDialog.cpp" line="115"/>
<source>Checkout into</source>
<translation>检出到</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="125"/>
+ <location filename="../../CloneDialog.cpp" line="127"/>
<source>Open existing directory</source>
<translation>打开现有文件夹</translation>
</message>
</context>
<context>
<name>CloneFromGitHubDialog</name>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="14"/>
<source>Clone from GitHub</source>
<translation>从GitHub克隆</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="45"/>
<source>ssh</source>
<translation>ssh</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="55"/>
<source>http</source>
<translation>http</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="95"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="102"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>CommitDialog</name>
<message>
<location filename="../../CommitDialog.ui" line="14"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="27"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="49"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="68"/>
<location filename="../../CommitDialog.ui" line="94"/>
<location filename="../../CommitDialog.ui" line="139"/>
<location filename="../../CommitDialog.ui" line="159"/>
<location filename="../../CommitDialog.ui" line="179"/>
<source>---</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="75"/>
<location filename="../../CommitDialog.ui" line="166"/>
<source>Mail</source>
<translation>邮箱</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="111"/>
<source>GPG Signing</source>
<translation>GPG签名</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="126"/>
<source>ID</source>
<translation>ID</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="146"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="189"/>
<source>Configure...</source>
<translation>配置...</translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="198"/>
+ <location filename="../../CommitDialog.ui" line="203"/>
<source>Message</source>
<translation>消息</translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="223"/>
+ <location filename="../../CommitDialog.ui" line="210"/>
+ <source>Amend</source>
+ <translation>修改</translation>
+ </message>
+ <message>
+ <location filename="../../CommitDialog.ui" line="237"/>
<source>OK</source>
<translation></translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="233"/>
+ <location filename="../../CommitDialog.ui" line="247"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>CommitExploreWindow</name>
<message>
<location filename="../../CommitExploreWindow.ui" line="14"/>
<source>Commit Explorer</source>
<translation>提交浏览器</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="65"/>
<source>Commit ID</source>
<translation>提交ID</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="82"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="89"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.cpp" line="78"/>
<source>Commit</source>
<translation>提交</translation>
</message>
</context>
<context>
<name>CommitPropertyDialog</name>
<message>
<location filename="../../CommitPropertyDialog.ui" line="14"/>
<source>Commit Property</source>
<oldsource>Commit Properties</oldsource>
<translation>提交属性</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="46"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="56"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="66"/>
<location filename="../../CommitPropertyDialog.ui" line="196"/>
<source>Mail</source>
<translation>邮箱</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="154"/>
<source>GPG Sign</source>
<translation>GPG签名</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="176"/>
<source>ID</source>
<translation>ID</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="186"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="233"/>
<source>Commit ID</source>
<translation>提交ID</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="243"/>
<source>Parent IDs</source>
<translation>父ID</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="265"/>
<source>Files...</source>
<translation>文件...</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="285"/>
<source>Explorer</source>
<translation>浏览器</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="305"/>
<source>Checkout</source>
<translation>检出</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="312"/>
<source>Jump</source>
<translation>跳转</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="332"/>
<source>Close</source>
<translation>关闭</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.cpp" line="60"/>
<source>&lt;Unknown&gt;</source>
<translation>&lt;未知的&gt;</translation>
</message>
</context>
<context>
<name>CommitViewWindow</name>
<message>
<location filename="../../CommitViewWindow.ui" line="14"/>
<source>Commit View</source>
<translation>提交视图</translation>
</message>
<message>
<location filename="../../CommitViewWindow.cpp" line="58"/>
<source>History</source>
<translation>历史</translation>
</message>
</context>
<context>
<name>ConfigCredentialHelperDialog</name>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="26"/>
<source>wincred</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="39"/>
<source>winstore</source>
<translation></translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="52"/>
<source>None</source>
<translation>无</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="65"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="78"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="91"/>
<source>Other</source>
<translation>其他</translation>
</message>
</context>
<context>
<name>ConfigSigningDialog</name>
<message>
<location filename="../../ConfigSigningDialog.ui" line="14"/>
<source>Signing Policy</source>
<translation>签名策略</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="20"/>
<source>Config commit.gpgsign</source>
<translation>配置commit.gpgsign</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="32"/>
<source>global</source>
<translation>全局</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="42"/>
<source>local</source>
<translation>局部</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="70"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="77"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>CreateRepositoryDialog</name>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="14"/>
<location filename="../../CreateRepositoryDialog.cpp" line="42"/>
<location filename="../../CreateRepositoryDialog.cpp" line="46"/>
<location filename="../../CreateRepositoryDialog.cpp" line="50"/>
<location filename="../../CreateRepositoryDialog.cpp" line="54"/>
<source>Create Repository</source>
<translation>创建仓库</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="22"/>
<source>Path</source>
<translation>路径</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="32"/>
<source>Browse</source>
<translation>浏览</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="54"/>
<source>Bookmark</source>
<translation>书签</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="63"/>
<location filename="../../CreateRepositoryDialog.ui" line="88"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="76"/>
<source>Remote</source>
<translation>远程</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="98"/>
<source>URL</source>
<translation>URL</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="108"/>
<source>Test</source>
<translation>测试</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="146"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="153"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="19"/>
<source>A valid git repository already exists there.</source>
<translation>已存在有效的仓库。</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="42"/>
<location filename="../../CreateRepositoryDialog.cpp" line="50"/>
<source>The specified path is not a directory.</source>
<translation>指定的路径不是文件夹。</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="54"/>
<source>Remote name is invalid.</source>
<translation>远程名称无效。</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="62"/>
<source>Destination Path</source>
<translation>目标路径</translation>
</message>
</context>
<context>
<name>DeleteBranchDialog</name>
<message>
<location filename="../../DeleteBranchDialog.ui" line="14"/>
<source>Delete Branch</source>
<translation>删除分支</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="25"/>
<source>&amp;All branches</source>
<translation>所有分支(&amp;A)</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="45"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="52"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.cpp" line="23"/>
<source>Delete Remote Branch</source>
<translation>删除远程分支</translation>
</message>
</context>
<context>
<name>DeleteTagsDialog</name>
<message>
<location filename="../../DeleteTagsDialog.ui" line="14"/>
<source>Delete tags</source>
<translation>删除标签</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="32"/>
<source>Check all</source>
<translation>检查所有</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="52"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="62"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>DoYouWantToInitDialog</name>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="14"/>
<source>Init Repository</source>
<translation>初始化仓库</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="20"/>
<source>The folder:</source>
<translation>文件夹:</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="32"/>
<source>is not a valid git working copy.</source>
<translation>不是有效的Git仓库。</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="39"/>
<source>Do you want to init it as a git repository ?</source>
<translation>您想将其初始化为Git仓库吗?</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="81"/>
<source>Yes, Please init it.</source>
<translation>是,请初始化。</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="88"/>
<source>No, Stop it.</source>
<translation>否,不要初始化。</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="140"/>
<source>Please choose option</source>
<translation>请选择</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.cpp" line="20"/>
<source>Next...</source>
<translation>下一步...</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.cpp" line="26"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>EditGitIgnoreDialog</name>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="14"/>
<source>Edit Git Ignore</source>
<translation>编辑Git忽略的文件</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="20"/>
<location filename="../../EditGitIgnoreDialog.ui" line="27"/>
<location filename="../../EditGitIgnoreDialog.ui" line="34"/>
<location filename="../../EditGitIgnoreDialog.ui" line="41"/>
<source>RadioButton</source>
<translation></translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="63"/>
<source>Edit the file</source>
<translation>编辑文件</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="83"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="90"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>EditRemoteDialog</name>
<message>
<location filename="../../EditRemoteDialog.ui" line="14"/>
<source>Edit Remote</source>
<translation>远程编辑</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="20"/>
<source>Remote</source>
<translation>远程</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="26"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="33"/>
<source>URL</source>
<translation>URL</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="53"/>
<source>&amp;Test</source>
<translation>测试(&amp;T)</translation>
</message>
<message>
- <location filename="../../EditRemoteDialog.ui" line="81"/>
+ <location filename="../../EditRemoteDialog.ui" line="104"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
- <location filename="../../EditRemoteDialog.ui" line="88"/>
+ <location filename="../../EditRemoteDialog.ui" line="111"/>
<source>Close</source>
<translation>关闭</translation>
</message>
</context>
<context>
<name>EditTagsDialog</name>
<message>
<location filename="../../EditTagsDialog.ui" line="14"/>
<source>Edit Tags</source>
<translation>编辑标签</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="41"/>
<source>Add...</source>
<translation>添加...</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="48"/>
<source>Delete</source>
<translation>删除</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="68"/>
<source>Close</source>
<translation>关闭</translation>
</message>
</context>
<context>
<name>ExperimentDialog</name>
<message>
<location filename="../../ExperimentDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
</context>
<context>
<name>FileDiffWidget</name>
<message>
<location filename="../../FileDiffWidget.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
</context>
<context>
<name>FileHistoryWindow</name>
<message>
<location filename="../../FileHistoryWindow.ui" line="14"/>
<source>File History</source>
<translation>文件历史</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.ui" line="20"/>
<location filename="../../FileHistoryWindow.ui" line="27"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="111"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="112"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="113"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="114"/>
<source>Message</source>
<translation>消息</translation>
</message>
<message>
<source>Description</source>
<translation type="vanished">描述</translation>
</message>
</context>
<context>
<name>FilePropertyDialog</name>
<message>
<location filename="../../FilePropertyDialog.ui" line="14"/>
<source>File Property</source>
<oldsource>File Properties</oldsource>
<translation>文件属性</translation>
</message>
<message>
<location filename="../../FilePropertyDialog.ui" line="78"/>
<source>&amp;Close</source>
<translation>关闭(&amp;C)</translation>
</message>
</context>
+<context>
+ <name>FileViewWidget</name>
+ <message>
+ <location filename="../../FileViewWidget.cpp" line="30"/>
+ <source>Form</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>FindCommitDialog</name>
<message>
<location filename="../../FindCommitDialog.ui" line="14"/>
<source>Find Commit</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../FindCommitDialog.ui" line="54"/>
<source>OK</source>
<translation type="unfinished">确定</translation>
</message>
<message>
<location filename="../../FindCommitDialog.ui" line="61"/>
<source>Cancel</source>
<translation type="unfinished">取消</translation>
</message>
</context>
<context>
<name>InputNewTagDialog</name>
<message>
<location filename="../../InputNewTagDialog.ui" line="14"/>
<source>Edit tag</source>
<translation>编辑标签</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="31"/>
<source>Tag</source>
<translation>标签</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="58"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="65"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>JumpDialog</name>
<message>
<location filename="../../JumpDialog.ui" line="14"/>
<source>Jump</source>
<translation>跳转</translation>
</message>
<message>
<source>Branches and Tags</source>
<translation type="vanished">分支和标签</translation>
</message>
<message>
<source>&amp;Filter</source>
<translation type="vanished">过滤器(&amp;F)</translation>
</message>
<message>
<source>Find</source>
<translation type="vanished">查找</translation>
</message>
<message>
<source>&amp;Checkout</source>
<translation type="vanished">检出(&amp;C)</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="20"/>
<source>Branch, Tag or Commit-ID :</source>
<translation>分支,标签,提交ID</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="71"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="81"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../JumpDialog.cpp" line="39"/>
<source>Name</source>
<translation>名称</translation>
</message>
</context>
<context>
<name>Languages</name>
<message>
<location filename="../../Languages.cpp" line="6"/>
<source>English</source>
<translation type="unfinished">英语</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="7"/>
<source>Japanese</source>
<translation type="unfinished">日语</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="8"/>
<source>Russian</source>
<translation type="unfinished">俄语</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="9"/>
<source>Chinese (Simplified)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../Languages.cpp" line="10"/>
<source>Chinese (Traditional/Taiwan)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>LineEditDialog</name>
<message>
<location filename="../../LineEditDialog.ui" line="14"/>
<source>Dialog</source>
<translation>行编辑</translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="20"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="45"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="52"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../MainWindow.ui" line="14"/>
<source>Guitar</source>
<translation>Guitar</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="96"/>
- <location filename="../../MainWindow.ui" line="1421"/>
- <location filename="../../MainWindow.ui" line="1424"/>
+ <location filename="../../MainWindow.ui" line="1416"/>
+ <location filename="../../MainWindow.ui" line="1419"/>
<source>Clone</source>
<translation>克隆</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="131"/>
- <location filename="../../MainWindow.cpp" line="1140"/>
- <location filename="../../MainWindow.cpp" line="1141"/>
+ <location filename="../../MainWindow.cpp" line="1135"/>
+ <location filename="../../MainWindow.cpp" line="1136"/>
<source>Fetch</source>
<translation>取出</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="166"/>
<source>Pull</source>
<translation>拉取</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="201"/>
<source>Push</source>
<translation>推送</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="303"/>
- <location filename="../../MainWindow.ui" line="1548"/>
+ <location filename="../../MainWindow.ui" line="1543"/>
<source>Terminal</source>
<translation>终端</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="338"/>
- <location filename="../../MainWindow.ui" line="1557"/>
+ <location filename="../../MainWindow.ui" line="1552"/>
<source>Explorer</source>
<translation>资源管理器</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="424"/>
<source>Repository</source>
<translation>仓库</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="431"/>
<source>Branch Name</source>
<translation>分支名称</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="483"/>
- <location filename="../../MainWindow.ui" line="1592"/>
+ <location filename="../../MainWindow.ui" line="1587"/>
<source>Offline</source>
<translation>离线</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="473"/>
- <location filename="../../MainWindow.ui" line="1587"/>
+ <location filename="../../MainWindow.ui" line="1582"/>
<source>Online</source>
<translation>在线</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="799"/>
- <location filename="../../MainWindow.cpp" line="1853"/>
+ <location filename="../../MainWindow.cpp" line="1848"/>
<source>Unstage</source>
<translation>unstage</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="828"/>
<source>Select all</source>
<translation>全选</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="863"/>
- <location filename="../../MainWindow.cpp" line="1723"/>
+ <location filename="../../MainWindow.cpp" line="1718"/>
<source>Stage</source>
<translation>stage</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="892"/>
- <location filename="../../MainWindow.cpp" line="879"/>
+ <location filename="../../MainWindow.cpp" line="874"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="1023"/>
<source>&amp;File</source>
<translation>文件(&amp;F)</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="1034"/>
<source>&amp;View</source>
<translation>表示(&amp;V)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1040"/>
+ <location filename="../../MainWindow.ui" line="1041"/>
<source>&amp;Edit</source>
<translation>编辑(&amp;E)</translation>
</message>
<message>
<source>Destructive</source>
<translation type="vanished">需要注意的命令</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1412"/>
+ <location filename="../../MainWindow.ui" line="1407"/>
<source>Settings</source>
<translation>设置</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1434"/>
- <location filename="../../MainWindow.cpp" line="1595"/>
+ <location filename="../../MainWindow.ui" line="1429"/>
+ <location filename="../../MainWindow.cpp" line="1590"/>
<source>Edit tags...</source>
<translation>编辑标签...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1437"/>
+ <location filename="../../MainWindow.ui" line="1432"/>
<source>Edit tags</source>
<translation>编辑标签</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1562"/>
+ <location filename="../../MainWindow.ui" line="1557"/>
<source>Clean -df</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1567"/>
+ <location filename="../../MainWindow.ui" line="1562"/>
<source>Reset --hard</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1494"/>
+ <location filename="../../MainWindow.ui" line="1489"/>
<source>Create a repository</source>
<translation>创建仓库</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1481"/>
+ <location filename="../../MainWindow.ui" line="1476"/>
<source>Push upstream</source>
<translation>推送到上游</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1503"/>
+ <location filename="../../MainWindow.ui" line="1498"/>
<source>Stop process</source>
<translation>停止处理</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1508"/>
+ <location filename="../../MainWindow.ui" line="1503"/>
<source>E&amp;xit</source>
<translation>退出(&amp;X)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1511"/>
+ <location filename="../../MainWindow.ui" line="1506"/>
<source>Ctrl+Q</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1516"/>
+ <location filename="../../MainWindow.ui" line="1511"/>
<source>Reflog...</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1521"/>
+ <location filename="../../MainWindow.ui" line="1516"/>
<source>Property...</source>
<translation>属性...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1526"/>
- <location filename="../../MainWindow.ui" line="1529"/>
+ <location filename="../../MainWindow.ui" line="1521"/>
+ <location filename="../../MainWindow.ui" line="1524"/>
<source>Set GPG signing</source>
<translation>设置GPG签名</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1539"/>
+ <location filename="../../MainWindow.ui" line="1534"/>
<source>Fetch --prune</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1055"/>
+ <location filename="../../MainWindow.ui" line="1056"/>
<source>&amp;Help</source>
<translation>帮助(&amp;H)</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="252"/>
- <location filename="../../MainWindow.ui" line="1628"/>
- <location filename="../../MainWindow.cpp" line="1353"/>
+ <location filename="../../MainWindow.ui" line="1623"/>
+ <location filename="../../MainWindow.cpp" line="1348"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="656"/>
<source>...</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1061"/>
+ <location filename="../../MainWindow.ui" line="1062"/>
<source>&amp;Window</source>
<translation>窗口(&amp;W)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1067"/>
+ <location filename="../../MainWindow.ui" line="1073"/>
<source>&amp;Repository</source>
<translation>仓库(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1071"/>
- <location filename="../../MainWindow.ui" line="1572"/>
+ <location filename="../../MainWindow.ui" line="1077"/>
+ <location filename="../../MainWindow.ui" line="1567"/>
<source>Stash</source>
<translation>贮存</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1098"/>
+ <location filename="../../MainWindow.ui" line="1104"/>
<source>Re&amp;mote</source>
<translation>远程(&amp;M)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1113"/>
+ <location filename="../../MainWindow.ui" line="1119"/>
<source>&amp;Destructive</source>
<translation>注意(&amp;D)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1125"/>
- <source>Experimental</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="../../MainWindow.ui" line="1155"/>
+ <location filename="../../MainWindow.ui" line="1150"/>
<source>Log</source>
<translation>日志</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1325"/>
+ <location filename="../../MainWindow.ui" line="1320"/>
<source>&amp;Open existing working copy...</source>
<translation>打开现有工作副本(&amp;O)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1328"/>
- <location filename="../../MainWindow.cpp" line="2027"/>
+ <location filename="../../MainWindow.ui" line="1323"/>
+ <location filename="../../MainWindow.cpp" line="2022"/>
<source>Add existing working copy</source>
<translation>添加现有工作副本</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1333"/>
- <location filename="../../MainWindow.ui" line="1336"/>
+ <location filename="../../MainWindow.ui" line="1328"/>
+ <location filename="../../MainWindow.ui" line="1331"/>
<source>Refresh</source>
<translation>刷新</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1339"/>
+ <location filename="../../MainWindow.ui" line="1334"/>
<source>F5</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1348"/>
+ <location filename="../../MainWindow.ui" line="1343"/>
<source>&amp;Commit</source>
<translation>提交(&amp;C)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1357"/>
+ <location filename="../../MainWindow.ui" line="1352"/>
<source>&amp;Push</source>
<translation>推送(&amp;P)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1365"/>
+ <location filename="../../MainWindow.ui" line="1360"/>
<source>test</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1368"/>
+ <location filename="../../MainWindow.ui" line="1363"/>
<source>Ctrl+T</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1377"/>
+ <location filename="../../MainWindow.ui" line="1372"/>
<source>Pu&amp;ll</source>
<translation>拉取(&amp;L)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1386"/>
+ <location filename="../../MainWindow.ui" line="1381"/>
<source>&amp;Fetch</source>
<translation>取出(&amp;F)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1391"/>
- <location filename="../../MainWindow.ui" line="1394"/>
+ <location filename="../../MainWindow.ui" line="1386"/>
+ <location filename="../../MainWindow.ui" line="1389"/>
<source>Edit global .gitconfig</source>
<translation>编辑全局.gitignore</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1399"/>
+ <location filename="../../MainWindow.ui" line="1394"/>
<source>Edit .git/config</source>
<translation>编辑.git/config</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1404"/>
+ <location filename="../../MainWindow.ui" line="1399"/>
<source>Edit .gitignore</source>
<translation>编辑.gitignore</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1409"/>
+ <location filename="../../MainWindow.ui" line="1404"/>
<source>&amp;Settings...</source>
<translation>设置(&amp;S)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1458"/>
+ <location filename="../../MainWindow.ui" line="1453"/>
<source>F4</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1463"/>
+ <location filename="../../MainWindow.ui" line="1458"/>
<source>&amp;Jump...</source>
<translation>跳转(&amp;J)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1466"/>
+ <location filename="../../MainWindow.ui" line="1461"/>
<source>Ctrl+J</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1471"/>
+ <location filename="../../MainWindow.ui" line="1466"/>
<source>Check&amp;out...</source>
<translation>检出(&amp;O)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1476"/>
- <location filename="../../MainWindow.cpp" line="1600"/>
+ <location filename="../../MainWindow.ui" line="1471"/>
+ <location filename="../../MainWindow.cpp" line="1595"/>
<source>Delete branch...</source>
<translation>删除分支...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1577"/>
+ <location filename="../../MainWindow.ui" line="1572"/>
<source>Apply</source>
<translation>应用</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1582"/>
+ <location filename="../../MainWindow.ui" line="1577"/>
<source>Drop</source>
<translation>丢弃</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1600"/>
+ <location filename="../../MainWindow.ui" line="1595"/>
<source>Repositories panel</source>
<translation>仓库面板</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1603"/>
+ <location filename="../../MainWindow.ui" line="1598"/>
<source>Ctrl+R</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1608"/>
+ <location filename="../../MainWindow.ui" line="1603"/>
<source>&amp;Find...</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1611"/>
+ <location filename="../../MainWindow.ui" line="1606"/>
<source>Ctrl+F</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1616"/>
+ <location filename="../../MainWindow.ui" line="1611"/>
<source>Find next</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1619"/>
+ <location filename="../../MainWindow.ui" line="1614"/>
<source>F3</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1633"/>
+ <location filename="../../MainWindow.ui" line="1628"/>
<source>Jump to &amp;HEAD</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1636"/>
+ <location filename="../../MainWindow.ui" line="1631"/>
<source>Ctrl+H</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1641"/>
+ <location filename="../../MainWindow.ui" line="1636"/>
<source>Merge...</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1646"/>
+ <location filename="../../MainWindow.ui" line="1641"/>
<source>Expand commit log</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1649"/>
+ <location filename="../../MainWindow.ui" line="1644"/>
<source>Ctrl+2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1654"/>
+ <location filename="../../MainWindow.ui" line="1649"/>
<source>Expand file list</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1657"/>
+ <location filename="../../MainWindow.ui" line="1652"/>
<source>Ctrl+3</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1662"/>
+ <location filename="../../MainWindow.ui" line="1657"/>
<source>Expand diff view</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1665"/>
+ <location filename="../../MainWindow.ui" line="1660"/>
<source>Ctrl+4</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1670"/>
+ <location filename="../../MainWindow.ui" line="1668"/>
<source>Sidebar</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1673"/>
- <source>F1</source>
+ <location filename="../../MainWindow.ui" line="1671"/>
+ <source>Ctrl+1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1678"/>
+ <location filename="../../MainWindow.ui" line="1687"/>
+ <source>Show labels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../MainWindow.ui" line="1690"/>
+ <source>Ctrl+L</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../MainWindow.ui" line="1676"/>
<source>Wide</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1681"/>
+ <location filename="../../MainWindow.ui" line="1679"/>
<source>F2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1486"/>
- <location filename="../../MainWindow.ui" line="1489"/>
+ <location filename="../../MainWindow.ui" line="1481"/>
+ <location filename="../../MainWindow.ui" line="1484"/>
<source>Reset HEAD~1</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1534"/>
- <location filename="../../MainWindow.cpp" line="1601"/>
+ <location filename="../../MainWindow.ui" line="1529"/>
+ <location filename="../../MainWindow.cpp" line="1596"/>
<source>Delete remote branch...</source>
<translation>删除远程分支...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1429"/>
+ <location filename="../../MainWindow.ui" line="1424"/>
<source>&amp;About</source>
<translation>关于Guitar(&amp;A)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1442"/>
+ <location filename="../../MainWindow.ui" line="1437"/>
<source>Push all tags</source>
<translation>推送所有标签</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1447"/>
+ <location filename="../../MainWindow.ui" line="1442"/>
<source>Set config user</source>
<translation>设置用户信息</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1455"/>
+ <location filename="../../MainWindow.ui" line="1450"/>
<source>&amp;Log</source>
<translation>日志(&amp;L)</translation>
</message>
<message>
<source>Unnamed</source>
<translation type="vanished">未命名</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="679"/>
+ <location filename="../../MainWindow.cpp" line="674"/>
<source>Default</source>
<translation>缺省</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="878"/>
+ <location filename="../../MainWindow.cpp" line="873"/>
<source>Graph</source>
<translation>树形图</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="880"/>
+ <location filename="../../MainWindow.cpp" line="875"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="881"/>
+ <location filename="../../MainWindow.cpp" line="876"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<source>Description</source>
<translation type="vanished">描述</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1029"/>
- <location filename="../../MainWindow.cpp" line="1199"/>
+ <location filename="../../MainWindow.cpp" line="1024"/>
+ <location filename="../../MainWindow.cpp" line="1194"/>
<source>Uncommited changes</source>
<translation>未提交的更改</translation>
</message>
<message>
<source>Are you sure you want to remove the repository from bookmarks ?</source>
<translation type="vanished">确定要从书签中删除仓库吗?</translation>
</message>
<message>
<source>(Files will NOT be deleted)</source>
<translation type="vanished">(该文件未被删除。)</translation>
</message>
<message>
<source>Open Repository</source>
<translation type="vanished">打开仓库く</translation>
</message>
<message>
<source>No such folder</source>
<translation type="vanished">没有这样的文件夹。</translation>
</message>
<message>
<source>Not a valid git repository</source>
<translation type="vanished">不是有效的仓库</translation>
</message>
<message>
<source>Commit message can not be omitted.</source>
<translation type="vanished">提交消息不能为空。</translation>
</message>
<message>
<source>Repository Property</source>
<translation type="vanished">仓库属性</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1457"/>
+ <location filename="../../MainWindow.cpp" line="1452"/>
<source>&amp;Add new group</source>
<translation>添加新组(&amp;A)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1458"/>
+ <location filename="../../MainWindow.cpp" line="1453"/>
<source>&amp;Delete group</source>
<translation>删除组(&amp;D)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1459"/>
+ <location filename="../../MainWindow.cpp" line="1454"/>
<source>&amp;Rename group</source>
<translation>重命名组(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1464"/>
+ <location filename="../../MainWindow.cpp" line="1459"/>
<source>New group</source>
<translation>新建组</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1488"/>
+ <location filename="../../MainWindow.cpp" line="1483"/>
<source>Open &amp;terminal</source>
<translation>打开终端(&amp;T)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1489"/>
+ <location filename="../../MainWindow.cpp" line="1484"/>
<source>Open command promp&amp;t</source>
<translation>打开命令提示符(&amp;T)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1491"/>
+ <location filename="../../MainWindow.cpp" line="1486"/>
<source>&amp;Open</source>
<translation>打开く(&amp;O)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1502"/>
+ <location filename="../../MainWindow.cpp" line="1497"/>
<source>Open &amp;folder</source>
<translation>打开文件夹(&amp;F)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1507"/>
+ <location filename="../../MainWindow.cpp" line="1502"/>
<source>&amp;Remove</source>
<translation>删除(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1550"/>
+ <location filename="../../MainWindow.cpp" line="1545"/>
<source>Copy commit id (7 letters)</source>
<translation>复制提交ID(7个字母)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1551"/>
+ <location filename="../../MainWindow.cpp" line="1546"/>
<source>Copy commit id (completely)</source>
<translation>复制提交ID(全部)</translation>
</message>
<message>
<source>Edit comment...</source>
<translation type="vanished">编辑注释...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1242"/>
- <location filename="../../MainWindow.cpp" line="1593"/>
+ <location filename="../../MainWindow.cpp" line="1237"/>
+ <location filename="../../MainWindow.cpp" line="1588"/>
<source>Rebase</source>
<translation>重新设置基线</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1605"/>
+ <location filename="../../MainWindow.cpp" line="1600"/>
<source>Explore</source>
<translation>浏览</translation>
</message>
<message>
<source>Reset HEAD</source>
<translation type="vanished">重置HEAD</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="882"/>
+ <location filename="../../MainWindow.cpp" line="877"/>
<source>Message</source>
<translation>消息</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1679"/>
- <location filename="../../MainWindow.cpp" line="1728"/>
- <location filename="../../MainWindow.cpp" line="1854"/>
+ <location filename="../../MainWindow.cpp" line="1674"/>
+ <location filename="../../MainWindow.cpp" line="1723"/>
+ <location filename="../../MainWindow.cpp" line="1849"/>
<source>History</source>
<translation>历史</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2436"/>
- <location filename="../../MainWindow.cpp" line="2450"/>
+ <location filename="../../MainWindow.cpp" line="2431"/>
+ <location filename="../../MainWindow.cpp" line="2445"/>
<source>No such commit</source>
<translation>没有这样的提交</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1143"/>
- <location filename="../../MainWindow.cpp" line="1144"/>
+ <location filename="../../MainWindow.cpp" line="1138"/>
+ <location filename="../../MainWindow.cpp" line="1139"/>
<source>Update</source>
<translation>更新</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2762"/>
+ <location filename="../../MainWindow.cpp" line="2757"/>
<source>Authentication Failed</source>
<translation>认证失败</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1596"/>
+ <location filename="../../MainWindow.cpp" line="1591"/>
<source>Revert</source>
<translation>放弃更改</translation>
</message>
<message>
<source>Failed to commit</source>
<translation type="vanished">提交失败</translation>
</message>
<message>
<source>No remote repository is registered.</source>
<translation type="vanished">远程仓库未注册。</translation>
</message>
<message>
<source>The current branch %1 has no upstream branch.</source>
<translation type="vanished">目前的分支「%1」没有上游分支。</translation>
</message>
<message>
<source>You try push --set-upstream</source>
<translation type="vanished">请尝试--set-upstream</translation>
</message>
<message>
<source>Connection refused.</source>
<translation type="vanished">连接被拒绝。</translation>
</message>
<message>
<source>&amp;Property</source>
<translation type="vanished">属性(&amp;P)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1567"/>
+ <location filename="../../MainWindow.cpp" line="1562"/>
<source>Checkout/Branch...</source>
<translation>检出/分支...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1589"/>
+ <location filename="../../MainWindow.cpp" line="1584"/>
<source>Edit message...</source>
<translation>编辑消息...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1592"/>
+ <location filename="../../MainWindow.cpp" line="1587"/>
<source>Merge</source>
<translation>合并</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1594"/>
+ <location filename="../../MainWindow.cpp" line="1589"/>
<source>Cherry-pick</source>
<translation>择优拣选</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1678"/>
- <location filename="../../MainWindow.cpp" line="1727"/>
+ <location filename="../../MainWindow.cpp" line="1673"/>
+ <location filename="../../MainWindow.cpp" line="1722"/>
<source>Untrack</source>
<translation>不跟踪</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1680"/>
- <location filename="../../MainWindow.cpp" line="1729"/>
- <location filename="../../MainWindow.cpp" line="1855"/>
+ <location filename="../../MainWindow.cpp" line="1675"/>
+ <location filename="../../MainWindow.cpp" line="1724"/>
+ <location filename="../../MainWindow.cpp" line="1850"/>
<source>Blame</source>
<translation>作者及版本修订信息</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1688"/>
+ <location filename="../../MainWindow.cpp" line="1683"/>
<source>Delete selected files.</source>
<translation>删除所选文件。</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1699"/>
+ <location filename="../../MainWindow.cpp" line="1694"/>
<source>rm --cached files</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1724"/>
+ <location filename="../../MainWindow.cpp" line="1719"/>
<source>Reset</source>
<translation>重置</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1725"/>
+ <location filename="../../MainWindow.cpp" line="1720"/>
<source>Ignore</source>
<translation>忽略</translation>
</message>
<message>
<source>Reset a file</source>
<translation type="vanished">重置文件</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1239"/>
+ <location filename="../../MainWindow.cpp" line="1234"/>
<source>Are you sure you want to rebase the commit ?</source>
<translation>是否确实要将该提交重新设置为基线?</translation>
</message>
<message>
<source>No repository selected</source>
<translation type="vanished">没有选择仓库</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1677"/>
- <location filename="../../MainWindow.cpp" line="1726"/>
+ <location filename="../../MainWindow.cpp" line="1672"/>
+ <location filename="../../MainWindow.cpp" line="1721"/>
<source>Delete</source>
<translation>删除</translation>
</message>
<message>
<source>Are you sure you want to run the following command ?</source>
<translation type="vanished">确定要运行以下命令吗??</translation>
</message>
<message>
<source>Revert all files</source>
<translation type="vanished">放弃所有文件更改</translation>
</message>
<message>
<source>Select %1 command</source>
<translation type="vanished">选择「%1」命令</translation>
</message>
<message>
<source>A file with same name already exists</source>
<translation type="vanished">同名的文件已存在</translation>
</message>
<message>
<source>A folder with same name already exists</source>
<translation type="vanished">同名的文件夹已存在</translation>
</message>
<message>
<source>Invalid folder</source>
<translation type="vanished">文件夹无效</translation>
</message>
<message>
<source>No such folder. Create it now ?</source>
<translation type="vanished">此文件夹不存在。是否要创建?</translation>
</message>
<message>
<source>The URL is a valid repository</source>
<translation type="vanished">此URL是有效的仓库</translation>
</message>
<message>
<source>Failed to access the URL</source>
<translation type="vanished">无法访问此URL</translation>
</message>
<message>
<source>Remote Repository</source>
<translation type="vanished">远程仓库</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2436"/>
- <location filename="../../MainWindow.cpp" line="2450"/>
+ <location filename="../../MainWindow.cpp" line="2431"/>
+ <location filename="../../MainWindow.cpp" line="2445"/>
<source>Jump</source>
<translation>跳转</translation>
</message>
<message>
<source>That commmit has not foud or not read yet</source>
<translation type="vanished">未找到或尚未读取到提交</translation>
</message>
<message>
<source>Failed to delete the branch &apos;%1&apos;
</source>
<translation type="vanished">无法删除分支 : &apos;%1&apos;</translation>
</message>
</context>
<context>
<name>MergeBranchDialog</name>
<message>
<source>Merge</source>
<translation type="vanished">合并</translation>
</message>
<message>
<source>Current branch :</source>
<translation type="vanished">目前的分支 :</translation>
</message>
<message>
<source>OK</source>
<translation type="vanished">确定</translation>
</message>
<message>
<source>Cancel</source>
<translation type="vanished">取消</translation>
</message>
</context>
<context>
<name>MergeDialog</name>
<message>
<location filename="../../MergeDialog.ui" line="14"/>
<source>Merge</source>
<translation type="unfinished">合并</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="20"/>
<source>Fast Forwarding</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="26"/>
<source>Default (--ff)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="33"/>
<source>No fast forward (--no-ff)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="40"/>
<source>Fast forward only (--ff-only)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="50"/>
<source>From</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="77"/>
<source>OK</source>
<translation type="unfinished">确定</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="84"/>
<source>Cancel</source>
<translation type="unfinished">取消</translation>
</message>
</context>
<context>
<name>MyImageViewWidget</name>
<message>
<location filename="../../MyImageViewWidget.cpp" line="34"/>
<source>Save as...</source>
<translation>另存为...</translation>
</message>
<message>
<location filename="../../MyImageViewWidget.cpp" line="42"/>
<source>Save as</source>
<translation>另存为</translation>
</message>
</context>
<context>
<name>MyTextEditorWidget</name>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="35"/>
<source>Save as...</source>
<translation>另存为...</translation>
</message>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="36"/>
<source>Copy</source>
<translation>复制</translation>
</message>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="43"/>
<source>Save as</source>
<translation>另存为</translation>
</message>
</context>
<context>
<name>ObjectBrowserDialog</name>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="14"/>
<source>Object Browser</source>
<translation>对象浏览器</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="38"/>
<source>Inspect</source>
<translation>检查</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="58"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="65"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="23"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="24"/>
<source>Type</source>
<translation>类型</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="119"/>
<source>Object Inspection</source>
<translation>对象检查</translation>
</message>
</context>
<context>
<name>PushDialog</name>
<message>
<location filename="../../PushDialog.ui" line="14"/>
<source>Push</source>
<translation>推送</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="20"/>
<source>push --set-upstream</source>
<translation>push --set-upstream</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="26"/>
<source>Remote</source>
<translation>远程</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="33"/>
<source>Branch</source>
<translation>分支</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="64"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="71"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>RebaseOntoDialog</name>
<message>
<source>Branch</source>
<translation type="vanished">分支</translation>
</message>
<message>
<source>Cancel</source>
<translation type="vanished">取消</translation>
</message>
</context>
<context>
<name>ReflogWindow</name>
<message>
<location filename="../../ReflogWindow.ui" line="14"/>
<source>Reflog</source>
<translation>参考日志</translation>
</message>
<message>
<location filename="../../ReflogWindow.ui" line="57"/>
<source>Close</source>
<translation>关闭</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="35"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="36"/>
<source>Head</source>
<translation></translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="37"/>
<source>Command</source>
<translation>命令</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="38"/>
<source>Message</source>
<translation>消息</translation>
</message>
<message>
<source>Comment</source>
<translation type="vanished">注释</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="98"/>
<source>Checkout</source>
<translation>检出</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="99"/>
<source>Explorer</source>
<translation>浏览器</translation>
</message>
</context>
+<context>
+ <name>RemoteAdvancedOptionWidget</name>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="32"/>
+ <source>Advanced Option</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="38"/>
+ <source>SSH key override...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="45"/>
+ <source>Clear</source>
+ <translation type="unfinished">清除</translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.cpp" line="24"/>
+ <source>SSH command is not registered.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.cpp" line="30"/>
+ <source>SSH key override</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>RemoteRepositoriesTableWidget</name>
<message>
<location filename="../../RemoteRepositoriesTableWidget.cpp" line="27"/>
<source>Copy URL</source>
<translation>复制URL</translation>
</message>
</context>
<context>
<name>RepositoryPropertyDialog</name>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="14"/>
<source>Repository Property</source>
<oldsource>Repository Properties</oldsource>
<translation>仓库属性</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="65"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="95"/>
<source>Local dir :</source>
<translation>本地目录 :</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="114"/>
<source>Remote URLs</source>
<translation>远程URL</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="124"/>
<source>Remote</source>
<translation>远程</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="130"/>
<source>Add</source>
<translation>添加</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="137"/>
<source>Edit</source>
<translation>编辑</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="157"/>
<source>Remove</source>
<translation>删除</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="169"/>
<source>&amp;Remote menu</source>
<translation>远程菜单(&amp;R)</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="189"/>
<source>Close</source>
<translation>关闭</translation>
</message>
<message>
- <location filename="../../RepositoryPropertyDialog.cpp" line="131"/>
+ <location filename="../../RepositoryPropertyDialog.cpp" line="155"/>
<source>Confirm Remove</source>
<translation>确认删除</translation>
</message>
<message>
- <location filename="../../RepositoryPropertyDialog.cpp" line="131"/>
+ <location filename="../../RepositoryPropertyDialog.cpp" line="155"/>
<source>Are you sure you want to remove the remote &apos;%1&apos; from the repository &apos;%2&apos; ?</source>
<translation>确定要从仓库「%2」中删除远程「%1」吗?</translation>
</message>
</context>
<context>
<name>SearchFromGitHubDialog</name>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="14"/>
<source>Search From GitHub</source>
<translation>从GitHub搜索</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="25"/>
<source>Search</source>
<translation>搜索</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="78"/>
<source>ssh</source>
<translation>ssh</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="88"/>
<source>http</source>
<translation>http</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="115"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="122"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="56"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="57"/>
<source>Owner</source>
<translation>所有者</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="58"/>
<source>Score</source>
<translation>范围</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="59"/>
<source>Description</source>
<translation>描述</translation>
</message>
</context>
<context>
<name>SelectCommandDialog</name>
<message>
<location filename="../../SelectCommandDialog.ui" line="14"/>
<source>Select git command</source>
<translation>选择git命令</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="20"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="32"/>
<source>&amp;Browse...</source>
<translation>浏览(&amp;B)...</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="52"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="59"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="29"/>
<source>Please select the &apos;%1&apos; command you want to use.</source>
<translation>请选择想用的&apos;%1&apos; 命令。</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="58"/>
<location filename="../../SelectCommandDialog.cpp" line="65"/>
<source>%1 command (%2);;</source>
<translation>%1 命令 (%2);;</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="60"/>
<source>Executable files (*.exe)</source>
<translation>可执行文件 (*.exe)</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="71"/>
<source>%1 command</source>
<translation>%1 命令</translation>
</message>
</context>
<context>
<name>SelectGpgKeyDialog</name>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="14"/>
<source>Select GPG Key</source>
<translation>选择GPG密钥</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="57"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="64"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="31"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="32"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="33"/>
<source>Mail</source>
<translation>电子邮件</translation>
</message>
</context>
<context>
<name>SelectItemDialog</name>
<message>
<location filename="../../SelectItemDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectItemDialog.ui" line="25"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../SelectItemDialog.ui" line="32"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>SetGlobalUserDialog</name>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="14"/>
<source>Global User Setting</source>
<translation>全局用户设置</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="31"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="41"/>
<source>Mail</source>
<translation>邮箱</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="68"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="78"/>
<source>Skip</source>
<translation>跳过</translation>
</message>
</context>
<context>
<name>SetGpgSigningDialog</name>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="14"/>
<source>Set GPG Signing</source>
<translation>设置GPG签名</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="26"/>
<source>Global</source>
<translation>全局</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="39"/>
<location filename="../../SetGpgSigningDialog.cpp" line="29"/>
<source>Repository</source>
<translation>仓库</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="61"/>
<source>ID</source>
<translation>ID</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="71"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="81"/>
<source>Mail</source>
<translation>邮箱</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="93"/>
<source>Select</source>
<translation>选择</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="113"/>
<source>Clear</source>
<translation>清除</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="127"/>
<source>Configure...</source>
<translation>配置...</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="147"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="154"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>SetRemoteUrlDialog</name>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="14"/>
<source>Set Remote URL</source>
<translation>设置远程URL</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="20"/>
<source>Current URLs</source>
<translation>当前URL</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="30"/>
<source>New URL</source>
<translation>新URL</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="36"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="91"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="98"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="43"/>
<source>URL</source>
<translation>URL</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="63"/>
<source>&amp;Test</source>
<translation>测试(&amp;T)</translation>
</message>
</context>
<context>
<name>SetUserDialog</name>
<message>
<location filename="../../SetUserDialog.ui" line="14"/>
<source>Set User</source>
<translation>设置用户</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="20"/>
<source>Global</source>
<translation>全局</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="27"/>
<location filename="../../SetUserDialog.cpp" line="31"/>
<source>Repository</source>
<translation>仓库</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="42"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="52"/>
<source>Mail</source>
<translation>邮箱</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="66"/>
<source>Get icon from Gravatar</source>
<translation>从Gravatar获取图标</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="122"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="132"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>SettingBehaviorForm</name>
<message>
<location filename="../../SettingBehaviorForm.ui" line="14"/>
<source>Behavior</source>
<translation>行为</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="20"/>
<location filename="../../SettingBehaviorForm.cpp" line="43"/>
<source>Default working folder</source>
<translation>默认工作文件夹</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="29"/>
<source>Browse...</source>
<translation>浏览...</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="39"/>
<source>Automatically fetch when opening the repository</source>
<translation>打开仓库时自动获取</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="46"/>
<source>Get committer&apos;s icon from gravatar.com</source>
<translation>从gravatar.com获取图标</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="61"/>
<source>Maximum number of commit item acquisitions</source>
<translation>要获取的最大提交信息数</translation>
</message>
<message>
<source>Watch remote changes periodically</source>
<oldsource>Periodically watch remote updates</oldsource>
<translation type="vanished">定期监视远程更改</translation>
</message>
<message>
<source>interval</source>
<oldsource>interval in min:</oldsource>
<translation type="vanished">间隔</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="94"/>
<source>GPG signing policy</source>
<oldsource>GPG Signing Policy</oldsource>
<translation>GPG签名策略</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="101"/>
<source>Configure...</source>
<translation>配置...</translation>
</message>
<message>
<source>Disable</source>
<translation type="vanished">禁用</translation>
</message>
<message>
<source>1 min</source>
<translation type="vanished">1分</translation>
</message>
<message>
<source>%1 mins</source>
<translation type="vanished">%1分</translation>
</message>
</context>
<context>
<name>SettingExampleForm</name>
<message>
<location filename="../../SettingExampleForm.ui" line="14"/>
<source>Example</source>
<translation>示例</translation>
</message>
<message>
<location filename="../../SettingExampleForm.ui" line="20"/>
<source>Underconstruction</source>
<translation>注意</translation>
</message>
</context>
<context>
<name>SettingGeneralForm</name>
<message>
<location filename="../../SettingGeneralForm.ui" line="14"/>
<source>General</source>
<translation>常规</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="20"/>
<source>Language</source>
<translation>语言</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="31"/>
<source>Change Language...</source>
<translation>选择语言...</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="54"/>
<source>Change Theme...</source>
<translation>选择主题...</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="66"/>
<source>Remember and restore window position</source>
<translation>记住并恢复窗口位置</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="73"/>
<source>Enable high DPI scaling</source>
<translation>启用高清屏幕缩放</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="43"/>
<source>Theme</source>
<translation>主题</translation>
</message>
<message>
<source>English</source>
<translation type="vanished">英语</translation>
</message>
<message>
<source>Japanese</source>
<translation type="vanished">日语</translation>
</message>
<message>
<source>Russian</source>
<translation type="vanished">俄语</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="19"/>
<source>Standard</source>
<translation>标准</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="20"/>
<source>Dark</source>
<translation>暗</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="82"/>
<source>Select Language</source>
<translation>选择语言</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="106"/>
<source>Select Theme</source>
<translation>选择主题</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="93"/>
<source>(Changes are applied at next run)</source>
<translation>(该设置将在下次运行时生效)</translation>
</message>
</context>
<context>
<name>SettingNetworkForm</name>
<message>
<location filename="../../SettingNetworkForm.ui" line="14"/>
<source>Network</source>
<translation>网络</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="20"/>
<source>Proxy server</source>
<oldsource>Proxy Server</oldsource>
<translation>代理服务器</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="26"/>
<source>No proxy</source>
<oldsource>No Proxy</oldsource>
<translation>无代理</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="33"/>
<source>Auto detect</source>
<oldsource>Auto Detect</oldsource>
<translation>自动检测</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="40"/>
<source>Manual</source>
<translation>手动</translation>
</message>
</context>
<context>
<name>SettingProgramsForm</name>
<message>
<location filename="../../SettingProgramsForm.ui" line="14"/>
<source>Programs</source>
<translation>程序</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="20"/>
<source>Git command</source>
<translation>Git命令</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="29"/>
<location filename="../../SettingProgramsForm.ui" line="48"/>
<location filename="../../SettingProgramsForm.ui" line="67"/>
+ <location filename="../../SettingProgramsForm.ui" line="86"/>
<source>Browse...</source>
<translation>浏览...</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="39"/>
<source>File command</source>
<translation>File命令</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="58"/>
<source>GPG command (option)</source>
<translation>GPG命令(选项)</translation>
</message>
+ <message>
+ <location filename="../../SettingProgramsForm.ui" line="77"/>
+ <source>SSH command (option)</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../../SettingsDialog.ui" line="14"/>
<source>Settings</source>
<translation>设置</translation>
</message>
<message>
<location filename="../../SettingsDialog.ui" line="141"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../SettingsDialog.ui" line="148"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>TextEditDialog</name>
<message>
<location filename="../../TextEditDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../TextEditDialog.ui" line="48"/>
<source>OK</source>
<translation>确定</translation>
</message>
<message>
<location filename="../../TextEditDialog.ui" line="58"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../TextEditDialog.cpp" line="29"/>
<source>&amp;Close</source>
<translation type="unfinished">关闭(&amp;C)</translation>
</message>
<message>
<location filename="../../TextEditDialog.cpp" line="29"/>
<source>Cacnel</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>WelcomeWizardDialog</name>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="14"/>
<source>Welcome to the Guitar Wizard</source>
<translation>欢迎来到Guitar向导</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="45"/>
<source>Helper Tools</source>
<translation>助手工具</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="61"/>
<location filename="../../WelcomeWizardDialog.ui" line="338"/>
<source>git</source>
<translation>git</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="71"/>
<location filename="../../WelcomeWizardDialog.ui" line="88"/>
<location filename="../../WelcomeWizardDialog.ui" line="248"/>
<source>Browse</source>
<translation>浏览</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="78"/>
<location filename="../../WelcomeWizardDialog.ui" line="352"/>
<source>file</source>
<translation>文件</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="120"/>
<source>Global User Information</source>
<translation>全局用户信息</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="142"/>
<source>git config --global user.name</source>
<translation>git config --global user.name</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="152"/>
<source>git config --global user.email</source>
<translation>git config --global user.email</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="168"/>
<source>Get icon from Gravatar</source>
<translation>从Gravatar获取图标</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="222"/>
<location filename="../../WelcomeWizardDialog.cpp" line="163"/>
<source>Default Working Folder</source>
<translation>默认工作文件夹</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="238"/>
<location filename="../../WelcomeWizardDialog.ui" line="324"/>
<source>folder</source>
<translation>文件夹</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="280"/>
<source>Ready to play the Guitar !</source>
<translation>Guitar已就绪!</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="296"/>
<source>user</source>
<translation>用户</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="310"/>
<source>email</source>
<translation>邮箱</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="426"/>
<location filename="../../WelcomeWizardDialog.cpp" line="155"/>
<source>&lt;&lt; Prev</source>
<translation>&lt;&lt; 上一步</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="433"/>
<location filename="../../WelcomeWizardDialog.cpp" line="156"/>
<source>Next &gt;&gt;</source>
<translation>下一步 &gt;&gt;</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.cpp" line="126"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.cpp" line="152"/>
<source>Finish</source>
<translation>结束</translation>
</message>
</context>
</TS>
diff --git a/src/resources/translations/Guitar_zh-TW.qm b/src/resources/translations/Guitar_zh-TW.qm
index e923cd7..1344a00 100644
Binary files a/src/resources/translations/Guitar_zh-TW.qm and b/src/resources/translations/Guitar_zh-TW.qm differ
diff --git a/src/resources/translations/Guitar_zh-TW.ts b/src/resources/translations/Guitar_zh-TW.ts
index 69b0f49..705f814 100644
--- a/src/resources/translations/Guitar_zh-TW.ts
+++ b/src/resources/translations/Guitar_zh-TW.ts
@@ -1,2994 +1,3049 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_TW">
<context>
<name>AboutDialog</name>
<message>
<location filename="../../AboutDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../AboutDialog.ui" line="34"/>
<location filename="../../AboutDialog.ui" line="46"/>
<location filename="../../AboutDialog.ui" line="58"/>
<location filename="../../AboutDialog.ui" line="70"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../AboutDialog.ui" line="86"/>
<source>&lt;a href=&quot;https://github.com/soramimi/Guitar&quot;&gt;https://github.com/soramimi/Guitar&lt;/a&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../AboutDialog.cpp" line="25"/>
<source>About %1</source>
<translation>%1 關於</translation>
</message>
</context>
<context>
<name>AreYouSureYouWantToContinueConnectingDialog</name>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="14"/>
<source>Unknown Host</source>
<translation>未知主機</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="20"/>
<source>Are you sure you want to continue connecting (yes/no)?</source>
<translation>您想要繼續連接嗎?(是/否)?</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="52"/>
<source>Continue</source>
<translation>繼續</translation>
</message>
<message>
<location filename="../../AreYouSureYouWantToContinueConnectingDialog.ui" line="59"/>
<source>Close</source>
<translation>關閉</translation>
</message>
</context>
<context>
<name>BasicMainWindow</name>
<message>
- <location filename="../../BasicMainWindow.cpp" line="261"/>
+ <location filename="../../BasicMainWindow.cpp" line="243"/>
<source>git command not specified</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="374"/>
+ <location filename="../../BasicMainWindow.cpp" line="358"/>
<source>The URL is a valid repository</source>
<translation>此URL是一個有效的儲存庫</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="375"/>
+ <location filename="../../BasicMainWindow.cpp" line="359"/>
<source>Failed to access the URL</source>
<translation>無法存取此URL</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="380"/>
+ <location filename="../../BasicMainWindow.cpp" line="364"/>
<source>Remote Repository</source>
<translation>遠端儲存庫</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="405"/>
+ <location filename="../../BasicMainWindow.cpp" line="389"/>
<source>&amp;Property</source>
<translation>內容(&amp;P)</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1273"/>
+ <location filename="../../BasicMainWindow.cpp" line="1272"/>
<source>Select %1 command</source>
<translation>選擇 %1 指令</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1452"/>
+ <location filename="../../BasicMainWindow.cpp" line="1447"/>
<source>Revert all files</source>
<translation>放棄所有檔案更動</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1610"/>
+ <location filename="../../BasicMainWindow.cpp" line="1605"/>
<source>The folder is not a valid git repository.</source>
<translation>此資料夾不是一個有效的Git儲存庫。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1614"/>
+ <location filename="../../BasicMainWindow.cpp" line="1609"/>
<source>Do you want to initialize it as a git repository ?</source>
<translation>是否要將其初始化為git儲存庫?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1615"/>
+ <location filename="../../BasicMainWindow.cpp" line="1610"/>
<source>Initialize Repository</source>
<translation>初始化儲存庫</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1745"/>
+ <location filename="../../BasicMainWindow.cpp" line="1740"/>
<source>No repository selected</source>
<translation>未選擇儲存庫</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1915"/>
<source>Repository Property</source>
- <translation>儲存庫內容</translation>
+ <translation type="vanished">儲存庫內容</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="1915"/>
<location filename="../../BasicMainWindow.cpp" line="2017"/>
<source>Not a valid git repository</source>
<translation>不是有效的git儲存庫。</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<location filename="../../BasicMainWindow.cpp" line="2017"/>
<source>Open Repository</source>
<translation>打開儲存庫</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<source>No such folder</source>
<translation>沒有此資料夾</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2010"/>
<source>Remove from bookmark ?</source>
<translation>是否將其從書籤中刪除?</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2248"/>
<source>, %1 ahead</source>
<translation></translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2251"/>
<source>, %1 behind</source>
<translation></translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>Confirm Remove</source>
<translation>確定刪除</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>Are you sure you want to remove the repository from bookmarks ?</source>
<translation>確定要將此儲存庫從書籤中刪除?</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2272"/>
<source>(Files will NOT be deleted)</source>
<translation>(檔案將不會被刪除)</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2302"/>
<source>A file with same name already exists</source>
<translation>已有相同名稱的檔案存在</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2303"/>
<location filename="../../BasicMainWindow.cpp" line="2308"/>
<location filename="../../BasicMainWindow.cpp" line="2325"/>
<location filename="../../BasicMainWindow.cpp" line="2330"/>
<source>Clone</source>
<translation>複製</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2307"/>
<source>A folder with same name already exists</source>
<translation>已有相同名稱的資料夾存在</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2324"/>
<source>Invalid folder</source>
<translation>無效的資料夾</translation>
</message>
<message>
<location filename="../../BasicMainWindow.cpp" line="2329"/>
<source>No such folder. Create it now ?</source>
<translation>此資料夾並不存在。是否建立?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2398"/>
+ <location filename="../../BasicMainWindow.cpp" line="2402"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2398"/>
+ <location filename="../../BasicMainWindow.cpp" line="2402"/>
<source>Commit message can not be omitted.</source>
<translation>提交訊息不能為空白。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2415"/>
+ <location filename="../../BasicMainWindow.cpp" line="2419"/>
<source>Failed to commit</source>
<translation>提交失敗</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2449"/>
- <location filename="../../BasicMainWindow.cpp" line="2533"/>
+ <location filename="../../BasicMainWindow.cpp" line="2453"/>
+ <location filename="../../BasicMainWindow.cpp" line="2538"/>
<source>Connection refused.</source>
<translation>連線被拒。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2502"/>
+ <location filename="../../BasicMainWindow.cpp" line="2506"/>
<source>No remote repository is registered.</source>
<translation>遠端儲存庫尚未被註冊。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2524"/>
+ <location filename="../../BasicMainWindow.cpp" line="2529"/>
<source>The current branch %1 has no upstream branch.</source>
<translation>目前的分支「%1」沒有上游分支。</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2527"/>
+ <location filename="../../BasicMainWindow.cpp" line="2532"/>
<source>You try push --set-upstream</source>
<translation>請嘗試使用 push --set-upstream </translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2616"/>
+ <location filename="../../BasicMainWindow.cpp" line="2621"/>
<source>Failed to delete the branch &apos;%1&apos;</source>
<translation>無法刪除分支「%1」</translation>
</message>
<message>
<source>Failed to delete the branch &apos;%1&apos;
</source>
<translation type="vanished">無法刪除分支: &apos;%1&apos;</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2680"/>
+ <location filename="../../BasicMainWindow.cpp" line="2685"/>
<source>Are you sure you want to run the following command ?</source>
<translation>您確定要執行以下指令嗎?</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2701"/>
+ <location filename="../../BasicMainWindow.cpp" line="2706"/>
<source>Reset a file</source>
<translation>重置文件</translation>
</message>
<message>
- <location filename="../../BasicMainWindow.cpp" line="2715"/>
+ <location filename="../../BasicMainWindow.cpp" line="2720"/>
<source>Unnamed</source>
<translation>未命名</translation>
</message>
</context>
<context>
<name>BasicRepositoryDialog</name>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="66"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="77"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="67"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="78"/>
<source>Purpose</source>
<translation>用途</translation>
</message>
<message>
- <location filename="../../BasicRepositoryDialog.cpp" line="68"/>
+ <location filename="../../BasicRepositoryDialog.cpp" line="79"/>
<source>URL</source>
<translation>網址</translation>
</message>
</context>
<context>
<name>BigDiffWindow</name>
<message>
<location filename="../../BigDiffWindow.ui" line="14"/>
<source>Diff</source>
<translation>差異</translation>
</message>
</context>
<context>
<name>BlameWindow</name>
<message>
<location filename="../../BlameWindow.ui" line="14"/>
<source>Blame</source>
<translation>作者與修訂版本訊息</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="45"/>
<source>Information</source>
<translation>資訊</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="60"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="70"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="90"/>
<source>Message</source>
<translation>訊息</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="80"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../BlameWindow.ui" line="118"/>
<source>Close</source>
<translation>關閉</translation>
</message>
</context>
<context>
<name>CheckoutDialog</name>
<message>
<location filename="../../CheckoutDialog.ui" line="14"/>
<source>Checkout</source>
<translation>簽出</translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="20"/>
<location filename="../../CheckoutDialog.ui" line="27"/>
<location filename="../../CheckoutDialog.ui" line="34"/>
<source>RadioButton</source>
<translation>單選按鈕</translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="63"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../CheckoutDialog.ui" line="70"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>CherryPickDialog</name>
<message>
<location filename="../../CherryPickDialog.ui" line="14"/>
<source>Cherry-pick</source>
<translation type="unfinished">擇優挑選</translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="20"/>
<source>HEAD</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="49"/>
<source>Pick</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="78"/>
<source>Mainline</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="109"/>
<source>Allow empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="131"/>
<source>OK</source>
<translation type="unfinished">確定</translation>
</message>
<message>
<location filename="../../CherryPickDialog.ui" line="138"/>
<source>Cancel</source>
<translation type="unfinished">取消</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="19"/>
<source>Commit</source>
<translation type="unfinished">提交</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="20"/>
<source>Date</source>
<translation type="unfinished">日期</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="21"/>
<source>Author</source>
<translation type="unfinished">作者</translation>
</message>
<message>
<location filename="../../CherryPickDialog.cpp" line="22"/>
<source>Message</source>
<translation type="unfinished">訊息</translation>
</message>
</context>
<context>
<name>CloneDialog</name>
<message>
<location filename="../../CloneDialog.ui" line="14"/>
- <location filename="../../CloneDialog.ui" line="103"/>
+ <location filename="../../CloneDialog.ui" line="113"/>
<source>Clone</source>
<translation>複製</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="22"/>
<source>Remote</source>
<translation>遠端</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="39"/>
<source>&amp;Test</source>
<translation>測試(&amp;T)</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="46"/>
<source>Local</source>
<translation>本地</translation>
</message>
<message>
<location filename="../../CloneDialog.ui" line="56"/>
<source>Browse</source>
<translation>瀏覽</translation>
</message>
<message>
- <location filename="../../CloneDialog.ui" line="83"/>
+ <location filename="../../CloneDialog.ui" line="93"/>
<source>Open existing local directory...</source>
<translation>打開現有的本地資料夾...</translation>
</message>
<message>
- <location filename="../../CloneDialog.ui" line="113"/>
+ <location filename="../../CloneDialog.ui" line="123"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="40"/>
+ <location filename="../../CloneDialog.cpp" line="41"/>
<source>Search</source>
<translation>搜尋</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="41"/>
+ <location filename="../../CloneDialog.cpp" line="42"/>
<source>GitHub</source>
<translation></translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="113"/>
+ <location filename="../../CloneDialog.cpp" line="115"/>
<source>Checkout into</source>
<translation>簽出至</translation>
</message>
<message>
- <location filename="../../CloneDialog.cpp" line="125"/>
+ <location filename="../../CloneDialog.cpp" line="127"/>
<source>Open existing directory</source>
<translation>打開現有的資料夾</translation>
</message>
</context>
<context>
<name>CloneFromGitHubDialog</name>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="14"/>
<source>Clone from GitHub</source>
<translation>從GitHub複製</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="45"/>
<source>ssh</source>
<translation>ssh</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="55"/>
<source>http</source>
<translation>http</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="95"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../CloneFromGitHubDialog.ui" line="102"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>CommitDialog</name>
<message>
<location filename="../../CommitDialog.ui" line="14"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="27"/>
<source>TextLabel</source>
<translation>文字標籤</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="49"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="68"/>
<location filename="../../CommitDialog.ui" line="94"/>
<location filename="../../CommitDialog.ui" line="139"/>
<location filename="../../CommitDialog.ui" line="159"/>
<location filename="../../CommitDialog.ui" line="179"/>
<source>---</source>
<translation></translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="75"/>
<location filename="../../CommitDialog.ui" line="166"/>
<source>Mail</source>
<translation>電子郵件</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="111"/>
<source>GPG Signing</source>
<translation>GPG簽名</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="126"/>
<source>ID</source>
<translation>ID</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="146"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../CommitDialog.ui" line="189"/>
<source>Configure...</source>
<translation>設定...</translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="198"/>
+ <location filename="../../CommitDialog.ui" line="203"/>
<source>Message</source>
<translation>訊息</translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="223"/>
+ <location filename="../../CommitDialog.ui" line="210"/>
+ <source>Amend</source>
+ <translation>修改</translation>
+ </message>
+ <message>
+ <location filename="../../CommitDialog.ui" line="237"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
- <location filename="../../CommitDialog.ui" line="233"/>
+ <location filename="../../CommitDialog.ui" line="247"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>CommitExploreWindow</name>
<message>
<location filename="../../CommitExploreWindow.ui" line="14"/>
<source>Commit Explorer</source>
<translation>提交瀏覽器</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="65"/>
<source>Commit ID</source>
<translation>提交ID</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="82"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.ui" line="89"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../CommitExploreWindow.cpp" line="78"/>
<source>Commit</source>
<translation>提交</translation>
</message>
</context>
<context>
<name>CommitPropertyDialog</name>
<message>
<location filename="../../CommitPropertyDialog.ui" line="14"/>
<source>Commit Property</source>
<oldsource>Commit Properties</oldsource>
<translation>提交內容</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="46"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="56"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="66"/>
<location filename="../../CommitPropertyDialog.ui" line="196"/>
<source>Mail</source>
<translation>電子郵件</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="154"/>
<source>GPG Sign</source>
<translation>GPG簽名</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="176"/>
<source>ID</source>
<translation>ID</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="186"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="233"/>
<source>Commit ID</source>
<translation>提交ID</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="243"/>
<source>Parent IDs</source>
<translation>父層ID</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="265"/>
<source>Files...</source>
<translation>檔案...</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="285"/>
<source>Explorer</source>
<translation>瀏覽器</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="305"/>
<source>Checkout</source>
<translation>簽出</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="312"/>
<source>Jump</source>
<translation>移至</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.ui" line="332"/>
<source>Close</source>
<translation>關閉</translation>
</message>
<message>
<location filename="../../CommitPropertyDialog.cpp" line="60"/>
<source>&lt;Unknown&gt;</source>
<translation>&lt;未知的&gt;</translation>
</message>
</context>
<context>
<name>CommitViewWindow</name>
<message>
<location filename="../../CommitViewWindow.ui" line="14"/>
<source>Commit View</source>
<translation>提交線圖</translation>
</message>
<message>
<location filename="../../CommitViewWindow.cpp" line="58"/>
<source>History</source>
<translation>歷史</translation>
</message>
</context>
<context>
<name>ConfigCredentialHelperDialog</name>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="14"/>
<source>Dialog</source>
<translation>對話框</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="26"/>
<source>wincred</source>
<translation>wincred</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="39"/>
<source>winstore</source>
<translation>winstore</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="52"/>
<source>None</source>
<translation>無</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="65"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="78"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../ConfigCredentialHelperDialog.ui" line="91"/>
<source>Other</source>
<translation>其他</translation>
</message>
</context>
<context>
<name>ConfigSigningDialog</name>
<message>
<location filename="../../ConfigSigningDialog.ui" line="14"/>
<source>Signing Policy</source>
<translation>簽名政策</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="20"/>
<source>Config commit.gpgsign</source>
<translation>設定commit.gpgsign</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="32"/>
<source>global</source>
<translation>全域</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="42"/>
<source>local</source>
<translation>本地</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="70"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../ConfigSigningDialog.ui" line="77"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>CreateRepositoryDialog</name>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="14"/>
<location filename="../../CreateRepositoryDialog.cpp" line="42"/>
<location filename="../../CreateRepositoryDialog.cpp" line="46"/>
<location filename="../../CreateRepositoryDialog.cpp" line="50"/>
<location filename="../../CreateRepositoryDialog.cpp" line="54"/>
<source>Create Repository</source>
<translation>建立儲存庫</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="22"/>
<source>Path</source>
<translation>路徑</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="32"/>
<source>Browse</source>
<translation>瀏覽</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="54"/>
<source>Bookmark</source>
<translation>書籤</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="63"/>
<location filename="../../CreateRepositoryDialog.ui" line="88"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="76"/>
<source>Remote</source>
<translation>遠端</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="98"/>
<source>URL</source>
<translation>URL</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="108"/>
<source>Test</source>
<translation>測試</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="146"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.ui" line="153"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="19"/>
<source>A valid git repository already exists there.</source>
<translation>已存在有效的儲存庫。</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="42"/>
<location filename="../../CreateRepositoryDialog.cpp" line="50"/>
<source>The specified path is not a directory.</source>
<translation>指定的路徑不是資料夾。</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="54"/>
<source>Remote name is invalid.</source>
<translation>遠端名稱無效。</translation>
</message>
<message>
<location filename="../../CreateRepositoryDialog.cpp" line="62"/>
<source>Destination Path</source>
<translation>目標路徑</translation>
</message>
</context>
<context>
<name>DeleteBranchDialog</name>
<message>
<location filename="../../DeleteBranchDialog.ui" line="14"/>
<source>Delete Branch</source>
<translation>刪除分支</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="25"/>
<source>&amp;All branches</source>
<translation>所有分支(&amp;A)</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="45"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.ui" line="52"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../DeleteBranchDialog.cpp" line="23"/>
<source>Delete Remote Branch</source>
<translation>刪除遠端分支</translation>
</message>
</context>
<context>
<name>DeleteTagsDialog</name>
<message>
<location filename="../../DeleteTagsDialog.ui" line="14"/>
<source>Delete tags</source>
<translation>刪除標籤</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="32"/>
<source>Check all</source>
<translation>全部檢查</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="52"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../DeleteTagsDialog.ui" line="62"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>DoYouWantToInitDialog</name>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="14"/>
<source>Init Repository</source>
<translation>初始化儲存庫</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="20"/>
<source>The folder:</source>
<translation>資料夾:</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="32"/>
<source>is not a valid git working copy.</source>
<translation>不是有效的git儲存庫。</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="39"/>
<source>Do you want to init it as a git repository ?</source>
<translation>您想要將其初始化為git儲存庫嗎?</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="81"/>
<source>Yes, Please init it.</source>
<translation>是,請初始化。</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="88"/>
<source>No, Stop it.</source>
<translation>否,不要初始化。</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.ui" line="140"/>
<source>Please choose option</source>
<translation>請選擇選項</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.cpp" line="20"/>
<source>Next...</source>
<translation>下一步...</translation>
</message>
<message>
<location filename="../../DoYouWantToInitDialog.cpp" line="26"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>EditGitIgnoreDialog</name>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="14"/>
<source>Edit Git Ignore</source>
<translation>編輯Git忽略檔案</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="20"/>
<location filename="../../EditGitIgnoreDialog.ui" line="27"/>
<location filename="../../EditGitIgnoreDialog.ui" line="34"/>
<location filename="../../EditGitIgnoreDialog.ui" line="41"/>
<source>RadioButton</source>
<translation>單選按鈕</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="63"/>
<source>Edit the file</source>
<translation>編輯文件</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="83"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../EditGitIgnoreDialog.ui" line="90"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>EditRemoteDialog</name>
<message>
<location filename="../../EditRemoteDialog.ui" line="14"/>
<source>Edit Remote</source>
<translation>遠端編輯</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="20"/>
<source>Remote</source>
<translation>遠端</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="26"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="33"/>
<source>URL</source>
<translation>URL</translation>
</message>
<message>
<location filename="../../EditRemoteDialog.ui" line="53"/>
<source>&amp;Test</source>
<translation>測試(&amp;T)</translation>
</message>
<message>
- <location filename="../../EditRemoteDialog.ui" line="81"/>
+ <location filename="../../EditRemoteDialog.ui" line="104"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
- <location filename="../../EditRemoteDialog.ui" line="88"/>
+ <location filename="../../EditRemoteDialog.ui" line="111"/>
<source>Close</source>
<translation>關閉</translation>
</message>
</context>
<context>
<name>EditTagsDialog</name>
<message>
<location filename="../../EditTagsDialog.ui" line="14"/>
<source>Edit Tags</source>
<translation>編輯標籤</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="41"/>
<source>Add...</source>
<translation>新增...</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="48"/>
<source>Delete</source>
<translation>刪除</translation>
</message>
<message>
<location filename="../../EditTagsDialog.ui" line="68"/>
<source>Close</source>
<translation>關閉</translation>
</message>
</context>
<context>
<name>ExperimentDialog</name>
<message>
<location filename="../../ExperimentDialog.ui" line="14"/>
<source>Dialog</source>
<translation>對話框</translation>
</message>
</context>
<context>
<name>FileDiffWidget</name>
<message>
<location filename="../../FileDiffWidget.ui" line="14"/>
<source>Dialog</source>
<translation>對話框</translation>
</message>
</context>
<context>
<name>FileHistoryWindow</name>
<message>
<location filename="../../FileHistoryWindow.ui" line="14"/>
<source>File History</source>
<translation>檔案歷史紀錄</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.ui" line="20"/>
<location filename="../../FileHistoryWindow.ui" line="27"/>
<source>TextLabel</source>
<translation>文字標籤</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="111"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="112"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="113"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<location filename="../../FileHistoryWindow.cpp" line="114"/>
<source>Message</source>
<translation>訊息</translation>
</message>
<message>
<source>Description</source>
<translation type="vanished">描述</translation>
</message>
</context>
<context>
<name>FilePropertyDialog</name>
<message>
<location filename="../../FilePropertyDialog.ui" line="14"/>
<source>File Property</source>
<oldsource>File Properties</oldsource>
<translation>檔案內容</translation>
</message>
<message>
<location filename="../../FilePropertyDialog.ui" line="78"/>
<source>&amp;Close</source>
<translation>關閉(&amp;C)</translation>
</message>
</context>
+<context>
+ <name>FileViewWidget</name>
+ <message>
+ <location filename="../../FileViewWidget.cpp" line="30"/>
+ <source>Form</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>FindCommitDialog</name>
<message>
<location filename="../../FindCommitDialog.ui" line="14"/>
<source>Find Commit</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../FindCommitDialog.ui" line="54"/>
<source>OK</source>
<translation type="unfinished">確定</translation>
</message>
<message>
<location filename="../../FindCommitDialog.ui" line="61"/>
<source>Cancel</source>
<translation type="unfinished">取消</translation>
</message>
</context>
<context>
<name>InputNewTagDialog</name>
<message>
<location filename="../../InputNewTagDialog.ui" line="14"/>
<source>Edit tag</source>
<translation>編輯標籤</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="31"/>
<source>Tag</source>
<translation>標籤</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="58"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../InputNewTagDialog.ui" line="65"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>JumpDialog</name>
<message>
<location filename="../../JumpDialog.ui" line="14"/>
<source>Jump</source>
<translation>移至</translation>
</message>
<message>
<source>Branches and Tags</source>
<translation type="vanished">分支與標籤</translation>
</message>
<message>
<source>&amp;Filter</source>
<translation type="vanished">篩選器(&amp;F)</translation>
</message>
<message>
<source>Find</source>
<translation type="vanished">尋找</translation>
</message>
<message>
<source>&amp;Checkout</source>
<translation type="vanished">簽出(&amp;C)</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="20"/>
<source>Branch, Tag or Commit-ID :</source>
<translation>分支,標籤,提交ID</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="71"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../JumpDialog.ui" line="81"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../JumpDialog.cpp" line="39"/>
<source>Name</source>
<translation>名稱</translation>
</message>
</context>
<context>
<name>Languages</name>
<message>
<location filename="../../Languages.cpp" line="6"/>
<source>English</source>
<translation type="unfinished">英文</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="7"/>
<source>Japanese</source>
<translation type="unfinished">日語</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="8"/>
<source>Russian</source>
<translation type="unfinished">俄文</translation>
</message>
<message>
<location filename="../../Languages.cpp" line="9"/>
<source>Chinese (Simplified)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../Languages.cpp" line="10"/>
<source>Chinese (Traditional/Taiwan)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>LineEditDialog</name>
<message>
<location filename="../../LineEditDialog.ui" line="14"/>
<source>Dialog</source>
<translation>對話框</translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="20"/>
<source>TextLabel</source>
<translation>文字標籤</translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="45"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../LineEditDialog.ui" line="52"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../MainWindow.ui" line="14"/>
<source>Guitar</source>
<translation>Guitar</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="96"/>
- <location filename="../../MainWindow.ui" line="1421"/>
- <location filename="../../MainWindow.ui" line="1424"/>
+ <location filename="../../MainWindow.ui" line="1416"/>
+ <location filename="../../MainWindow.ui" line="1419"/>
<source>Clone</source>
<translation>複製</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="131"/>
- <location filename="../../MainWindow.cpp" line="1140"/>
- <location filename="../../MainWindow.cpp" line="1141"/>
+ <location filename="../../MainWindow.cpp" line="1135"/>
+ <location filename="../../MainWindow.cpp" line="1136"/>
<source>Fetch</source>
<translation>提取</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="166"/>
<source>Pull</source>
<translation>拉取</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="201"/>
<source>Push</source>
<translation>推送</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="303"/>
- <location filename="../../MainWindow.ui" line="1548"/>
+ <location filename="../../MainWindow.ui" line="1543"/>
<source>Terminal</source>
<translation>終端機</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="338"/>
- <location filename="../../MainWindow.ui" line="1557"/>
+ <location filename="../../MainWindow.ui" line="1552"/>
<source>Explorer</source>
<translation>檔案總管</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="424"/>
<source>Repository</source>
<translation>儲存庫</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="431"/>
<source>Branch Name</source>
<translation>分支名稱</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="483"/>
- <location filename="../../MainWindow.ui" line="1592"/>
+ <location filename="../../MainWindow.ui" line="1587"/>
<source>Offline</source>
<translation>離線</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="473"/>
- <location filename="../../MainWindow.ui" line="1587"/>
+ <location filename="../../MainWindow.ui" line="1582"/>
<source>Online</source>
<translation>在线</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="799"/>
- <location filename="../../MainWindow.cpp" line="1853"/>
+ <location filename="../../MainWindow.cpp" line="1848"/>
<source>Unstage</source>
<translation>取消暫存變更</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="828"/>
<source>Select all</source>
<translation>全選</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="863"/>
- <location filename="../../MainWindow.cpp" line="1723"/>
+ <location filename="../../MainWindow.cpp" line="1718"/>
<source>Stage</source>
<translation>暫存變更</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="892"/>
- <location filename="../../MainWindow.cpp" line="879"/>
+ <location filename="../../MainWindow.cpp" line="874"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="1023"/>
<source>&amp;File</source>
<translation>檔案(&amp;F)</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="1034"/>
<source>&amp;View</source>
<translation>檢視(&amp;V)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1040"/>
+ <location filename="../../MainWindow.ui" line="1041"/>
<source>&amp;Edit</source>
<translation>編輯(&amp;E)</translation>
</message>
<message>
<source>Destructive</source>
<translation type="vanished">需要注意的指令</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1412"/>
+ <location filename="../../MainWindow.ui" line="1407"/>
<source>Settings</source>
<translation>設定</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1434"/>
- <location filename="../../MainWindow.cpp" line="1595"/>
+ <location filename="../../MainWindow.ui" line="1429"/>
+ <location filename="../../MainWindow.cpp" line="1590"/>
<source>Edit tags...</source>
<translation>編輯標籤...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1437"/>
+ <location filename="../../MainWindow.ui" line="1432"/>
<source>Edit tags</source>
<translation>編輯標籤</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1562"/>
+ <location filename="../../MainWindow.ui" line="1557"/>
<source>Clean -df</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1567"/>
+ <location filename="../../MainWindow.ui" line="1562"/>
<source>Reset --hard</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1494"/>
+ <location filename="../../MainWindow.ui" line="1489"/>
<source>Create a repository</source>
<translation>建立儲存庫</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1481"/>
+ <location filename="../../MainWindow.ui" line="1476"/>
<source>Push upstream</source>
<translation>推送至上游</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1503"/>
+ <location filename="../../MainWindow.ui" line="1498"/>
<source>Stop process</source>
<translation>停止處理</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1508"/>
+ <location filename="../../MainWindow.ui" line="1503"/>
<source>E&amp;xit</source>
<translation>離開(&amp;X)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1511"/>
+ <location filename="../../MainWindow.ui" line="1506"/>
<source>Ctrl+Q</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1516"/>
+ <location filename="../../MainWindow.ui" line="1511"/>
<source>Reflog...</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1521"/>
+ <location filename="../../MainWindow.ui" line="1516"/>
<source>Property...</source>
<translation>內容...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1526"/>
- <location filename="../../MainWindow.ui" line="1529"/>
+ <location filename="../../MainWindow.ui" line="1521"/>
+ <location filename="../../MainWindow.ui" line="1524"/>
<source>Set GPG signing</source>
<translation>設定GPG簽名</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1539"/>
+ <location filename="../../MainWindow.ui" line="1534"/>
<source>Fetch --prune</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1055"/>
+ <location filename="../../MainWindow.ui" line="1056"/>
<source>&amp;Help</source>
<translation>說明(&amp;H)</translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="252"/>
- <location filename="../../MainWindow.ui" line="1628"/>
- <location filename="../../MainWindow.cpp" line="1353"/>
+ <location filename="../../MainWindow.ui" line="1623"/>
+ <location filename="../../MainWindow.cpp" line="1348"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MainWindow.ui" line="656"/>
<source>...</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1061"/>
+ <location filename="../../MainWindow.ui" line="1062"/>
<source>&amp;Window</source>
<translation>視窗(&amp;W)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1067"/>
+ <location filename="../../MainWindow.ui" line="1073"/>
<source>&amp;Repository</source>
<translation>儲存庫(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1071"/>
- <location filename="../../MainWindow.ui" line="1572"/>
+ <location filename="../../MainWindow.ui" line="1077"/>
+ <location filename="../../MainWindow.ui" line="1567"/>
<source>Stash</source>
<translation>擱置</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1098"/>
+ <location filename="../../MainWindow.ui" line="1104"/>
<source>Re&amp;mote</source>
<translation>遠端(&amp;M)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1113"/>
+ <location filename="../../MainWindow.ui" line="1119"/>
<source>&amp;Destructive</source>
<translation>注意(&amp;D)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1125"/>
- <source>Experimental</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="../../MainWindow.ui" line="1155"/>
+ <location filename="../../MainWindow.ui" line="1150"/>
<source>Log</source>
<translation>日誌</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1325"/>
+ <location filename="../../MainWindow.ui" line="1320"/>
<source>&amp;Open existing working copy...</source>
<translation>打開所有工作副本(&amp;O)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1328"/>
- <location filename="../../MainWindow.cpp" line="2027"/>
+ <location filename="../../MainWindow.ui" line="1323"/>
+ <location filename="../../MainWindow.cpp" line="2022"/>
<source>Add existing working copy</source>
<translation>新增所有工作副本</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1333"/>
- <location filename="../../MainWindow.ui" line="1336"/>
+ <location filename="../../MainWindow.ui" line="1328"/>
+ <location filename="../../MainWindow.ui" line="1331"/>
<source>Refresh</source>
<translation>更新</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1339"/>
+ <location filename="../../MainWindow.ui" line="1334"/>
<source>F5</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1348"/>
+ <location filename="../../MainWindow.ui" line="1343"/>
<source>&amp;Commit</source>
<translation>提交(&amp;C)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1357"/>
+ <location filename="../../MainWindow.ui" line="1352"/>
<source>&amp;Push</source>
<translation>推送(&amp;P)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1365"/>
+ <location filename="../../MainWindow.ui" line="1360"/>
<source>test</source>
<translation>測試</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1368"/>
+ <location filename="../../MainWindow.ui" line="1363"/>
<source>Ctrl+T</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1377"/>
+ <location filename="../../MainWindow.ui" line="1372"/>
<source>Pu&amp;ll</source>
<translation>拉取(&amp;L)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1386"/>
+ <location filename="../../MainWindow.ui" line="1381"/>
<source>&amp;Fetch</source>
<translation>提取(&amp;F)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1391"/>
- <location filename="../../MainWindow.ui" line="1394"/>
+ <location filename="../../MainWindow.ui" line="1386"/>
+ <location filename="../../MainWindow.ui" line="1389"/>
<source>Edit global .gitconfig</source>
<translation>編輯全域.gitignore</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1399"/>
+ <location filename="../../MainWindow.ui" line="1394"/>
<source>Edit .git/config</source>
<translation>編輯.git/config</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1404"/>
+ <location filename="../../MainWindow.ui" line="1399"/>
<source>Edit .gitignore</source>
<translation>編輯.gitignore</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1409"/>
+ <location filename="../../MainWindow.ui" line="1404"/>
<source>&amp;Settings...</source>
<translation>設定(&amp;S)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1458"/>
+ <location filename="../../MainWindow.ui" line="1453"/>
<source>F4</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1463"/>
+ <location filename="../../MainWindow.ui" line="1458"/>
<source>&amp;Jump...</source>
<translation>移至(&amp;J)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1466"/>
+ <location filename="../../MainWindow.ui" line="1461"/>
<source>Ctrl+J</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1471"/>
+ <location filename="../../MainWindow.ui" line="1466"/>
<source>Check&amp;out...</source>
<translation>簽出(&amp;O)...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1476"/>
- <location filename="../../MainWindow.cpp" line="1600"/>
+ <location filename="../../MainWindow.ui" line="1471"/>
+ <location filename="../../MainWindow.cpp" line="1595"/>
<source>Delete branch...</source>
<translation>刪除分支...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1577"/>
+ <location filename="../../MainWindow.ui" line="1572"/>
<source>Apply</source>
<translation>套用</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1582"/>
+ <location filename="../../MainWindow.ui" line="1577"/>
<source>Drop</source>
<translation>丟棄</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1600"/>
+ <location filename="../../MainWindow.ui" line="1595"/>
<source>Repositories panel</source>
<translation>儲存庫面板</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1603"/>
+ <location filename="../../MainWindow.ui" line="1598"/>
<source>Ctrl+R</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1608"/>
+ <location filename="../../MainWindow.ui" line="1603"/>
<source>&amp;Find...</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1611"/>
+ <location filename="../../MainWindow.ui" line="1606"/>
<source>Ctrl+F</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1616"/>
+ <location filename="../../MainWindow.ui" line="1611"/>
<source>Find next</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1619"/>
+ <location filename="../../MainWindow.ui" line="1614"/>
<source>F3</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1633"/>
+ <location filename="../../MainWindow.ui" line="1628"/>
<source>Jump to &amp;HEAD</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1636"/>
+ <location filename="../../MainWindow.ui" line="1631"/>
<source>Ctrl+H</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1641"/>
+ <location filename="../../MainWindow.ui" line="1636"/>
<source>Merge...</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1646"/>
+ <location filename="../../MainWindow.ui" line="1641"/>
<source>Expand commit log</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1649"/>
+ <location filename="../../MainWindow.ui" line="1644"/>
<source>Ctrl+2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1654"/>
+ <location filename="../../MainWindow.ui" line="1649"/>
<source>Expand file list</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1657"/>
+ <location filename="../../MainWindow.ui" line="1652"/>
<source>Ctrl+3</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1662"/>
+ <location filename="../../MainWindow.ui" line="1657"/>
<source>Expand diff view</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1665"/>
+ <location filename="../../MainWindow.ui" line="1660"/>
<source>Ctrl+4</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1670"/>
+ <location filename="../../MainWindow.ui" line="1668"/>
<source>Sidebar</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1673"/>
- <source>F1</source>
+ <location filename="../../MainWindow.ui" line="1671"/>
+ <source>Ctrl+1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1678"/>
+ <location filename="../../MainWindow.ui" line="1687"/>
+ <source>Show labels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../MainWindow.ui" line="1690"/>
+ <source>Ctrl+L</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../MainWindow.ui" line="1676"/>
<source>Wide</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1681"/>
+ <location filename="../../MainWindow.ui" line="1679"/>
<source>F2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1486"/>
- <location filename="../../MainWindow.ui" line="1489"/>
+ <location filename="../../MainWindow.ui" line="1481"/>
+ <location filename="../../MainWindow.ui" line="1484"/>
<source>Reset HEAD~1</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1534"/>
- <location filename="../../MainWindow.cpp" line="1601"/>
+ <location filename="../../MainWindow.ui" line="1529"/>
+ <location filename="../../MainWindow.cpp" line="1596"/>
<source>Delete remote branch...</source>
<translation>刪除遠端分支...</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1429"/>
+ <location filename="../../MainWindow.ui" line="1424"/>
<source>&amp;About</source>
<translation>關於Guitar(&amp;A)</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1442"/>
+ <location filename="../../MainWindow.ui" line="1437"/>
<source>Push all tags</source>
<translation>推送所有標籤</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1447"/>
+ <location filename="../../MainWindow.ui" line="1442"/>
<source>Set config user</source>
<translation>設定使用者訊息</translation>
</message>
<message>
- <location filename="../../MainWindow.ui" line="1455"/>
+ <location filename="../../MainWindow.ui" line="1450"/>
<source>&amp;Log</source>
<translation>日誌(&amp;L)</translation>
</message>
<message>
<source>Unnamed</source>
<translation type="vanished">未命名</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="679"/>
+ <location filename="../../MainWindow.cpp" line="674"/>
<source>Default</source>
<translation>預設</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="878"/>
+ <location filename="../../MainWindow.cpp" line="873"/>
<source>Graph</source>
<translation>線圖</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="880"/>
+ <location filename="../../MainWindow.cpp" line="875"/>
<source>Date</source>
<translation>日期</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="881"/>
+ <location filename="../../MainWindow.cpp" line="876"/>
<source>Author</source>
<translation>作者</translation>
</message>
<message>
<source>Description</source>
<translation type="vanished">描述</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1029"/>
- <location filename="../../MainWindow.cpp" line="1199"/>
+ <location filename="../../MainWindow.cpp" line="1024"/>
+ <location filename="../../MainWindow.cpp" line="1194"/>
<source>Uncommited changes</source>
<translation>未提交的變更</translation>
</message>
<message>
<source>Are you sure you want to remove the repository from bookmarks ?</source>
<translation type="vanished">確定將此儲存庫從書籤中刪除嗎?</translation>
</message>
<message>
<source>(Files will NOT be deleted)</source>
<translation type="vanished">(此文件將不會被刪除)</translation>
</message>
<message>
<source>Open Repository</source>
<translation type="vanished">開啟儲存庫</translation>
</message>
<message>
<source>No such folder</source>
<translation type="vanished">沒有此資料夾。</translation>
</message>
<message>
<source>Not a valid git repository</source>
<translation type="vanished">不是有效的儲存庫</translation>
</message>
<message>
<source>Commit message can not be omitted.</source>
<translation type="vanished">提交訊息不能為空白。</translation>
</message>
<message>
<source>Repository Property</source>
<translation type="vanished">儲存庫內容</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1457"/>
+ <location filename="../../MainWindow.cpp" line="1452"/>
<source>&amp;Add new group</source>
<translation>新增群組(&amp;A)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1458"/>
+ <location filename="../../MainWindow.cpp" line="1453"/>
<source>&amp;Delete group</source>
<translation>刪除群組(&amp;D)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1459"/>
+ <location filename="../../MainWindow.cpp" line="1454"/>
<source>&amp;Rename group</source>
<translation>重新命名群組(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1464"/>
+ <location filename="../../MainWindow.cpp" line="1459"/>
<source>New group</source>
<translation>新群組</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1488"/>
+ <location filename="../../MainWindow.cpp" line="1483"/>
<source>Open &amp;terminal</source>
<translation>開啟終端機(&amp;T)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1489"/>
+ <location filename="../../MainWindow.cpp" line="1484"/>
<source>Open command promp&amp;t</source>
<translation>開啟命令提示字元(&amp;T)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1491"/>
+ <location filename="../../MainWindow.cpp" line="1486"/>
<source>&amp;Open</source>
<translation>開啟(&amp;O)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1502"/>
+ <location filename="../../MainWindow.cpp" line="1497"/>
<source>Open &amp;folder</source>
<translation>打開資料夾(&amp;F)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1507"/>
+ <location filename="../../MainWindow.cpp" line="1502"/>
<source>&amp;Remove</source>
<translation>刪除(&amp;R)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1550"/>
+ <location filename="../../MainWindow.cpp" line="1545"/>
<source>Copy commit id (7 letters)</source>
<translation>複製提交ID(7個字母)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1551"/>
+ <location filename="../../MainWindow.cpp" line="1546"/>
<source>Copy commit id (completely)</source>
<translation>複製提交ID(全部)</translation>
</message>
<message>
<source>Edit comment...</source>
<translation type="vanished">編輯提交訊息...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1242"/>
- <location filename="../../MainWindow.cpp" line="1593"/>
+ <location filename="../../MainWindow.cpp" line="1237"/>
+ <location filename="../../MainWindow.cpp" line="1588"/>
<source>Rebase</source>
<translation>Rebase</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1605"/>
+ <location filename="../../MainWindow.cpp" line="1600"/>
<source>Explore</source>
<translation>瀏覽</translation>
</message>
<message>
<source>Reset HEAD</source>
<translation type="vanished">重置HEAD</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="882"/>
+ <location filename="../../MainWindow.cpp" line="877"/>
<source>Message</source>
<translation>訊息</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1679"/>
- <location filename="../../MainWindow.cpp" line="1728"/>
- <location filename="../../MainWindow.cpp" line="1854"/>
+ <location filename="../../MainWindow.cpp" line="1674"/>
+ <location filename="../../MainWindow.cpp" line="1723"/>
+ <location filename="../../MainWindow.cpp" line="1849"/>
<source>History</source>
<translation>歷史紀錄</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2436"/>
- <location filename="../../MainWindow.cpp" line="2450"/>
+ <location filename="../../MainWindow.cpp" line="2431"/>
+ <location filename="../../MainWindow.cpp" line="2445"/>
<source>No such commit</source>
<translation>沒有這樣的提交</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1143"/>
- <location filename="../../MainWindow.cpp" line="1144"/>
+ <location filename="../../MainWindow.cpp" line="1138"/>
+ <location filename="../../MainWindow.cpp" line="1139"/>
<source>Update</source>
<translation>更新</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2762"/>
+ <location filename="../../MainWindow.cpp" line="2757"/>
<source>Authentication Failed</source>
<translation>認證失敗</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1596"/>
+ <location filename="../../MainWindow.cpp" line="1591"/>
<source>Revert</source>
<translation>放棄變更</translation>
</message>
<message>
<source>Failed to commit</source>
<translation type="vanished">提交失敗</translation>
</message>
<message>
<source>No remote repository is registered.</source>
<translation type="vanished">遠端儲存庫尚未被註冊。</translation>
</message>
<message>
<source>The current branch %1 has no upstream branch.</source>
<translation type="vanished">目前的分支「%1」沒有上游分支。</translation>
</message>
<message>
<source>You try push --set-upstream</source>
<translation type="vanished">請嘗試使用 push --set-upstream</translation>
</message>
<message>
<source>Connection refused.</source>
<translation type="vanished">連線被拒絕</translation>
</message>
<message>
<source>&amp;Property</source>
<translation type="vanished">內容(&amp;P)</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1567"/>
+ <location filename="../../MainWindow.cpp" line="1562"/>
<source>Checkout/Branch...</source>
<translation>簽出/分支...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1589"/>
+ <location filename="../../MainWindow.cpp" line="1584"/>
<source>Edit message...</source>
<translation>編輯訊息...</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1592"/>
+ <location filename="../../MainWindow.cpp" line="1587"/>
<source>Merge</source>
<translation>合併</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1594"/>
+ <location filename="../../MainWindow.cpp" line="1589"/>
<source>Cherry-pick</source>
<translation>擇優挑選</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1678"/>
- <location filename="../../MainWindow.cpp" line="1727"/>
+ <location filename="../../MainWindow.cpp" line="1673"/>
+ <location filename="../../MainWindow.cpp" line="1722"/>
<source>Untrack</source>
<translation>不追蹤</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1680"/>
- <location filename="../../MainWindow.cpp" line="1729"/>
- <location filename="../../MainWindow.cpp" line="1855"/>
+ <location filename="../../MainWindow.cpp" line="1675"/>
+ <location filename="../../MainWindow.cpp" line="1724"/>
+ <location filename="../../MainWindow.cpp" line="1850"/>
<source>Blame</source>
<translation>作者及版本修訂訊息</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1688"/>
+ <location filename="../../MainWindow.cpp" line="1683"/>
<source>Delete selected files.</source>
<translation>刪除選擇的檔案。</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1699"/>
+ <location filename="../../MainWindow.cpp" line="1694"/>
<source>rm --cached files</source>
<translation></translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1724"/>
+ <location filename="../../MainWindow.cpp" line="1719"/>
<source>Reset</source>
<translation>重置</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1725"/>
+ <location filename="../../MainWindow.cpp" line="1720"/>
<source>Ignore</source>
<translation>忽略</translation>
</message>
<message>
<source>Reset a file</source>
<translation type="vanished">重置檔案</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1239"/>
+ <location filename="../../MainWindow.cpp" line="1234"/>
<source>Are you sure you want to rebase the commit ?</source>
<translation></translation>
</message>
<message>
<source>No repository selected</source>
<translation type="vanished">沒有選擇儲存庫</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="1677"/>
- <location filename="../../MainWindow.cpp" line="1726"/>
+ <location filename="../../MainWindow.cpp" line="1672"/>
+ <location filename="../../MainWindow.cpp" line="1721"/>
<source>Delete</source>
<translation>刪除</translation>
</message>
<message>
<source>Are you sure you want to run the following command ?</source>
<translation type="vanished">確定要執行以下指令嗎?</translation>
</message>
<message>
<source>Revert all files</source>
<translation type="vanished">放棄所有變更</translation>
</message>
<message>
<source>Select %1 command</source>
<translation type="vanished">選擇「%1」指令</translation>
</message>
<message>
<source>A file with same name already exists</source>
<translation type="vanished">已有相同檔案名稱</translation>
</message>
<message>
<source>A folder with same name already exists</source>
<translation type="vanished">已有相同的資料夾名稱</translation>
</message>
<message>
<source>Invalid folder</source>
<translation type="vanished">資料夾無效</translation>
</message>
<message>
<source>No such folder. Create it now ?</source>
<translation type="vanished">此資料夾不存在,是否要建立?</translation>
</message>
<message>
<source>The URL is a valid repository</source>
<translation type="vanished">此URL是有效的儲存庫</translation>
</message>
<message>
<source>Failed to access the URL</source>
<translation type="vanished">無法存取此URL</translation>
</message>
<message>
<source>Remote Repository</source>
<translation type="vanished">遠端儲存庫</translation>
</message>
<message>
- <location filename="../../MainWindow.cpp" line="2436"/>
- <location filename="../../MainWindow.cpp" line="2450"/>
+ <location filename="../../MainWindow.cpp" line="2431"/>
+ <location filename="../../MainWindow.cpp" line="2445"/>
<source>Jump</source>
<translation>移至</translation>
</message>
<message>
<source>That commmit has not foud or not read yet</source>
<translation type="vanished">未找到或尚未讀取到此提交</translation>
</message>
<message>
<source>Failed to delete the branch &apos;%1&apos;
</source>
<translation type="vanished">無法刪除分支:&apos;%1&apos;</translation>
</message>
</context>
<context>
<name>MergeBranchDialog</name>
<message>
<source>Merge</source>
<translation type="vanished">合併</translation>
</message>
<message>
<source>Current branch :</source>
<translation type="vanished">目前分支:</translation>
</message>
<message>
<source>OK</source>
<translation type="vanished">確定</translation>
</message>
<message>
<source>Cancel</source>
<translation type="vanished">取消</translation>
</message>
</context>
<context>
<name>MergeDialog</name>
<message>
<location filename="../../MergeDialog.ui" line="14"/>
<source>Merge</source>
<translation type="unfinished">合併</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="20"/>
<source>Fast Forwarding</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="26"/>
<source>Default (--ff)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="33"/>
<source>No fast forward (--no-ff)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="40"/>
<source>Fast forward only (--ff-only)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="50"/>
<source>From</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="77"/>
<source>OK</source>
<translation type="unfinished">確定</translation>
</message>
<message>
<location filename="../../MergeDialog.ui" line="84"/>
<source>Cancel</source>
<translation type="unfinished">取消</translation>
</message>
</context>
<context>
<name>MyImageViewWidget</name>
<message>
<location filename="../../MyImageViewWidget.cpp" line="34"/>
<source>Save as...</source>
<translation>另存為...</translation>
</message>
<message>
<location filename="../../MyImageViewWidget.cpp" line="42"/>
<source>Save as</source>
<translation>另存為</translation>
</message>
</context>
<context>
<name>MyTextEditorWidget</name>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="35"/>
<source>Save as...</source>
<translation>另存為...</translation>
</message>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="36"/>
<source>Copy</source>
<translation>複製</translation>
</message>
<message>
<location filename="../../MyTextEditorWidget.cpp" line="43"/>
<source>Save as</source>
<translation>另存為</translation>
</message>
</context>
<context>
<name>ObjectBrowserDialog</name>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="14"/>
<source>Object Browser</source>
<translation>物件瀏覽器</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="38"/>
<source>Inspect</source>
<translation>檢查</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="58"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.ui" line="65"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="23"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="24"/>
<source>Type</source>
<translation>類型</translation>
</message>
<message>
<location filename="../../ObjectBrowserDialog.cpp" line="119"/>
<source>Object Inspection</source>
<translation>物件檢查</translation>
</message>
</context>
<context>
<name>PushDialog</name>
<message>
<location filename="../../PushDialog.ui" line="14"/>
<source>Push</source>
<translation>推送</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="20"/>
<source>push --set-upstream</source>
<translation>push --set-upstream</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="26"/>
<source>Remote</source>
<translation>遠端</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="33"/>
<source>Branch</source>
<translation>分支</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="64"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../PushDialog.ui" line="71"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>RebaseOntoDialog</name>
<message>
<source>Branch</source>
<translation type="vanished">分支</translation>
</message>
<message>
<source>Cancel</source>
<translation type="vanished">取消</translation>
</message>
</context>
<context>
<name>ReflogWindow</name>
<message>
<location filename="../../ReflogWindow.ui" line="14"/>
<source>Reflog</source>
<translation></translation>
</message>
<message>
<location filename="../../ReflogWindow.ui" line="57"/>
<source>Close</source>
<translation>關閉</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="35"/>
<source>Commit</source>
<translation>提交</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="36"/>
<source>Head</source>
<translation></translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="37"/>
<source>Command</source>
<translation>指令</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="38"/>
<source>Message</source>
<translation>訊息</translation>
</message>
<message>
<source>Comment</source>
<translation type="vanished">備註</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="98"/>
<source>Checkout</source>
<translation>簽出</translation>
</message>
<message>
<location filename="../../ReflogWindow.cpp" line="99"/>
<source>Explorer</source>
<translation>瀏覽器</translation>
</message>
</context>
+<context>
+ <name>RemoteAdvancedOptionWidget</name>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="32"/>
+ <source>Advanced Option</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="38"/>
+ <source>SSH key override...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.ui" line="45"/>
+ <source>Clear</source>
+ <translation type="unfinished">清除</translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.cpp" line="24"/>
+ <source>SSH command is not registered.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../RemoteAdvancedOptionWidget.cpp" line="30"/>
+ <source>SSH key override</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>RemoteRepositoriesTableWidget</name>
<message>
<location filename="../../RemoteRepositoriesTableWidget.cpp" line="27"/>
<source>Copy URL</source>
<translation>複製網址</translation>
</message>
</context>
<context>
<name>RepositoryPropertyDialog</name>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="14"/>
<source>Repository Property</source>
<oldsource>Repository Properties</oldsource>
<translation>儲存庫內容</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="65"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="95"/>
<source>Local dir :</source>
<translation>本地資料夾 :</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="114"/>
<source>Remote URLs</source>
<translation>遠端網址</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="124"/>
<source>Remote</source>
<translation>遠端</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="130"/>
<source>Add</source>
<translation>新增</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="137"/>
<source>Edit</source>
<translation>編輯</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="157"/>
<source>Remove</source>
<translation>移除</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="169"/>
<source>&amp;Remote menu</source>
<translation>遠端選單(&amp;R)</translation>
</message>
<message>
<location filename="../../RepositoryPropertyDialog.ui" line="189"/>
<source>Close</source>
<translation>關閉</translation>
</message>
<message>
- <location filename="../../RepositoryPropertyDialog.cpp" line="131"/>
+ <location filename="../../RepositoryPropertyDialog.cpp" line="155"/>
<source>Confirm Remove</source>
<translation>確定移除</translation>
</message>
<message>
- <location filename="../../RepositoryPropertyDialog.cpp" line="131"/>
+ <location filename="../../RepositoryPropertyDialog.cpp" line="155"/>
<source>Are you sure you want to remove the remote &apos;%1&apos; from the repository &apos;%2&apos; ?</source>
<translation>確定要從儲存庫「%2」中移除遠端「%1」嗎?</translation>
</message>
</context>
<context>
<name>SearchFromGitHubDialog</name>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="14"/>
<source>Search From GitHub</source>
<translation>從GitHub搜尋</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="25"/>
<source>Search</source>
<translation>搜尋</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="78"/>
<source>ssh</source>
<translation>ssh</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="88"/>
<source>http</source>
<translation>http</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="115"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.ui" line="122"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="56"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="57"/>
<source>Owner</source>
<translation>擁有者</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="58"/>
<source>Score</source>
<translation>範圍</translation>
</message>
<message>
<location filename="../../SearchFromGitHubDialog.cpp" line="59"/>
<source>Description</source>
<translation>描述</translation>
</message>
</context>
<context>
<name>SelectCommandDialog</name>
<message>
<location filename="../../SelectCommandDialog.ui" line="14"/>
<source>Select git command</source>
<translation>選擇git指令</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="20"/>
<source>TextLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="32"/>
<source>&amp;Browse...</source>
<translation>瀏覽(&amp;B)...</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="52"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.ui" line="59"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="29"/>
<source>Please select the &apos;%1&apos; command you want to use.</source>
<translation>請選擇想使用的&apos;%1&apos; 指令。</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="58"/>
<location filename="../../SelectCommandDialog.cpp" line="65"/>
<source>%1 command (%2);;</source>
<translation>%1 指令 (%2);;</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="60"/>
<source>Executable files (*.exe)</source>
<translation>執行檔 (*.exe)</translation>
</message>
<message>
<location filename="../../SelectCommandDialog.cpp" line="71"/>
<source>%1 command</source>
<translation>%1 指令</translation>
</message>
</context>
<context>
<name>SelectGpgKeyDialog</name>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="14"/>
<source>Select GPG Key</source>
<translation>選擇GPG金鑰</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="57"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.ui" line="64"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="31"/>
<source>ID</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="32"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../SelectGpgKeyDialog.cpp" line="33"/>
<source>Mail</source>
<translation>電子郵件</translation>
</message>
</context>
<context>
<name>SelectItemDialog</name>
<message>
<location filename="../../SelectItemDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../SelectItemDialog.ui" line="25"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../SelectItemDialog.ui" line="32"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>SetGlobalUserDialog</name>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="14"/>
<source>Global User Setting</source>
<translation>全域使用者設定</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="31"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="41"/>
<source>Mail</source>
<translation>電子郵件</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="68"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../SetGlobalUserDialog.ui" line="78"/>
<source>Skip</source>
<translation>略過</translation>
</message>
</context>
<context>
<name>SetGpgSigningDialog</name>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="14"/>
<source>Set GPG Signing</source>
<translation>設定GPG簽名</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="26"/>
<source>Global</source>
<translation>全域</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="39"/>
<location filename="../../SetGpgSigningDialog.cpp" line="29"/>
<source>Repository</source>
<translation>儲存庫</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="61"/>
<source>ID</source>
<translation>ID</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="71"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="81"/>
<source>Mail</source>
<translation>電子郵件</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="93"/>
<source>Select</source>
<translation>選擇</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="113"/>
<source>Clear</source>
<translation>清除</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="127"/>
<source>Configure...</source>
<translation>設定...</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="147"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../SetGpgSigningDialog.ui" line="154"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>SetRemoteUrlDialog</name>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="14"/>
<source>Set Remote URL</source>
<translation>設定遠端網址</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="20"/>
<source>Current URLs</source>
<translation>目前網址</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="30"/>
<source>New URL</source>
<translation>新網址</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="36"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="91"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="98"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="43"/>
<source>URL</source>
<translation>網址</translation>
</message>
<message>
<location filename="../../SetRemoteUrlDialog.ui" line="63"/>
<source>&amp;Test</source>
<translation>測試(&amp;T)</translation>
</message>
</context>
<context>
<name>SetUserDialog</name>
<message>
<location filename="../../SetUserDialog.ui" line="14"/>
<source>Set User</source>
<translation>設定使用者</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="20"/>
<source>Global</source>
<translation>全域</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="27"/>
<location filename="../../SetUserDialog.cpp" line="31"/>
<source>Repository</source>
<translation>儲存庫</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="42"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="52"/>
<source>Mail</source>
<translation>電子郵件</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="66"/>
<source>Get icon from Gravatar</source>
<translation>從Gravatar獲得圖案</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="122"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../SetUserDialog.ui" line="132"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>SettingBehaviorForm</name>
<message>
<location filename="../../SettingBehaviorForm.ui" line="14"/>
<source>Behavior</source>
<translation>行為</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="20"/>
<location filename="../../SettingBehaviorForm.cpp" line="43"/>
<source>Default working folder</source>
<translation>預設工作資料夾</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="29"/>
<source>Browse...</source>
<translation>瀏覽...</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="39"/>
<source>Automatically fetch when opening the repository</source>
<translation>開啟儲存庫時自動fetch</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="46"/>
<source>Get committer&apos;s icon from gravatar.com</source>
<translation>從Gravatar獲得圖案</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="61"/>
<source>Maximum number of commit item acquisitions</source>
<translation>最大獲取的提交數量</translation>
</message>
<message>
<source>Watch remote changes periodically</source>
<oldsource>Periodically watch remote updates</oldsource>
<translation type="vanished">定期檢查遠端的變更</translation>
</message>
<message>
<source>interval</source>
<oldsource>interval in min:</oldsource>
<translation type="vanished">間隔</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="94"/>
<source>GPG signing policy</source>
<oldsource>GPG Signing Policy</oldsource>
<translation>GPG簽名政策</translation>
</message>
<message>
<location filename="../../SettingBehaviorForm.ui" line="101"/>
<source>Configure...</source>
<translation>設定...</translation>
</message>
<message>
<source>Disable</source>
<translation type="vanished">停用</translation>
</message>
<message>
<source>1 min</source>
<translation type="vanished">1分</translation>
</message>
<message>
<source>%1 mins</source>
<translation type="vanished">%1分</translation>
</message>
</context>
<context>
<name>SettingExampleForm</name>
<message>
<location filename="../../SettingExampleForm.ui" line="14"/>
<source>Example</source>
<translation>範例</translation>
</message>
<message>
<location filename="../../SettingExampleForm.ui" line="20"/>
<source>Underconstruction</source>
<translation>正在施工</translation>
</message>
</context>
<context>
<name>SettingGeneralForm</name>
<message>
<location filename="../../SettingGeneralForm.ui" line="14"/>
<source>General</source>
<translation>一般設定</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="20"/>
<source>Language</source>
<translation>語言</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="31"/>
<source>Change Language...</source>
<translation>選擇語言...</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="54"/>
<source>Change Theme...</source>
<translation>選擇主題...</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="66"/>
<source>Remember and restore window position</source>
<translation>記住並恢復視窗位置</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="73"/>
<source>Enable high DPI scaling</source>
<translation>啟用高解析度螢幕縮放</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="43"/>
<source>Theme</source>
<translation>主題</translation>
</message>
<message>
<source>English</source>
<translation type="vanished">英文</translation>
</message>
<message>
<source>Japanese</source>
<translation type="vanished">日語</translation>
</message>
<message>
<source>Russian</source>
<translation type="vanished">俄文</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="19"/>
<source>Standard</source>
<translation>標準</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="20"/>
<source>Dark</source>
<translation>暗</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="82"/>
<source>Select Language</source>
<translation>選擇語言</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.cpp" line="106"/>
<source>Select Theme</source>
<translation>選擇主題</translation>
</message>
<message>
<location filename="../../SettingGeneralForm.ui" line="93"/>
<source>(Changes are applied at next run)</source>
<translation>(此設定必須下次執行時才會套用)</translation>
</message>
</context>
<context>
<name>SettingNetworkForm</name>
<message>
<location filename="../../SettingNetworkForm.ui" line="14"/>
<source>Network</source>
<translation>網路</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="20"/>
<source>Proxy server</source>
<oldsource>Proxy Server</oldsource>
<translation>代理伺服器</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="26"/>
<source>No proxy</source>
<oldsource>No Proxy</oldsource>
<translation>無代理伺服器</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="33"/>
<source>Auto detect</source>
<oldsource>Auto Detect</oldsource>
<translation>自動偵測</translation>
</message>
<message>
<location filename="../../SettingNetworkForm.ui" line="40"/>
<source>Manual</source>
<translation>手動</translation>
</message>
</context>
<context>
<name>SettingProgramsForm</name>
<message>
<location filename="../../SettingProgramsForm.ui" line="14"/>
<source>Programs</source>
<translation>程式</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="20"/>
<source>Git command</source>
<translation>Git指令</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="29"/>
<location filename="../../SettingProgramsForm.ui" line="48"/>
<location filename="../../SettingProgramsForm.ui" line="67"/>
+ <location filename="../../SettingProgramsForm.ui" line="86"/>
<source>Browse...</source>
<translation>瀏覽...</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="39"/>
<source>File command</source>
<translation>檔案總管指令</translation>
</message>
<message>
<location filename="../../SettingProgramsForm.ui" line="58"/>
<source>GPG command (option)</source>
<translation>GPG指令(可選)</translation>
</message>
+ <message>
+ <location filename="../../SettingProgramsForm.ui" line="77"/>
+ <source>SSH command (option)</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../../SettingsDialog.ui" line="14"/>
<source>Settings</source>
<translation>設定</translation>
</message>
<message>
<location filename="../../SettingsDialog.ui" line="141"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../SettingsDialog.ui" line="148"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
</context>
<context>
<name>TextEditDialog</name>
<message>
<location filename="../../TextEditDialog.ui" line="14"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../../TextEditDialog.ui" line="48"/>
<source>OK</source>
<translation>確定</translation>
</message>
<message>
<location filename="../../TextEditDialog.ui" line="58"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../TextEditDialog.cpp" line="29"/>
<source>&amp;Close</source>
<translation type="unfinished">關閉(&amp;C)</translation>
</message>
<message>
<location filename="../../TextEditDialog.cpp" line="29"/>
<source>Cacnel</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>WelcomeWizardDialog</name>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="14"/>
<source>Welcome to the Guitar Wizard</source>
<translation>歡迎使用Guitar設定精靈</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="45"/>
<source>Helper Tools</source>
<translation>助手工具</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="61"/>
<location filename="../../WelcomeWizardDialog.ui" line="338"/>
<source>git</source>
<translation>git</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="71"/>
<location filename="../../WelcomeWizardDialog.ui" line="88"/>
<location filename="../../WelcomeWizardDialog.ui" line="248"/>
<source>Browse</source>
<translation>瀏覽</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="78"/>
<location filename="../../WelcomeWizardDialog.ui" line="352"/>
<source>file</source>
<translation>檔案</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="120"/>
<source>Global User Information</source>
<translation>全域使用者訊息</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="142"/>
<source>git config --global user.name</source>
<translation>git config --global user.name</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="152"/>
<source>git config --global user.email</source>
<translation>git config --global user.email</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="168"/>
<source>Get icon from Gravatar</source>
<translation>從Gravatar獲得圖案</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="222"/>
<location filename="../../WelcomeWizardDialog.cpp" line="163"/>
<source>Default Working Folder</source>
<translation>預設工作資料夾</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="238"/>
<location filename="../../WelcomeWizardDialog.ui" line="324"/>
<source>folder</source>
<translation>資料夾</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="280"/>
<source>Ready to play the Guitar !</source>
<translation>Guitar已就緒!</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="296"/>
<source>user</source>
<translation>使用者</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="310"/>
<source>email</source>
<translation>電子郵件</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="426"/>
<location filename="../../WelcomeWizardDialog.cpp" line="155"/>
<source>&lt;&lt; Prev</source>
<translation>&lt;&lt; 上一步</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.ui" line="433"/>
<location filename="../../WelcomeWizardDialog.cpp" line="156"/>
<source>Next &gt;&gt;</source>
<translation>下一步 &gt;&gt;</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.cpp" line="126"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../WelcomeWizardDialog.cpp" line="152"/>
<source>Finish</source>
<translation>完成</translation>
</message>
</context>
</TS>
diff --git a/src/unix/UnixPtyProcess.cpp b/src/unix/UnixPtyProcess.cpp
index 351bee9..f43a734 100644
--- a/src/unix/UnixPtyProcess.cpp
+++ b/src/unix/UnixPtyProcess.cpp
@@ -1,248 +1,255 @@
#include "UnixPtyProcess.h"
#include <QDir>
#include <QMutex>
#include <csignal>
#include <cstdlib>
#include <deque>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
namespace {
void make_argv(char *command, std::vector<char *> *out)
{
char *dst = command;
char *src = command;
char *arg = command;
bool quote = false;
bool accept = false;
while (1) {
char c = *src;
if (c == '\"') {
if (src[1] == '\"' && src[2] == '\"') {
*dst++ = c;
src += 3;
} else {
quote = !quote;
accept = true;
src++;
}
} else if (quote && c != 0) {
*dst++ = *src++;
} else if (c == 0 || isspace(c & 0xff)) {
*dst++ = 0;
if (accept || *arg) {
out->push_back(arg);
}
if (c == 0) break;
accept = false;
arg = dst;
src++;
} else {
*dst++ = *src++;
}
}
}
class OutputReaderThread : public QThread {
private:
QMutex *mutex;
int pty_master;
std::deque<char> *output_queue;
std::vector<char> *output_vector;
protected:
void run() override
{
while (1) {
if (isInterruptionRequested()) break;
char buf[1024];
int len = read(pty_master, buf, sizeof(buf));
if (len < 1) break;
{
QMutexLocker lock(mutex);
output_queue->insert(output_queue->end(), buf, buf + len);
output_vector->insert(output_vector->end(), buf, buf + len);
}
}
}
public:
void start(QMutex *mutex, int pty_master, std::deque<char> *outq, std::vector<char> *outv)
{
this->mutex = mutex;
this->pty_master = pty_master;
this->output_queue = outq;
this->output_vector = outv;
QThread::start();
}
};
} // namespace
// UnixPtyProcess
struct UnixPtyProcess::Private {
QMutex mutex;
std::string command;
+ std::string env;
int pty_master;
std::deque<char> output_queue;
std::vector<char> output_vector;
OutputReaderThread th_output_reader;
int exit_code = -1;
};
UnixPtyProcess::UnixPtyProcess()
: m(new Private)
{
}
UnixPtyProcess::~UnixPtyProcess()
{
stop();
delete m;
}
bool UnixPtyProcess::isRunning() const
{
return QThread::isRunning();
}
void UnixPtyProcess::writeInput(char const *ptr, int len)
{
int r = write(m->pty_master, ptr, len);
(void)r;
}
int UnixPtyProcess::readOutput(char *ptr, int len)
{
QMutexLocker lock(&m->mutex);
int n = m->output_queue.size();
if (n > len) {
n = len;
}
if (n > 0) {
auto it = m->output_queue.begin();
std::copy(it, it + n, ptr);
m->output_queue.erase(it, it + n);
}
return n;
}
-void UnixPtyProcess::start(QString const &cmd, QVariant const &userdata)
+void UnixPtyProcess::start(QString const &cmd, QString const &env, QVariant const &userdata)
{
if (isRunning()) return;
m->command = cmd.toStdString();
+ m->env = env.toStdString();
user_data = userdata;
QThread::start();
}
bool UnixPtyProcess::wait(unsigned long time)
{
return QThread::wait(time);
}
void UnixPtyProcess::run()
{
struct termios orig_termios;
struct winsize orig_winsize;
tcgetattr(STDIN_FILENO, &orig_termios);
ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&orig_winsize);
m->pty_master = posix_openpt(O_RDWR);
grantpt(m->pty_master);
unlockpt(m->pty_master);
pid_t pid = fork();
if (pid == 0) {
setsid();
setenv("LANG", "C", 1);
+ if (!m->env.empty()) {
+ char *env = (char *)alloca(m->env.size() + 1);
+ strcpy(env, m->env.c_str());
+ putenv(env);
+ }
char *pts_name = ptsname(m->pty_master);
int pty_slave = open(pts_name, O_RDWR);
close(m->pty_master);
struct termios tio;
memset(&tio, 0, sizeof(tio));
cfmakeraw(&tio);
tio.c_cc[VMIN] = 1;
tio.c_cc[VTIME] = 0;
tio.c_lflag |= ECHO;
tcsetattr(pty_slave, TCSANOW, &tio);
ioctl(pty_slave, TIOCSWINSZ, &orig_winsize);
dup2(pty_slave, STDIN_FILENO);
dup2(pty_slave, STDOUT_FILENO);
dup2(pty_slave, STDERR_FILENO);
close(pty_slave);
QDir::setCurrent(change_dir);
char *command = (char *)alloca(m->command.size() + 1);
strcpy(command, m->command.c_str());
std::vector<char *> argv;
make_argv(command, &argv);
argv.push_back(nullptr);
execvp(argv[0], &argv[0]);
} else {
bool ok = false;
m->th_output_reader.start(&m->mutex, m->pty_master, &m->output_queue, &m->output_vector);
while (1) {
if (isInterruptionRequested()) break;
int status = 0;
int r = waitpid(pid, &status, WNOHANG);
if (r < 0) break;
QThread::msleep(1);
if (r > 0) {
if (WIFEXITED(status)) {
m->exit_code = WEXITSTATUS(status);
ok = true;
break;
}
if (WIFSIGNALED(status)) {
break;
}
}
}
kill(pid, SIGTERM);
m->th_output_reader.requestInterruption();
m->th_output_reader.wait();
close(m->pty_master);
m->pty_master = -1;
emit completed(ok, user_data);
}
}
void UnixPtyProcess::stop()
{
requestInterruption();
wait();
}
int UnixPtyProcess::getExitCode() const
{
return m->exit_code;
}
QString UnixPtyProcess::getMessage() const
{
QString s;
if (!m->output_vector.empty()) {
s = QString::fromUtf8(&m->output_vector[0], m->output_vector.size());
}
return s;
}
void UnixPtyProcess::readResult(std::vector<char> *out)
{
*out = m->output_vector;
m->output_vector.clear();
}
diff --git a/src/unix/UnixPtyProcess.h b/src/unix/UnixPtyProcess.h
index fafaaad..3d8bc4c 100644
--- a/src/unix/UnixPtyProcess.h
+++ b/src/unix/UnixPtyProcess.h
@@ -1,27 +1,27 @@
#ifndef UNIXPTYPROCESS_H
#define UNIXPTYPROCESS_H
#include "AbstractProcess.h"
#include <QThread>
class UnixPtyProcess : public AbstractPtyProcess, public QThread {
private:
struct Private;
Private *m;
protected:
void run() override;
public:
UnixPtyProcess();
~UnixPtyProcess() override;
bool isRunning() const override;
void writeInput(char const *ptr, int len) override;
int readOutput(char *ptr, int len) override;
- void start(QString const &cmd, QVariant const &userdata) override;
+ void start(QString const &cmd, QString const &env, QVariant const &userdata) override;
bool wait(unsigned long time = ULONG_MAX) override;
void stop() override;
int getExitCode() const override;
QString getMessage() const override;
- void readResult(std::vector<char> *out);
+ void readResult(std::vector<char> *out) override;
};
#endif // UNIXPTYPROCESS_H
diff --git a/src/webclient.cpp b/src/webclient.cpp
index 7dae696..b40b3a2 100644
--- a/src/webclient.cpp
+++ b/src/webclient.cpp
@@ -1,1237 +1,1233 @@
#include "webclient.h"
#include <cstring>
#include <cstdint>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
-//#if USE_OPENSSL
-//#pragma comment(lib, "libeay32.lib")
-//#pragma comment(lib, "ssleay32.lib")
-//#endif
typedef SOCKET socket_t;
#pragma warning(disable:4996)
#else
#include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define closesocket(S) ::close(S)
using socket_t = int;
#define INVALID_SOCKET (-1)
#define SOCKET_ERROR (-1)
#define stricmp(A, B) strcasecmp(A, B)
#define strnicmp(A, B, C) strncasecmp(A, B, C)
#endif
#if USE_OPENSSL
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/x509.h>
#else
typedef void SSL;
typedef void SSL_CTX;
#endif
#include <cassert>
#include "charvec.h"
#define USER_AGENT "Generic Web Client"
struct WebContext::Private {
SSL_CTX *ctx = nullptr;
bool use_keep_alive = false;
WebProxy http_proxy;
WebProxy https_proxy;
};
WebClient::URL::URL(std::string const &addr)
{
data.full_request = addr;
char const *str = addr.c_str();
char const *left;
char const *right;
left = str;
right = strstr(left, "://");
if (right) {
data.scheme.assign(str, right - str);
left = right + 3;
}
right = strchr(left, '/');
if (!right) {
right = left + strlen(left);
}
if (right) {
char const *p = strchr(left, ':');
if (p && left < p && p < right) {
int n = 0;
char const *q = p + 1;
while (q < right) {
if (isdigit(*q & 0xff)) {
n = n * 10 + (*q - '0');
} else {
n = -1;
break;
}
q++;
}
data.host.assign(left, p - left);
if (n > 0 && n < 65536) {
data.port = n;
}
} else {
data.host.assign(left, right - left);
}
data.path = right;
}
}
bool WebClient::URL::isssl() const
{
if (scheme() == "https") return true;
if (scheme() == "http") return false;
if (port() == 443) return true;
return false;
}
void WebClientHandler::abort(std::string const &message)
{
throw WebClient::Error(message);
}
struct WebClient::Private {
std::vector<std::string> request_header;
Error error;
WebClient::Response response;
WebContext *webcx;
int crlf_state = 0;
size_t content_offset = 0;
std::string last_host_name;
int last_port = 0;
bool keep_alive = false;
socket_t sock = INVALID_SOCKET;
SSL *ssl = nullptr;
};
WebClient::WebClient(WebContext *webcx)
: m(new Private)
{
assert(webcx);
m->webcx = webcx;
}
WebClient::~WebClient()
{
close();
delete m;
}
void WebClient::initialize()
{
#ifdef _WIN32
WSADATA wsaData;
WORD wVersionRequested;
wVersionRequested = MAKEWORD(1, 1);
WSAStartup(wVersionRequested, &wsaData);
atexit(cleanup);
#endif
#if USE_OPENSSL
OpenSSL_add_all_algorithms();
#endif
}
void WebClient::cleanup()
{
#if USE_OPENSSL
ERR_free_strings();
#endif
#ifdef _WIN32
WSACleanup();
#endif
}
void WebClient::reset()
{
// m->request_header.clear();
m->error = Error();
m->response = Response();
m->crlf_state = 0;
m->content_offset = 0;
}
void WebClient::output_debug_string(char const *str)
{
if (0) {
#ifdef _WIN32
OutputDebugStringA(str);
#else
fwrite(str, 1, strlen(str), stderr);
#endif
}
}
void WebClient::output_debug_strings(std::vector<std::string> const &vec)
{
for (std::string const &s : vec) {
output_debug_string((s + '\n').c_str());
}
}
WebClient::Error const &WebClient::error() const
{
return m->error;
}
void WebClient::clear_error()
{
m->error = Error();
}
int WebClient::get_port(URL const *url, char const *scheme, char const *protocol)
{
int port = url->port();
if (port < 1 || port > 65535) {
struct servent *s;
s = getservbyname(url->scheme().c_str(), protocol);
if (s) {
port = ntohs(s->s_port);
} else {
s = getservbyname(scheme, protocol);
if (s) {
port = ntohs(s->s_port);
}
}
if (port < 1 || port > 65535) {
port = 80;
}
}
return port;
}
static inline std::string to_s(size_t n)
{
char tmp[100];
sprintf(tmp, "%u", (int)n);
return tmp;
}
void WebClient::set_default_header(Request const &req, Post const *post, RequestOption const &opt)
{
std::vector<std::string> header;
auto AddHeader = [&](std::string const &s){
header.push_back(s);
};
AddHeader("Host: " + req.url.host());
AddHeader("User-Agent: " USER_AGENT);
AddHeader("Accept: */*");
if (opt.keep_alive) {
AddHeader("Connection: keep-alive");
} else {
AddHeader("Connection: close");
}
if (post) {
AddHeader("Content-Length: " + to_s(post->data.size()));
std::string ct = "Content-Type: ";
if (post->content_type.empty()) {
ct += CT_APPLICATION_OCTET_STREAM;
} else if (post->content_type == CT_MULTIPART_FORM_DATA) {
ct += post->content_type;
if (!post->boundary.empty()) {
ct += "; boundary=";
ct += post->boundary;
}
} else {
ct += post->content_type;
}
AddHeader(ct);
}
if (req.auth.type == Authorization::Basic) {
std::string s = req.auth.uid + ':' + req.auth.pwd;
AddHeader("Authorization: Basic " + base64_encode(s));
}
// header.insert(header.end(), m->request_header.begin(), m->request_header.end());
header.insert(header.end(), req.headers.begin(), req.headers.end());
m->request_header = std::move(header);
}
std::string WebClient::make_http_request(Request const &req, Post const *post, WebProxy const *proxy, bool https)
{
std::string str;
str = post ? "POST " : "GET ";
if (proxy && !https) {
str += req.url.data.full_request;
str += " HTTP/1.0";
str += "\r\n";
} else {
str += req.url.path();
str += " HTTP/1.0";
str += "\r\n";
}
for (std::string const &s: m->request_header) {
str += s;
str += "\r\n";
}
str += "\r\n";
return str;
}
void WebClient::parse_http_header(char const *begin, char const *end, std::vector<std::string> *header)
{
if (begin < end) {
char const *left = begin;
char const *right = left;
while (1) {
if (right >= end) {
break;
}
if (*right == '\r' || *right == '\n') {
if (left < right) {
header->push_back(std::string(left, right));
}
if (right + 1 < end && *right == '\r' && right[1] == '\n') {
right++;
}
right++;
if (*right == '\r' || *right == '\n') {
if (right + 1 < end && *right == '\r' && right[1] == '\n') {
right++;
}
right++;
left = right;
break;
}
left = right;
} else {
right++;
}
}
}
}
void WebClient::parse_http_header(char const *begin, char const *end, WebClient::Response *out)
{
*out = Response();
parse_http_header(begin, end, &out->header);
parse_header(&out->header, out);
}
static void send_(socket_t s, char const *ptr, int len)
{
while (len > 0) {
int n = send(s, ptr, len, 0);
if (n < 1 || n > len) {
throw WebClient::Error("send request failed.");
}
ptr += n;
len -= n;
}
}
void WebClient::on_end_header(std::vector<char> const *vec, WebClientHandler *handler)
{
if (vec->empty()) return;
char const *begin = &vec->at(0);
char const *end = begin + vec->size();
parse_http_header(begin, end, &m->response);
if (handler) {
handler->checkHeader(this);
}
}
void WebClient::append(char const *ptr, size_t len, std::vector<char> *out, WebClientHandler *handler)
{
size_t offset = out->size();
out->insert(out->end(), ptr, ptr + len);
if (m->crlf_state < 0) {
// nop
} else {
for (size_t i = 0; i < len; i++) {
int c = ptr[i] & 0xff;
if (c == '\r') {
m->crlf_state |= 1;
} else if (c == '\n') {
m->crlf_state |= 1;
m->crlf_state++;
} else {
m->crlf_state = 0;
}
if (m->crlf_state == 4) {
m->content_offset = offset + i + 1;
on_end_header(out, handler);
m->crlf_state = -1;
break;
}
}
}
if (handler && m->content_offset > 0) {
offset = out->size();
if (offset > m->content_offset) {
size_t len = offset - m->content_offset;
char const *ptr = &out->at(m->content_offset);
handler->checkContent(ptr, len);
}
}
}
static char *stristr(char *str1, char const *str2)
{
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
for (size_t i = 0; i + len2 <= len1; i++) {
if (strnicmp(str1 + i, str2, len2) == 0) {
return str1 + i;
}
}
return nullptr;
}
class ResponseHeader {
public:
size_t pos = 0;
std::vector<char> line;
int content_length = -1;
bool connection_keep_alive = false;
bool connection_close = false;
int lf = 0;
enum State {
Header,
Content,
};
State state = Header;
void put(int c)
{
pos++;
if (state == Header) {
if (c== '\r' || c == '\n') {
if (!line.empty()) {
line.push_back(0);
char *begin = &line[0];
char *p = strchr(begin, ':');
if (p && *p == ':') {
*p++ = 0;
auto IS = [&](char const *name){ return stricmp(begin, name) == 0; };
if (IS("content-length")) {
content_length = strtol(p, nullptr, 10);
} else if (IS("connection")) {
if (stristr(p, "keep-alive")) {
connection_keep_alive = true;
} else if (stristr(p, "close")) {
connection_close = true;
}
}
}
line.clear();
}
if (c== '\r') {
return;
}
if (c == '\n') {
lf++;
if (lf == 2) {
state = Content;
}
return;
}
}
lf = 0;
line.push_back(c);
}
}
};
void WebClient::receive_(RequestOption const &opt, std::function<int(char *, int)> const &rcv, std::vector<char> *out)
{
char buf[4096];
size_t pos = 0;
ResponseHeader rh;
while (1) {
int n;
if (rh.state == ResponseHeader::Content && rh.content_length >= 0) {
n = rh.pos + rh.content_length - pos;
if (n > (int)sizeof(buf)) {
n = sizeof(buf);
}
if (n < 1) break;
} else {
n = sizeof(buf);
}
n = rcv(buf, n);
if (n < 1) break;
if (0) { // debug
fwrite(buf, 1, n, stderr);
}
append(buf, n, out, opt.handler);
pos += n;
if (rh.state == ResponseHeader::Header) {
for (int i = 0; i < n; i++) {
rh.put(buf[i]);
if (rh.state == ResponseHeader::Content) {
m->keep_alive = rh.connection_keep_alive && !rh.connection_close;
break;
}
}
}
}
}
namespace {
struct sockaddr_in getinetaddr(char const *name)
{
struct sockaddr_in addr = {};
#if 0
struct hostent *host;
uint32_t a = inet_addr(name);
if (a != INADDR_NONE) {
host = gethostbyaddr((const char *)&a, 4, AF_INET);
} else {
host = gethostbyname(name);
}
if (!host) {
throw WebClient::Error(std::string("gethostbyname failed: ") + name);
}
addr.sin_family = AF_INET;
memcpy((char *)&addr.sin_addr, host->h_addr, host->h_length);
#else
struct addrinfo hints = {};
struct addrinfo *res = nullptr;
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_INET;
getaddrinfo(name, nullptr, &hints, &res);
if (res) {
if (res->ai_family == AF_INET) {
addr = *reinterpret_cast<struct sockaddr_in *>(res->ai_addr);
}
freeaddrinfo(res);
}
if (addr.sin_family == 0) {
throw WebClient::Error(std::string("getaddrinfo failed: ") + name);
}
#endif
return addr;
}
}
bool WebClient::http_get(Request const &request, Post const *post, RequestOption const &opt, std::vector<char> *out)
{
clear_error();
out->clear();
Request server_req;
WebProxy const *proxy = m->webcx->http_proxy();
if (proxy) {
server_req = Request(proxy->server);
} else {
server_req = request;
}
std::string hostname = server_req.url.host();
int port = get_port(&server_req.url, "http", "tcp");
m->keep_alive = opt.keep_alive && hostname == m->last_host_name && port == m->last_port;
if (!m->keep_alive) close();
if (m->sock == INVALID_SOCKET) {
struct sockaddr_in server = getinetaddr(server_req.url.host().c_str());
server.sin_port = htons(port);
m->sock = socket(AF_INET, SOCK_STREAM, 0);
if (m->sock == INVALID_SOCKET) {
throw Error("socket failed.");
}
if (connect(m->sock, (struct sockaddr*) &server, sizeof(server)) == SOCKET_ERROR) {
throw Error("connect failed.");
}
}
m->last_host_name = hostname;
m->last_port = port;
set_default_header(request, post, opt);
std::string req = make_http_request(request, post, proxy, false);
send_(m->sock, req.c_str(), (int)req.size());
if (post && !post->data.empty()) {
send_(m->sock, (char const *)&post->data[0], (int)post->data.size());
}
m->crlf_state = 0;
m->content_offset = 0;
receive_(opt, [&](char *ptr, int len){
return recv(m->sock, ptr, len, 0);
}, out);
if (!m->keep_alive) close();
return true;
}
bool WebClient::https_get(Request const &request_req, Post const *post, RequestOption const &opt, std::vector<char> *out)
{
#if USE_OPENSSL
auto *sslctx = m->webcx->m->ctx;
if (!m->webcx || !sslctx) {
output_debug_string("SSL context is null.\n");
return false;
}
clear_error();
out->clear();
auto get_ssl_error = []()->std::string{
char tmp[1000];
unsigned long e = ERR_get_error();
ERR_error_string_n(e, tmp, sizeof(tmp));
return tmp;
};
Request server_req;
WebProxy const *proxy = m->webcx->https_proxy();
if (proxy) {
server_req = Request(proxy->server);
} else {
server_req = request_req;
}
std::string hostname = server_req.url.host();
int port = get_port(&server_req.url, "https", "tcp");
m->keep_alive = opt.keep_alive && hostname == m->last_host_name && port == m->last_port;
if (!m->keep_alive) close();
socket_t sock = m->sock;
SSL *ssl = m->ssl;
if (sock == INVALID_SOCKET || !ssl) {
struct sockaddr_in server = getinetaddr(server_req.url.host().c_str());
server.sin_port = htons(port);
if (sock == INVALID_SOCKET) {
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
throw Error("socket failed.");
}
if (connect(sock, (struct sockaddr*) &server, sizeof(server)) == SOCKET_ERROR) {
throw Error("connect failed.");
}
}
if (proxy) { // testing
char port[10];
sprintf(port, ":%u", get_port(&request_req.url, "https", "tcp"));
std::string str = "CONNECT ";
str += request_req.url.data.host;
str += port;
str += " HTTP/1.0\r\n\r\n";
send_(sock, str.c_str(), str.size());
char tmp[1000];
int n = recv(sock, tmp, sizeof(tmp), 0);
int i;
for (i = 0; i < n; i++) {
char c = tmp[i];
if (c < 0x20) break;
}
if (i > 0) {
std::string s(tmp, i);
s = "proxy: " + s + '\n';
#ifdef _WIN32
OutputDebugStringA(s.c_str());
#else
fprintf(stderr, "%s", tmp);
#endif
}
}
ssl = SSL_new(sslctx);
if (!ssl) {
throw Error(get_ssl_error());
}
SSL_set_options(ssl, SSL_OP_NO_SSLv2);
SSL_set_options(ssl, SSL_OP_NO_SSLv3);
int ret;
ret = SSL_set_fd(ssl, sock);
if (ret == 0) {
throw Error(get_ssl_error());
}
RAND_poll();
while (RAND_status() == 0) {
unsigned short rand_ret = rand() % 65536;
RAND_seed(&rand_ret, sizeof(rand_ret));
}
ret = SSL_connect(ssl);
if (ret != 1) {
throw Error(get_ssl_error());
}
std::string cipher = SSL_get_cipher(ssl);
cipher += '\n';
output_debug_string(cipher.c_str());
std::string version = SSL_get_cipher_version(ssl);
version += '\n';
output_debug_string(version.c_str());
X509 *x509 = SSL_get_peer_certificate(ssl);
if (x509) {
#ifndef OPENSSL_NO_SHA1
std::string fingerprint;
for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {
if (i > 0) {
fingerprint += ':';
}
char tmp[10];
sprintf(tmp, "%02X", x509->sha1_hash[i]);
fingerprint += tmp;
}
fingerprint += '\n';
output_debug_string(fingerprint.c_str());
#endif
long l = SSL_get_verify_result(ssl);
if (l == X509_V_OK) {
// ok
} else {
// wrong
std::string err = X509_verify_cert_error_string(l);
err += '\n';
output_debug_string(err.c_str());
}
std::vector<std::string> vec;
auto GETSTRINGS = [](X509_NAME *x509name, std::vector<std::string> *out){
out->clear();
if (x509name) {
int n = X509_NAME_entry_count(x509name);
for (int i = 0; i < n; i++) {
X509_NAME_ENTRY *entry = X509_NAME_get_entry(x509name, i);
ASN1_STRING *asn1str = X509_NAME_ENTRY_get_data(entry);
int asn1len = ASN1_STRING_length(asn1str);
unsigned char *p = ASN1_STRING_data(asn1str);
std::string str((char const *)p, asn1len);
out->push_back(str);
}
}
};
X509_NAME *subject = X509_get_subject_name(x509);
GETSTRINGS(subject, &vec);
output_debug_string("--- subject ---\n");
output_debug_strings(vec);
X509_NAME *issuer = X509_get_issuer_name(x509);
GETSTRINGS(issuer, &vec);
output_debug_string("--- issuer ---\n");
output_debug_strings(vec);
ASN1_TIME *not_before = X509_get_notBefore(x509);
ASN1_TIME *not_after = X509_get_notAfter(x509);
(void)not_before;
(void)not_after;
X509_free(x509);
} else {
// wrong
}
}
m->last_host_name = hostname;
m->last_port = port;
set_default_header(request_req, post, opt);
std::string request = make_http_request(request_req, post, proxy, true);
auto SEND = [&](char const *ptr, int len){
while (len > 0) {
int n = SSL_write(ssl, ptr, len);
if (n < 1 || n > len) {
throw WebClient::Error(get_ssl_error());
}
ptr += n;
len -= n;
}
};
SEND(request.c_str(), (int)request.size());
if (post && !post->data.empty()) {
SEND((char const *)&post->data[0], (int)post->data.size());
}
m->crlf_state = 0;
m->content_offset = 0;
receive_(opt, [&](char *ptr, int len){
return SSL_read(ssl, ptr, len);
}, out);
m->sock = sock;
m->ssl = ssl;
if (!m->keep_alive) close();
return true;
#endif
return false;
}
bool WebClient::get(Request const &req, Post const *post, Response *out, WebClientHandler *handler)
{
reset();
try {
if (!m->webcx->m) {
throw Error("WebContext is null.");
}
RequestOption opt;
opt.keep_alive = m->webcx->m->use_keep_alive;
opt.handler = handler;
std::vector<char> res;
if (req.url.isssl()) {
#if USE_OPENSSL
https_get(req, post, opt, &res);
#endif
} else {
http_get(req, post, opt, &res);
}
if (!res.empty()) {
char const *begin = &res[0];
char const *end = begin + res.size();
char const *ptr = begin + m->content_offset;
if (ptr < end) {
out->content.assign(ptr, end);
}
}
return true;
} catch (Error const &e) {
m->error = e;
close();
}
*out = Response();
return false;
}
void WebClient::parse_header(std::vector<std::string> const *header, WebClient::Response *res)
{
if (!header->empty()) {
std::string const &line = header->at(0);
char const *begin = line.c_str();
char const *end = begin + line.size();
if (line.size() > 5 && strncmp(line.c_str(), "HTTP/", 5) == 0) {
int state = 0;
res->version.hi = res->version.lo = res->code = 0;
char const *ptr = begin + 5;
while (1) {
int c = 0;
if (ptr < end) {
c = *ptr & 0xff;
}
switch (state) {
case 0:
if (isdigit(c)) {
res->version.hi = res->version.hi * 10 + (c - '0');
} else if (c == '.') {
state = 1;
} else {
state = -1;
}
break;
case 1:
if (isdigit(c)) {
res->version.lo = res->version.lo * 10 + (c - '0');
} else if (isspace(c)) {
state = 2;
} else {
state = -1;
}
break;
case 2:
if (isspace(c)) {
if (res->code != 0) {
state = -1;
}
} else if (isdigit(c)) {
res->code = res->code * 10 + (c - '0');
} else {
state = -1;
}
break;
default:
state = -1;
break;
}
if (c == 0 || state < 0) {
break;
}
ptr++;
}
}
}
}
std::string WebClient::header_value(std::vector<std::string> const *header, std::string const &name)
{
for (size_t i = 1; i < header->size(); i++) {
std::string const &line = header->at(i);
char const *begin = line.c_str();
char const *end = begin + line.size();
char const *colon = strchr(begin, ':');
if (colon) {
if (strnicmp(begin, name.c_str(), name.size()) == 0) {
char const *ptr = colon + 1;
while (ptr < end && isspace(*ptr & 0xff)) ptr++;
return std::string(ptr, end);
}
}
}
return std::string();
}
std::string WebClient::header_value(std::string const &name) const
{
return header_value(&m->response.header, name);
}
std::string WebClient::content_type() const
{
std::string s = header_value("Content-Type");
char const *begin = s.c_str();
char const *end = begin + s.size();
char const *ptr = begin;
while (ptr < end) {
int c = *ptr & 0xff;
if (c == ';' || c < 0x21) break;
ptr++;
}
if (ptr < end) return std::string(begin, ptr);
return s;
}
size_t WebClient::content_length() const
{
return m->response.content.size();
}
char const *WebClient::content_data() const
{
if (m->response.content.empty()) return "";
return &m->response.content[0];
}
int WebClient::get(Request const &req, WebClientHandler *handler)
{
get(req, nullptr, &m->response, handler);
return m->response.code;
}
int WebClient::post(Request const &req, Post const *post, WebClientHandler *handler)
{
get(req, post, &m->response, handler);
return m->response.code;
}
void WebClient::close()
{
#if USE_OPENSSL
if (m->ssl) {
SSL_shutdown(m->ssl);
SSL_free(m->ssl);
m->ssl = nullptr;
}
#endif
if (m->sock != INVALID_SOCKET) {
shutdown(m->sock, 2); // SD_BOTH or SHUT_RDWR
closesocket(m->sock);
m->sock = INVALID_SOCKET;
}
}
void WebClient::add_header(std::string const &text)
{
m->request_header.push_back(text);
}
WebClient::Response const &WebClient::response() const
{
return m->response;
}
void WebClient::make_application_www_form_urlencoded(char const *begin, char const *end, WebClient::Post *out)
{
*out = WebClient::Post();
out->content_type = CT_APPLICATION_X_WWW_FORM_URLENCODED;
print(&out->data, begin, end - begin);
}
void WebClient::make_multipart_form_data(std::vector<Part> const &parts, WebClient::Post *out, std::string const &boundary)
{
*out = WebClient::Post();
out->content_type = CT_MULTIPART_FORM_DATA;
out->boundary = boundary;
for (Part const &part : parts) {
print(&out->data, "--");
print(&out->data, out->boundary);
print(&out->data, "\r\n");
if (!part.content_disposition.type.empty()) {
ContentDisposition const &cd = part.content_disposition;
std::string s;
s = "Content-Disposition: ";
s += cd.type;
auto Add = [&s](std::string const &name, std::string const &value){
if (!value.empty()) {
s += "; " + name + "=\"";
s += value;
s += '\"';
}
};
Add("name", cd.name);
Add("filename", cd.filename);
print(&out->data, s);
print(&out->data, "\r\n");
}
if (!part.content_type.empty()) {
print(&out->data, "Content-Type: " + part.content_type + "\r\n");
}
if (!part.content_transfer_encoding.empty()) {
print(&out->data, "Content-Transfer-Encoding: " + part.content_transfer_encoding + "\r\n");
}
print(&out->data, "\r\n");
print(&out->data, part.data, part.size);
print(&out->data, "\r\n");
}
print(&out->data, "--");
print(&out->data, out->boundary);
print(&out->data, "--\r\n");
}
void WebClient::make_multipart_form_data(char const *data, size_t size, WebClient::Post *out, std::string const &boundary)
{
Part part;
part.data = data;
part.size = size;
std::vector<Part> parts;
parts.push_back(part);
make_multipart_form_data(parts, out, boundary);
}
//
WebContext::WebContext()
: m(new Private)
{
#if USE_OPENSSL
SSL_load_error_strings();
SSL_library_init();
m->ctx = SSL_CTX_new(SSLv23_client_method());
#endif
}
WebContext::~WebContext()
{
#if USE_OPENSSL
SSL_CTX_free(m->ctx);
#endif
delete m;
}
void WebContext::set_keep_alive_enabled(bool f)
{
m->use_keep_alive = f;
}
void WebContext::set_http_proxy(std::string const &proxy)
{
m->http_proxy = WebProxy();
m->http_proxy.server = proxy;
}
void WebContext::set_https_proxy(std::string const &proxy)
{
m->https_proxy = WebProxy();
m->https_proxy.server = proxy;
}
const WebProxy *WebContext::http_proxy() const
{
if (!m->http_proxy.empty()) {
return &m->http_proxy;
}
return nullptr;
}
const WebProxy *WebContext::https_proxy() const
{
if (!m->https_proxy.empty()) {
return &m->https_proxy;
}
if (!m->http_proxy.empty()) {
return &m->http_proxy;
}
return nullptr;
}
bool WebContext::load_cacert(char const *path)
{
#if USE_OPENSSL
int r = SSL_CTX_load_verify_locations(m->ctx, path, nullptr);
return r == 1;
#else
return false;
#endif
}
//
static unsigned char const PAD = '=';
static const unsigned char _encode_table[] = {
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f,
};
static const unsigned char _decode_table[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
};
static inline unsigned char enc(int c)
{
return _encode_table[c & 63];
}
static inline unsigned char dec(int c)
{
return _decode_table[c & 127];
}
void base64_encode(char const *src, size_t length, std::vector<char> *out)
{
size_t srcpos, dstlen, dstpos;
dstlen = (length + 2) / 3 * 4;
out->resize(dstlen);
if (dstlen == 0) {
return;
}
char *dst = &out->at(0);
dstpos = 0;
for (srcpos = 0; srcpos < length; srcpos += 3) {
int v = (unsigned char)src[srcpos] << 16;
if (srcpos + 1 < length) {
v |= (unsigned char)src[srcpos + 1] << 8;
if (srcpos + 2 < length) {
v |= (unsigned char)src[srcpos + 2];
dst[dstpos + 3] = enc(v);
} else {
dst[dstpos + 3] = PAD;
}
dst[dstpos + 2] = enc(v >> 6);
} else {
dst[dstpos + 2] = PAD;
dst[dstpos + 3] = PAD;
}
dst[dstpos + 1] = enc(v >> 12);
dst[dstpos] = enc(v >> 18);
dstpos += 4;
}
}
void base64_decode(char const *src, size_t length, std::vector<char> *out)
{
unsigned char const *begin = (unsigned char const *)src;
unsigned char const *end = begin + length;
unsigned char const *ptr = begin;
out->clear();
out->reserve(length * 3 / 4);
int count = 0;
int bits = 0;
while (1) {
if (isspace(*ptr)) {
ptr++;
} else {
unsigned char c = 0xff;
if (ptr < end && *ptr < 0x80) {
c = dec(*ptr);
}
if (c < 0x40) {
bits = (bits << 6) | c;
count++;
} else {
if (count < 4) {
bits <<= (4 - count) * 6;
}
c = 0xff;
}
if (count == 4 || c == 0xff) {
if (count >= 2) {
out->push_back(bits >> 16);
if (count >= 3) {
out->push_back(bits >> 8);
if (count == 4) {
out->push_back(bits);
}
}
}
count = 0;
bits = 0;
if (c == 0xff) {
break;
}
}
ptr++;
}
}
}
void base64_encode(std::vector<char> const *src, std::vector<char> *out)
{
base64_encode(&src->at(0), src->size(), out);
}
void base64_decode(std::vector<char> const *src, std::vector<char> *out)
{
base64_decode(&src->at(0), src->size(), out);
}
void base64_encode(char const *src, std::vector<char> *out)
{
base64_encode((char const *)src, strlen(src), out);
}
void base64_decode(char const *src, std::vector<char> *out)
{
base64_decode((char const *)src, strlen(src), out);
}
diff --git a/src/win32/Win32PtyProcess.cpp b/src/win32/Win32PtyProcess.cpp
index fc6a5ef..a0fb5e9 100644
--- a/src/win32/Win32PtyProcess.cpp
+++ b/src/win32/Win32PtyProcess.cpp
@@ -1,264 +1,271 @@
#include <windows.h>
#include "Win32PtyProcess.h"
#include <deque>
#include <winpty.h>
#include <QDir>
#include <QMutex>
namespace {
class OutputReaderThread : public QThread {
friend class ::Win32PtyProcess;
private:
public:
HANDLE handle;
std::deque<char> *output_queue = nullptr;
std::vector<char> *output_vector = nullptr;
protected:
void run() override;
public:
void start(HANDLE hOutput, std::deque<char> *outq, std::vector<char> *outv);
};
void OutputReaderThread::run()
{
char buf[1024];
while (1) {
if (isInterruptionRequested()) break;
DWORD amount = 0;
BOOL ret = ReadFile(handle, buf, sizeof(buf), &amount, nullptr);
if (!ret || amount == 0) {
break;
}
output_queue->insert(output_queue->end(), buf, buf + amount);
output_vector->insert(output_vector->end(), buf, buf + amount);
}
}
void OutputReaderThread::start(HANDLE hOutput, std::deque<char> *outq, std::vector<char> *outv)
{
handle = hOutput;
output_queue = outq;
output_vector = outv;
QThread::start();
}
} // namespace
// Win32PtyProcess
struct Win32PtyProcess::Private {
QMutex mutex;
QString command;
+ QString env;
std::deque<char> output_queue;
std::vector<char> output_vector;
OutputReaderThread th_output_reader;
HANDLE hProcess = INVALID_HANDLE_VALUE;
HANDLE hOutput = INVALID_HANDLE_VALUE;
HANDLE hInput = INVALID_HANDLE_VALUE;
DWORD exit_code = 0;
};
Win32PtyProcess::Win32PtyProcess()
: m(new Private)
{
}
Win32PtyProcess::~Win32PtyProcess()
{
delete m;
}
bool Win32PtyProcess::isRunning() const
{
return QThread::isRunning();
}
QString Win32PtyProcess::getProgram(QString const &cmdline)
{
ushort const *begin = cmdline.utf16();
ushort const *end = begin + cmdline.size();
ushort const *ptr = begin;
bool quote = 0;
while (1) {
ushort c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == '\"') {
if (quote) {
quote = false;
} else {
quote = true;
}
ptr++;
} else if (quote && c != 0) {
ptr++;
} else if (QChar(c).isSpace() || c == 0) {
break;
} else {
ptr++;
}
}
ushort const *left = begin;
ushort const *right = ptr;
if (left + 1 < right) {
if (left[0] == '\"' && right[-1] == '\"') {
left++;
right--;
}
}
return QString::fromUtf16(left, right - left);
}
void Win32PtyProcess::run()
{
QString program;
program = getProgram(m->command);
QString cwd = QDir::currentPath();
QDir::setCurrent(change_dir);
winpty_config_t *agent_cfg = winpty_config_new(WINPTY_FLAG_PLAIN_OUTPUT, nullptr);
winpty_t *pty = winpty_open(agent_cfg, nullptr);
winpty_config_free(agent_cfg);
m->hInput = CreateFileW(winpty_conin_name(pty), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
m->hOutput = CreateFileW(winpty_conout_name(pty), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
m->th_output_reader.start(m->hOutput, &m->output_queue, &m->output_vector);
- winpty_spawn_config_t *spawn_cfg = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, (wchar_t const *)program.utf16(), (wchar_t const *)m->command.utf16(), nullptr, nullptr, nullptr);
+ std::vector<wchar_t> envbuf;
+ if (!m->env.isEmpty()) {
+ envbuf.resize(m->env.size() + 2);
+ memcpy(envbuf.data(), m->env.utf16(), sizeof(wchar_t) * m->env.size());
+ }
+ winpty_spawn_config_t *spawn_cfg = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, (wchar_t const *)program.utf16(), (wchar_t const *)m->command.utf16(), nullptr, envbuf.empty() ? nullptr : envbuf.data(), nullptr);
BOOL spawnSuccess = winpty_spawn(pty, spawn_cfg, &m->hProcess, nullptr, nullptr, nullptr);
bool ok = false;
if (spawnSuccess) {
while (1) {
if (isInterruptionRequested()) break;
GetExitCodeProcess(m->hProcess, &m->exit_code);
if (m->exit_code == STILL_ACTIVE) {
// running
msleep(1);
} else {
ok = true;
break;
}
}
}
// プロセスの出力を確実に取得するため、ここで output reader スレッドの終了を待つ
m->th_output_reader.wait();
winpty_free(pty);
CloseHandle(m->hInput);
CloseHandle(m->hOutput);
CloseHandle(m->hProcess);
m->hInput = INVALID_HANDLE_VALUE;
m->hOutput = INVALID_HANDLE_VALUE;
m->hProcess = INVALID_HANDLE_VALUE;
emit completed(ok, user_data);
QDir::setCurrent(cwd);
}
int Win32PtyProcess::readOutput(char *dstptr, int maxlen)
{
int len = m->output_queue.size();
if (len > maxlen) {
len = maxlen;
}
if (len > 0) {
auto begin = m->output_queue.begin();
std::copy(begin, begin + len, dstptr);
m->output_queue.erase(begin, begin + len);
}
return len;
}
void Win32PtyProcess::writeInput(char const *ptr, int len)
{
char const *begin = ptr;
char const *end = begin + len;
char const *left = begin;
char const *right = begin;
while (1) {
int c = -1;
if (right < end) {
c = *right & 0xff;
}
if (c == '\r' || c == '\n' || c < 0) {
if (left < right) {
DWORD written;
WriteFile(m->hInput, left, right - left, &written, nullptr);
}
if (c < 0) break;
right++;
if (c == '\r') {
if (*right == '\n') {
right++;
}
c = '\r';
} else if (c == '\n') {
c = '\r';
} else {
c = -1;
}
if (c >= 0) {
DWORD written;
WriteFile(m->hInput, &c, 1, &written, nullptr);
}
left = right;
} else {
right++;
}
}
}
-void Win32PtyProcess::start(QString const &cmdline, QVariant const &userdata)
+void Win32PtyProcess::start(QString const &cmdline, QString const &env, QVariant const &userdata)
{
if (isRunning()) return;
m->command = cmdline;
+ m->env = env;
this->user_data = userdata;
QThread::start();
}
bool Win32PtyProcess::wait(unsigned long time)
{
return QThread::wait(time) && m->th_output_reader.wait(time);
}
void Win32PtyProcess::stop()
{
// 標準出力読み出しスレッドを強制終了しないとwinptyプロセスが終了してくれない
m->th_output_reader.terminate();
// プロセススレッド停止
requestInterruption();
wait();
}
int Win32PtyProcess::getExitCode() const
{
return m->exit_code;
}
QString Win32PtyProcess::getMessage() const
{
QString s;
if (!m->output_vector.empty()) {
s = QString::fromUtf8(&m->output_vector[0], m->output_vector.size());
}
return s;
}
void Win32PtyProcess::readResult(std::vector<char> *out)
{
*out = m->output_vector;
m->output_vector.clear();
}
diff --git a/src/win32/Win32PtyProcess.h b/src/win32/Win32PtyProcess.h
index e1c35ef..f4e0419 100644
--- a/src/win32/Win32PtyProcess.h
+++ b/src/win32/Win32PtyProcess.h
@@ -1,35 +1,35 @@
#ifndef WIN32PTYPROCESS_H
#define WIN32PTYPROCESS_H
#include <AbstractProcess.h>
#include <QString>
#include <QThread>
#include <vector>
class Win32PtyProcess : public AbstractPtyProcess, public QThread {
private:
struct Private;
Private *m;
static QString getProgram(QString const &cmdline);
protected:
void run();
public:
Win32PtyProcess();
~Win32PtyProcess() override;
bool isRunning() const override;
int readOutput(char *dstptr, int maxlen) override;
void writeInput(char const *ptr, int len) override;
- void start(QString const &cmdline, QVariant const &userdata) override;
+ void start(QString const &cmdline, const QString &env, QVariant const &userdata) override;
bool wait(unsigned long time = ULONG_MAX) override;
void stop() override;
int getExitCode() const override;
QString getMessage() const override;
void clearResult();
void readResult(std::vector<char> *out) override;
};
#endif // WIN32PTYPROCESS_H
diff --git a/version.rb b/version.rb
index f175119..9f43ae3 100644
--- a/version.rb
+++ b/version.rb
@@ -1,57 +1,70 @@
#!/dev/null
$product_name = "Guitar"
-$copyright_year = 2019
+$copyright_year = 2020
$version_a = 1
-$version_b = 1
+$version_b = 2
$version_c = 999
$version_d = 0
+# Future plans
+#
+# v1.3.0 xxxx-xx-xx
+# - submodule support
+# - word oriented diffs
+#
-# v1.1.1 2019-09-xx
+# History
+#
+# v1.2.0 2020-06-27
+# - SSHキーオーバーライド機能
+# - ウィンドウレイアウト(仮)
+# - 中国語(簡体字)
+#
+# v1.1.1 2019-09-02
# - ダークテーマ描画修正
# - 初回起動時のウェルカムダイアログボックスの改良
# - 小さな修正
#
# v1.1.0 2019-09-01
# - 中国語対応
# - 日本語翻訳ファイルのtypo修正
# - Windows版インストーラー対応
# - ファイルリストのバッジを描画
# - コミットグラフで異なるブランチの線が重なって描画されることがあった問題を修正
# - コミットグラフで先祖のブランチを強調表示する機能
# - ダークテーマの描画を改良
# - Ctrl+HでHEADにジャンプする
# - fetch tags -f コマンド
# - 新マージダイアログ
# - チェリーピックダイアログ
#
# v1.0.0 2019-06-01
# - ロシア語対応
# - 細かい修正
#
# v0.9.3 2019-01-19
# - stashコマンドを実装
# - GitHubウェブページからのドラッグアンドドロップ動作を改良
# - メニューバー項目を整理
#
# v0.9.2 2018-11-06
# - 定期的リモート監視機能
# - ソースコードリファクタリング
# - 安定性向上
# - cmake対応
#
# v0.9.1 2018-11-06
# - support ssh key with passphrase
#
# v0.9.0 2018-10-24
# -
#
# v0.2.0 2017-12-18
# -
#
# v0.1.0 2017-05-03
# -
#
# v0.0.0 2016-12-25
# - alpha release

File Metadata

Mime Type
text/x-diff
Expires
Thu, May 14, 9:42 PM (1 d, 8 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
af/e9/2f49192a7db1bfb35693e5b8c742
Default Alt Text
(1 MB)

Event Timeline