Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F133963
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
162 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/FileDiffWidget.cpp b/src/FileDiffWidget.cpp
index 78d8f40..5de6e1c 100644
--- a/src/FileDiffWidget.cpp
+++ b/src/FileDiffWidget.cpp
@@ -1,744 +1,746 @@
#include <memory>
#include <memory>
#include "FileDiffWidget.h"
#include "ui_FileDiffWidget.h"
#include "BigDiffWindow.h"
#include "GitDiff.h"
#include "common/joinpath.h"
#include "MainWindow.h"
#include "common/misc.h"
#include "ApplicationGlobal.h"
#include "Theme.h"
#include <QBuffer>
#include <QDebug>
#include <QKeyEvent>
#include <QMenu>
#include <QPainter>
#include <QStyle>
#include <QTextCodec>
enum {
DiffIndexRole = Qt::UserRole,
};
struct FileDiffWidget::Private {
BasicMainWindow *mainwindow = nullptr;
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(true);
+ 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)
{
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()
{
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 /*g*/, QString const &id)
{
return mainwindow()->cat_file(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)
{
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);
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);
}
}
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);
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);
if (setupPreviewWidget() == FileViewType::Text) {
TextDiffLineList left_lines;
TextDiffLineList right_lines;
makeSideBySideDiffData(diff, m->original_lines, &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);
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 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);
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());
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表示
} 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 id_left, QString 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());
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()
{
BigDiffWindow win(mainwindow());
win.setWindowState(Qt::WindowMaximized);
win.init(mainwindow(), m->init_param_);
win.exec();
}
-void FileDiffWidget::setFocusAcceptable(bool f)
+void FileDiffWidget::setFocusAcceptable(Qt::FocusPolicy focuspolicy)
{
- Qt::FocusPolicy focuspolicy = f ? Qt::StrongFocus : Qt::NoFocus;
+// 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 11b576b..e9741c6 100644
--- a/src/FileDiffWidget.h
+++ b/src/FileDiffWidget.h
@@ -1,170 +1,170 @@
#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();
};
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(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);
QString diffObjects(GitPtr g, QString const &a_id, QString const &b_id);
BasicMainWindow *mainwindow();
protected:
void resizeEvent(QResizeEvent *) override;
void keyPressEvent(QKeyEvent *event) override;
public:
explicit FileDiffWidget(QWidget *parent = nullptr);
~FileDiffWidget() override;
void bind(BasicMainWindow *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(QString id_left, QString id_right, QString const &path = QString());
void setMaximizeButtonEnabled(bool f);
- void setFocusAcceptable(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/FileDiffWidget.ui b/src/FileDiffWidget.ui
index ff370b4..8530573 100644
--- a/src/FileDiffWidget.ui
+++ b/src/FileDiffWidget.ui
@@ -1,187 +1,198 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileDiffWidget</class>
<widget class="QWidget" name="FileDiffWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>298</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<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="QStackedWidget" name="stackedWidget">
<widget class="QWidget" name="page">
<layout class="QGridLayout" name="gridLayout">
<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>
<property name="spacing">
<number>0</number>
</property>
<item row="1" column="1" colspan="2">
<widget class="QScrollBar" name="horizontalScrollBar">
<property name="maximum">
<number>0</number>
</property>
<property name="pageStep">
<number>0</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QScrollBar" name="verticalScrollBar">
<property name="maximum">
<number>0</number>
</property>
<property name="pageStep">
<number>0</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="MaximizeButton" name="toolButton_fullscreen">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
</widget>
</item>
<item row="0" column="2">
- <widget class="FileViewWidget" name="widget_diff_right" native="true"/>
+ <widget class="FileViewWidget" name="widget_diff_right" native="true">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ </widget>
</item>
<item row="0" column="1">
- <widget class="FileViewWidget" name="widget_diff_left" native="true"/>
+ <widget class="FileViewWidget" name="widget_diff_left" native="true">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ </widget>
</item>
<item row="0" column="0">
<widget class="FileDiffSliderWidget" name="widget_diff_slider" native="true">
<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="focusPolicy">
- <enum>Qt::StrongFocus</enum>
+ <enum>Qt::NoFocus</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="MenuButton" name="toolButton_menu">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileViewWidget</class>
<extends>QWidget</extends>
<header>FileViewWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>FileDiffSliderWidget</class>
<extends>QWidget</extends>
<header>FileDiffSliderWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MaximizeButton</class>
<extends>QToolButton</extends>
<header>MaximizeButton.h</header>
</customwidget>
<customwidget>
<class>MenuButton</class>
<extends>QToolButton</extends>
<header>MenuButton.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/src/FileViewWidget.cpp b/src/FileViewWidget.cpp
index ae199f8..fce6e6a 100644
--- a/src/FileViewWidget.cpp
+++ b/src/FileViewWidget.cpp
@@ -1,171 +1,171 @@
#include "FileViewWidget.h"
#include <QMenu>
#include <QPainter>
#include <QStackedWidget>
#include <QVBoxLayout>
#include "common/misc.h"
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::StrongFocus);
+ 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::StrongFocus);
+ 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 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() const
{
return ui_page_text->latin1Width();
}
int FileViewWidget::lineHeight() const
{
return ui_page_text->lineHeight();
}
void FileViewWidget::setDiffMode(TextEditorEnginePtr 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 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);
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/MainWindow.cpp b/src/MainWindow.cpp
index 048367d..a424a6d 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -1,2603 +1,2582 @@
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "AboutDialog.h"
#include "ApplicationGlobal.h"
#include "AreYouSureYouWantToContinueConnectingDialog.h"
#include "AvatarLoader.h"
#include "BlameWindow.h"
#include "CommitPropertyDialog.h"
#include "DeleteBranchDialog.h"
#include "EditGitIgnoreDialog.h"
#include "EditTagsDialog.h"
#include "FileDiffWidget.h"
#include "GitDiff.h"
#include "JumpDialog.h"
#include "LineEditDialog.h"
#include "MySettings.h"
#include "ReflogWindow.h"
#include "RemoteWatcher.h"
#include "SetGpgSigningDialog.h"
#include "SettingsDialog.h"
#include "StatusLabel.h"
#include "TextEditDialog.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include "CloneFromGitHubDialog.h"
#include "platform.h"
#include "webclient.h"
#include <QClipboard>
#include <QFileDialog>
#include <QFileIconProvider>
#include <QMessageBox>
#include <QMimeData>
#include <QPainter>
#include <QStandardPaths>
#include <QTimer>
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::Private {
bool is_online_mode = true;
QTimer interval_10ms_timer;
QTimer remote_watcher_timer;
QImage graph_color;
QPixmap digits;
StatusLabel *status_bar_label;
QObject *last_focused_file_list = nullptr;
QListWidgetItem *last_selected_file_item = nullptr;
RemoteWatcher remote_watcher;
};
MainWindow::MainWindow(QWidget *parent)
: BasicMainWindow(parent)
, ui(new Ui::MainWindow)
, m(new Private)
{
ui->setupUi(this);
#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);
ui->widget_diff_view->bind(this);
qApp->installEventFilter(this);
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();
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(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, [&](){
ui->lineEdit_remote->setText(currentRemoteName());
});
// リモート監視
connect(this, &BasicMainWindow::signalCheckRemoteUpdate, &m->remote_watcher, &RemoteWatcher::checkRemoteUpdate);
connect(&m->remote_watcher_timer, &QTimer::timeout, &m->remote_watcher, &RemoteWatcher::checkRemoteUpdate);
connect(this, &MainWindow::updateButton, [&](){
doUpdateButton();
});
m->remote_watcher.start(this);
setRemoteMonitoringEnabled(true);
connect(this, &MainWindow::signalSetRemoteChanged, [&](bool f){
setRemoteChanged(f);
updateButton();
});
//
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;
connect(ui->widget_diff_view, &FileDiffWidget::textcodecChanged, [&](){ updateDiffView(); });
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());
// ui->splitter->restoreState(settings.value("SplitterState").toByteArray());
settings.endGroup();
if (maximized) {
state |= Qt::WindowMaximized;
setWindowState(state);
}
}
+ setTabOrder(ui->treeWidget_repos, ui->widget_log);
+// setTabOrder(ui->widget_log, ui->treeWidget_repos);
+
startTimers();
}
MainWindow::~MainWindow()
{
stopPtyProcess();
getAvatarLoader()->stop();
m->remote_watcher.quit();
m->remote_watcher.wait();
delete m;
delete ui;
}
void MainWindow::notifyRemoteChanged(bool f)
{
postUserFunctionEvent([&](QVariant const &v){
setRemoteChanged(v.toBool());
updateButton();
}, QVariant(f));
}
bool MainWindow::shown()
{
while (!misc::isExecutable(appsettings()->git_command) || !misc::isExecutable(appsettings()->file_command)) {
if (!execWelcomeWizardDialog()) {
return false;
}
}
setGitCommand(appsettings()->git_command, false);
setFileCommand(appsettings()->file_command, false);
writeLog(AboutDialog::appVersion() + '\n'); // print application version
logGitVersion(); // print git command version
setGpgCommand(appsettings()->gpg_command, false);
{
MySettings s;
s.beginGroup("Remote");
bool f = s.value("Online", true).toBool();
s.endGroup();
setRemoteOnline(f);
}
setUnknownRepositoryInfo();
checkUser();
return true;
}
void MainWindow::startTimers()
{
// 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);
}
TextEditorThemePtr BasicMainWindow::themeForTextEditor()
{
return global->theme->text_editor_theme;
}
void MainWindow::setCurrentLogRow(int row)
{
if (row >= 0 && row < ui->tableWidget_log->rowCount()) {
ui->tableWidget_log->setCurrentCell(row, 2);
ui->tableWidget_log->setFocus();
updateStatusBarText();
}
}
bool MainWindow::event(QEvent *event)
{
QEvent::Type et = event->type();
if (et == QEvent::WindowActivate) {
checkRemoteUpdate();
} else 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;
}
}
}
return BasicMainWindow::event(event);
}
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_Tab) {
- if (centralWidget()->isAncestorOf(qApp->focusWidget())) {
- QList<QWidget *> tabstops;
- tabstops.push_back(ui->treeWidget_repos);
- tabstops.push_back(ui->tableWidget_log);
- if (ui->stackedWidget->currentWidget() == ui->page_files) {
- tabstops.push_back(ui->listWidget_files);
- } else if (ui->stackedWidget->currentWidget() == ui->page_uncommited) {
- tabstops.push_back(ui->listWidget_unstaged);
- tabstops.push_back(ui->toolButton_select_all);
- tabstops.push_back(ui->toolButton_stage);
- tabstops.push_back(ui->toolButton_unstage);
- tabstops.push_back(ui->toolButton_commit);
- tabstops.push_back(ui->listWidget_staged);
- }
- tabstops.push_back(ui->widget_diff_view);
- int n = tabstops.size();
- if (n > 0) {
- QWidget *f = qApp->focusWidget();
- int i;
- for (i = 0; i < n; i++) {
- if (tabstops[i] == f) {
- break;
- }
- }
- if (i < n) {
- if (e->modifiers() & Qt::ShiftModifier) {
- i = (i + n - 1) % n;
- } else {
- i = (i + 1) % n;
- }
- tabstops[i]->setFocus();
- }
- }
- return true;
- }
- }
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();
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);
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) {
if (k == Qt::Key_Home) {
setCurrentLogRow(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) {
if (k == Qt::Key_Escape) {
ui->tableWidget_log->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);
return true;
}
if (watched == ui->listWidget_staged) {
m->last_focused_file_list = watched;
updateStatusBarText();
updateStagedFileCurrentItem();
+ SelectItem(ui->listWidget_staged);
return true;
}
if (watched == ui->listWidget_files) {
m->last_focused_file_list = watched;
+ SelectItem(ui->listWidget_files);
+ return true;
}
}
return false;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
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);
}
{
MySettings settings;
settings.beginGroup("MainWindow");
settings.setValue("Maximized", maximized);
settings.setValue("Geometry", saveGeometry());
// settings.setValue("SplitterState", ui->splitter->saveState());
settings.endGroup();
}
}
QMainWindow::closeEvent(event);
}
void MainWindow::setStatusBarText(QString const &text)
{
m->status_bar_label->setText(text);
}
void MainWindow::clearStatusBarText()
{
setStatusBarText(QString());
}
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 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 BasicMainWindow::writeLog(QString const &str)
{
std::string s = str.toStdString();
writeLog(s.c_str(), s.size());
}
void BasicMainWindow::writeLog_(QByteArray ba)
{
if (!ba.isEmpty()) {
writeLog(ba.data(), ba.size());
}
}
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;
}
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);
}
QString BasicMainWindow::defaultWorkingDir() const
{
return appsettings()->default_working_dir;
}
QColor MainWindow::color(unsigned int i)
{
unsigned int n = m->graph_color.width();
if (n > 0) {
n--;
if (i > n) i = n;
QRgb const *p = (QRgb const *)m->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;
}
RepositoryItem const *BasicMainWindow::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();
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->setCurrentWidget(ui->page_files);
break;
case FilesListType::SideBySide:
ui->stackedWidget->setCurrentWidget(ui->page_uncommited);
break;
}
}
void MainWindow::clearFileList()
{
showFileList(FilesListType::SingleList);
ui->listWidget_unstaged->clear();
ui->listWidget_staged->clear();
ui->listWidget_files->clear();
}
void MainWindow::clearDiffView()
{
ui->widget_diff_view->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)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (!wait) return;
clearFileList();
Git::FileStatusList stats = g->status();
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(header + filename);
item->setData(FilePathRole, filename);
item->setData(DiffIndexRole, idiff);
item->setData(HunkIndexRole, -1);
switch (files_list_type) {
case FilesListType::SingleList:
ui->listWidget_files->addItem(item);
break;
case FilesListType::SideBySide:
if (staged) {
ui->listWidget_staged->addItem(item);
} else {
ui->listWidget_unstaged->addItem(item);
}
break;
}
};
if (id.isEmpty()) {
bool uncommited = isThereUncommitedChanges();
if (uncommited) {
files_list_type = FilesListType::SideBySide;
}
if (!makeDiff(uncommited ? QString() : id, diffResult())) {
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());
if (it != diffmap.end()) {
idiff = it->second;
}
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);
}
} else {
if (!makeDiff(id, diffResult())) {
return;
}
showFileList(files_list_type);
addDiffItems(diffResult(), AddItem);
}
for (Git::Diff const &diff : *diffResult()) {
QString key = GitDiff::makeKey(diff);
(*getDiffCacheMap())[key] = diff;
}
}
void MainWindow::updateFilesList(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);
}
void MainWindow::updateCurrentFilesList()
{
auto logs = getLogs();
QTableWidgetItem *item = ui->tableWidget_log->item(selectedLogIndex(), 0);
if (!item) return;
int row = item->data(IndexRole).toInt();
int count = (int)logs.size();
if (row < count) {
updateFilesList(logs[row], true);
}
}
void MainWindow::prepareLogTableWidget()
{
QStringList cols = {
tr("Graph"),
tr("Commit"),
tr("Date"),
tr("Author"),
tr("Description"),
};
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::detectGitServerType(GitPtr 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);
}
}
bool BasicMainWindow::fetch(GitPtr g, bool prune)
{
setPtyCondition(PtyCondition::Fetch);
setPtyProcessOk(true);
g->fetch(getPtyProcess(), prune);
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
return getPtyProcessOk();
}
void MainWindow::clearLog()
{
clearLogs();
clearLabelMap();
setUncommitedChanges(false);
ui->tableWidget_log->clearContents();
ui->tableWidget_log->scrollToTop();
}
void MainWindow::openRepository_(GitPtr g)
{
getObjCache()->setup(g);
if (isValidWorkingCopy(g)) {
bool do_fetch = isRemoteOnline() && (getForceFetch() || appsettings()->automatically_fetch_when_opening_the_repository);
setForceFetch(false);
if (do_fetch) {
if (!fetch(g, false)) {
return;
}
}
clearLog();
clearRepositoryInfo();
detectGitServerType(g);
updateFilesList(QString(), true);
bool canceled = false;
ui->tableWidget_log->setEnabled(false);
// ログを取得
setLogs(retrieveCommitLog(g));
// ブランチを取得
queryBranches(g);
// タグを取得
ptrTagMap()->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);
}
ui->tableWidget_log->setEnabled(true);
updateCommitTableLater();
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();
clearRepositoryInfo();
}
updateRemoteInfo();
updateWindowTitle(g);
setHeadId(getObjCache()->revParse("HEAD"));
if (isThereUncommitedChanges()) {
Git::CommitItem item;
item.parent_ids.push_back(currentBranch().id);
item.message = tr("Uncommited changes");
auto p = getLogsPtr();
p->insert(p->begin(), item);
}
prepareLogTableWidget();
auto const &logs = getLogs();
const int count = logs.size();
ui->tableWidget_log->setRowCount(count);
int selrow = -1;
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);
}
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);
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]);
}
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);
}
ui->tableWidget_log->resizeColumnsToContents();
ui->tableWidget_log->horizontalHeader()->setStretchLastSection(false);
ui->tableWidget_log->horizontalHeader()->setStretchLastSection(true);
m->last_focused_file_list = nullptr;
ui->tableWidget_log->setFocus();
setCurrentLogRow(0);
QTableWidgetItem *p = ui->tableWidget_log->item(selrow < 0 ? 0 : selrow, 2);
ui->tableWidget_log->setCurrentItem(p);
m->remote_watcher.setCurrent(currentRemoteName(), currentBranchName());
checkRemoteUpdate();
doUpdateButton();
}
void MainWindow::removeSelectedRepositoryFromBookmark(bool ask)
{
int i = indexOfRepository(ui->treeWidget_repos->currentItem());
removeRepositoryFromBookmark(i, ask);
}
void MainWindow::doUpdateButton()
{
setNetworkingCommandsEnabled(isRemoteOnline());
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);
}
void MainWindow::updateStatusBarText()
{
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);
if (item) {
auto const &logs = getLogs();
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))
;
}
}
}
}
setStatusBarText(text);
}
void MainWindow::on_action_commit_triggered()
{
commit();
}
void MainWindow::on_action_fetch_triggered()
{
if (!isRemoteOnline()) return;
reopenRepository(true, [&](GitPtr g){
fetch(g, false);
});
}
void MainWindow::on_action_fetch_prune_triggered()
{
if (!isRemoteOnline()) return;
reopenRepository(true, [&](GitPtr g){
fetch(g, true);
});
}
void MainWindow::on_action_push_triggered()
{
push();
}
void MainWindow::on_action_pull_triggered()
{
if (!isRemoteOnline()) 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_treeWidget_repos_currentItemChanged(QTreeWidgetItem * /*current*/, QTreeWidgetItem * /*previous*/)
{
updateStatusBarText();
}
void MainWindow::on_treeWidget_repos_itemDoubleClicked(QTreeWidgetItem * /*item*/, int /*column*/)
{
openSelectedRepository();
}
void BasicMainWindow::execCommitPropertyDialog(QWidget *parent, Git::CommitItem const *commit)
{
CommitPropertyDialog dlg(parent, this, 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
QAction *a_open_folder = menu.addAction(tr("Open &folder"));
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);
return;
}
}
}
}
void MainWindow::on_tableWidget_log_customContextMenuRequested(const QPoint &pos)
{
Git::CommitItem const *commit = selectedCommitItem();
if (commit) {
bool is_valid_commit_id = Git::isValidID(commit->commit_id);
int row = selectedLogIndex();
QMenu menu;
QAction *a_copy_id_7_letters = menu.addAction(tr("Copy commit id (7 letters)"));
QAction *a_copy_id_complete = menu.addAction(tr("Copy commit id (completely)"));
menu.addSeparator();
QAction *a_checkout = menu.addAction(tr("Checkout/Branch..."));
menu.addSeparator();
QAction *a_edit_comment = nullptr;
if (row == 0 && currentBranch().ahead > 0) {
a_edit_comment = menu.addAction(tr("Edit comment..."));
}
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;
QAction *a_reset_head = nullptr;
#if 0 // 下手に使うと危険なので、とりあえず無効にしておく
if (is_valid_commit_id && commit->commit_id == head_id_) {
a_reset_head = menu.addAction(tr("Reset HEAD"));
}
#endif
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..."));
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));
if (a) {
if (a == a_copy_id_7_letters) {
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);
return;
}
if (a == a_edit_comment) {
commitAmend();
return;
}
if (a == a_checkout) {
checkout(this, commit);
return;
}
if (a == a_delbranch) {
deleteBranch(commit);
return;
}
if (a == a_delrembranch) {
deleteRemoteBranch(commit);
return;
}
if (a == a_merge) {
mergeBranch(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();
return;
}
if (a == a_explore) {
execCommitExploreWindow(this, commit);
return;
}
if (a == a_reset_head) {
reopenRepository(false, [](GitPtr g){
g->reset_head1();
});
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);
QAction *a = menu.exec(pt);
if (a) {
QListWidgetItem *item = ui->listWidget_files->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);
}
}
}
void MainWindow::on_listWidget_unstaged_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QList<QListWidgetItem *> items = ui->listWidget_unstaged->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);
QAction *a = menu.exec(pt);
if (a) {
QListWidgetItem *item = ui->listWidget_unstaged->currentItem();
if (a == a_stage) {
for_each_selected_files([&](QString const &path){
g->stage(path);
});
updateCurrentFilesList();
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();
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();
}
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);
return;
}
}
}
}
void MainWindow::on_listWidget_staged_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QListWidgetItem *item = ui->listWidget_staged->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);
QAction *a = menu.exec(pt);
if (a) {
QListWidgetItem *item = ui->listWidget_unstaged->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);
}
}
}
}
}
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);
return QStringList();
}
void MainWindow::for_each_selected_files(std::function<void(QString const&)> fn)
{
for (QString path : selectedFiles()) {
fn(path);
}
}
void BasicMainWindow::execFileHistory(QListWidgetItem *item)
{
if (item) {
QString path = getFilePath(item);
if (!path.isEmpty()) {
execFileHistory(path);
}
}
}
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*/)
{
clearFileList();
QTableWidgetItem *item = ui->tableWidget_log->item(selectedLogIndex(), 0);
if (!item) return;
auto const &logs = getLogs();
int row = item->data(IndexRole).toInt();
if (row < (int)logs.size()) {
updateStatusBarText();
*ptrUpdateFilesListCounter() = 200;
}
}
void MainWindow::on_toolButton_stage_clicked()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->stage(selectedFiles());
updateCurrentFilesList();
}
void MainWindow::on_toolButton_unstage_clicked()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->unstage(selectedFiles());
updateCurrentFilesList();
}
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();
}
}
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();
}
}
int MainWindow::selectedLogIndex() const
{
auto const &logs = getLogs();
int i = ui->tableWidget_log->currentRow();
if (i >= 0 && i < (int)logs.size()) {
return i;
}
return -1;
}
void MainWindow::updateDiffView(QListWidgetItem *item)
{
clearDiffView();
m->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();
bool uncommited = (row >= 0 && row < (int)logs.size() && Git::isUncommited(logs[row]));
ui->widget_diff_view->updateDiffView(it->second, uncommited);
}
}
}
void MainWindow::updateDiffView()
{
updateDiffView(m->last_selected_file_item);
}
void MainWindow::updateUnstagedFileCurrentItem()
{
updateDiffView(ui->listWidget_unstaged->currentItem());
}
void MainWindow::updateStagedFileCurrentItem()
{
updateDiffView(ui->listWidget_staged->currentItem());
}
void MainWindow::on_listWidget_unstaged_currentRowChanged(int /*currentRow*/)
{
updateUnstagedFileCurrentItem();
}
void MainWindow::on_listWidget_staged_currentRowChanged(int /*currentRow*/)
{
updateStagedFileCurrentItem();
}
void MainWindow::on_listWidget_files_currentRowChanged(int /*currentRow*/)
{
updateDiffView(ui->listWidget_files->currentItem());
}
void MainWindow::setWatchRemoteInterval(int mins)
{
if (mins > 0) {
m->remote_watcher_timer.start(mins * 60000);
} else {
m->remote_watcher_timer.stop();
}
}
void MainWindow::setRemoteMonitoringEnabled(bool enable)
{
if (enable) {
setWatchRemoteInterval(appsettings()->watch_remote_changes_every_mins);
} else {
setWatchRemoteInterval(0);
}
}
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)
{
if (QApplication::focusWidget() == ui->widget_log) {
int c = event->key();
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)
{
int row = ui->tableWidget_log->currentRow();
internalDeleteTags(tagnames);
ui->tableWidget_log->selectRow(row);
}
void MainWindow::revertCommit()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
Git::CommitItem const *commit = selectedCommitItem();
if (commit) {
g->revert(commit->commit_id);
openRepository(false);
}
}
bool MainWindow::addTag(QString const &name)
{
int row = ui->tableWidget_log->currentRow();
bool ok = internalAddTag(name);
ui->tableWidget_log->selectRow(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();
if (commit) {
execCommitPropertyDialog(this, commit);
}
}
void MainWindow::on_listWidget_unstaged_itemDoubleClicked(QListWidgetItem * item)
{
execFilePropertyDialog(item);
}
void MainWindow::on_listWidget_staged_itemDoubleClicked(QListWidgetItem *item)
{
execFilePropertyDialog(item);
}
void MainWindow::on_listWidget_files_itemDoubleClicked(QListWidgetItem *item)
{
execFilePropertyDialog(item);
}
QListWidgetItem *MainWindow::currentFileItem() const
{
QListWidget *listwidget = nullptr;
if (ui->stackedWidget->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::on_action_window_log_triggered(bool checked)
{
ui->dockWidget_log->setVisible(checked);
}
void MainWindow::on_action_repo_jump_triggered()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
NamedCommitList items = namedCommitItems(Branches | Tags | Remotes);
{
NamedCommitItem head;
head.name = "HEAD";
head.id = getHeadId();
items.push_front(head);
}
JumpDialog dlg(this, items);
if (dlg.exec() == QDialog::Accepted) {
JumpDialog::Action action = dlg.action();
if (action == JumpDialog::Action::BranchsAndTags) {
QString name = dlg.text();
QString id = g->rev_parse(name);
if (g->objectType(id) == "tag") {
id = getObjCache()->getCommitIdFromTag(id);
}
int row = rowFromCommitId(id);
if (row < 0) {
QMessageBox::warning(this, tr("Jump"), QString("%1\n(%2)\n\n").arg(name).arg(id) + tr("That commmit has not foud or not read yet"));
} else {
setCurrentLogRow(row);
if (dlg.isCheckoutChecked()) {
NamedCommitItem item;
for (NamedCommitItem const &t : items) {
if (t.name == name) {
item = t;
break;
}
}
bool ok = false;
if (item.type == NamedCommitItem::Type::Branch) {
ok = g->git(QString("checkout \"%1\"").arg(name), true);
} else if (item.type == NamedCommitItem::Type::Tag) {
ok = g->git(QString("checkout -b \"%1\" \"%2\"").arg(name).arg(id), true);
}
if (ok) {
openRepository(true);
}
}
}
} else if (action == JumpDialog::Action::CommitId) {
QString id = dlg.text();
jumpToCommit(id);
}
}
}
void MainWindow::mergeBranch(Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->mergeBranch(commit->commit_id);
openRepository(true);
}
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;
g->cherrypick(commit->commit_id);
openRepository(true);
}
void MainWindow::on_action_repo_checkout_triggered()
{
checkout();
}
void MainWindow::on_action_delete_branch_triggered()
{
deleteBranch();
}
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());
}
void MainWindow::setNetworkingCommandsEnabled(bool f)
{
ui->action_clone->setEnabled(f);
ui->toolButton_clone->setEnabled(f);
if (!Git::isValidWorkingCopy(currentWorkingCopyDir())) {
f = false;
}
ui->action_fetch->setEnabled(f);
ui->action_fetch_prune->setEnabled(f);
ui->action_pull->setEnabled(f);
ui->action_push->setEnabled(f);
ui->action_push_u->setEnabled(f);
ui->action_push_all_tags->setEnabled(f);
ui->toolButton_fetch->setEnabled(f);
ui->toolButton_pull->setEnabled(f);
ui->toolButton_push->setEnabled(f);
}
bool MainWindow::isRemoteOnline() const
{
return m->is_online_mode;
}
void MainWindow::setRemoteOnline(bool f)
{
m->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);
MySettings s;
s.beginGroup("Remote");
s.setValue("Online", f);
s.endGroup();
}
void MainWindow::on_radioButton_remote_online_clicked()
{
setRemoteOnline(true);
}
void MainWindow::on_radioButton_remote_offline_clicked()
{
setRemoteOnline(false);
}
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());
}
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();
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());
}
void MainWindow::on_action_terminal_triggered()
{
auto const *repo = ¤tRepository();
openTerminal(repo);
}
void MainWindow::on_action_explorer_triggered()
{
auto const *repo = ¤tRepository();
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){
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_test_triggered()
{
}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index ef9cd5b..c849dd3 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -1,215 +1,205 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "BasicMainWindow.h"
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 ImageViewWidget;
friend class FileDiffSliderWidget;
friend class FileHistoryWindow;
friend class FileDiffWidget;
friend class AboutDialog;
private:
struct Private;
Private *m;
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
QPixmap const &digitsPixmap() const;
QString currentWorkingCopyDir() const override;
QColor color(unsigned int i);
bool isRemoteOnline() const override;
private:
Ui::MainWindow *ui;
void updateFilesList(QString id, bool wait) override;
void updateFilesList(Git::CommitItem const &commit, bool wait);
void updateRepositoriesList() override;
void openRepository_(GitPtr g) override;
void prepareLogTableWidget();
QStringList selectedFiles_(QListWidget *listwidget) const;
QStringList selectedFiles() const;
void for_each_selected_files(std::function<void (QString const &)> fn);
// void updateCommitGraph();
void showFileList(FilesListType files_list_type);
void clearLog();
void clearFileList() override;
void clearDiffView();
void clearRepositoryInfo();
int repositoryIndex_(const QTreeWidgetItem *item) const;
RepositoryItem const *repositoryItem(const QTreeWidgetItem *item) const;
int selectedLogIndex() const override;
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();
void setRepositoryInfo(QString const &reponame, QString const &brname) override;
int indexOfRepository(const QTreeWidgetItem *treeitem) const;
void clearRepoFilter();
void appendCharToRepoFilter(ushort c);
void backspaceRepoFilter();
void revertCommit();
void cherrypick(Git::CommitItem const *commit);
void mergeBranch(Git::CommitItem const *commit);
void rebaseBranch(Git::CommitItem const *commit);
void detectGitServerType(GitPtr g);
void setRemoteOnline(bool f);
void startTimers();
void onCloneCompleted(bool success, const QVariant &userdata);
// bool fetch(GitPtr g, bool prune);
void setNetworkingCommandsEnabled(bool f);
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 setWatchRemoteInterval(int mins);
protected:
void dragEnterEvent(QDragEnterEvent *event) 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;
bool shown();
void deleteTags(QStringList const &tagnames) override;
bool addTag(QString const &name);
void updateCurrentFilesList();
void notifyRemoteChanged(bool f);
void postOpenRepositoryFromGitHub(const QString &username, const QString &reponame);
private slots:
void doUpdateButton();
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_fetch_triggered();
+ void on_action_explorer_triggered();
void on_action_fetch_prune_triggered();
+ void on_action_fetch_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_triggered();
void on_action_repository_property_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_push_all_tags_triggered();
+ void on_action_terminal_triggered();
void on_action_test_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_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_terminal_triggered();
-
- void on_action_explorer_triggered();
-
- void on_action_clean_df_triggered();
-
- void on_action_reset_hard_triggered();
-
- void on_action_stash_triggered();
-
- void on_action_stash_apply_triggered();
-
- void on_action_stash_drop_triggered();
-
- void on_action_online_triggered();
-
- void on_action_offline_triggered();
-
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 setRemoteMonitoringEnabled(bool enable) override;
protected slots:
void onLogIdle();
signals:
void signalSetRemoteChanged(bool f);
void onEscapeKeyPressed();
void updateButton();
};
#endif // MAINWINDOW_H
diff --git a/src/MainWindow.ui b/src/MainWindow.ui
index 693b1aa..3c98b7b 100644
--- a/src/MainWindow.ui
+++ b/src/MainWindow.ui
@@ -1,1598 +1,1533 @@
<?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>
</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_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="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="RepositoriesTreeWidget" name="treeWidget_repos">
<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="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="QSplitter" name="splitter_h">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
- <widget class="QStackedWidget" name="stackedWidget_log">
- <property name="currentIndex">
- <number>0</number>
+ <widget class="LogTableWidget" name="tableWidget_log">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
</property>
- <widget class="QWidget" name="page_log_table">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_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>
- <item>
- <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>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="page_log_progress">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>244</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="pushButton">
- <property name="text">
- <string>PushButton</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>243</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
+ <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">
<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="QListWidget" 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::StyledPanel</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="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>
</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="QListWidget" 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="QListWidget" name="listWidget_files">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QFrame" name="frame_6">
<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::StrongFocus</enum>
+ <enum>Qt::ClickFocus</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>848</width>
<height>24</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
<property name="title">
<string>&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>&View</string>
</property>
<addaction name="action_view_refresh"/>
</widget>
<widget class="QMenu" name="menu_Edit">
<property name="title">
<string>&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_edit_settings"/>
</widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
<string>&Help</string>
</property>
<addaction name="action_about"/>
</widget>
<widget class="QMenu" name="menu_Window">
<property name="title">
<string>&Window</string>
</property>
<addaction name="action_window_log"/>
</widget>
<widget class="QMenu" name="menu_Repository">
<property name="title">
<string>&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="action_stash_drop"/>
</widget>
<addaction name="action_commit"/>
<addaction name="separator"/>
<addaction name="action_repo_jump"/>
<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&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>&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>
<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"/>
</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::StrongFocus</enum>
+ <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>&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>&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>&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&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>&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>&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>&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>&Log</string>
</property>
</action>
<action name="action_repo_jump">
<property name="text">
<string>&Jump...</string>
</property>
<property name="shortcut">
<string>Ctrl+J</string>
</property>
</action>
<action name="action_repo_checkout">
<property name="text">
<string>Check&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&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>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<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>FileDiffWidget</class>
<extends>QWidget</extends>
<header location="global">FileDiffWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ReadOnlyLineEdit</class>
<extends>QLineEdit</extends>
<header>ReadOnlyLineEdit.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>treeWidget_repos</tabstop>
- <tabstop>toolButton_erase_filter</tabstop>
<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>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Jun 17, 9:11 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
71011
Default Alt Text
(162 KB)
Attached To
Mode
R77 Guitar
Attached
Detach File
Event Timeline